sima-cli 0.0.37__py3-none-any.whl → 0.0.39__py3-none-any.whl
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/__version__.py +1 -1
- sima_cli/cli.py +1 -1
- sima_cli/install/metadata_installer.py +52 -18
- sima_cli/install/metadata_validator.py +11 -2
- sima_cli/update/netboot.py +37 -24
- sima_cli/update/updater.py +22 -3
- sima_cli/utils/env.py +59 -1
- sima_cli/utils/pkg_update_check.py +11 -8
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/METADATA +1 -1
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/RECORD +14 -14
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/WHEEL +0 -0
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.37.dist-info → sima_cli-0.0.39.dist-info}/top_level.txt +0 -0
sima_cli/__version__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# sima_cli/__version__.py
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.39"
|
sima_cli/cli.py
CHANGED
@@ -335,7 +335,7 @@ def bootimg_cmd(ctx, version, boardtype, netboot, autoflash, fwtype, rootfs):
|
|
335
335
|
try:
|
336
336
|
boardtype = boardtype if boardtype != 'mlsoc' else 'davinci'
|
337
337
|
if netboot or autoflash:
|
338
|
-
setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs)
|
338
|
+
setup_netboot(version, boardtype, internal, autoflash, flavor='headless', rootfs=rootfs, swtype=fwtype)
|
339
339
|
click.echo("✅ Netboot image prepared and TFTP server is running.")
|
340
340
|
else:
|
341
341
|
write_image(version, boardtype, fwtype, internal, flavor='headless')
|
@@ -9,11 +9,9 @@ import tarfile
|
|
9
9
|
import zipfile
|
10
10
|
import stat
|
11
11
|
import shlex
|
12
|
-
from urllib.parse import urlparse
|
13
|
-
|
12
|
+
from urllib.parse import urlparse, quote, urljoin
|
14
13
|
from typing import Dict
|
15
14
|
from tqdm import tqdm
|
16
|
-
from urllib.parse import urljoin
|
17
15
|
from pathlib import Path
|
18
16
|
import subprocess
|
19
17
|
import requests
|
@@ -24,7 +22,7 @@ from rich.panel import Panel
|
|
24
22
|
from huggingface_hub import snapshot_download
|
25
23
|
|
26
24
|
from sima_cli.utils.disk import check_disk_space
|
27
|
-
from sima_cli.utils.env import get_environment_type, get_exact_devkit_type
|
25
|
+
from sima_cli.utils.env import get_environment_type, get_exact_devkit_type, get_sima_build_version
|
28
26
|
from sima_cli.download.downloader import download_file_from_url
|
29
27
|
from sima_cli.install.metadata_validator import validate_metadata, MetadataValidationError
|
30
28
|
from sima_cli.install.metadata_info import print_metadata_summary, parse_size_string_to_bytes
|
@@ -162,7 +160,7 @@ def _download_requirements_wheels(repo_dir: Path):
|
|
162
160
|
req_file = deps_dir / "requirements.txt"
|
163
161
|
|
164
162
|
if not req_file.exists():
|
165
|
-
click.echo("⚠️
|
163
|
+
click.echo("⚠️ No requirements.txt found under resources/dependencies in the repo, skipping wheel download, safe to ignore this message")
|
166
164
|
return
|
167
165
|
|
168
166
|
deps_dir.mkdir(parents=True, exist_ok=True)
|
@@ -216,7 +214,15 @@ def _download_github_repo(owner: str, repo: str, ref: str, dest_folder: str, tok
|
|
216
214
|
Returns:
|
217
215
|
str: Path to the extracted repo
|
218
216
|
"""
|
219
|
-
|
217
|
+
# Encode ref for API, but sanitize separately for filesystem usage
|
218
|
+
if ref:
|
219
|
+
ref_encoded = quote(ref, safe="") # safe for URL
|
220
|
+
ref_safe = ref.replace("/", "_") # safe for filesystem
|
221
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/tarball/{ref_encoded}"
|
222
|
+
else:
|
223
|
+
ref_encoded = ref_safe = None
|
224
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/tarball"
|
225
|
+
|
220
226
|
headers = {}
|
221
227
|
if token:
|
222
228
|
headers["Authorization"] = f"Bearer {token}"
|
@@ -233,11 +239,13 @@ def _download_github_repo(owner: str, repo: str, ref: str, dest_folder: str, tok
|
|
233
239
|
tmp_file.write(chunk)
|
234
240
|
tmp_path = Path(tmp_file.name)
|
235
241
|
|
242
|
+
# Use sanitized ref in folder name (if provided)
|
236
243
|
repo_dir = Path(dest_folder) / repo
|
237
244
|
repo_dir.mkdir(parents=True, exist_ok=True)
|
238
245
|
|
239
246
|
_extract_tar_strip_top_level(tmp_path, repo_dir)
|
240
247
|
tmp_path.unlink(missing_ok=True)
|
248
|
+
|
241
249
|
click.echo(f"✅ Downloaded GitHub repo to folder: {repo_dir}")
|
242
250
|
_download_requirements_wheels(repo_dir=repo_dir)
|
243
251
|
|
@@ -300,9 +308,10 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
300
308
|
org, name = repo_id.split("/", 1)
|
301
309
|
target_dir = os.path.join(dest_folder, name)
|
302
310
|
|
303
|
-
click.echo(f"🤗 Downloading Hugging Face repo: {repo_id}" + (f"@{revision}" if revision else ""))
|
311
|
+
click.echo(f"🤗 Downloading Hugging Face repo: {org}/{repo_id}" + (f"@{revision}" if revision else ""))
|
304
312
|
model_path = snapshot_download(
|
305
313
|
repo_id=repo_id,
|
314
|
+
revision=revision,
|
306
315
|
local_dir=target_dir,
|
307
316
|
local_dir_use_symlinks=False
|
308
317
|
)
|
@@ -311,6 +320,7 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
311
320
|
|
312
321
|
if resource.startswith("gh:"):
|
313
322
|
resource_spec = resource[3:]
|
323
|
+
|
314
324
|
if "@" in resource_spec:
|
315
325
|
repo_id, ref = resource_spec.split("@", 1)
|
316
326
|
else:
|
@@ -740,13 +750,19 @@ def _run_installation_script(metadata: Dict, extract_path: str = "."):
|
|
740
750
|
|
741
751
|
print("✅ Installation completed successfully.")
|
742
752
|
|
743
|
-
def _resolve_github_metadata_url(gh_ref: str) -> str:
|
753
|
+
def _resolve_github_metadata_url(gh_ref: str) -> tuple[str, str]:
|
744
754
|
"""
|
745
|
-
Resolve a GitHub shorthand like gh:org/repo@tag into a
|
755
|
+
Resolve a GitHub shorthand like gh:org/repo@tag into a local metadata.json file path.
|
746
756
|
If tag is omitted, defaults to 'main'.
|
757
|
+
|
758
|
+
Args:
|
759
|
+
gh_ref (str): Reference in the form 'gh:org/repo@tag'
|
760
|
+
|
761
|
+
Returns:
|
762
|
+
tuple[str, str]: (local_path_to_metadata_json, tag_used)
|
747
763
|
"""
|
748
764
|
try:
|
749
|
-
_, repo_ref = gh_ref.split(":", 1) #
|
765
|
+
_, repo_ref = gh_ref.split(":", 1) # strip 'gh:'
|
750
766
|
if "@" in repo_ref:
|
751
767
|
org_repo, tag = repo_ref.split("@", 1)
|
752
768
|
else:
|
@@ -755,17 +771,25 @@ def _resolve_github_metadata_url(gh_ref: str) -> str:
|
|
755
771
|
owner, repo = org_repo.split("/", 1)
|
756
772
|
token = os.getenv("GITHUB_TOKEN")
|
757
773
|
|
758
|
-
#
|
759
|
-
|
774
|
+
# Encode the ref safely for GitHub API
|
775
|
+
tag_encoded = quote(tag, safe="")
|
776
|
+
|
777
|
+
# GitHub API URL for raw file contents
|
778
|
+
api_url = (
|
779
|
+
f"https://api.github.com/repos/{owner}/{repo}/contents/metadata.json?ref={tag_encoded}"
|
780
|
+
)
|
760
781
|
headers = {"Accept": "application/vnd.github.v3.raw"}
|
761
782
|
if token:
|
762
|
-
headers["Authorization"] = f"
|
783
|
+
headers["Authorization"] = f"Bearer {token}"
|
763
784
|
|
764
785
|
r = requests.get(api_url, headers=headers)
|
765
786
|
r.raise_for_status()
|
766
|
-
|
767
|
-
#
|
768
|
-
|
787
|
+
|
788
|
+
# --- Sanitize tag for filesystem use ---
|
789
|
+
tag_safe = tag.replace("/", "_")
|
790
|
+
|
791
|
+
# Write metadata.json locally
|
792
|
+
local_path = os.path.join("/tmp", f"{repo}-{tag_safe}-metadata.json")
|
769
793
|
with open(local_path, "wb") as f:
|
770
794
|
f.write(r.content)
|
771
795
|
|
@@ -804,22 +828,32 @@ def metadata_resolver(component: str, version: str = None, tag: str = None) -> s
|
|
804
828
|
|
805
829
|
Args:
|
806
830
|
component (str): Component name (e.g., "examples.llima" or "assets/ragfps")
|
807
|
-
version (str):
|
831
|
+
version (str): Optional. If not provided, auto-detect from /etc/build.
|
808
832
|
tag (str): Optional tag to use (e.g., "dev")
|
809
833
|
|
810
834
|
Returns:
|
811
835
|
str: Fully qualified metadata URL
|
812
836
|
"""
|
837
|
+
|
813
838
|
if tag:
|
814
839
|
metadata_name = f"metadata-{tag}.json"
|
815
840
|
else:
|
816
841
|
metadata_name = "metadata.json"
|
817
842
|
|
843
|
+
# --- Asset case, assets are SDK version agnostic ---
|
818
844
|
if component.startswith("assets/"):
|
819
845
|
return f"https://docs.sima.ai/{component}/{metadata_name}"
|
820
846
|
|
847
|
+
# --- Auto-detect SDK version if missing ---
|
821
848
|
if not version:
|
822
|
-
|
849
|
+
core_version, _ = get_sima_build_version()
|
850
|
+
if core_version:
|
851
|
+
version = core_version
|
852
|
+
else:
|
853
|
+
raise ValueError(
|
854
|
+
"Version (-v) is required and could not be auto-detected "
|
855
|
+
"from /etc/build or /etc/buildinfo."
|
856
|
+
)
|
823
857
|
|
824
858
|
sdk_path = f"SDK{version}"
|
825
859
|
return f"https://docs.sima.ai/pkg_downloads/{sdk_path}/{component}/{metadata_name}"
|
@@ -8,6 +8,7 @@ class MetadataValidationError(Exception):
|
|
8
8
|
|
9
9
|
VALID_TYPES = {"board", "palette", "host"}
|
10
10
|
VALID_OS = {"linux", "windows", "mac"}
|
11
|
+
VALID_DEVKIT_SW = {"yocto", "elxr"}
|
11
12
|
|
12
13
|
def validate_metadata(data: dict):
|
13
14
|
# Top-level required fields
|
@@ -34,6 +35,15 @@ def validate_metadata(data: dict):
|
|
34
35
|
if not isinstance(platform["compatible_with"], list):
|
35
36
|
raise MetadataValidationError(f"'compatible_with' must be a list in entry {i}")
|
36
37
|
|
38
|
+
# ✅ Check optional devkit_sw
|
39
|
+
if "devkit_sw" in platform:
|
40
|
+
devkit_sw_value = platform["devkit_sw"].lower()
|
41
|
+
if devkit_sw_value not in VALID_DEVKIT_SW:
|
42
|
+
raise MetadataValidationError(
|
43
|
+
f"Invalid 'devkit_sw' value '{platform['devkit_sw']}' in platform entry {i}. "
|
44
|
+
f"Must be one of {VALID_DEVKIT_SW}"
|
45
|
+
)
|
46
|
+
|
37
47
|
if "os" in platform:
|
38
48
|
if not isinstance(platform["os"], list):
|
39
49
|
raise MetadataValidationError(f"'os' must be a list in entry {i}")
|
@@ -81,7 +91,6 @@ def validate_metadata(data: dict):
|
|
81
91
|
)
|
82
92
|
|
83
93
|
try:
|
84
|
-
# Extract number (e.g., "30GB" → 30.0)
|
85
94
|
float(size_str[:-2].strip())
|
86
95
|
except ValueError:
|
87
96
|
raise MetadataValidationError(
|
@@ -135,4 +144,4 @@ def main():
|
|
135
144
|
sys.exit(1)
|
136
145
|
|
137
146
|
if __name__ == "__main__":
|
138
|
-
main()
|
147
|
+
main()
|
sima_cli/update/netboot.py
CHANGED
@@ -52,7 +52,9 @@ def flash_emmc(client_manager, emmc_image_paths):
|
|
52
52
|
|
53
53
|
for path in emmc_image_paths:
|
54
54
|
click.echo(f"📤 Copying {path} to {selected_ip}:{remote_dir}")
|
55
|
-
success = copy_file_to_remote_board(
|
55
|
+
success = copy_file_to_remote_board(
|
56
|
+
selected_ip, path, remote_dir, passwd=DEFAULT_PASSWORD
|
57
|
+
)
|
56
58
|
if not success:
|
57
59
|
click.echo(f"❌ Failed to copy {path} to {selected_ip}. Aborting.")
|
58
60
|
return
|
@@ -61,36 +63,44 @@ def flash_emmc(client_manager, emmc_image_paths):
|
|
61
63
|
ssh = init_ssh_session(selected_ip, password=DEFAULT_PASSWORD)
|
62
64
|
|
63
65
|
# Step a: Check if eMMC exists
|
64
|
-
check_cmd = "[ -e /dev/
|
66
|
+
check_cmd = "[ -e /dev/mmcblk0 ] || (echo '❌ /dev/mmcblk0 not found'; exit 1)"
|
65
67
|
run_remote_command(ssh, check_cmd)
|
66
68
|
|
67
|
-
# Step b:
|
68
|
-
wic_path = next((p for p in emmc_image_paths if p.endswith(".wic.gz")), None)
|
69
|
-
if not wic_path:
|
70
|
-
click.echo("❌ No .wic.gz image found in emmc_image_paths.")
|
71
|
-
return
|
72
|
-
|
73
|
-
# Step c: umount the emmc
|
69
|
+
# Step b: umount eMMC
|
74
70
|
pre_unmount_cmd = (
|
75
71
|
"sudo mount | grep mmcblk0 | awk '{print $3}' | while read mnt; do "
|
76
72
|
"sudo umount \"$mnt\"; done"
|
77
73
|
)
|
78
74
|
run_remote_command(ssh, pre_unmount_cmd)
|
79
75
|
|
80
|
-
# Step
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
76
|
+
# Step c: Decide flashing method
|
77
|
+
wic_path = next((p for p in emmc_image_paths if p.endswith(".wic.gz")), None)
|
78
|
+
img_path = next((p for p in emmc_image_paths if p.endswith(".img")), None)
|
79
|
+
|
80
|
+
if wic_path:
|
81
|
+
filename = os.path.basename(wic_path)
|
82
|
+
remote_path = f"/tmp/{filename}"
|
83
|
+
flash_cmd = f"sudo bmaptool copy {remote_path} /dev/mmcblk0"
|
84
|
+
run_remote_command(ssh, flash_cmd)
|
85
|
+
|
86
|
+
# Step d: Fix GPT for Yocto
|
87
|
+
fix_cmd = 'sudo printf "fix\n" | sudo parted ---pretend-input-tty /dev/mmcblk0 print'
|
88
|
+
run_remote_command(ssh, fix_cmd)
|
89
|
+
|
90
|
+
elif img_path:
|
91
|
+
filename = os.path.basename(img_path)
|
92
|
+
remote_path = f"/tmp/{filename}"
|
93
|
+
flash_cmd = f"sudo dd if={remote_path} of=/dev/mmcblk0 bs=4M conv=fsync status=progress"
|
94
|
+
run_remote_command(ssh, flash_cmd)
|
95
|
+
else:
|
96
|
+
click.echo("❌ No .wic.gz or .img image found in emmc_image_paths.")
|
97
|
+
return
|
89
98
|
|
90
99
|
click.echo("✅ Flash completed. Please reboot the board to boot from eMMC.")
|
91
100
|
except Exception as e:
|
92
101
|
click.echo(f"❌ Flashing failed: {e}")
|
93
102
|
|
103
|
+
|
94
104
|
class ClientManager:
|
95
105
|
"""Manages TFTP client state and monitoring."""
|
96
106
|
def __init__(self):
|
@@ -337,7 +347,7 @@ def run_cli(client_manager):
|
|
337
347
|
else:
|
338
348
|
click.echo("📭 No TFTP client requests received yet.")
|
339
349
|
elif user_input == "f":
|
340
|
-
click.echo("🔧 Initiating eMMC flash
|
350
|
+
click.echo(f"🔧 Initiating eMMC flash {emmc_image_paths}.")
|
341
351
|
flash_emmc(client_manager, emmc_image_paths)
|
342
352
|
elif user_input == "":
|
343
353
|
continue
|
@@ -347,7 +357,7 @@ def run_cli(client_manager):
|
|
347
357
|
click.echo("\n🛑 Exiting netboot session.")
|
348
358
|
return True
|
349
359
|
|
350
|
-
def setup_netboot(version: str, board: str, internal: bool = False, autoflash: bool = False, flavor: str = 'headless', rootfs: str = ''):
|
360
|
+
def setup_netboot(version: str, board: str, internal: bool = False, autoflash: bool = False, flavor: str = 'headless', rootfs: str = '', swtype: str = 'yocto'):
|
351
361
|
"""
|
352
362
|
Download and serve a bootable image for network boot over TFTP with client monitoring.
|
353
363
|
|
@@ -358,6 +368,7 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
|
|
358
368
|
autoflash (bool): Whether to automatically flash the devkit when networked booted. Defaults to False.
|
359
369
|
flavor (str): The software flavor, can be either headless or full.
|
360
370
|
rootfs (str): The root fs folder, which contains the .wic.gz file and the .bmap file, for custom image writing.
|
371
|
+
swtype (str): The software type, either yocto or elxr.
|
361
372
|
|
362
373
|
Raises:
|
363
374
|
RuntimeError: If the download or TFTP setup fails.
|
@@ -370,8 +381,8 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
|
|
370
381
|
exit(1)
|
371
382
|
|
372
383
|
try:
|
373
|
-
click.echo(f"⬇️ Downloading netboot image for version: {version}, board: {board}")
|
374
|
-
file_list = download_image(version, board, swtype=
|
384
|
+
click.echo(f"⬇️ Downloading netboot image for version: {version}, board: {board}, swtype: {swtype}")
|
385
|
+
file_list = download_image(version, board, swtype=swtype, internal=internal, update_type='netboot', flavor=flavor)
|
375
386
|
if not isinstance(file_list, list):
|
376
387
|
raise ValueError("Expected list of extracted files, got something else.")
|
377
388
|
extract_dir = os.path.dirname(file_list[0])
|
@@ -380,7 +391,8 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
|
|
380
391
|
# Extract specific image paths
|
381
392
|
wic_gz_file = next((f for f in file_list if f.endswith(".wic.gz")), None)
|
382
393
|
bmap_file = next((f for f in file_list if f.endswith(".wic.bmap")), None)
|
383
|
-
|
394
|
+
elxr_img_file = next((f for f in file_list if f.endswith(".img")), None)
|
395
|
+
emmc_image_paths = [p for p in [wic_gz_file, bmap_file, elxr_img_file] if p]
|
384
396
|
|
385
397
|
# Check global custom_rootfs before doing anything else
|
386
398
|
custom_rootfs = rootfs
|
@@ -391,13 +403,14 @@ def setup_netboot(version: str, board: str, internal: bool = False, autoflash: b
|
|
391
403
|
import glob
|
392
404
|
wic_gz_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.wic.gz"))), None)
|
393
405
|
bmap_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.wic.bmap"))), None)
|
406
|
+
exlr_file = next(iter(glob.glob(os.path.join(custom_rootfs, "*.img"))), None)
|
394
407
|
|
395
408
|
if not (wic_gz_file and bmap_file):
|
396
409
|
raise RuntimeError(
|
397
410
|
f"❌ custom_rootfs '{custom_rootfs}' must contain both .wic.gz and .wic.bmap files."
|
398
411
|
)
|
399
412
|
|
400
|
-
emmc_image_paths = [wic_gz_file, bmap_file]
|
413
|
+
emmc_image_paths = [wic_gz_file, bmap_file, exlr_file]
|
401
414
|
click.echo(f"📁 Using custom_rootfs: {custom_rootfs}")
|
402
415
|
|
403
416
|
click.echo(f"📁 eMMC image paths are: {emmc_image_paths}")
|
sima_cli/update/updater.py
CHANGED
@@ -65,7 +65,7 @@ def _resolve_firmware_url(version_or_url: str, board: str, internal: bool = Fals
|
|
65
65
|
image_file = 'release.tar.gz' if flavor == 'headless' else 'graphics.tar.gz'
|
66
66
|
download_url = url.rstrip("/") + f"/soc-images/{board}/{version_or_url}/artifacts/{image_file}"
|
67
67
|
elif swtype == 'elxr':
|
68
|
-
image_file = '
|
68
|
+
image_file = f'{board}-tftp-boot.tar.gz'
|
69
69
|
download_url = url.rstrip("/") + f"/soc-images/elxr/{board}/{version_or_url}/artifacts/palette/{image_file}"
|
70
70
|
|
71
71
|
return download_url
|
@@ -203,6 +203,8 @@ def _extract_required_files(tar_path: str, board: str, update_type: str = 'stand
|
|
203
203
|
f"{board}-som.dtb",
|
204
204
|
f"{board}-dvt.dtb",
|
205
205
|
f"{board}-hhhl_x16.dtb",
|
206
|
+
f"pcie-4rc-2rc-2rc.dtbo",
|
207
|
+
f"elxr-palette-{board}-arm64.cpio.gz",
|
206
208
|
f"simaai-image-{_flavor}-{board}.wic.gz",
|
207
209
|
f"simaai-image-{_flavor}-{board}.wic.bmap",
|
208
210
|
f"simaai-image-{_flavor}-{board}.cpio.gz"
|
@@ -321,9 +323,26 @@ def _download_image(version_or_url: str, board: str, internal: bool = False, upd
|
|
321
323
|
# Download the file
|
322
324
|
click.echo(f"📦 Downloading from {image_url}")
|
323
325
|
firmware_path = download_file_from_url(image_url, dest_path, internal=internal)
|
326
|
+
extracted_files = _extract_required_files(firmware_path, board, update_type, flavor)
|
327
|
+
|
328
|
+
# If internal, netboot and elxr, we need to download some additional files to prepare for eMMC flash.
|
329
|
+
if internal and update_type == "netboot" and swtype == "elxr":
|
330
|
+
base_url = os.path.dirname(image_url)
|
331
|
+
base_version = version_or_url.split('_')[0]
|
332
|
+
extra_files = [f"elxr-palette-{board}-{base_version}-arm64.img.gz"]
|
333
|
+
|
334
|
+
for fname in extra_files:
|
335
|
+
extra_url = f"{base_url}/{fname}"
|
336
|
+
try:
|
337
|
+
click.echo(f"📥 Downloading extra file: {fname} from {extra_url} saving into {dest_path}")
|
338
|
+
netboot_file_path = download_file_from_url(extra_url, dest_path, internal=internal)
|
339
|
+
extracted_files.extend(_extract_required_files(netboot_file_path, board, update_type, flavor))
|
340
|
+
click.echo(f"✅ Saved {fname} to {dest_path}")
|
341
|
+
except Exception as e:
|
342
|
+
click.echo(f"⚠️ Failed to download {fname}: {e}")
|
324
343
|
|
325
344
|
click.echo(f"📦 Firmware downloaded to: {firmware_path}")
|
326
|
-
return
|
345
|
+
return extracted_files
|
327
346
|
|
328
347
|
except Exception as e:
|
329
348
|
click.echo(f"❌ Host update failed: {e}")
|
@@ -471,7 +490,7 @@ def download_image(version_or_url: str, board: str, swtype: str, internal: bool
|
|
471
490
|
Args:
|
472
491
|
version_or_url (str): Either a version string (e.g., "1.6.0") or a direct URL or local file path to the image.
|
473
492
|
board (str): The board type (e.g., "mlsoc", "modalix").
|
474
|
-
swtype (str): The software type (default to '
|
493
|
+
swtype (str): The software type (default to 'yocto', possible values: `yocto`, `elxr`): not supported for now
|
475
494
|
internal (bool): Whether to use internal download paths (e.g., Artifactory).
|
476
495
|
update_type (str): Whether this is standard update or writing boot image.
|
477
496
|
flavor (str): Flavor of the image, can be headless or full.
|
sima_cli/utils/env.py
CHANGED
@@ -2,7 +2,8 @@ import os
|
|
2
2
|
import subprocess
|
3
3
|
import platform
|
4
4
|
import shutil
|
5
|
-
|
5
|
+
import re
|
6
|
+
from typing import Tuple, Optional
|
6
7
|
|
7
8
|
# Utility functions to determine the environment:
|
8
9
|
# - Whether we are running on a SiMa board
|
@@ -33,6 +34,38 @@ def is_sima_board() -> bool:
|
|
33
34
|
|
34
35
|
return False
|
35
36
|
|
37
|
+
def get_sima_build_version() -> Tuple[Optional[str], Optional[str]]:
|
38
|
+
"""
|
39
|
+
Retrieve the current SiMa build version from /etc/build or /etc/buildinfo.
|
40
|
+
|
41
|
+
It searches for 'SIMA_BUILD_VERSION=' and extracts:
|
42
|
+
- core_version: the semantic version (e.g., '2.0.0')
|
43
|
+
- full_version: the complete build string (e.g., '2.0.0_develop_B1932')
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
tuple: (core_version, full_version)
|
47
|
+
If not found, returns (None, None)
|
48
|
+
"""
|
49
|
+
build_file_paths = ["/etc/build", "/etc/buildinfo"]
|
50
|
+
version_pattern = re.compile(r"SIMA_BUILD_VERSION\s*=\s*([\w\.\-\+]+)")
|
51
|
+
|
52
|
+
for path in build_file_paths:
|
53
|
+
if os.path.exists(path):
|
54
|
+
try:
|
55
|
+
with open(path, "r") as f:
|
56
|
+
for line in f:
|
57
|
+
match = version_pattern.search(line)
|
58
|
+
if match:
|
59
|
+
full_version = match.group(1)
|
60
|
+
# Extract only major.minor.patch portion
|
61
|
+
core_match = re.match(r"(\d+\.\d+\.\d+)", full_version)
|
62
|
+
core_version = core_match.group(1) if core_match else None
|
63
|
+
return core_version, full_version
|
64
|
+
except Exception:
|
65
|
+
continue
|
66
|
+
|
67
|
+
return None, None
|
68
|
+
|
36
69
|
def is_pcie_host() -> bool:
|
37
70
|
"""
|
38
71
|
Detect if running from a PCIe host (typically a Linux or macOS dev machine).
|
@@ -71,6 +104,31 @@ def get_sima_board_type() -> str:
|
|
71
104
|
|
72
105
|
return ""
|
73
106
|
|
107
|
+
def is_devkit_running_elxr() -> bool:
|
108
|
+
"""
|
109
|
+
Check if the system is a SiMa devkit and is running ELXR software.
|
110
|
+
|
111
|
+
Conditions:
|
112
|
+
- Must be identified as a SiMa board via is_sima_board()
|
113
|
+
- /etc/buildinfo exists and contains "elxr" (case-insensitive)
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
bool: True if SiMa devkit with ELXR software, False otherwise.
|
117
|
+
"""
|
118
|
+
if not is_sima_board():
|
119
|
+
return False
|
120
|
+
|
121
|
+
buildinfo_path = "/etc/buildinfo"
|
122
|
+
if not os.path.exists(buildinfo_path):
|
123
|
+
return False
|
124
|
+
|
125
|
+
try:
|
126
|
+
with open(buildinfo_path, "r") as f:
|
127
|
+
content = f.read().lower()
|
128
|
+
return "elxr" in content
|
129
|
+
except Exception:
|
130
|
+
return False
|
131
|
+
|
74
132
|
def is_modalix_devkit() -> bool:
|
75
133
|
"""
|
76
134
|
Determines whether the current system is a Modalix DevKit (SoM)
|
@@ -41,16 +41,19 @@ def update_package(package_name: str):
|
|
41
41
|
def has_internet(timeout: float = 1.0) -> bool:
|
42
42
|
"""
|
43
43
|
Quick check for internet connectivity by connecting to a known DNS server.
|
44
|
+
First tries Cloudflare (1.1.1.1), falls back to Google (8.8.8.8).
|
44
45
|
Uses IP to avoid DNS lookup delays.
|
45
46
|
"""
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
def try_connect(ip: str, port: int = 53) -> bool:
|
48
|
+
try:
|
49
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
50
|
+
sock.settimeout(timeout)
|
51
|
+
sock.connect((ip, port))
|
52
|
+
return True
|
53
|
+
except OSError:
|
54
|
+
return False
|
55
|
+
|
56
|
+
return try_connect("1.1.1.1") or try_connect("8.8.8.8")
|
54
57
|
|
55
58
|
def check_for_update(package_name: str, timeout: float = 2.0):
|
56
59
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
sima_cli/__init__.py,sha256=Nb2jSg9-CX1XvSc1c21U9qQ3atINxphuNkNfmR-9P3o,332
|
2
2
|
sima_cli/__main__.py,sha256=ehzD6AZ7zGytC2gLSvaJatxeD0jJdaEvNJvwYeGsWOg,69
|
3
|
-
sima_cli/__version__.py,sha256=
|
4
|
-
sima_cli/cli.py,sha256=
|
3
|
+
sima_cli/__version__.py,sha256=1TIyHW8Bk0vixt9Nebb4dCiN94FOO3JEiRQVppoIYuM,49
|
4
|
+
sima_cli/cli.py,sha256=jGufO70y0NGLsIfEhr1pQNdFoBdzMxTO1R7p386d4-8,19291
|
5
5
|
sima_cli/app_zoo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
sima_cli/app_zoo/app.py,sha256=6u3iIqVZkuMK49kK0f3dVJCCf5-qC-xzLOS78-TkeN8,15738
|
7
7
|
sima_cli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -14,8 +14,8 @@ sima_cli/download/downloader.py,sha256=UQdrBWLQsPQygaoVaufOjbzWmRoNnk6pgLdnbnVi0
|
|
14
14
|
sima_cli/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
sima_cli/install/hostdriver.py,sha256=kAWDLebs60mbWIyTbUxmNrChcKW1uD5r7FtWNSUVUE4,5852
|
16
16
|
sima_cli/install/metadata_info.py,sha256=wmMqwzGfXbuilkqaxRVrFOzOtTOiONkmPCyA2oDAQpA,2168
|
17
|
-
sima_cli/install/metadata_installer.py,sha256
|
18
|
-
sima_cli/install/metadata_validator.py,sha256=
|
17
|
+
sima_cli/install/metadata_installer.py,sha256=-hgMSF_1awwnnibzUqoMv4_GC7IUavu11eqetBVJZHE,31390
|
18
|
+
sima_cli/install/metadata_validator.py,sha256=Xp51J68hMvpQeX-11kPLgz1YarQZ2m-95gGpfe3-D3Q,5644
|
19
19
|
sima_cli/install/optiview.py,sha256=r4DYdQDTUbZVCR87hl5T21gsjZrhqpU8hWnYxKmUJ_k,4790
|
20
20
|
sima_cli/install/palette.py,sha256=uRznoHa4Mv9ZXHp6AoqknfC3RxpYNKi9Ins756Cyifk,3930
|
21
21
|
sima_cli/mla/meminfo.py,sha256=ndc8kQJmWGEIdvNh6iIhATGdrkqM2pbddr_eHxaPNfg,1466
|
@@ -33,20 +33,20 @@ sima_cli/update/bmaptool.py,sha256=KrhUGShBwY4Wzz50QiuMYAxxPgEy1nz5C68G-0a4qF4,4
|
|
33
33
|
sima_cli/update/bootimg.py,sha256=OA_GyZwI8dbU3kaucKmoxAZbnnSnjXeOkU6yuDPji1k,13632
|
34
34
|
sima_cli/update/cleanlog.py,sha256=-V6eDl3MdsvDmCfkKUJTqkXJ_WnLJE01uxS7z96b15g,909
|
35
35
|
sima_cli/update/local.py,sha256=z3oRk6JH-zbCdoS3JpgeW_ZB4kolG7nPhLC55A2yssk,5597
|
36
|
-
sima_cli/update/netboot.py,sha256=
|
36
|
+
sima_cli/update/netboot.py,sha256=V0QiqyHzAdrucJ87Q3Xlm2EBvkpjpu4RwPRWw9NXRcs,20334
|
37
37
|
sima_cli/update/query.py,sha256=6RgvQfQT1_EtBGcibvVcz003dRKOq17NaGgL2mhaBbY,4891
|
38
38
|
sima_cli/update/remote.py,sha256=wC4MSBQVxmibxtPBchAzFMhZYcRjxTiLlPSzVI0en4o,14690
|
39
|
-
sima_cli/update/updater.py,sha256=
|
39
|
+
sima_cli/update/updater.py,sha256=CYOXTV-8zIo92Lv2vyBzKtUyrtFBwkJiknttlS-UxF4,25648
|
40
40
|
sima_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
41
|
sima_cli/utils/artifactory.py,sha256=6YyVpzVm8ATy7NEwT9nkWx-wptkXrvG7Wl_zDT6jmLs,2390
|
42
42
|
sima_cli/utils/config.py,sha256=wE-cPQqY_gOqaP8t01xsRHD9tBUGk9MgBUm2GYYxI3E,1616
|
43
43
|
sima_cli/utils/config_loader.py,sha256=7I5we1yiCai18j9R9jvhfUzAmT3OjAqVK35XSLuUw8c,2005
|
44
44
|
sima_cli/utils/disk.py,sha256=66Kr631yhc_ny19up2aijfycWfD35AeLQOJgUsuH2hY,446
|
45
|
-
sima_cli/utils/env.py,sha256=
|
45
|
+
sima_cli/utils/env.py,sha256=0qY1-PJiI1G3uDVv774aimPXhHQBtY56FqXSMeMQTps,10401
|
46
46
|
sima_cli/utils/net.py,sha256=WVntA4CqipkNrrkA4tBVRadJft_pMcGYh4Re5xk3rqo,971
|
47
47
|
sima_cli/utils/network.py,sha256=UvqxbqbWUczGFyO-t1SybG7Q-x9kjUVRNIn_D6APzy8,1252
|
48
|
-
sima_cli/utils/pkg_update_check.py,sha256=
|
49
|
-
sima_cli-0.0.
|
48
|
+
sima_cli/utils/pkg_update_check.py,sha256=GD0XVS2_l2WpuzL81TKkEQIDHXXffJeF3LELefncHNM,3467
|
49
|
+
sima_cli-0.0.39.dist-info/licenses/LICENSE,sha256=a260OFuV4SsMZ6sQCkoYbtws_4o2deFtbnT9kg7Rfd4,1082
|
50
50
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
51
|
tests/test_app_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
52
|
tests/test_auth.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -55,8 +55,8 @@ tests/test_download.py,sha256=t87DwxlHs26_ws9rpcHGwr_OrcRPd3hz6Zmm0vRee2U,4465
|
|
55
55
|
tests/test_firmware.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
56
|
tests/test_model_zoo.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
57
|
tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
|
-
sima_cli-0.0.
|
59
|
-
sima_cli-0.0.
|
60
|
-
sima_cli-0.0.
|
61
|
-
sima_cli-0.0.
|
62
|
-
sima_cli-0.0.
|
58
|
+
sima_cli-0.0.39.dist-info/METADATA,sha256=GyCe69CeeFe8gcAtM0_ZS5eILLMMtJemMqnQPz_N9Z8,3705
|
59
|
+
sima_cli-0.0.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
60
|
+
sima_cli-0.0.39.dist-info/entry_points.txt,sha256=xRYrDq1nCs6R8wEdB3c1kKuimxEjWJkHuCzArQPT0Xk,47
|
61
|
+
sima_cli-0.0.39.dist-info/top_level.txt,sha256=FtrbAUdHNohtEPteOblArxQNwoX9_t8qJQd59fagDlc,15
|
62
|
+
sima_cli-0.0.39.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|