idf_build_apps 0.6.1__tar.gz → 1.0.0.dev0__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 (51) hide show
  1. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/CHANGELOG.md +27 -0
  2. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/PKG-INFO +1 -1
  3. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/__init__.py +1 -1
  4. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/app.py +39 -9
  5. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/config.py +7 -3
  6. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/finder.py +24 -7
  7. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/main.py +112 -89
  8. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/manifest/manifest.py +9 -4
  9. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/setup.py +1 -1
  10. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/tests/conftest.py +1 -1
  11. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/tests/test_build.py +31 -4
  12. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/tests/test_finder.py +92 -4
  13. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.editorconfig +0 -0
  14. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.git-blame-ignore-revs +0 -0
  15. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.gitattributes +0 -0
  16. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/check-pre-commit.yml +0 -0
  17. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/issue_comment.yml +0 -0
  18. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/new_issues.yml +0 -0
  19. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/new_prs.yml +0 -0
  20. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/publish-pypi.yml +0 -0
  21. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/test-build-docs.yml +0 -0
  22. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.github/workflows/test-build-idf-apps.yml +0 -0
  23. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.gitignore +0 -0
  24. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.pre-commit-config.yaml +0 -0
  25. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/.readthedocs.yml +0 -0
  26. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/CONTRIBUTING.md +0 -0
  27. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/LICENSE +0 -0
  28. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/README.md +0 -0
  29. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/CHANGELOG.md +0 -0
  30. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/CONTRIBUTING.md +0 -0
  31. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/Makefile +0 -0
  32. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/_static/espressif-logo.svg +0 -0
  33. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/_static/theme_overrides.css +0 -0
  34. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/_templates/layout.html +0 -0
  35. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/api.rst +0 -0
  36. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/conf.py +0 -0
  37. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/config_file.md +0 -0
  38. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/find_build.md +0 -0
  39. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/index.rst +0 -0
  40. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/docs/manifest.md +0 -0
  41. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/__main__.py +0 -0
  42. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/constants.py +0 -0
  43. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/log.py +0 -0
  44. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/manifest/__init__.py +0 -0
  45. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/manifest/if_parser.py +0 -0
  46. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/manifest/soc_header.py +0 -0
  47. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/idf_build_apps/utils.py +0 -0
  48. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/license_header.txt +0 -0
  49. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/pyproject.toml +0 -0
  50. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/tests/test_manifest.py +0 -0
  51. {idf-build-apps-0.6.1 → idf_build_apps-1.0.0.dev0}/tests/test_utils.py +0 -0
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased - 1.0.0]
6
+
7
+ ## Added
8
+
9
+ - Support keyword `depends_filepatterns` in the manfiest file
10
+
11
+ ## BREAKING CHANGES
12
+
13
+ - Renames
14
+ - Members
15
+ - `App.requires_components` renamed to `App.depends_components`
16
+ - `FolderRule.requires_components` renamed to `FolderRule.depends_components`
17
+ - `Manifest.requires_components()` renamed to `Manifest.depends_components()`
18
+ - Parameters
19
+ - `App.build()` parameter `depends_on_components` renamed to `modified_components`
20
+ - `App.is_modified()` parameter `depends_on_components` renamed to `modified_components`
21
+ - `find_apps()` parameter `depends_on_components` renamed to `modified_components`
22
+ - `find_apps()` parameter `depends_on_files` renamed to `modified_files`
23
+ - `build_apps()` parameter `depends_on_components` renamed to `modified_components`
24
+ - `build_apps()` parameter `depends_on_files` renamed to `modified_files`
25
+ - CLI Options
26
+ - `--depends-on-components` renamed to `--modified-components`
27
+ - `--depends-on-files` renamed to `--modified-files`
28
+ - Reorder Parameters
29
+ - `find_apps()`
30
+ - `build_apps()`
31
+
5
32
  ## [0.6.1] (2023-05-10)
6
33
 
7
34
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: idf-build-apps
3
- Version: 0.6.1
3
+ Version: 1.0.0.dev0
4
4
  Summary: Tools for building ESP-IDF related apps.
5
5
  Author-email: Fu Hanxi <fuhanxi@espressif.com>
6
6
  Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
@@ -7,7 +7,7 @@ Tools for building ESP-IDF related apps.
7
7
 
8
8
  import logging
9
9
 
10
- __version__ = '0.6.1'
10
+ __version__ = '1.0.0.dev0'
11
11
 
12
12
  LOGGER = logging.getLogger('idf_build_apps')
13
13
 
@@ -40,6 +40,7 @@ from .utils import (
40
40
  find_first_match,
41
41
  rmdir,
42
42
  subprocess_run,
43
+ to_absolute_path,
43
44
  to_list,
44
45
  )
45
46
 
@@ -126,6 +127,13 @@ class App(object):
126
127
  self.build_path,
127
128
  )
128
129
 
130
+ def __str__(self):
131
+ return 'App {}, target {}, sdkconfig {}'.format(
132
+ self.app_dir,
133
+ self.target,
134
+ self.sdkconfig_path or '(default)',
135
+ )
136
+
129
137
  def __lt__(self, other):
