idf-build-apps 2.4.1.dev0__tar.gz → 2.4.3__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 (65) hide show
  1. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/test-build-idf-apps.yml +6 -3
  2. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.pre-commit-config.yaml +4 -5
  3. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/CHANGELOG.md +27 -1
  4. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/PKG-INFO +1 -1
  5. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/conf.py +14 -12
  6. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/__init__.py +1 -1
  7. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/app.py +65 -35
  8. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/constants.py +6 -5
  9. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/finder.py +4 -2
  10. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/junit/report.py +10 -6
  11. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/junit/utils.py +2 -2
  12. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/main.py +94 -56
  13. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/manifest/soc_header.py +18 -14
  14. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/pyproject.toml +1 -5
  15. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/setup.py +1 -1
  16. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/test_build.py +23 -5
  17. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/test_finder.py +27 -0
  18. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.editorconfig +0 -0
  19. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.git-blame-ignore-revs +0 -0
  20. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.gitattributes +0 -0
  21. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/dependabot.yml +0 -0
  22. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/check-pre-commit.yml +0 -0
  23. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/issue_comment.yml +0 -0
  24. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/new_issues.yml +0 -0
  25. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/new_prs.yml +0 -0
  26. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/publish-pypi.yml +0 -0
  27. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.github/workflows/test-build-docs.yml +0 -0
  28. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.gitignore +0 -0
  29. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/.readthedocs.yml +0 -0
  30. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/CONTRIBUTING.md +0 -0
  31. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/LICENSE +0 -0
  32. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/README.md +0 -0
  33. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/CHANGELOG.md +0 -0
  34. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/CONTRIBUTING.md +0 -0
  35. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/Makefile +0 -0
  36. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_apidoc_templates/module.rst_t +0 -0
  37. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_apidoc_templates/package.rst_t +0 -0
  38. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_apidoc_templates/toc.rst_t +0 -0
  39. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_static/espressif-logo.svg +0 -0
  40. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_static/theme_overrides.css +0 -0
  41. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/_templates/layout.html +0 -0
  42. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/cli.rst +0 -0
  43. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/config_file.md +0 -0
  44. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/find_build.md +0 -0
  45. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/index.rst +0 -0
  46. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/manifest.md +0 -0
  47. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/docs/migration/1.x_to_2.x.md +0 -0
  48. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/__main__.py +0 -0
  49. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/autocompletions.py +0 -0
  50. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/build_apps_args.py +0 -0
  51. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/config.py +0 -0
  52. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/junit/__init__.py +0 -0
  53. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/log.py +0 -0
  54. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/manifest/__init__.py +0 -0
  55. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/manifest/if_parser.py +0 -0
  56. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/manifest/manifest.py +0 -0
  57. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/session_args.py +0 -0
  58. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/utils.py +0 -0
  59. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/yaml/__init__.py +0 -0
  60. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/idf_build_apps/yaml/parser.py +0 -0
  61. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/license_header.txt +0 -0
  62. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/conftest.py +0 -0
  63. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/test_app.py +0 -0
  64. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/test_manifest.py +0 -0
  65. {idf_build_apps-2.4.1.dev0 → idf_build_apps-2.4.3}/tests/test_utils.py +0 -0
@@ -52,9 +52,12 @@ jobs:
52
52
  bash $IDF_PATH/install.sh
53
53
  . $IDF_PATH/export.sh
54
54
  pip install idf_build_apps-*.whl
55
- python -m idf_build_apps build -vv -t esp32 \
56
- -p $IDF_PATH/examples/get-started/hello_world \
57
- --size-file size_info.json
55
+ cd $IDF_PATH/examples/get-started/hello_world
56
+ cat 'CONFIG_IDF_TARGET="esp32"' >sdkconfig.defaults
57
+ idf-build-apps build --build-dir build_@t --size-file size_info.json
58
+ test -f build_esp32/hello_world.bin
59
+ test -f build_esp32/size_info.json
60
+ test ! -f build_esp32s2/hello_world.bin
58
61
 
59
62
  build-apps-on-idf-master:
60
63
  runs-on: ubuntu-latest
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/pre-commit/pre-commit-hooks
3
- rev: v4.5.0
3
+ rev: v4.6.0
4
4
  hooks:
5
5
  - id: trailing-whitespace
6
6
  - id: end-of-file-fixer
@@ -16,14 +16,13 @@ repos:
16
16
  - license_header.txt # defaults to: LICENSE.txt
17
17
  - --use-current-year
18
18
  - repo: https://github.com/charliermarsh/ruff-pre-commit
19
- rev: 'v0.2.1'
19
+ rev: 'v0.5.5'
20
20
  hooks:
21
21
  - id: ruff
22
- args: ['--fix', '--preview']
22
+ args: ['--fix']
23
23
  - id: ruff-format
24
- args: ['--preview']
25
24
  - repo: https://github.com/pre-commit/mirrors-mypy
26
- rev: 'v1.8.0'
25
+ rev: 'v1.8.0' # 1.9 doesn't support python 3.7
27
26
  hooks:
28
27
  - id: mypy
29
28
  args: ['--warn-unused-ignores']
