idf-build-apps 2.9.0__tar.gz → 2.10.1__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.9.0 → idf_build_apps-2.10.1}/.pre-commit-config.yaml +1 -1
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/CHANGELOG.md +12 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/PKG-INFO +3 -2
- idf_build_apps-2.10.1/docs/en/guides/custom_app.md +80 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/index.rst +2 -1
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/__init__.py +3 -1
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/app.py +8 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/args.py +56 -6
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/main.py +33 -18
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/pyproject.toml +3 -2
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/setup.py +1 -1
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_app.py +1 -2
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_build.py +103 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.editorconfig +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.git-blame-ignore-revs +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.gitattributes +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.github/dependabot.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.github/workflows/publish-pypi.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.github/workflows/sync-jira.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.github/workflows/test-build-docs.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.github/workflows/test-build-idf-apps.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.gitignore +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/.readthedocs.yml +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/CONTRIBUTING.md +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/LICENSE +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/README.md +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_apidoc_templates/module.rst_t +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_apidoc_templates/package.rst_t +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_apidoc_templates/toc.rst_t +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_static/espressif-logo.svg +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_static/theme_overrides.css +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/_templates/layout.html +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/conf_common.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/Makefile +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/conf.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/explanations/build.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/explanations/config_rules.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/explanations/dependency_driven_build.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/explanations/find.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/guides/1.x_to_2.x.md +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/others/CHANGELOG.md +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/others/CONTRIBUTING.md +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/references/cli.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/references/config_file.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/docs/en/references/manifest.rst +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/__main__.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/autocompletions.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/constants.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/finder.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/junit/__init__.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/junit/report.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/junit/utils.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/log.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/manifest/__init__.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/manifest/manifest.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/manifest/soc_header.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/py.typed +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/session_args.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/utils.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/vendors/__init__.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/vendors/pydantic_sources.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/yaml/__init__.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/idf_build_apps/yaml/parser.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/license_header.txt +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/conftest.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_args.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_cmd.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_finder.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_manifest.py +0 -0
- {idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/tests/test_utils.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.10.1 (2025-05-05)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
- cache custom app classes
|
|
10
|
+
|
|
11
|
+
## v2.10.0 (2025-04-22)
|
|
12
|
+
|
|
13
|
+
### Feat
|
|
14
|
+
|
|
15
|
+
- support custom class load from CLI
|
|
16
|
+
|
|
5
17
|
## v2.9.0 (2025-04-16)
|
|
6
18
|
|
|
7
19
|
### Feat
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: idf-build-apps
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.1
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.7
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
17
|
License-File: LICENSE
|
|
17
18
|
Requires-Dist: pyparsing
|
|
18
19
|
Requires-Dist: pyyaml
|
|
@@ -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)
|
|
@@ -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.
|
|
11
|
+
__version__ = '2.10.1'
|
|
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_list_files_to_apps,
|
|
31
32
|
json_to_app,
|
|
32
33
|
)
|
|
33
34
|
|
|
@@ -38,6 +39,7 @@ __all__ = [
|
|
|
38
39
|
'MakeApp',
|
|
39
40
|
'build_apps',
|
|
40
41
|
'find_apps',
|
|
42
|
+
'json_list_files_to_apps',
|
|
41
43
|
'json_to_app',
|
|
42
44
|
'setup_logging',
|
|
43
45
|
]
|
|
@@ -1011,3 +1011,11 @@ class AppDeserializer(BaseModel):
|
|
|
1011
1011
|
def from_json(cls, json_data: t.Union[str, bytes, bytearray]) -> App:
|
|
1012
1012
|
json_dict = json.loads(json_data.strip())
|
|
1013
1013
|
return cls.model_validate({'app': json_dict}).app
|
|
1014
|
+
|
|
1015
|
+
@classmethod
|
|
1016
|
+
def from_json_list(cls, json_list: t.Sequence[t.Union[str, bytes, bytearray]]) -> t.List[App]:
|
|
1017
|
+
apps = []
|
|
1018
|
+
for app_json in json_list:
|
|
1019
|
+
apps.append(cls.from_json(app_json))
|
|
1020
|
+
|
|
1021
|
+
return apps
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import argparse
|
|
5
5
|
import enum
|
|
6
6
|
import glob
|
|
7
|
+
import importlib
|
|
7
8
|
import inspect
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
@@ -27,7 +28,7 @@ from pydantic_settings import (
|
|
|
27
28
|
)
|
|
28
29
|
from typing_extensions import Concatenate, ParamSpec
|
|
29
30
|
|
|
30
|
-
from . import SESSION_ARGS, App, setup_logging
|
|
31
|
+
from . import SESSION_ARGS, App, CMakeApp, MakeApp, setup_logging
|
|
31
32
|
from .constants import ALL_TARGETS, IDF_BUILD_APPS_TOML_FN, SUPPORTED_TARGETS
|
|
32
33
|
from .manifest.manifest import FolderRule, Manifest
|
|
33
34
|
from .utils import InvalidCommand, files_matches_patterns, semicolon_separated_str_to_list, to_absolute_path, to_list
|
|
@@ -397,6 +398,12 @@ class DependencyDrivenBuildArguments(GlobalArguments):
|
|
|
397
398
|
|
|
398
399
|
|
|
399
400
|
class FindBuildArguments(DependencyDrivenBuildArguments):
|
|
401
|
+
_KNOWN_APP_CLASSES: t.ClassVar[t.Dict[str, t.Type[App]]] = {
|
|
402
|
+
'cmake': CMakeApp,
|
|
403
|
+
'make': MakeApp,
|
|
404
|
+
}
|
|
405
|
+
_LOADED_MODULE_APPS: t.ClassVar[t.Dict[str, t.Type[App]]] = {}
|
|
406
|
+
|
|
400
407
|
paths: t.List[str] = field(
|
|
401
408
|
FieldMetadata(
|
|
402
409
|
validate_method=[ValidateMethod.TO_LIST],
|
|
@@ -414,10 +421,9 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
|
|
|
414
421
|
default='all', # type: ignore
|
|
415
422
|
)
|
|
416
423
|
build_system: t.Union[str, t.Type[App]] = field(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
description='Filter the apps by build system. By default set to "cmake"',
|
|
424
|
+
None,
|
|
425
|
+
description='Filter the apps by build system. By default set to "cmake". '
|
|
426
|
+
'Can be either "cmake", "make" or a custom App class path in format "module:class"',
|
|
421
427
|
default='cmake', # type: ignore
|
|
422
428
|
)
|
|
423
429
|
recursive: bool = field(
|
|
@@ -587,12 +593,56 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
|
|
|
587
593
|
|
|
588
594
|
if self.disable_targets and FolderRule.DEFAULT_BUILD_TARGETS:
|
|
589
595
|
LOGGER.info('Disable targets: %s', self.disable_targets)
|
|
590
|
-
self.default_build_targets = [
|
|
596
|
+
self.default_build_targets = [
|
|
597
|
+
_target for _target in FolderRule.DEFAULT_BUILD_TARGETS if _target not in self.disable_targets
|
|
598
|
+
]
|
|
591
599
|
FolderRule.DEFAULT_BUILD_TARGETS = self.default_build_targets
|
|
592
600
|
|
|
593
601
|
if self.override_sdkconfig_files or self.override_sdkconfig_items:
|
|
594
602
|
SESSION_ARGS.set(self)
|
|
595
603
|
|
|
604
|
+
# load build system
|
|
605
|
+
# here could be a string or a class of type App
|
|
606
|
+
if not isinstance(self.build_system, str):
|
|
607
|
+
# do nothing, only cache
|
|
608
|
+
self._KNOWN_APP_CLASSES[self.build_system('', '').build_system] = self.build_system
|
|
609
|
+
return
|
|
610
|
+
|
|
611
|
+
# here could only be a string
|
|
612
|
+
if self.build_system in self._KNOWN_APP_CLASSES:
|
|
613
|
+
self.build_system = self._KNOWN_APP_CLASSES[self.build_system]
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
if ':' not in self.build_system:
|
|
617
|
+
raise ValueError(
|
|
618
|
+
f'Invalid build system: {self.build_system}. '
|
|
619
|
+
f'Known build systems: {", ".join(self._KNOWN_APP_CLASSES.keys())}'
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
# here could only be a string in format "module:class"
|
|
623
|
+
if self.build_system in self._LOADED_MODULE_APPS:
|
|
624
|
+
self.build_system = self._LOADED_MODULE_APPS[self.build_system]
|
|
625
|
+
return
|
|
626
|
+
|
|
627
|
+
# here could only be a string in format "module:class", and not loaded yet
|
|
628
|
+
module_path, class_name = self.build_system.split(':')
|
|
629
|
+
try:
|
|
630
|
+
module = importlib.import_module(module_path)
|
|
631
|
+
except ImportError as e:
|
|
632
|
+
raise ImportError(f'Failed to import module {module_path}. Error: {e!s}')
|
|
633
|
+
|
|
634
|
+
try:
|
|
635
|
+
app_cls = getattr(module, class_name)
|
|
636
|
+
if not issubclass(app_cls, App):
|
|
637
|
+
raise ValueError(f'Class {class_name} must be a subclass of App')
|
|
638
|
+
except (ValueError, AttributeError):
|
|
639
|
+
raise ValueError(f'Class {class_name} not found in module {module_path}')
|
|
640
|
+
|
|
641
|
+
self._LOADED_MODULE_APPS[self.build_system] = app_cls
|
|
642
|
+
self._KNOWN_APP_CLASSES[app_cls('', '').build_system] = app_cls
|
|
643
|
+
|
|
644
|
+
self.build_system = app_cls
|
|
645
|
+
|
|
596
646
|
|
|
597
647
|
class FindArguments(FindBuildArguments):
|
|
598
648
|
output: t.Optional[str] = field(
|
|
@@ -29,8 +29,6 @@ from idf_build_apps.args import (
|
|
|
29
29
|
from .app import (
|
|
30
30
|
App,
|
|
31
31
|
AppDeserializer,
|
|
32
|
-
CMakeApp,
|
|
33
|
-
MakeApp,
|
|
34
32
|
)
|
|
35
33
|
from .autocompletions import activate_completions
|
|
36
34
|
from .constants import ALL_TARGETS, BuildStatus, completion_instructions
|
|
@@ -88,18 +86,6 @@ def find_apps(
|
|
|
88
86
|
**kwargs,
|
|
89
87
|
)
|
|
90
88
|
|
|
91
|
-
app_cls: t.Type[App]
|
|
92
|
-
if isinstance(find_arguments.build_system, str):
|
|
93
|
-
# backwards compatible
|
|
94
|
-
if find_arguments.build_system == 'cmake':
|
|
95
|
-
app_cls = CMakeApp
|
|
96
|
-
elif find_arguments.build_system == 'make':
|
|
97
|
-
app_cls = MakeApp
|
|
98
|
-
else:
|
|
99
|
-
raise ValueError('Only Support "make" and "cmake"')
|
|
100
|
-
else:
|
|
101
|
-
app_cls = find_arguments.build_system
|
|
102
|
-
|
|
103
89
|
apps: t.Set[App] = set()
|
|
104
90
|
if find_arguments.target == 'all':
|
|
105
91
|
targets = ALL_TARGETS
|
|
@@ -115,7 +101,7 @@ def find_apps(
|
|
|
115
101
|
_find_apps(
|
|
116
102
|
_p,
|
|
117
103
|
_t,
|
|
118
|
-
app_cls=
|
|
104
|
+
app_cls=find_arguments.build_system, # type: ignore
|
|
119
105
|
args=find_arguments,
|
|
120
106
|
)
|
|
121
107
|
)
|
|
@@ -487,14 +473,43 @@ def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] =
|
|
|
487
473
|
:param extra_classes: extra App class
|
|
488
474
|
:return: App object
|
|
489
475
|
"""
|
|
490
|
-
|
|
476
|
+
_known_classes = list(FindArguments()._KNOWN_APP_CLASSES.values())
|
|
491
477
|
if extra_classes:
|
|
492
|
-
|
|
478
|
+
_known_classes.extend(extra_classes)
|
|
493
479
|
|
|
494
480
|
custom_deserializer = create_model(
|
|
495
481
|
'_CustomDeserializer',
|
|
496
|
-
app=(t.Union[tuple(
|
|
482
|
+
app=(t.Union[tuple(_known_classes)], Field(discriminator='build_system')),
|
|
497
483
|
__base__=AppDeserializer,
|
|
498
484
|
)
|
|
499
485
|
|
|
500
486
|
return custom_deserializer.from_json(json_str)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def json_list_files_to_apps(
|
|
490
|
+
json_list_filepaths: t.List[str],
|
|
491
|
+
extra_classes: t.Optional[t.List[t.Type[App]]] = None,
|
|
492
|
+
) -> t.List[App]:
|
|
493
|
+
"""
|
|
494
|
+
Deserialize a list of json strings to App objects
|
|
495
|
+
|
|
496
|
+
:param json_list_filepaths: filepath to the files, each line is a json string
|
|
497
|
+
:param extra_classes: extra App class
|
|
498
|
+
:return: list of App object
|
|
499
|
+
"""
|
|
500
|
+
_known_classes = list(FindArguments()._KNOWN_APP_CLASSES.values())
|
|
501
|
+
if extra_classes:
|
|
502
|
+
_known_classes.extend(extra_classes)
|
|
503
|
+
|
|
504
|
+
custom_deserializer = create_model(
|
|
505
|
+
'_CustomDeserializer',
|
|
506
|
+
app=(t.Union[tuple(_known_classes)], Field(discriminator='build_system')),
|
|
507
|
+
__base__=AppDeserializer,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
jsons = []
|
|
511
|
+
for fp in json_list_filepaths:
|
|
512
|
+
with open(fp) as fr:
|
|
513
|
+
jsons.extend(fr.readlines())
|
|
514
|
+
|
|
515
|
+
return custom_deserializer.from_json_list(jsons)
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = {file = "LICENSE"}
|
|
12
12
|
classifiers = [
|
|
13
|
-
"Development Status ::
|
|
13
|
+
"Development Status :: 5 - Production/Stable",
|
|
14
14
|
"License :: OSI Approved :: Apache Software License",
|
|
15
15
|
"Programming Language :: Python :: 3.7",
|
|
16
16
|
"Programming Language :: Python :: 3.8",
|
|
@@ -18,6 +18,7 @@ classifiers = [
|
|
|
18
18
|
"Programming Language :: Python :: 3.10",
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
21
22
|
]
|
|
22
23
|
dynamic = ["version", "description"]
|
|
23
24
|
requires-python = ">=3.7"
|
|
@@ -64,7 +65,7 @@ idf-build-apps = "idf_build_apps:main.main"
|
|
|
64
65
|
|
|
65
66
|
[tool.commitizen]
|
|
66
67
|
name = "cz_conventional_commits"
|
|
67
|
-
version = "2.
|
|
68
|
+
version = "2.10.1"
|
|
68
69
|
tag_format = "v$version"
|
|
69
70
|
version_files = [
|
|
70
71
|
"idf_build_apps/__init__.py",
|
|
@@ -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.1',
|
|
42
42
|
description='Tools for building ESP-IDF related apps.',
|
|
43
43
|
author=None,
|
|
44
44
|
author_email='Fu Hanxi <fuhanxi@espressif.com>',
|
|
@@ -79,8 +79,7 @@ def test_app_deserializer():
|
|
|
79
79
|
|
|
80
80
|
with pytest.raises(
|
|
81
81
|
ValidationError,
|
|
82
|
-
match="Input tag 'custom' found using 'build_system' does not match "
|
|
83
|
-
"any of the expected tags: 'unknown', 'cmake', 'make'",
|
|
82
|
+
match="Input tag 'custom' found using 'build_system' does not match any of the expected tags: 'cmake', 'make'",
|
|
84
83
|
):
|
|
85
84
|
assert json_to_app(c.to_json()) == c
|
|
86
85
|
|
|
@@ -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
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{idf_build_apps-2.9.0 → idf_build_apps-2.10.1}/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
|
|
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
|