idf-build-apps 2.10.3__tar.gz → 2.11.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.
Files changed (70) hide show
  1. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/CHANGELOG.md +6 -0
  2. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/PKG-INFO +1 -1
  3. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/references/config_file.rst +1 -1
  4. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/__init__.py +1 -1
  5. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/args.py +25 -9
  6. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/pyproject.toml +1 -1
  7. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/setup.py +1 -1
  8. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_args.py +9 -2
  9. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_finder.py +106 -3
  10. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.editorconfig +0 -0
  11. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.git-blame-ignore-revs +0 -0
  12. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.gitattributes +0 -0
  13. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.github/dependabot.yml +0 -0
  14. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.github/workflows/publish-pypi.yml +0 -0
  15. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.github/workflows/sync-jira.yml +0 -0
  16. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.github/workflows/test-build-docs.yml +0 -0
  17. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.github/workflows/test-build-idf-apps.yml +0 -0
  18. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.gitignore +0 -0
  19. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.pre-commit-config.yaml +0 -0
  20. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/.readthedocs.yml +0 -0
  21. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/CONTRIBUTING.md +0 -0
  22. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/LICENSE +0 -0
  23. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/README.md +0 -0
  24. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_apidoc_templates/module.rst_t +0 -0
  25. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_apidoc_templates/package.rst_t +0 -0
  26. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_apidoc_templates/toc.rst_t +0 -0
  27. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_static/espressif-logo.svg +0 -0
  28. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_static/theme_overrides.css +0 -0
  29. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/_templates/layout.html +0 -0
  30. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/conf_common.py +0 -0
  31. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/Makefile +0 -0
  32. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/conf.py +0 -0
  33. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/explanations/build.rst +0 -0
  34. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/explanations/config_rules.rst +0 -0
  35. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/explanations/dependency_driven_build.rst +0 -0
  36. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/explanations/find.rst +0 -0
  37. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/guides/1.x_to_2.x.md +0 -0
  38. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/guides/custom_app.md +0 -0
  39. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/index.rst +0 -0
  40. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/others/CHANGELOG.md +0 -0
  41. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/others/CONTRIBUTING.md +0 -0
  42. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/references/cli.rst +0 -0
  43. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/docs/en/references/manifest.rst +0 -0
  44. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/__main__.py +0 -0
  45. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/app.py +0 -0
  46. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/autocompletions.py +0 -0
  47. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/constants.py +0 -0
  48. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/finder.py +0 -0
  49. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/junit/__init__.py +0 -0
  50. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/junit/report.py +0 -0
  51. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/junit/utils.py +0 -0
  52. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/log.py +0 -0
  53. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/main.py +0 -0
  54. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/manifest/__init__.py +0 -0
  55. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/manifest/manifest.py +0 -0
  56. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/manifest/soc_header.py +0 -0
  57. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/py.typed +0 -0
  58. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/session_args.py +0 -0
  59. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/utils.py +0 -0
  60. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/vendors/__init__.py +0 -0
  61. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/vendors/pydantic_sources.py +0 -0
  62. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/yaml/__init__.py +0 -0
  63. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/idf_build_apps/yaml/parser.py +0 -0
  64. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/license_header.txt +0 -0
  65. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/conftest.py +0 -0
  66. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_app.py +0 -0
  67. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_build.py +0 -0
  68. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_cmd.py +0 -0
  69. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_manifest.py +0 -0
  70. {idf_build_apps-2.10.3 → idf_build_apps-2.11.0}/tests/test_utils.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v2.11.0 (2025-06-03)
6
+
7
+ ### Feat
8
+
9
+ - support extra_pythonpaths injection during the runtime
10
+
5
11
  ## v2.10.3 (2025-06-03)
6
12
 
7
13
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idf-build-apps
3
- Version: 2.10.3
3
+ Version: 2.11.0
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
@@ -101,7 +101,7 @@ This indicates that in the configuration file, you should specify it with the na
101
101
  Expand Environment Variables
