idf-build-apps 2.4.2__py3-none-any.whl → 2.5.0rc0__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.
idf_build_apps/main.py CHANGED
@@ -7,11 +7,10 @@ import argparse
7
7
  import json
8
8
  import logging
9
9
  import os
10
- import re
11
10
  import sys
12
11
  import textwrap
13
12
  import typing as t
14
- from copy import deepcopy
13
+ from dataclasses import asdict
15
14
 
16
15
  import argcomplete
17
16
  from pydantic import (
@@ -19,9 +18,8 @@ from pydantic import (
19
18
  create_model,
20
19
  )
21
20
 
22
- from . import (
23
- SESSION_ARGS,
24
- )
21
+ from idf_build_apps.args import BuildArguments, DumpManifestShaArguments, FindArguments, add_arguments_to_parser
22
+
25
23
  from .app import (
26
24
  App,
27
25
  AppDeserializer,
@@ -29,12 +27,6 @@ from .app import (
29
27
  MakeApp,
30
28
  )
31
29
  from .autocompletions import activate_completions
32
- from .build_apps_args import (
33
- BuildAppsArgs,
34
- )
35
- from .config import (
36
- get_valid_config,
37
- )
38
30
  from .constants import ALL_TARGETS, BuildStatus, completion_instructions
39
31
  from .finder import (
40
32
  _find_apps,
@@ -44,189 +36,65 @@ from .junit import (
44
36
  TestReport,
45
37
  TestSuite,
46
38
  )
47
- from .log import (
48
- setup_logging,
49
- )
50
39
  from .manifest.manifest import (
51
- FolderRule,
52
40
  Manifest,
53
41
  )
54
42
  from .utils import (
55
43
  AutocompleteActivationError,
56
44
  InvalidCommand,
57
- files_matches_patterns,
45
+ drop_none_kwargs,
58
46
  get_parallel_start_stop,
59
- semicolon_separated_str_to_list,
60
- to_absolute_path,
61
47
  to_list,
62
48
  )
63
49
 
64
50
  LOGGER = logging.getLogger(__name__)
65
51
 
66
52
 
67
- def _check_app_dependency(
68
- manifest_rootpath: t.Optional[str] = None,
69
- modified_components: t.Optional[t.List[str]] = None,
70
- modified_files: t.Optional[t.List[str]] = None,
71
- ignore_app_dependencies_components: t.Optional[t.List[str]] = None,
72
- ignore_app_dependencies_filepatterns: t.Optional[t.List[str]] = None,
73
- ) -> bool:
74
- # not check since modified_components and modified_files are not passed
75
- if modified_components is None and modified_files is None:
76
- return False
77
-
78
- # not check since ignore_app_dependencies_components is passed and matched
79
- if (
80
- ignore_app_dependencies_components
81
- and modified_components is not None
82
- and set(modified_components).intersection(ignore_app_dependencies_components)
83
- ):
84
- LOGGER.info(
85
- 'Build all apps since modified components %s matches ignored components %s',
86
- ', '.join(modified_components),
87
- ', '.join(ignore_app_dependencies_components),
88
- )
89
- return False
90
-
91
- # not check since ignore_app_dependencies_filepatterns is passed and matched
92
- if (
93
- ignore_app_dependencies_filepatterns
94
- and modified_files is not None
95
- and files_matches_patterns(modified_files, ignore_app_dependencies_filepatterns, manifest_rootpath)
96
- ):
97
- LOGGER.info(
98
- 'Build all apps since modified files %s matches ignored file patterns %s',
99
- ', '.join(modified_files),
100
- ', '.join(ignore_app_dependencies_filepatterns),
101
- )
102
- return False
103
-
104
- return True
105
-
106
-
107
53
  def find_apps(
108
- paths: t.Union[t.List[str], str],
109
- target: str,
54
+ paths: t.Union[t.List[str], str, None] = None,
55
+ target: t.Optional[str] = None,
110
56
  *,
111
- build_system: t.Union[t.Type[App], str] = CMakeApp,
112
- recursive: bool = False,
113
- exclude_list: t.Optional[t.List[str]] = None,
114
- work_dir: t.Optional[str] = None,
115
- build_dir: str = 'build',
116
- config_rules_str: t.Optional[t.Union[t.List[str], str]] = None,
117
- build_log_filename: t.Optional[str] = None,
118
- size_json_filename: t.Optional[str] = None,
119
- check_warnings: bool = False,
120
- preserve: bool = True,
121
- manifest_rootpath: t.Optional[str] = None,
122
- manifest_files: t.Optional[t.Union[t.List[str], str]] = None,
123
- check_manifest_rules: bool = False,
124
- default_build_targets: t.Optional[t.Union[t.List[str], str]] = None,
125
- modified_components: t.Optional[t.Union[t.List[str], str]] = None,
126
- modified_files: t.Optional[t.Union[t.List[str], str]] = None,
127
- ignore_app_dependencies_components: t.Optional[t.Union[t.List[str], str]] = None,
128
- ignore_app_dependencies_filepatterns: t.Optional[t.Union[t.List[str], str]] = None,
129
- sdkconfig_defaults: t.Optional[str] = None,
130
- include_skipped_apps: bool = False,
131
- include_disabled_apps: bool = False,
57
+ find_arguments: t.Optional[FindArguments] = None,
58
+ **kwargs,
132
59
  ) -> t.List[App]:
133
60
  """
134
- Find app directories in paths (possibly recursively), which contain apps for the given build system, compatible
135
- with the given target
136
-
137
- :param paths: list of app directories (can be / usually will be a relative path)
138
- :param target: desired value of IDF_TARGET; apps incompatible with the given target are skipped.
139
- :param build_system: class of the build system, default CMakeApp
140
- :param recursive: Recursively search into the nested sub-folders if no app is found or not
141
- :param exclude_list: list of paths to be excluded from the recursive search
142
- :param work_dir: directory where the app should be copied before building. Support placeholders
143
- :param build_dir: directory where the build will be done. Support placeholders.
144
- :param config_rules_str: mapping of sdkconfig file name patterns to configuration names
145
- :param build_log_filename: filename of the build log. Will be placed under the app.build_path.
146
- Support placeholders. The logs will go to stdout/stderr if not specified
147
- :param size_json_filename: filename to collect the app's size information. Will be placed under the app.build_path.
148
- Support placeholders. The app's size information won't be collected if not specified
149
- :param check_warnings: Check for warnings in the build log or not
150
- :param preserve: Preserve the built binaries or not
151
- :param manifest_rootpath: The root path of the manifest files. Usually the folders specified in the manifest files
152
- are relative paths. Use the current directory if not specified
153
- :param manifest_files: paths of the manifest files
154
- :param check_manifest_rules: check the manifest rules or not
155
- :param default_build_targets: default build targets used in manifest files
156
- :param modified_components: modified components
157
- :param modified_files: modified files
158
- :param ignore_app_dependencies_components: components used for ignoring checking the app dependencies
159
- :param ignore_app_dependencies_filepatterns: file patterns used for ignoring checking the app dependencies
160
- :param sdkconfig_defaults: semicolon-separated string, pass to idf.py -DSDKCONFIG_DEFAULTS if specified,
161
- also could be set via environment variables "SDKCONFIG_DEFAULTS"
162
- :param include_skipped_apps: include skipped apps or not
163
- :param include_disabled_apps: include disabled apps or not
61
+ Find apps in the given paths for the specified target. For all kwargs, please refer to `FindArguments`
62
+
164
63
  :return: list of found apps
165
64
  """
166
- if default_build_targets:
167
- default_build_targets = to_list(default_build_targets)
168
- LOGGER.info('Overriding default build targets to %s', default_build_targets)
169
- FolderRule.DEFAULT_BUILD_TARGETS = default_build_targets
65
+ if find_arguments is None:
66
+ find_arguments = FindArguments(
67
+ paths=to_list(paths), # type: ignore
68
+ target=target, # type: ignore
69
+ **kwargs,
70
+ )
170
71
 
171
- if isinstance(build_system, str):
72
+ app_cls: t.Type[App]
73
+ if isinstance(find_arguments.build_system, str):
172
74
  # backwards compatible
173
- if build_system == 'cmake':
174
- build_system = CMakeApp
175
- elif build_system == 'make':
176
- build_system = MakeApp
75
+ if find_arguments.build_system == 'cmake':
76
+ app_cls = CMakeApp
77
+ elif find_arguments.build_system == 'make':
78
+ app_cls = MakeApp
177
79
  else:
178
80
  raise ValueError('Only Support "make" and "cmake"')
179
- app_cls = build_system
180
-
181
- # always set the manifest rootpath at the very beginning of find_apps in case ESP-IDF switches the branch.
182
- Manifest.ROOTPATH = to_absolute_path(manifest_rootpath or os.curdir)
183
- Manifest.CHECK_MANIFEST_RULES = check_manifest_rules
184
-
185
- if manifest_files:
186
- App.MANIFEST = Manifest.from_files(to_list(manifest_files))
187
-
188
- modified_components = to_list(modified_components)
189
- modified_files = to_list(modified_files)
190
- ignore_app_dependencies_components = to_list(ignore_app_dependencies_components)
191
- ignore_app_dependencies_filepatterns = to_list(ignore_app_dependencies_filepatterns)
192
- config_rules_str = to_list(config_rules_str)
81
+ else:
82
+ app_cls = find_arguments.build_system
193
83
 
194
84
  apps = []
195
- if target == 'all':
85
+ if find_arguments.target == 'all':
196
86
  targets = ALL_TARGETS
197
87
  else:
198
- targets = [target]
88
+ targets = [find_arguments.target]
199
89
 
200
- for target in targets:
201
- for path in to_list(paths):
202
- path = path.strip()
90
+ for _t in targets:
91
+ for _p in find_arguments.paths:
203
92
  apps.extend(
204
93
  _find_apps(
205
- path,
206
- target,
207
- app_cls,
208
- recursive,
209
- exclude_list or [],
210
- work_dir=work_dir,
211
- build_dir=build_dir or 'build',
212
- config_rules_str=config_rules_str,
213
- build_log_filename=build_log_filename,
214
- size_json_filename=size_json_filename,
215
- check_warnings=check_warnings,
216
- preserve=preserve,
217
- manifest_rootpath=manifest_rootpath,
218
- check_app_dependencies=_check_app_dependency(
219
- manifest_rootpath=manifest_rootpath,
220
- modified_components=modified_components,
221
- modified_files=modified_files,
222
- ignore_app_dependencies_components=ignore_app_dependencies_components,
223
- ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
224
- ),
225
- modified_components=modified_components,
226
- modified_files=modified_files,
227
- sdkconfig_defaults_str=sdkconfig_defaults,
228
- include_skipped_apps=include_skipped_apps,
229
- include_disabled_apps=include_disabled_apps,
94
+ _p,
95
+ _t,
96
+ app_cls=app_cls,
97
+ args=find_arguments,
230
98
  )
231
99
  )
232
100
 
@@ -236,85 +104,32 @@ def find_apps(
236
104
 
237
105
 
238
106
  def build_apps(
239
- apps: t.Union[t.List[App], App],
240
- *,
241
- build_verbose: bool = False,
242
- dry_run: bool = False,
243
- keep_going: bool = False,
244
- ignore_warning_strs: t.Optional[t.List[str]] = None,
245
- ignore_warning_file: t.Optional[t.TextIO] = None,
246
- copy_sdkconfig: bool = False,
247
- manifest_rootpath: t.Optional[str] = None,
248
- modified_components: t.Optional[t.Union[t.List[str], str]] = None,
249
- modified_files: t.Optional[t.Union[t.List[str], str]] = None,
250
- ignore_app_dependencies_components: t.Optional[t.Union[t.List[str], str]] = None,
251
- ignore_app_dependencies_filepatterns: t.Optional[t.Union[t.List[str], str]] = None,
252
- check_app_dependencies: t.Optional[bool] = None,
253
- # BuildAppsArgs
254
- parallel_count: int = 1,
255
- parallel_index: int = 1,
256
- collect_size_info: t.Optional[str] = None,
257
- collect_app_info: t.Optional[str] = None,
258
- junitxml: t.Optional[str] = None,
107
+ apps: t.Union[t.List[App], App, None] = None, *, build_arguments: t.Optional[BuildArguments] = None, **kwargs
259
108
  ) -> int:
260
109
  """
261
- Build all the specified apps
262
-
263
- :param apps: list of apps to be built
264
- :param build_verbose: call ``--verbose`` in ``idf.py build`` or not
265
- :param dry_run: simulate this run or not
266
- :param keep_going: keep building or not if one app's build failed
267
- :param ignore_warning_strs: ignore build warnings that matches any of the specified regex patterns
268
- :param ignore_warning_file: ignore build warnings that matches any of the lines of the regex patterns in the
269
- specified file
270
- :param copy_sdkconfig: copy the sdkconfig file to the build directory or not
271
- :param manifest_rootpath: The root path of the manifest files. Usually the folders specified in the manifest files
272
- are relative paths. Use the current directory if not specified
273
- :param modified_components: modified components
274
- :param modified_files: modified files
275
- :param ignore_app_dependencies_components: components used for ignoring checking the app dependencies
276
- :param ignore_app_dependencies_filepatterns: file patterns used for ignoring checking the app dependencies
277
- :param check_app_dependencies: check app dependencies or not. if not set, will be calculated by modified_components,
278
- modified_files, and ignore_app_dependencies_filepatterns
279
- :param parallel_count: number of parallel tasks to run
280
- :param parallel_index: index of the parallel task to run
281
- :param collect_size_info: file path to record all generated size files' paths if specified
282
- :param collect_app_info: file path to record all the built apps' info if specified
283
- :param junitxml: path of the junitxml file
110
+ Build all the specified apps. For all kwargs, please refer to `BuildArguments`
111
+
284
112
  :return: exit code
285
113
  """
286
114
  apps = to_list(apps)
287
- modified_components = to_list(modified_components)
288
- modified_files = to_list(modified_files)
289
- ignore_app_dependencies_components = to_list(ignore_app_dependencies_components)
290
- ignore_app_dependencies_filepatterns = to_list(ignore_app_dependencies_filepatterns)
115
+ if build_arguments is None:
116
+ build_arguments = BuildArguments(
117
+ **kwargs,
118
+ )
291
119
 
292
- test_suite = TestSuite('build_apps')
120
+ if apps is None:
121
+ apps = find_apps(find_arguments=FindArguments.from_dict(asdict(build_arguments)))
293
122
 
294
- ignore_warnings_regexes = []
295
- if ignore_warning_strs:
296
- for s in ignore_warning_strs:
297
- ignore_warnings_regexes.append(re.compile(s))
298
- if ignore_warning_file:
299
- for s in ignore_warning_file:
300
- ignore_warnings_regexes.append(re.compile(s.strip()))
301
- App.IGNORE_WARNS_REGEXES = ignore_warnings_regexes
123
+ test_suite = TestSuite('build_apps')
302
124
 
303
- start, stop = get_parallel_start_stop(len(apps), parallel_count, parallel_index)
125
+ start, stop = get_parallel_start_stop(len(apps), build_arguments.parallel_count, build_arguments.parallel_index)
304
126
  LOGGER.info('Total %s apps. running build for app %s-%s', len(apps), start, stop)
305
127
 
306
- build_apps_args = BuildAppsArgs(
307
- parallel_count=parallel_count,
308
- parallel_index=parallel_index,
309
- collect_size_info=collect_size_info,
310
- collect_app_info=collect_app_info,
311
- junitxml=junitxml,
312
- )
313
128
  for app in apps[start - 1 : stop]: # we use 1-based
314
- app.build_apps_args = build_apps_args
129
+ app.parallel_index = build_arguments.parallel_index
315
130
 
316
131
  # cleanup collect files if exists at this early-stage
317
- for f in (build_apps_args.collect_app_info, build_apps_args.collect_size_info, build_apps_args.junitxml):
132
+ for f in (build_arguments.collect_app_info, build_arguments.collect_size_info, build_arguments.junitxml):
318
133
  if f and os.path.isfile(f):
319
134
  os.remove(f)
320
135
  LOGGER.debug('Remove existing collect file %s', f)
@@ -326,26 +141,18 @@ def build_apps(
326
141
  continue
327
142
 
328
143
  # attrs
329
- app.dry_run = dry_run
144
+ app.dry_run = build_arguments.dry_run
330
145
  app.index = index
331
- app.verbose = build_verbose
332
- app.copy_sdkconfig = copy_sdkconfig
146
+ app.verbose = build_arguments.build_verbose
147
+ app.copy_sdkconfig = build_arguments.copy_sdkconfig
333
148
 
334
149
  LOGGER.info('(%s/%s) Building app: %s', index, len(apps), app)
335
150
 
336
151
  app.build(
337
- manifest_rootpath=manifest_rootpath,
338
- modified_components=modified_components,
339
- modified_files=modified_files,
340
- check_app_dependencies=_check_app_dependency(
341
- manifest_rootpath=manifest_rootpath,
342
- modified_components=modified_components,
343
- modified_files=modified_files,
344
- ignore_app_dependencies_components=ignore_app_dependencies_components,
345
- ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
346
- )
347
- if check_app_dependencies is None
348
- else check_app_dependencies,
152
+ manifest_rootpath=build_arguments.manifest_rootpath,
153
+ modified_components=build_arguments.modified_components,
154
+ modified_files=build_arguments.modified_files,
155
+ check_app_dependencies=build_arguments.dependency_driven_build_enabled,
349
156
  )
350
157
  test_suite.add_test_case(TestCase.from_app(app))
351
158
 
@@ -354,20 +161,20 @@ def build_apps(
354
161
  else:
355
162
  LOGGER.info('%s', app.build_status.value)
356
163
 
357
- if build_apps_args.collect_app_info:
358
- with open(build_apps_args.collect_app_info, 'a') as fw:
164
+ if build_arguments.collect_app_info:
165
+ with open(build_arguments.collect_app_info, 'a') as fw:
359
166
  fw.write(app.to_json() + '\n')
360
- LOGGER.debug('Recorded app info in %s', build_apps_args.collect_app_info)
167
+ LOGGER.debug('Recorded app info in %s', build_arguments.collect_app_info)
361
168
 
362
169
  if app.build_status == BuildStatus.FAILED:
363
- if not keep_going:
170
+ if not build_arguments.keep_going:
364
171
  return 1
365
172
  else:
366
173
  exit_code = 1
367
174
  elif app.build_status == BuildStatus.SUCCESS:
368
- if build_apps_args.collect_size_info and app.size_json_path:
175
+ if build_arguments.collect_size_info and app.size_json_path:
369
176
  if os.path.isfile(app.size_json_path):
370
- with open(build_apps_args.collect_size_info, 'a') as fw:
177
+ with open(build_arguments.collect_size_info, 'a') as fw:
371
178
  fw.write(
372
179
  json.dumps(
373
180
  {
@@ -379,13 +186,13 @@ def build_apps(
379
186
  )
380
187
  + '\n'
381
188
  )
382
- LOGGER.debug('Recorded size info file path in %s', build_apps_args.collect_size_info)
189
+ LOGGER.debug('Recorded size info file path in %s', build_arguments.collect_size_info)
383
190
 
384
191
  LOGGER.info('') # add one empty line for separating different builds
385
192
 
386
- if build_apps_args.junitxml:
387
- TestReport([test_suite], build_apps_args.junitxml).create_test_report()
388
- LOGGER.info('Generated junit report for build apps: %s', build_apps_args.junitxml)
193
+ if build_arguments.junitxml:
194
+ TestReport([test_suite], build_arguments.junitxml).create_test_report()
195
+ LOGGER.info('Generated junit report for build apps: %s', build_arguments.junitxml)
389
196
 
390
197
  return exit_code
391
198
 
@@ -438,7 +245,7 @@ class IdfBuildAppsCliFormatter(argparse.HelpFormatter):
438
245
 
439
246
  def get_parser() -> argparse.ArgumentParser:
440
247
  parser = argparse.ArgumentParser(
441
- description='Tools for building ESP-IDF related apps.'
248
+ description='Tools for building ESP-IDF related apps. '
442
249
  'Some CLI options can be expanded by the following placeholders, like "--work-dir", "--build-dir", etc.:\n'
443
250
  '- @t: would be replaced by the target chip type\n'
444
251
  '- @w: would be replaced by the wildcard, usually the sdkconfig\n'
@@ -449,176 +256,11 @@ def get_parser() -> argparse.ArgumentParser:
449
256
  '- @p: would be replaced by the parallel index (only available in `build` command)',
450
257
  formatter_class=argparse.RawDescriptionHelpFormatter,
451
258
  )
452
- actions = parser.add_subparsers(dest='action')
453
-
454
- common_args = argparse.ArgumentParser(add_help=False)
455
- common_args.add_argument(
456
- '-c',
457
- '--config-file',
458
- help='Path to the default configuration file, toml file',
459
- )
460
-
461
- common_args.add_argument('-p', '--paths', nargs='+', help='One or more paths to look for apps')
462
- common_args.add_argument('-t', '--target', help='filter apps by given target')
463
- common_args.add_argument(
464
- '--build-system', default='cmake', choices=['cmake', 'make'], help='filter apps by given build system'
465
- )
466
- common_args.add_argument(
467
- '--recursive',
468
- action='store_true',
469
- help='Look for apps in the specified paths recursively',
470
- )
471
- common_args.add_argument('--exclude', nargs='+', help='Ignore specified path (if --recursive is given)')
472
- common_args.add_argument(
473
- '--work-dir',
474
- help='If set, the app is first copied into the specified directory, and then built. '
475
- 'If not set, the work directory is the directory of the app. Can expand placeholders',
476
- )
477
- common_args.add_argument(
478
- '--build-dir',
479
- default='build',
480
- help='If set, specifies the build directory name. Can be either a name relative to the work directory, '
481
- 'or an absolute path. Can expand placeholders',
482
- )
483
- common_args.add_argument(
484
- '--build-log',
485
- help='Relative to build dir. The build log will be written to this file instead of sys.stdout if specified. '
486
- 'Can expand placeholders',
487
- )
488
- common_args.add_argument(
489
- '--size-file',
490
- help='Relative to build dir. The size json will be written to this file if specified. Can expand placeholders',
491
- )
492
- common_args.add_argument(
493
- '--config',
494
- nargs='+',
495
- help='Adds configurations (sdkconfig file names) to build. '
496
- 'This can either be FILENAME[=NAME] or FILEPATTERN. FILENAME is the name of the sdkconfig file, '
497
- 'relative to the project directory, to be used. Optional NAME can be specified, '
498
- 'which can be used as a name of this configuration. FILEPATTERN is the name of '
499
- 'the sdkconfig file, relative to the project directory, with at most one wildcard. '
500
- 'The part captured by the wildcard is used as the name of the configuration',
501
- )
502
-
503
- common_args.add_argument(
504
- '--override-sdkconfig-items',
505
- nargs='?',
506
- type=str,
507
- help='The --override-sdkconfig-items option is a comma-separated list '
508
- 'that permits the overriding of specific configuration items defined '
509
- "in the SDK's sdkconfig file and Kconfig using a command-line argument. "
510
- 'The sdkconfig items specified here override the same sdkconfig '
511
- 'item defined in the --override-sdkconfig-files, if exists.',
512
- )
513
- common_args.add_argument(
514
- '--override-sdkconfig-files',
515
- nargs='?',
516
- type=str,
517
- help='"The --override-sdkconfig-files option is a comma-separated list, '
518
- 'which provides an alternative (alt: --override-sdkconfig-items) '
519
- 'approach for overriding SDK configuration items. '
520
- 'The filepath may be global or relative to the root.',
521
- )
522
- common_args.add_argument(
523
- '--sdkconfig-defaults',
524
- help='semicolon-separated string, pass to idf.py -DSDKCONFIG_DEFAULTS if specified, also could be set via '
525
- 'environment variables "SDKCONFIG_DEFAULTS"',
526
- )
527
- common_args.add_argument(
528
- '-v',
529
- '--verbose',
530
- default=0,
531
- action='count',
532
- help='Increase the logging level of the whole process. Can be specified multiple times. '
533
- 'By default set to WARNING level. '
534
- 'Specify once to set to INFO level. '
535
- 'Specify twice or more to set to DEBUG level',
536
- )
537
- common_args.add_argument(
538
- '--log-file',
539
- help='Write the log to the specified file, instead of stderr',
540
- )
541
- common_args.add_argument(
542
- '--check-warnings', action='store_true', help='If set, fail the build if warnings are found'
543
- )
544
-
545
- common_args.add_argument(
546
- '--manifest-file',
547
- nargs='+',
548
- help='Manifest files which specify the build test rules of the apps',
549
- )
550
- common_args.add_argument(
551
- '--manifest-rootpath',
552
- help='Root directory for calculating the realpath of the relative path defined in the manifest files. '
553
- 'Would use the current directory if not set',
554
- )
555
- common_args.add_argument(
556
- '--check-manifest-rules',
557
- action='store_true',
558
- help='Exit with error if any of the manifest rules does not exist on your filesystem',
559
- )
560
- common_args.add_argument(
561
- '--enable-preview-targets',
562
- action='store_true',
563
- help='Build the apps with all targets in the current ESP-IDF branch, '
564
- 'including preview targets, when the app supports the target.',
565
- )
566
- common_args.add_argument(
567
- '--default-build-targets',
568
- nargs='+',
569
- help='space-separated list of string which specifies the targets for building the apps. '
570
- 'If provided, the apps will be built only with the specified targets '
571
- 'when the app supports the target.',
572
- )
573
- common_args.add_argument(
574
- '--modified-components',
575
- type=semicolon_separated_str_to_list,
576
- help='semicolon-separated string which specifies the modified components. '
577
- 'app with `depends_components` set in the corresponding manifest files would only be built '
578
- 'if depends on any of the specified components. '
579
- 'If set to "", the value would be considered as None. '
580
- 'If set to ";", the value would be considered as an empty list',
581
- )
582
- common_args.add_argument(
583
- '--modified-files',
584
- type=semicolon_separated_str_to_list,
585
- help='semicolon-separated string which specifies the modified files. '
586
- 'app with `depends_filepatterns` set in the corresponding manifest files would only be built '
587
- 'if any of the specified file pattern matches any of the specified modified files. '
588
- 'If set to "", the value would be considered as None. '
589
- 'If set to ";", the value would be considered as an empty list',
590
- )
591
- common_args.add_argument(
592
- '-ic',
593
- '--ignore-app-dependencies-components',
594
- type=semicolon_separated_str_to_list,
595
- help='semicolon-separated string which specifies the modified components used for '
596
- 'ignoring checking the app dependencies. '
597
- 'The `depends_components` and `depends_filepatterns` set in the manifest files will be ignored when any of the '
598
- 'specified components matches any of the modified components. '
599
- 'Must be used together with --modified-components. '
600
- 'If set to "", the value would be considered as None. '
601
- 'If set to ";", the value would be considered as an empty list',
602
- )
603
- common_args.add_argument(
604
- '-if',
605
- '--ignore-app-dependencies-filepatterns',
606
- type=semicolon_separated_str_to_list,
607
- help='semicolon-separated string which specifies the file patterns used for '
608
- 'ignoring checking the app dependencies. '
609
- 'The `depends_components` and `depends_filepatterns` set in the manifest files will be ignored when any of the '
610
- 'specified file patterns matches any of the modified files. '
611
- 'Must be used together with --modified-files. '
612
- 'If set to "", the value would be considered as None. '
613
- 'If set to ";", the value would be considered as an empty list',
614
- )
615
-
616
- common_args.add_argument(
617
- '--no-color',
618
- action='store_true',
619
- help='enable colored output by default on UNIX-like systems. enable this flag to make the logs uncolored.',
620
- )
259
+ actions = parser.add_subparsers(dest='action', required=True)
621
260
 
261
+ ########
262
+ # Find #
263
+ ########
622
264
  find_parser = actions.add_parser(
623
265
  'find',
624
266
  help='Find the buildable applications. Run `idf-build-apps find --help` for more information on a command.',
@@ -626,95 +268,25 @@ def get_parser() -> argparse.ArgumentParser:
626
268
  '`--path` and `--target` options must be provided. '
627
269
  'By default, print the found apps in stdout. '
628
270
  'To find apps for all chips use the `--target` option with the `all` argument.',
629
- parents=[common_args],
630
271
  formatter_class=IdfBuildAppsCliFormatter,
631
272
  )
632
- find_parser.add_argument('-o', '--output', help='Print the found apps to the specified file instead of stdout')
633
- find_parser.add_argument(
634
- '--output-format',
635
- choices=['raw', 'json'],
636
- default='raw',
637
- help='Output format. In "raw" format, each line is a valid json that represents the app. '
638
- 'In "json" format, the whole file is a JSON file of a list of apps.',
639
- )
640
- find_parser.add_argument(
641
- '--include-all-apps',
642
- action='store_true',
643
- help='Include skipped and disabled apps. By default only apps that should be built.',
644
- )
273
+ add_arguments_to_parser(FindArguments, find_parser)
645
274
 
275
+ #########
276
+ # Build #
277
+ #########
646
278
  build_parser = actions.add_parser(
647
279
  'build',
648
280
  help='Build the found applications. Run `idf-build-apps build --help` for more information on a command.',
649
281
  description='Build the application in the given path or paths for specified chips. '
650
282
  '`--path` and `--target` options must be provided.',
651
- parents=[common_args],
652
283
  formatter_class=IdfBuildAppsCliFormatter,
653
284
  )
654
- build_parser.add_argument(
655
- '--build-verbose',
656
- action='store_true',
657
- help='Enable verbose output of the build system',
658
- )
659
- build_parser.add_argument(
660
- '--parallel-count',
661
- default=1,
662
- type=int,
663
- help="Number of parallel build jobs. Note that this script doesn't start all jobs simultaneously. "
664
- 'It needs to be executed multiple times with same value of --parallel-count and '
665
- 'different values of --parallel-index',
666
- )
667
- build_parser.add_argument(
668
- '--parallel-index',
669
- default=1,
670
- type=int,
671
- help='Index (1-based) of the job, out of the number specified by --parallel-count',
672
- )
673
- build_parser.add_argument(
674
- '--dry-run',
675
- action='store_true',
676
- help="Don't actually build, only print the build commands",
677
- )
678
- build_parser.add_argument(
679
- '--keep-going',
680
- action='store_true',
681
- help="Don't exit immediately when a build fails",
682
- )
683
- build_parser.add_argument(
684
- '--no-preserve',
685
- action='store_true',
686
- help="Don't preserve the build directory after a successful build",
687
- )
688
- build_parser.add_argument(
689
- '--collect-size-info',
690
- help='write size info json file while building into the specified file. each line is a json object. '
691
- 'Can expand placeholder @p',
692
- )
693
- build_parser.add_argument(
694
- '--collect-app-info',
695
- help='write app info json file while building into the specified file. each line is a json object. '
696
- 'Can expand placeholder @p',
697
- )
698
- build_parser.add_argument(
699
- '--ignore-warning-str',
700
- nargs='+',
701
- help='Ignore the warning string that match the specified regex in the build output',
702
- )
703
- build_parser.add_argument(
704
- '--ignore-warning-file',
705
- type=argparse.FileType('r'),
706
- help='Ignore the warning strings in the specified file. Each line should be a regex string',
707
- )
708
- build_parser.add_argument(
709
- '--copy-sdkconfig',
710
- action='store_true',
711
- help='Copy the sdkconfig file to the build directory',
712
- )
713
- build_parser.add_argument(
714
- '--junitxml',
715
- help='Path to the junitxml file. If specified, the junitxml file will be generated. Can expand placeholder @p',
716
- )
285
+ add_arguments_to_parser(BuildArguments, build_parser)
717
286
 
287
+ ###############
288
+ # Completions #
289
+ ###############
718
290
  completions_parser = actions.add_parser(
719
291
  'completions',
720
292
  help='Add the autocompletion activation script to the shell rc file. '
@@ -735,8 +307,18 @@ def get_parser() -> argparse.ArgumentParser:
735
307
  '-s',
736
308
  '--shell',
737
309
  choices=['bash', 'zsh', 'fish'],
738
- help='Specify the shell type for the autocomplite activation script. ',
310
+ help='Specify the shell type for the autocomplete activation script.',
311
+ )
312
+
313
+ ############################
314
+ # Dump Manifest SHA Values #
315
+ ############################
316
+ dump_manifest_parser = actions.add_parser(
317
+ 'dump-manifest-sha',
318
+ help='Dump the manifest files SHA values. '
319
+ 'This could be useful in CI to check if the manifest files are changed.',
739
320
  )
321
+ add_arguments_to_parser(DumpManifestShaArguments, dump_manifest_parser)
740
322
 
741
323
  return parser
742
324
 
@@ -752,56 +334,6 @@ def handle_completions(args: argparse.Namespace) -> None:
752
334
  print(completion_instructions)
753
335
 
754
336
 
755
- def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
756
- # validate cli subcommands
757
- if args.action not in ['find', 'build', 'completions']:
758
- parser.print_help()
759
- raise InvalidCommand('subcommand is required. {find, build, completions}')
760
-
761
- if not args.paths:
762
- raise InvalidCommand(
763
- 'Must specify at least one path to search for the apps ' 'with CLI option "-p <path>" or "--path <path>"'
764
- )
765
-
766
- if not args.target:
767
- raise InvalidCommand(
768
- 'Must specify current build target with CLI option "-t <target>" or "--target <target>". '
769
- '(choices: [{}]'.format(','.join([*ALL_TARGETS, 'all']))
770
- )
771
-
772
- default_build_targets = []
773
- if args.default_build_targets:
774
- for target in args.default_build_targets:
775
- if target not in ALL_TARGETS:
776
- LOGGER.warning(
777
- f'Ignoring... Unrecognizable target {target} specified with "--default-build-targets". '
778
- f'Current ESP-IDF available targets: {ALL_TARGETS}'
779
- )
780
- elif target not in default_build_targets:
781
- default_build_targets.append(target)
782
- args.default_build_targets = default_build_targets
783
- elif args.enable_preview_targets:
784
- args.default_build_targets = deepcopy(ALL_TARGETS)
785
-
786
- if args.ignore_app_dependencies_components is not None:
787
- if args.modified_components is None:
788
- raise InvalidCommand('Must specify "--ignore-app-dependencies-components" with "--modified-components", ')
789
-
790
- if args.ignore_app_dependencies_filepatterns is not None:
791
- if args.modified_files is None:
792
- raise InvalidCommand('Must specify "--ignore-app-dependencies-filepatterns" with "--modified-files", ')
793
-
794
-
795
- def apply_config_args(args: argparse.Namespace) -> None:
796
- # support toml config file
797
- config_dict = get_valid_config(custom_path=args.config_file)
798
- if config_dict:
799
- for k, v in config_dict.items():
800
- setattr(args, k, v)
801
-
802
- setup_logging(args.verbose, args.log_file, not args.no_color)
803
-
804
-
805
337
  def main():
806
338
  parser = get_parser()
807
339
  argcomplete.autocomplete(parser)
@@ -811,86 +343,42 @@ def main():
811
343
  handle_completions(args)
812
344
  sys.exit(0)
813
345
 
814
- apply_config_args(args)
815
- validate_args(parser, args)
816
-
817
- SESSION_ARGS.set(args)
818
-
819
- if args.action == 'build':
820
- args.output = None # build action doesn't support output option
821
-
822
- kwargs = {
823
- 'build_system': args.build_system,
824
- 'recursive': args.recursive,
825
- 'exclude_list': args.exclude or [],
826
- 'work_dir': args.work_dir,
827
- 'build_dir': args.build_dir or 'build',
828
- 'config_rules_str': args.config,
829
- 'build_log_filename': args.build_log,
830
- 'size_json_filename': args.size_file,
831
- 'check_warnings': args.check_warnings,
832
- 'manifest_rootpath': args.manifest_rootpath,
833
- 'manifest_files': args.manifest_file,
834
- 'check_manifest_rules': args.check_manifest_rules,
835
- 'default_build_targets': args.default_build_targets,
836
- 'modified_components': args.modified_components,
837
- 'modified_files': args.modified_files,
838
- 'ignore_app_dependencies_components': args.ignore_app_dependencies_components,
839
- 'ignore_app_dependencies_filepatterns': args.ignore_app_dependencies_filepatterns,
840
- 'sdkconfig_defaults': args.sdkconfig_defaults,
841
- }
842
- # only useful in find
843
- if args.action == 'find' and args.include_all_apps:
844
- kwargs['include_skipped_apps'] = True
845
- kwargs['include_disabled_apps'] = True
846
-
847
- # real call starts here
848
- apps = find_apps(args.paths, args.target, **kwargs)
346
+ if args.action == 'dump-manifest-sha':
347
+ arguments = DumpManifestShaArguments.from_dict(drop_none_kwargs(vars(args)))
348
+ Manifest.from_files(arguments.manifest_files).dump_sha_values(arguments.output)
349
+ sys.exit(0)
849
350
 
850
351
  if args.action == 'find':
851
- if args.output:
852
- os.makedirs(os.path.dirname(os.path.realpath(args.output)), exist_ok=True)
853
- if args.output.endswith('.json'):
854
- LOGGER.info('Detecting output file ends with ".json", writing json file.')
855
- args.output_format = 'json'
856
-
857
- with open(args.output, 'w') as fw:
858
- if args.output_format == 'raw':
352
+ arguments = FindArguments.from_dict(drop_none_kwargs(vars(args)))
353
+ else:
354
+ arguments = BuildArguments.from_dict(drop_none_kwargs(vars(args)))
355
+
356
+ # real call starts here
357
+ # build also needs to find first
358
+ apps = find_apps(args.paths, args.target, find_arguments=arguments)
359
+ if isinstance(arguments, FindArguments): # find only
360
+ if arguments.output:
361
+ os.makedirs(os.path.dirname(os.path.realpath(arguments.output)), exist_ok=True)
362
+ with open(arguments.output, 'w') as fw:
363
+ if arguments.output_format == 'raw':
859
364
  for app in apps:
860
365
  fw.write(app.to_json() + '\n')
861
- elif args.output_format == 'json':
366
+ elif arguments.output_format == 'json':
862
367
  fw.write(json.dumps([app.model_dump() for app in apps], indent=2))
863
368
  else:
864
- raise ValueError(f'Output format {args.output_format} is not supported.')
369
+ raise InvalidCommand(f'Output format {arguments.output_format} is not supported.')
865
370
  else:
866
371
  for app in apps:
867
372
  print(app)
868
373
 
869
374
  sys.exit(0)
870
375
 
871
- if args.no_preserve:
376
+ # build
377
+ if arguments.no_preserve:
872
378
  for app in apps:
873
379
  app.preserve = False
874
380
 
875
- res = build_apps(
876
- apps,
877
- build_verbose=args.build_verbose,
878
- parallel_count=args.parallel_count,
879
- parallel_index=args.parallel_index,
880
- dry_run=args.dry_run,
881
- keep_going=args.keep_going,
882
- collect_size_info=args.collect_size_info,
883
- collect_app_info=args.collect_app_info,
884
- ignore_warning_strs=args.ignore_warning_str,
885
- ignore_warning_file=args.ignore_warning_file,
886
- copy_sdkconfig=args.copy_sdkconfig,
887
- manifest_rootpath=args.manifest_rootpath,
888
- modified_components=args.modified_components,
889
- modified_files=args.modified_files,
890
- ignore_app_dependencies_components=args.ignore_app_dependencies_components,
891
- ignore_app_dependencies_filepatterns=args.ignore_app_dependencies_filepatterns,
892
- junitxml=args.junitxml,
893
- )
381
+ ret_code = build_apps(apps, build_arguments=arguments)
894
382
 
895
383
  built_apps = [app for app in apps if app.build_status == BuildStatus.SUCCESS]
896
384
  if built_apps:
@@ -910,7 +398,7 @@ def main():
910
398
  for app in failed_apps:
911
399
  print(f' {app}')
912
400
 
913
- sys.exit(res)
401
+ sys.exit(ret_code)
914
402
 
915
403
 
916
404
  def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] = None) -> App:
@@ -922,13 +410,17 @@ def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] =
922
410
  You can pass extra_cls to support custom App class. A custom App class must be a subclass of App, and have a
923
411
  different value of `build_system`. For example, a custom CMake app
924
412
 
925
- >>> class CustomApp(CMakeApp):
926
- >>> build_system: Literal['custom_cmake'] = 'custom_cmake'
413
+ .. code:: python
414
+
415
+ class CustomApp(CMakeApp):
416
+ build_system: Literal['custom_cmake'] = 'custom_cmake'
417
+
418
+ Then you can pass the :class:`CustomApp` class to the :attr:`extra_cls` argument
927
419
 
928
- Then you can pass the CustomApp class to the `extra_cls` argument
420
+ .. code:: python
929
421
 
930
- >>> json_str = CustomApp('.', 'esp32').to_json()
931
- >>> json_to_app(json_str, extra_classes=[CustomApp])
422
+ json_str = CustomApp('.', 'esp32').to_json()
423
+ json_to_app(json_str, extra_classes=[CustomApp])
932
424
 
933
425
  :param json_str: json string
934
426
  :param extra_classes: extra App class