idf-build-apps 2.5.2__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.2'
11
+ __version__ = '2.6.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
idf_build_apps/args.py CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import argparse
5
5
  import enum
6
+ import glob
6
7
  import inspect
7
8
  import logging
8
9
  import os
@@ -26,7 +27,7 @@ from pydantic_settings import (
26
27
  from typing_extensions import Concatenate, ParamSpec
27
28
 
28
29
  from . import SESSION_ARGS, App, setup_logging
29
- from .constants import ALL_TARGETS
30
+ from .constants import ALL_TARGETS, IDF_BUILD_APPS_TOML_FN
30
31
  from .manifest.manifest import FolderRule, Manifest
31
32
  from .utils import InvalidCommand, files_matches_patterns, semicolon_separated_str_to_list, to_absolute_path, to_list
32
33
  from .vendors.pydantic_sources import PyprojectTomlConfigSettingsSource, TomlConfigSettingsSource
@@ -36,6 +37,7 @@ LOGGER = logging.getLogger(__name__)
36
37
 
37
38
  class ValidateMethod(str, enum.Enum):
38
39
  TO_LIST = 'to_list'
40
+ EXPAND_VARS = 'expand_vars'
39
41
 
40
42
 
41
43
  @dataclass
@@ -114,10 +116,11 @@ class BaseArguments(BaseSettings):
114
116
  """Base settings class for all settings classes"""
115
117
 
116
118
  model_config = SettingsConfigDict(
117
- toml_file='.idf_build_apps.toml',
119
+ toml_file=IDF_BUILD_APPS_TOML_FN,
120
+ # these below two are supported in pydantic 2.6
118
121
  pyproject_toml_table_header=('tool', 'idf-build-apps'),
119
122
  pyproject_toml_depth=sys.maxsize,
120
- extra='ignore',
123
+ extra='ignore', # we're supporting pydantic <2.6 as well, so we ignore extra fields
121
124
  )
122
125
 
123
126
  @classmethod
@@ -142,8 +145,15 @@ class BaseArguments(BaseSettings):
142
145
  f = cls.model_fields[info.field_name]
143
146
  meta = get_meta(f)
144
147
  if meta and meta.validate_method:
145
- if ValidateMethod.TO_LIST in meta.validate_method:
146
- 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
147
157
 
148
158
  return v
149
159
 
@@ -155,19 +165,19 @@ class GlobalArguments(BaseArguments):
155
165
  action='count',
156
166
  ),
157
167
  description='Verbosity level. By default set to WARNING. Specify -v for INFO, -vv for DEBUG',
158
- default=0,
168
+ default=0, # type: ignore
159
169
  )
160
170
  log_file: t.Optional[str] = field(
161
171
  None,
162
172
  description='Path to the log file, if not specified logs will be printed to stderr',
163
- default=None,
173
+ default=None, # type: ignore
164
174
  )
165
175
  no_color: bool = field(
166
176
  FieldMetadata(
167
177
  action='store_true',
168
178
  ),
169
179
  description='Disable colored output',
170
- default=False,
180
+ default=False, # type: ignore
171
181
  )
172
182
 
173
183
  def model_post_init(self, __context: Any) -> None:
@@ -189,13 +199,24 @@ class DependencyDrivenBuildArguments(GlobalArguments):
189
199
  ),
190
200
  description='Path to the manifest files which contains the build test rules of the apps',
191
201
  validation_alias=AliasChoices('manifest_files', 'manifest_file'),
192
- default=None,
202
+ default=None, # type: ignore
203
+ )
204
+ manifest_filepatterns: t.Optional[t.List[str]] = field(
205
+ FieldMetadata(
206
+ validate_method=[ValidateMethod.TO_LIST],
207
+ nargs='+',
208
+ ),
209
+ description='space-separated list of file patterns to search for the manifest files. '
210
+ 'The matched files will be loaded as the manifest files.',
211
+ default=None, # type: ignore
193
212
  )