130
138
  if self.app_dir != other.app_dir:
131
139
  return self.app_dir < other.app_dir
@@ -346,9 +354,16 @@ class App(object):
346
354
  return [os.path.realpath(file) for file in self._sdkconfig_files]
347
355
 
348
356
  @property
349
- def requires_components(self): # type: () -> list[str]
357
+ def depends_components(self): # type: () -> list[str]
350
358
  if self.MANIFEST:
351
- return self.MANIFEST.requires_components(self.app_dir)
359
+ return self.MANIFEST.depends_components(self.app_dir)
360
+
361
+ return []
362
+
363
+ @property
364
+ def depends_filepatterns(self): # type: () -> list[str]
365
+ if self.MANIFEST:
366
+ return self.MANIFEST.depends_filepatterns(self.app_dir)
352
367
 
353
368
  return []
354
369
 
@@ -376,8 +391,9 @@ class App(object):
376
391
  @abstractmethod
377
392
  def build(
378
393
  self,
379
- depends_on_components=None, # type: list[str] | str | None
394
+ modified_components=None, # type: list[str] | str | None
380
395
  check_component_dependencies=False, # type: bool
396
+ is_modified=False, # type: bool
381
397
  ): # type: (...) -> bool
382
398
  pass
383
399
 
@@ -440,6 +456,19 @@ class App(object):
440
456
  def is_app(cls, path): # type: (str) -> bool
441
457
  raise NotImplementedError('Please implement this function in sub classes')
442
458
 
459
+ def is_modified(self, modified_files): # type: (list[str] | None) -> bool
460
+ _app_dir_fullpath = to_absolute_path(self.app_dir)
461
+ if modified_files:
462
+ for f in modified_files:
463
+ _f_fullpath = to_absolute_path(f)
464
+ if _f_fullpath.parts[-1].endswith('.md'):
465
+ continue
466
+
467
+ if _app_dir_fullpath in _f_fullpath.parents:
468
+ return True
469
+
470
+ return False
471
+
443
472
 
444
473
  class CMakeApp(App):
445
474
  BUILD_SYSTEM = 'cmake'
@@ -464,8 +493,9 @@ class CMakeApp(App):
464
493
 
465
494
  def build(
466
495
  self,
467
- depends_on_components=None, # type: list[str] | str | None
496
+ modified_components=None, # type: list[str] | str | None
468
497
  check_component_dependencies=False, # type: bool
498
+ is_modified=False, # type: bool
469
499
  ): # type: (...) -> bool
470
500
  LOGGER.debug('=> Preparing...')
471
501
  if self.work_dir != self.app_dir:
@@ -524,8 +554,8 @@ class CMakeApp(App):
524
554
  '-DSDKCONFIG_DEFAULTS={}'.format(';'.join(self.sdkconfig_files) if self.sdkconfig_files else ';'),
525
555
  ]
526
556
 
