idf-build-apps 2.11.1__py3-none-any.whl → 2.12.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.
@@ -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.11.1'
11
+ __version__ = '2.12.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
idf_build_apps/app.py CHANGED
@@ -95,6 +95,7 @@ class App(BaseModel):
95
95
 
96
96
  _build_log_filename: t.Optional[str] = None
97
97
  _size_json_filename: t.Optional[str] = None
98
+ size_json_extra_args: t.Optional[t.List[str]] = None
98
99
 
99
100
  dry_run: bool = False
100
101
  verbose: bool = False
@@ -105,15 +106,16 @@ class App(BaseModel):
105
106
  # build_apps() related
106
107
  index: t.Optional[int] = None
107
108
 
108
- # build status related
109
109
  build_status: BuildStatus = BuildStatus.UNKNOWN
110
110
  build_comment: t.Optional[str] = None
111
+ test_comment: t.Optional[str] = None
111
112
 
112
113
  _build_duration: float = 0
113
114
  _build_timestamp: t.Optional[datetime] = None
114
115
 
115
116
  __EQ_IGNORE_FIELDS__ = [
116
117
  'build_comment',
118
+ 'test_comment',
117
119
  ]
118
120
  __EQ_TUNE_FIELDS__ = {
119
121
  'app_dir': lambda x: (os.path.realpath(os.path.expanduser(x))),
@@ -324,8 +326,7 @@ class App(BaseModel):
324
326
  # put the expanded variable files in a temporary directory
325
327
  # will remove if the content is the same as the original one
326
328
  expanded_dir = os.path.join(self.work_dir, 'expanded_sdkconfig_files', os.path.basename(self.build_dir))
327
- if not os.path.isdir(expanded_dir):
328
- os.makedirs(expanded_dir)
329
+ os.makedirs(expanded_dir, exist_ok=True)
329
330
 
330
331
  for f in self.sdkconfig_defaults_candidates + ([self.sdkconfig_path] if self.sdkconfig_path else []):
331
332
  # use filepath if abs/rel already point to itself
@@ -338,57 +339,47 @@ class App(BaseModel):
338
339
  continue
339
340
 
340
341
  expanded_fp = os.path.join(expanded_dir, os.path.basename(f))
341
- with open(f) as fr:
342
- with open(expanded_fp, 'w') as fw:
343
- for line in fr:
344
- line = os.path.expandvars(line)
345
-
346
- m = self.SDKCONFIG_LINE_REGEX.match(line)
347
- if m:
348
- key = m.group(1)
349
- if key == 'CONFIG_IDF_TARGET':
350
- sdkconfig_files_defined_target = m.group(2)
351
-
352
- if isinstance(self, CMakeApp):
353
- if key in self.SDKCONFIG_TEST_OPTS:
354
- self.cmake_vars[key] = m.group(2)
355
- continue
356
-
357
- if key in self.SDKCONFIG_IGNORE_OPTS:
358
- continue
359
-
360
- fw.write(line)
361
-
362
- with open(f) as fr:
363
- with open(expanded_fp) as new_fr:
364
- if fr.read() == new_fr.read():
365
- LOGGER.debug('Use sdkconfig file %s', f)
366
- try:
367
- os.unlink(expanded_fp)
368
- except OSError:
369
- LOGGER.debug('Failed to remove file %s', expanded_fp)
370
- real_sdkconfig_files.append(f)
371
- else:
372
- LOGGER.debug('Expand sdkconfig file %s to %s', f, expanded_fp)
373
- real_sdkconfig_files.append(expanded_fp)
374
- # copy the related target-specific sdkconfig files
375
- par_dir = os.path.abspath(os.path.join(f, '..'))
376
- for target_specific_file in (
377
- os.path.join(par_dir, str(p))
378
- for p in Path(par_dir).glob(os.path.basename(f) + f'.{self.target}')
379
- ):
380
- LOGGER.debug(
381
- 'Copy target-specific sdkconfig file %s to %s', target_specific_file, expanded_dir
382
- )
383
- shutil.copy(target_specific_file, expanded_dir)
384
-
385
- # remove if expanded folder is empty
386
- try:
387
- os.rmdir(expanded_dir)
388
- except OSError:
389
- pass
342
+ with open(f) as fr, open(expanded_fp, 'w') as fw:
343
+ for line in fr:
344
+ line = os.path.expandvars(line)
345
+
346
+ m = self.SDKCONFIG_LINE_REGEX.match(line)
347
+ if m:
348
+ key, value = m.group(1), m.group(2)
349
+ if key == 'CONFIG_IDF_TARGET':
350
+ sdkconfig_files_defined_target = value
351
+
352
+ if isinstance(self, CMakeApp):
353
+ if key in self.SDKCONFIG_TEST_OPTS:
354
+ self.cmake_vars[key] = value
355
+ continue
356
+ if key in self.SDKCONFIG_IGNORE_OPTS:
357
+ continue
358
+
359
+ fw.write(line)
360
+
361
+ with open(f) as fr, open(expanded_fp) as new_fr:
362
+ if fr.read() == new_fr.read():
363
+ LOGGER.debug('Use sdkconfig file %s', f)
364
+ try:
365
+ os.unlink(expanded_fp)
366
+ except OSError:
367
+ LOGGER.debug('Failed to remove file %s', expanded_fp)
368
+ real_sdkconfig_files.append(f)
369
+ else:
370
+ LOGGER.debug('Expand sdkconfig file %s to %s', f, expanded_fp)
371
+ real_sdkconfig_files.append(expanded_fp)
372
+ # copy the related target-specific sdkconfig files
373
+ par_dir = os.path.abspath(os.path.join(f, '..'))
374
+ for target_specific_file in (
375
+ os.path.join(par_dir, str(p))
376
+ for p in Path(par_dir).glob(os.path.basename(f) + f'.{self.target}')
377
+ ):
378
+ LOGGER.debug('Copy target-specific sdkconfig file %s to %s', target_specific_file, expanded_dir)
379
+ shutil.copy(target_specific_file, expanded_dir)
390
380
 
391
381
  try:
382
+ os.rmdir(expanded_dir)
392
383
  os.rmdir(os.path.join(self.work_dir, 'expanded_sdkconfig_files'))
393
384
  except OSError:
394
385
  pass
@@ -428,14 +419,14 @@ class App(BaseModel):
428
419
 
429
420
  @property
430
421
  def supported_targets(self) -> t.List[str]:
422
+ if self.sdkconfig_files_defined_idf_target:
423
+ return [self.sdkconfig_files_defined_idf_target]
424
+
431
425
  if self.MANIFEST:
432
426
  return self.MANIFEST.enable_build_targets(
433
427
  self.app_dir, self.sdkconfig_files_defined_idf_target, self.config_name
434
428
  )
435
429
 
436
- if self.sdkconfig_files_defined_idf_target:
437
- return [self.sdkconfig_files_defined_idf_target]
438
-
439
430
  return DEFAULT_BUILD_TARGETS.get()
440
431
 
441
432
  @property
@@ -656,11 +647,10 @@ class App(BaseModel):
656
647
  [
657
648
  sys.executable,
658
649
  str(IDF_SIZE_PY),
659
- ]
660
- + (['--json'] if IDF_VERSION < Version('5.1') else ['--format', 'json'])
661
- + [
650
+ *(['--json'] if IDF_VERSION < Version('5.1') else ['--format', 'json']),
662
651
  '-o',
663
652
  self.size_json_path,
653
+ *(self.size_json_extra_args or []),
664
654
  map_file,
665
655
  ],
666
656
  check=True,
@@ -668,14 +658,13 @@ class App(BaseModel):
668
658
  else:
669
659
  with open(self.size_json_path, 'w') as fw:
670
660
  subprocess_run(
671
- (
672
- [
673
- sys.executable,
674
- str(IDF_SIZE_PY),
675
- '--json',
676
- map_file,
677
- ]
678
- ),
661
+ [
662
+ sys.executable,
663
+ str(IDF_SIZE_PY),
664
+ '--json',
665
+ *(self.size_json_extra_args or []),
666
+ map_file,
667
+ ],
679
668
  log_terminal=False,
680
669
  log_fs=fw,
681
670
  check=True,
@@ -735,9 +724,35 @@ class App(BaseModel):
735
724
  modified_components: t.Optional[t.List[str]] = None,
736
725
  modified_files: t.Optional[t.List[str]] = None,
737
726
  ) -> None:
727
+ """Check if this app should be built based on the modified files and components."""
738
728
  if self.build_status != BuildStatus.UNKNOWN:
739
729
  return
740
730
 
731
+ if self.target not in self.supported_targets:
732
+ # default error message
733
+ self.build_comment = (
734
+ f'Target {self.target} not in default build targets {",".join(DEFAULT_BUILD_TARGETS.get())}'
735
+ )
736
+
737
+ if self.MANIFEST:
738
+ rule = self.MANIFEST.most_suitable_rule(self.app_dir)
739
+
740
+ # disable > enable
741
+ for clause in rule.disable:
742
+ if clause.get_value(self.target, self.config_name or ''):
743
+ self.build_comment = f'Disabled by manifest rule: {clause}'
744
+ break
745
+ else:
746
+ # Check if it's not enabled by manifest rules
747
+ if rule.enable:
748
+ # Has enable rules but target not in enabled targets
749
+ self.build_comment = 'Not enabled by manifest rules:\n'
750
+ self.build_comment += '\n'.join(f'- {clause}' for clause in rule.enable)
751
+
752
+ self.build_status = BuildStatus.DISABLED
753
+ self._checked_should_build = True
754
+ return
755
+
741
756
  if not check_app_dependencies:
742
757
  self.build_status = BuildStatus.SHOULD_BE_BUILT
743
758
  self._checked_should_build = True
@@ -804,6 +819,35 @@ class App(BaseModel):
804
819
  self.build_comment = 'current build does not modify any components or files required by this app'
805
820
  self._checked_should_build = True
806
821
 
822
+ def check_should_test(self) -> None:
823
+ """Check if testing is disabled for this app and set test_disable_reason."""
824
+ if not self.MANIFEST:
825
+ return
826
+
827
+ rule = self.MANIFEST.most_suitable_rule(self.app_dir)
828
+
829
+ # Check if testing is enabled for this target
830
+ if self.target not in self.verified_targets:
831
+ # default error message
832
+ self.test_comment = f'Target {self.target} not in verified targets {",".join(self.verified_targets)}'
833
+
834
+ # disable_test > disable > enable
835
+ for clause in rule.disable_test:
836
+ if clause.get_value(self.target, self.config_name or ''):
837
+ self.test_comment = f'Disabled by manifest rule: {clause}'
838
+ return
839
+
840
+ # Check if disabled by general disable rules
841
+ for clause in rule.disable:
842
+ if clause.get_value(self.target, self.config_name or ''):
843
+ self.test_comment = f'Disabled by manifest rule: {clause}'
844
+ return
845
+
846
+ # If not explicitly disabled but not in enabled targets
847
+ if rule.enable:
848
+ self.test_comment = 'Not enabled by manifest rules:\n'
849
+ self.test_comment += '\n'.join(f'- {clause}' for clause in rule.enable)
850
+
807
851
 
808
852
  class MakeApp(App):
809
853
  MAKE_PROJECT_LINE: t.ClassVar[str] = r'include $(IDF_PATH)/make/project.mk'
idf_build_apps/args.py CHANGED
@@ -240,14 +240,24 @@ class DependencyDrivenBuildArguments(GlobalArguments):
240
240
  validate_method=[ValidateMethod.TO_LIST],
241
241
  nargs='+',
242
242
  ),
