idf-build-apps 2.0.0rc0__tar.gz → 2.0.0rc1__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 (63) hide show
  1. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/check-pre-commit.yml +4 -0
  2. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/test-build-idf-apps.yml +3 -3
  3. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/CHANGELOG.md +26 -0
  4. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/PKG-INFO +1 -1
  5. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/index.rst +1 -0
  6. idf_build_apps-2.0.0rc1/docs/migration/1.x_to_2.x.md +135 -0
  7. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/__init__.py +4 -3
  8. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/app.py +81 -69
  9. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/constants.py +3 -3
  10. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/main.py +42 -3
  11. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/manifest/manifest.py +11 -9
  12. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/session_args.py +17 -9
  13. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/utils.py +43 -14
  14. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/pyproject.toml +1 -1
  15. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/setup.py +1 -1
  16. idf_build_apps-2.0.0rc1/tests/test_app.py +95 -0
  17. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/tests/test_manifest.py +29 -2
  18. idf_build_apps-2.0.0rc0/tests/test_app.py +0 -34
  19. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.editorconfig +0 -0
  20. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.git-blame-ignore-revs +0 -0
  21. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.gitattributes +0 -0
  22. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/dependabot.yml +0 -0
  23. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/issue_comment.yml +0 -0
  24. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/new_issues.yml +0 -0
  25. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/new_prs.yml +0 -0
  26. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/publish-pypi.yml +0 -0
  27. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.github/workflows/test-build-docs.yml +0 -0
  28. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.gitignore +0 -0
  29. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.pre-commit-config.yaml +0 -0
  30. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/.readthedocs.yml +0 -0
  31. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/CONTRIBUTING.md +0 -0
  32. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/LICENSE +0 -0
  33. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/README.md +0 -0
  34. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/CHANGELOG.md +0 -0
  35. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/CONTRIBUTING.md +0 -0
  36. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/Makefile +0 -0
  37. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_apidoc_templates/module.rst_t +0 -0
  38. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_apidoc_templates/package.rst_t +0 -0
  39. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_apidoc_templates/toc.rst_t +0 -0
  40. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_static/espressif-logo.svg +0 -0
  41. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_static/theme_overrides.css +0 -0
  42. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/_templates/layout.html +0 -0
  43. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/cli.rst +0 -0
  44. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/conf.py +0 -0
  45. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/config_file.md +0 -0
  46. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/find_build.md +0 -0
  47. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/docs/manifest.md +0 -0
  48. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/__main__.py +0 -0
  49. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/build_apps_args.py +0 -0
  50. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/config.py +0 -0
  51. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/finder.py +0 -0
  52. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/junit/__init__.py +0 -0
  53. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/junit/report.py +0 -0
  54. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/junit/utils.py +0 -0
  55. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/log.py +0 -0
  56. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/manifest/__init__.py +0 -0
  57. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/manifest/if_parser.py +0 -0
  58. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/idf_build_apps/manifest/soc_header.py +0 -0
  59. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/license_header.txt +0 -0
  60. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/tests/conftest.py +0 -0
  61. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/tests/test_build.py +0 -0
  62. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/tests/test_finder.py +0 -0
  63. {idf_build_apps-2.0.0rc0 → idf_build_apps-2.0.0rc1}/tests/test_utils.py +0 -0
@@ -11,4 +11,8 @@ jobs:
11
11
  with:
12
12
  fetch-depth: 0
13
13
  - uses: actions/setup-python@v5
14
+ - id: changed-files
15
+ uses: tj-actions/changed-files@v41
14
16
  - uses: pre-commit/action@v3.0.0
17
+ with:
18
+ extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }}
@@ -28,7 +28,7 @@ jobs:
28
28
  pip install flit
29
29
  flit build
30
30
  - name: Upload built python packages
31
- uses: actions/upload-artifact@v3
31
+ uses: actions/upload-artifact@v4
32
32
  with:
33
33
  name: wheel
34
34
  path: dist/idf_build_apps-*.whl
@@ -38,13 +38,13 @@ jobs:
38
38
  needs: build-python-packages
39
39
  strategy:
40
40
  matrix:
41
- idf-branch: [ release-v5.0, release-v5.1 ]
41
+ idf-branch: [ release-v5.0, release-v5.1, release-v5.2 ]
42
42
  runs-on: ubuntu-latest
43
43
  container:
44
44
  image: espressif/idf:${{ matrix.idf-branch }}
45
45
  steps:
46
46
  - name: Download wheel
47
- uses: actions/download-artifact@v3
47
+ uses: actions/download-artifact@v4
48
48
  with:
49
49
  name: wheel
50
50
  - name: Build the Apps
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v2.0.0rc1 (2024-01-05)
6
+
7
+ ### BREAKING CHANGE
8
+
9
+ - - Turn `app.build()` arguments to kwargs
10
+
11
+ ### Feat
12
+
13
+ - support custom `_pre_build`, `_post_build` in App instances
14
+ - add `json_to_app` method with support custom classes
15
+
16
+ ### Fix
17
+
18
+ - remove os-specific `os.mknod`
19
+ - sort App instance correctly
20
+ - modify yaml dict shared by yaml anchors
21
+ - make build_comment a `dump_only` field
22
+ - improve error message when env var IDF_PATH not set
23
+ - search sdkconfig path
24
+
5
25
  ## v2.0.0rc0 (2023-12-18)
6
26
 
7
27
  ### Refactor
