sima-cli 2.1.11__tar.gz → 2.1.12__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.
- {sima_cli-2.1.11/sima_cli.egg-info → sima_cli-2.1.12}/PKG-INFO +1 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/pyproject.toml +1 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/__version__.py +1 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/auth/auth0.py +12 -7
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/auth/devportal.py +10 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/cli.py +25 -4
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/compatibility.py +57 -5
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/metadata_installer.py +153 -51
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/metadata_validator.py +40 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/package_builder.py +2 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/registry.py +12 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/commands.py +165 -2
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/install.py +52 -6
- sima_cli-2.1.12/sima_cli/sdk/linux_devkit_network.py +3 -0
- sima_cli-2.1.12/sima_cli/sdk/linux_shared_network.py +1425 -0
- sima_cli-2.1.12/sima_cli/sdk/network_doctor.py +920 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/preinstall.py +47 -18
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/utils.py +12 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/pkg_update_check.py +82 -8
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/vulcan/commands.py +4 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12/sima_cli.egg-info}/PKG-INFO +1 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli.egg-info/SOURCES.txt +5 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_auth.py +9 -3
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_auth0.py +30 -0
- sima_cli-2.1.12/tests/unit/test_generate_cli_markdown_docs.py +90 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_metadata_installer.py +159 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_metadata_validator.py +38 -1
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_package_builder.py +9 -2
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_pkg_update_check.py +63 -7
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_sdk_image_detection.py +256 -11
- sima_cli-2.1.12/tests/unit/test_sdk_linux_shared_network.py +402 -0
- sima_cli-2.1.12/tests/unit/test_sdk_network_doctor.py +282 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_sdk_preinstall.py +73 -3
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_vulcan.py +114 -0
- sima_cli-2.1.11/sima_cli/sdk/linux_shared_network.py +0 -579
- {sima_cli-2.1.11 → sima_cli-2.1.12}/LICENSE +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/MANIFEST.in +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/README.md +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/requirements.txt +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/setup.cfg +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/setup.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/__main__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/app_zoo/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/app_zoo/app.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/app_zoo/commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/auth/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/auth/login.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/data/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/data/resources_internal.yaml +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/data/resources_public.yaml +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/deploy_only/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/deploy_only/device/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/deploy_only/device/commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/deploy_only/mpk/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/deploy_only/mpk/commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/discover/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/discover/discover.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/discover/linuxll.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/download/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/download/downloader.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/github_assets.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/hostdriver.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/metadata_info.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/optiview.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/install/palette.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/mla/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/mla/meminfo.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/model_zoo/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/model_zoo/model.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/network/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/network/network.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/playbooks/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/playbooks/commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/playbooks/manager.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/cmdexec.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/config.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/neat.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/requirements.json +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/script.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/stop.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/sdk/uninstall.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/serial/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/serial/serial.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/storage/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/storage/nvme.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/storage/sdcard.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/bmaptool.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/bootimg.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/cleanlog.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/elxr.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/local.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/netboot.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/query.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/remote.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/update/updater.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/upgrade/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/upgrade/selfupdate.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/api_common.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/artifactory.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/common.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/config.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/config_loader.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/container_registries.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/deprecation.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/device_api.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/disk.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/docker.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/env.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/errors.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/mpk_api.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/net.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/network.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/pcie.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/serializers.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/services.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/utils/tag.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/vulcan/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli/vulcan/artifacts.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli.egg-info/dependency_links.txt +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli.egg-info/entry_points.txt +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli.egg-info/requires.txt +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/sima_cli.egg-info/top_level.txt +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/e2e/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/__init__.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_app_zoo.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_appzoo_commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_cli.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_cli_stdio.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_cli_update.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_cli_update_rerun.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_deprecation.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_docker_utils.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_download.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_elxr_update.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_firmware.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_install_stub.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_model_zoo.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_netboot.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_sdk_uninstall.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_selfupdate.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_skills_commands.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_skills_manager.py +0 -0
- {sima_cli-2.1.11 → sima_cli-2.1.12}/tests/unit/test_utils.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# sima_cli/__version__.py
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.12"
|
|
@@ -286,7 +286,7 @@ def request_device_code(auth_cfg):
|
|
|
286
286
|
|
|
287
287
|
def poll_for_token(auth_cfg, device_code, interval):
|
|
288
288
|
"""Step 2: Poll for user authorization."""
|
|
289
|
-
print(f"⏳ Waiting for
|
|
289
|
+
print(f"⏳ Waiting for authorization. Complete the browser login above; checking every {interval} seconds.")
|
|
290
290
|
while True:
|
|
291
291
|
time.sleep(interval)
|
|
292
292
|
resp = requests.post(
|
|
@@ -333,16 +333,21 @@ def login_auth0(auth_cfg):
|
|
|
333
333
|
highlighted_url = click.style(verify_complete, fg="cyan", bold=True)
|
|
334
334
|
highlighted_code = click.style(user_code, fg="yellow", bold=True)
|
|
335
335
|
|
|
336
|
-
print(
|
|
336
|
+
print("🔐 sima-cli needs you to authorize this device login.")
|
|
337
|
+
print(f"⏰ Login link/code expires in {expires_in} minutes.\n")
|
|
337
338
|
|
|
338
339
|
# Auto-open browser if possible
|
|
339
|
-
if is_browser_available() and verify_complete:
|
|
340
|
+
if is_browser_available() and verify_complete and webbrowser.open(verify_complete):
|
|
340
341
|
print(f"🌐 Opening browser for login → {highlighted_url}")
|
|
341
|
-
|
|
342
|
+
print("After signing in, return to this terminal. sima-cli will continue automatically.")
|
|
342
343
|
else:
|
|
343
|
-
print("
|
|
344
|
-
print(
|
|
345
|
-
print(
|
|
344
|
+
print("⚠️ A browser could not be opened from this environment.")
|
|
345
|
+
print("Action required:")
|
|
346
|
+
print(" 1. Open this URL in a browser on your workstation:")
|
|
347
|
+
print(f" {highlighted_url}")
|
|
348
|
+
print(" 2. Sign in with your SiMa Developer Portal account.")
|
|
349
|
+
print(f" 3. If prompted, confirm this code: {highlighted_code}")
|
|
350
|
+
print(" 4. Return to this terminal. sima-cli will continue automatically after authorization.")
|
|
346
351
|
|
|
347
352
|
return poll_for_token(auth_cfg, data["device_code"], data["interval"])
|
|
348
353
|
|
|
@@ -69,8 +69,17 @@ HEADERS = {
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def _prompt_manual_developer_portal_login(confirm_completion: bool = False) -> bool:
|
|
72
|
+
next_step = (
|
|
73
|
+
"After signing in and accepting the EULA, return here and confirm when prompted."
|
|
74
|
+
if confirm_completion
|
|
75
|
+
else "After signing in and accepting the EULA, rerun the command that required authentication."
|
|
76
|
+
)
|
|
72
77
|
click.secho(
|
|
73
|
-
|
|
78
|
+
"\nA browser could not be opened from this environment.\n"
|
|
79
|
+
"Open the following URL in a browser on your workstation to authenticate "
|
|
80
|
+
"with the SiMa Developer Portal and accept the EULA if prompted:\n\n"
|
|
81
|
+
f"{DEV_PORTAL_LOGIN_URL}\n\n"
|
|
82
|
+
f"{next_step}\n",
|
|
74
83
|
fg="green",
|
|
75
84
|
)
|
|
76
85
|
if confirm_completion:
|
|
@@ -579,7 +579,7 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
|
|
|
579
579
|
default=".",
|
|
580
580
|
show_default=True,
|
|
581
581
|
type=click.Path(file_okay=False, dir_okay=True, path_type=str),
|
|
582
|
-
help="Directory where
|
|
582
|
+
help="Directory where package resources are downloaded and installed.",
|
|
583
583
|
)
|
|
584
584
|
@click.option("--json", "json_output", is_flag=True, help="With --neat or --vulcan, print resolved metadata URL and exit.")
|
|
585
585
|
@click.option(
|
|
@@ -655,6 +655,7 @@ def install_cmd(
|
|
|
655
655
|
install_dir=install_dir,
|
|
656
656
|
force=force,
|
|
657
657
|
json_output=json_output,
|
|
658
|
+
command_name="sima-cli install",
|
|
658
659
|
)
|
|
659
660
|
return None
|
|
660
661
|
|
|
@@ -663,7 +664,13 @@ def install_cmd(
|
|
|
663
664
|
if component:
|
|
664
665
|
click.echo(f"⚠️ Component '{component}' is ignored when using --metadata. Proceeding with metadata-based installation.")
|
|
665
666
|
click.echo(f"🔧 Installing generic component from metadata URL: {mirror}")
|
|
666
|
-
install_from_metadata(
|
|
667
|
+
install_from_metadata(
|
|
668
|
+
metadata_url=mirror,
|
|
669
|
+
internal=internal,
|
|
670
|
+
install_dir=install_dir,
|
|
671
|
+
force=force,
|
|
672
|
+
command_name="sima-cli install",
|
|
673
|
+
)
|
|
667
674
|
return None
|
|
668
675
|
|
|
669
676
|
# No component and no metadata: error
|
|
@@ -673,7 +680,13 @@ def install_cmd(
|
|
|
673
680
|
|
|
674
681
|
# if user specified gh: as component, treat it the same as -m
|
|
675
682
|
if component.startswith("gh:"):
|
|
676
|
-
install_from_metadata(
|
|
683
|
+
install_from_metadata(
|
|
684
|
+
metadata_url=component,
|
|
685
|
+
internal=False,
|
|
686
|
+
install_dir=install_dir,
|
|
687
|
+
force=force,
|
|
688
|
+
command_name="sima-cli install",
|
|
689
|
+
)
|
|
677
690
|
return None
|
|
678
691
|
|
|
679
692
|
# if the user specified cr: or ghcr: as component, install from container registry
|
|
@@ -707,8 +720,16 @@ def install_cmd(
|
|
|
707
720
|
try:
|
|
708
721
|
metadata_url = metadata_resolver(component, version, tag)
|
|
709
722
|
click.echo(f"🔧 Installing '{component}' from resolved metadata: {metadata_url}")
|
|
710
|
-
if install_from_metadata(
|
|
723
|
+
if install_from_metadata(
|
|
724
|
+
metadata_url=metadata_url,
|
|
725
|
+
internal=internal,
|
|
726
|
+
install_dir=install_dir,
|
|
727
|
+
force=force,
|
|
728
|
+
command_name="sima-cli install",
|
|
729
|
+
):
|
|
711
730
|
click.echo("✅ Installation complete.")
|
|
731
|
+
except click.ClickException:
|
|
732
|
+
raise
|
|
712
733
|
except Exception as e:
|
|
713
734
|
click.echo(f"❌ Failed to resolve metadata for component '{component}': {e}")
|
|
714
735
|
ctx.exit(1)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import platform
|
|
1
2
|
import re
|
|
2
3
|
from typing import Dict, List, Optional, Sequence
|
|
3
4
|
|
|
@@ -51,6 +52,33 @@ def validate_version_spec(spec: str) -> None:
|
|
|
51
52
|
normalize_version_spec(spec)
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
def normalize_host_arch(arch: str) -> str:
|
|
56
|
+
if not isinstance(arch, str) or not arch.strip():
|
|
57
|
+
raise ValueError("host architecture must be a non-empty string")
|
|
58
|
+
normalized = arch.strip().lower()
|
|
59
|
+
aliases = {
|
|
60
|
+
"x86_64": "amd64",
|
|
61
|
+
"amd64": "amd64",
|
|
62
|
+
"aarch64": "arm64",
|
|
63
|
+
"arm64": "arm64",
|
|
64
|
+
}
|
|
65
|
+
if normalized not in aliases:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"invalid host architecture '{}'; supported values are amd64, arm64, x86_64, aarch64".format(
|
|
68
|
+
arch
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
return aliases[normalized]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def current_host_arch() -> str:
|
|
75
|
+
machine = platform.machine().lower()
|
|
76
|
+
try:
|
|
77
|
+
return normalize_host_arch(machine)
|
|
78
|
+
except ValueError:
|
|
79
|
+
return machine
|
|
80
|
+
|
|
81
|
+
|
|
54
82
|
def normalize_exact_version(version: str) -> str:
|
|
55
83
|
if not isinstance(version, str) or not version.strip():
|
|
56
84
|
raise ValueError("version must be a non-empty string")
|
|
@@ -92,25 +120,48 @@ def version_matches(version: str, spec: str) -> bool:
|
|
|
92
120
|
return True
|
|
93
121
|
|
|
94
122
|
|
|
95
|
-
def parse_host_platform_specs(
|
|
123
|
+
def parse_host_platform_specs(
|
|
124
|
+
host_platforms: Optional[Sequence[str]],
|
|
125
|
+
host_arches: Optional[Sequence[str]] = None,
|
|
126
|
+
) -> List[Dict]:
|
|
127
|
+
normalized_arches = []
|
|
128
|
+
seen_arches = set()
|
|
129
|
+
for raw_arch in host_arches or []:
|
|
130
|
+
for value in _split_csv(raw_arch):
|
|
131
|
+
arch = normalize_host_arch(value)
|
|
132
|
+
if arch not in seen_arches:
|
|
133
|
+
seen_arches.add(arch)
|
|
134
|
+
normalized_arches.append(arch)
|
|
135
|
+
|
|
96
136
|
platforms = []
|
|
97
137
|
for raw_spec in host_platforms or []:
|
|
98
138
|
os_values = []
|
|
139
|
+
versions = {}
|
|
99
140
|
seen = set()
|
|
100
141
|
for value in _split_csv(raw_spec):
|
|
101
|
-
|
|
142
|
+
raw_os, separator, raw_version_spec = value.partition("@")
|
|
143
|
+
os_value = raw_os.lower()
|
|
102
144
|
if os_value not in VALID_OS:
|
|
103
145
|
raise ValueError(
|
|
104
146
|
"invalid host platform OS '{}'; supported values are {}".format(
|
|
105
|
-
|
|
147
|
+
raw_os, ", ".join(sorted(VALID_OS))
|
|
106
148
|
)
|
|
107
149
|
)
|
|
108
150
|
if os_value not in seen:
|
|
109
151
|
seen.add(os_value)
|
|
110
152
|
os_values.append(os_value)
|
|
153
|
+
if separator:
|
|
154
|
+
versions.setdefault(os_value, []).append(normalize_version_spec(raw_version_spec))
|
|
111
155
|
if not os_values:
|
|
112
156
|
raise ValueError("host platform spec must include at least one OS")
|
|
113
|
-
|
|
157
|
+
platform_spec = {"type": "host", "os": os_values}
|
|
158
|
+
if versions:
|
|
159
|
+
platform_spec["versions"] = versions
|
|
160
|
+
if normalized_arches:
|
|
161
|
+
platform_spec["arch"] = list(normalized_arches)
|
|
162
|
+
platforms.append(platform_spec)
|
|
163
|
+
if normalized_arches and not platforms:
|
|
164
|
+
raise ValueError("--host-arch requires at least one --host-platform entry")
|
|
114
165
|
return platforms
|
|
115
166
|
|
|
116
167
|
|
|
@@ -147,11 +198,12 @@ def parse_board_platform_specs(board_platforms: Optional[Sequence[str]]) -> List
|
|
|
147
198
|
|
|
148
199
|
def build_platform_specs(
|
|
149
200
|
host_platforms: Optional[Sequence[str]] = None,
|
|
201
|
+
host_arches: Optional[Sequence[str]] = None,
|
|
150
202
|
board_platforms: Optional[Sequence[str]] = None,
|
|
151
203
|
palette_platform: Optional[str] = None,
|
|
152
204
|
) -> List[Dict]:
|
|
153
205
|
platforms = []
|
|
154
|
-
platforms.extend(parse_host_platform_specs(host_platforms))
|
|
206
|
+
platforms.extend(parse_host_platform_specs(host_platforms, host_arches=host_arches))
|
|
155
207
|
platforms.extend(parse_board_platform_specs(board_platforms))
|
|
156
208
|
if palette_platform is not None:
|
|
157
209
|
platform = {"type": "palette"}
|
|
@@ -20,6 +20,7 @@ import requests
|
|
|
20
20
|
|
|
21
21
|
from rich.console import Console
|
|
22
22
|
from rich.panel import Panel
|
|
23
|
+
from rich.text import Text
|
|
23
24
|
from typing import Tuple
|
|
24
25
|
|
|
25
26
|
from huggingface_hub import snapshot_download
|
|
@@ -28,7 +29,7 @@ from sima_cli.utils.disk import check_disk_space
|
|
|
28
29
|
from sima_cli.utils.env import get_environment_type, get_exact_devkit_type, get_sima_build_version
|
|
29
30
|
from sima_cli.download.downloader import download_file_from_url
|
|
30
31
|
from sima_cli.install.metadata_validator import validate_metadata, MetadataValidationError
|
|
31
|
-
from sima_cli.install.compatibility import version_matches
|
|
32
|
+
from sima_cli.install.compatibility import current_host_arch, normalize_host_arch, version_matches
|
|
32
33
|
from sima_cli.install.metadata_info import print_metadata_summary, parse_size_string_to_bytes
|
|
33
34
|
from sima_cli.utils.container_registries import install_from_cr
|
|
34
35
|
from sima_cli.install.registry import PackageRegistry
|
|
@@ -36,6 +37,52 @@ from sima_cli.install.registry import PackageRegistry
|
|
|
36
37
|
console = Console()
|
|
37
38
|
registry = PackageRegistry()
|
|
38
39
|
|
|
40
|
+
class InstallationPreflightError(click.ClickException):
|
|
41
|
+
def show(self, file=None) -> None:
|
|
42
|
+
console.print(
|
|
43
|
+
Panel(
|
|
44
|
+
Text(str(self.message), style="yellow"),
|
|
45
|
+
title="Installation Failed",
|
|
46
|
+
border_style="yellow",
|
|
47
|
+
expand=False,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ensure_install_dir_writable(install_dir: str, command_name: str = "sima-cli install") -> None:
|
|
53
|
+
target = Path(install_dir or ".").expanduser()
|
|
54
|
+
display_path = Path.cwd() if str(target) == "." else target
|
|
55
|
+
|
|
56
|
+
check_dir = target
|
|
57
|
+
if not check_dir.exists():
|
|
58
|
+
check_dir = target.parent
|
|
59
|
+
while not check_dir.exists() and check_dir != check_dir.parent:
|
|
60
|
+
check_dir = check_dir.parent
|
|
61
|
+
|
|
62
|
+
if not check_dir.is_dir():
|
|
63
|
+
raise click.ClickException(f"Install path '{display_path}' is not a directory.")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
with tempfile.NamedTemporaryFile(prefix=".sima-cli-write-test-", dir=str(check_dir)):
|
|
67
|
+
pass
|
|
68
|
+
except OSError as exc:
|
|
69
|
+
if str(target) == ".":
|
|
70
|
+
raise InstallationPreflightError(
|
|
71
|
+
f"Current directory '{Path.cwd()}' is not writable.\n\n"
|
|
72
|
+
"This install downloads package assets into the current directory before installation.\n\n"
|
|
73
|
+
"Run the command again from a writable work directory, for example:\n"
|
|
74
|
+
" mkdir -p ~/sima-install && cd ~/sima-install\n"
|
|
75
|
+
f" {command_name} ...\n\n"
|
|
76
|
+
"Or choose a destination explicitly:\n"
|
|
77
|
+
f" {command_name} ... --install-dir <writable-directory>"
|
|
78
|
+
) from exc
|
|
79
|
+
raise InstallationPreflightError(
|
|
80
|
+
f"Install directory '{display_path}' is not writable.\n\n"
|
|
81
|
+
"This install downloads package assets into the install directory before installation.\n\n"
|
|
82
|
+
"Choose a writable destination and rerun the command:\n"
|
|
83
|
+
f" {command_name} ... --install-dir <writable-directory>"
|
|
84
|
+
) from exc
|
|
85
|
+
|
|
39
86
|
def _copy_dir(src: Path, dest: Path, label: str):
|
|
40
87
|
"""
|
|
41
88
|
Copy files from src → dest, merging with existing files (no deletion).
|
|
@@ -1065,23 +1112,26 @@ def _print_compatible_platforms(platforms):
|
|
|
1065
1112
|
table.add_column("Platform Type", style="bold cyan", no_wrap=True)
|
|
1066
1113
|
table.add_column("Details", style="bold yellow")
|
|
1067
1114
|
table.add_column("Supported Versions / Targets", style="white")
|
|
1115
|
+
table.add_column("Arch", style="white")
|
|
1068
1116
|
|
|
1069
1117
|
for p in platforms:
|
|
1070
1118
|
ptype = p.get("type", "N/A")
|
|
1071
1119
|
|
|
1072
1120
|
if ptype == "host":
|
|
1121
|
+
arch = ", ".join(p.get("arch") or ["All"])
|
|
1122
|
+
versions_by_os = {str(k).lower(): v for k, v in p.get("versions", {}).items()}
|
|
1073
1123
|
for os_name in p.get("os", []):
|
|
1074
|
-
versions =
|
|
1075
|
-
table.add_row(ptype, os_name.capitalize(), ", ".join(versions))
|
|
1124
|
+
versions = versions_by_os.get(str(os_name).lower(), ["All"])
|
|
1125
|
+
table.add_row(ptype, os_name.capitalize(), ", ".join(versions), arch)
|
|
1076
1126
|
|
|
1077
1127
|
elif ptype == "board":
|
|
1078
1128
|
compat = p.get("compatible_with", [])
|
|
1079
1129
|
version = p.get("version", "All")
|
|
1080
1130
|
compat_text = ", ".join(compat) if compat else "N/A"
|
|
1081
|
-
table.add_row(ptype, compat_text, version)
|
|
1131
|
+
table.add_row(ptype, compat_text, version, "N/A")
|
|
1082
1132
|
|
|
1083
1133
|
else:
|
|
1084
|
-
table.add_row(ptype, "N/A", "N/A")
|
|
1134
|
+
table.add_row(ptype, "N/A", "N/A", "N/A")
|
|
1085
1135
|
|
|
1086
1136
|
console.print(table)
|
|
1087
1137
|
console.print()
|
|
@@ -1091,8 +1141,14 @@ def _compare_versions(current: str, condition: str) -> bool:
|
|
|
1091
1141
|
Compare current version (e.g. '15.5') against a condition string like:
|
|
1092
1142
|
'>=12', '<=16', '>20.04', '<23.0', '14'
|
|
1093
1143
|
"""
|
|
1094
|
-
cur = _version_to_tuple(current)
|
|
1095
1144
|
cond = condition.strip()
|
|
1145
|
+
if re.match(r"^(>=|<=|==|=|>|<)", cond):
|
|
1146
|
+
try:
|
|
1147
|
+
return version_matches(current, cond)
|
|
1148
|
+
except ValueError:
|
|
1149
|
+
pass
|
|
1150
|
+
|
|
1151
|
+
cur = _version_to_tuple(current)
|
|
1096
1152
|
|
|
1097
1153
|
# Detect operator and target
|
|
1098
1154
|
match = re.match(r"^(>=|<=|>|<|=)?\s*([\d.]+)$", cond)
|
|
@@ -1128,6 +1184,73 @@ def _get_palette_sdk_version(release_file: Path = Path("/etc/sdk-release")) -> s
|
|
|
1128
1184
|
return match.group(1).split("_", 1)[0].strip()
|
|
1129
1185
|
|
|
1130
1186
|
|
|
1187
|
+
def _detected_host_platform() -> tuple:
|
|
1188
|
+
os_name = platform.system().lower()
|
|
1189
|
+
os_version = "Unknown"
|
|
1190
|
+
|
|
1191
|
+
if os_name == "darwin":
|
|
1192
|
+
return "mac", platform.mac_ver()[0] or "Unknown", current_host_arch()
|
|
1193
|
+
if os_name == "windows":
|
|
1194
|
+
return "windows", platform.release() or "Unknown", current_host_arch()
|
|
1195
|
+
if os_name != "linux":
|
|
1196
|
+
return os_name, os_version, current_host_arch()
|
|
1197
|
+
|
|
1198
|
+
os_release = {}
|
|
1199
|
+
try:
|
|
1200
|
+
with open("/etc/os-release", encoding="utf-8") as f:
|
|
1201
|
+
for line in f:
|
|
1202
|
+
key, separator, value = line.strip().partition("=")
|
|
1203
|
+
if separator:
|
|
1204
|
+
os_release[key] = value.strip().strip('"')
|
|
1205
|
+
except OSError:
|
|
1206
|
+
os_release = {}
|
|
1207
|
+
|
|
1208
|
+
distro_id = (os_release.get("ID") or "").lower()
|
|
1209
|
+
version_id = os_release.get("VERSION_ID") or ""
|
|
1210
|
+
if distro_id == "ubuntu":
|
|
1211
|
+
os_name = "ubuntu"
|
|
1212
|
+
os_version = version_id or "Unknown"
|
|
1213
|
+
else:
|
|
1214
|
+
os_name = "linux"
|
|
1215
|
+
os_version = version_id or "Unknown"
|
|
1216
|
+
|
|
1217
|
+
if os_version == "Unknown":
|
|
1218
|
+
try:
|
|
1219
|
+
out = subprocess.check_output(["lsb_release", "-ds"], text=True).strip().lower()
|
|
1220
|
+
if "ubuntu" in out:
|
|
1221
|
+
os_name = "ubuntu"
|
|
1222
|
+
match = re.search(r"(\d+\.\d+|\d+)", out)
|
|
1223
|
+
os_version = match.group(1) if match else "Unknown"
|
|
1224
|
+
except Exception:
|
|
1225
|
+
pass
|
|
1226
|
+
|
|
1227
|
+
match = re.search(r"(\d+\.\d+|\d+)", os_version)
|
|
1228
|
+
if match:
|
|
1229
|
+
os_version = match.group(1)
|
|
1230
|
+
return os_name, os_version, current_host_arch()
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
def _host_os_matches(detected_os: str, supported_oses: List[str]) -> bool:
|
|
1234
|
+
if detected_os in supported_oses:
|
|
1235
|
+
return True
|
|
1236
|
+
return detected_os == "ubuntu" and "linux" in supported_oses
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def _host_versions_for_os(platform_entry: dict, detected_os: str) -> list:
|
|
1240
|
+
versions_dict = {str(k).lower(): v for k, v in platform_entry.get("versions", {}).items()}
|
|
1241
|
+
return versions_dict.get(detected_os) or (versions_dict.get("linux") if detected_os == "ubuntu" else []) or []
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def _host_arch_matches(platform_entry: dict, detected_arch: str) -> bool:
|
|
1245
|
+
supported_arches = []
|
|
1246
|
+
for value in platform_entry.get("arch", []):
|
|
1247
|
+
try:
|
|
1248
|
+
supported_arches.append(normalize_host_arch(value))
|
|
1249
|
+
except ValueError:
|
|
1250
|
+
supported_arches.append(str(value).lower())
|
|
1251
|
+
return not supported_arches or detected_arch in supported_arches
|
|
1252
|
+
|
|
1253
|
+
|
|
1131
1254
|
def _is_platform_compatible(metadata: dict, force: bool = False) -> bool:
|
|
1132
1255
|
"""
|
|
1133
1256
|
Determines if the current environment is compatible with the package metadata.
|
|
@@ -1142,40 +1265,7 @@ def _is_platform_compatible(metadata: dict, force: bool = False) -> bool:
|
|
|
1142
1265
|
click.echo("ℹ️ No platform restrictions specified; treating package as compatible with all platforms.")
|
|
1143
1266
|
return True
|
|
1144
1267
|
|
|
1145
|
-
|
|
1146
|
-
os_name = platform.system().lower() # 'darwin', 'windows', 'linux'
|
|
1147
|
-
os_version = "Unknown"
|
|
1148
|
-
|
|
1149
|
-
if os_name == "darwin":
|
|
1150
|
-
os_name = "mac"
|
|
1151
|
-
os_version = platform.mac_ver()[0] or "Unknown"
|
|
1152
|
-
elif os_name == "windows":
|
|
1153
|
-
os_version = platform.release()
|
|
1154
|
-
elif os_name == "linux":
|
|
1155
|
-
try:
|
|
1156
|
-
out = subprocess.check_output(["lsb_release", "-ds"], text=True).strip().lower()
|
|
1157
|
-
if "ubuntu" in out:
|
|
1158
|
-
os_name = "ubuntu"
|
|
1159
|
-
match = re.search(r"(\d+\.\d+)", out)
|
|
1160
|
-
os_version = match.group(1) if match else "Unknown"
|
|
1161
|
-
else:
|
|
1162
|
-
os_name = "linux"
|
|
1163
|
-
match = re.search(r"(\d+\.\d+)", out)
|
|
1164
|
-
os_version = match.group(1) if match else "Unknown"
|
|
1165
|
-
except Exception:
|
|
1166
|
-
os_name = "linux"
|
|
1167
|
-
try:
|
|
1168
|
-
with open("/etc/os-release") as f:
|
|
1169
|
-
for line in f:
|
|
1170
|
-
if line.startswith("VERSION_ID"):
|
|
1171
|
-
os_version = line.split("=")[1].strip().strip('"')
|
|
1172
|
-
break
|
|
1173
|
-
except FileNotFoundError:
|
|
1174
|
-
os_version = "Unknown"
|
|
1175
|
-
|
|
1176
|
-
match = re.search(r"(\d+\.\d+|\d+)", os_version)
|
|
1177
|
-
if match:
|
|
1178
|
-
os_version = match.group(1)
|
|
1268
|
+
os_name, os_version, host_arch = _detected_host_platform()
|
|
1179
1269
|
|
|
1180
1270
|
# ──────────────────────────────────────────────
|
|
1181
1271
|
# Compatibility checks
|
|
@@ -1214,16 +1304,19 @@ def _is_platform_compatible(metadata: dict, force: bool = False) -> bool:
|
|
|
1214
1304
|
|
|
1215
1305
|
# 2️⃣ OS match (mac, ubuntu, linux, windows)
|
|
1216
1306
|
supported_oses = [o.lower() for o in platform_entry.get("os", [])]
|
|
1217
|
-
if
|
|
1218
|
-
|
|
1219
|
-
continue
|
|
1307
|
+
if not _host_os_matches(os_name, supported_oses):
|
|
1308
|
+
continue
|
|
1220
1309
|
|
|
1221
|
-
# 3️⃣
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1310
|
+
# 3️⃣ Architecture match
|
|
1311
|
+
if not _host_arch_matches(platform_entry, host_arch):
|
|
1312
|
+
click.echo(
|
|
1313
|
+
f"❌ Host architecture {host_arch or 'unknown'} is not supported. "
|
|
1314
|
+
f"Allowed: {platform_entry.get('arch')}"
|
|
1315
|
+
)
|
|
1316
|
+
continue
|
|
1317
|
+
|
|
1318
|
+
# 4️⃣ Version match
|
|
1319
|
+
supported_versions = _host_versions_for_os(platform_entry, os_name)
|
|
1227
1320
|
|
|
1228
1321
|
if supported_versions:
|
|
1229
1322
|
ok = False
|
|
@@ -1248,11 +1341,12 @@ def _is_platform_compatible(metadata: dict, force: bool = False) -> bool:
|
|
|
1248
1341
|
|
|
1249
1342
|
click.secho(
|
|
1250
1343
|
f"❌ Current environment [{env_type}:{env_subtype}] "
|
|
1251
|
-
f"({os_name} {os_version}) is not compatible with this package.", fg='red'
|
|
1344
|
+
f"({os_name} {os_version} {host_arch}) is not compatible with this package.", fg='red'
|
|
1252
1345
|
)
|
|
1253
1346
|
_print_compatible_platforms(platforms)
|
|
1254
1347
|
if not force:
|
|
1255
1348
|
exit(1)
|
|
1349
|
+
return False
|
|
1256
1350
|
|
|
1257
1351
|
|
|
1258
1352
|
def _print_post_install_message(metadata: Dict):
|
|
@@ -1413,7 +1507,15 @@ def _resolve_github_metadata_url(gh_ref: str) -> Tuple[str, str]:
|
|
|
1413
1507
|
except Exception as e:
|
|
1414
1508
|
raise RuntimeError(f"Failed to resolve GitHub metadata URL {gh_ref}: {e}")
|
|
1415
1509
|
|
|
1416
|
-
def install_from_metadata(
|
|
1510
|
+
def install_from_metadata(
|
|
1511
|
+
metadata_url: str,
|
|
1512
|
+
internal: bool,
|
|
1513
|
+
install_dir: str = '.',
|
|
1514
|
+
force: bool = False,
|
|
1515
|
+
command_name: str = "sima-cli install",
|
|
1516
|
+
):
|
|
1517
|
+
_ensure_install_dir_writable(install_dir, command_name=command_name)
|
|
1518
|
+
|
|
1417
1519
|
try:
|
|
1418
1520
|
tag = None
|
|
1419
1521
|
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from sima_cli.install.compatibility import (
|
|
7
7
|
VALID_OS,
|
|
8
8
|
VALID_PLATFORM_TYPES,
|
|
9
|
+
normalize_host_arch,
|
|
9
10
|
normalize_exact_version,
|
|
10
11
|
validate_version_spec,
|
|
11
12
|
)
|
|
@@ -85,6 +86,45 @@ def validate_metadata(data: dict):
|
|
|
85
86
|
f"Invalid OS '{os_value}' in platform entry {i}. Supported: {VALID_OS}"
|
|
86
87
|
)
|
|
87
88
|
|
|
89
|
+
if "versions" in platform:
|
|
90
|
+
versions = platform["versions"]
|
|
91
|
+
if not isinstance(versions, dict):
|
|
92
|
+
raise MetadataValidationError(f"'versions' must be an object for host in entry {i}")
|
|
93
|
+
for os_key, specs in versions.items():
|
|
94
|
+
if not isinstance(os_key, str) or os_key.lower() not in VALID_OS:
|
|
95
|
+
raise MetadataValidationError(
|
|
96
|
+
f"Invalid OS '{os_key}' in host versions for platform entry {i}. Supported: {VALID_OS}"
|
|
97
|
+
)
|
|
98
|
+
if os_key.lower() not in [str(value).lower() for value in platform["os"]]:
|
|
99
|
+
raise MetadataValidationError(
|
|
100
|
+
f"Host versions key '{os_key}' must also be listed in 'os' for platform entry {i}"
|
|
101
|
+
)
|
|
102
|
+
if not isinstance(specs, list) or not specs:
|
|
103
|
+
raise MetadataValidationError(
|
|
104
|
+
f"'versions.{os_key}' must be a non-empty list in platform entry {i}"
|
|
105
|
+
)
|
|
106
|
+
for spec in specs:
|
|
107
|
+
if not isinstance(spec, str):
|
|
108
|
+
raise MetadataValidationError(
|
|
109
|
+
f"'versions.{os_key}' values must be strings in platform entry {i}"
|
|
110
|
+
)
|
|
111
|
+
try:
|
|
112
|
+
validate_version_spec(spec)
|
|
113
|
+
except ValueError as exc:
|
|
114
|
+
raise MetadataValidationError(
|
|
115
|
+
f"Invalid host version spec for '{os_key}' in entry {i}: {exc}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if "arch" in platform:
|
|
119
|
+
arches = platform["arch"]
|
|
120
|
+
if not isinstance(arches, list) or not arches:
|
|
121
|
+
raise MetadataValidationError(f"'arch' must be a non-empty list for host in entry {i}")
|
|
122
|
+
for arch in arches:
|
|
123
|
+
try:
|
|
124
|
+
normalize_host_arch(arch)
|
|
125
|
+
except ValueError as exc:
|
|
126
|
+
raise MetadataValidationError(f"Invalid host architecture in entry {i}: {exc}")
|
|
127
|
+
|
|
88
128
|
if platform["type"] == "palette" and "version" in platform:
|
|
89
129
|
if not isinstance(platform["version"], str):
|
|
90
130
|
raise MetadataValidationError(f"'version' must be a string for palette in entry {i}")
|
|
@@ -246,6 +246,7 @@ def build_metadata(
|
|
|
246
246
|
exclude: Optional[Sequence[str]] = None,
|
|
247
247
|
download_compatible_files_only: bool = False,
|
|
248
248
|
host_platforms: Optional[Sequence[str]] = None,
|
|
249
|
+
host_arches: Optional[Sequence[str]] = None,
|
|
249
250
|
board_platforms: Optional[Sequence[str]] = None,
|
|
250
251
|
palette_platform: Optional[str] = None,
|
|
251
252
|
) -> Dict:
|
|
@@ -279,6 +280,7 @@ def build_metadata(
|
|
|
279
280
|
"description": resolved_description or "",
|
|
280
281
|
"platforms": build_platform_specs(
|
|
281
282
|
host_platforms=host_platforms,
|
|
283
|
+
host_arches=host_arches,
|
|
282
284
|
board_platforms=board_platforms,
|
|
283
285
|
palette_platform=palette_platform,
|
|
284
286
|
),
|
|
@@ -135,10 +135,19 @@ def show_metadata(name, version):
|
|
|
135
135
|
"--host-platform",
|
|
136
136
|
multiple=True,
|
|
137
137
|
help=(
|
|
138
|
-
"Host OS compatibility as
|
|
138
|
+
"Host OS compatibility as OS[,OS...][@VERSION_SPEC], for example "
|
|
139
|
+
"linux, ubuntu@==24.04, or ubuntu@>=22.04,<=24.04. Supported OS values: "
|
|
139
140
|
"linux, ubuntu, mac, windows. May be repeated."
|
|
140
141
|
),
|
|
141
142
|
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--host-arch",
|
|
145
|
+
multiple=True,
|
|
146
|
+
help=(
|
|
147
|
+
"Host CPU architecture compatibility as ARCH[,ARCH...], for example amd64 or arm64. "
|
|
148
|
+
"Aliases x86_64 and aarch64 are normalized. Requires --host-platform."
|
|
149
|
+
),
|
|
150
|
+
)
|
|
142
151
|
@click.option(
|
|
143
152
|
"--board-platform",
|
|
144
153
|
multiple=True,
|
|
@@ -168,6 +177,7 @@ def build_package_metadata(
|
|
|
168
177
|
variant,
|
|
169
178
|
download_compatible_files_only,
|
|
170
179
|
host_platform,
|
|
180
|
+
host_arch,
|
|
171
181
|
board_platform,
|
|
172
182
|
palette_platform,
|
|
173
183
|
):
|
|
@@ -185,6 +195,7 @@ def build_package_metadata(
|
|
|
185
195
|
exclude=exclude,
|
|
186
196
|
download_compatible_files_only=download_compatible_files_only,
|
|
187
197
|
host_platforms=host_platform,
|
|
198
|
+
host_arches=host_arch,
|
|
188
199
|
board_platforms=board_platform,
|
|
189
200
|
palette_platform=palette_platform,
|
|
190
201
|
)
|