243
- description='space-separated list of file patterns to search for the manifest files. '
243
+ description='space-separated list of file glob patterns to search for the manifest files. '
244
244
  'The matched files will be loaded as the manifest files.',
245
245
  default=None, # type: ignore
246
246
  )
247
+ manifest_exclude_regexes: t.Optional[t.List[str]] = field(
248
+ FieldMetadata(
249
+ validate_method=[ValidateMethod.TO_LIST],
250
+ nargs='+',
251
+ ),
252
+ description='space-separated list of regex to exclude when searching for manifest files. '
253
+ 'Files matching these patterns will be ignored. '
254
+ 'By default excludes files under "managed_components" directories.',
255
+ default=['/managed_components/'], # type: ignore
256
+ )
247
257
  manifest_rootpath: str = field(
248
258
  None,
249
259
  description='Root path to resolve the relative paths defined in the manifest files. '
250
- 'By default set to the current directory. Support environment variables.',
260
+ 'By default set to the current directory.',
251
261
  default=os.curdir, # type: ignore
252
262
  )
253
263
  modified_components: t.Optional[t.List[str]] = field(
@@ -337,6 +347,22 @@ class DependencyDrivenBuildArguments(GlobalArguments):
337
347
  for pat in [to_absolute_path(p, self.manifest_rootpath) for p in self.manifest_filepatterns]:
338
348
  matched_paths.update(glob.glob(str(pat), recursive=True))
339
349
 
350
+ exclude_regexes = {re.compile(regex) for regex in self.manifest_exclude_regexes or []}
351
+
352
+ # Filter out files matching excluded patterns
353
+ if matched_paths:
354
+ filtered_paths = set()
355
+ for path in matched_paths:
356
+ posix_path = Path(path).as_posix()
357
+ for regex in exclude_regexes:
358
+ if regex.search(posix_path):
359
+ LOGGER.debug(f'Excluding manifest file {path} due to excluded regex match')
360
+ break
361
+ else:
362
+ filtered_paths.add(path)
363
+
364
+ matched_paths = filtered_paths
365
+
340
366
  if matched_paths:
341
367
  if self.manifest_files:
342
368
  self.manifest_files.extend(matched_paths)
@@ -482,6 +508,13 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
482
508
  validation_alias=AliasChoices('size_json_filename', 'size_file'),
483
509
  default=None, # type: ignore
484
510
  )