@@ -106,6 +126,12 @@ All notable changes to this project will be documented in this file.
106
126
  - migrate `App` class to pydantic model
107
127
  - update dependencies and do code upgrade to python 3.7
108
128
 
129
+ ## v1.1.4 (2023-12-29)
130
+
131
+ ### Fix
132
+
133
+ - stop modifying yaml dict shared by yaml anchors
134
+
109
135
  ## v1.1.3 (2023-11-13)
110
136
 
111
137
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: idf-build-apps
3
- Version: 2.0.0rc0
3
+ Version: 2.0.0rc1
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
@@ -17,5 +17,6 @@ Welcome to idf-build-apps's documentation!
17
17
  config_file
18
18
  cli
19
19
  api/modules
20
+ migration/1.x_to_2.x
20
21
  CHANGELOG
21
22
  CONTRIBUTING
@@ -0,0 +1,135 @@
1
+ # Migration From 1.x to 2.x
2
+
3
+ There are a few breaking changes in 2.x. This document will help you migrate your code from 1.x to 2.x.
4
+
5
+ ## Python Version Support
6
+
7
+ idf-build-apps 1.x supports Python 2.7 and Python 3.4 or newer. idf-build-apps 2.x only supports Python 3.7 or newer.
8
+
9
+ ## Logging Related Changes
10
+
11
+ In 2.x, we're following the standard Python logging convention.
12
+
13
+ Before:
14
+
15
+ ```python
16
+ from idf_build_apps import LOGGER
17
+ ```
18
+
19
+ After:
20
+
21
+ ```python
22
+ import logging
23
+ idf_build_apps_logger = logging.getLogger('idf_build_apps')
24
+ ```
25
+
26
+ ## Normal Arguments to Keyword-only Arguments
27
+
28
+ In 2.x, we move some arguments from normal arguments to keyword-only arguments. This is to make the API more consistent and easier to use.
29
+
30
+ To understand the difference between these terms better, here's a quick summary:
31
+ - "positonal-only argument" means the argument is a positional-only argument. (python 3.8+ only)
32
+ - "keyword-only argument" means the argument is a keyword-only argument.
33
+ - "normal argument" means the argument is not a positional-only argument, nor a keyword-only argument.
34
+
35
+ For example, in the following function:
36
+
37
+ ```python
38
+ def foo(a, /, b, c, *, d=1, e=2, f=3):
39
+ pass
40
+ ```
41
+
42
+ - "a" is a positional-only argument.
43
+ - "b" and "c" are normal arguments.
44
+ - "d", "e", and "f" are keyword-only arguments.
45
+
46
+ The following calls are valid:
47
+
48
+ ```python
49
+ foo(1, 2, 3, d=4, e=5, f=6)
50
+ foo(1, 2, c=3, d=4, e=5, f=6)
51
+ foo(1, b=2, c=3, d=4, e=5, f=6)
52
+ ```
53
+
54
+ The following calls are invalid:
55
+
56
+ ```python
57
+ foo(1, 2, 3, 4, 5, 6)
58
+ foo(1, b=2, 3, d=4, e=5, f=6)
59
+ foo(a=1, b=2, c=3, d=4, e=5, f=6)
60
+ ```
61
+
62
+ ### `App.__init__()`
63
+
64
+ The `__init__` function of `App` class, and all its sub-classes, like `CMakeApp`, and `MakeApp`, now takes only `app_dir`, and `target` as normal arguments. All the rest of the arguments are keyword-only arguments.
65
+
66
+ Before:
67
+
68
+ ```python
69
+ app = App('foo', 'esp32', 'sdkconfig.ci', 'default')
70
+ ```
71
+
72
+ After:
73
+
74
+ ```python
75
+ app = App('foo', 'esp32', sdkconfig_path='sdkconfig.ci', config_name='default')
76
+ ```
77
+
78
+ or all in keyword-only arguments:
79
+
80
+ ```python
81
+ app = App(app_dir='foo', target='esp32', sdkconfig_path='sdkconfig.ci', config_name='default')
82
+ ```
83
+
84
+ ### `App.build()`
85
+
86
+ The `build` function of `App` class, and all its sub-classes, like `CMakeApp`, and `MakeApp`, now takes all arguments as keyword-only arguments.
87
+
88
+ ### `find_apps()`
89
+
90
+ The `find_apps` function now takes only `paths` and `target` as normal arguments. All the rest of the arguments are keyword-only arguments.
91
+
92
+ ### `build_apps()`
93
+
94
+ The `build_apps` function now takes only `apps` as normal argument. All the rest of the arguments are keyword-only arguments.
95
+
96
+ ## Function Signature Changes
97
+
98
+ In 2.x, we change the signature of some functions to make them more intuitive and self-explanatory.
99
+
100
+ ### `find_apps()`
101
+
102
+ - `build_log_path` is renamed to `build_log_filename`. The file will be generated under `build_dir` if specified.
103
+ - `size_json_path` is renamed to `size_json_filename`. The file will be generated under `build_dir` if specified.
104
+
105
+ ## CLI Changes
106
+
107
+ In 2.x, we change the separator for some options to better differentiate them from `None` and empty list.
108
+
109
+ - `--modified-components`
110
+ - `--modified-files`
111
+ - `--ignore-app-dependencies-filepatterns`
112
+
113
+ Before:
114
+
115
+ ```shell
116
+ idf-build-apps build -p foo -t esp32 --modified-components foo bar --modified-files foo bar --ignore-app-dependencies-filepatterns foo bar
117
+ ```
118
+
119
+ After:
120
+
121
+ ```shell
122
+ idf-build-apps build -p foo -t esp32 --modified-components 'foo;bar' --modified-files 'foo;bar' --ignore-app-dependencies-filepatterns 'foo;bar'
123
+ ```
124
+
125
+ passing `''` to specify it as `None`
126
+
127
+ ```shell
128
+ idf-build-apps build -p foo -t esp32 --modified-components ''
129
+ ```
130
+
131
+ or passing `';'` to specify it as an empty list
132
+
133
+ ```shell
134
+ idf-build-apps build -p foo -t esp32 --modified-components ';'
135
+ ```
@@ -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
  """
@@ -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.0.0rc0'
11
+ __version__ = '2.0.0rc1'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -28,6 +28,7 @@ from .log import (
28
28
  from .main import (
29
29
  build_apps,
30
30
  find_apps,
31
+ json_to_app,
31
32
  )
32
33
 
33
34
  __all__ = [
@@ -37,6 +38,6 @@ __all__ = [
37
38
  'MakeApp',
38
39
  'find_apps',
39
40
  'build_apps',
41
+ 'json_to_app',
40
42
  'setup_logging',
41
- 'SESSION_ARGS',
42
43
  ]
@@ -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 functools
@@ -8,11 +8,7 @@ import os
8
8
  import re
9
9
  import shutil
10
10
  import sys
11
- import tempfile
12
11
  import typing as t
13
- from copy import (
14
- deepcopy,
15
- )
16
12
  from datetime import (
17
13
  datetime,
18
14
  )
@@ -28,10 +24,9 @@ from pydantic import (
28
24
  computed_field,
29
25
  )
30
26
 
31
- from idf_build_apps import (
27
+ from . import (
32
28
  SESSION_ARGS,
33
29
  )
34
-
35
30
  from .build_apps_args import (
36
31
  BuildAppsArgs,
37
32
  )
@@ -132,12 +127,16 @@ class App(BaseModel):
132
127
 
133
128
  # build status related
134
129
  build_status: BuildStatus = BuildStatus.UNKNOWN
130
+ build_comment: t.Optional[str] = None
135
131
 
136
- _build_comment: t.Optional[str] = None
137
132
  _build_stage: t.Optional[BuildStage] = None
138
133
  _build_duration: float = 0
139
134
  _build_timestamp: t.Optional[datetime] = None
140
135
 
136
+ __EQ_IGNORE_FIELDS__ = [
137
+ 'build_comment',
138
+ ]
139
+
141
140
  def __init__(
142
141
  self,
143
142
  app_dir: str,
@@ -164,6 +163,7 @@ class App(BaseModel):
164
163
 
165
164
  self._build_log_filename = build_log_filename
166
165
  self._size_json_filename = size_json_filename
166
+ self._is_build_log_path_temp = not bool(build_log_filename)
167
167
 
168
168
  # pass all parameters to initialize hook method
169
169
  kwargs.update(
@@ -297,25 +297,18 @@ class App(BaseModel):
297
297
 
298
298
  return os.path.join(self.work_dir, self.build_dir)
299
299
 
300
- @property
301
- def build_comment(self) -> str:
302
- return self._build_comment or ''
303
-
304
- @build_comment.setter
305
- def build_comment(self, value: str) -> None:
306
- self._build_comment = value
307
-
308
300
  @computed_field # type: ignore
309
301
  @property
310
302
  def build_log_filename(self) -> t.Optional[str]:
311
303
  return self._expand(self._build_log_filename)
312
304
 
313
305
  @property
314
- def build_log_path(self) -> t.Optional[str]:
306
+ def build_log_path(self) -> str:
315
307
  if self.build_log_filename:
316
308
  return os.path.join(self.build_path, self.build_log_filename)
317
309
 
318
- return None
310
+ # use a temp file if build log path is not specified
311
+ return os.path.join(self.build_path, f'.temp.build.{hash(self)}.log')
319
312
 
320
313
  @computed_field # type: ignore
321
314
  @property
@@ -340,17 +333,21 @@ class App(BaseModel):
340
333
  real_sdkconfig_files: t.List[str] = []
341
334
  sdkconfig_files_defined_target: t.Optional[str] = None
342
335
 
336
+ # put the expanded variable files in a temporary directory
337
+ # will remove if the content is the same as the original one
343
338
  expanded_dir = os.path.join(self.work_dir, 'expanded_sdkconfig_files', os.path.basename(self.build_dir))
344
339
  if not os.path.isdir(expanded_dir):
345
340
  os.makedirs(expanded_dir)
346
341
 
347
342
  for f in self.sdkconfig_defaults_candidates + ([self.sdkconfig_path] if self.sdkconfig_path else []):
348
- if not os.path.isabs(f):
349
- f = os.path.join(self.work_dir, f)
350
-
343
+ # use filepath if abs/rel already point to itself
351
344
  if not os.path.isfile(f):
352
- self._logger.debug('sdkconfig file %s not exists, skipping...', f)
353
- continue
345
+ # find it in the app_dir
346
+ self._logger.debug('sdkconfig file %s not found, checking under app_dir...', f)
347
+ f = os.path.join(self.app_dir, f)
348
+ if not os.path.isfile(f):
349
+ self._logger.debug('sdkconfig file %s not found, skipping...', f)
350
+ continue
354
351
 
355
352
  expanded_fp = os.path.join(expanded_dir, os.path.basename(f))
356
353
  with open(f) as fr:
@@ -469,14 +466,7 @@ class App(BaseModel):
469
466
 
470
467
  return wrapper
471
468
 
472
- @record_build_duration # type: ignore
473
- def build(
474
- self,
475
- manifest_rootpath: t.Optional[str] = None,
476
- modified_components: t.Union[t.List[str], str, None] = None,
477
- modified_files: t.Union[t.List[str], str, None] = None,
478
- check_app_dependencies: bool = False,
479
- ) -> None:
469
+ def _pre_build(self) -> None:
480
470
  if self.dry_run:
481
471
  self._build_stage = BuildStage.DRY_RUN
482
472
  else:
@@ -518,26 +508,32 @@ class App(BaseModel):
518
508
  if not self.dry_run:
519
509
  os.unlink(sdkconfig_file)
520
510
 
521
- if self.build_log_path:
522
- self._logger.info('Writing build log to %s', self.build_log_path)
511
+ if os.path.isfile(self.build_log_path):
512
+ self._logger.debug('Removed existing build log file: %s', self.build_log_path)
513
+ if not self.dry_run:
514
+ os.unlink(self.build_log_path)
515
+ elif not self.dry_run:
516
+ os.makedirs(os.path.dirname(self.build_log_path), exist_ok=True)
517
+ self._logger.info('Writing build log to %s', self.build_log_path)
523
518
 
524
519
  if self.dry_run:
525
520
  self.build_status = BuildStatus.SKIPPED
526
521
  self.build_comment = 'dry run'
527
522
  return
528
523
 
529
- if self.build_log_path:
530
- logfile: t.IO[str] = open(self.build_log_path, 'w')
531
- keep_logfile = True
532
- else:
533
- # delete manually later, used for tracking debugging info
534
- logfile = tempfile.NamedTemporaryFile('w', delete=False)
535
- keep_logfile = False
524
+ @record_build_duration # type: ignore
525
+ def build(
526
+ self,
527
+ *,
528
+ manifest_rootpath: t.Optional[str] = None,
529
+ modified_components: t.Union[t.List[str], str, None] = None,
530
+ modified_files: t.Union[t.List[str], str, None] = None,
531
+ check_app_dependencies: bool = False,
532
+ ) -> None:
533
+ self._pre_build()
536
534
 
537
- self._build_stage = BuildStage.BUILD
538
535
  try:
539
536
  self._build(
540
- logfile=logfile,
541
537
  manifest_rootpath=manifest_rootpath,
542
538
  modified_components=to_list(modified_components),
543
539
  modified_files=to_list(modified_files),
@@ -546,12 +542,17 @@ class App(BaseModel):
546
542
  except BuildError as e:
547
543
  self.build_status = BuildStatus.FAILED
548
544
  self.build_comment = str(e)
549
- finally:
550
- logfile.close()
551
545
 
546
+ self._post_build()
547
+
548
+ def _post_build(self) -> None:
552
549
  self._build_stage = BuildStage.POST_BUILD
550
+
551
+ if not os.path.isfile(self.build_log_path):
552
+ return
553
+
553
554
  has_unignored_warning = False
554
- with open(logfile.name) as fr:
555
+ with open(self.build_log_path) as fr:
555
556
  lines = [line.rstrip() for line in fr.readlines() if line.rstrip()]
556
557
  for line in lines:
557
558
  is_error_or_warning, ignored = self.is_error_or_warning(line)
@@ -567,15 +568,14 @@ class App(BaseModel):
567
568
  self._logger.error(
568
569
  'Last %s lines from the build log "%s":',
569
570
  self.LOG_DEBUG_LINES,
570
- logfile.name,
571
+ self.build_log_path,
571
572
  )
572
573
  for line in lines[-self.LOG_DEBUG_LINES :]:
573
574
  self._logger.error('%s', line)
574
575
 
575
- # remove the log file if not specified and build succeeded
576
- if not keep_logfile and self.build_status == BuildStatus.SUCCESS:
577
- os.unlink(logfile.name)
578
- self._logger.debug('Removed temporary build log file: %s', logfile.name)
576
+ if self._is_build_log_path_temp and self.build_status == BuildStatus.SUCCESS:
577
+ os.unlink(self.build_log_path)
578
+ self._logger.debug('Removed success build temporary log file: %s', self.build_log_path)
579
579
 
580
580
  # Generate Size Files
581
581
  if self.build_status == BuildStatus.SUCCESS:
@@ -586,8 +586,7 @@ class App(BaseModel):
586
586
  exclude_list = []
587
587
  if self.size_json_path:
588
588
  exclude_list.append(os.path.basename(self.size_json_path))
589
- if self.build_log_path:
590
- exclude_list.append(os.path.basename(self.build_log_path))
589
+ exclude_list.append(os.path.basename(self.build_log_path))
591
590
 
592
591
  rmdir(
593
592
  self.build_path,
@@ -604,13 +603,13 @@ class App(BaseModel):
604
603
 
605
604
  def _build(
606
605
  self,
607
- logfile: t.IO[str],
606
+ *,
608
607
  manifest_rootpath: t.Optional[str] = None,
609
608
  modified_components: t.Optional[t.List[str]] = None,
610
609
  modified_files: t.Optional[t.List[str]] = None,
611
610
  check_app_dependencies: bool = False,
612
611
  ) -> None:
613
- pass
612
+ self._build_stage = BuildStage.BUILD
614
613
 
615
614
  def _write_size_json(self) -> None:
616
615
  if not self.size_json_path:
@@ -779,12 +778,19 @@ class MakeApp(App):
779
778
 
780
779
  def _build(
781
780
  self,
782
- logfile: t.IO[str],
781
+ *,
783
782
  manifest_rootpath: t.Optional[str] = None,
784
783
  modified_components: t.Optional[t.List[str]] = None,
785
784
  modified_files: t.Optional[t.List[str]] = None,
786
785
  check_app_dependencies: bool = False,
787
786
  ) -> None:
787
+ super()._build(
788
+ manifest_rootpath=manifest_rootpath,
789
+ modified_components=modified_components,
790
+ modified_files=modified_files,
791
+ check_app_dependencies=check_app_dependencies,
792
+ )
793
+
788
794
  # additional env variables
789
795
  additional_env_dict = {
790
796
  'IDF_TARGET': self.target,
@@ -801,8 +807,8 @@ class MakeApp(App):
801
807
  for cmd in commands:
802
808
  subprocess_run(
803
809
  cmd,
804
- log_terminal=False if self.build_log_path else True,
805
- log_fs=logfile,
810
+ log_terminal=self._is_build_log_path_temp,
811
+ log_fs=self.build_log_path,
806
812
  check=True,
807
813
  additional_env_dict=additional_env_dict,
808
814
  cwd=self.work_dir,
@@ -846,12 +852,19 @@ class CMakeApp(App):
846
852
 
847
853
  def _build(
848
854
  self,
849
- logfile: t.IO[str],
855
+ *,
850
856
  manifest_rootpath: t.Optional[str] = None,
851
857
  modified_components: t.Optional[t.List[str]] = None,
852
858
  modified_files: t.Optional[t.List[str]] = None,
853
859
  check_app_dependencies: bool = False,
854
860
  ) -> None:
861
+ super()._build(
862
+ manifest_rootpath=manifest_rootpath,
863
+ modified_components=modified_components,
864
+ modified_files=modified_files,
865
+ check_app_dependencies=check_app_dependencies,
866
+ )
867
+
855
868
  if not self._checked_should_build:
856
869
  self._check_should_build(
857
870
  manifest_rootpath=manifest_rootpath,
@@ -883,8 +896,8 @@ class CMakeApp(App):
883
896
  if modified_components is not None and check_app_dependencies and self.build_status == BuildStatus.UNKNOWN:
884
897
  subprocess_run(
885
898
  common_args + ['reconfigure'],
886
- log_terminal=False if self.build_log_path else True,
887
- log_fs=logfile,
899
+ log_terminal=self._is_build_log_path_temp,
900
+ log_fs=self.build_log_path,
888
901
  check=True,
889
902
  additional_env_dict=additional_env_dict,
890
903
  )
@@ -905,23 +918,22 @@ class CMakeApp(App):
905
918
  return
906
919
 
907
920
  # idf.py build
908
- build_args = deepcopy(common_args)
909
921
  if self.cmake_vars:
910
922
  for key, val in self.cmake_vars.items():
911
- build_args.append(f'-D{key}={val}')
923
+ common_args.append(f'-D{key}={val}')
912
924
  if 'TEST_EXCLUDE_COMPONENTS' in self.cmake_vars and 'TEST_COMPONENTS' not in self.cmake_vars:
913
- build_args.append('-DTESTS_ALL=1')
925
+ common_args.append('-DTESTS_ALL=1')
914
926
  if 'CONFIG_APP_BUILD_BOOTLOADER' in self.cmake_vars:
915
927
  # In case if secure_boot is enabled then for bootloader build need to add `bootloader` cmd
916
- build_args.append('bootloader')
917
- build_args.append('build')
928
+ common_args.append('bootloader')
929
+ common_args.append('build')
918
930
  if self.verbose:
919
- build_args.append('-v')
931
+ common_args.append('-v')
920
932
 
921
933
  subprocess_run(
922
- build_args,
923
- log_terminal=False if self.build_log_path else True,
924
- log_fs=logfile,
934
+ common_args,
935
+ log_terminal=self._is_build_log_path_temp,
936
+ log_fs=self.build_log_path,
925
937
  check=True,
926
938
  additional_env_dict=additional_env_dict,
927
939
  )
@@ -24,9 +24,9 @@ if _BUILDING_DOCS:
24
24
  if _BUILDING_DOCS:
25
25
  _idf_env = tempfile.gettempdir()
26
26
  else:
27
- _idf_env = os.getenv('IDF_PATH', '')
28
- if not os.path.isdir(_idf_env):
29
- raise ValueError(f'Invalid value for IDF_PATH: {_idf_env}')
27
+ _idf_env = os.getenv('IDF_PATH') or ''
28
+ if not _idf_env:
29
+ raise SystemExit('environment variable IDF_PATH must be set')
30
30
 
31
31
 
32
32
  IDF_PATH = os.path.abspath(_idf_env)
@@ -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 argparse
@@ -11,11 +11,17 @@ import sys
11
11
  import textwrap
12
12
  import typing as t
13
13
 
14
+ from pydantic import (
15
+ Field,
16
+ create_model,
17
+ )
18
+
14
19
  from . import (
15
20
  SESSION_ARGS,
16
21
  )
17
22
  from .app import (
18
23
  App,
24
+ AppDeserializer,
19
25
  CMakeApp,
20
26
  MakeApp,
21
27
  )
@@ -293,7 +299,6 @@ def build_apps(
293
299
  if f and os.path.isfile(f):
294
300
  os.remove(f)
295
301
  LOGGER.debug('Remove existing collect file %s', f)
296
- os.mknod(f)
297
302
 
298
303
  exit_code = 0
299
304
  for i, app in enumerate(apps):
@@ -742,7 +747,7 @@ def main():
742
747
  os.makedirs(os.path.dirname(os.path.realpath(args.output)), exist_ok=True)
743
748
  with open(args.output, 'w') as fw:
744
749
  for app in apps:
745
- fw.write(app.model_dump_json() + '\n')
750
+ fw.write(app.to_json() + '\n')
746
751
  else:
747
752
  for app in apps:
748
753
  print(app)
@@ -791,3 +796,37 @@ def main():
791
796
  print(f' {app}')
792
797
 
793
798
  sys.exit(res)
799
+
800
+
801
+ def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] = None) -> App:
802
+ """
803
+ Deserialize json string to App object
804
+
805
+ .. note::
806
+
807
+ You can pass extra_cls to support custom App class. A custom App class must be a subclass of App, and have a
808
+ different value of `build_system`. For example, a custom CMake app
809
+
810
+ >>> class CustomApp(CMakeApp):
811
+ >>> build_system: Literal['custom_cmake'] = 'custom_cmake'
812
+
813
+ Then you can pass the CustomApp class to the `extra_cls` argument
814
+
815
+ >>> json_str = CustomApp('.', 'esp32').to_json()
816
+ >>> json_to_app(json_str, extra_classes=[CustomApp])
817
+
818
+ :param json_str: json string
819
+ :param extra_classes: extra App class
820
+ :return: App object
821
+ """
822
+ types = [App, CMakeApp, MakeApp]
823
+ if extra_classes:
824
+ types.extend(extra_classes)
825
+
826
+ custom_deserializer = create_model(
827
+ '_CustomDeserializer',
828
+ app=(t.Union[tuple(types)], Field(discriminator='build_system')),
829
+ __base__=AppDeserializer,
830
+ )
831
+
832
+ return custom_deserializer.from_json(json_str)
@@ -58,15 +58,17 @@ class FolderRule:
58
58
  ) -> None:
59
59
  self.folder = os.path.abspath(folder)
60
60
 
61
- for group in [enable, disable, disable_test]:
62
- if group:
63
- for d in group:
64
- d['stmt'] = d['if'] # avoid keyword `if`
65
- del d['if']
66
-
67
- self.enable = [IfClause(**clause) for clause in enable] if enable else []
68
- self.disable = [IfClause(**clause) for clause in disable] if disable else []
69
- self.disable_test = [IfClause(**clause) for clause in disable_test] if disable_test else []
61
+ def _clause_to_if_clause(clause: t.Dict[str, t.Any]) -> IfClause:
62
+ _kwargs = {'stmt': clause['if']}
63
+ if 'temporary' in clause:
64
+ _kwargs['temporary'] = clause['temporary']
65
+ if 'reason' in clause:
66
+ _kwargs['reason'] = clause['reason']
67
+ return IfClause(**_kwargs)
68
+
69
+ self.enable = [_clause_to_if_clause(clause) for clause in enable] if enable else []
70
+ self.disable = [_clause_to_if_clause(clause) for clause in disable] if disable else []
71
+ self.disable_test = [_clause_to_if_clause(clause) for clause in disable_test] if disable_test else []
70
72
  self.depends_components = depends_components or []
71
73
  self.depends_filepatterns = depends_filepatterns or []
72
74
 
@@ -1,10 +1,13 @@
1
1
  # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ import logging
4
5
  import os
5
6
  import re
6
7
  import typing as t
7
8
 
9
+ LOGGER = logging.getLogger(__name__)
10
+
8
11
 
9
12
  class SessionArgs:
10
13
  workdir: str = os.getcwd()
@@ -35,27 +38,32 @@ class SessionArgs:
35
38
  self.override_sdkconfig_file_path = override_sdkconfig_merged_file
36
39
 
37
40
  def _get_override_sdkconfig_files_items(self, override_sdkconfig_files: t.Tuple[str]) -> t.Dict:
38
- dct = {}
41
+ d = {}
39
42
  for f in override_sdkconfig_files:
40
- if not os.path.isabs(f):
41
- f = os.path.join(self.workdir, f)
43
+ # use filepath if abs/rel already point to itself
42
44
  if not os.path.isfile(f):
43
- continue
45
+ # find it in the workdir
46
+ LOGGER.debug('override sdkconfig file %s not found, checking under app_dir...', f)
47
+ f = os.path.join(self.workdir, f)
48
+ if not os.path.isfile(f):
49
+ LOGGER.debug('override sdkconfig file %s not found, skipping...', f)
50
+ continue
51
+
44
52
  with open(f) as fr:
45
53
  for line in fr:
46
54
  m = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$").match(line)
47
55
  if not m:
48
56
  continue
49
- dct[m.group(1)] = m.group(2)
50
- return dct
57
+ d[m.group(1)] = m.group(2)
58
+ return d
51
59
 
52
60
  def _get_override_sdkconfig_items(self, override_sdkconfig_items: t.Tuple[str]) -> t.Dict:
53
- dct = {}
61
+ d = {}
54
62
  for line in override_sdkconfig_items:
55
63
  m = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$").match(line)
56
64
  if m:
57
- dct[m.group(1)] = m.group(2)
58
- return dct
65
+ d[m.group(1)] = m.group(2)
66
+ return d
59
67
 
60
68
  def _create_override_sdkconfig_merged_file(self, override_sdkconfig_merged_items) -> t.Optional[str]:
61
69
  if not override_sdkconfig_merged_items:
@@ -1,7 +1,8 @@
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 fnmatch
5
+ import functools
5
6
  import glob
6
7
  import logging
7
8
  import os
@@ -134,7 +135,7 @@ def find_first_match(pattern: str, path: str) -> t.Optional[str]:
134
135
  def subprocess_run(
135
136
  cmd: t.List[str],
136
137
  log_terminal: bool = True,
137
- log_fs: t.Optional[t.IO[str]] = None,
138
+ log_fs: t.Union[t.IO[str], str, None] = None,
138
139
  check: bool = False,
139
140
  additional_env_dict: t.Optional[t.Dict[str, str]] = None,
140
141
  **kwargs,
@@ -157,16 +158,26 @@ def subprocess_run(
157
158
  subprocess_env.update(additional_env_dict)
158
159
 
159
160
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=subprocess_env, **kwargs)
160
- if p.stdout:
161
- for line in p.stdout:
162
- if isinstance(line, bytes):
163
- line = line.decode('utf-8')
164
161
 
165
- if log_terminal:
166
- sys.stdout.write(line)
162
+ def _log_stdout(fs: t.Optional[t.IO[str]] = None):
163
+ if p.stdout:
164
+ for line in p.stdout:
165
+ if isinstance(line, bytes):
166
+ line = line.decode('utf-8')
167
+
168
+ if log_terminal:
169
+ sys.stdout.write(line)
170
+
171
+ if fs:
172
+ fs.write(line)
167
173
 
168
- if log_fs:
169
- log_fs.write(line)
174
+ if p.stdout:
175
+ if log_fs:
176
+ if isinstance(log_fs, str):
177
+ with open(log_fs, 'a') as fa:
178
+ _log_stdout(fa)
179
+ else:
180
+ _log_stdout(log_fs)
170
181
 
171
182
  returncode = p.wait()
172
183
  if check and returncode != 0:
@@ -305,25 +316,43 @@ def files_matches_patterns(
305
316
  return False
306
317
 
307
318
 
319
+ @functools.total_ordering
308
320
  class BaseModel(_BaseModel):
309
321
  """
