idf-build-apps 2.5.3__py3-none-any.whl → 2.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  """
@@ -8,7 +8,7 @@ Tools for building ESP-IDF related apps.
8
8
  # ruff: noqa: E402
9
9
  # avoid circular imports
10
10
 
11
- __version__ = '2.5.3'
11
+ __version__ = '2.6.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
idf_build_apps/args.py CHANGED
@@ -27,7 +27,7 @@ from pydantic_settings import (
27
27
  from typing_extensions import Concatenate, ParamSpec
28
28
 
29
29
  from . import SESSION_ARGS, App, setup_logging
30
- from .constants import ALL_TARGETS
30
+ from .constants import ALL_TARGETS, IDF_BUILD_APPS_TOML_FN
31
31
  from .manifest.manifest import FolderRule, Manifest
32
32
  from .utils import InvalidCommand, files_matches_patterns, semicolon_separated_str_to_list, to_absolute_path, to_list
33
33
  from .vendors.pydantic_sources import PyprojectTomlConfigSettingsSource, TomlConfigSettingsSource
@@ -37,6 +37,7 @@ LOGGER = logging.getLogger(__name__)
37
37
 
38
38
  class ValidateMethod(str, enum.Enum):
39
39
  TO_LIST = 'to_list'
40
+ EXPAND_VARS = 'expand_vars'
40
41
 
41
42
 
42
43
  @dataclass
@@ -115,10 +116,11 @@ class BaseArguments(BaseSettings):
115
116
  """Base settings class for all settings classes"""
116
117
 
117
118
  model_config = SettingsConfigDict(
118
- toml_file='.idf_build_apps.toml',
119
+ toml_file=IDF_BUILD_APPS_TOML_FN,
120
+ # these below two are supported in pydantic 2.6
119
121
  pyproject_toml_table_header=('tool', 'idf-build-apps'),
120
122
  pyproject_toml_depth=sys.maxsize,
121
- extra='ignore',
123
+ extra='ignore', # we're supporting pydantic <2.6 as well, so we ignore extra fields
122
124
  )
123
125
 
124
126
  @classmethod
@@ -143,8 +145,15 @@ class BaseArguments(BaseSettings):
143
145
  f = cls.model_fields[info.field_name]
144
146
  meta = get_meta(f)
145
147
  if meta and meta.validate_method:
146
- if ValidateMethod.TO_LIST in meta.validate_method:
147
- return to_list(v)
148
+ for method in meta.validate_method:
149
+ if method == ValidateMethod.TO_LIST:
150
+ v = to_list(v)
151
+ elif method == ValidateMethod.EXPAND_VARS:
152
+ v = os.path.expandvars(v)
153
+ else:
154
+ raise NotImplementedError(f'Unknown validate method: {method}')
155
+
156
+ return v
148
157
 
149
158
  return v
150
159
 
@@ -156,19 +165,19 @@ class GlobalArguments(BaseArguments):
156
165
  action='count',
157
166
  ),
158
167
  description='Verbosity level. By default set to WARNING. Specify -v for INFO, -vv for DEBUG',
159
- default=0,
168
+ default=0, # type: ignore
160
169
  )
161
170
  log_file: t.Optional[str] = field(
162
171
  None,
163
172
  description='Path to the log file, if not specified logs will be printed to stderr',
164
- default=None,
173
+ default=None, # type: ignore
165
174
  )
166
175
  no_color: bool = field(
167
176
  FieldMetadata(
168
177
  action='store_true',
169
178
  ),
170
179
  description='Disable colored output',
171
- default=False,
180
+ default=False, # type: ignore
172
181
  )
173
182
 
174
183
  def model_post_init(self, __context: Any) -> None:
@@ -190,7 +199,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
190
199
  ),
191
200
  description='Path to the manifest files which contains the build test rules of the apps',
192
201
  validation_alias=AliasChoices('manifest_files', 'manifest_file'),
193
- default=None,
202
+ default=None, # type: ignore
194
203
  )
195
204
  manifest_filepatterns: t.Optional[t.List[str]] = field(
196
205
  FieldMetadata(
@@ -199,13 +208,15 @@ class DependencyDrivenBuildArguments(GlobalArguments):
199
208
  ),
200
209
  description='space-separated list of file patterns to search for the manifest files. '
201
210
  'The matched files will be loaded as the manifest files.',
202
- default=None,
211
+ default=None, # type: ignore
203
212
  )
204
213
  manifest_rootpath: str = field(
205
- None,
214
+ FieldMetadata(
215
+ validate_method=[ValidateMethod.EXPAND_VARS],
216
+ ),
206
217
  description='Root path to resolve the relative paths defined in the manifest files. '
207
- 'By default set to the current directory',
208
- default=os.curdir,
218
+ 'By default set to the current directory. Support environment variables.',
219
+ default=os.curdir, # type: ignore
209
220
  )
210
221
  modified_components: t.Optional[t.List[str]] = field(
211
222
  FieldMetadata(
@@ -215,7 +226,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
215
226
  description='semicolon-separated list of modified components. '
216
227
  'If set to "", the value would be considered as None. '
217
228
  'If set to ";", the value would be considered as an empty list.',
218
- default=None,
229
+ default=None, # type: ignore
219
230
  )
220
231
  modified_files: t.Optional[t.List[str]] = field(
221
232
  FieldMetadata(
@@ -225,7 +236,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
225
236
  description='semicolon-separated list of modified files. '
226
237
  'If set to "", the value would be considered as None. '
227
238
  'If set to ";", the value would be considered as an empty list.',
228
- default=None,
239
+ default=None, # type: ignore
229
240
  )
230
241
  deactivate_dependency_driven_build_by_components: t.Optional[t.List[str]] = field(
231
242
  FieldMetadata(
@@ -247,7 +258,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
247
258
  validation_alias=AliasChoices(
248
259
  'deactivate_dependency_driven_build_by_components', 'ignore_app_dependencies_components'
249
260
  ),
250
- default=None,
261
+ default=None, # type: ignore
251
262
  )
252
263
  deactivate_dependency_driven_build_by_filepatterns: t.Optional[t.List[str]] = field(
253
264
  FieldMetadata(
@@ -269,21 +280,21 @@ class DependencyDrivenBuildArguments(GlobalArguments):
269
280
  validation_alias=AliasChoices(
270
281
  'deactivate_dependency_driven_build_by_filepatterns', 'ignore_app_dependencies_filepatterns'
271
282
  ),
272
- default=None,
283
+ default=None, # type: ignore
273
284
  )
274
285
  check_manifest_rules: bool = field(
275
286
  FieldMetadata(
276
287
  action='store_true',
277
288
  ),
278
289
  description='Check if all folders defined in the manifest files exist. Fail if not',
279
- default=False,
290
+ default=False, # type: ignore
280
291
  )
281
292
  compare_manifest_sha_filepath: t.Optional[str] = field(
282
293
  None,
283
294
  description='Path to the file containing the hash of the manifest rules. '
284
295
  'Compare the hash with the current manifest rules. '
285
296
  'All matched apps will be built if the corresponding manifest rule is modified',
286
- default=None,
297
+ default=None, # type: ignore
287
298
  )
288
299
 
289
300
  def model_post_init(self, __context: Any) -> None:
@@ -377,28 +388,28 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
377
388
  nargs='*',
378
389
  ),
379
390
  description='Paths to the directories containing the apps. By default set to the current directory',
380
- default=os.curdir,
391
+ default=os.curdir, # type: ignore
381
392
  )
382
393
  target: str = field(
383
394
  FieldMetadata(
384
395
  shorthand='-t',
385
396
  ),
386
397
  description='Filter the apps by target. By default set to "all"',
387
- default='all',
398
+ default='all', # type: ignore
388
399
  )
389
400
  build_system: t.Union[str, t.Type[App]] = field(
390
401
  FieldMetadata(
391
402
  choices=['cmake', 'make'],
392
403
  ),
393
404
  description='Filter the apps by build system. By default set to "cmake"',
394
- default='cmake',
405
+ default='cmake', # type: ignore
395
406
  )
396
407
  recursive: bool = field(
397
408
  FieldMetadata(
398
409
  action='store_true',
399
410
  ),
400
411
  description='Search for apps recursively under the specified paths',
401
- default=False,
412
+ default=False, # type: ignore
402
413
  )
403
414
  exclude: t.Optional[t.List[str]] = field(
404
415
  FieldMetadata(
@@ -407,20 +418,20 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
407
418
  ),
408
419
  description='Ignore the specified directories while searching recursively',
409
420
  validation_alias=AliasChoices('exclude', 'exclude_list'),
410
- default=None,
421
+ default=None, # type: ignore
411
422
  )
412
423
  work_dir: t.Optional[str] = field(
413
424
  None,
414
425
  description='Copy the app to this directory before building. '
415
426
  'By default set to the app directory. Can expand placeholders',
416
- default=None,
427
+ default=None, # type: ignore
417
428
  )
418
429
  build_dir: str = field(
419
430
  None,
420
431
  description='Build directory for the app. By default set to "build". '
421
432
  'When set to relative path, it will be treated as relative to the app directory. '
422
433
  'Can expand placeholders',
423
- default='build',
434
+ default='build', # type: ignore
424
435
  )
425
436
  build_log_filename: t.Optional[str] = field(
426
437
  FieldMetadata(
@@ -428,7 +439,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
428
439
  ),
429
440
  description='Log filename under the build directory instead of stdout. Can expand placeholders',
430
441
  validation_alias=AliasChoices('build_log_filename', 'build_log'),
431
- default=None,
442
+ default=None, # type: ignore
432
443
  )
433
444
  size_json_filename: t.Optional[str] = field(
434
445
  FieldMetadata(
@@ -436,7 +447,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
436
447
  ),
437
448
  description='`idf.py size` output file under the build directory when specified. ' 'Can expand placeholders',
438
449
  validation_alias=AliasChoices('size_json_filename', 'size_file'),
439
- default=None,
450
+ default=None, # type: ignore
440
451
  )
441
452
  config_rules: t.Optional[t.List[str]] = field(
442
453
  FieldMetadata(
@@ -454,31 +465,31 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
454
465
  'FILEPATTERN is the filename of the sdkconfig file with a single wildcard character (*). '
455
466
  'The NAME is the value matched by the wildcard',
456
467
  validation_alias=AliasChoices('config_rules', 'config_rules_str', 'config'),
457
- default=None,
468
+ default=None, # type: ignore
458
469
  )
459
470
  override_sdkconfig_items: t.Optional[str] = field(
460
471
  None,
461
472
  description='A comma-separated list of key=value pairs to override the sdkconfig items',
462
- default=None,
473
+ default=None, # type: ignore
463
474
  )
464
475
  override_sdkconfig_files: t.Optional[str] = field(
465
476
  None,
466
477
  description='A comma-separated list of sdkconfig files to override the sdkconfig items. '
467
478
  'When set to relative path, it will be treated as relative to the current directory',
468
- default=None,
479
+ default=None, # type: ignore
469
480
  )
470
481
  sdkconfig_defaults: t.Optional[str] = field(
471
482
  None,
472
483
  description='A semicolon-separated list of sdkconfig files passed to `idf.py -DSDKCONFIG_DEFAULTS`. '
473
484
  'SDKCONFIG_DEFAULTS environment variable is used when not specified',
474
- default=os.getenv('SDKCONFIG_DEFAULTS', None),
485
+ default=os.getenv('SDKCONFIG_DEFAULTS', None), # type: ignore
475
486
  )
476
487
  check_warnings: bool = field(
477
488
  FieldMetadata(
478
489
  action='store_true',
479
490
  ),
480
491
  description='Check for warnings in the build output. Fail if any warnings are found',
481
- default=False,
492
+ default=False, # type: ignore
482
493
  )
483
494
  default_build_targets: t.Optional[t.List[str]] = field(
484
495
  FieldMetadata(
@@ -487,7 +498,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
487
498
  ),
488
499
  description='space-separated list of the default enabled build targets for the apps. '
489
500
  'When not specified, the default value is the targets listed by `idf.py --list-targets`',
490
- default=None,
501
+ default=None, # type: ignore
491
502
  )
492
503
  enable_preview_targets: bool = field(
493
504
  FieldMetadata(
@@ -495,28 +506,28 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
495
506
  ),
496
507
  description='When enabled, the default build targets will be set to all apps, '
497
508
  'including the preview targets. As the targets defined in `idf.py --list-targets --preview`',
498
- default=False,
509
+ default=False, # type: ignore
499
510
  )
500
511
  include_skipped_apps: bool = field(
501
512
  FieldMetadata(
502
513
  action='store_true',
503
514
  ),
504
515
  description='Include the skipped apps in the output, together with the enabled ones',
505
- default=False,
516
+ default=False, # type: ignore
506
517
  )
507
518
  include_disabled_apps: bool = field(
508
519
  FieldMetadata(
509
520
  action='store_true',
510
521
  ),
511
522
  description='Include the disabled apps in the output, together with the enabled ones',
512
- default=False,
523
+ default=False, # type: ignore
513
524
  )
514
525
  include_all_apps: bool = field(
515
526
  FieldMetadata(
516
527
  action='store_true',
517
528
  ),
518
529
  description='Include skipped, and disabled apps in the output, together with the enabled ones',
519
- default=False,
530
+ default=False, # type: ignore
520
531
  )
521
532
 
522
533
  def model_post_init(self, __context: Any) -> None:
@@ -557,7 +568,7 @@ class FindArguments(FindBuildArguments):
557
568
  shorthand='-o',
558
569
  ),
559
570
  description='Record the found apps to the specified file instead of stdout',
560
- default=None,
571
+ default=None, # type: ignore
561
572
  )
562
573
  output_format: str = field(
563
574
  FieldMetadata(
@@ -566,7 +577,7 @@ class FindArguments(FindBuildArguments):
566
577
  description='Output format of the found apps. '
567
578
  'In "raw" format, each line is a json string serialized from the app model. '
568
579
  'In "json" format, the output is a json list of the serialized app models',
569
- default='raw',
580
+ default='raw', # type: ignore
570
581
  )
571
582
 
572
583
  def model_post_init(self, __context: Any) -> None:
@@ -587,7 +598,7 @@ class BuildArguments(FindBuildArguments):
587
598
  action='store_true',
588
599
  ),
589
600
  description='Enable verbose output of the build system',
590
- default=False,
601
+ default=False, # type: ignore
591
602
  )
592
603
  parallel_count: int = field(
593
604
  FieldMetadata(
@@ -597,7 +608,7 @@ class BuildArguments(FindBuildArguments):
597
608
  'Specified together with --parallel-index. '
598
609
  'The given apps will be divided into parallel_count parts, '
599
610
  'and the current run will build the parallel_index-th part',
600
- default=1,
611
+ default=1, # type: ignore
601
612
  )
602
613
  parallel_index: int = field(
603
614
  FieldMetadata(
@@ -607,28 +618,28 @@ class BuildArguments(FindBuildArguments):
607
618
  'Specified together with --parallel-count. '
608
619
  'The given apps will be divided into parallel_count parts, '
609
620
  'and the current run will build the parallel_index-th part',
610
- default=1,
621
+ default=1, # type: ignore
611
622
  )
612
623
  dry_run: bool = field(
613
624
  FieldMetadata(
614
625
  action='store_true',
615
626
  ),
616
627
  description='Skip the actual build, only print the build process',
617
- default=False,
628
+ default=False, # type: ignore
618
629
  )
619
630
  keep_going: bool = field(
620
631
  FieldMetadata(
621
632
  action='store_true',
622
633
  ),
623
634
  description='Continue building the next app when the current build fails',
624
- default=False,
635
+ default=False, # type: ignore
625
636
  )
626
637
  no_preserve: bool = field(
627
638
  FieldMetadata(
628
639
  action='store_true',
629
640
  ),
630
641
  description='Do not preserve the build directory after a successful build',
631
- default=False,
642
+ default=False, # type: ignore
632
643
  )
633
644
  ignore_warning_strs: t.Optional[t.List[str]] = field(
634
645
  FieldMetadata(
@@ -641,7 +652,7 @@ class BuildArguments(FindBuildArguments):
641
652
  description='space-separated list of patterns. '
642
653
  'Ignore the warnings in the build output that match the patterns',
643
654
  validation_alias=AliasChoices('ignore_warning_strs', 'ignore_warning_str'),
644
- default=None,
655
+ default=None, # type: ignore
645
656
  )
646
657
  ignore_warning_files: t.Optional[t.List[t.Union[str, TextIOWrapper]]] = field(
647
658
  FieldMetadata(
@@ -656,14 +667,14 @@ class BuildArguments(FindBuildArguments):
656
667
  ),
657
668
  description='Path to the files containing the patterns to ignore the warnings in the build output',
658
669
  validation_alias=AliasChoices('ignore_warning_files', 'ignore_warning_file'),
659
- default=None,
670
+ default=None, # type: ignore
660
671
  )
661
672
  copy_sdkconfig: bool = field(
662
673
  FieldMetadata(
663
674
  action='store_true',
664
675
  ),
665
676
  description='Copy the sdkconfig file to the build directory',
666
- default=False,
677
+ default=False, # type: ignore
667
678
  )
668
679
 
669
680
  # Attrs that support placeholders
@@ -675,7 +686,7 @@ class BuildArguments(FindBuildArguments):
675
686
  description='Record size json filepath of the built apps to the specified file. '
676
687
  'Each line is a json string. Can expand placeholders @p',
677
688
  validation_alias=AliasChoices('collect_size_info_filename', 'collect_size_info'),
678
- default=None,
689
+ default=None, # type: ignore
679
690
  exclude=True, # computed field is used
680
691
  )
681
692
  collect_app_info_filename: t.Optional[str] = field(
@@ -686,7 +697,7 @@ class BuildArguments(FindBuildArguments):
686
697
  description='Record serialized app model of the built apps to the specified file. '
687
698
  'Each line is a json string. Can expand placeholders @p',
688
699
  validation_alias=AliasChoices('collect_app_info_filename', 'collect_app_info'),
689
- default=None,
700
+ default=None, # type: ignore
690
701
  exclude=True, # computed field is used
691
702
  )
692
703
  junitxml_filename: t.Optional[str] = field(
@@ -696,7 +707,7 @@ class BuildArguments(FindBuildArguments):
696
707
  ),
697
708
  description='Path to the junitxml file to record the build results. Can expand placeholder @p',
698
709
  validation_alias=AliasChoices('junitxml_filename', 'junitxml'),
699
- default=None,
710
+ default=None, # type: ignore
700
711
  exclude=True, # computed field is used
701
712
  )
702
713
  # used for expanding placeholders
@@ -753,7 +764,7 @@ class DumpManifestShaArguments(GlobalArguments):
753
764
  required=True,
754
765
  ),
755
766
  description='Path to the manifest files which contains the build test rules of the apps',
756
- default=None,
767
+ default=None, # type: ignore
757
768
  )
758
769
 
759
770
  output: t.Optional[str] = field(
@@ -762,7 +773,7 @@ class DumpManifestShaArguments(GlobalArguments):
762
773
  required=True,
763
774
  ),
764
775
  description='Path to the output file to record the sha256 hash of the manifest rules',
765
- default=None,
776
+ default=None, # type: ignore
766
777
  )
767
778
 
768
779
  def model_post_init(self, __context: Any) -> None:
idf_build_apps/finder.py CHANGED
@@ -50,6 +50,7 @@ def _get_apps_from_path(
50
50
 
51
51
  # for unknown ones, we keep them to the build stage to judge
52
52
  if _app.build_status == BuildStatus.SKIPPED:
53
+ LOGGER.debug('=> Skipped. Reason: %s', _app.build_comment or 'Unknown')
53
54
  return args.include_skipped_apps
54
55
 
55
56
  return True
@@ -28,7 +28,7 @@ def get_processor_name():
28
28
  with open('/proc/cpuinfo') as f:
29
29
  for line in f:
30
30
  if 'model name' in line:
31
- return re.sub('.*model name.*:', '', line, 1).strip()
31
+ return re.sub('.*model name.*:', '', line, count=1).strip()
32
32
  except Exception:
33
33
  pass
34
34
 
@@ -42,7 +42,7 @@ _name = Word(alphas, alphas + nums + '_')
42
42
  # Define value, either a hex, int or a string
43
43
  _hex_value = Combine(Literal('0x') + Word(hexnums) + Optional(_literal_suffix).suppress())('hex_value')
44
44
  _str_value = QuotedString('"')('str_value')
45
- _int_value = Word(nums)('int_value') + ~Char('.') + Optional(_literal_suffix)('literal_suffix')
45
+ _int_value = Combine(Optional('-') + Word(nums))('int_value') + ~Char('.') + Optional(_literal_suffix)('literal_suffix')
46
46
 
47
47
  # Remove optional parenthesis around values
48
48
  _value = Optional('(').suppress() + MatchFirst([_hex_value, _str_value, _int_value])('value') + Optional(')').suppress()
File without changes
@@ -14,6 +14,7 @@ Modifications:
14
14
  - use toml instead of tomli when python < 3.11
15
15
  - stop using global variables
16
16
  - fix some warnings
17
+ - recursively find TOML file.
17
18
  """
