idf-build-apps 2.11.2__tar.gz → 2.12.0__tar.gz

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.
Files changed (72) hide show
  1. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.pre-commit-config.yaml +2 -2
  2. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/CHANGELOG.md +8 -0
  3. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/CONTRIBUTING.md +8 -12
  4. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/PKG-INFO +1 -1
  5. idf_build_apps-2.12.0/docs/en/explanations/build_status.rst +122 -0
  6. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/index.rst +1 -0
  7. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/__init__.py +1 -1
  8. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/app.py +110 -66
  9. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/args.py +35 -2
  10. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/finder.py +39 -55
  11. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/main.py +1 -2
  12. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/manifest/manifest.py +17 -5
  13. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/session_args.py +1 -2
  14. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/pyproject.toml +2 -2
  15. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/setup.py +1 -1
  16. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_app.py +6 -2
  17. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_args.py +182 -0
  18. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_build.py +48 -0
  19. idf_build_apps-2.12.0/tests/test_disable_reasons.py +238 -0
  20. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_finder.py +3 -2
  21. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.editorconfig +0 -0
  22. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.git-blame-ignore-revs +0 -0
  23. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.gitattributes +0 -0
  24. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.github/dependabot.yml +0 -0
  25. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.github/workflows/publish-pypi.yml +0 -0
  26. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.github/workflows/sync-jira.yml +0 -0
  27. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.github/workflows/test-build-docs.yml +0 -0
  28. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.github/workflows/test-build-idf-apps.yml +0 -0
  29. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.gitignore +0 -0
  30. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/.readthedocs.yml +0 -0
  31. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/LICENSE +0 -0
  32. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/README.md +0 -0
  33. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_apidoc_templates/module.rst_t +0 -0
  34. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_apidoc_templates/package.rst_t +0 -0
  35. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_apidoc_templates/toc.rst_t +0 -0
  36. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_static/espressif-logo.svg +0 -0
  37. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_static/theme_overrides.css +0 -0
  38. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/_templates/layout.html +0 -0
  39. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/conf_common.py +0 -0
  40. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/Makefile +0 -0
  41. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/conf.py +0 -0
  42. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/explanations/build.rst +0 -0
  43. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/explanations/config_rules.rst +0 -0
  44. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/explanations/dependency_driven_build.rst +0 -0
  45. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/explanations/find.rst +0 -0
  46. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/guides/1.x_to_2.x.md +0 -0
  47. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/guides/custom_app.md +0 -0
  48. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/others/CHANGELOG.md +0 -0
  49. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/others/CONTRIBUTING.md +0 -0
  50. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/references/cli.rst +0 -0
  51. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/references/config_file.rst +0 -0
  52. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/docs/en/references/manifest.rst +0 -0
  53. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/__main__.py +0 -0
  54. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/autocompletions.py +0 -0
  55. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/constants.py +0 -0
  56. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/junit/__init__.py +0 -0
  57. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/junit/report.py +0 -0
  58. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/junit/utils.py +0 -0
  59. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/log.py +0 -0
  60. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/manifest/__init__.py +0 -0
  61. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/manifest/soc_header.py +0 -0
  62. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/py.typed +0 -0
  63. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/utils.py +0 -0
  64. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/vendors/__init__.py +0 -0
  65. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/vendors/pydantic_sources.py +0 -0
  66. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/yaml/__init__.py +0 -0
  67. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/idf_build_apps/yaml/parser.py +0 -0
  68. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/license_header.txt +0 -0
  69. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/conftest.py +0 -0
  70. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_cmd.py +0 -0
  71. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_manifest.py +0 -0
  72. {idf_build_apps-2.11.2 → idf_build_apps-2.12.0}/tests/test_utils.py +0 -0
@@ -17,13 +17,13 @@ repos:
17
17
  - --use-current-year