@@ -2,7 +2,33 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## v2.4.1.dev0 (2024-06-17)
5
+ ## v2.4.3 (2024-08-07)
6
+
7
+ ### Feat
8
+
9
+ - set default building target to "all" if `--target` is not specified
10
+ - set default paths to current directory if `--paths` is not specified
11
+
12
+ ## v2.4.2 (2024-08-01)
13
+
14
+ ### Feat
15
+
16
+ - support `--enable-preview-targets`
17
+ - support `--include-all-apps` while find_apps
18
+ - support `--output-format json` while find_apps
19
+ - support `include_disabled_apps` while `find_apps`
20
+
21
+ ### Fix
22
+
23
+ - ignore specified target if unknown in current ESP-IDF branch instead of raise exception
24
+ - correct `post_build` actions for succeeded with warnings builds
25
+ - **completions**: fix typos in help
26
+
27
+ ### Refactor
28
+
29
+ - update deprecated `datetime.utcnow`
30
+
31
+ ## v2.4.1 (2024-06-18)
6
32
 
7
33
  ### Fix
8
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: idf-build-apps
3
- Version: 2.4.1.dev0
3
+ Version: 2.4.3
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
@@ -52,15 +52,17 @@ docs_dir = os.path.dirname(__file__)
52
52
  api_dir = os.path.join(docs_dir, 'api')
53
53
  if os.path.isdir(api_dir):
54
54
  shutil.rmtree(api_dir)
55
- subprocess.run([
56
- 'sphinx-apidoc',
57
- os.path.join(docs_dir, '..', 'idf_build_apps'),
58
- '-f',
59
- '-H',
60
- 'API Reference',
61
- '--no-headings',
62
- '-t',
63
- '_apidoc_templates',
64
- '-o',
65
- api_dir,
66
- ])
55
+ subprocess.run(
56
+ [
57
+ 'sphinx-apidoc',
58
+ os.path.join(docs_dir, '..', 'idf_build_apps'),
59
+ '-f',
60
+ '-H',
61
+ 'API Reference',
62
+ '--no-headings',
63
+ '-t',
64
+ '_apidoc_templates',
65
+ '-o',
66
+ api_dir,
67
+ ]
68
+ )
@@ -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.4.1.dev0'
11
+ __version__ = '2.4.3'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -10,9 +10,7 @@ import re
10
10
  import shutil
11
11
  import sys
12
12
  import typing as t
13
- from datetime import (
14
- datetime,
15
- )
13
+ from datetime import datetime, timezone
16
14
  from pathlib import (
17
15
  Path,
18
16
  )
@@ -153,10 +151,12 @@ class App(BaseModel):
153
151
  size_json_filename: t.Optional[str] = None,
154
152
  **kwargs: t.Any,
155
153
  ) -> None:
156
- kwargs.update({
157
- 'app_dir': app_dir,
158
- 'target': target,
159
- })
154
+ kwargs.update(
155
+ {
156
+ 'app_dir': app_dir,
157
+ 'target': target,
158
+ }
159
+ )
160
160
  super().__init__(**kwargs)
161
161
 
162
162
  # These internal variables store the paths with environment variables and placeholders;
@@ -169,12 +169,14 @@ class App(BaseModel):
169
169
  self._is_build_log_path_temp = not bool(build_log_filename)
170
170
 
171
171
  # pass all parameters to initialize hook method
172
- kwargs.update({
173
- 'work_dir': self._work_dir,
174
- 'build_dir': self._build_dir,
175
- 'build_log_filename': build_log_filename,
176
- 'size_json_filename': size_json_filename,
177
- })
172
+ kwargs.update(
173
+ {
174
+ 'work_dir': self._work_dir,
175
+ 'build_dir': self._build_dir,
176
+ 'build_log_filename': build_log_filename,
177
+ 'size_json_filename': size_json_filename,
178
+ }
179
+ )
178
180
  self._kwargs = kwargs
179
181
  self._initialize_hook(**kwargs)
180
182
 
@@ -472,11 +474,11 @@ class App(BaseModel):
472
474
  def record_build_duration(func):
473
475
  @functools.wraps(func)
474
476
  def wrapper(self, *args, **kwargs):
475
- self._build_timestamp = datetime.utcnow()
477
+ self._build_timestamp = datetime.now(timezone.utc)
476
478
  try:
477
479
  return func(self, *args, **kwargs)
478
480
  finally:
479
- self._build_duration = (datetime.utcnow() - self._build_timestamp).total_seconds()
481
+ self._build_duration = (datetime.now(timezone.utc) - self._build_timestamp).total_seconds()
480
482
 
481
483
  return wrapper
482
484
 
@@ -544,6 +546,14 @@ class App(BaseModel):
544
546
  modified_files: t.Union[t.List[str], str, None] = None,
545
547
  check_app_dependencies: bool = False,
546
548
  ) -> None:
549
+ if self.build_status not in (
550
+ BuildStatus.UNKNOWN,
551
+ BuildStatus.SHOULD_BE_BUILT,
552
+ ):
553
+ self.build_comment = f'Build {self.build_status.value}. Skipping...'
554
+ return
555
+
556
+ # real build starts here
547
557
  self._pre_build()