310
322
  BaseModel that is hashable
311
323
  """
312
324
 
325
+ __EQ_IGNORE_FIELDS__: t.List[str] = []
326
+
313
327
  def __lt__(self, other: t.Any) -> bool:
314
328
  if isinstance(other, self.__class__):
315
329
  for k in self.model_dump():
316
- if getattr(self, k) != getattr(other, k):
317
- return getattr(self, k) < getattr(other, k)
318
- else:
330
+ if k in self.__EQ_IGNORE_FIELDS__:
319
331
  continue
320
332
 
333
+ self_attr = getattr(self, k, '') or ''
334
+ other_attr = getattr(other, k, '') or ''
335
+
336
+ if self_attr != other_attr:
337
+ return self_attr < other_attr
338
+
339
+ continue
340
+
341
+ return False
342
+
321
343
  return NotImplemented
322
344
 
323
345
  def __eq__(self, other: t.Any) -> bool:
324
346
  if isinstance(other, self.__class__):
325
347
  # we only care the public attributes
326
- return self.model_dump() == other.model_dump()
348
+ self_model_dump = self.model_dump()
349
+ other_model_dump = other.model_dump()
350
+
351
+ for _field in self.__EQ_IGNORE_FIELDS__:
352
+ self_model_dump.pop(_field, None)
353
+ other_model_dump.pop(_field, None)
354
+
355
+ return self_model_dump == other_model_dump
327
356
 
328
357
  return NotImplemented
329
358
 
@@ -60,7 +60,7 @@ idf-build-apps = "idf_build_apps:main.main"
60
60
 
61
61
  [tool.commitizen]
62
62
  name = "cz_conventional_commits"
63
- version = "2.0.0rc0"
63
+ version = "2.0.0rc1"
64
64
  tag_format = "v$version"
65
65
  version_files = [
66
66
  "idf_build_apps/__init__.py",
@@ -27,7 +27,7 @@ entry_points = \
27
27
  {'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
28
28
 
29
29
  setup(name='idf-build-apps',
30
- version='2.0.0rc0',
30
+ version='2.0.0rc1',
31
31
  description='Tools for building ESP-IDF related apps.',
32
32
  author=None,
33
33
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -0,0 +1,95 @@
1
+ # SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import sys
5
+
6
+ import pytest
7
+ from pydantic import (
8
+ ValidationError,
9
+ )
10
+
11
+ from idf_build_apps import (
12
+ AppDeserializer,
13
+ CMakeApp,
14
+ MakeApp,
15
+ )
16
+ from idf_build_apps.main import (
17
+ json_to_app,
18
+ )
19
+
20
+ if sys.version_info < (3, 8):
21
+ from typing_extensions import (
22
+ Literal,
23
+ )
24
+ else:
25
+ from typing import (
26
+ Literal,
27
+ )
28
+
29
+
30
+ def test_serialization():
31
+ a = CMakeApp('foo', 'bar')
32
+ a_s = a.to_json()
33
+
34
+ b = CMakeApp.model_validate_json(a_s)
35
+ assert a == b
36
+
37
+
38
+ def test_deserialization(tmp_path):
39
+ a = CMakeApp('foo', 'bar', size_json_filename='size.json')
40
+ b = MakeApp('foo', 'bar', build_log_filename='build.log')
41
+
42
+ assert a != b
43
+
44
+ with open(tmp_path / 'test.txt', 'w') as fw:
45
+ fw.write(a.to_json() + '\n')
46
+ fw.write(b.to_json() + '\n')
47
+
48
+ with open(tmp_path / 'test.txt') as fr:
49
+ a_s = AppDeserializer.from_json(fr.readline())
50
+ b_s = AppDeserializer.from_json(fr.readline())
51
+
52
+ assert a == a_s
53
+ assert b == b_s
54
+
55
+
56
+ def test_app_sorting():
57
+ a = CMakeApp('foo', 'esp32')
58
+ b = MakeApp('foo', 'esp32')
59
+
60
+ c = CMakeApp('foo', 'esp32', size_json_filename='size.json')
61
+ d = CMakeApp('foo', 'esp32s2')
62
+ e = CMakeApp('foo', 'esp32s2', build_comment='build_comment')
63
+
64
+ with pytest.raises(TypeError, match="'<' not supported between instances of 'CMakeApp' and 'MakeApp'"):
65
+ assert a < b
66
+
67
+ assert a < c < d
68
+ assert d > c > a
69
+
70
+ # __EQ_IGNORE_FIELDS__
71
+ assert d == e
72
+ assert not d < e
73
+ assert not d > e
74
+
75
+
76
+ def test_app_deserializer():
77
+ a = CMakeApp('foo', 'esp32')
78
+ b = MakeApp('foo', 'esp32')
79
+
80
+ class CustomApp(CMakeApp):
81
+ build_system: Literal['custom'] = 'custom' # type: ignore
82
+
83
+ c = CustomApp('foo', 'esp32')
84
+
85
+ assert json_to_app(a.to_json()) == a
86
+ assert json_to_app(b.to_json()) == b
87
+
88
+ with pytest.raises(
89
+ ValidationError,
90
+ match="Input tag 'custom' found using 'build_system' does not match "
91
+ "any of the expected tags: 'unknown', 'cmake', 'make'",
92
+ ):
93
+ assert json_to_app(c.to_json()) == c
94
+
95
+ assert json_to_app(c.to_json(), extra_classes=[CustomApp]) == c
@@ -21,7 +21,7 @@ from idf_build_apps.utils import (
21
21
  )
22
22
 
23
23
 
24
- def test_manifest(tmpdir, recwarn):
24
+ def test_manifest(tmpdir, recwarn, monkeypatch):
25
25
  yaml_file = tmpdir / 'test.yml'
26
26
  yaml_file.write_text(
27
27
  """