18
19
 
19
20
  import os
@@ -25,8 +26,10 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union
25
26
  from pydantic_settings import InitSettingsSource
26
27
  from pydantic_settings.main import BaseSettings
27
28
 
29
+ from idf_build_apps.constants import IDF_BUILD_APPS_TOML_FN
30
+
28
31
  PathType = Union[Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]]
29
- DEFAULT_PATH: PathType = Path('')
32
+ DEFAULT_PATH = Path('')
30
33
 
31
34
 
32
35
  class ConfigFileSourceMixin(ABC):
@@ -43,7 +46,7 @@ class ConfigFileSourceMixin(ABC):
43
46
  return kwargs
44
47
 
45
48
  @abstractmethod
46
- def _read_file(self, path: Path) -> Dict[str, Any]:
49
+ def _read_file(self, path: Optional[Path]) -> Dict[str, Any]:
47
50
  pass
48
51
 
49
52
 
@@ -55,24 +58,59 @@ class TomlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
55
58
  def __init__(
56
59
  self,
57
60
  settings_cls: Type[BaseSettings],
58
- toml_file: Optional[PathType] = DEFAULT_PATH,
61
+ toml_file: Optional[Path] = DEFAULT_PATH,
59
62
  ):
60
- self.toml_file_path = toml_file if toml_file != DEFAULT_PATH else settings_cls.model_config.get('toml_file')
63
+ self.toml_file_path = self._pick_toml_file(
64
+ toml_file,
65
+ settings_cls.model_config.get('pyproject_toml_depth', sys.maxsize),
66
+ IDF_BUILD_APPS_TOML_FN,
67
+ )
61
68
  self.toml_data = self._read_files(self.toml_file_path)