18
18
  exclude: 'idf_build_apps/vendors/'
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: 'v0.11.10'
20
+ rev: 'v0.12.5'
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: ['--fix']
24
24
  - id: ruff-format
25
25
  - repo: https://github.com/pre-commit/mirrors-mypy
26
- rev: 'v1.15.0'
26
+ rev: 'v1.17.0'
27
27
  hooks:
28
28
  - id: mypy
29
29
  args: ['--warn-unused-ignores']
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v2.12.0 (2025-08-11)
6
+
7
+ ### Feat
8
+
9
+ - recording build_disable_reason and test_disable_reason
10
+ - support --archives, --archive-details ARCHIVE or --files args of idf.py size
11
+ - support config `manifest_exclude_regexes`
12
+
5
13
  ## v2.11.2 (2025-06-10)
6
14
 
7
15
  ### Fix
@@ -6,18 +6,14 @@ Hi! We're glad that you're interested in contributing to `idf-build-apps`. This
6
6
 
7
7
  Here's a table shows the supported ESP-IDF versions and the corresponding Python versions.
8
8
 
9
- | ESP-IDF Version | ESP-IDF Supported Python Versions | idf-build-apps Releases |
10
- |-----------------|-----------------------------------|-------------------------|
11
- | 4.1 | 2.7, 3.4+ | 1.x |
12
- | 4.2 | 2.7, 3.4+ | 1.x |
13
- | 4.3 | 2.7, 3.4+ | 1.x |
14
- | 4.4 | 2.7, 3.4+ | 1.x |
15
- | 5.0 | 3.7+ | main (2.x) |
16
- | 5.1 | 3.7+ | main (2.x) |
17
- | 5.2 | 3.7+ | main (2.x) |
18
- | 5.3 | 3.8+ | main (2.x) |
19
- | 5.4 | 3.8+ | main (2.x) |
20
- | master (5.5) | 3.9+ | main (2.x) |
9
+ | ESP-IDF Version | ESP-IDF Version EOL | ESP-IDF Minimum Python Version | idf-build-apps Releases |
10
+ |-----------------|---------------------|--------------------------------|-------------------------|
11
+ | 5.1 | 2025.12.30 | 3.7 | 2.x |
12
+ | 5.2 | 2026.08.16 | 3.8 | 2.x |
13
+ | 5.3 | 2027.01.25 | 3.8 | 2.x |
14
+ | 5.4 | 2027.07.05 | 3.8 | 2.x |
15
+ | 5.5 | 2028.01.21 | 3.9 | 2.x |
16
+ | 6.0 (master) | N/A | 3.10 | 3.x WIP |
21
17
 
22
18
  `idf-build-apps` is following the semantic versioning. The major version of `idf-build-apps` is the same as the ESP-IDF version it supports. For example, `idf-build-apps` 1.x supports ESP-IDF 4.x, and `idf-build-apps` 2.x supports ESP-IDF 5.x.
