|
3 | 3 | # SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD |
4 | 4 | # SPDX-License-Identifier: Apache-2.0 |
5 | 5 |
|
| 6 | +""" |
| 7 | +Managed components overriding |
| 8 | +------------------------------------ |
| 9 | +
|
| 10 | +The script: |
| 11 | +- finds all examples on specified path, based on presence of idf_component.yml inside main folder |
| 12 | +- skips CherryUSB example |
| 13 | +- creates idf_component.yml for usb_host_lib example, as it does not have any |
| 14 | +- override all the components defined in the example's idf_component.yml |
| 15 | + with a local paths to the components present in esp-usb |
| 16 | +- it can separately override esp_tinyusb, usb (usb_host_lib) or all class drivers |
| 17 | +
|
| 18 | +Usage Examples: |
| 19 | +--------------- |
| 20 | +
|
| 21 | +To override esp_tinyusb component in usb device examples: |
| 22 | +override_managed_component.py esp_tinyusb device/esp_tinyusb ${IDF_PATH}/examples/peripherals/usb/device/* |
| 23 | +
|
| 24 | +To override usb (usb_host_lib) component in usb host examples: |
| 25 | +override_managed_component.py usb host/usb ${IDF_PATH}/examples/peripherals/usb/host/* |
| 26 | +
|
| 27 | +To override all class drivers in usb host examples: |
| 28 | +override_managed_component.py class host/class ${IDF_PATH}/examples/peripherals/usb/host/* |
| 29 | +""" |
| 30 | + |
6 | 31 | import sys |
7 | 32 | import argparse |
8 | 33 | import yaml |
9 | 34 | from pathlib import Path |
10 | 35 | from glob import glob |
11 | 36 | from idf_component_tools.manager import ManifestManager |
12 | 37 |
|
13 | | -def find_apps_with_manifest(apps): |
| 38 | + |
| 39 | +def _has_manifest(path: Path) -> bool: |
| 40 | + """Check if a given directory contains main/idf_component.yml""" |
| 41 | + return (path / "main" / "idf_component.yml").exists() |
| 42 | + |
| 43 | +def _create_usb_host_lib_manifest(path: Path) -> None: |
| 44 | + """Ensure usb_host_lib has a manifest file""" |
| 45 | + main_dir = path / "main" |
| 46 | + main_dir.mkdir(parents=True, exist_ok=True) |
| 47 | + manifest_file = main_dir / "idf_component.yml" |
| 48 | + if not manifest_file.exists(): |
| 49 | + try: |
| 50 | + print(f"[Info] Creating idf_component.yml for usb_host_lib: {manifest_file}") |
| 51 | + with open(manifest_file, "w", encoding="utf8") as f: |
| 52 | + f.write("dependencies: {}\n") |
| 53 | + except (OSError, PermissionError) as e: |
| 54 | + print(f"[Error] Failed to create manifest {manifest_file}: {e}") |
| 55 | + raise |
| 56 | + |
| 57 | +def find_apps_with_manifest(component, apps): |
14 | 58 | """ |
15 | 59 | Given a list of paths or glob patterns, return a list of directories |
16 | 60 | that contain 'main/idf_component.yml'. Checks one level of subfolders. |
| 61 | + Excludes any path that contains 'CherryUSB'. |
17 | 62 | """ |
18 | 63 | apps_with_glob = [] |
19 | 64 |
|
20 | 65 | for app in apps: |
21 | 66 | # Expand wildcards |
22 | 67 | for match in glob(app): |
| 68 | + if 'cherryusb' in match: |
| 69 | + continue # Skip CHerryUSB examples, path containing 'cherryusb' |
| 70 | + |
23 | 71 | p = Path(match) |
| 72 | + if not p.is_dir(): |
| 73 | + continue |
24 | 74 |
|
25 | | - if p.is_dir(): |
26 | | - # Check main/idf_component.yml directly inside |
27 | | - manifest = p / "main" / "idf_component.yml" |
28 | | - if manifest.exists(): |
29 | | - apps_with_glob.append(str(p)) |
30 | | - continue # already found, no need to check subfolders |
| 75 | + # Check main/idf_component.yml directly inside |
| 76 | + if _has_manifest(p): |
| 77 | + apps_with_glob.append(str(p)) |
| 78 | + continue # already found, no need to check subfolders |
31 | 79 |
|
32 | | - # Check one level of subfolders |
33 | | - for sub in p.iterdir(): |
34 | | - if sub.is_dir(): |
35 | | - manifest = sub / "main" / "idf_component.yml" |
36 | | - if manifest.exists(): |
37 | | - apps_with_glob.append(str(sub)) |
| 80 | + # Check one level of subfolders |
| 81 | + for sub in p.iterdir(): |
| 82 | + if sub.is_dir() and _has_manifest(sub): |
| 83 | + apps_with_glob.append(str(sub)) |
| 84 | + |
| 85 | + # Special handling: create empty manifest for usb_host_lib if missing |
| 86 | + if component == "usb": |
| 87 | + for app in apps: |
| 88 | + for match in glob(app): |
| 89 | + p = Path(match) |
| 90 | + if p.is_dir() and p.name == "usb_host_lib": |
| 91 | + _create_usb_host_lib_manifest(p) |
| 92 | + apps_with_glob.append(str(p)) |
38 | 93 |
|
39 | 94 | return apps_with_glob |
40 | 95 |
|
| 96 | +def _get_class_name(app_path: Path) -> str: |
| 97 | + """Determine class name from example path""" |
| 98 | + known_classes = {"cdc", "hid", "msc", "uvc"} |
| 99 | + parts = app_path.parts |
| 100 | + |
| 101 | + if parts and parts[-1] in known_classes: |
| 102 | + return app_path.parts[-1] |
| 103 | + elif len(parts) >= 2 and parts[-2] in known_classes: |
| 104 | + return app_path.parts[-2] |
| 105 | + else: |
| 106 | + raise ValueError(f"Could not determine class name from path: {app_path}") |
| 107 | + |
| 108 | + |
41 | 109 | def override_with_local_component(component, local_path, app): |
| 110 | + """Override managed component with local component""" |
42 | 111 | app_path = Path(app) |
43 | | - |
44 | 112 | absolute_local_path = Path(local_path).absolute() |
| 113 | + |
45 | 114 | if not absolute_local_path.exists(): |
46 | | - print('[Error] {} path does not exist'.format(local_path)) |
47 | | - raise Exception |
| 115 | + raise FileNotFoundError(f'[Error] {local_path} path does not exist') |
48 | 116 | if not app_path.exists(): |
49 | | - print('[Error] {} path does not exist'.format(app_path)) |
50 | | - raise Exception |
| 117 | + raise FileNotFoundError(f'[Error] {app_path} path does not exist') |
51 | 118 |
|
52 | | - print('[Info] Processing app {}'.format(app)) |
| 119 | + print(f'[Info] Processing app {app}') |
53 | 120 | manager = ManifestManager(app_path / 'main', 'app') |
54 | | - if '/' not in component: |
55 | | - # Prepend with default namespace |
56 | | - component_with_namespace = 'espressif/' + component |
| 121 | + |
| 122 | + # Normalize main component name |
| 123 | + component_with_namespace = component if '/' in component else f'espressif/{component}' |
57 | 124 |
|
58 | 125 | try: |
59 | | - manifest_tree = yaml.safe_load(Path(manager.path).read_text()) |
60 | | - manifest_tree['dependencies'][component_with_namespace] = { |
| 126 | + manifest_tree = yaml.safe_load(Path(manager.path).read_text(encoding='utf8')) |
| 127 | + except yaml.YAMLError as e: |
| 128 | + raise FileNotFoundError(f"[Error] Failed to parse manifest {manager.path}: {e}") from e |
| 129 | + deps = manifest_tree.get('dependencies', {}) |
| 130 | + |
| 131 | + # --- Override esp_tinyusb component in usb device examples --- |
| 132 | + if component == "esp_tinyusb": |
| 133 | + deps[component_with_namespace] = { |
| 134 | + 'version': '*', |
| 135 | + 'override_path': str(absolute_local_path) |
| 136 | + } |
| 137 | + print(f"[Info] Overridden esp_tinyusb {component_with_namespace} -> {absolute_local_path}") |
| 138 | + |
| 139 | + # --- Override usb component in usb host examples --- |
| 140 | + if component == "usb": |
| 141 | + deps[component_with_namespace] = { |
61 | 142 | 'version': '*', |
62 | 143 | 'override_path': str(absolute_local_path) |
63 | 144 | } |
64 | | - with open(manager.path, 'w') as f: |
| 145 | + print(f"[Info] Overridden usb {component_with_namespace} -> {absolute_local_path}") |
| 146 | + |
| 147 | + # --- Override all class drivers components in usb host examples --- |
| 148 | + if component == "class": |
| 149 | + # Determine class name from example path |
| 150 | + class_name = _get_class_name(app_path) |
| 151 | + |
| 152 | + for usb_host_dep in [dep for dep in deps if dep.split("/")[-1].startswith("usb_host_")]: |
| 153 | + short_name = usb_host_dep.split("/")[-1] |
| 154 | + local_subpath = absolute_local_path.parent / "class" / class_name / short_name |
| 155 | + if not local_subpath.exists(): |
| 156 | + raise FileNotFoundError(f"[Error] Local path for {usb_host_dep} not found at {local_subpath}") |
| 157 | + |
| 158 | + usb_host_dep_with_ns = usb_host_dep if '/' in usb_host_dep else f'espressif/{usb_host_dep}' |
| 159 | + |
| 160 | + # Remove old key if it’s the non-namespaced one |
| 161 | + if usb_host_dep_with_ns != usb_host_dep and usb_host_dep in deps: |
| 162 | + deps.pop(usb_host_dep) |
| 163 | + |
| 164 | + deps[usb_host_dep_with_ns] = { |
| 165 | + 'version': '*', |
| 166 | + 'override_path': str(local_subpath) |
| 167 | + } |
| 168 | + print(f"[Info] Overridden class {usb_host_dep} -> {local_subpath}") |
| 169 | + |
| 170 | + # Special override for cdc_acm_vcp examples |
| 171 | + if app_path.name == "cdc_acm_vcp": |
| 172 | + extra_dep = "usb_host_cdc_acm" |
| 173 | + extra_path = absolute_local_path.parent / "class" / class_name / extra_dep |
| 174 | + if not extra_path.exists(): |
| 175 | + raise FileNotFoundError(f"[Error] Local path for {extra_dep} not found at {extra_path}") |
| 176 | + |
| 177 | + extra_dep_with_ns = f'espressif/{extra_dep}' |
| 178 | + deps[extra_dep_with_ns] = { |
| 179 | + 'version': '*', |
| 180 | + 'override_path': str(extra_path) |
| 181 | + } |
| 182 | + print(f"[Info] Overridden extra component {extra_dep} -> {extra_path}") |
| 183 | + |
| 184 | + manifest_tree['dependencies'] = deps |
| 185 | + |
| 186 | + try: |
| 187 | + with open(manager.path, 'w', encoding='utf8') as f: |
65 | 188 | yaml.dump(manifest_tree, f, allow_unicode=True, Dumper=yaml.SafeDumper) |
66 | | - except KeyError: |
67 | | - print('[Error] {} app does not depend on {}'.format(app, component_with_namespace)) |
68 | | - raise KeyError |
| 189 | + except (OSError, PermissionError) as e: |
| 190 | + print(f"[Error] Failed to write manifest {manager.path}: {e}") |
| 191 | + raise |
| 192 | + |
69 | 193 |
|
70 | 194 | def override_with_local_component_all(component, local_path, apps): |
| 195 | + """Find apps and override components""" |
| 196 | + |
71 | 197 | # Process wildcard, e.g. "app_prefix_*" |
72 | | - apps_with_glob = find_apps_with_manifest(apps) |
| 198 | + apps_with_glob = find_apps_with_manifest(component, apps) |
| 199 | + |
| 200 | + print("[Info] Apps found:") |
| 201 | + for app in apps_with_glob: |
| 202 | + print(f"[Info] {app}") |
73 | 203 |
|
74 | 204 | # Go through all collected apps |
75 | 205 | for app in apps_with_glob: |
76 | 206 | try: |
77 | 207 | override_with_local_component(component, local_path, app) |
78 | | - except: |
79 | | - print("[Error] Could not process app {}".format(app)) |
| 208 | + except Exception as e: |
| 209 | + print(f"[Error] Could not process app {app}: {e}") |
80 | 210 | return -1 |
| 211 | + |
| 212 | + print("[Info] Overriding complete") |
81 | 213 | return 0 |
82 | 214 |
|
83 | 215 |
|
|
0 commit comments