62
69
  super().__init__(settings_cls, self.toml_data)
63
70
 
64
- def _read_file(self, file_path: Path) -> Dict[str, Any]:
71
+ def _read_file(self, path: Optional[Path]) -> Dict[str, Any]:
72
+ if not path or not path.is_file():
73
+ return {}
74
+
65
75
  if sys.version_info < (3, 11):
66
76
  import toml
67
77
 
68
- with open(file_path) as toml_file:
78
+ with open(path) as toml_file:
69
79
  return toml.load(toml_file)
70
80
  else:
71
81
  import tomllib
72
82
 
73
- with open(file_path, 'rb') as toml_file:
83
+ with open(path, 'rb') as toml_file:
74
84
  return tomllib.load(toml_file)
75
85
 
86
+ @staticmethod
87
+ def _pick_toml_file(provided: Optional[Path], depth: int, filename: str) -> Optional[Path]:
88
+ """
89
+ Pick a file path to use. If a file path is provided, use it. Otherwise, search up the directory tree for a
90
+ file with the given name.
91
+
92
+ :param provided: Explicit path provided when instantiating this class.
93
+ :param depth: Number of directories up the tree to check of a pyproject.toml.
94
+ """
95
+ if provided and Path(provided).is_file():
96
+ return provided.resolve()
97
+
98
+ rv = Path.cwd()
99
+ count = -1
100
+ while count < depth:
101
+ if str(rv) == rv.root:
102
+ break
103
+
104
+ fp = rv / filename
105
+ if fp.is_file():
106
+ print(f'Loading config file: {fp}')
107
+ return fp
108
+
109
+ rv = rv.parent
110
+ count += 1
111
+
112
+ return None
113
+
76
114
 
