idf-build-apps 2.10.0__tar.gz → 2.10.2__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 (71) hide show
  1. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.pre-commit-config.yaml +1 -1
  2. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/CHANGELOG.md +13 -0
  3. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/PKG-INFO +3 -2
  4. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/__init__.py +3 -1
  5. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/app.py +8 -0
  6. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/args.py +53 -2
  7. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/main.py +33 -30
  8. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/manifest/manifest.py +14 -4
  9. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/pyproject.toml +3 -2
  10. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/setup.py +1 -1
  11. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_app.py +1 -2
  12. idf_build_apps-2.10.2/tests/test_manifest.py +561 -0
  13. idf_build_apps-2.10.0/tests/test_manifest.py +0 -568
  14. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.editorconfig +0 -0
  15. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.git-blame-ignore-revs +0 -0
  16. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.gitattributes +0 -0
  17. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.github/dependabot.yml +0 -0
  18. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.github/workflows/publish-pypi.yml +0 -0
  19. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.github/workflows/sync-jira.yml +0 -0
  20. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.github/workflows/test-build-docs.yml +0 -0
  21. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.github/workflows/test-build-idf-apps.yml +0 -0
  22. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.gitignore +0 -0
  23. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/.readthedocs.yml +0 -0
  24. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/CONTRIBUTING.md +0 -0
  25. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/LICENSE +0 -0
  26. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/README.md +0 -0
  27. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_apidoc_templates/module.rst_t +0 -0
  28. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_apidoc_templates/package.rst_t +0 -0
  29. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_apidoc_templates/toc.rst_t +0 -0
  30. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_static/espressif-logo.svg +0 -0
  31. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_static/theme_overrides.css +0 -0
  32. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/_templates/layout.html +0 -0
  33. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/conf_common.py +0 -0
  34. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/Makefile +0 -0
  35. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/conf.py +0 -0
  36. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/explanations/build.rst +0 -0
  37. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/explanations/config_rules.rst +0 -0
  38. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/explanations/dependency_driven_build.rst +0 -0
  39. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/explanations/find.rst +0 -0
  40. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/guides/1.x_to_2.x.md +0 -0
  41. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/guides/custom_app.md +0 -0
  42. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/index.rst +0 -0
  43. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/others/CHANGELOG.md +0 -0
  44. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/others/CONTRIBUTING.md +0 -0
  45. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/references/cli.rst +0 -0
  46. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/references/config_file.rst +0 -0
  47. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/docs/en/references/manifest.rst +0 -0
  48. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/__main__.py +0 -0
  49. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/autocompletions.py +0 -0
  50. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/constants.py +0 -0
  51. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/finder.py +0 -0
  52. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/junit/__init__.py +0 -0
  53. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/junit/report.py +0 -0
  54. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/junit/utils.py +0 -0
  55. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/log.py +0 -0
  56. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/manifest/__init__.py +0 -0
  57. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/manifest/soc_header.py +0 -0
  58. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/py.typed +0 -0
  59. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/session_args.py +0 -0
  60. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/utils.py +0 -0
  61. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/vendors/__init__.py +0 -0
  62. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/vendors/pydantic_sources.py +0 -0
  63. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/yaml/__init__.py +0 -0
  64. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/idf_build_apps/yaml/parser.py +0 -0
  65. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/license_header.txt +0 -0
  66. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/conftest.py +0 -0
  67. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_args.py +0 -0
  68. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_build.py +0 -0
  69. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_cmd.py +0 -0
  70. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_finder.py +0 -0
  71. {idf_build_apps-2.10.0 → idf_build_apps-2.10.2}/tests/test_utils.py +0 -0
@@ -17,7 +17,7 @@ repos:
17
17
  - --use-current-year
18
18
  exclude: 'idf_build_apps/vendors/'
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: 'v0.11.6'
20
+ rev: 'v0.11.10'
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.2 (2025-05-22)
6
+
7
+ ### Perf
8
+
9
+ - `most_suitable_rule` stop searching till reached root dir
10
+ - pre-compute rules folder, reduced 50% time on `most_suitable_rule`
11
+
12
+ ## v2.10.1 (2025-05-05)
13
+
14
+ ### Fix
15
+
16
+ - cache custom app classes
17
+
5
18
  ## v2.10.0 (2025-04-22)
6
19
 
7
20
  ### Feat
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idf-build-apps
3
- Version: 2.10.0
3
+ Version: 2.10.2
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 :: 2 - Pre-Alpha
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
@@ -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.10.0'
11
+ __version__ = '2.10.2'
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],
@@ -586,12 +593,56 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
586
593
 
587
594
  if self.disable_targets and FolderRule.DEFAULT_BUILD_TARGETS:
588
595
  LOGGER.info('Disable targets: %s', self.disable_targets)
