diff --git a/docs/find_build.md b/docs/find_build.md index c289f61..7121a40 100644 --- a/docs/find_build.md +++ b/docs/find_build.md @@ -225,6 +225,7 @@ Placeholders are a set of symbols, which could be used when setting work directo - `@w`: Would be replaced by the wildcard if exists, otherwise would be replaced by the config name. - `@n`: Would be replaced by the project name. - `@f`: Would be replaced by the escaped project path (replaced "/" to "_"). +- `@v`: Would be replaced by the ESP-IDF version like `5_3_0`. - `@i`: Would be replaced by the build index. (only available in `build` command) - `@p`: Would be replaced by the parallel build index. (default to `1`, only available in `build` command) diff --git a/idf_build_apps/app.py b/idf_build_apps/app.py index afcbd86..6f2f61d 100644 --- a/idf_build_apps/app.py +++ b/idf_build_apps/app.py @@ -16,9 +16,6 @@ from datetime import ( datetime, ) -from functools import ( - lru_cache, -) from pathlib import ( Path, ) @@ -92,8 +89,8 @@ class App(BaseModel): WILDCARD_PLACEHOLDER: t.ClassVar[str] = '@w' # replace it with the wildcard, usually the sdkconfig NAME_PLACEHOLDER: t.ClassVar[str] = '@n' # replace it with self.name FULL_NAME_PLACEHOLDER: t.ClassVar[str] = '@f' # replace it with escaped self.app_dir - INDEX_PLACEHOLDER: t.ClassVar[str] = '@i' # replace it with the build index IDF_VERSION_PLACEHOLDER: t.ClassVar[str] = '@v' # replace it with the IDF version + INDEX_PLACEHOLDER: t.ClassVar[str] = '@i' # replace it with the build index (while build_apps) SDKCONFIG_LINE_REGEX: t.ClassVar[t.Pattern] = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$") @@ -115,8 +112,7 @@ class App(BaseModel): target: str sdkconfig_path: t.Optional[str] = None config_name: t.Optional[str] = None - - build_status: BuildStatus = BuildStatus.UNKNOWN + sdkconfig_defaults_str: t.Optional[str] = None # Attrs that support placeholders _work_dir: t.Optional[str] = None @@ -125,15 +121,17 @@ class App(BaseModel): _build_log_filename: t.Optional[str] = None _size_json_filename: t.Optional[str] = None - # Build related dry_run: bool = False - index: t.Union[int, None] = None verbose: bool = False check_warnings: bool = False preserve: bool = True - # logging - build_apps_args: t.Optional[BuildAppsArgs] = BuildAppsArgs() + # build_apps() related + build_apps_args: t.Optional[BuildAppsArgs] = None + index: t.Optional[int] = None + + # build status related + build_status: BuildStatus = BuildStatus.UNKNOWN _build_comment: t.Optional[str] = None _build_stage: t.Optional[BuildStage] = None @@ -145,28 +143,20 @@ def __init__( app_dir: str, target: str, *, - sdkconfig_path: t.Optional[str] = None, - config_name: t.Optional[str] = None, work_dir: t.Optional[str] = None, build_dir: str = 'build', build_log_filename: t.Optional[str] = None, size_json_filename: t.Optional[str] = None, - check_warnings: bool = False, - preserve: bool = True, - sdkconfig_defaults_str: t.Optional[str] = None, **kwargs: t.Any, ) -> None: kwargs.update( { 'app_dir': app_dir, 'target': target, - 'sdkconfig_path': sdkconfig_path, - 'config_name': config_name, - 'check_warnings': check_warnings, - 'preserve': preserve, } ) super().__init__(**kwargs) + # These internal variables store the paths with environment variables and placeholders; # Public properties with similar names use the _expand method to get the actual paths. self._work_dir = work_dir or app_dir @@ -175,30 +165,24 @@ def __init__( self._build_log_filename = build_log_filename self._size_json_filename = size_json_filename - # should be built or not - self._checked_should_build = False - - # sdkconfig attrs, use properties instead - self._sdkconfig_defaults = self._get_sdkconfig_defaults(sdkconfig_defaults_str) - self._sdkconfig_files: t.List[str] = None # type: ignore - self._sdkconfig_files_defined_target: str = None # type: ignore - # pass all parameters to initialize hook method kwargs.update( { - 'work_dir': work_dir, - 'build_dir': build_dir, + 'work_dir': self._work_dir, + 'build_dir': self._build_dir, 'build_log_filename': build_log_filename, 'size_json_filename': size_json_filename, - 'sdkconfig_defaults_str': sdkconfig_defaults_str, } ) self._initialize_hook(**kwargs) + # private attrs, won't be dumped to json + self._checked_should_build = False + self._logger = logging.getLogger(f'{__name__}.{hash(self)}') self._logger.addFilter(_AppBuildStageFilter(app=self)) - self._process_sdkconfig_files() + self._sdkconfig_files, self._sdkconfig_files_defined_target = self._process_sdkconfig_files() def _initialize_hook(self, **kwargs): """ @@ -231,16 +215,15 @@ def __str__(self): return default_fmt.format(*default_args) - @staticmethod - def _get_sdkconfig_defaults(sdkconfig_defaults_str: t.Optional[str] = None) -> t.List[str]: - if sdkconfig_defaults_str is not None: - candidates = sdkconfig_defaults_str.split(';') - elif os.getenv('SDKCONFIG_DEFAULTS', None) is not None: - candidates = os.getenv('SDKCONFIG_DEFAULTS', '').split(';') - else: - candidates = [DEFAULT_SDKCONFIG] + @property + def sdkconfig_defaults_candidates(self) -> t.List[str]: + if self.sdkconfig_defaults_str is not None: + return self.sdkconfig_defaults_str.split(';') - return candidates + if os.getenv('SDKCONFIG_DEFAULTS', None) is not None: + return os.getenv('SDKCONFIG_DEFAULTS', '').split(';') + + return [DEFAULT_SDKCONFIG] @t.overload def _expand(self, path: None) -> None: @@ -259,7 +242,8 @@ def _expand(self, path): if self.index is not None: path = path.replace(self.INDEX_PLACEHOLDER, str(self.index)) - path = self.build_apps_args.expand(path) + if self.build_apps_args: + path = self.build_apps_args.expand(path) path = path.replace( self.IDF_VERSION_PLACEHOLDER, f'{IDF_VERSION_MAJOR}_{IDF_VERSION_MINOR}_{IDF_VERSION_PATCH}' ) @@ -282,9 +266,10 @@ def _expand(self, path): return path @property - @lru_cache(1) def name(self) -> str: base_name = os.path.basename(self.app_dir) + # '.' for relative path like '.' + # '' for path endswith '/' if base_name in ['.', '']: return os.path.basename(os.path.abspath(self.app_dir)) return base_name @@ -348,22 +333,18 @@ def size_json_path(self) -> t.Optional[str]: return None - @computed_field # type: ignore - @property - def config(self) -> t.Optional[str]: - return self.config_name - - def _process_sdkconfig_files(self): + def _process_sdkconfig_files(self) -> t.Tuple[t.List[str], t.Optional[str]]: """ Expand environment variables in default sdkconfig files and remove some CI related settings. """ - res = [] + real_sdkconfig_files: t.List[str] = [] + sdkconfig_files_defined_target: t.Optional[str] = None expanded_dir = os.path.join(self.work_dir, 'expanded_sdkconfig_files', os.path.basename(self.build_dir)) if not os.path.isdir(expanded_dir): os.makedirs(expanded_dir) - for f in self._sdkconfig_defaults + ([self.sdkconfig_path] if self.sdkconfig_path else []): + for f in self.sdkconfig_defaults_candidates + ([self.sdkconfig_path] if self.sdkconfig_path else []): if not os.path.isabs(f): f = os.path.join(self.work_dir, f) @@ -381,7 +362,7 @@ def _process_sdkconfig_files(self): if m: key = m.group(1) if key == 'CONFIG_IDF_TARGET': - self._sdkconfig_files_defined_target = m.group(2) + sdkconfig_files_defined_target = m.group(2) if isinstance(self, CMakeApp): if key in self.SDKCONFIG_TEST_OPTS: @@ -401,10 +382,10 @@ def _process_sdkconfig_files(self): os.unlink(expanded_fp) except OSError: self._logger.debug('Failed to remove file %s', expanded_fp) - res.append(f) + real_sdkconfig_files.append(f) else: self._logger.debug('Expand sdkconfig file %s to %s', f, expanded_fp) - res.append(expanded_fp) + real_sdkconfig_files.append(expanded_fp) # copy the related target-specific sdkconfig files par_dir = os.path.abspath(os.path.join(f, '..')) for target_specific_file in ( @@ -427,22 +408,18 @@ def _process_sdkconfig_files(self): except OSError: pass - if SESSION_ARGS.override_sdkconfig_items: - res.append(SESSION_ARGS.override_sdkconfig_file_path) + if SESSION_ARGS.override_sdkconfig_file_path: + real_sdkconfig_files.append(SESSION_ARGS.override_sdkconfig_file_path) if 'CONFIG_IDF_TARGET' in SESSION_ARGS.override_sdkconfig_items: - self._sdkconfig_files_defined_target = SESSION_ARGS.override_sdkconfig_items['CONFIG_IDF_TARGET'] + sdkconfig_files_defined_target = SESSION_ARGS.override_sdkconfig_items['CONFIG_IDF_TARGET'] - self._sdkconfig_files = res + return real_sdkconfig_files, sdkconfig_files_defined_target @property - @lru_cache() - # @cached_property requires python 3.8 def sdkconfig_files_defined_idf_target(self) -> t.Optional[str]: return self._sdkconfig_files_defined_target @property - @lru_cache() - # @cached_property requires python 3.8 def sdkconfig_files(self) -> t.List[str]: return [os.path.abspath(file) for file in self._sdkconfig_files] diff --git a/idf_build_apps/main.py b/idf_build_apps/main.py index 1412918..ffa91f8 100644 --- a/idf_build_apps/main.py +++ b/idf_build_apps/main.py @@ -426,8 +426,9 @@ def get_parser() -> argparse.ArgumentParser: '- @w: would be replaced by the wildcard, usually the sdkconfig\n' '- @n: would be replaced by the app name\n' '- @f: would be replaced by the escaped app path (replaced "/" to "_")\n' - '- @i: would be replaced by the build index\n' - '- @p: would be replaced by the parallel index', + '- @v: Would be replaced by the ESP-IDF version like `5_3_0`\n' + '- @i: would be replaced by the build index (only available in `build` command)\n' + '- @p: would be replaced by the parallel index (only available in `build` command)', formatter_class=argparse.RawDescriptionHelpFormatter, ) actions = parser.add_subparsers(dest='action') diff --git a/idf_build_apps/utils.py b/idf_build_apps/utils.py index 24b7c73..9c1c646 100644 --- a/idf_build_apps/utils.py +++ b/idf_build_apps/utils.py @@ -323,13 +323,13 @@ def __lt__(self, other: t.Any) -> bool: def __eq__(self, other: t.Any) -> bool: if isinstance(other, self.__class__): # we only care the public attributes - return self.__dict__ == other.__dict__ + return self.model_dump() == other.model_dump() return NotImplemented def __hash__(self) -> int: hash_list = [] - for v in self.__dict__.values(): + for v in self.model_dump().values(): if isinstance(v, list): hash_list.append(tuple(v)) elif isinstance(v, dict):