527
- depends_on_components = to_list(depends_on_components)
528
- if depends_on_components is not None and check_component_dependencies:
557
+ modified_components = to_list(modified_components)
558
+ if modified_components is not None and check_component_dependencies and not is_modified:
529
559
  subprocess_run(
530
560
  common_args + ['reconfigure'],
531
561
  log_terminal=False if self.build_log_path else True,
@@ -537,12 +567,12 @@ class CMakeApp(App):
537
567
  with open(os.path.join(self.build_path, PROJECT_DESCRIPTION_JSON)) as fr:
538
568
  build_components = set(item for item in json.load(fr)['build_components'] if item)
539
569
 
540
- if not set(depends_on_components).intersection(set(build_components)):
570
+ if not set(modified_components).intersection(set(build_components)):
541
571
  LOGGER.info(
542
- '=> Skip building... app %s depends on components: %s, while current build based on component dependencies: %s',
572
+ '=> Skip building... app %s depends components: %s, while current build modified components: %s',
543
573
  self.app_dir,
544
574
  build_components,
545
- depends_on_components,
575
+ modified_components,
546
576
  )
547
577
  return False
548
578
 
@@ -6,6 +6,10 @@ from pathlib import (
6
6
  Path,
7
7
  )
8
8
 
9
+ from idf_build_apps.utils import (
10
+ to_absolute_path,
11
+ )
12
+
9
13
 
10
14
  class InvalidTomlError(SystemExit):
11
15
  def __init__(self, filepath, msg): # type: (str | Path, str) -> None
@@ -60,12 +64,12 @@ def _get_config_from_path(dirpath): # type: (Path) -> (dict | None, Path)
60
64
 
61
65
 
62
66
  def get_valid_config(starts_from=os.getcwd(), custom_path=None): # type: (str, str | None) -> dict | None
63
- root_dir = Path('/').resolve()
64
- cur_dir = Path(os.path.expanduser(starts_from)).resolve()
67
+ root_dir = to_absolute_path('/')
68
+ cur_dir = to_absolute_path(starts_from)
65
69
 
66
70
  config = None
67
71
  if custom_path and os.path.isfile(custom_path):
68
- config, filepath = _get_config_from_file(Path(os.path.expanduser(custom_path)).resolve())
72
+ config, filepath = _get_config_from_file(to_absolute_path(custom_path))
69
73
  if config is not None:
70
74
  # use print here since the verbose settings may be set in the config file
71
75
  print('Using custom config file: {}'.format(filepath))
@@ -16,6 +16,7 @@ from .app import (
16
16
  )
17
17
  from .utils import (
18
18
  config_rules_from_str,
19
+ files_matches_patterns,
19
20
  to_list,
20
21
  )
21
22
 
@@ -31,24 +32,40 @@ def _get_apps_from_path(
31
32
  size_json_path=None, # type: str | None
32
33
  check_warnings=False, # type: bool
33
34
  preserve=True, # type: bool
34
- depends_on_components=None, # type: list[str] | str | None
35
+ manifest_rootpath=None, # type: str | None
36
+ modified_components=None, # type: list[str] | str | None
37
+ modified_files=None, # type: list[str] | str | None,
35
38
  check_component_dependencies=False, # type: bool
36
39
  sdkconfig_defaults_str=None, # type: str | None
37
40
  ): # type: (...) -> list[App]
38
- depends_on_components = to_list(depends_on_components)
41
+ modified_components = to_list(modified_components)
42
+ modified_files = to_list(modified_files)
39
43
 
40
44
  def _validate_app(_app): # type: (App) -> bool
41
45
  if target not in _app.supported_targets:
42
46
  LOGGER.debug('=> Skipping. %s only supports targets: %s', _app, ', '.join(_app.supported_targets))
43
47
  return False
44
48
 
45
- if _app.requires_components and check_component_dependencies:
46
- if not set(_app.requires_components).intersection(set(depends_on_components)):
49
+ if _app.is_modified(modified_files):
50
+ return True
51
+
52
+ if _app.depends_components and check_component_dependencies:
53
+ if not set(_app.depends_components).intersection(set(modified_components)):
54
+ LOGGER.debug(
55
+ '=> Skipping. %s requires components: %s, but you passed "--modified-components %s"',
56
+ _app,
57
+ ', '.join(_app.depends_components),
58
+ ', '.join(modified_components),
59
+ )
60
+ return False
61
+
62
+ if _app.depends_filepatterns and check_component_dependencies:
63
+ if not files_matches_patterns(modified_files, _app.depends_filepatterns, manifest_rootpath):
47
64
  LOGGER.debug(
48
- '=> Skipping. %s requires components: %s, but you passed "--depends-on-components %s"',
65
+ '=> Skipping. %s depends on file patterns: %s, but you passed "--modified-files %s"',
49
66
  _app,
50
- ', '.join(_app.requires_components),
51
- ', '.join(depends_on_components),
67
+ ', '.join(_app.depends_filepatterns),
68
+ ', '.join(modified_files),
52
69
  )
53
70
  return False
54
71
 
@@ -41,6 +41,7 @@ from .utils import (
41
41
  InvalidCommand,
42
42
  files_matches_patterns,
43
43
  get_parallel_start_stop,
44
+ to_absolute_path,
44
45
  to_list,
45
46
  )
46
47
 
@@ -50,6 +51,32 @@ except ImportError:
50
51
  pass
51
52
 
52
53
 
54
+ def _check_components_dependency(
55
+ manifest_rootpath, # type: str
56
+ modified_components, # type: list[str] | None
57
+ modified_files, # type: list[str] | None
58
+ ignore_component_dependencies_file_patterns, # type: list[str] | None
59
+ ): # type: (...) -> bool
60
+ # not check since `--modified-components` is not passed
61
+ if modified_components is None and modified_files is None:
62
+ return False
63
+
64
+ # not check since `--ignore-component-dependency-file-pattern` is passed and matched
65
+ if (
66
+ ignore_component_dependencies_file_patterns
67
+ and modified_files
68
+ and files_matches_patterns(modified_files, ignore_component_dependencies_file_patterns, manifest_rootpath)
69
+ ):
70
+ LOGGER.debug(
71
+ 'Skipping check component dependencies for apps since files %s matches patterns: %s',
72
+ ', '.join(modified_files),
73
+ ', '.join(ignore_component_dependencies_file_patterns),
74
+ )
75
+ return False
76
+
77
+ return True
78
+
79
+
53
80
  def find_apps(
54
81
  paths, # type: list[str] | str
55
82
  target, # type: str
@@ -63,12 +90,12 @@ def find_apps(
63
90
  size_json_path=None, # type: str | None
64
91
  check_warnings=False, # type: bool
65
92
  preserve=True, # type: bool
93
+ manifest_rootpath=None, # type: str | None
66
94
  manifest_files=None, # type: list[str] | str | None
67
95
  default_build_targets=None, # type: list[str] | str | None
68
- depends_on_components=None, # type: list[str] | str | None
69
- manifest_rootpath=None, # type: str | None
96
+ modified_components=None, # type: list[str] | str | None
97
+ modified_files=None, # type: list[str] | str | None
70
98
  ignore_component_dependencies_file_patterns=None, # type: list[str] | str | None
71
- depends_on_files=None, # type: list[str] | str | None
72
99
  sdkconfig_defaults=None, # type: str | None
73
100
  ): # type: (...) -> list[App]
74
101
  """
@@ -101,22 +128,20 @@ def find_apps(
101
128
  :type check_warnings: bool
102
129
  :param preserve: Preserve the built binaries or not
103
130
  :type preserve: bool
131
+ :param manifest_rootpath: The root path of the manifest files. Usually the folders specified in the manifest files
132
+ are relative paths. Use the current directory if not specified
133
+ :type manifest_rootpath: str | None
104
134
  :param manifest_files: paths of the manifest files
105
135
  :type manifest_files: list[str] | str | None
106
136
  :param default_build_targets: default build targets used in manifest files
107
137
  :type default_build_targets: list[str] | str | None
108
- :param depends_on_components: app with ``requires_components`` set in the corresponding manifest files will only
109
- be built if it depends on any of the specified components
110
- :type depends_on_components: list[str] | str | None
111
- :param manifest_rootpath: The root path of the manifest files. Usually the folders specified in the manifest files
112
- are relative paths. Use the current directory if not specified
113
- :type manifest_rootpath: str | None
114
- :param ignore_component_dependencies_file_patterns: file patterns that use to ignore checking the component
138
+ :param modified_components: modified components
139
+ :type modified_components: list[str] | str | None
140
+ :param modified_files: modified files
141
+ :type modified_files: list[str] | str | None
142
+ :param ignore_component_dependencies_file_patterns: file patterns that used for ignoring checking the component
115
143
  dependencies
116
144
  :type ignore_component_dependencies_file_patterns: list[str] | str | None
117
- :param depends_on_files: skip check app's component dependencies if any of the specified files matches
118
- ``ignore_component_dependencies_file_patterns``
119
- :type depends_on_files: list[str] | str | None
120
145
  :param sdkconfig_defaults: semicolon-separated string, pass to idf.py -DSDKCONFIG_DEFAULTS if specified,
121
146
  also could be set via environment variables "SDKCONFIG_DEFAULTS"
122
147
  :type sdkconfig_defaults: str | None
@@ -129,7 +154,7 @@ def find_apps(
129
154
  FolderRule.DEFAULT_BUILD_TARGETS = default_build_targets
130
155
 
131
156
  # always set the manifest rootpath at the very beginning of find_apps in case ESP-IDF switches the branch.
132
- Manifest.ROOTPATH = Path(manifest_rootpath or os.curdir).resolve()
157
+ Manifest.ROOTPATH = to_absolute_path(manifest_rootpath or os.curdir)
133
158
 
134
159
  if manifest_files:
135
160
  rules = set()
@@ -139,22 +164,9 @@ def find_apps(
139
164
  manifest = Manifest(rules)
140
165
  App.MANIFEST = manifest
141
166
 
142
- depends_on_components = to_list(depends_on_components)
143
- if depends_on_components is None:
144
- check_component_dependencies = False
145
- elif (
146
- ignore_component_dependencies_file_patterns
147
- and depends_on_files
148
- and files_matches_patterns(depends_on_files, ignore_component_dependencies_file_patterns)
149
- ):
150
- LOGGER.debug(
151
- 'Skipping check component dependencies for apps since files %s matches patterns: %s',
152
- ', '.join(depends_on_files),
153
- ', '.join(ignore_component_dependencies_file_patterns),
154
- )
155
- check_component_dependencies = False
156
- else:
157
- check_component_dependencies = True
167
+ modified_components = to_list(modified_components)
168
+ modified_files = to_list(modified_files)
169
+ ignore_component_dependencies_file_patterns = to_list(ignore_component_dependencies_file_patterns)
158
170
 
159
171
  apps = []
160
172
  if target == 'all':
@@ -178,8 +190,15 @@ def find_apps(
178
190
  size_json_path=size_json_path,
179
191
  check_warnings=check_warnings,
180
192
  preserve=preserve,
181
- depends_on_components=depends_on_components,
182
- check_component_dependencies=check_component_dependencies,
193
+ manifest_rootpath=manifest_rootpath,
194
+ modified_components=modified_components,
195
+ modified_files=modified_files,
196
+ check_component_dependencies=_check_components_dependency(
197
+ manifest_rootpath,
198
+ modified_components,
199
+ modified_files,
200
+ ignore_component_dependencies_file_patterns,
201
+ ),
183
202
  sdkconfig_defaults_str=sdkconfig_defaults,
184
203
  )
185
204
  )
@@ -201,10 +220,10 @@ def build_apps(
201
220
  ignore_warning_strs=None, # type: list[str] | None
202
221
  ignore_warning_file=None, # type: t.TextIO | None
203
222
  copy_sdkconfig=False, # type: bool
204
- depends_on_components=None, # type: list[str] | str | None
205
223
  manifest_rootpath=None, # type: str | None
224
+ modified_components=None, # type: list[str] | str | None
225
+ modified_files=None, # type: list[str] | str | None
206
226
  ignore_component_dependencies_file_patterns=None, # type: list[str] | str | None
207
- depends_on_files=None, # type: list[str] | str | None
208
227
  ): # type: (...) -> (int, list[App]) | int
209
228
  """
210
229
  Build all the specified apps
@@ -232,24 +251,25 @@ def build_apps(
232
251
  :type ignore_warning_file: list[str] | None
233
252
  :param copy_sdkconfig: copy the sdkconfig file to the build directory or not
234
253
  :type copy_sdkconfig: bool
235
- :param depends_on_components: app with ``requires_components`` set in the corresponding manifest files would only be
236
- built if it depends on any of the specified components
237
- :type depends_on_components: list[str] | str | None
238
254
  :param manifest_rootpath: The root path of the manifest files. Usually the folders specified in the manifest files
239
255
  are relative paths. Use the current directory if not specified
240
256
  :type manifest_rootpath: str | None
241
- :param ignore_component_dependencies_file_patterns: file patterns that use to ignore checking the component
257
+ :param modified_components: modified components
258
+ :type modified_components: list[str] | str | None
259
+ :param modified_files: modified files
260
+ :type modified_files: list[str] | str | None
261
+ :param ignore_component_dependencies_file_patterns: file patterns that used for ignoring checking the component
242
262
  dependencies
243
263
  :type ignore_component_dependencies_file_patterns: list[str] | str | None
244
- :param depends_on_files: skip check app's component dependencies if any of the specified files matches
245
- ``ignore_component_dependencies_file_patterns``
246
- :type depends_on_files: list[str] | str | None
247
- :return: exit_code, built_apps if specified ``depends_on_components``
264
+ :return: (exit_code, built_apps) if specified ``modified_components``
248
265
  :rtype: int, list[App]
249
- :return: exit_code if not specified ``depends_on_components``
266
+ :return: exit_code if not specified ``modified_components``
250
267
  :rtype: int
251
268
  """
252
269
  apps = to_list(apps) # type: list[App]
270
+ modified_components = to_list(modified_components)
271
+ modified_files = to_list(modified_files)
272
+ ignore_component_dependencies_file_patterns = to_list(ignore_component_dependencies_file_patterns)
253
273
 
254
274
  ignore_warnings_regexes = []
255
275
  if ignore_warning_strs:
@@ -260,26 +280,6 @@ def build_apps(
260
280
  ignore_warnings_regexes.append(re.compile(s.strip()))
261
281
  App.IGNORE_WARNS_REGEXES = ignore_warnings_regexes
262
282
 
263
- depends_on_components = to_list(depends_on_components)
264
- # here depends_on_components [''] means that the user use --depends-on-components
265
- # the ones with `requires_components` are already been filtered out
266
- # if we skip all build, that would be too aggressive
267
- if depends_on_components is None or depends_on_components == ['']:
268
- check_component_dependencies = False
269
- elif (
270
- ignore_component_dependencies_file_patterns
271
- and depends_on_files
272
- and files_matches_patterns(depends_on_files, ignore_component_dependencies_file_patterns, manifest_rootpath)
273
- ):
274
- LOGGER.debug(
275
- 'Skipping check component dependencies for apps since files %s matches patterns: %s',
276
- ', '.join(depends_on_files),
277
- ', '.join(ignore_component_dependencies_file_patterns),
278
- )
279
- check_component_dependencies = False
280
- else:
281
- check_component_dependencies = True
282
-
283
283
  start, stop = get_parallel_start_stop(len(apps), parallel_count, parallel_index)
284
284
  LOGGER.info('Total %s apps. running build for app %s-%s', len(apps), start, stop)
285
285
 
@@ -332,6 +332,8 @@ def build_apps(
332
332
  Path(f).touch()
333
333
 
334
334
  actual_built_apps = []
335
+ built_apps = [] # type: list[App]
336
+ skipped_apps = [] # type: list[App]
335
337
  for i, app in enumerate(apps):
336
338
  index = i + 1 # we use 1-based
337
339
  if index < start or index > stop:
@@ -346,8 +348,11 @@ def build_apps(
346
348
  is_built = False
347
349
  try:
348
350
  is_built = app.build(
349
- depends_on_components=depends_on_components,
350
- check_component_dependencies=check_component_dependencies,
351
+ modified_components=modified_components,
352
+ check_component_dependencies=_check_components_dependency(
353
+ manifest_rootpath, modified_components, modified_files, ignore_component_dependencies_file_patterns
354
+ ),
355
+ is_modified=app.is_modified(modified_files),
351
356
  )
352
357
  except BuildError as e:
353
358
  LOGGER.error(str(e))
@@ -355,11 +360,16 @@ def build_apps(
355
360
  failed_apps.append(app)
356
361
  exit_code = 1
357
362
  else:
358
- if depends_on_components is not None:
363
+ if modified_components is not None:
359
364
  return 1, actual_built_apps
360
365
  else:
361
366
  return 1
362
367
  finally:
368
+ if is_built:
369
+ built_apps.append(app)
370
+ else:
371
+ skipped_apps.append(app)
372
+
363
373
  if is_built:
364
374
  actual_built_apps.append(app)
365
375
 
@@ -405,12 +415,22 @@ def build_apps(
405
415
 
406
416
  LOGGER.info('') # add one empty line for separating different builds
407
417
 
418
+ if built_apps:
419
+ LOGGER.info('Built the following apps:')
420
+ for app in built_apps:
421
+ LOGGER.info(' %s', app)
422
+
423
+ if skipped_apps:
424
+ LOGGER.info('Skipped the following apps:')
425
+ for app in skipped_apps:
426
+ LOGGER.info(' %s', app)
427
+
408
428
  if failed_apps:
409
429
  LOGGER.error('Build failed for the following apps:')
410
430
  for app in failed_apps:
411
431
  LOGGER.error(' %s', app)
412
432
 
413
- if depends_on_components is not None:
433
+ if modified_components is not None:
414
434
  return exit_code, actual_built_apps
415
435
  else:
416
436
  return exit_code
@@ -575,28 +595,31 @@ def get_parser(): # type: () -> argparse.ArgumentParser
575
595
  ),
576
596
  )
577
597
  common_args.add_argument(
578
- '--depends-on-components',
598
+ '--modified-components',
579
599
  nargs='*',
580
600
  default=None,
581
- help='space-separated components list, app with `requires_components` set in the corresponding manifest files '
582
- 'would only be built if depends on any of the specified components',
601
+ help='space-separated list which specifies the modified components. app with `depends_components` set in the '
602
+ 'corresponding manifest files would only be built if depends on any of the specified components.',
583
603
  )
584
604
  common_args.add_argument(
585
- '-if',
586
- '--ignore-component-dependencies-file-patterns',
605
+ '--modified-files',
587
606
  nargs='*',
588
607
  default=None,
589
- help='ignore component dependencies when changed files matches any of the specified file patterns. must used '
590
- 'with --depends-on-files',
608
+ help='space-separated list which specifies the modified files. app with `depends_filepatterns` set in the '
609
+ 'corresponding manifest files would only be built if any of the specified file pattern matches any of the '
610
+ 'specified modified files.',
591
611
  )
592
612
  common_args.add_argument(
593
- '--depends-on-files',
613
+ '-if',
614
+ '--ignore-component-dependencies-file-patterns',
594
615
  nargs='*',
595
616
  default=None,
596
- help='space-separated file pattern list, the `requires_components` set in the manifest files will be '
597
- 'ignored when specified files match any of the specified file patterns defined with '
598
- '--ignore-component-dependencies-file-patterns. Must used with --ignore-component-dependencies-file-patterns',
617
+ help='space-separated list which specifies the file patterns used for ignoring the component dependencies. '
618
+ 'The `depends_components` and `depends_filepatterns` set in the manifest files will be ignored when any of '
619
+ 'the specified file patterns matches any of the modified files. Must be used together with '
620
+ '--modified-files',
599
621
  )
622
+
600
623
  common_args.add_argument(
601
624
  '--no-color',
602
625
  action='store_true',
@@ -701,11 +724,11 @@ def validate_args(parser, args): # type: (argparse.ArgumentParser, argparse.Nam
701
724
 
702
725
  args.default_build_targets = default_build_targets
703
726
 
704
- if (args.ignore_component_dependencies_file_patterns is None) != (args.depends_on_files is None):
705
- raise InvalidCommand(
706
- 'Must specify both "--ignore-component-dependencies-file-patterns" and "--depends-on-files" '
707
- 'or neither of them'
708
- )
727
+ if args.ignore_component_dependencies_file_patterns:
728
+ if args.modified_files is None:
729
+ raise InvalidCommand(
730
+ 'Must specify "--ignore-component-dependencies-file-patterns" with "--modified-files", '
731
+ )
709
732
 
710
733
 
711
734
  def apply_config_args(args): # type: (argparse.Namespace) -> None
@@ -738,12 +761,12 @@ def main():
738
761
  build_log_path=args.build_log,
739
762
  size_json_path=args.size_file,
740
763
  check_warnings=args.check_warnings,
764
+ manifest_rootpath=args.manifest_rootpath,
741
765
  manifest_files=args.manifest_file,
742
766
  default_build_targets=args.default_build_targets,
743
- depends_on_components=args.depends_on_components,
744
- manifest_rootpath=args.manifest_rootpath,
767
+ modified_components=args.modified_components,
768
+ modified_files=args.modified_files,
745
769
  ignore_component_dependencies_file_patterns=args.ignore_component_dependencies_file_patterns,
746
- depends_on_files=args.depends_on_files,
747
770
  sdkconfig_defaults=args.sdkconfig_defaults,
748
771
  )
749
772
 
@@ -774,13 +797,13 @@ def main():
774
797
  ignore_warning_strs=args.ignore_warning_str,
775
798
  ignore_warning_file=args.ignore_warning_file,
776
799
  copy_sdkconfig=args.copy_sdkconfig,
777
- depends_on_components=args.depends_on_components,
778
800
  manifest_rootpath=args.manifest_rootpath,
801
+ modified_components=args.modified_components,
802
+ modified_files=args.modified_files,
779
803
  ignore_component_dependencies_file_patterns=args.ignore_component_dependencies_file_patterns,
780
- depends_on_files=args.depends_on_files,
781
804
  )
782
805
 
783
- if args.depends_on_components is not None:
806
+ if args.modified_components is not None:
784
807
  sys.exit(res[0])
785
808
  else:
786
809
  sys.exit(res)
@@ -43,7 +43,8 @@ class FolderRule:
43
43
  enable=None, # type: list[dict[str, str]] | None
44
44
  disable=None, # type: list[dict[str, str]] | None
45
45
  disable_test=None, # type: list[dict[str, str]] | None
46
- requires_components=None, # type: list[str] | None
46
+ depends_components=None, # type: list[str] | None
47
+ depends_filepatterns=None, # type: list[str] | None
47
48
  ): # type: (...) -> None
48
49
  self.folder = folder.resolve()
49
50
 
@@ -56,7 +57,8 @@ class FolderRule:
56
57
  self.enable = [IfClause(**clause) for clause in enable] if enable else []
57
58
  self.disable = [IfClause(**clause) for clause in disable] if disable else []
58
59
  self.disable_test = [IfClause(**clause) for clause in disable_test] if disable_test else []
59
- self.requires_components = requires_components or []
60
+ self.depends_components = depends_components or []
61
+ self.depends_filepatterns = depends_filepatterns or []
60
62
 
61
63
  def __hash__(self):
62
64
  return hash(self.folder)
@@ -168,5 +170,8 @@ class Manifest:
168
170
  ): # type: (str, str | None, str | None) -> list[str]
169
171
  return self._most_suitable_rule(folder).enable_test_targets(default_sdkconfig_target, config_name)
170
172
 
171
- def requires_components(self, folder): # type: (str) -> list[str]
172
- return self._most_suitable_rule(folder).requires_components
173
+ def depends_components(self, folder): # type: (str) -> list[str]
174
+ return self._most_suitable_rule(folder).depends_components
175
+
176
+ def depends_filepatterns(self, folder): # type: (str) -> list[str]
177
+ return self._most_suitable_rule(folder).depends_filepatterns
@@ -26,7 +26,7 @@ entry_points = \
26
26
  {'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
27
27
 
28
28
  setup(name='idf-build-apps',
29
- version='0.6.1',
29
+ version='1.0.0.dev0',
30
30
  description='Tools for building ESP-IDF related apps.',
31
31
  author=None,
32
32
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -17,7 +17,7 @@ def clean_cls_attr():
17
17
 
18
18
  @pytest.fixture(autouse=True)
19
19
  def setup_logging_debug():
20
- setup_logging(2)
20
+ setup_logging(1)
21
21
 
22
22
 
23
23
  def create_project(name, folder):
@@ -24,7 +24,7 @@ class TestBuild:
24
24
  assert 'Project build complete.' in captured.out
25
25
 
26
26
  @pytest.mark.parametrize(
27
- 'depends_on_components, check_component_dependencies, assert_build_done',
27
+ 'modified_components, check_component_dependencies, assert_build_done',
28
28
  [
29
29
  (None, True, True),
30
30
  ([], True, False),
@@ -36,13 +36,13 @@ class TestBuild:
36
36
  (['soc', 'fake'], True, True),
37
37
  ],
38
38
  )
39
- def test_build_with_depends_on_components(
40
- self, tmpdir, capsys, depends_on_components, check_component_dependencies, assert_build_done
39
+ def test_build_with_modified_components(
40
+ self, tmpdir, capsys, modified_components, check_component_dependencies, assert_build_done
41
41
  ):
42
42
  path = IDF_PATH / 'examples' / 'get-started' / 'hello_world'
43
43
 
44
44
  CMakeApp(str(path), 'esp32', work_dir=str(tmpdir / 'test')).build(
45
- depends_on_components=depends_on_components,
45
+ modified_components=modified_components,
46
46
  check_component_dependencies=check_component_dependencies,
47
47
  )
48
48
 
@@ -53,6 +53,33 @@ class TestBuild:
53
53
  else:
54
54
  assert 'Project build complete.' not in captured.out
55
55
 
56
+ @pytest.mark.parametrize(
57
+ 'modified_files, is_built',
58
+ [
59
+ ('/foo', False),
60
+ (str(IDF_PATH / 'examples' / 'README.md'), False),
61
+ ([str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md')], False),
62
+ (
63
+ [
64
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md'),
65
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.c'),
66
+ ],
67
+ True,
68
+ ),
69
+ ],
70
+ )
71
+ def test_build_with_modified_files(self, modified_files, is_built):
72
+ test_dir = str(IDF_PATH / 'examples' / 'get-started' / 'hello_world')
73
+
74
+ app = CMakeApp(test_dir, 'esp32')
75
+ built = app.build(
76
+ modified_components=[],
77
+ check_component_dependencies=True,
78
+ is_modified=app.is_modified(modified_files),
79
+ )
80
+
81
+ assert built == is_built
82
+
56
83
 
57
84
  @pytest.mark.skipif(not shutil.which('idf.py'), reason='idf.py not found')
58
85
  def test_idf_version_keywords_type():
@@ -102,7 +102,7 @@ get-started:
102
102
  assert find_apps(str(test_dir), 'esp32', recursive=True, manifest_files=str(yaml_file)) == apps
103
103
 
104
104
  @pytest.mark.parametrize(
105
- 'depends_on_components, could_find_apps',
105
+ 'modified_components, could_find_apps',
106
106
  [
107
107
  (None, True),
108
108
  ([], False),
@@ -111,7 +111,7 @@ get-started:
111
111
  (['soc', 'fake'], True),
112
112
  ],
113
113
  )
114
- def test_with_requires_and_depends_on_components(self, tmpdir, depends_on_components, could_find_apps):
114
+ def test_with_requires_and_modified_components(self, tmpdir, modified_components, could_find_apps):
115
115
  test_dir = str(IDF_PATH / 'examples')
116
116
  apps = find_apps(test_dir, 'esp32', recursive=True)
117
117
  assert apps
@@ -120,7 +120,7 @@ get-started:
120
120
  yaml_file.write_text(
121
121
  f'''
122
122
  {test_dir}:
123
- requires_components:
123
+ depends_components:
124
124
  - freertos
125
125
  - soc
126
126
  ''',
@@ -132,7 +132,95 @@ get-started:
132
132
  'esp32',
133
133
  recursive=True,
134
134
  manifest_files=yaml_file,
135
- depends_on_components=depends_on_components,
135
+ modified_components=modified_components,
136
+ )
137
+ if could_find_apps:
138
+ assert filtered_apps == apps
139
+ else:
140
+ assert not filtered_apps
141
+
142
+ @pytest.mark.parametrize(
143
+ 'modified_files, could_find_apps',
144
+ [
145
+ ('/foo', False),
146
+ (str(IDF_PATH / 'examples' / 'README.md'), False),
147
+ ([str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md')], False),
148
+ (
149
+ [
150
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md'),
151
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.c'),
152
+ ],
153
+ True,
154
+ ),
155
+ ],
156
+ )
157
+ def test_with_depends_components_but_modified(self, tmp_path, modified_files, could_find_apps):
158
+ test_dir = str(IDF_PATH / 'examples' / 'get-started' / 'hello_world')
159
+ apps = find_apps(test_dir, 'esp32', recursive=True)
160
+ assert apps
161
+
162
+ yaml_file = tmp_path / 'test.yml'
163
+ yaml_file.write_text(
164
+ f'''
165
+ {test_dir}:
166
+ depends_components:
167
+ - soc
168
+ ''',
169
+ encoding='utf8',
170
+ )
171
+
172
+ filtered_apps = find_apps(
173
+ test_dir,
174
+ 'esp32',
175
+ recursive=True,
176
+ manifest_files=yaml_file,
177
+ modified_components=[],
178
+ modified_files=modified_files,
179
+ )
180
+ if could_find_apps:
181
+ assert filtered_apps == apps
182
+ else:
183
+ assert not filtered_apps
184
+
185
+ @pytest.mark.parametrize(
186
+ 'modified_files, could_find_apps',
187
+ [
188
+ ('/foo', True),
189
+ (str(IDF_PATH / 'examples' / 'README.md'), False),
190
+ ([str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md')], True),
191
+ (
192
+ [
193
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.md'),
194
+ str(IDF_PATH / 'examples' / 'get-started' / 'hello_world' / 'a.c'),
195
+ ],
196
+ True,
197
+ ),
198
+ ],
199
+ )
200
+ def test_with_depends_filepatterns(self, tmp_path, modified_files, could_find_apps):
201
+ test_dir = str(IDF_PATH / 'examples' / 'get-started' / 'hello_world')
202
+ apps = find_apps(test_dir, 'esp32', recursive=True)
203
+ assert apps
204
+
205
+ yaml_file = tmp_path / 'test.yml'
206
+ yaml_file.write_text(
207
+ f'''
208
+ {test_dir}:
209
+ depends_filepatterns:
210
+ - /foo
211
+ - examples/get-started/hello_world/**
212
+ - examples/foo/**
213
+ ''',
214
+ encoding='utf8',
215
+ )
216
+
217
+ filtered_apps = find_apps(
218
+ test_dir,
219
+ 'esp32',
220
+ recursive=True,
221
+ manifest_rootpath=str(IDF_PATH),
222
+ manifest_files=yaml_file,
223
+ modified_files=modified_files,
136
224
  )
137
225
  if could_find_apps:
138
226
  assert filtered_apps == apps