23
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idf-build-apps
3
- Version: 2.11.2
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
@@ -0,0 +1,122 @@
1
+ ##############
2
+ Build Status
3
+ ##############
4
+
5
+ This page explains the different build statuses that apps can have during the build process and what each status means.
6
+
7
+ Each app in `idf-build-apps` has a build status that indicates its current state in the build pipeline. Understanding these statuses is crucial for interpreting build results and troubleshooting issues.
8
+
9
+ ********************
10
+ Available Statuses
11
+ ********************
12
+
13
+ The following build statuses are available:
14
+
15
+ ``unknown``
16
+ ===========
17
+
18
+ The default status when an app is first discovered. The app's build status has not yet been determined.
19
+
20
+ This status indicates that:
21
+
22
+ - The app has been found but not yet processed
23
+ - Further ``idf.py reconfigure`` is needed to determine if the build can proceed
24
+
25
+ ``disabled``
26
+ ============
27
+
28
+ The app supports the target architecture, but has been disabled by manifest rules or configuration.
29
+
30
+ This status indicates that:
31
+
32
+ - The app is compatible with the target but explicitly disabled
33
+ - Manifest rules contain ``disable`` clauses that match this app and target combination
34
+
35
+ Apps with this status will not be built unless specifically included with ``--include-disabled-apps``.
36
+
37
+ ``skipped``
38
+ ===========
39
+
40
+ The app supports the target and could potentially be built, but has been skipped due to dependency-driven build logic or dry run mode.
41
+
42
+ This status indicates that:
43
+
44
+ - **Dependency-driven build**: The app doesn't depend on any of the modified components/files in the current build
45
+ - **Dry run mode**: The ``--dry-run`` flag was used, so no actual building occurs
46
+
47
+ Apps with this status will not be built unless specifically included with ``--include-skipped-apps``.
48
+
49
+ ``should be built``
50
+ ===================
51
+
52
+ The app should be built but hasn't been built yet.
53
+
54
+ This status indicates that:
55
+
56
+ - The app has passed all checks and is ready for building
57
+ - Dependency analysis shows the app should be included in this build
58
+ - The app has not been built yet in the current run
59
+
60
+ ``build failed``
61
+ ================
62
+
63
+ The app build process was attempted but failed.
64
+
65
+ This status indicates that:
66
+
67
+ - The build process started but encountered an error
68
+ - Compilation, linking, or other build steps failed
69
+ - The failure reason is typically available in the build comment and build logs
70
+
71
+ ``build success``
72
+ =================
73
+
74
+ The app was successfully built.
75
+
76
+ This status indicates that:
77
+
78
+ - All build steps completed without errors
79
+ - The app binary was generated successfully
80
+ - The build process finished normally
81
+
82
+ ************************
83
+ Status Transition Flow
84
+ ************************
85
+
86
+ The typical flow of build statuses is:
87
+
88
+ #. **unknown** → Initial state when app is discovered
89
+ #. **unknown** → **disabled** (if app doesn't support target or is disabled by manifest)
90
+ #. **unknown** → **skipped** (if dependency checks show app shouldn't be built)
91
+ #. **unknown** → **should be built** (if app passes all checks)
92
+ #. **should be built** → **build failed** (if build encounters errors)
93
+ #. **should be built** → **build success** (if build completes successfully)
94
+
95
+ .. note::
96
+
97
+ Apps in ``disabled`` or ``skipped`` status will not transition to ``should be built`` unless the conditions change or specific inclusion flags are used.
98
+
99
+ **********************
100
+ Viewing Build Status
101
+ **********************
102
+
103
+ You can view the build status of apps in several ways:
104
+
105
+ - **JSON output**: Use ``--json-output`` to save detailed status information
106
+ - **Build logs**: Status and comments are included in build output
107
+ - **Summary reports**: Final status counts are shown at the end of builds
108
+
109
+ **************************
110
+ Including Apps by Status
111
+ **************************
112
+
113
+ By default, only apps with ``unknown``, ``should be built``, ``build failed``, and ``build success`` statuses are processed. You can include additional apps using:
114
+
115
+ - ``--include-disabled-apps``: Include apps with ``disabled`` status
116
+ - ``--include-skipped-apps``: Include apps with ``skipped`` status
117
+
118
+ This is useful for:
119
+
120
+ - Testing disabled apps during development
121
+ - Force-building apps that would normally be skipped
122
+ - Comprehensive testing regardless of dependency analysis
@@ -11,6 +11,7 @@ This documentation is for idf-build-apps. idf-build-apps is a tool that allows d
11
11
  explanations/config_rules
12
12
  explanations/find
13
13
  explanations/build
14
+ explanations/build_status
14
15
  explanations/dependency_driven_build
15
16
 
16
17
  .. toctree::
@@ -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.2'
11
+ __version__ = '2.12.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -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'
@@ -240,10 +240,20 @@ 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. '
@@ -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)