194
213
  manifest_rootpath: str = field(
195
- None,
214
+ FieldMetadata(
215
+ validate_method=[ValidateMethod.EXPAND_VARS],
216
+ ),
196
217
  description='Root path to resolve the relative paths defined in the manifest files. '
197
- 'By default set to the current directory',
198
- default=os.curdir,
218
+ 'By default set to the current directory. Support environment variables.',
219
+ default=os.curdir, # type: ignore
199
220
  )
200
221
  modified_components: t.Optional[t.List[str]] = field(
201
222
  FieldMetadata(
@@ -205,7 +226,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
205
226
  description='semicolon-separated list of modified components. '
206
227
  'If set to "", the value would be considered as None. '
207
228
  'If set to ";", the value would be considered as an empty list.',
208
- default=None,
229
+ default=None, # type: ignore
209
230
  )
210
231
  modified_files: t.Optional[t.List[str]] = field(
211
232
  FieldMetadata(
@@ -215,7 +236,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
215
236
  description='semicolon-separated list of modified files. '
216
237
  'If set to "", the value would be considered as None. '
217
238
  'If set to ";", the value would be considered as an empty list.',
218
- default=None,
239
+ default=None, # type: ignore
219
240
  )
220
241
  deactivate_dependency_driven_build_by_components: t.Optional[t.List[str]] = field(
221
242
  FieldMetadata(
@@ -237,7 +258,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
237
258
  validation_alias=AliasChoices(
238
259
  'deactivate_dependency_driven_build_by_components', 'ignore_app_dependencies_components'
239
260
  ),
240
- default=None,
261
+ default=None, # type: ignore
241
262
  )
242
263
  deactivate_dependency_driven_build_by_filepatterns: t.Optional[t.List[str]] = field(
243
264
  FieldMetadata(
@@ -259,26 +280,37 @@ class DependencyDrivenBuildArguments(GlobalArguments):
259
280
  validation_alias=AliasChoices(
260
281
  'deactivate_dependency_driven_build_by_filepatterns', 'ignore_app_dependencies_filepatterns'
261
282
  ),
262
- default=None,
283
+ default=None, # type: ignore
263
284
  )
264
285
  check_manifest_rules: bool = field(
265
286
  FieldMetadata(
266
287
  action='store_true',
267
288
  ),
268
289
  description='Check if all folders defined in the manifest files exist. Fail if not',
269
- default=False,
290
+ default=False, # type: ignore
270
291
  )
271
292
  compare_manifest_sha_filepath: t.Optional[str] = field(
272
293
  None,
273
294
  description='Path to the file containing the hash of the manifest rules. '
274
295
  'Compare the hash with the current manifest rules. '
275
296
  'All matched apps will be built if the corresponding manifest rule is modified',
276
- default=None,
297
+ default=None, # type: ignore
277
298
  )
278
299
 
279
300
  def model_post_init(self, __context: Any) -> None:
280
301
  super().model_post_init(__context)
281
302
 
303
+ if self.manifest_filepatterns:
304
+ matched_paths = set()
305
+ for pat in [to_absolute_path(p, self.manifest_rootpath) for p in self.manifest_filepatterns]:
306
+ matched_paths.update(glob.glob(str(pat), recursive=True))
307
+
308
+ if matched_paths:
309
+ if self.manifest_files:
310
+ self.manifest_files.extend(matched_paths)
311
+ else:
312
+ self.manifest_files = list(matched_paths)
313
+
282
314
  Manifest.CHECK_MANIFEST_RULES = self.check_manifest_rules
283
315
  if self.manifest_files:
284
316
  App.MANIFEST = Manifest.from_files(
@@ -356,28 +388,28 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
356
388
  nargs='*',
357
389
  ),
358
390
  description='Paths to the directories containing the apps. By default set to the current directory',
359
- default=os.curdir,
391
+ default=os.curdir, # type: ignore
360
392
  )
361
393
  target: str = field(
362
394
  FieldMetadata(
363
395
  shorthand='-t',
364
396
  ),
365
397
  description='Filter the apps by target. By default set to "all"',
366
- default='all',
398
+ default='all', # type: ignore
367
399
  )
368
400
  build_system: t.Union[str, t.Type[App]] = field(
369
401
  FieldMetadata(
370
402
  choices=['cmake', 'make'],
371
403
  ),
372
404
  description='Filter the apps by build system. By default set to "cmake"',
373
- default='cmake',
405
+ default='cmake', # type: ignore
374
406
  )
375
407
  recursive: bool = field(
376
408
  FieldMetadata(
377
409
  action='store_true',
378
410
  ),
379
411
  description='Search for apps recursively under the specified paths',
380
- default=False,
412
+ default=False, # type: ignore
381
413
  )
382
414
  exclude: t.Optional[t.List[str]] = field(
383
415
  FieldMetadata(
@@ -386,20 +418,20 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
386
418
  ),
387
419
  description='Ignore the specified directories while searching recursively',
388
420
  validation_alias=AliasChoices('exclude', 'exclude_list'),
389
- default=None,
421
+ default=None, # type: ignore
390
422
  )
391
423
  work_dir: t.Optional[str] = field(
392
424
  None,
393
425
  description='Copy the app to this directory before building. '
394
426
  'By default set to the app directory. Can expand placeholders',
395
- default=None,
427
+ default=None, # type: ignore
396
428
  )
397
429
  build_dir: str = field(
398
430
  None,
399
431
  description='Build directory for the app. By default set to "build". '
400
432
  'When set to relative path, it will be treated as relative to the app directory. '
401
433
  'Can expand placeholders',
402
- default='build',
434
+ default='build', # type: ignore
403
435
  )
404
436
  build_log_filename: t.Optional[str] = field(
405
437
  FieldMetadata(
@@ -407,7 +439,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
407
439
  ),
408
440
  description='Log filename under the build directory instead of stdout. Can expand placeholders',
409
441
  validation_alias=AliasChoices('build_log_filename', 'build_log'),
410
- default=None,
442
+ default=None, # type: ignore
411
443
  )
412
444
  size_json_filename: t.Optional[str] = field(
413
445
  FieldMetadata(
@@ -415,7 +447,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
415
447
  ),
416
448
  description='`idf.py size` output file under the build directory when specified. ' 'Can expand placeholders',
417
449
  validation_alias=AliasChoices('size_json_filename', 'size_file'),
418
- default=None,
450
+ default=None, # type: ignore
419
451
  )
420
452
  config_rules: t.Optional[t.List[str]] = field(
421
453
  FieldMetadata(
@@ -433,31 +465,31 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
433
465
  'FILEPATTERN is the filename of the sdkconfig file with a single wildcard character (*). '
434
466
  'The NAME is the value matched by the wildcard',
435
467
  validation_alias=AliasChoices('config_rules', 'config_rules_str', 'config'),
436
- default=None,
468
+ default=None, # type: ignore
437
469
  )
438
470
  override_sdkconfig_items: t.Optional[str] = field(
439
471
  None,
440
472
  description='A comma-separated list of key=value pairs to override the sdkconfig items',
441
- default=None,
473
+ default=None, # type: ignore
442
474
  )
443
475
  override_sdkconfig_files: t.Optional[str] = field(
444
476
  None,
445
477
  description='A comma-separated list of sdkconfig files to override the sdkconfig items. '
446
478
  'When set to relative path, it will be treated as relative to the current directory',
447
- default=None,
479
+ default=None, # type: ignore
448
480
  )
449
481
  sdkconfig_defaults: t.Optional[str] = field(
450
482
  None,
451
483
  description='A semicolon-separated list of sdkconfig files passed to `idf.py -DSDKCONFIG_DEFAULTS`. '
452
484
  'SDKCONFIG_DEFAULTS environment variable is used when not specified',
453
- default=os.getenv('SDKCONFIG_DEFAULTS', None),
485
+ default=os.getenv('SDKCONFIG_DEFAULTS', None), # type: ignore
454
486
  )
455
487
  check_warnings: bool = field(
456
488
  FieldMetadata(
457
489
  action='store_true',
458
490
  ),
459
491
  description='Check for warnings in the build output. Fail if any warnings are found',
460
- default=False,
492
+ default=False, # type: ignore
461
493
  )
462
494
  default_build_targets: t.Optional[t.List[str]] = field(
463
495
  FieldMetadata(
@@ -466,7 +498,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
466
498
  ),
467
499
  description='space-separated list of the default enabled build targets for the apps. '
468
500
  'When not specified, the default value is the targets listed by `idf.py --list-targets`',
469
- default=None,
501
+ default=None, # type: ignore
470
502
  )
471
503
  enable_preview_targets: bool = field(
472
504
  FieldMetadata(
@@ -474,28 +506,28 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
474
506
  ),
475
507
  description='When enabled, the default build targets will be set to all apps, '
476
508
  'including the preview targets. As the targets defined in `idf.py --list-targets --preview`',
477
- default=False,
509
+ default=False, # type: ignore
478
510
  )
479
511
  include_skipped_apps: bool = field(
480
512
  FieldMetadata(
481
513
  action='store_true',
482
514
  ),
483
515
  description='Include the skipped apps in the output, together with the enabled ones',
484
- default=False,
516
+ default=False, # type: ignore
485
517
  )
486
518
  include_disabled_apps: bool = field(
487
519
  FieldMetadata(
488
520
  action='store_true',
489
521
  ),
490
522
  description='Include the disabled apps in the output, together with the enabled ones',
491
- default=False,
523
+ default=False, # type: ignore
492
524
  )
493
525
  include_all_apps: bool = field(
494
526
  FieldMetadata(
495
527
  action='store_true',
496
528
  ),
497
529
  description='Include skipped, and disabled apps in the output, together with the enabled ones',
498
- default=False,
530
+ default=False, # type: ignore
499
531
  )
500
532
 
501
533
  def model_post_init(self, __context: Any) -> None:
@@ -536,7 +568,7 @@ class FindArguments(FindBuildArguments):
536
568
  shorthand='-o',
537
569
  ),
538
570
  description='Record the found apps to the specified file instead of stdout',
539
- default=None,
571
+ default=None, # type: ignore
540
572
  )
541
573
  output_format: str = field(
542
574
  FieldMetadata(
@@ -545,7 +577,7 @@ class FindArguments(FindBuildArguments):
545
577
  description='Output format of the found apps. '
546
578
  'In "raw" format, each line is a json string serialized from the app model. '
547
579
  'In "json" format, the output is a json list of the serialized app models',
548
- default='raw',
580
+ default='raw', # type: ignore
549
581
  )
550
582
 
551
583
  def model_post_init(self, __context: Any) -> None:
@@ -566,7 +598,7 @@ class BuildArguments(FindBuildArguments):
566
598
  action='store_true',
567
599
  ),
568
600
  description='Enable verbose output of the build system',
569
- default=False,
601
+ default=False, # type: ignore
570
602
  )
571
603
  parallel_count: int = field(
572
604
  FieldMetadata(
@@ -576,7 +608,7 @@ class BuildArguments(FindBuildArguments):
576
608
  'Specified together with --parallel-index. '
577
609
  'The given apps will be divided into parallel_count parts, '
578
610
  'and the current run will build the parallel_index-th part',
579
- default=1,
611
+ default=1, # type: ignore
580
612
  )
581
613
  parallel_index: int = field(
582
614
  FieldMetadata(
@@ -586,28 +618,28 @@ class BuildArguments(FindBuildArguments):
586
618
  'Specified together with --parallel-count. '
587
619
  'The given apps will be divided into parallel_count parts, '
588
620
  'and the current run will build the parallel_index-th part',
589
- default=1,
621
+ default=1, # type: ignore
590
622
  )
591
623
  dry_run: bool = field(
592
624
  FieldMetadata(
593
625
  action='store_true',
594
626
  ),
595
627
  description='Skip the actual build, only print the build process',
596
- default=False,
628
+ default=False, # type: ignore
597
629
  )
598
630
  keep_going: bool = field(
599
631
  FieldMetadata(
600
632
  action='store_true',
601
633
  ),
602
634
  description='Continue building the next app when the current build fails',
603
- default=False,
635
+ default=False, # type: ignore
604
636
  )
605
637
  no_preserve: bool = field(
606
638
  FieldMetadata(
607
639
  action='store_true',
608
640
  ),
609
641
  description='Do not preserve the build directory after a successful build',
610
- default=False,
642
+ default=False, # type: ignore
611
643
  )
612
644
  ignore_warning_strs: t.Optional[t.List[str]] = field(
613
645
  FieldMetadata(
@@ -620,7 +652,7 @@ class BuildArguments(FindBuildArguments):
620
652
  description='space-separated list of patterns. '
621
653
  'Ignore the warnings in the build output that match the patterns',
622
654
  validation_alias=AliasChoices('ignore_warning_strs', 'ignore_warning_str'),
623
- default=None,
655
+ default=None, # type: ignore
624
656
  )
625
657
  ignore_warning_files: t.Optional[t.List[t.Union[str, TextIOWrapper]]] = field(
626
658
  FieldMetadata(
@@ -635,14 +667,14 @@ class BuildArguments(FindBuildArguments):
635
667
  ),
636
668
  description='Path to the files containing the patterns to ignore the warnings in the build output',
637
669
  validation_alias=AliasChoices('ignore_warning_files', 'ignore_warning_file'),
638
- default=None,
670
+ default=None, # type: ignore
639
671
  )
640
672
  copy_sdkconfig: bool = field(
641
673
  FieldMetadata(
642
674
  action='store_true',
643
675
  ),
644
676
  description='Copy the sdkconfig file to the build directory',
645
- default=False,
677
+ default=False, # type: ignore
646
678
  )
647
679
 
648
680
  # Attrs that support placeholders
@@ -654,7 +686,7 @@ class BuildArguments(FindBuildArguments):
654
686
  description='Record size json filepath of the built apps to the specified file. '
655
687
  'Each line is a json string. Can expand placeholders @p',
656
688
  validation_alias=AliasChoices('collect_size_info_filename', 'collect_size_info'),
657
- default=None,
689
+ default=None, # type: ignore
658
690
  exclude=True, # computed field is used
659
691
  )
660
692
  collect_app_info_filename: t.Optional[str] = field(
@@ -665,7 +697,7 @@ class BuildArguments(FindBuildArguments):
665
697
  description='Record serialized app model of the built apps to the specified file. '
666
698
  'Each line is a json string. Can expand placeholders @p',
667
699
  validation_alias=AliasChoices('collect_app_info_filename', 'collect_app_info'),
668
- default=None,
700
+ default=None, # type: ignore
669
701
  exclude=True, # computed field is used
670
702
  )
671
703
  junitxml_filename: t.Optional[str] = field(
@@ -675,7 +707,7 @@ class BuildArguments(FindBuildArguments):
675
707
  ),
676
708
  description='Path to the junitxml file to record the build results. Can expand placeholder @p',
677
709
  validation_alias=AliasChoices('junitxml_filename', 'junitxml'),
678
- default=None,
710
+ default=None, # type: ignore
679
711
  exclude=True, # computed field is used
680
712
  )
681
713
  # used for expanding placeholders
@@ -732,7 +764,7 @@ class DumpManifestShaArguments(GlobalArguments):
732
764
  required=True,
733
765
  ),
734
766
  description='Path to the manifest files which contains the build test rules of the apps',
735
- default=None,
767
+ default=None, # type: ignore
736
768
  )
737
769
 
738
770
  output: t.Optional[str] = field(
@@ -741,7 +773,7 @@ class DumpManifestShaArguments(GlobalArguments):
741
773
  required=True,
742
774
  ),
743
775
  description='Path to the output file to record the sha256 hash of the manifest rules',
744
- default=None,
776
+ default=None, # type: ignore
745
777
  )
746
778
 
747
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.2
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=_czizzLpsxKDZG61HbrIaZjdeib-_E4ZqTMOcxbxNxo,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=L1r7iS3gvV5UDqaZpu9M4Y36LUp-m-BK0rOqfpJjXu4,31918
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.2.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
24
- idf_build_apps-2.5.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
- idf_build_apps-2.5.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
26
- idf_build_apps-2.5.2.dist-info/METADATA,sha256=ZegY4svIIuKIUgCFWJdgiiMIZzhSzLWRbOCiwBAVoKo,4610
27
- idf_build_apps-2.5.2.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