77
115
  class PyprojectTomlConfigSettingsSource(TomlConfigSettingsSource):
78
116
  """
@@ -84,37 +122,16 @@ class PyprojectTomlConfigSettingsSource(TomlConfigSettingsSource):
84
122
  settings_cls: Type[BaseSettings],
85
123
  toml_file: Optional[Path] = None,
86
124
  ) -> None:
87
- self.toml_file_path = self._pick_pyproject_toml_file(
88
- toml_file, settings_cls.model_config.get('pyproject_toml_depth', 0)
125
+ self.toml_file_path = self._pick_toml_file(
126
+ toml_file,
127
+ settings_cls.model_config.get('pyproject_toml_depth', sys.maxsize),
128
+ 'pyproject.toml',
89
129
  )
90
130
  self.toml_table_header: Tuple[str, ...] = settings_cls.model_config.get(
91
- 'pyproject_toml_table_header', ('tool', 'pydantic-settings')
131
+ 'pyproject_toml_table_header',
132
+ ('tool', 'idf-build-apps'),
92
133
  )
93
134
  self.toml_data = self._read_files(self.toml_file_path)
94
135
  for key in self.toml_table_header:
95
136
  self.toml_data = self.toml_data.get(key, {})
96
137
  super(TomlConfigSettingsSource, self).__init__(settings_cls, self.toml_data)
97
-
98
- @staticmethod
99
- def _pick_pyproject_toml_file(provided: Optional[Path], depth: int) -> Path:
100
- """Pick a `pyproject.toml` file path to use.
101
-
102
- Args:
103
- provided: Explicit path provided when instantiating this class.
104
- depth: Number of directories up the tree to check of a pyproject.toml.
105
-
106
- """
107
- if provided:
108
- return provided.resolve()
109
- rv = Path.cwd() / 'pyproject.toml'
110
- count = 0
111
- if not rv.is_file():
112
- child = rv.parent.parent / 'pyproject.toml'
113
- while count < depth:
114
- if child.is_file():
115
- return child
116
- if str(child.parent) == rv.root:
117
- break # end discovery after checking system root once
118
- child = child.parent.parent / 'pyproject.toml'
119
- count += 1
120
- return rv
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: idf-build-apps
3
- Version: 2.5.3
3
+ Version: 2.6.0
4
4
  Summary: Tools for building ESP-IDF related apps.
5
5
  Author-email: Fu Hanxi <fuhanxi@espressif.com>
6
6
  Requires-Python: >=3.7
@@ -27,9 +27,10 @@ Requires-Dist: sphinx_copybutton ; extra == "doc"
27
27
  Requires-Dist: myst-parser ; extra == "doc"
28
28
  Requires-Dist: sphinxcontrib-mermaid ; extra == "doc"
29
29
  Requires-Dist: sphinx-argparse ; extra == "doc"
30
+ Requires-Dist: sphinx-tabs ; extra == "doc"
30
31
  Requires-Dist: pytest ; extra == "test"
31
32
  Requires-Dist: pytest-cov ; extra == "test"
32
- Project-URL: changelog, https://github.com/espressif/idf-build-apps/blob/master/CHANGELOG.md
33
+ Project-URL: changelog, https://github.com/espressif/idf-build-apps/blob/main/CHANGELOG.md
33
34
  Project-URL: documentation, https://docs.espressif.com/projects/idf-build-apps
34
35
  Project-URL: homepage, https://github.com/espressif/idf-build-apps
35
36
  Project-URL: repository, https://github.com/espressif/idf-build-apps
@@ -1,27 +1,28 @@
1
- idf_build_apps/__init__.py,sha256=NC4Y3XHI515eQBX8WScygPjZaCgogb4zOZjjg_UEyaY,650
1
+ idf_build_apps/__init__.py,sha256=VMMqqRp30Golk7dqGCARNL8mR9oZaz86HOXoySoXvIY,650
2
2
  idf_build_apps/__main__.py,sha256=8E-5xHm2MlRun0L88XJleNh5U50dpE0Q1nK5KqomA7I,182
3
3
  idf_build_apps/app.py,sha256=F-MKOsaz7cJ0H2wsEE4gpO4kkkEdkyFmIZBHDoM2qgs,37359
4
- idf_build_apps/args.py,sha256=3lSzUZIkfJzzmhCJ8GICtKQQ1iY-dDotIOmmqwwUrF8,32762
4
+ idf_build_apps/args.py,sha256=7-rphv1hhbjJRfE_agBUyQbJaiUhsjsBo6C3eDmug2k,34101
5
5
  idf_build_apps/autocompletions.py,sha256=g-bx0pzXoFKI0VQqftkHyGVWN6MLjuFOdozeuAf45yo,2138
6
6
  idf_build_apps/constants.py,sha256=07ve2FtWuLBuc_6LFzbs1XncB1VNi9HJUqGjQQauRNM,3952
7
- idf_build_apps/finder.py,sha256=Wv0QYArXmVZZDslLxfPwGgt202s94112AlAHk0s3N1g,5710
7
+ idf_build_apps/finder.py,sha256=hY6uSMB2s65MqMKIDBSHABOfa93mOLT7x5hlMxC43EQ,5794
8
8
  idf_build_apps/log.py,sha256=pyvT7N4MWzGjIXph5mThQCGBiSt53RNPW0WrFfLr0Kw,2650
9
9
  idf_build_apps/main.py,sha256=Z_hetbOavgCJZQPaP01_jx57fR1w0DefiFz0J2cCwp0,16498
10
+ idf_build_apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  idf_build_apps/session_args.py,sha256=2WDTy40IFAc0KQ57HaeBcYj_k10eUXRKkDOWLrFCaHY,2985
11
12
  idf_build_apps/utils.py,sha256=s4D8P7QA17XcaCUQ_EoiNOW_VpU3cPQgiZVV9KQ8I30,10171
12
13
  idf_build_apps/junit/__init__.py,sha256=IxvdaS6eSXp7kZxRuXqyZyGxuA_A1nOW1jF1HMi8Gns,231
13
14
  idf_build_apps/junit/report.py,sha256=T7dVU3Sz5tqjfbcFW7wjsb65PDH6C2HFf73ePJqBhMs,6555
14
- idf_build_apps/junit/utils.py,sha256=j0PYhFTZjXtTwkENdeL4bFJcX24ktf1CsOOVXz65yNo,1297
15
+ idf_build_apps/junit/utils.py,sha256=NXZxQD4tdbSVKjKMNx1kO2H3IoEiysXkDoDjLEf1RO8,1303
15
16
  idf_build_apps/manifest/__init__.py,sha256=Q2-cb3ngNjnl6_zWhUfzZZB10f_-Rv2JYNck3Lk7UkQ,133
16
17
  idf_build_apps/manifest/if_parser.py,sha256=AAEyYPgcBWL2ToCmcvhx3fl8ebYdC5-LMvvDkYtWn8o,6546
17
18
  idf_build_apps/manifest/manifest.py,sha256=6F6Nt93eaoBOHlGHZtv2hHT8Zw0HmJf0d_MESlgye6M,14478
18
- idf_build_apps/manifest/soc_header.py,sha256=PzJ37xFspt5f0AXWvAFNA_avHZA9fMXHBrwDYLi3qEI,4344
19
+ idf_build_apps/manifest/soc_header.py,sha256=8vlsAjNsTxxb85RFJxVJTe11ealNQ8n5abUdNuC9ULo,4369
19
20
  idf_build_apps/vendors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- idf_build_apps/vendors/pydantic_sources.py,sha256=2IN6opo6qjCwaqhERFbgA4PtEwqKaTtEkccy5_fYAT0,4130
21
+ idf_build_apps/vendors/pydantic_sources.py,sha256=2e-zA_aHe7NVXMoPKU_0Z9Lg9lw80TFWQ-2PZf10FMs,4419
21
22
  idf_build_apps/yaml/__init__.py,sha256=W-3z5no07RQ6eYKGyOAPA8Z2CLiMPob8DD91I4URjrA,162
22
23
  idf_build_apps/yaml/parser.py,sha256=b3LvogO6do-eJPRsYzT-8xk8AT2MnXpLCzQutJqyC7M,2128
23
- idf_build_apps-2.5.3.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
24
- idf_build_apps-2.5.3.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
- idf_build_apps-2.5.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
26
- idf_build_apps-2.5.3.dist-info/METADATA,sha256=XZLgJfyaJgEl6rULmN0zIeG9quKVH9EhuigO1hnQOvk,4610
27
- idf_build_apps-2.5.3.dist-info/RECORD,,
24
+ idf_build_apps-2.6.0.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
25
+ idf_build_apps-2.6.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
26
+ idf_build_apps-2.6.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
27
+ idf_build_apps-2.6.0.dist-info/METADATA,sha256=StRvId-xGrePTaC0FjO9O58mazLMSD0EZGrPDSOOxvs,4652
28
+ idf_build_apps-2.6.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.9.0
2
+ Generator: flit 3.10.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any