|
1 | 1 | # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD |
2 | 2 | # SPDX-License-Identifier: Apache-2.0 |
| 3 | +import json |
3 | 4 | import re |
4 | 5 | import shutil |
5 | 6 | import typing as t |
6 | 7 | from pathlib import Path |
7 | 8 |
|
| 9 | +from ruamel.yaml import YAML |
8 | 10 | from tqdm import tqdm |
9 | 11 |
|
10 | 12 | from idf_component_tools import notice |
|
17 | 19 | from idf_component_tools.manifest import Manifest |
18 | 20 | from idf_component_tools.manifest.constants import SLUG_BODY_REGEX |
19 | 21 | from idf_component_tools.semver import SimpleSpec |
| 22 | +from idf_component_tools.semver.base import Version |
| 23 | +from idf_component_tools.sources.web_service import WebServiceSource |
20 | 24 |
|
21 | 25 | CREATE_PROJECT_FROM_EXAMPLE_NAME_REGEX = ( |
22 | 26 | r'^((?P<namespace>{slug})\/)?' |
@@ -210,3 +214,83 @@ def check_examples_folder( |
210 | 214 | 'Please check the path of the custom example folder in `examples` field ' |
211 | 215 | 'in `idf_component.yml` file'.format(', '.join(error_paths)) |
212 | 216 | ) |
| 217 | + |
| 218 | + |
| 219 | +def try_remove_dependency_from_manifest(manifest_path: Path, dependency: str) -> bool: |
| 220 | + yaml = YAML() |
| 221 | + try: |
| 222 | + manifest = yaml.load(manifest_path.read_text(encoding='utf8')) |
| 223 | + except FileNotFoundError: |
| 224 | + raise FatalError(f'Cannot find manifest file at {manifest_path}') |
| 225 | + |
| 226 | + if 'dependencies' in manifest and dependency in manifest['dependencies']: |
| 227 | + manifest['dependencies'].pop(dependency) |
| 228 | + with open(manifest_path, 'w', encoding='utf8') as manifest_file: |
| 229 | + yaml.dump(manifest, manifest_file) |
| 230 | + return True |
| 231 | + return False |
| 232 | + |
| 233 | + |
| 234 | +def try_remove_dependency_with_fallback(all_components_info, dependency_name): |
| 235 | + def remove_dependency_and_collect_paths( |
| 236 | + all_component_info: t.Dict, dependency_name: str |
| 237 | + ) -> t.List[Path]: |
| 238 | + removed_from_paths = [] |
| 239 | + |
| 240 | + for component in all_component_info: |
| 241 | + if not component.get('dir'): |
| 242 | + raise FatalError( |
| 243 | + 'Project description file is missing a required "dir" key' |
| 244 | + 'This may indicate an unsupported format version.' |
| 245 | + ) |
| 246 | + |
| 247 | + manifest_path = Path(component.get('dir')) / MANIFEST_FILENAME |
| 248 | + |
| 249 | + if component.get('source') != 'project_components' or not manifest_path.exists(): |
| 250 | + continue |
| 251 | + |
| 252 | + if try_remove_dependency_from_manifest(manifest_path, dependency_name): |
| 253 | + removed_from_paths.append(manifest_path) |
| 254 | + |
| 255 | + return removed_from_paths |
| 256 | + |
| 257 | + # Try to remove dependency (e.g. with included namespace, git, local) |
| 258 | + removed_from_paths = remove_dependency_and_collect_paths(all_components_info, dependency_name) |
| 259 | + |
| 260 | + # If not found, fallback by prefixing the dependency with espressif namespace |
| 261 | + if not removed_from_paths: |
| 262 | + # Normalize the name (e.g. if no namespace -> "espressif/<component_name>") |
| 263 | + dependency_name = WebServiceSource().normalized_name(dependency_name) |
| 264 | + removed_from_paths = remove_dependency_and_collect_paths( |
| 265 | + all_components_info, dependency_name |
| 266 | + ) |
| 267 | + |
| 268 | + return removed_from_paths |
| 269 | + |
| 270 | + |
| 271 | +def validate_project_description_version(project_description: dict): |
| 272 | + version = project_description.get('version', 'unknown') |
| 273 | + |
| 274 | + if version == 'unknown': |
| 275 | + raise FatalError('Project description file is missing version information') |
| 276 | + |
| 277 | + v = Version.coerce(version) |
| 278 | + supported = (Version.coerce('1.3') <= v < Version.coerce('2.0')) or ( |
| 279 | + v >= Version.coerce('2.0') and 'all_component_info' in project_description |
| 280 | + ) |
| 281 | + |
| 282 | + if not supported: |
| 283 | + raise FatalError(f'project_description.json format version {version} is not supported.') |
| 284 | + |
| 285 | + |
| 286 | +def load_project_description_file(build_path): |
| 287 | + try: |
| 288 | + return json.loads((build_path / 'project_description.json').read_text()) |
| 289 | + except FileNotFoundError: |
| 290 | + raise FatalError( |
| 291 | + f'Cannot find project description file at {build_path / "project_description.json"}' |
| 292 | + ) |
| 293 | + except json.JSONDecodeError as e: |
| 294 | + raise FatalError(f'Invalid JSON in project description file: {e}') |
| 295 | + except Exception as e: |
| 296 | + raise FatalError(f'Error reading project description file: {e}') |
0 commit comments