511
+ size_json_extra_args: t.Optional[t.List[str]] = field(
512
+ FieldMetadata(
513
+ validate_method=[ValidateMethod.TO_LIST],
514
+ ),
515
+ description='Additional arguments to pass to esp_idf_size tool',
516
+ default=None, # type: ignore
517
+ )
485
518
  config_rules: t.Optional[t.List[str]] = field(
486
519
  FieldMetadata(
487
520
  validate_method=[ValidateMethod.TO_LIST],
@@ -607,7 +640,7 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
607
640
  elif self.enable_preview_targets:
608
641
  self.default_build_targets = deepcopy(ALL_TARGETS)
609
642
  LOGGER.info('Overriding default build targets to %s', self.default_build_targets)
610
- DEFAULT_BUILD_TARGETS.set(self.default_build_targets) # type: ignore
643
+ DEFAULT_BUILD_TARGETS.set(self.default_build_targets)
611
644
 
612
645
  if self.disable_targets and DEFAULT_BUILD_TARGETS.get():
613
646
  LOGGER.info('Disable targets: %s', self.disable_targets)
@@ -791,7 +824,7 @@ class BuildArguments(FindBuildArguments):
791
824
  hidden=True,
792
825
  ),
793
826
  description='Record size json filepath of the built apps to the specified file. '
794
- 'Each line is a json string. Can expand placeholders @p. Support environment variables.',
827
+ 'Each line is a json string. Can expand placeholders @p.',
795
828
  validation_alias=AliasChoices('collect_size_info_filename', 'collect_size_info'),
796
829
  default=None, # type: ignore
797
830
  exclude=True, # computed field is used
@@ -802,7 +835,7 @@ class BuildArguments(FindBuildArguments):
802
835
  hidden=True,
803
836
  ),
