idf-build-apps 2.8.1__tar.gz → 2.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.pre-commit-config.yaml +2 -2
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/CHANGELOG.md +13 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/PKG-INFO +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/conf_common.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/conf.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/explanations/config_rules.rst +6 -0
- idf_build_apps-2.10.0/docs/en/guides/custom_app.md +80 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/index.rst +2 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/references/config_file.rst +12 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/__init__.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/__main__.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/args.py +35 -8
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/autocompletions.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/finder.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/junit/__init__.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/junit/utils.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/main.py +14 -2
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/manifest/manifest.py +8 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/session_args.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/yaml/__init__.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/yaml/parser.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/pyproject.toml +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/setup.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_args.py +10 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_build.py +103 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_cmd.py +1 -1
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_finder.py +0 -3
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_manifest.py +22 -0
- idf_build_apps-2.8.1/.github/workflows/check-pre-commit.yml +0 -18
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.editorconfig +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.git-blame-ignore-revs +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.gitattributes +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.github/dependabot.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.github/workflows/publish-pypi.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.github/workflows/sync-jira.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.github/workflows/test-build-docs.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.github/workflows/test-build-idf-apps.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.gitignore +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/.readthedocs.yml +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/CONTRIBUTING.md +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/LICENSE +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/README.md +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_apidoc_templates/module.rst_t +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_apidoc_templates/package.rst_t +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_apidoc_templates/toc.rst_t +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_static/espressif-logo.svg +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_static/theme_overrides.css +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/_templates/layout.html +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/Makefile +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/explanations/build.rst +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/explanations/dependency_driven_build.rst +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/explanations/find.rst +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/guides/1.x_to_2.x.md +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/others/CHANGELOG.md +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/others/CONTRIBUTING.md +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/references/cli.rst +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/references/manifest.rst +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/app.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/constants.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/junit/report.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/log.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/manifest/__init__.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/manifest/soc_header.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/py.typed +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/utils.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/vendors/__init__.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/idf_build_apps/vendors/pydantic_sources.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/license_header.txt +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/conftest.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_app.py +0 -0
- {idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/tests/test_utils.py +0 -0
|
@@ -16,8 +16,8 @@ repos:
|
|
|
16
16
|
- license_header.txt # defaults to: LICENSE.txt
|
|
17
17
|
- --use-current-year
|
|
18
18
|
exclude: 'idf_build_apps/vendors/'
|
|
19
|
-
- repo: https://github.com/
|
|
20
|
-
rev: 'v0.
|
|
19
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
20
|
+
rev: 'v0.11.6'
|
|
21
21
|
hooks:
|
|
22
22
|
- id: ruff
|
|
23
23
|
args: ['--fix']
|
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.10.0 (2025-04-22)
|
|
6
|
+
|
|
7
|
+
### Feat
|
|
8
|
+
|
|
9
|
+
- support custom class load from CLI
|
|
10
|
+
|
|
11
|
+
## v2.9.0 (2025-04-16)
|
|
12
|
+
|
|
13
|
+
### Feat
|
|
14
|
+
|
|
15
|
+
- record manifest_path that introduced the folder rule
|
|
16
|
+
- support env var expansion in some fields
|
|
17
|
+
|
|
5
18
|
## v2.8.1 (2025-03-04)
|
|
6
19
|
|
|
7
20
|
### Fix
|
|
@@ -146,6 +146,12 @@ When building the project for the ESP32 target, `sdkconfig files`_ with the ``.e
|
|
|
146
146
|
B --> C --> D -- "populates sdkconfig file, then build" --> F
|
|
147
147
|
B --> E -- "populates sdkconfig file, then build" --> G
|
|
148
148
|
|
|
149
|
+
.. warning::
|
|
150
|
+
|
|
151
|
+
Standalone target-specific sdkconfig files are ignored. To make the target-specific sdkconfig files effective, the original sdkconfig file, (without the target name suffix) must be present.
|
|
152
|
+
|
|
153
|
+
For example, ``sdkconfig.ci.foo.esp32`` will only be taken into account while building with target ``esp32`` if ``sdkconfig.ci.foo`` is also present.
|
|
154
|
+
|
|
149
155
|
Override In CLI
|
|
150
156
|
===============
|
|
151
157
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Custom App Classes
|
|
2
|
+
|
|
3
|
+
`idf-build-apps` allows you to create custom app classes by subclassing the base `App` class. This is useful when you need to implement custom build logic or handle special project types.
|
|
4
|
+
|
|
5
|
+
## Creating a Custom App Class
|
|
6
|
+
|
|
7
|
+
Here's an example of creating a custom app class:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from idf_build_apps import App
|
|
11
|
+
from idf_build_apps.constants import BuildStatus
|
|
12
|
+
import os
|
|
13
|
+
from typing import Literal # Python 3.8+ only. from typing_extensions import Literal for earlier versions
|
|
14
|
+
|
|
15
|
+
class CustomApp(App):
|
|
16
|
+
build_system: Literal['custom'] = 'custom' # Must be unique to identify your custom app type
|
|
17
|
+
|
|
18
|
+
def build(self, *args, **kwargs):
|
|
19
|
+
# Implement your custom build logic here
|
|
20
|
+
os.makedirs(self.build_path, exist_ok=True)
|
|
21
|
+
with open(os.path.join(self.build_path, 'dummy.txt'), 'w') as f:
|
|
22
|
+
f.write('Custom build successful')
|
|
23
|
+
self.build_status = BuildStatus.SUCCESS
|
|
24
|
+
print('Custom build successful')
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def is_app(cls, path: str) -> bool:
|
|
28
|
+
# Implement logic to determine if a path contains your custom app type
|
|
29
|
+
return True
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Using Custom App Classes
|
|
33
|
+
|
|
34
|
+
You can use custom app classes in two ways:
|
|
35
|
+
|
|
36
|
+
### Via CLI
|
|
37
|
+
|
|
38
|
+
```shell
|
|
39
|
+
idf-build-apps build -p /path/to/app --target esp32 --build-system custom:CustomApp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Where `custom:CustomApp` is in the format `module:class`. The module must be in your Python path.
|
|
43
|
+
|
|
44
|
+
### Via Python API
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from idf_build_apps import find_apps
|
|
48
|
+
|
|
49
|
+
apps = find_apps(
|
|
50
|
+
paths=['/path/to/app'],
|
|
51
|
+
target='esp32',
|
|
52
|
+
build_system=CustomApp,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
for app in apps:
|
|
56
|
+
app.build()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Important Notes
|
|
60
|
+
|
|
61
|
+
- Your custom app class must subclass `App`
|
|
62
|
+
- The `build_system` attribute must be unique to identify your app type
|
|
63
|
+
- You must implement the `is_app()` class method to identify your app type
|
|
64
|
+
- For JSON serialization support, you need to pass your custom class to `json_to_app()` when deserializing
|
|
65
|
+
|
|
66
|
+
## Example: JSON Serialization
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from idf_build_apps import json_to_app
|
|
70
|
+
|
|
71
|
+
# Serialize
|
|
72
|
+
json_str = custom_app.to_json()
|
|
73
|
+
|
|
74
|
+
# Deserialize
|
|
75
|
+
deserialized_app = json_to_app(json_str, extra_classes=[CustomApp])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Available Methods and Properties
|
|
79
|
+
|
|
80
|
+
Please refer to the [API reference of the class `App`](https://docs.espressif.com/projects/idf-build-apps/en/latest/references/api/idf_build_apps.html#idf_build_apps.app.App)
|
|
@@ -97,6 +97,18 @@ This indicates that in the configuration file, you should specify it with the na
|
|
|
97
97
|
"bar",
|
|
98
98
|
]
|
|
99
99
|
|
|
100
|
+
******************************
|
|
101
|
+
Expand Environment Variables
|
|
102
|
+
******************************
|
|
103
|
+
|
|
104
|
+
Some configuration options support environment variables. You can use environment variables in the configuration file by using the syntax ``${VAR_NAME}`` or ``$VAR_NAME``. Undeclared environment variables will be replaced with an empty string. For exmaple:
|
|
105
|
+
|
|
106
|
+
.. code:: toml
|
|
107
|
+
|
|
108
|
+
collect_app_info_filename = "app_info_${CI_JOB_NAME_SLUG}"
|
|
109
|
+
|
|
110
|
+
when the environment variable ``CI_JOB_NAME_SLUG`` is set to ``my_job``, the ``collect_app_info_filename`` will be expanded to ``app_info_my_job``. When the environment variable is not set, the value will be ``app_info_``.
|
|
111
|
+
|
|
100
112
|
*************************
|
|
101
113
|
CLI Argument Precedence
|
|
102
114
|
*************************
|
|
@@ -14,6 +14,7 @@ from copy import deepcopy
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
from io import TextIOWrapper
|
|
16
16
|
from pathlib import Path
|
|
17
|
+
from string import Template
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
19
20
|
from pydantic import AliasChoices, Field, computed_field, field_validator
|
|
@@ -112,6 +113,29 @@ def get_meta(f: FieldInfo) -> t.Optional[FieldMetadata]:
|
|
|
112
113
|
return None
|
|
113
114
|
|
|
114
115
|
|
|
116
|
+
def expand_vars(v: t.Optional[str]) -> t.Optional[str]:
|
|
117
|
+
"""
|
|
118
|
+
Expand environment variables in the string. If the variable is not found, use an empty string.
|
|
119
|
+
|
|
120
|
+
:param v: string to expand
|
|
121
|
+
:return: expanded string or None if the input is None
|
|
122
|
+
"""
|
|
123
|
+
if v is None:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
unknown_vars: t.Dict[str, str] = dict()
|
|
127
|
+
while True:
|
|
128
|
+
try:
|
|
129
|
+
v = Template(v).substitute(os.environ, **unknown_vars)
|
|
130
|
+
except KeyError as e:
|
|
131
|
+
LOGGER.debug('Environment variable %s not found. use empty string', e)
|
|
132
|
+
unknown_vars[e.args[0]] = ''
|
|
133
|
+
else:
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
return v
|
|
137
|
+
|
|
138
|
+
|
|
115
139
|
class BaseArguments(BaseSettings):
|
|
116
140
|
"""Base settings class for all settings classes"""
|
|
117
141
|
|
|
@@ -154,7 +178,7 @@ class BaseArguments(BaseSettings):
|
|
|
154
178
|
if method == ValidateMethod.TO_LIST:
|
|
155
179
|
v = to_list(v)
|
|
156
180
|
elif method == ValidateMethod.EXPAND_VARS:
|
|
157
|
-
v =
|
|
181
|
+
v = expand_vars(v)
|
|
158
182
|
else:
|
|
159
183
|
raise NotImplementedError(f'Unknown validate method: {method}')
|
|
160
184
|
|
|
@@ -390,10 +414,9 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
|
|
|
390
414
|
default='all', # type: ignore
|
|
391
415
|
)
|
|
392
416
|
build_system: t.Union[str, t.Type[App]] = field(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
description='Filter the apps by build system. By default set to "cmake"',
|
|
417
|
+
None,
|
|
418
|
+
description='Filter the apps by build system. By default set to "cmake". '
|
|
419
|
+
'Can be either "cmake", "make" or a custom App class path in format "module:class"',
|
|
397
420
|
default='cmake', # type: ignore
|
|
398
421
|
)
|
|
399
422
|
recursive: bool = field(
|
|
@@ -689,9 +712,10 @@ class BuildArguments(FindBuildArguments):
|
|
|
689
712
|
FieldMetadata(
|
|
690
713
|
deprecates={'collect_size_info': {}},
|
|
691
714
|
hidden=True,
|
|
715
|
+
validate_method=[ValidateMethod.EXPAND_VARS],
|
|
692
716
|
),
|
|
693
717
|
description='Record size json filepath of the built apps to the specified file. '
|
|
694
|
-
'Each line is a json string. Can expand placeholders @p',
|
|
718
|
+
'Each line is a json string. Can expand placeholders @p. Support environment variables.',
|
|
695
719
|
validation_alias=AliasChoices('collect_size_info_filename', 'collect_size_info'),
|
|
696
720
|
default=None, # type: ignore
|
|
697
721
|
exclude=True, # computed field is used
|
|
@@ -700,9 +724,10 @@ class BuildArguments(FindBuildArguments):
|
|
|
700
724
|
FieldMetadata(
|
|
701
725
|
deprecates={'collect_app_info': {}},
|
|
702
726
|
hidden=True,
|
|
727
|
+
validate_method=[ValidateMethod.EXPAND_VARS],
|
|
703
728
|
),
|
|
704
729
|
description='Record serialized app model of the built apps to the specified file. '
|
|
705
|
-
'Each line is a json string. Can expand placeholders @p',
|
|
730
|
+
'Each line is a json string. Can expand placeholders @p. Support environment variables.',
|
|
706
731
|
validation_alias=AliasChoices('collect_app_info_filename', 'collect_app_info'),
|
|
707
732
|
default=None, # type: ignore
|
|
708
733
|
exclude=True, # computed field is used
|
|
@@ -711,8 +736,10 @@ class BuildArguments(FindBuildArguments):
|
|
|
711
736
|
FieldMetadata(
|
|
712
737
|
deprecates={'junitxml': {}},
|
|
713
738
|
hidden=True,
|
|
739
|
+
validate_method=[ValidateMethod.EXPAND_VARS],
|
|
714
740
|
),
|
|
715
|
-
description='Path to the junitxml file to record the build results. Can expand placeholder @p'
|
|
741
|
+
description='Path to the junitxml file to record the build results. Can expand placeholder @p. '
|
|
742
|
+
'Support environment variables.',
|
|
716
743
|
validation_alias=AliasChoices('junitxml_filename', 'junitxml'),
|
|
717
744
|
default=None, # type: ignore
|
|
718
745
|
exclude=True, # computed field is used
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
|
+
import importlib
|
|
7
8
|
import json
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
@@ -90,13 +91,24 @@ def find_apps(
|
|
|
90
91
|
|
|
91
92
|
app_cls: t.Type[App]
|
|
92
93
|
if isinstance(find_arguments.build_system, str):
|
|
93
|
-
# backwards compatible
|
|
94
94
|
if find_arguments.build_system == 'cmake':
|
|
95
95
|
app_cls = CMakeApp
|
|
96
96
|
elif find_arguments.build_system == 'make':
|
|
97
97
|
app_cls = MakeApp
|
|
98
|
+
elif ':' in find_arguments.build_system:
|
|
99
|
+
# Custom App class in format "module:class"
|
|
100
|
+
try:
|
|
101
|
+
module_path, class_name = find_arguments.build_system.split(':')
|
|
102
|
+
module = importlib.import_module(module_path)
|
|
103
|
+
app_cls = getattr(module, class_name)
|
|
104
|
+
if not issubclass(app_cls, App):
|
|
105
|
+
raise ValueError(f'Class {class_name} must be a subclass of App')
|
|
106
|
+
except (ValueError, ImportError, AttributeError) as e:
|
|
107
|
+
raise ValueError(f'Invalid custom App class path: {find_arguments.build_system}. Error: {e!s}')
|
|
98
108
|
else:
|
|
99
|
-
raise ValueError(
|
|
109
|
+
raise ValueError(
|
|
110
|
+
'build_system must be either "cmake", "make" or a custom App class path in format "module:class"'
|
|
111
|
+
)
|
|
100
112
|
else:
|
|
101
113
|
app_cls = find_arguments.build_system
|
|
102
114
|
|
|
@@ -89,7 +89,10 @@ class FolderRule:
|
|
|
89
89
|
disable_test: t.Optional[t.List[t.Dict[str, t.Any]]] = None,
|
|
90
90
|
depends_components: t.Optional[t.List[t.Union[str, t.Dict[str, t.Any]]]] = None,
|
|
91
91
|
depends_filepatterns: t.Optional[t.List[t.Union[str, t.Dict[str, t.Any]]]] = None,
|
|
92
|
+
manifest_filepath: t.Optional[str] = None,
|
|
92
93
|
) -> None:
|
|
94
|
+
self._manifest_filepath = manifest_filepath
|
|
95
|
+
|
|
93
96
|
self.folder = os.path.abspath(folder)
|
|
94
97
|
|
|
95
98
|
def _clause_to_if_clause(clause: t.Dict[str, t.Any]) -> IfClause:
|
|
@@ -168,6 +171,10 @@ class FolderRule:
|
|
|
168
171
|
def __repr__(self) -> str:
|
|
169
172
|
return f'FolderRule({self.folder})'
|
|
170
173
|
|
|
174
|
+
@property
|
|
175
|
+
def by_manifest_file(self) -> t.Optional[str]:
|
|
176
|
+
return self._manifest_filepath
|
|
177
|
+
|
|
171
178
|
def _enable_build(self, target: str, config_name: str) -> bool:
|
|
172
179
|
if self.enable:
|
|
173
180
|
res = False
|
|
@@ -309,7 +316,7 @@ class Manifest:
|
|
|
309
316
|
LOGGER.warning(msg)
|
|
310
317
|
|
|
311
318
|
try:
|
|
312
|
-
rules.append(FolderRule(folder, **folder_rule if folder_rule else {}))
|
|
319
|
+
rules.append(FolderRule(folder, **folder_rule if folder_rule else {}, manifest_filepath=str(path)))
|
|
313
320
|
except InvalidIfClause as e:
|
|
314
321
|
raise InvalidManifest(f'Invalid manifest file {path}: {e}')
|
|
315
322
|
|
|
@@ -38,7 +38,7 @@ entry_points = \
|
|
|
38
38
|
{'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
|
|
39
39
|
|
|
40
40
|
setup(name='idf-build-apps',
|
|
41
|
-
version='2.
|
|
41
|
+
version='2.10.0',
|
|
42
42
|
description='Tools for building ESP-IDF related apps.',
|
|
43
43
|
author=None,
|
|
44
44
|
author_email='Fu Hanxi <fuhanxi@espressif.com>',
|
|
@@ -15,6 +15,7 @@ from idf_build_apps.args import (
|
|
|
15
15
|
DependencyDrivenBuildArguments,
|
|
16
16
|
FindArguments,
|
|
17
17
|
FindBuildArguments,
|
|
18
|
+
expand_vars,
|
|
18
19
|
)
|
|
19
20
|
from idf_build_apps.constants import IDF_BUILD_APPS_TOML_FN, PREVIEW_TARGETS, SUPPORTED_TARGETS
|
|
20
21
|
from idf_build_apps.main import main
|
|
@@ -408,3 +409,12 @@ dry_run = false
|
|
|
408
409
|
assert test_suite.attrib['errors'] == '0'
|
|
409
410
|
assert test_suite.attrib['skipped'] == '1'
|
|
410
411
|
assert test_suite.findall('testcase')[0].attrib['name'] == 'bar/build'
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def test_expand_vars(monkeypatch):
|
|
415
|
+
assert expand_vars('Value is $TEST_VAR') == 'Value is '
|
|
416
|
+
monkeypatch.setenv('TEST_VAR', 'test_value')
|
|
417
|
+
assert expand_vars('Value is $TEST_VAR') == 'Value is test_value'
|
|
418
|
+
assert expand_vars('Value is $TEST_VAR and $NON_EXISTING_VAR') == 'Value is test_value and '
|
|
419
|
+
assert expand_vars('No variables here') == 'No variables here'
|
|
420
|
+
assert expand_vars('') == ''
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
5
|
import shutil
|
|
6
|
+
import subprocess
|
|
6
7
|
from copy import (
|
|
7
8
|
deepcopy,
|
|
8
9
|
)
|
|
10
|
+
from pathlib import Path
|
|
9
11
|
from xml.etree import (
|
|
10
12
|
ElementTree,
|
|
11
13
|
)
|
|
@@ -20,6 +22,7 @@ from idf_build_apps import (
|
|
|
20
22
|
find_apps,
|
|
21
23
|
)
|
|
22
24
|
from idf_build_apps.app import (
|
|
25
|
+
App,
|
|
23
26
|
CMakeApp,
|
|
24
27
|
)
|
|
25
28
|
from idf_build_apps.args import BuildArguments
|
|
@@ -27,6 +30,7 @@ from idf_build_apps.constants import (
|
|
|
27
30
|
IDF_PATH,
|
|
28
31
|
BuildStatus,
|
|
29
32
|
)
|
|
33
|
+
from idf_build_apps.utils import Literal
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
@pytest.mark.skipif(not shutil.which('idf.py'), reason='idf.py not found')
|
|
@@ -205,6 +209,105 @@ class TestBuild:
|
|
|
205
209
|
assert test_suite.findall('testcase')[0].attrib['name'] == 'foo/bar/build'
|
|
206
210
|
|
|
207
211
|
|
|
212
|
+
class CustomClassApp(App):
|
|
213
|
+
build_system: Literal['custom_class'] = 'custom_class' # type: ignore
|
|
214
|
+
|
|
215
|
+
def build(self, *args, **kwargs):
|
|
216
|
+
# For testing, we'll just create a dummy build directory
|
|
217
|
+
if not self.dry_run:
|
|
218
|
+
os.makedirs(self.build_path, exist_ok=True)
|
|
219
|
+
with open(os.path.join(self.build_path, 'dummy.txt'), 'w') as f:
|
|
220
|
+
f.write('Custom build successful')
|
|
221
|
+
self.build_status = BuildStatus.SUCCESS
|
|
222
|
+
print('Custom build successful')
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def is_app(cls, path: str) -> bool: # noqa: ARG003
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.mark.skipif(not shutil.which('idf.py'), reason='idf.py not found')
|
|
230
|
+
class TestBuildWithCustomApp:
|
|
231
|
+
custom_app_code = """
|
|
232
|
+
from idf_build_apps import App
|
|
233
|
+
import os
|
|
234
|
+
from idf_build_apps.constants import BuildStatus
|
|
235
|
+
from idf_build_apps.utils import Literal
|
|
236
|
+
|
|
237
|
+
class CustomApp(App):
|
|
238
|
+
build_system: Literal['custom'] = 'custom'
|
|
239
|
+
|
|
240
|
+
def build(self, *args, **kwargs):
|
|
241
|
+
# For testing, we'll just create a dummy build directory
|
|
242
|
+
if not self.dry_run:
|
|
243
|
+
os.makedirs(self.build_path, exist_ok=True)
|
|
244
|
+
with open(os.path.join(self.build_path, 'dummy.txt'), 'w') as f:
|
|
245
|
+
f.write('Custom build successful')
|
|
246
|
+
self.build_status = BuildStatus.SUCCESS
|
|
247
|
+
print('Custom build successful')
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def is_app(cls, path: str) -> bool:
|
|
251
|
+
return True
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
@pytest.fixture(autouse=True)
|
|
255
|
+
def _setup(self, tmp_path: Path, monkeypatch):
|
|
256
|
+
os.chdir(tmp_path)
|
|
257
|
+
|
|
258
|
+
test_app = tmp_path / 'test_app'
|
|
259
|
+
|
|
260
|
+
test_app.mkdir()
|
|
261
|
+
(test_app / 'main' / 'main.c').parent.mkdir(parents=True)
|
|
262
|
+
(test_app / 'main' / 'main.c').write_text('void app_main() {}')
|
|
263
|
+
|
|
264
|
+
# Create a custom app module
|
|
265
|
+
custom_module = tmp_path / 'custom.py'
|
|
266
|
+
custom_module.write_text(self.custom_app_code)
|
|
267
|
+
|
|
268
|
+
monkeypatch.setenv('PYTHONPATH', os.getenv('PYTHONPATH', '') + os.pathsep + str(tmp_path))
|
|
269
|
+
|
|
270
|
+
return test_app
|
|
271
|
+
|
|
272
|
+
def test_custom_app_cli(self, tmp_path):
|
|
273
|
+
subprocess.run(
|
|
274
|
+
[
|
|
275
|
+
'idf-build-apps',
|
|
276
|
+
'build',
|
|
277
|
+
'-p',
|
|
278
|
+
'test_app',
|
|
279
|
+
'--target',
|
|
280
|
+
'esp32',
|
|
281
|
+
'--build-system',
|
|
282
|
+
'custom:CustomApp',
|
|
283
|
+
],
|
|
284
|
+
check=True,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
assert (tmp_path / 'test_app' / 'build' / 'dummy.txt').exists()
|
|
288
|
+
assert (tmp_path / 'test_app' / 'build' / 'dummy.txt').read_text() == 'Custom build successful'
|
|
289
|
+
|
|
290
|
+
def test_custom_app_function(self, tmp_path):
|
|
291
|
+
# Import the custom app class
|
|
292
|
+
# Find and build the app using the imported CustomApp class
|
|
293
|
+
apps = find_apps(
|
|
294
|
+
paths=['test_app'],
|
|
295
|
+
target='esp32',
|
|
296
|
+
build_system=CustomClassApp,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
assert len(apps) == 1
|
|
300
|
+
app = apps[0]
|
|
301
|
+
assert isinstance(app, CustomClassApp)
|
|
302
|
+
assert app.build_system == 'custom_class'
|
|
303
|
+
|
|
304
|
+
# Build the app
|
|
305
|
+
app.build()
|
|
306
|
+
assert app.build_status == BuildStatus.SUCCESS
|
|
307
|
+
assert (tmp_path / 'test_app' / 'build' / 'dummy.txt').exists()
|
|
308
|
+
assert (tmp_path / 'test_app' / 'build' / 'dummy.txt').read_text() == 'Custom build successful'
|
|
309
|
+
|
|
310
|
+
|
|
208
311
|
def test_build_apps_collect_files_when_no_apps_built(tmp_path):
|
|
209
312
|
os.chdir(tmp_path)
|
|
210
313
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import logging
|
|
5
4
|
import os
|
|
6
5
|
import tempfile
|
|
7
6
|
from pathlib import (
|
|
@@ -443,7 +442,6 @@ class TestFindWithSdkconfigFiles:
|
|
|
443
442
|
if not os.path.isfile(default_sdkconfig_path):
|
|
444
443
|
with open(default_sdkconfig_path, 'w') as fw:
|
|
445
444
|
fw.write('CONFIG_IDF_TARGET="linux"')
|
|
446
|
-
logging.info('Created temp %s %s', DEFAULT_SDKCONFIG, default_sdkconfig_path)
|
|
447
445
|
_app = app
|
|
448
446
|
_default_sdkconfig_path = default_sdkconfig_path
|
|
449
447
|
break
|
|
@@ -456,7 +454,6 @@ class TestFindWithSdkconfigFiles:
|
|
|
456
454
|
finally:
|
|
457
455
|
try:
|
|
458
456
|
os.remove(_default_sdkconfig_path)
|
|
459
|
-
logging.info('Removed temp %s %s', DEFAULT_SDKCONFIG, _default_sdkconfig_path)
|
|
460
457
|
except: # noqa
|
|
461
458
|
pass
|
|
462
459
|
|
|
@@ -535,6 +535,28 @@ baz:
|
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
|
|
538
|
+
def test_folder_rule_introduced_by(tmp_path):
|
|
539
|
+
yaml_file = tmp_path / 'test.yml'
|
|
540
|
+
yaml_file.write_text(
|
|
541
|
+
"""
|
|
542
|
+
foo:
|
|
543
|
+
enable:
|
|
544
|
+
- if: IDF_TARGET == "esp32"
|
|
545
|
+
- if: IDF_TARGET == "esp32c3"
|
|
546
|
+
bar:
|
|
547
|
+
enable:
|
|
548
|
+
- if: IDF_TARGET == "esp32"
|
|
549
|
+
baz:
|
|
550
|
+
enable:
|
|
551
|
+
- if: IDF_TARGET == "esp32"
|
|
552
|
+
""",
|
|
553
|
+
encoding='utf8',
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
manifest = Manifest.from_file(yaml_file)
|
|
557
|
+
assert manifest.most_suitable_rule('baz').by_manifest_file == str(yaml_file)
|
|
558
|
+
|
|
559
|
+
|
|
538
560
|
class TestIfParser:
|
|
539
561
|
def test_invalid_if_statement(self):
|
|
540
562
|
statement = '1'
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
name: pre-commit check
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
|
|
6
|
-
jobs:
|
|
7
|
-
pre-commit:
|
|
8
|
-
runs-on: ubuntu-latest
|
|
9
|
-
steps:
|
|
10
|
-
- uses: actions/checkout@v4
|
|
11
|
-
with:
|
|
12
|
-
fetch-depth: 0
|
|
13
|
-
- uses: actions/setup-python@v5
|
|
14
|
-
- id: changed-files
|
|
15
|
-
uses: tj-actions/changed-files@v45
|
|
16
|
-
- uses: pre-commit/action@v3.0.1
|
|
17
|
-
with:
|
|
18
|
-
extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{idf_build_apps-2.8.1 → idf_build_apps-2.10.0}/docs/en/explanations/dependency_driven_build.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|