sima-cli 0.0.18__py3-none-any.whl → 0.0.20__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/auth/login.py +2 -2
- sima_cli/cli.py +111 -2
- sima_cli/install/__init__.py +0 -0
- sima_cli/install/hostdriver.py +152 -0
- sima_cli/install/optiview.py +85 -0
- sima_cli/install/palette.py +87 -0
- sima_cli/serial/__init__.py +0 -0
- sima_cli/serial/serial.py +114 -0
- sima_cli/update/bmaptool.py +137 -0
- sima_cli/update/bootimg.py +339 -0
- sima_cli/update/netboot.py +408 -0
- sima_cli/update/remote.py +42 -9
- sima_cli/update/updater.py +103 -33
- sima_cli/utils/env.py +2 -3
- sima_cli/utils/net.py +29 -0
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/METADATA +3 -1
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/RECORD +22 -12
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/WHEEL +0 -0
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/entry_points.txt +0 -0
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/licenses/LICENSE +0 -0
- {sima_cli-0.0.18.dist-info → sima_cli-0.0.20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
import subprocess
|
2
|
+
import sys
|
3
|
+
import os
|
4
|
+
import urllib.request
|
5
|
+
import tarfile
|
6
|
+
import tempfile
|
7
|
+
import json
|
8
|
+
import shutil
|
9
|
+
|
10
|
+
def run_command(command, check=True, shell=False, cwd=None):
|
11
|
+
"""Run a shell command and return its output or raise an exception if it fails."""
|
12
|
+
try:
|
13
|
+
result = subprocess.run(
|
14
|
+
command,
|
15
|
+
check=check,
|
16
|
+
shell=shell,
|
17
|
+
stdout=subprocess.PIPE,
|
18
|
+
stderr=subprocess.PIPE,
|
19
|
+
text=True,
|
20
|
+
cwd=cwd
|
21
|
+
)
|
22
|
+
return result.stdout.strip(), result.stderr.strip()
|
23
|
+
except subprocess.CalledProcessError as e:
|
24
|
+
if check:
|
25
|
+
print(f"Error running command {command}: {e.stderr}")
|
26
|
+
raise
|
27
|
+
return "", str(e)
|
28
|
+
|
29
|
+
def is_bmaptool_installed():
|
30
|
+
"""Check if bmaptool is installed and callable from the shell."""
|
31
|
+
try:
|
32
|
+
result = subprocess.run(
|
33
|
+
["bmaptool", "--version"],
|
34
|
+
stdout=subprocess.PIPE,
|
35
|
+
stderr=subprocess.PIPE,
|
36
|
+
text=True,
|
37
|
+
timeout=5
|
38
|
+
)
|
39
|
+
if result.returncode == 0:
|
40
|
+
print(f"✅ bmaptool is installed: {result.stdout.strip()}")
|
41
|
+
return True
|
42
|
+
else:
|
43
|
+
print(f"⚠️ bmaptool returned non-zero exit: {result.stderr.strip()}")
|
44
|
+
return False
|
45
|
+
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
|
46
|
+
print(f"❌ bmaptool is not installed or not in PATH: {e}")
|
47
|
+
return False
|
48
|
+
|
49
|
+
def clone_bmaptool_repo(temp_dir, branch_or_tag="main"):
|
50
|
+
"""Clone the bmaptool repository to a temporary directory."""
|
51
|
+
repo_url = "https://github.com/yoctoproject/bmaptool.git"
|
52
|
+
print(f"Cloning bmaptool from {repo_url} (branch/tag: {branch_or_tag})...")
|
53
|
+
try:
|
54
|
+
subprocess.run(
|
55
|
+
["git", "clone", "--depth", "1", "--branch", branch_or_tag, repo_url, temp_dir],
|
56
|
+
check=True,
|
57
|
+
capture_output=True,
|
58
|
+
text=True
|
59
|
+
)
|
60
|
+
print(f"Cloned bmaptool to {temp_dir}")
|
61
|
+
return temp_dir
|
62
|
+
except subprocess.CalledProcessError as e:
|
63
|
+
print(f"Failed to clone repository: {e.stderr}")
|
64
|
+
sys.exit(1)
|
65
|
+
|
66
|
+
def get_python_and_pip_bins():
|
67
|
+
"""Get the Python and pip binaries from the active virtual environment or system."""
|
68
|
+
if "VIRTUAL_ENV" in os.environ:
|
69
|
+
venv_dir = os.environ["VIRTUAL_ENV"]
|
70
|
+
print(f"Using existing virtual environment: {venv_dir}")
|
71
|
+
if sys.platform == "win32":
|
72
|
+
python_bin = os.path.join(venv_dir, "Scripts", "python.exe")
|
73
|
+
pip_bin = os.path.join(venv_dir, "Scripts", "pip.exe")
|
74
|
+
else:
|
75
|
+
python_bin = os.path.join(venv_dir, "bin", "python3")
|
76
|
+
pip_bin = os.path.join(venv_dir, "bin", "pip3")
|
77
|
+
else:
|
78
|
+
print("No virtual environment detected; using system Python.")
|
79
|
+
python_bin = shutil.which("python3") or sys.executable
|
80
|
+
pip_bin = shutil.which("pip3") or "pip3"
|
81
|
+
return python_bin, pip_bin
|
82
|
+
|
83
|
+
def install_bmaptool(temp_dir):
|
84
|
+
"""Install bmaptool from the cloned repository."""
|
85
|
+
try:
|
86
|
+
subprocess.run(
|
87
|
+
[sys.executable, "-m", "pip", "install", temp_dir],
|
88
|
+
check=True,
|
89
|
+
capture_output=True,
|
90
|
+
text=True
|
91
|
+
)
|
92
|
+
print("Installed bmaptool successfully")
|
93
|
+
except subprocess.CalledProcessError as e:
|
94
|
+
print(f"Failed to install bmaptool: {e.stderr}")
|
95
|
+
sys.exit(1)
|
96
|
+
|
97
|
+
def verify_installation(python_bin):
|
98
|
+
"""Verify bmaptool installation by running python -m bmaptool --version."""
|
99
|
+
stdout, stderr = run_command([python_bin, "-m", "bmaptool", "--version"])
|
100
|
+
if stdout:
|
101
|
+
print(f"bmaptool installation verified: {stdout}")
|
102
|
+
else:
|
103
|
+
print(f"Verification failed: {stderr}")
|
104
|
+
sys.exit(1)
|
105
|
+
|
106
|
+
def check_bmap_and_install():
|
107
|
+
# Get Python and pip binaries from the active virtual environment or system
|
108
|
+
python_bin, _ = get_python_and_pip_bins()
|
109
|
+
|
110
|
+
# Check if bmaptool is already installed
|
111
|
+
if is_bmaptool_installed():
|
112
|
+
return
|
113
|
+
|
114
|
+
# Create a temporary directory
|
115
|
+
temp_dir = "bmaptool_temp"
|
116
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
117
|
+
if os.path.exists(temp_dir):
|
118
|
+
shutil.rmtree(temp_dir)
|
119
|
+
os.makedirs(temp_dir)
|
120
|
+
|
121
|
+
clone_bmaptool_repo(temp_dir, branch_or_tag="main") # Or specify a tag like "v3.8"
|
122
|
+
install_bmaptool(temp_dir)
|
123
|
+
|
124
|
+
# Verify installation
|
125
|
+
verify_installation(python_bin)
|
126
|
+
|
127
|
+
# Provide usage instructions
|
128
|
+
if "VIRTUAL_ENV" in os.environ:
|
129
|
+
print(f"\nbmaptool is installed in the virtual environment at {os.environ['VIRTUAL_ENV']}")
|
130
|
+
print("The virtual environment is already active.")
|
131
|
+
print("Run 'bmaptool --help' to see available commands.")
|
132
|
+
else:
|
133
|
+
print("\nbmaptool is installed in the system Python environment.")
|
134
|
+
print("Run 'bmaptool --help' to see available commands.")
|
135
|
+
|
136
|
+
if __name__ == "__main__":
|
137
|
+
check_bmap_and_install()
|
@@ -0,0 +1,339 @@
|
|
1
|
+
import click
|
2
|
+
import platform
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
import os
|
6
|
+
import select
|
7
|
+
import re
|
8
|
+
import threading
|
9
|
+
from tftpy import TftpServer
|
10
|
+
|
11
|
+
try:
|
12
|
+
from tqdm import tqdm
|
13
|
+
except ImportError:
|
14
|
+
tqdm = None
|
15
|
+
|
16
|
+
|
17
|
+
def list_removable_devices():
|
18
|
+
system = platform.system()
|
19
|
+
|
20
|
+
if system == "Linux":
|
21
|
+
return get_linux_removable()
|
22
|
+
elif system == "Darwin":
|
23
|
+
return get_macos_removable()
|
24
|
+
elif system == "Windows":
|
25
|
+
return get_windows_removable()
|
26
|
+
else:
|
27
|
+
click.echo(f"❌ Unsupported platform: {system}")
|
28
|
+
return []
|
29
|
+
|
30
|
+
# Linux: Use lsblk to find removable drives
|
31
|
+
def get_linux_removable():
|
32
|
+
try:
|
33
|
+
output = subprocess.check_output(["lsblk", "-o", "NAME,RM,SIZE,MOUNTPOINT", "-J"]).decode()
|
34
|
+
import json
|
35
|
+
data = json.loads(output)
|
36
|
+
devices = []
|
37
|
+
for block in data['blockdevices']:
|
38
|
+
if block.get('rm') == True and block.get('mountpoint') is None:
|
39
|
+
devices.append({
|
40
|
+
"name": block['name'],
|
41
|
+
"size": block['size'],
|
42
|
+
"path": f"/dev/{block['name']}"
|
43
|
+
})
|
44
|
+
return devices
|
45
|
+
except Exception:
|
46
|
+
return []
|
47
|
+
|
48
|
+
# macOS: Use diskutil
|
49
|
+
def get_macos_removable():
|
50
|
+
try:
|
51
|
+
output = subprocess.check_output(["diskutil", "list"], text=True)
|
52
|
+
devices = []
|
53
|
+
|
54
|
+
candidate_disks = [
|
55
|
+
line.split()[0]
|
56
|
+
for line in output.splitlines()
|
57
|
+
if line.startswith("/dev/disk")
|
58
|
+
]
|
59
|
+
|
60
|
+
for disk in candidate_disks:
|
61
|
+
info = subprocess.check_output(["diskutil", "info", disk], text=True)
|
62
|
+
is_removable = False
|
63
|
+
is_disk_image = False
|
64
|
+
size = "Unknown"
|
65
|
+
device_name = ''
|
66
|
+
|
67
|
+
for info_line in info.splitlines():
|
68
|
+
if "Secure Digital" in info_line or "USB" in info_line:
|
69
|
+
is_removable = True
|
70
|
+
elif "Disk Size" in info_line and "(" in info_line:
|
71
|
+
size = info_line.split("(")[1].split()[0]
|
72
|
+
elif "Volume Name" in info_line:
|
73
|
+
volume_name = info_line.split(":")[-1].strip() or "Unknown"
|
74
|
+
elif "Device / Media Name" in info_line:
|
75
|
+
is_disk_image = ('Disk Image' in info_line)
|
76
|
+
device_name = info_line.split(":")[-1].strip() or "Unknown"
|
77
|
+
|
78
|
+
# switch to raw device to speed up dd performance
|
79
|
+
if is_removable and not is_disk_image:
|
80
|
+
devices.append({
|
81
|
+
"name": device_name,
|
82
|
+
"size": round(int(size) / (1024 ** 3), 0),
|
83
|
+
"path": disk.replace('/disk', '/rdisk')
|
84
|
+
})
|
85
|
+
|
86
|
+
return devices
|
87
|
+
except Exception as e:
|
88
|
+
click.echo(f"Failed to detect removable devices on macOS: {e}")
|
89
|
+
return []
|
90
|
+
|
91
|
+
# Windows: Use wmic or powershell
|
92
|
+
def get_windows_removable():
|
93
|
+
try:
|
94
|
+
output = subprocess.check_output(
|
95
|
+
['powershell', '-Command',
|
96
|
+
'Get-WmiObject Win32_DiskDrive | Where { $_.MediaType -match "Removable" } | '
|
97
|
+
'Select-Object DeviceID,Model,Size | ConvertTo-Json']
|
98
|
+
).decode()
|
99
|
+
import json
|
100
|
+
parsed = json.loads(output)
|
101
|
+
if not isinstance(parsed, list):
|
102
|
+
parsed = [parsed]
|
103
|
+
devices = []
|
104
|
+
for d in parsed:
|
105
|
+
size_gb = int(d.get("Size", 0)) // (1024 ** 3)
|
106
|
+
devices.append({
|
107
|
+
"name": d.get("Model", "Removable Drive"),
|
108
|
+
"size": f"{size_gb} GB",
|
109
|
+
"path": d["DeviceID"]
|
110
|
+
})
|
111
|
+
return devices
|
112
|
+
except Exception:
|
113
|
+
return []
|
114
|
+
|
115
|
+
def check_dd_installed():
|
116
|
+
"""Check if dd is installed on the system."""
|
117
|
+
try:
|
118
|
+
subprocess.run(["which", "dd"], capture_output=True, check=True)
|
119
|
+
return True
|
120
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
121
|
+
return False
|
122
|
+
|
123
|
+
def unmount_device(device_path):
|
124
|
+
"""Unmount the device using platform-specific commands."""
|
125
|
+
system = platform.system()
|
126
|
+
try:
|
127
|
+
if system == "Darwin": # macOS
|
128
|
+
subprocess.run(["diskutil", "unmountDisk", device_path], check=True, capture_output=True, text=True)
|
129
|
+
click.echo(f"✅ Unmounted {device_path} on macOS")
|
130
|
+
elif system == "Linux":
|
131
|
+
result = subprocess.run(["umount", device_path], capture_output=True, text=True)
|
132
|
+
if result.returncode == 0:
|
133
|
+
click.echo(f"✅ Unmounted {device_path} on Linux")
|
134
|
+
elif "not mounted" in result.stderr.lower():
|
135
|
+
click.echo(f"ℹ️ {device_path} was not mounted. Continuing.")
|
136
|
+
else:
|
137
|
+
click.echo(f"❌ Failed to unmount {device_path}: {result.stderr.strip()}")
|
138
|
+
sys.exit(1)
|
139
|
+
else:
|
140
|
+
click.echo(f"❌ Unsupported platform: {system}. Cannot unmount {device_path}.")
|
141
|
+
sys.exit(1)
|
142
|
+
except Exception as e:
|
143
|
+
click.echo(f"❌ Unexpected error while unmounting {device_path}: {e}")
|
144
|
+
sys.exit(1)
|
145
|
+
|
146
|
+
def _require_sudo():
|
147
|
+
try:
|
148
|
+
# This will prompt for password if necessary and cache it for a few minutes
|
149
|
+
click.echo("✅ Running this command requires sudo access.")
|
150
|
+
subprocess.run(["sudo", "-v"], check=True)
|
151
|
+
except subprocess.CalledProcessError:
|
152
|
+
click.echo("❌ Sudo authentication failed.")
|
153
|
+
sys.exit(1)
|
154
|
+
|
155
|
+
def copy_image_to_device(image_path, device_path):
|
156
|
+
"""Copy the image file to the device using dd with 16M block size."""
|
157
|
+
# Get file size for progress calculation
|
158
|
+
file_size = os.path.getsize(image_path)
|
159
|
+
click.echo(f"ℹ️ Running 'sudo dd' to copy {image_path} to {device_path}")
|
160
|
+
|
161
|
+
# Debug: Log raw dd output to a file for diagnosis
|
162
|
+
debug_log = "dd_output.log"
|
163
|
+
click.echo(f"ℹ️ Logging raw dd output to {debug_log} for debugging.")
|
164
|
+
_require_sudo()
|
165
|
+
|
166
|
+
dd_command = ["sudo", "dd", f"if={image_path}", f"of={device_path}", "bs=16M", "status=progress"]
|
167
|
+
try:
|
168
|
+
# Start dd process with unbuffered output
|
169
|
+
process = subprocess.Popen(
|
170
|
+
dd_command,
|
171
|
+
stdout=subprocess.PIPE,
|
172
|
+
stderr=subprocess.PIPE,
|
173
|
+
text=True,
|
174
|
+
bufsize=1,
|
175
|
+
universal_newlines=True,
|
176
|
+
errors="replace"
|
177
|
+
)
|
178
|
+
|
179
|
+
# Regex to parse dd progress (more robust to handle variations)
|
180
|
+
pattern = re.compile(
|
181
|
+
r"(?:dd:\s*)?(?P<bytes>\d+)\s+bytes(?:\s+\(.*?\))?\s+(?:transferred|copied),?\s+[\d\.]+\s*s?,?\s+[\d\.]+\s+MB/s",
|
182
|
+
re.IGNORECASE
|
183
|
+
)
|
184
|
+
|
185
|
+
# Initialize tqdm progress bar
|
186
|
+
with tqdm(total=file_size, unit="B", unit_scale=True, desc="Copying", ncols=100) as pbar:
|
187
|
+
with open(debug_log, "w") as log_file:
|
188
|
+
while process.poll() is None:
|
189
|
+
rlist, _, _ = select.select([process.stdout, process.stderr], [], [], 0.1)
|
190
|
+
for stream in rlist:
|
191
|
+
line = stream.readline().strip()
|
192
|
+
if line:
|
193
|
+
log_file.write(f"{line}\n")
|
194
|
+
log_file.flush()
|
195
|
+
match = pattern.search(line)
|
196
|
+
if match:
|
197
|
+
bytes_transferred = int(match.group(1))
|
198
|
+
pbar.n = min(bytes_transferred, file_size)
|
199
|
+
pbar.refresh()
|
200
|
+
elif line:
|
201
|
+
click.echo(f"⚠️ dd: {line}") # Show other messages (e.g., errors)
|
202
|
+
|
203
|
+
# Capture remaining output and check for errors
|
204
|
+
stdout, stderr = process.communicate()
|
205
|
+
with open(debug_log, "a") as log_file:
|
206
|
+
if stdout.strip():
|
207
|
+
log_file.write(f"Final stdout: {stdout.strip()}\n")
|
208
|
+
if stderr.strip():
|
209
|
+
log_file.write(f"Final stderr: {stderr.strip()}\n")
|
210
|
+
|
211
|
+
if process.returncode != 0:
|
212
|
+
click.echo(f"❌ Failed to copy {image_path} to {device_path}: {stderr}")
|
213
|
+
click.echo(f"ℹ️ Check {debug_log} for raw dd output.")
|
214
|
+
sys.exit(1)
|
215
|
+
|
216
|
+
click.echo(f"✅ Successfully copied {image_path} to {device_path}")
|
217
|
+
subprocess.run(["sync"], check=True)
|
218
|
+
click.echo("✅ Synced data to device")
|
219
|
+
except subprocess.CalledProcessError as e:
|
220
|
+
click.echo(f"❌ Failed to copy {image_path} to {device_path}: {e.stderr}")
|
221
|
+
click.echo(f"ℹ️ Check {debug_log} for raw dd output.")
|
222
|
+
sys.exit(1)
|
223
|
+
except FileNotFoundError:
|
224
|
+
click.echo("❌ 'dd' not found. Ensure both are installed and accessible.")
|
225
|
+
sys.exit(1)
|
226
|
+
|
227
|
+
def write_bootimg(image_path):
|
228
|
+
"""Write a boot image to a removable device."""
|
229
|
+
# Step 1: Validate image file
|
230
|
+
if not os.path.isfile(image_path):
|
231
|
+
click.echo(f"❌ Image file {image_path} does not exist.")
|
232
|
+
sys.exit(1)
|
233
|
+
|
234
|
+
click.echo(f"✅ Valid image file: {image_path}")
|
235
|
+
|
236
|
+
# Step 2: Check if dd is installed
|
237
|
+
if not check_dd_installed():
|
238
|
+
click.echo("⚠️ 'dd' is not installed on this system.")
|
239
|
+
if platform.system() == "Darwin":
|
240
|
+
click.echo("ℹ️ On macOS, 'dd' is included by default. Check your PATH or system configuration.")
|
241
|
+
elif platform.system() == "Linux":
|
242
|
+
click.echo("ℹ️ On Linux, install 'dd' using your package manager (e.g., 'sudo apt install coreutils' on Debian/Ubuntu).")
|
243
|
+
else:
|
244
|
+
click.echo("ℹ️ Please install 'dd' for your system.")
|
245
|
+
sys.exit(1)
|
246
|
+
click.echo("✅ 'dd' is installed")
|
247
|
+
|
248
|
+
# Step 3: Detect removable devices
|
249
|
+
devices = list_removable_devices() # Assumes this function exists
|
250
|
+
if not devices:
|
251
|
+
click.echo("⚠️ No removable devices detected. Please plug in your USB drive or SD card.")
|
252
|
+
sys.exit(1)
|
253
|
+
|
254
|
+
# Step 4: Display devices
|
255
|
+
click.echo("\n🔍 Detected removable devices:")
|
256
|
+
for i, dev in enumerate(devices):
|
257
|
+
click.echo(f" [{i}] Name: {dev['name']}, Size: {dev['size']} GB, Path: {dev['path']}")
|
258
|
+
|
259
|
+
# Step 5: Select device
|
260
|
+
selected_device = None
|
261
|
+
if len(devices) == 1:
|
262
|
+
confirm = input(f"\n✅ Do you want to use device {devices[0]['path']}? (y/N): ").strip().lower()
|
263
|
+
if confirm == 'y':
|
264
|
+
selected_device = devices[0]
|
265
|
+
else:
|
266
|
+
click.echo("❌ Operation cancelled by user.")
|
267
|
+
sys.exit(0)
|
268
|
+
else:
|
269
|
+
try:
|
270
|
+
selection = int(input("\n🔢 Multiple devices found. Enter the number of the device to use: ").strip())
|
271
|
+
if 0 <= selection < len(devices):
|
272
|
+
selected_device = devices[selection]
|
273
|
+
else:
|
274
|
+
click.echo("❌ Invalid selection.")
|
275
|
+
sys.exit(1)
|
276
|
+
except ValueError:
|
277
|
+
click.echo("❌ Invalid input. Please enter a number.")
|
278
|
+
sys.exit(1)
|
279
|
+
|
280
|
+
click.echo(f"\n🚀 Proceeding with device: {selected_device['path']}")
|
281
|
+
|
282
|
+
# Step 6: Unmount the selected device
|
283
|
+
unmount_device(selected_device['path'])
|
284
|
+
|
285
|
+
# Step 7: Copy the image to the device
|
286
|
+
copy_image_to_device(image_path, selected_device['path'])
|
287
|
+
|
288
|
+
# Step 8: Eject the device (platform-dependent)
|
289
|
+
try:
|
290
|
+
if platform.system() == "Darwin":
|
291
|
+
subprocess.run(["diskutil", "eject", selected_device['path']], check=True, capture_output=True, text=True)
|
292
|
+
click.echo(f"✅ Ejected {selected_device['path']} on macOS")
|
293
|
+
elif platform.system() == "Linux":
|
294
|
+
click.echo("ℹ️ Linux does not require explicit eject. Device is ready.")
|
295
|
+
else:
|
296
|
+
click.echo("ℹ️ Please manually eject the device if required.")
|
297
|
+
except subprocess.CalledProcessError as e:
|
298
|
+
click.echo(f"⚠️ Failed to eject {selected_device['path']}: {e.stderr}")
|
299
|
+
click.echo("ℹ️ Please manually eject the device.")
|
300
|
+
|
301
|
+
|
302
|
+
def write_image(version: str, board: str, swtype: str, internal: bool = False):
|
303
|
+
"""
|
304
|
+
Download and write a bootable firmware image to a removable storage device.
|
305
|
+
|
306
|
+
Parameters:
|
307
|
+
version (str): Firmware version to download (e.g., "1.6.0").
|
308
|
+
board (str): Target board type, e.g., "modalix" or "mlsoc".
|
309
|
+
swtype (str): Software image type, e.g., "yocto" or "exlr".
|
310
|
+
internal (bool): Whether to use internal download sources. Defaults to False.
|
311
|
+
|
312
|
+
Raises:
|
313
|
+
RuntimeError: If the download or write process fails.
|
314
|
+
"""
|
315
|
+
try:
|
316
|
+
click.echo(f"⬇️ Downloading boot image for version: {version}, board: {board}, swtype: {swtype}")
|
317
|
+
file_list = download_image(version, board, swtype, internal, update_type='bootimg')
|
318
|
+
if not isinstance(file_list, list):
|
319
|
+
raise ValueError("Expected list of extracted files, got something else.")
|
320
|
+
|
321
|
+
wic_file = next((f for f in file_list if f.endswith(".wic")), None)
|
322
|
+
if not wic_file:
|
323
|
+
raise FileNotFoundError("No .wic image file found after extraction.")
|
324
|
+
|
325
|
+
except Exception as e:
|
326
|
+
raise RuntimeError(f"❌ Failed to download image: {e}")
|
327
|
+
|
328
|
+
try:
|
329
|
+
click.echo(f"📝 Writing image to removable media: {wic_file}")
|
330
|
+
write_bootimg(wic_file)
|
331
|
+
except Exception as e:
|
332
|
+
raise RuntimeError(f"❌ Failed to write image: {e}")
|
333
|
+
|
334
|
+
if __name__ == "__main__":
|
335
|
+
if len(sys.argv) != 2:
|
336
|
+
click.echo("❌ Usage: python write_bootimg.py <image_file>")
|
337
|
+
sys.exit(1)
|
338
|
+
|
339
|
+
write_image('1.7.0', 'modalix', 'davinci', True)
|