Skip to content

Commit 86d4137

Browse files
committed
Merge branch 'feat/remove_dependency' into 'main'
feat: Add remove-dependency command to idf extensions Closes PACMAN-1110 See merge request espressif/idf-component-manager!513
2 parents 6ce2234 + 79c4161 commit 86d4137

File tree

7 files changed

+596
-141
lines changed

7 files changed

+596
-141
lines changed

idf_component_manager/core.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,11 @@
8282
check_examples_folder,
8383
dist_name,
8484
get_validated_manifest,
85+
load_project_description_file,
8586
parse_example,
8687
raise_component_modified_error,
88+
try_remove_dependency_with_fallback,
89+
validate_project_description_version,
8790
)
8891
from .dependencies import download_project_dependencies
8992
from .local_component_list import parse_component_list
@@ -1013,3 +1016,25 @@ def sync_registry(
10131016
recursive=recursive,
10141017
resolution=resolution,
10151018
)
1019+
1020+
1021+
def remove_dependency_from_project(build_path: Path, dependency_name: str):
1022+
project_description = load_project_description_file(build_path)
1023+
validate_project_description_version(project_description)
1024+
1025+
if not project_description.get('all_component_info'):
1026+
raise FatalError(
1027+
'Project description file is missing required key "all_component_info". '
1028+
'This may indicate an unsupported format version.'
1029+
)
1030+
1031+
removed_from_paths = try_remove_dependency_with_fallback(
1032+
project_description['all_component_info'].values(), dependency_name
1033+
)
1034+
1035+
if removed_from_paths:
1036+
joined_paths = '\n'.join(str(path) for path in removed_from_paths)
1037+
notice(f'Successfully removed dependency "{dependency_name}" from: \n{joined_paths}')
1038+
return
1039+
1040+
notice(f'Dependency "{dependency_name}" not found in any component')

idf_component_manager/core_utils.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
22
# SPDX-License-Identifier: Apache-2.0
3+
import json
34
import re
45
import shutil
56
import typing as t
67
from pathlib import Path
78

9+
from ruamel.yaml import YAML
810
from tqdm import tqdm
911

1012
from idf_component_tools import notice
@@ -17,6 +19,8 @@
1719
from idf_component_tools.manifest import Manifest
1820
from idf_component_tools.manifest.constants import SLUG_BODY_REGEX
1921
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
2024

2125
CREATE_PROJECT_FROM_EXAMPLE_NAME_REGEX = (
2226
r'^((?P<namespace>{slug})\/)?'
@@ -210,3 +214,83 @@ def check_examples_folder(
210214
'Please check the path of the custom example folder in `examples` field '
211215
'in `idf_component.yml` file'.format(', '.join(error_paths))
212216
)
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

Comments
 (0)