102
102
  ******************************
103
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:
104
+ All 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 example:
105
105
 
106
106
  .. code:: toml
107
107
 
@@ -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.3'
11
+ __version__ = '2.11.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -39,7 +39,6 @@ LOGGER = logging.getLogger(__name__)
39
39
 
40
40
  class ValidateMethod(str, enum.Enum):
41
41
  TO_LIST = 'to_list'
42
- EXPAND_VARS = 'expand_vars'
43
42
 
44
43
 
45
44
  @dataclass
@@ -174,12 +173,17 @@ class BaseArguments(BaseSettings):
174
173
  if info.field_name and info.field_name in cls.model_fields:
175
174
  f = cls.model_fields[info.field_name]
176
175
  meta = get_meta(f)
176
+
177
+ # always expand vars for all fields
178
+ if isinstance(v, str):
179
+ v = expand_vars(v)
180
+ elif isinstance(v, list):
181
+ v = [expand_vars(item) if isinstance(item, str) else item for item in v]
182
+
177
183
  if meta and meta.validate_method:
178
184
  for method in meta.validate_method:
179
185
  if method == ValidateMethod.TO_LIST:
180
186
  v = to_list(v)
181
- elif method == ValidateMethod.EXPAND_VARS:
182
- v = expand_vars(v)
183
187
  else:
184
188
  raise NotImplementedError(f'Unknown validate method: {method}')
185
189
 
@@ -241,9 +245,7 @@ class DependencyDrivenBuildArguments(GlobalArguments):
241
245
  default=None, # type: ignore
242
246
  )
243
247
  manifest_rootpath: str = field(
244
- FieldMetadata(
245
- validate_method=[ValidateMethod.EXPAND_VARS],
246
- ),
248
+ None,
247
249
  description='Root path to resolve the relative paths defined in the manifest files. '
248
250
  'By default set to the current directory. Support environment variables.',
249
251
  default=os.curdir, # type: ignore
@@ -420,6 +422,15 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
420
422
  description='Filter the apps by target. By default set to "all"',
421
423
  default='all', # type: ignore
422
424
  )