804
837
  description='Record serialized app model of the built apps to the specified file. '
805
- 'Each line is a json string. Can expand placeholders @p. Support environment variables.',
838
+ 'Each line is a json string. Can expand placeholders @p.',
806
839
  validation_alias=AliasChoices('collect_app_info_filename', 'collect_app_info'),
807
840
  default=None, # type: ignore
808
841
  exclude=True, # computed field is used
@@ -812,8 +845,7 @@ class BuildArguments(FindBuildArguments):
812
845
  deprecates={'junitxml': {}},
813
846
  hidden=True,
814
847
  ),
815
- description='Path to the junitxml file to record the build results. Can expand placeholder @p. '
816
- 'Support environment variables.',
848
+ description='Path to the junitxml file to record the build results. Can expand placeholder @p.',
817
849
  validation_alias=AliasChoices('junitxml_filename', 'junitxml'),
818
850
  default=None, # type: ignore
819
851
  exclude=True, # computed field is used
idf_build_apps/finder.py CHANGED
@@ -18,7 +18,6 @@ from .args import FindArguments
18
18
  from .constants import (
19
19
  BuildStatus,
20
20
  )
21
- from .manifest.manifest import DEFAULT_BUILD_TARGETS
22
21
  from .utils import (
23
22
  config_rules_from_str,
24
23
  to_absolute_path,
@@ -34,38 +33,6 @@ def _get_apps_from_path(
34
33
  app_cls: t.Type[App] = CMakeApp,
35
34
  args: FindArguments,
36
35
  ) -> t.List[App]:
37
- def _validate_app(_app: App) -> bool:
38
- if target not in _app.supported_targets:
39
- LOGGER.debug('=> Ignored. %s only supports targets: %s', _app, ', '.join(_app.supported_targets))
40
- _app.build_status = BuildStatus.DISABLED
41
- return args.include_disabled_apps
42
-
43
- if target == 'all' and _app.target not in DEFAULT_BUILD_TARGETS.get():
44
- LOGGER.debug(
45
- '=> Ignored. %s is not in the default build targets: %s', _app.target, DEFAULT_BUILD_TARGETS.get()
46
- )
47
- _app.build_status = BuildStatus.DISABLED
48
- return args.include_disabled_apps
49
- elif _app.target != target:
50
- LOGGER.debug('=> Ignored. %s is not for target %s', _app, target)
51
- _app.build_status = BuildStatus.DISABLED
52
- return args.include_disabled_apps
53
-
54
- _app.check_should_build(
55
- manifest_rootpath=args.manifest_rootpath,
56
- modified_manifest_rules_folders=args.modified_manifest_rules_folders,
57
- modified_components=args.modified_components,
58
- modified_files=args.modified_files,
59
- check_app_dependencies=args.dependency_driven_build_enabled,
60
- )
61
-
62
- # for unknown ones, we keep them to the build stage to judge
63
- if _app.build_status == BuildStatus.SKIPPED:
64
- LOGGER.debug('=> Skipped. Reason: %s', _app.build_comment or 'Unknown')
65
- return args.include_skipped_apps
66
-
67
- return True
68
-
69
36
  if not app_cls.is_app(path):
70
37
  LOGGER.debug('Skipping. %s is not an app', path)
71
38
  return []
@@ -73,8 +40,10 @@ def _get_apps_from_path(
73
40
  config_rules = config_rules_from_str(args.config_rules)
74
41
 
75
42
  apps = []
43
+ app_configs: t.List[t.Tuple[t.Optional[str], str]] = [] # List of (sdkconfig_path, config_name) tuples
76
44
  default_config_name = ''
77
45
  sdkconfig_paths_matched = False
46
+
78
47
  for rule in config_rules:
79
48
  if not rule.file_name:
80
49
  default_config_name = rule.config_name
@@ -99,45 +68,60 @@ def _get_apps_from_path(
99
68
  assert groups
100
69
  config_name = groups.group(1)
101
70
 
102
- app = app_cls(
103
- path,
104
- target,
105
- sdkconfig_path=sdkconfig_path,
106
- config_name=config_name,
107
- work_dir=args.work_dir,
108
- build_dir=args.build_dir,
109
- build_log_filename=args.build_log_filename,
110
- size_json_filename=args.size_json_filename,
111
- check_warnings=args.check_warnings,
112
- sdkconfig_defaults_str=args.sdkconfig_defaults,
113
- )
114
- if _validate_app(app):
115
- LOGGER.debug('Found app: %s', app)
116
- apps.append(app)
117
-
118
- LOGGER.debug('') # add one empty line for separating different finds
71
+ app_configs.append((sdkconfig_path, config_name))
119
72
 
120
73
  # no config rules matched, use default app
121
74
  if not sdkconfig_paths_matched:
75
+ app_configs.append((None, default_config_name))
76
+
77
+ # Create, validate, and add all apps
78
+ for p, n in app_configs:
122
79
  app = app_cls(
123
80
  path,
124
81
  target,
125
- sdkconfig_path=None,
126
- config_name=default_config_name,
82
+ sdkconfig_path=p,
83
+ config_name=n,
127
84
  work_dir=args.work_dir,
128
85
  build_dir=args.build_dir,
129
86
  build_log_filename=args.build_log_filename,
130
87
  size_json_filename=args.size_json_filename,
88
+ size_json_extra_args=args.size_json_extra_args,
131
89
  check_warnings=args.check_warnings,
132
90
  sdkconfig_defaults_str=args.sdkconfig_defaults,
133
91
  )
134
92
 
135
- if _validate_app(app):
93
+ if app.sdkconfig_files_defined_idf_target and app.target != app.sdkconfig_files_defined_idf_target:
94
+ LOGGER.debug(
95
+ 'Project %s with config %s defined CONFIG_IDF_TARGET=%s, Ignoring target %s',
96
+ app.app_dir,
97
+ app.config_name or 'default',
98
+ app.sdkconfig_files_defined_idf_target,
99
+ target,
100
+ )
101
+ continue
102
+
103
+ app.check_should_build(
104
+ manifest_rootpath=args.manifest_rootpath,
105
+ modified_manifest_rules_folders=args.modified_manifest_rules_folders,
106
+ modified_components=args.modified_components,
107
+ modified_files=args.modified_files,
108
+ check_app_dependencies=args.dependency_driven_build_enabled,
109
+ )
110
+ app.check_should_test()
111
+
112
+ if app.build_status == BuildStatus.DISABLED:
113
+ LOGGER.debug('=> Disabled. Reason: %s', app.build_comment or 'Unknown')
114
+ should_include = args.include_disabled_apps
115
+ elif app.build_status == BuildStatus.SKIPPED:
116
+ LOGGER.debug('=> Skipped. Reason: %s', app.build_comment or 'Unknown')
117
+ should_include = args.include_skipped_apps
118
+ else:
119
+ should_include = True
120
+
121
+ if should_include:
136
122
  LOGGER.debug('Found app: %s', app)
137
123
  apps.append(app)
138
124
 
139
- LOGGER.debug('') # add one empty line for separating different finds
140
-
141
125
  return sorted(apps)
142
126
 
143
127
 
idf_build_apps/main.py CHANGED
@@ -408,8 +408,7 @@ def main():
408
408
  os.makedirs(os.path.dirname(os.path.realpath(arguments.output)), exist_ok=True)
409
409
  with open(arguments.output, 'w') as fw:
410
410
  if arguments.output_format == 'raw':
411
- for app in apps:
412
- fw.write(app.to_json() + '\n')
411
+ fw.writelines(app.to_json() + '\n' for app in apps)
413
412
  elif arguments.output_format == 'json':
414
413
  fw.write(json.dumps([app.model_dump() for app in apps], indent=2))
415
414
  else:
@@ -5,9 +5,14 @@
5
5
  Manifest file
6
6
  """
7
7
 
8
- from esp_bool_parser import register_addition_attribute
8
+ from .manifest import DEFAULT_BUILD_TARGETS, FolderRule
9
+
10
+ __all__ = [
11
+ 'DEFAULT_BUILD_TARGETS',
12
+ 'FolderRule',
13
+ ]
9
14
 
10
- from .manifest import DEFAULT_BUILD_TARGETS
15
+ from esp_bool_parser import register_addition_attribute
11
16
 
12
17
 
13
18
  def folder_rule_attr(target, **kwargs):
@@ -5,6 +5,7 @@ import logging
5
5
  import os
6
6
  import typing as t
7
7
  import warnings
8
+ from functools import lru_cache
8
9
  from hashlib import sha512
9
10
 
10
11
  from esp_bool_parser import BoolStmt, parse_bool_expr
@@ -60,6 +61,14 @@ class IfClause:
60
61
  f' reason: lack of ci runners'
61
62
  )
62
63
 
64
+ def __str__(self):
65
+ s = self._stmt
66
+ if self.temporary:
67
+ s += ' (temporary)'
68
+ if self.reason:
69
+ s += f' (reason: {self.reason})'
70
+ return s
71
+
63
72
  def __repr__(self):
64
73
  return f'IfClause(stmt={self._stmt!r}, temporary={self.temporary!r}, reason={self.reason!r})'
65
74
 
@@ -95,7 +104,7 @@ def _getattr_default_build_targets(name: str) -> t.Any:
95
104
  warnings.warn(
96
105
  'FolderRule.DEFAULT_BUILD_TARGETS is deprecated. Use DEFAULT_BUILD_TARGETS.get() directly.',
97
106
  DeprecationWarning,
98
- stacklevel=2,
107
+ stacklevel=3,
99
108
  )
100
109
  return DEFAULT_BUILD_TARGETS.get()
101
110
  return None
@@ -106,7 +115,7 @@ def _setattr_default_build_targets(name: str, value: t.Any) -> bool:
106
115
  warnings.warn(
107
116
  'FolderRule.DEFAULT_BUILD_TARGETS is deprecated. Use DEFAULT_BUILD_TARGETS.set() directly.',
108
117
  DeprecationWarning,
109
- stacklevel=2,
118
+ stacklevel=3,
110
119
  )
111
120
  if not isinstance(value, list):
112
121
  raise TypeError('Default build targets must be a list')
@@ -244,6 +253,7 @@ class FolderRule(metaclass=_FolderRuleMeta):
244
253
  def by_manifest_file(self) -> t.Optional[str]:
245
254
  return self._manifest_filepath
246
255
 
256
+ @lru_cache(None)
247
257
  def _enable_build(self, target: str, config_name: str) -> bool:
248
258
  if self.enable:
249
259
  res = False
@@ -252,7 +262,7 @@ class FolderRule(metaclass=_FolderRuleMeta):
252
262
  res = True
253
263
  break
254
264
  else:
255
- res = target in self.DEFAULT_BUILD_TARGETS
265
+ res = target in DEFAULT_BUILD_TARGETS.get()
256
266
 
257
267
  if self.disable:
258
268
  for clause in self.disable:
@@ -262,6 +272,7 @@ class FolderRule(metaclass=_FolderRuleMeta):
262
272
 
263
273
  return res
264
274
 
275
+ @lru_cache(None)
265
276
  def _enable_test(
266
277
  self, target: str, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
267
278
  ) -> bool:
@@ -275,6 +286,7 @@ class FolderRule(metaclass=_FolderRuleMeta):
275
286
 
276
287
  return res
277
288
 
289
+ @lru_cache(None)
278
290
  def enable_build_targets(
279
291
  self, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
280
292
  ) -> t.List[str]:
@@ -301,6 +313,7 @@ class FolderRule(metaclass=_FolderRuleMeta):
301
313
 
302
314
  return sorted(res)
303
315
 
316
+ @lru_cache(None)
304
317
  def enable_test_targets(
305
318
  self, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
306
319
  ) -> t.List[str]:
@@ -402,8 +415,7 @@ class Manifest:
402
415
  :return: None
403
416
  """
404
417
  with open(sha_filepath, 'w') as fw:
405
- for rule in self.rules:
406
- fw.write(f'{os.path.relpath(rule.folder, self._root_path)}:{rule.sha}\n')
418
+ fw.writelines(f'{os.path.relpath(rule.folder, self._root_path)}:{rule.sha}\n' for rule in self.rules)
407
419
 
408
420
  def diff_sha_with_filepath(self, sha_filepath: str, use_abspath: bool = False) -> t.Set[str]:
409
421
  """
@@ -70,6 +70,5 @@ class SessionArgs:
70
70
  return None
71
71
  f_path = os.path.join(self.workdir, 'override-result.sdkconfig')
72
72
  with open(f_path, 'w+') as f:
73
- for key, value in override_sdkconfig_merged_items.items():
74
- f.write(f'{key}={value}\n')
73
+ f.writelines(f'{key}={value}\n' for key, value in override_sdkconfig_merged_items.items())
75
74
  return f_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idf-build-apps
3
- Version: 2.11.1
3
+ Version: 2.12.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
@@ -1,27 +1,27 @@
1
- idf_build_apps/__init__.py,sha256=wlRfbcKx42p01NE1C2JQjK88MunDVVApQOTV4XsOHbQ,711
1
+ idf_build_apps/__init__.py,sha256=SRpJLsenairxEG76BDWkuxQt3p_gJ7ZDI1Sr8JayilU,711
2
2
  idf_build_apps/__main__.py,sha256=pT6OsFQRjCw39Jg43HAeGKzq8h5E_0m7kHDE2QMqDe0,182
3
- idf_build_apps/app.py,sha256=Z4TZegsyushNPvWcdSv4jJG1TIkuBMGdQ5FkpOSDFlE,36915
4
- idf_build_apps/args.py,sha256=Ria0VDBtnUyN3fgHbG9S9P7gyWYoJ3YxrN_C0z6XEeQ,38977
3
+ idf_build_apps/app.py,sha256=-pP1vCPGYePFr9nO-Y00WfQ4Bg5DWyHlH_gWDtvAZio,39181
4
+ idf_build_apps/args.py,sha256=rt8CQUiWtgADgVtvv4R7JSzAOlFGYqNPGeNNfX8mnsI,40257
5
5
  idf_build_apps/autocompletions.py,sha256=2fZQxzgZ21ie_2uk-B-7-xWYCChfOSgRFRYb7I2Onfo,2143
6
6
  idf_build_apps/constants.py,sha256=2iwLPZRhSQcn1v4RAcOJnHbqp1fDTp6A1gHaxn5ciTE,2166
7
- idf_build_apps/finder.py,sha256=yJnsYdcbX-TzUDrp8h1YJrLfBziQW4RiyLiJ47sHRY4,6375
7
+ idf_build_apps/finder.py,sha256=gpOcpGrzAGiBVRCgKQ_YW22_OUJv-D-hMApuE-tPhVQ,5644
8
8
  idf_build_apps/log.py,sha256=15sSQhv9dJsHShDR2KgFGFp8ByjV0HogLr1X1lHYqGs,3899
9
- idf_build_apps/main.py,sha256=GQWx7KH6Qn6z43iZMpdh0myyCJRywf0wmLWFEqRDlL0,17980
9
+ idf_build_apps/main.py,sha256=P_TsUA2s048qcRb-wVngF-zqNjH_NEYrQsAYKB1GHmU,17960
10
10
  idf_build_apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- idf_build_apps/session_args.py,sha256=yM-78vFzCDCdhXHNxTALmzE4w2NSmka8yVBbh3wtvC8,2985
11
+ idf_build_apps/session_args.py,sha256=1B7e3M9_eKdQezGNXaocqCq7iE4MOxpYJkfanCfdkDE,2973
12
12
  idf_build_apps/utils.py,sha256=cQJ5N-53vrASa4d8WW0AQCPJzendArXyU3kB5Vx-AH8,10880
13
13
  idf_build_apps/junit/__init__.py,sha256=ljILW1rfeBAIlwZIw8jstYrVbugErlmCYzSzJwNFC2I,231
14
14
  idf_build_apps/junit/report.py,sha256=yzt5SiJEA_AUlw2Ld23J7enYlfDluvmKAcCnAM8ccqE,6565
15
15
  idf_build_apps/junit/utils.py,sha256=idBrLgsz6Co2QUQqq1AiyzRHnqbJf_EoykEAxCkjHZw,1303
16
- idf_build_apps/manifest/__init__.py,sha256=TxNrtn6uiSI4Uh8mQWxws5F7Gd6Lr3rMuBzGyl3yFCE,405
17
- idf_build_apps/manifest/manifest.py,sha256=B_yZIfaPEYwIDR8J-EiTCxitiR9BTqFTZj8e1iaTvkk,17754
16
+ idf_build_apps/manifest/__init__.py,sha256=CP4_LSkh7sb_DsWLSzqkSZ3UojyVdM10bX9avlmn2pg,479
17
+ idf_build_apps/manifest/manifest.py,sha256=i8bXxyHeIFxYN5Z1l0dNNBFdgP7SKl3t0TCVqhlF0Lo,18051
18
18
  idf_build_apps/manifest/soc_header.py,sha256=_F6H5-HP6ateAqKUGlRGH-SUtQ8NJ1RI0hBeCFvsDYA,172
19
19
  idf_build_apps/vendors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  idf_build_apps/vendors/pydantic_sources.py,sha256=cxSIPRc3eI5peVMhDxwf58YaGhuG4SCwPRVX2znFEek,4553
21
21
  idf_build_apps/yaml/__init__.py,sha256=R6pYasVsD31maeZ4dWRZnS10hwzM7gXdnfzDsOIRJ-4,167
22
22
  idf_build_apps/yaml/parser.py,sha256=IhY7rCWXOxrzzgEiKipTdPs_8yXDf8JZr-sMewV1pk8,2133
23
- idf_build_apps-2.11.1.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
24
- idf_build_apps-2.11.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
- idf_build_apps-2.11.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
26
- idf_build_apps-2.11.1.dist-info/METADATA,sha256=jmFKCREqID9WdX9KVtf1A8clSF6WRCW2Abs25DqnioY,4795
27
- idf_build_apps-2.11.1.dist-info/RECORD,,
23
+ idf_build_apps-2.12.0.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
24
+ idf_build_apps-2.12.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
+ idf_build_apps-2.12.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
26
+ idf_build_apps-2.12.0.dist-info/METADATA,sha256=b_zMQBsp3JjRhO30xXyahg7FGvmfJr5SQ49Eh5hNWsI,4795
27
+ idf_build_apps-2.12.0.dist-info/RECORD,,