589
- self.default_build_targets = [t for t in FolderRule.DEFAULT_BUILD_TARGETS if t not in self.disable_targets]
596
+ self.default_build_targets = [
597
+ _target for _target in FolderRule.DEFAULT_BUILD_TARGETS if _target not in self.disable_targets
598
+ ]
590
599
  FolderRule.DEFAULT_BUILD_TARGETS = self.default_build_targets
591
600
 
592
601
  if self.override_sdkconfig_files or self.override_sdkconfig_items:
593
602
  SESSION_ARGS.set(self)
594
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
+
595
646
 
596
647
  class FindArguments(FindBuildArguments):
597
648
  output: t.Optional[str] = field(
@@ -4,7 +4,6 @@
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
 
6
6
  import argparse
7
- import importlib
8
7
  import json
9
8
  import logging
10
9
  import os
@@ -30,8 +29,6 @@ from idf_build_apps.args import (
30
29
  from .app import (
31
30
  App,
32
31
  AppDeserializer,
33
- CMakeApp,
34
- MakeApp,
35
32
  )
36
33
  from .autocompletions import activate_completions
37
34
  from .constants import ALL_TARGETS, BuildStatus, completion_instructions
@@ -89,29 +86,6 @@ def find_apps(
89
86
  **kwargs,
90
87
  )
91
88
 
92
- app_cls: t.Type[App]
93
- if isinstance(find_arguments.build_system, str):
94
- if find_arguments.build_system == 'cmake':
95
- app_cls = CMakeApp
96
- elif find_arguments.build_system == 'make':
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}')
108
- else:
109
- raise ValueError(
110
- 'build_system must be either "cmake", "make" or a custom App class path in format "module:class"'
111
- )
112
- else:
113
- app_cls = find_arguments.build_system
114
-
115
89
  apps: t.Set[App] = set()
116
90
  if find_arguments.target == 'all':
117
91
  targets = ALL_TARGETS
@@ -127,7 +101,7 @@ def find_apps(
127
101
  _find_apps(
128
102
  _p,
129
103
  _t,
130
- app_cls=app_cls,
104
+ app_cls=find_arguments.build_system, # type: ignore
131
105
  args=find_arguments,
132
106
  )
133
107
  )
@@ -499,14 +473,43 @@ def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] =
499
473
  :param extra_classes: extra App class
500
474
  :return: App object
501
475
  """
502
- types = [App, CMakeApp, MakeApp]
476
+ _known_classes = list(FindArguments()._KNOWN_APP_CLASSES.values())
503
477
  if extra_classes:
504
- types.extend(extra_classes)
478
+ _known_classes.extend(extra_classes)
505
479
 
506
480
  custom_deserializer = create_model(
507
481
  '_CustomDeserializer',
508
- app=(t.Union[tuple(types)], Field(discriminator='build_system')),
482
+ app=(t.Union[tuple(_known_classes)], Field(discriminator='build_system')),
509
483
  __base__=AppDeserializer,
510
484
  )
511
485
 
512
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)
@@ -254,9 +254,11 @@ class Manifest:
254
254
 
255
255
  def __init__(self, rules: t.Iterable[FolderRule], *, root_path: str = os.curdir) -> None:
256
256
  self.rules = sorted(rules, key=lambda x: x.folder)
257
-
258
257
  self._root_path = to_absolute_path(root_path)
259
258
 
259
+ # Pre-compute rule paths
260
+ self._rule_paths = {rule.folder: rule for rule in self.rules}
261
+
260
262
  @classmethod
261
263
  def from_files(cls, paths: t.Iterable[PathLike], *, root_path: str = os.curdir) -> 'Manifest':
262
264
  """
@@ -383,9 +385,17 @@ class Manifest:
383
385
 
384
386
  def most_suitable_rule(self, _folder: str) -> FolderRule:
385
387
  folder = to_absolute_path(_folder)
386
- for rule in self.rules[::-1]:
387
- if os.path.commonpath([folder, rule.folder]) == rule.folder:
388
- return rule
388
+
389
+ while True:
390
+ if folder in self._rule_paths:
391
+ return self._rule_paths[folder]
392
+ folder = os.path.dirname(folder)
393
+
394
+ # reached the root path, stop searching
395
+ if folder == self._root_path:
396
+ if folder in self._rule_paths:
397
+ return self._rule_paths[folder]
398
+ break
389
399
 
390
400
  return DefaultRule(folder)
391
401
 
@@ -10,7 +10,7 @@ authors = [
10
10
  readme = "README.md"
11
11
  license = {file = "LICENSE"}
12
12
  classifiers = [
13
- "Development Status :: 2 - Pre-Alpha",
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.10.0"
68
+ version = "2.10.2"
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.10.0',
41
+ version='2.10.2',
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