425
+ extra_pythonpaths: t.Optional[t.List[str]] = field(
426
+ FieldMetadata(
427
+ validate_method=[ValidateMethod.TO_LIST],
428
+ nargs='+',
429
+ ),
430
+ description='space-separated list of additional Python paths to search for the app classes. '
431
+ 'Will be injected into the head of sys.path.',
432
+ default=None, # type: ignore
433
+ )
423
434
  build_system: t.Union[str, t.Type[App]] = field(
424
435
  None,
425
436
  description='Filter the apps by build system. By default set to "cmake". '
@@ -608,6 +619,14 @@ class FindBuildArguments(DependencyDrivenBuildArguments):
608
619
  if self.override_sdkconfig_files or self.override_sdkconfig_items:
609
620
  SESSION_ARGS.set(self)
610
621
 
622
+ # update PYTHONPATH
623
+ if self.extra_pythonpaths:
624
+ LOGGER.debug('Adding extra Python paths: %s', self.extra_pythonpaths)
625
+ for path in self.extra_pythonpaths:
626
+ abs_path = to_absolute_path(path)
627
+ if abs_path not in sys.path:
628
+ sys.path.insert(0, abs_path)
629
+
611
630
  # load build system
612
631
  # here could be a string or a class of type App
613
632
  if not isinstance(self.build_system, str):
@@ -770,7 +789,6 @@ class BuildArguments(FindBuildArguments):
770
789
  FieldMetadata(
771
790
  deprecates={'collect_size_info': {}},
772
791
  hidden=True,
773
- validate_method=[ValidateMethod.EXPAND_VARS],
774
792
  ),
775
793
  description='Record size json filepath of the built apps to the specified file. '
776
794
  'Each line is a json string. Can expand placeholders @p. Support environment variables.',
@@ -782,7 +800,6 @@ class BuildArguments(FindBuildArguments):
782
800
  FieldMetadata(
783
801
  deprecates={'collect_app_info': {}},
784
802
  hidden=True,
785
- validate_method=[ValidateMethod.EXPAND_VARS],
786
803
  ),
787
804
  description='Record serialized app model of the built apps to the specified file. '
788
805
  'Each line is a json string. Can expand placeholders @p. Support environment variables.',
@@ -794,7 +811,6 @@ class BuildArguments(FindBuildArguments):
794
811
  FieldMetadata(
795
812
  deprecates={'junitxml': {}},
796
813
  hidden=True,
797
- validate_method=[ValidateMethod.EXPAND_VARS],
798
814
  ),
799
815
  description='Path to the junitxml file to record the build results. Can expand placeholder @p. '
800
816
  'Support environment variables.',
@@ -65,7 +65,7 @@ idf-build-apps = "idf_build_apps:main.main"
65
65
 
66
66
  [tool.commitizen]
67
67
  name = "cz_conventional_commits"
68
- version = "2.10.3"
68
+ version = "2.11.0"
69
69
  tag_format = "v$version"
70
70
  version_files = [
71
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.3',
41
+ version='2.11.0',
42
42
  description='Tools for building ESP-IDF related apps.',
43
43
  author=None,
44
44
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -97,12 +97,19 @@ def test_empty_argument():
97
97
  assert args.config_rules is None
98
98
 
99
99
 
100
- def test_build_args_expansion():
100
+ def test_build_args_expansion(monkeypatch):
101
+ monkeypatch.setenv('FOO', '2')
102
+
101
103
  args = BuildArguments(
102
- parallel_index=2, collect_app_info='@p.txt', junitxml='x_@p.txt', collect_size_info='@p_@p.txt'
104
+ parallel_index=2,
105
+ parallel_count='$FOO',
106
+ collect_app_info='@p.txt',
107
+ junitxml='x_@p.txt',
108
+ collect_size_info='@p_@p.txt',
103
109
  )
104
110
  assert args.collect_app_info == '2.txt'
105
111
  assert args.junitxml == 'x_2.txt'
112
+ assert args.parallel_count == 2
106
113
 
107
114
  args.parallel_index = 3
108
115
  assert args.collect_app_info == '3.txt'
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import os
5
+ import sys
5
6
  import tempfile
6
7
  from pathlib import (
7
8
  Path,
@@ -25,8 +26,9 @@ from idf_build_apps.manifest.manifest import Manifest
25
26
 
26
27
 
27
28
  class TestFindWithManifest:
28
- def test_manifest_rootpath_chdir(self, capsys):
29
- test_dir = Path(IDF_PATH) / 'examples' / 'get-started'
29
+ def test_manifest_rootpath_chdir(self, capsys, tmp_path):
30
+ test_dir = tmp_path / 'examples' / 'get-started'
31
+ create_project('hello_world', test_dir)
30
32
 
31
33
  yaml_file = test_dir / 'test.yml'
32
34
  yaml_file.write_text(
@@ -38,7 +40,7 @@ examples/get-started:
38
40
  encoding='utf8',
39
41
  )
40
42
 
41
- os.chdir(IDF_PATH)
43
+ os.chdir(tmp_path)
42
44
  assert not find_apps(str(test_dir), 'esp32', recursive=True, manifest_files=str(yaml_file))
43
45
  assert not capsys.readouterr().err
44
46
 
@@ -769,3 +771,104 @@ def test_find_apps_with_duplicated_paths(tmp_path):
769
771
  == len(find_apps(str(tmp_path / 'folder1'), 'esp32', recursive=True))
770
772
  == 2
771
773
  )
774
+
775
+
776
+ class TestFindWithExtraPythonPaths:
777
+ custom_app_code = """
778
+ import os
779
+ import typing as t
780
+ from idf_build_apps import App
781
+ from idf_build_apps.constants import BuildStatus
782
+ from idf_build_apps.utils import Literal
783
+
784
+
785
+ class ExtraPathTestApp(App):
786
+ build_system: Literal['extra_path_test'] = 'extra_path_test' # type: ignore
787
+
788
+ @property
789
+ def supported_targets(self) -> t.List[str]:
790
+ return ['esp32', 'esp32s2', 'esp32c3']
791
+
792
+ def build(self, *args, **kwargs):
793
+ if not self.dry_run:
794
+ os.makedirs(self.build_path, exist_ok=True)
795
+ with open(os.path.join(self.build_path, 'extra_path_test_marker.txt'), 'w') as f:
796
+ f.write('Extra path test build successful')
797
+ self.build_status = BuildStatus.SUCCESS
798
+
799
+ @classmethod
800
+ def is_app(cls, path: str) -> bool:
801
+ return True
802
+ """
803
+
804
+ @pytest.fixture
805
+ def setup_custom_module_and_app(self, tmp_path):
806
+ """Set up a custom module in a separate directory and a test app"""
807
+ # Create custom module directory (separate from app directory)
808
+ custom_module_dir = tmp_path / 'custom_modules'
809
+ custom_module_dir.mkdir()
810
+
811
+ # Create the custom module file
812
+ custom_module_file = custom_module_dir / 'extra_path_test_module.py'
813
+ custom_module_file.write_text(self.custom_app_code)
814
+
815
+ # Create test app directory
816
+ test_app_dir = tmp_path / 'test_app'
817
+ test_app_dir.mkdir()
818
+
819
+ # Create basic app structure
820
+ main_dir = test_app_dir / 'main'
821
+ main_dir.mkdir()
822
+ (main_dir / 'main.c').write_text('void app_main() {}')
823
+ (main_dir / 'CMakeLists.txt').write_text('idf_component_register(SRCS "main.c")')
824
+ (test_app_dir / 'CMakeLists.txt').write_text(
825
+ 'cmake_minimum_required(VERSION 3.16)\ninclude($ENV{IDF_PATH}/tools/cmake/project.cmake)\nproject(test_app)'
826
+ )
827
+
828
+ return {
829
+ 'custom_module_dir': str(custom_module_dir),
830
+ 'test_app_dir': str(test_app_dir),
831
+ 'custom_module_file': str(custom_module_file),
832
+ }
833
+
834
+ def test_extra_pythonpaths_with_custom_build_system(self, setup_custom_module_and_app):
835
+ """Test that extra_pythonpaths allows loading custom build system classes"""
836
+ setup = setup_custom_module_and_app
837
+
838
+ original_path = sys.path.copy()
839
+
840
+ try:
841
+ # First verify that without extra_pythonpaths, the module cannot be found
842
+ with pytest.raises(ImportError, match=r'Failed to import module extra_path_test_module'):
843
+ find_apps(
844
+ paths=[setup['test_app_dir']],
845
+ target='esp32',
846
+ build_system='extra_path_test_module:ExtraPathTestApp',
847
+ )
848
+
849
+ # Now test with extra_pythonpaths - this should work
850
+ apps = find_apps(
851
+ paths=[setup['test_app_dir']],
852
+ target='esp32',
853
+ build_system='extra_path_test_module:ExtraPathTestApp',
854
+ extra_pythonpaths=[setup['custom_module_dir']],
855
+ )
856
+
857
+ # Verify we found the app
858
+ assert len(apps) == 1
859
+ app = apps[0]
860
+
861
+ # Verify it's using our custom class
862
+ assert app.build_system == 'extra_path_test'
863
+ assert app.__class__.__name__ == 'ExtraPathTestApp'
864
+
865
+ # Verify the custom module dir was added to sys.path
866
+ assert setup['custom_module_dir'] in sys.path
867
+
868
+ # Test building the app
869
+ app.build(dry_run=True)
870
+ assert app.build_status == BuildStatus.SUCCESS
871
+
872
+ finally:
873
+ # Restore original sys.path
874
+ sys.path[:] = original_path
File without changes