548
558
 
549
559
  try:
@@ -560,8 +570,16 @@ class App(BaseModel):
560
570
  self._post_build()
561
571
 
562
572
  def _post_build(self) -> None:
573
+ """Post build actions for failed/success builds"""
574
+ if self.build_status not in (
575
+ BuildStatus.FAILED,
576
+ BuildStatus.SUCCESS,
577
+ ):
578
+ return
579
+
563
580
  self._build_stage = BuildStage.POST_BUILD
564
581
 
582
+ # both status applied
565
583
  if self.copy_sdkconfig:
566
584
  try:
567
585
  shutil.copy(
@@ -573,12 +591,21 @@ class App(BaseModel):
573
591
  else:
574
592
  self._logger.debug('Copied sdkconfig file from %s to %s', self.work_dir, self.build_path)
575
593
 
594
+ # for originally success builds generate size.json if enabled
595
+ #
596
+ # for the rest of the actions, we need to check if there's further build warnings
597
+ # to tell if this build is successful or not
598
+ if self.build_status == BuildStatus.SUCCESS:
599
+ self.write_size_json()
600
+
576
601
  if not os.path.isfile(self.build_log_path):
602
+ self._logger.warning(f'{self.build_log_path} does not exist. Skipping post build actions...')
577
603
  return
578
604
 
605
+ # check warnings
579
606
  has_unignored_warning = False
580
607
  with open(self.build_log_path) as fr:
581
- lines = [line.rstrip() for line in fr.readlines() if line.rstrip()]
608
+ lines = [line.rstrip() for line in fr if line.rstrip()]
582
609
  for line in lines:
583
610
  is_error_or_warning, ignored = self.is_error_or_warning(line)
584
611
  if is_error_or_warning:
@@ -588,6 +615,14 @@ class App(BaseModel):
588
615
  self._logger.warning('%s', line)
589
616
  has_unignored_warning = True
590
617
 
618
+ # correct build status for originally successful builds
619
+ if self.build_status == BuildStatus.SUCCESS:
620
+ if self.check_warnings and has_unignored_warning:
621
+ self.build_status = BuildStatus.FAILED
622
+ self.build_comment = 'build succeeded with warnings'
623
+ elif has_unignored_warning:
624
+ self.build_comment = 'build succeeded with warnings'
625
+
591
626
  if self.build_status == BuildStatus.FAILED:
592
627
  # print last few lines to help debug
593
628
  self._logger.error(
@@ -598,14 +633,14 @@ class App(BaseModel):
598
633
  for line in lines[-self.LOG_DEBUG_LINES :]:
599
634
  self._logger.error('%s', line)
600
635
 
601
- if self._is_build_log_path_temp and self.build_status == BuildStatus.SUCCESS:
636
+ return
637
+
638
+ # Actions for real success builds
639
+ # remove temp log file
640
+ if self._is_build_log_path_temp:
602
641
  os.unlink(self.build_log_path)
603
642
  self._logger.debug('Removed success build temporary log file: %s', self.build_log_path)
604
643
 
605
- # Generate Size Files
606
- if self.build_status == BuildStatus.SUCCESS:
607
- self.write_size_json()
608
-
609
644
  # Cleanup build directory if not preserving
610
645
  if not self.preserve:
611
646
  exclude_list = []
@@ -619,13 +654,6 @@ class App(BaseModel):
619
654
  )
620
655
  self._logger.debug('Removed built binaries under: %s', self.build_path)
621
656
 
622
- # Build Result
623
- if self.check_warnings and has_unignored_warning:
624
- self.build_status = BuildStatus.FAILED
625
- self.build_comment = 'build succeeded with warnings'
626
- elif has_unignored_warning:
627
- self.build_comment = 'build succeeded with warnings'
628
-
629
657
  def _build(
630
658
  self,
631
659
  *,
@@ -665,12 +693,14 @@ class App(BaseModel):
665
693
  else:
666
694
  with open(self.size_json_path, 'w') as fw:
667
695
  subprocess_run(
668
- ([
669
- sys.executable,
670
- str(IDF_SIZE_PY),
671
- '--json',
672
- map_file,
673
- ]),
696
+ (
697
+ [
698
+ sys.executable,
699
+ str(IDF_SIZE_PY),
700
+ '--json',
701
+ map_file,
702
+ ]
703
+ ),
674
704
  log_terminal=False,
675
705
  log_fs=fw,
676
706
  check=True,
@@ -922,7 +952,7 @@ class CMakeApp(App):
922
952
  '-DSDKCONFIG_DEFAULTS={}'.format(';'.join(self.sdkconfig_files) if self.sdkconfig_files else ';'),
923
953
  ]
924
954
 
925
- if modified_components is not None and check_app_dependencies and self.build_status == BuildStatus.UNKNOWN:
955
+ if self.build_status == BuildStatus.UNKNOWN and modified_components is not None and check_app_dependencies:
926
956
  subprocess_run(
927
957
  [*common_args, 'reconfigure'],
928
958
  log_terminal=self._is_build_log_path_temp,
@@ -80,6 +80,7 @@ IDF_VERSION = to_version(f'{IDF_VERSION_MAJOR}.{IDF_VERSION_MINOR}.{IDF_VERSION_
80
80
 
81
81
  class BuildStatus(str, enum.Enum):
82
82
  UNKNOWN = 'unknown'
83
+ DISABLED = 'disabled'
83
84
  SKIPPED = 'skipped'
84
85
  SHOULD_BE_BUILT = 'should be built'
85
86
  FAILED = 'build failed'
@@ -98,14 +99,14 @@ class BuildStage(str, enum.Enum):
98
99
 
99
100
 
100
101
  completion_instructions = """
101
- With `--activate` option detect your shell type and add the appropriate commands to your shell's config file
102
- so that it is run on startup. You will likely have to restart
102
+ With the `--activate` option, detect your shell type and add the appropriate commands to your shell's config file
103
+ so that it runs on startup. You will likely have to restart.
103
104
  or re-login for the autocompletion to start working.
104
105
 
105
- You can also specify your shell by the `--shell` option.
106
+ You can also specify your shell using the `--shell` option.
106
107
 
107
- If you do not want automaticall modifying your shell config file
108
- you can manually add commands provided below to activate autocompletion
108
+ If you do not want automatic modification of your shell configuration file
109
+ You can manually add the commands provided below to activate autocompletion.
109
110
  or run them in your current terminal session for one-time activation.
110
111
 
111
112
  Once again, you will likely have to restart
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import logging
@@ -43,6 +43,7 @@ def _get_apps_from_path(
43
43
  check_app_dependencies: bool = False,
44
44
  sdkconfig_defaults_str: t.Optional[str] = None,
45
45
  include_skipped_apps: bool = False,
46
+ include_disabled_apps: bool = False,
46
47
  ) -> t.List[App]:
47
48
  modified_components = to_list(modified_components)
48
49
  modified_files = to_list(modified_files)
@@ -50,7 +51,8 @@ def _get_apps_from_path(
50
51
  def _validate_app(_app: App) -> bool:
51
52
  if target not in _app.supported_targets:
52
53
  LOGGER.debug('=> Ignored. %s only supports targets: %s', _app, ', '.join(_app.supported_targets))
53
- return False
54
+ _app.build_status = BuildStatus.DISABLED
55
+ return include_disabled_apps
54
56
 
55
57
  _app._check_should_build(
56
58
  manifest_rootpath=manifest_rootpath,
@@ -38,6 +38,7 @@ import os.path
38
38
  import typing as t
39
39
  from datetime import (
40
40
  datetime,
41
+ timezone,
41
42
  )
42
43
  from xml.etree import (
43
44
  ElementTree,
@@ -81,14 +82,17 @@ class TestCase:
81
82
  raise ValueError('Only one of failure_reason, skipped_reason, error_reason can be set')
82
83
 
83
84
  self.duration_sec = duration_sec
84
- self.timestamp = timestamp or datetime.utcnow()
85
+ self.timestamp = timestamp or datetime.now(timezone.utc)
85
86
 
86
87
  self.properties = properties or {}
87
88
 
88
89
  @classmethod
89
90
  def from_app(cls, app: App) -> 'TestCase':
90
- if app.build_status not in (BuildStatus.FAILED, BuildStatus.SUCCESS, BuildStatus.SKIPPED):
91
- raise ValueError(f'Cannot create test case from app with build status {app.build_status}')
91
+ if app.build_status in (BuildStatus.UNKNOWN, BuildStatus.SHOULD_BE_BUILT):
92
+ raise ValueError(
93
+ f'Cannot create build report for apps with build status {app.build_status}. '
94
+ f'Please finish the build process first.'
95
+ )
92
96
 
93
97
  kwargs: t.Dict[str, t.Any] = {
94
98
  'name': app.build_path,
@@ -98,7 +102,7 @@ class TestCase:
98
102
  }
99
103
  if app.build_status == BuildStatus.FAILED:
100
104
  kwargs['failure_reason'] = app.build_comment
101
- elif app.build_status == BuildStatus.SKIPPED:
105
+ elif app.build_status in (BuildStatus.DISABLED, BuildStatus.SKIPPED):
102
106
  kwargs['skipped_reason'] = app.build_comment
103
107
 
104
108
  if app.size_json_path and os.path.isfile(app.size_json_path):
@@ -106,7 +110,7 @@ class TestCase:
106
110
  for k, v in json.load(f).items():
107
111
  kwargs['properties'][f'{k}'] = str(v)
108
112
 
109
- return cls(**kwargs) # type
113
+ return cls(**kwargs)
110
114
 
111
115
  @property
112
116
  def is_failed(self) -> bool:
@@ -155,7 +159,7 @@ class TestSuite:
155
159
  self.skipped = 0
156
160
 
157
161
  self.duration_sec: float = 0
158
- self.timestamp = datetime.utcnow()
162
+ self.timestamp = datetime.now(timezone.utc)
159
163
 
160
164
  self.properties = get_sys_info()
161
165
 
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import os
@@ -26,7 +26,7 @@ def get_processor_name():
26
26
  if os.path.isfile('/proc/cpuinfo'):
27
27
  try:
28
28
  with open('/proc/cpuinfo') as f:
29
- for line in f.readlines():
29
+ for line in f:
30
30
  if 'model name' in line:
31
31
  return re.sub('.*model name.*:', '', line, 1).strip()
32
32
  except Exception:
@@ -11,6 +11,7 @@ import re
11
11
  import sys
12
12
  import textwrap
13
13
  import typing as t
14
+ from copy import deepcopy
14
15
 
15
16
  import argcomplete
16
17
  from pydantic import (
@@ -127,6 +128,7 @@ def find_apps(
127
128
  ignore_app_dependencies_filepatterns: t.Optional[t.Union[t.List[str], str]] = None,
128
129
  sdkconfig_defaults: t.Optional[str] = None,
129
130
  include_skipped_apps: bool = False,
131
+ include_disabled_apps: bool = False,
130
132
  ) -> t.List[App]:
131
133
  """
132
134
  Find app directories in paths (possibly recursively), which contain apps for the given build system, compatible
@@ -158,6 +160,7 @@ def find_apps(
158
160
  :param sdkconfig_defaults: semicolon-separated string, pass to idf.py -DSDKCONFIG_DEFAULTS if specified,
159
161
  also could be set via environment variables "SDKCONFIG_DEFAULTS"
160
162
  :param include_skipped_apps: include skipped apps or not
163
+ :param include_disabled_apps: include disabled apps or not
161
164
  :return: list of found apps
162
165
  """
163
166
  if default_build_targets:
@@ -223,6 +226,7 @@ def find_apps(
223
226
  modified_files=modified_files,
224
227
  sdkconfig_defaults_str=sdkconfig_defaults,
225
228
  include_skipped_apps=include_skipped_apps,
229
+ include_disabled_apps=include_disabled_apps,
226
230
  )
227
231
  )
228
232
 
@@ -365,12 +369,14 @@ def build_apps(
365
369
  if os.path.isfile(app.size_json_path):
366
370
  with open(build_apps_args.collect_size_info, 'a') as fw:
367
371
  fw.write(
368
- json.dumps({
369
- 'app_name': app.name,
370
- 'config_name': app.config_name,
371
- 'target': app.target,
372
- 'path': app.size_json_path,
373
- })
372
+ json.dumps(
373
+ {
374
+ 'app_name': app.name,
375
+ 'config_name': app.config_name,
376
+ 'target': app.target,
377
+ 'path': app.size_json_path,
378
+ }
379
+ )
374
380
  + '\n'
375
381
  )
376
382
  LOGGER.debug('Recorded size info file path in %s', build_apps_args.collect_size_info)
@@ -452,8 +458,12 @@ def get_parser() -> argparse.ArgumentParser:
452
458
  help='Path to the default configuration file, toml file',
453
459
  )
454
460
 
455
- common_args.add_argument('-p', '--paths', nargs='+', help='One or more paths to look for apps')
456
- common_args.add_argument('-t', '--target', help='filter apps by given target')
461
+ common_args.add_argument(
462
+ '-p', '--paths', nargs='*', help='One or more paths to look for apps. By default build the current directory.'
463
+ )
464
+ common_args.add_argument(
465
+ '-t', '--target', default='all', help='filter apps by given target. By default build all supported targets.'
466
+ )
457
467
  common_args.add_argument(
458
468
  '--build-system', default='cmake', choices=['cmake', 'make'], help='filter apps by given build system'
459
469
  )
@@ -551,14 +561,19 @@ def get_parser() -> argparse.ArgumentParser:
551
561
  action='store_true',
552
562
  help='Exit with error if any of the manifest rules does not exist on your filesystem',
553
563
  )
554
-
564
+ common_args.add_argument(
565
+ '--enable-preview-targets',
566
+ action='store_true',
567
+ help='Build the apps with all targets in the current ESP-IDF branch, '
568
+ 'including preview targets, when the app supports the target.',
569
+ )
555
570
  common_args.add_argument(
556
571
  '--default-build-targets',
557
572
  nargs='+',
558
- help='space-separated list of supported targets. Targets supported in current ESP-IDF branch '
559
- '(except preview ones) would be used if this option is not set.',
573
+ help='space-separated list of string which specifies the targets for building the apps. '
574
+ 'If provided, the apps will be built only with the specified targets '
575
+ 'when the app supports the target.',
560
576
  )
561
-
562
577
  common_args.add_argument(
563
578
  '--modified-components',
564
579
  type=semicolon_separated_str_to_list,
@@ -610,7 +625,7 @@ def get_parser() -> argparse.ArgumentParser:
610
625
 
611
626
  find_parser = actions.add_parser(
612
627
  'find',
613
- help='Find the buildable applications. Run `idf-build-apps find --help` for more information on a command',
628
+ help='Find the buildable applications. Run `idf-build-apps find --help` for more information on a command.',
614
629
  description='Find the buildable applications in the given path or paths for specified chips. '
615
630
  '`--path` and `--target` options must be provided. '
616
631
  'By default, print the found apps in stdout. '
@@ -618,12 +633,23 @@ def get_parser() -> argparse.ArgumentParser:
618
633
  parents=[common_args],
619
634
  formatter_class=IdfBuildAppsCliFormatter,
620
635
  )
621
-
622
636
  find_parser.add_argument('-o', '--output', help='Print the found apps to the specified file instead of stdout')
637
+ find_parser.add_argument(
638
+ '--output-format',
639
+ choices=['raw', 'json'],
640
+ default='raw',
641
+ help='Output format. In "raw" format, each line is a valid json that represents the app. '
642
+ 'In "json" format, the whole file is a JSON file of a list of apps.',
643
+ )
644
+ find_parser.add_argument(
645
+ '--include-all-apps',
646
+ action='store_true',
647
+ help='Include skipped and disabled apps. By default only apps that should be built.',
648
+ )
623
649
 
624
650
  build_parser = actions.add_parser(
625
651
  'build',
626
- help='Build the found applications. Run `idf-build-apps build --help` for more information on a command',
652
+ help='Build the found applications. Run `idf-build-apps build --help` for more information on a command.',
627
653
  description='Build the application in the given path or paths for specified chips. '
628
654
  '`--path` and `--target` options must be provided.',
629
655
  parents=[common_args],
@@ -696,18 +722,18 @@ def get_parser() -> argparse.ArgumentParser:
696
722
  completions_parser = actions.add_parser(
697
723
  'completions',
698
724
  help='Add the autocompletion activation script to the shell rc file. '
699
- 'Run `idf-build-apps complations --help` for more information on a command',
725
+ 'Run `idf-build-apps completions --help` for more information on a command.',
700
726
  description='Without `--activate` option print instructions for manual activation. '
701
- 'With `--activate` option add the autocompletion activation script to the shell rc file '
702
- 'for bash, zsh, or fish. Other shells are not supported. '
703
- 'The `--shell` option used only with the `--activate` option, '
727
+ 'With the `--activate` option, add the autocompletion activation script to the shell rc file '
728
+ 'for bash, zsh, or fish. Other shells are not supported.'
729
+ 'The `--shell` option is used only with the `--activate` option, '
704
730
  'if provided, add the autocompletion activation script to the given shell; '
705
731
  'without this argument, will detect shell type automatically. '
706
732
  'May need to restart or re-login for the autocompletion to start working',
707
733
  formatter_class=IdfBuildAppsCliFormatter,
708
734
  )
709
735
  completions_parser.add_argument(
710
- '-a', '--activate', action='store_true', help='Activate autocompletion automatically and permanently'
736
+ '-a', '--activate', action='store_true', help='Activate autocompletion automatically and permanently. '
711
737
  )
712
738
  completions_parser.add_argument(
713
739
  '-s',
@@ -737,28 +763,27 @@ def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace) ->
737
763
  raise InvalidCommand('subcommand is required. {find, build, completions}')
738
764
 
739
765
  if not args.paths:
740
- raise InvalidCommand(
741
- 'Must specify at least one path to search for the apps ' 'with CLI option "-p <path>" or "--path <path>"'
742
- )
766
+ cur_dir = os.getcwd()
767
+ LOGGER.debug(f'--paths is missing. Set --path as current directory "{cur_dir}".')
768
+ args.paths = [cur_dir]
743
769
 
744
770
  if not args.target:
745
- raise InvalidCommand(
746
- 'Must specify current build target with CLI option "-t <target>" or "--target <target>". '
747
- '(choices: [{}]'.format(','.join([*ALL_TARGETS, 'all']))
748
- )
771
+ LOGGER.debug('--target is missing. Set --target as "all".')
772
+ args.target = 'all'
749
773
 
750
774
  default_build_targets = []
751
775
  if args.default_build_targets:
752
776
  for target in args.default_build_targets:
753
777
  if target not in ALL_TARGETS:
754
- raise InvalidCommand(
755
- f'Unrecognizable target {target} specified with "--default-build-targets". '
778
+ LOGGER.warning(
779
+ f'Ignoring... Unrecognizable target {target} specified with "--default-build-targets". '
756
780
  f'Current ESP-IDF available targets: {ALL_TARGETS}'
757
781
  )
758
-
759
- if target not in default_build_targets:
782
+ elif target not in default_build_targets:
760
783
  default_build_targets.append(target)
761
- args.default_build_targets = default_build_targets
784
+ args.default_build_targets = default_build_targets
785
+ elif args.enable_preview_targets:
786
+ args.default_build_targets = deepcopy(ALL_TARGETS)
762
787
 
763
788
  if args.ignore_app_dependencies_components is not None:
764
789
  if args.modified_components is None:
@@ -796,36 +821,49 @@ def main():
796
821
  if args.action == 'build':
797
822
  args.output = None # build action doesn't support output option
798
823
 
824
+ kwargs = {
825
+ 'build_system': args.build_system,
826
+ 'recursive': args.recursive,
827
+ 'exclude_list': args.exclude or [],
828
+ 'work_dir': args.work_dir,
829
+ 'build_dir': args.build_dir or 'build',
830
+ 'config_rules_str': args.config,
831
+ 'build_log_filename': args.build_log,
832
+ 'size_json_filename': args.size_file,
833
+ 'check_warnings': args.check_warnings,
834
+ 'manifest_rootpath': args.manifest_rootpath,
835
+ 'manifest_files': args.manifest_file,
836
+ 'check_manifest_rules': args.check_manifest_rules,
837
+ 'default_build_targets': args.default_build_targets,
838
+ 'modified_components': args.modified_components,
839
+ 'modified_files': args.modified_files,
840
+ 'ignore_app_dependencies_components': args.ignore_app_dependencies_components,
841
+ 'ignore_app_dependencies_filepatterns': args.ignore_app_dependencies_filepatterns,
842
+ 'sdkconfig_defaults': args.sdkconfig_defaults,
843
+ }
844
+ # only useful in find
845
+ if args.action == 'find' and args.include_all_apps:
846
+ kwargs['include_skipped_apps'] = True
847
+ kwargs['include_disabled_apps'] = True
848
+
799
849
  # real call starts here
800
- apps = find_apps(
801
- args.paths,
802
- args.target,
803
- build_system=args.build_system,
804
- recursive=args.recursive,
805
- exclude_list=args.exclude or [],
806
- work_dir=args.work_dir,
807
- build_dir=args.build_dir or 'build',
808
- config_rules_str=args.config,
809
- build_log_filename=args.build_log,
810
- size_json_filename=args.size_file,
811
- check_warnings=args.check_warnings,
812
- manifest_rootpath=args.manifest_rootpath,
813
- manifest_files=args.manifest_file,
814
- check_manifest_rules=args.check_manifest_rules,
815
- default_build_targets=args.default_build_targets,
816
- modified_components=args.modified_components,
817
- modified_files=args.modified_files,
818
- ignore_app_dependencies_components=args.ignore_app_dependencies_components,
819
- ignore_app_dependencies_filepatterns=args.ignore_app_dependencies_filepatterns,
820
- sdkconfig_defaults=args.sdkconfig_defaults,
821
- )
850
+ apps = find_apps(args.paths, args.target, **kwargs)
822
851
 
823
852
  if args.action == 'find':
824
853
  if args.output:
825
854
  os.makedirs(os.path.dirname(os.path.realpath(args.output)), exist_ok=True)
855
+ if args.output.endswith('.json'):
856
+ LOGGER.info('Detecting output file ends with ".json", writing json file.')
857
+ args.output_format = 'json'
858
+
826
859
  with open(args.output, 'w') as fw:
827
- for app in apps:
828
- fw.write(app.to_json() + '\n')
860
+ if args.output_format == 'raw':
861
+ for app in apps:
862
+ fw.write(app.to_json() + '\n')
863
+ elif args.output_format == 'json':
864
+ fw.write(json.dumps([app.model_dump() for app in apps], indent=2))
865
+ else:
866
+ raise ValueError(f'Output format {args.output_format} is not supported.')
829
867
  else:
830
868
  for app in apps:
831
869
  print(app)
@@ -94,20 +94,24 @@ class SocHeader(dict):
94
94
 
95
95
  @classmethod
96
96
  def _parse_soc_header(cls, target: str) -> t.Dict[str, t.Any]:
97
- soc_headers_dirs = cls._get_dirs_from_candidates([
98
- # master c5 mp
99
- os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'mp', 'include', 'soc')),
100
- # other branches
101
- os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'include', 'soc')),
102
- # release/v4.2
103
- os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', 'soc', target, 'include', 'soc')),
104
- ])
105
- esp_rom_headers_dirs = cls._get_dirs_from_candidates([
106
- # master c5 mp
107
- os.path.abspath(os.path.join(IDF_PATH, 'components', 'esp_rom', target, 'mp', target)),
108
- # others
109
- os.path.abspath(os.path.join(IDF_PATH, 'components', 'esp_rom', target)),
110
- ])
97
+ soc_headers_dirs = cls._get_dirs_from_candidates(
98
+ [
99
+ # master c5 mp
100
+ os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'mp', 'include', 'soc')),
101
+ # other branches
102
+ os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'include', 'soc')),
103
+ # release/v4.2
104
+ os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', 'soc', target, 'include', 'soc')),
105
+ ]
106
+ )
107
+ esp_rom_headers_dirs = cls._get_dirs_from_candidates(
108
+ [
109
+ # master c5 mp
110
+ os.path.abspath(os.path.join(IDF_PATH, 'components', 'esp_rom', target, 'mp', target)),
111
+ # others
112
+ os.path.abspath(os.path.join(IDF_PATH, 'components', 'esp_rom', target)),
113
+ ]
114
+ )
111
115
 
112
116
  header_files: t.List[str] = []
113
117
  for d in [*soc_headers_dirs, *esp_rom_headers_dirs]:
@@ -61,7 +61,7 @@ idf-build-apps = "idf_build_apps:main.main"
61
61
 
62
62
  [tool.commitizen]
63
63
  name = "cz_conventional_commits"
64
- version = "2.4.1.dev0"
64
+ version = "2.4.3"
65
65
  tag_format = "v$version"
66
66
  version_files = [
67
67
  "idf_build_apps/__init__.py",
@@ -77,10 +77,6 @@ testpaths = [
77
77
  profile = 'black'
78
78
  force_grid_wrap = 1
79
79
 
80
- [tool.black]
81
- line-length = 120
82
- skip-string-normalization = true
83
-
84
80
  [tool.ruff]
85
81
  line-length = 120
86
82
  target-version = "py37"
@@ -30,7 +30,7 @@ entry_points = \
30
30
  {'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
31
31
 
32
32
  setup(name='idf-build-apps',
33
- version='2.4.1.dev0',
33
+ version='2.4.3',
34
34
  description='Tools for building ESP-IDF related apps.',
35
35
  author=None,
36
36
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -112,7 +112,11 @@ class TestBuild:
112
112
  apps = [
113
113
  CMakeApp(test_dir, 'esp32', build_dir='build_1'),
114
114
  CMakeApp(test_dir, 'esp32', build_dir='build_2'),
115
+ CMakeApp(test_dir, 'esp32', build_dir='build_3'),
116
+ CMakeApp(test_dir, 'esp32', build_dir='build_4'),
115
117
  ]
118
+ apps[2].build_status = BuildStatus.DISABLED
119
+ apps[3].build_status = BuildStatus.SKIPPED
116
120
 
117
121
  build_apps(deepcopy(apps), dry_run=True, junitxml=str(tmpdir / 'test.xml'))
118
122
 
@@ -123,13 +127,20 @@ class TestBuild:
123
127
  assert test_suite.attrib['tests'] == '0'
124
128
  assert test_suite.attrib['failures'] == '0'
125
129
  assert test_suite.attrib['errors'] == '0'
126
- assert test_suite.attrib['skipped'] == '2'
130
+ assert test_suite.attrib['skipped'] == '4'
127
131
 
128
132
  for i, testcase in enumerate(test_suite.findall('testcase')):
129
133
  assert testcase.attrib['name'] == apps[i].build_path
130
134
  assert float(testcase.attrib['time']) > 0
131
135
  assert testcase.find('skipped') is not None
132
- assert testcase.find('skipped').attrib['message'] == 'dry run'
136
+ if i in (0, 1):
137
+ assert testcase.find('skipped').attrib['message'] == 'dry run'
138
+ elif i == 2:
139
+ assert testcase.find('skipped').attrib['message'] == 'Build disabled. Skipping...'
140
+ elif i == 3:
141
+ assert testcase.find('skipped').attrib['message'] == 'Build skipped. Skipping...'
142
+ else:
143
+ assert False # not expected
133
144
 
134
145
  build_apps(deepcopy(apps), junitxml=str(tmpdir / 'test.xml'))
135
146
 
@@ -140,14 +151,21 @@ class TestBuild:
140
151
  assert test_suite.attrib['tests'] == '2'
141
152
  assert test_suite.attrib['failures'] == '0'
142
153
  assert test_suite.attrib['errors'] == '0'
143
- assert test_suite.attrib['skipped'] == '0'
154
+ assert test_suite.attrib['skipped'] == '2'
144
155
 
145
156
  for i, testcase in enumerate(test_suite.findall('testcase')):
146
- assert testcase.attrib['name'] == apps[i].build_path
147
157
  assert float(testcase.attrib['time']) > 0
148
- assert testcase.find('skipped') is None
158
+ assert testcase.attrib['name'] == apps[i].build_path
149
159
  assert testcase.find('error') is None
150
160
  assert testcase.find('failure') is None
161
+ if i in (0, 1):
162
+ assert testcase.find('skipped') is None
163
+ elif i == 2:
164
+ assert testcase.find('skipped').attrib['message'] == 'Build disabled. Skipping...'
165
+ elif i == 3:
166
+ assert testcase.find('skipped').attrib['message'] == 'Build skipped. Skipping...'
167
+ else:
168
+ assert False # not expected
151
169
 
152
170
  def test_work_dir_inside_relative_app_dir(self, tmp_path):
153
171
  create_project('foo', tmp_path)
@@ -109,6 +109,33 @@ examples/get-started:
109
109
  == apps
110
110
  )
111
111
 
112
+ def test_include_disabled_apps(self, tmpdir):
113
+ test_dir = Path(IDF_PATH) / 'examples' / 'get-started'
114
+ yaml_file = tmpdir / 'test.yml'
115
+ yaml_file.write_text(
116
+ f"""
117
+ {test_dir}:
118
+ enable:
119
+ - if: IDF_TARGET == "esp32s2"
120
+ """,
121
+ encoding='utf8',
122
+ )
123
+
124
+ test_dir = Path(IDF_PATH) / 'examples' / 'get-started'
125
+ apps = find_apps(
126
+ str(test_dir), 'esp32', recursive=True, manifest_files=str(yaml_file), include_disabled_apps=False
127
+ )
128
+ assert not apps
129
+
130
+ apps = find_apps(
131
+ str(test_dir), 'esp32', recursive=True, manifest_files=str(yaml_file), include_disabled_apps=True
132
+ )
133
+ assert apps
134
+ for app in apps:
135
+ assert app.build_status == BuildStatus.DISABLED
136
+ app.build()
137
+ assert app.build_comment == 'Build disabled. Skipping...'
138
+
112
139
 
113
140
  class TestFindWithModifiedFilesComponents:
114
141
  @pytest.mark.parametrize(