@@ -54,11 +54,38 @@ test2:
54
54
  assert manifest.enable_build_targets('test2') == ['linux']
55
55
  assert manifest.enable_test_targets('test2') == ['linux']
56
56
 
57
- Manifest.CHECK_MANIFEST_RULES = True
57
+ monkeypatch.setattr(idf_build_apps.manifest.manifest.Manifest, 'CHECK_MANIFEST_RULES', True)
58
58
  with pytest.raises(InvalidManifest, match=msg_fmt.format(os.path.join(tmpdir, 'test1'), yaml_file)):
59
59
  Manifest.from_file(yaml_file)
60
60
 
61
61
 
62
+ def test_manifest_with_anchor(tmpdir, monkeypatch):
63
+ yaml_file = tmpdir / 'test.yml'
64
+ yaml_file.write_text(
65
+ """
66
+ .base: &base
67
+ depends_components:
68
+ - a
69
+
70
+ foo: &foo
71
+ <<: *base
72
+ disable:
73
+ - if: IDF_TARGET == "esp32"
74
+
75
+ bar:
76
+ <<: *foo
77
+ """,
78
+ encoding='utf8',
79
+ )
80
+
81
+ monkeypatch.setattr(idf_build_apps.manifest.manifest.FolderRule, 'DEFAULT_BUILD_TARGETS', ['esp32'])
82
+
83
+ with pytest.warns(UserWarning, match='Folder ".+" does not exist. Please check your manifest file'):
84
+ manifest = Manifest.from_file(yaml_file)
85
+
86
+ assert manifest.enable_build_targets('bar') == []
87
+
88
+
62
89
  class TestIfParser:
63
90
  def test_idf_version(self, monkeypatch):
64
91
  monkeypatch.setattr(idf_build_apps.manifest.if_parser, 'IDF_VERSION', Version('5.9.0'))
@@ -1,34 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
2
- # SPDX-License-Identifier: Apache-2.0
3
-
4
- from idf_build_apps import (
5
- AppDeserializer,
6
- CMakeApp,
7
- MakeApp,
8
- )
9
-
10
-
11
- def test_serialization():
12
- a = CMakeApp('foo', 'bar')
13
- a_s = a.to_json()
14
-
15
- b = CMakeApp.model_validate_json(a_s)
16
- assert a == b
17
-
18
-
19
- def test_deserialization(tmp_path):
20
- a = CMakeApp('foo', 'bar', size_json_filename='size.json')
21
- b = MakeApp('foo', 'bar', build_log_filename='build.log')
22
-
23
- assert a != b
24
-
25
- with open(tmp_path / 'test.txt', 'w') as fw:
26
- fw.write(a.to_json() + '\n')
27
- fw.write(b.to_json() + '\n')
28
-
29
- with open(tmp_path / 'test.txt') as fr:
30
- a_s = AppDeserializer.from_json(fr.readline())
31
- b_s = AppDeserializer.from_json(fr.readline())
32
-
33
- assert a == a_s
34
- assert b == b_s