taotoolkit 1.0.0__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.
- taotoolkit/__init__.py +81 -0
- taotoolkit/asrVrf.py +322 -0
- taotoolkit/tao_camDvt.py +1186 -0
- taotoolkit/tao_cmdTool.py +626 -0
- taotoolkit/tao_common.py +435 -0
- taotoolkit/tao_device.py +1011 -0
- taotoolkit/tao_flash.py +823 -0
- taotoolkit/tao_tuning.py +1344 -0
- taotoolkit/tao_vrfTool.py +690 -0
- taotoolkit-1.0.0.dist-info/METADATA +50 -0
- taotoolkit-1.0.0.dist-info/RECORD +13 -0
- taotoolkit-1.0.0.dist-info/WHEEL +5 -0
- taotoolkit-1.0.0.dist-info/top_level.txt +1 -0
taotoolkit/tao_flash.py
ADDED
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- encoding: utf-8 -*-
|
|
3
|
+
"""版本下载与烧录相关命令模块。"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import shutil
|
|
8
|
+
import zipfile
|
|
9
|
+
import argparse
|
|
10
|
+
import requests
|
|
11
|
+
import re
|
|
12
|
+
import jenkins # pip install python-jenkins
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from time import sleep
|
|
16
|
+
from bs4 import BeautifulSoup as bs
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from .tao_common import (
|
|
20
|
+
debugCtrl,
|
|
21
|
+
eExit,
|
|
22
|
+
logi,
|
|
23
|
+
logw,
|
|
24
|
+
loge,
|
|
25
|
+
logd,
|
|
26
|
+
logh,
|
|
27
|
+
oCmd,
|
|
28
|
+
rCmd,
|
|
29
|
+
pCmd,
|
|
30
|
+
refreshWait,
|
|
31
|
+
inputSerial,
|
|
32
|
+
createFolder,
|
|
33
|
+
findFilesBySuffixKeyword,
|
|
34
|
+
sysVersion,
|
|
35
|
+
normalize_args,
|
|
36
|
+
register_command,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from .tao_device import DevOp
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class jenkinsTool:
|
|
43
|
+
# logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
44
|
+
def __init__(self, branch, buildTag, ciOwner, isUserDebug="1", jenkinsUser=None, jenkinsPassword=None):
|
|
45
|
+
self.jenkinsUrl = "http://build.asrmicro.com/"
|
|
46
|
+
self.jenkinsUser = jenkinsUser if jenkinsUser else os.environ.get("JENKINS_USERNAME", "")
|
|
47
|
+
self.jenkinsPassword = jenkinsPassword if jenkinsPassword else os.environ.get("JENKINS_PASSWORD", "")
|
|
48
|
+
self.baseUrl = "http://10.1.24.40/"
|
|
49
|
+
self.branch = branch
|
|
50
|
+
self.buildTag = buildTag
|
|
51
|
+
self.ciOwner = ciOwner
|
|
52
|
+
self.isUserDebug = True if isUserDebug == "1" else False
|
|
53
|
+
self.packageFile = "release.zip"
|
|
54
|
+
self.retainUserData = False # 是否保留用户数据,默认不保留
|
|
55
|
+
if sysVersion == "Linux":
|
|
56
|
+
self.downloadPath = "/home/local/Downloads/jenkin" # 下载路径和解压路径
|
|
57
|
+
self.extractPath = "/home/local/Downloads/jenkin"
|
|
58
|
+
self.aimgTool = "aimgtool"
|
|
59
|
+
elif sysVersion == "Windows":
|
|
60
|
+
self.downloadPath = r"D:\jenkin" # 下载路径和解压路径
|
|
61
|
+
self.extractPath = r"D:\jenkin"
|
|
62
|
+
self.aimgTool = "aimgtool.exe"
|
|
63
|
+
# logi(f"download package to {self.downloadPath}")
|
|
64
|
+
|
|
65
|
+
def pickBuildBoard(self, build_info, jobName):
|
|
66
|
+
logi(f"在 branch[{jobName}] buildID[{build_info['number']}] 找到如下版本,请选择一个序号下载:")
|
|
67
|
+
filtered_list = []
|
|
68
|
+
logd(f"artifacts: {build_info['artifacts']}")
|
|
69
|
+
for i in build_info["artifacts"]:
|
|
70
|
+
logd(f"relativePath: {i['relativePath']}")
|
|
71
|
+
if i["fileName"].endswith(self.packageFile):
|
|
72
|
+
if self.isUserDebug:
|
|
73
|
+
if "userdebug" in i["relativePath"]:
|
|
74
|
+
filtered_list.append(i["relativePath"].split("/")[1])
|
|
75
|
+
else:
|
|
76
|
+
filtered_list.append(i["relativePath"].split("/")[1])
|
|
77
|
+
|
|
78
|
+
foundBoard = {key: index for key, index in enumerate(filtered_list)}
|
|
79
|
+
if len(foundBoard) == 0:
|
|
80
|
+
buildType = "userdebug" if self.isUserDebug else "release"
|
|
81
|
+
eExit(f"no board found in {jobName} buildID {build_info['number']} buildType {buildType}")
|
|
82
|
+
for k, v in foundBoard.items():
|
|
83
|
+
print(k, v)
|
|
84
|
+
|
|
85
|
+
pickBoard = foundBoard[inputSerial(0, len(foundBoard) - 1)]
|
|
86
|
+
zip_artifact = next(
|
|
87
|
+
a
|
|
88
|
+
for a in build_info["artifacts"]
|
|
89
|
+
if a["fileName"].endswith(self.packageFile) and pickBoard in a["relativePath"]
|
|
90
|
+
)
|
|
91
|
+
zip_url = f"{self.jenkinsUrl}/job/{jobName}/{build_info['number']}/artifact/{zip_artifact['relativePath']}"
|
|
92
|
+
# print(pickBoard,zip_url)
|
|
93
|
+
return zip_url
|
|
94
|
+
|
|
95
|
+
def getZipArtifact(self, build_info, jobName, buildBoard):
|
|
96
|
+
zipList = list()
|
|
97
|
+
|
|
98
|
+
logd(f"artifacts: {build_info['artifacts']}")
|
|
99
|
+
for a in build_info["artifacts"]:
|
|
100
|
+
if (
|
|
101
|
+
a["fileName"].endswith(self.packageFile)
|
|
102
|
+
and buildBoard in a["relativePath"]
|
|
103
|
+
and "xts" not in a["relativePath"]
|
|
104
|
+
):
|
|
105
|
+
if self.isUserDebug:
|
|
106
|
+
if "userdebug" in a["relativePath"]:
|
|
107
|
+
zipList.append(a)
|
|
108
|
+
else:
|
|
109
|
+
zipList.append(a)
|
|
110
|
+
|
|
111
|
+
if len(zipList) == 1:
|
|
112
|
+
zip_artifact = zipList[0]
|
|
113
|
+
elif len(zipList) == 0:
|
|
114
|
+
loge(f"find {jobName} board[{buildBoard}] failed")
|
|
115
|
+
zip_url = self.pickBuildBoard(build_info, jobName)
|
|
116
|
+
return zip_url
|
|
117
|
+
else:
|
|
118
|
+
print(f"找到如下包含 {buildBoard} 的版本,请选择一个board用于下载:")
|
|
119
|
+
for index, value in enumerate(zipList):
|
|
120
|
+
print(f"{index}: {value['relativePath']}")
|
|
121
|
+
zip_artifact = zipList[inputSerial(0, len(zipList) - 1)]
|
|
122
|
+
|
|
123
|
+
zip_url = f"{self.jenkinsUrl}/job/{jobName}/{build_info['number']}/artifact/{zip_artifact['relativePath']}"
|
|
124
|
+
return zip_url
|
|
125
|
+
|
|
126
|
+
def _getFileserverUrl(self, jobName, buildNum, buildBoard):
|
|
127
|
+
url = f"{self.baseUrl}{jobName}/{buildNum}/{buildBoard}/{self.packageFile}"
|
|
128
|
+
return url
|
|
129
|
+
|
|
130
|
+
def _verifyFileserverUrl(self, url):
|
|
131
|
+
try:
|
|
132
|
+
resp = requests.head(url, auth=(self.jenkinsUser, self.jenkinsPassword), timeout=10)
|
|
133
|
+
return resp.status_code == 200
|
|
134
|
+
except Exception:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def _listFileserverBoards(self, jobName, buildNum):
|
|
138
|
+
url = f"{self.baseUrl}{jobName}/{buildNum}/"
|
|
139
|
+
try:
|
|
140
|
+
resp = requests.get(url, auth=(self.jenkinsUser, self.jenkinsPassword), timeout=10)
|
|
141
|
+
resp.raise_for_status()
|
|
142
|
+
soup = bs(resp.text, "html.parser")
|
|
143
|
+
boards = []
|
|
144
|
+
for a in soup.find_all("a", href=True):
|
|
145
|
+
href = str(a["href"])
|
|
146
|
+
if href in ("/", "../", "?") or href.startswith("?"):
|
|
147
|
+
continue
|
|
148
|
+
if href.endswith("/"):
|
|
149
|
+
name = href.rstrip("/")
|
|
150
|
+
if "-" in name and "sp01" in name:
|
|
151
|
+
boards.append(name)
|
|
152
|
+
return boards
|
|
153
|
+
except Exception as e:
|
|
154
|
+
loge(f"列出文件服务器 board 失败: {e}")
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
def findTargetBuild(
|
|
158
|
+
self, buildNum=0, triggerUser="taotaoli", jobName="adroid16.0_ci", buildBoard="larkpro_phone-sp01-userdebug"
|
|
159
|
+
):
|
|
160
|
+
getDB = "ci" not in jobName
|
|
161
|
+
server = jenkins.Jenkins(self.jenkinsUrl, self.jenkinsUser, self.jenkinsPassword)
|
|
162
|
+
buildNum = int(buildNum)
|
|
163
|
+
zip_url = ""
|
|
164
|
+
build_info = None
|
|
165
|
+
|
|
166
|
+
if buildNum != 0:
|
|
167
|
+
try:
|
|
168
|
+
logi(f"getting build info for {jobName} buildNum {buildNum}")
|
|
169
|
+
build_info = server.get_build_info(jobName, buildNum)
|
|
170
|
+
|
|
171
|
+
if build_info.get("building"):
|
|
172
|
+
eExit(f"build {buildNum} is still running, please wait")
|
|
173
|
+
|
|
174
|
+
logi(f"构建状态: {build_info.get('result')}")
|
|
175
|
+
logd(f"artifacts: {build_info.get('artifacts')}")
|
|
176
|
+
|
|
177
|
+
if build_info.get("artifacts"):
|
|
178
|
+
# ── 正常走 Jenkins artifact ──
|
|
179
|
+
zip_url = self.getZipArtifact(build_info, jobName, buildBoard)
|
|
180
|
+
else:
|
|
181
|
+
# ── artifact 已被清理,回退到文件服务器 ──
|
|
182
|
+
logw(f"build #{buildNum} artifact 已被清理,尝试从文件服务器下载")
|
|
183
|
+
zip_url = self._fallbackFileserver(jobName, buildNum, buildBoard)
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
loge(f"获取 build info 失败: {str(e).splitlines()[0]}")
|
|
187
|
+
if build_info and build_info.get("artifacts"):
|
|
188
|
+
zip_url = self.pickBuildBoard(build_info, jobName)
|
|
189
|
+
else:
|
|
190
|
+
# build_info 获取失败或 artifact 为空,直接试文件服务器
|
|
191
|
+
logw("尝试直接从文件服务器获取")
|
|
192
|
+
zip_url = self._fallbackFileserver(jobName, buildNum, buildBoard)
|
|
193
|
+
finally:
|
|
194
|
+
if not zip_url:
|
|
195
|
+
eExit(f"find {buildNum} {jobName} board[{buildBoard}] failed")
|
|
196
|
+
return str(build_info["number"]) if build_info else str(buildNum), zip_url
|
|
197
|
+
|
|
198
|
+
else:
|
|
199
|
+
if getDB:
|
|
200
|
+
try:
|
|
201
|
+
# 先获取最后一次完成构建的编号
|
|
202
|
+
last_build_number = server.get_job_info(jobName)["lastCompletedBuild"]["number"]
|
|
203
|
+
# 再用编号获取详细信息
|
|
204
|
+
build_info = server.get_build_info(jobName, last_build_number)
|
|
205
|
+
|
|
206
|
+
build_time_local = time.strftime(
|
|
207
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(build_info["timestamp"] / 1000)
|
|
208
|
+
)
|
|
209
|
+
if build_info["result"] != "SUCCESS":
|
|
210
|
+
logw(
|
|
211
|
+
f"目标构建号: {build_info['number']}, 状态: {build_info['result']}, 时间: {build_time_local}"
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
logi(
|
|
215
|
+
f"目标构建号: {build_info['number']}, 状态: {build_info['result']}, 时间: {build_time_local}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if build_info.get("artifacts"):
|
|
219
|
+
zip_url = self.getZipArtifact(build_info, jobName, buildBoard)
|
|
220
|
+
else:
|
|
221
|
+
logw(f"最新 build #{build_info['number']} artifact 已清理,回退文件服务器")
|
|
222
|
+
zip_url = self._fallbackFileserver(jobName, build_info["number"], buildBoard)
|
|
223
|
+
|
|
224
|
+
if not zip_url:
|
|
225
|
+
raise Exception("no zip found")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
loge(f"find {jobName} board[{buildBoard}] newest DB failed: {e}")
|
|
228
|
+
if build_info and build_info.get("artifacts"):
|
|
229
|
+
zip_url = self.pickBuildBoard(build_info, jobName)
|
|
230
|
+
elif build_info:
|
|
231
|
+
zip_url = self._fallbackFileserver(jobName, build_info["number"], buildBoard)
|
|
232
|
+
|
|
233
|
+
if build_info:
|
|
234
|
+
return str(build_info["number"]), zip_url
|
|
235
|
+
else:
|
|
236
|
+
eExit(f"find {jobName} board[{buildBoard}] failed")
|
|
237
|
+
|
|
238
|
+
else:
|
|
239
|
+
# 获取指定用户最新 CI(逻辑不变,artifact 为空时同样回退)
|
|
240
|
+
builds = server.get_job_info(jobName, fetch_all_builds=True)["builds"]
|
|
241
|
+
buildTmp = None
|
|
242
|
+
for build in builds:
|
|
243
|
+
build_info = server.get_build_info(jobName, build["number"])
|
|
244
|
+
actions = build_info.get("actions", [])
|
|
245
|
+
triggered_by = next(
|
|
246
|
+
(action.get("causes", [{}])[0].get("userId", "") for action in actions if "causes" in action),
|
|
247
|
+
"",
|
|
248
|
+
)
|
|
249
|
+
if triggerUser and triggered_by != triggerUser:
|
|
250
|
+
continue
|
|
251
|
+
if build_info["result"] != "SUCCESS":
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
build_time_local = time.strftime(
|
|
255
|
+
"%Y-%m-%d %H:%M:%S", time.localtime(build_info["timestamp"] / 1000)
|
|
256
|
+
)
|
|
257
|
+
logi(f"目标构建号: {build_info['number']}, 状态: {build_info['result']}, 时间: {build_time_local}")
|
|
258
|
+
buildTmp = build_info
|
|
259
|
+
|
|
260
|
+
if build_info.get("artifacts"):
|
|
261
|
+
zip_url = self.getZipArtifact(build_info, jobName, buildBoard)
|
|
262
|
+
else:
|
|
263
|
+
logw(f"build #{build_info['number']} artifact 已清理,回退文件服务器")
|
|
264
|
+
zip_url = self._fallbackFileserver(jobName, build_info["number"], buildBoard)
|
|
265
|
+
|
|
266
|
+
if zip_url:
|
|
267
|
+
return str(build_info["number"]), zip_url
|
|
268
|
+
|
|
269
|
+
logw(f"find {triggerUser} {jobName} board[{buildBoard}] newest CI failed")
|
|
270
|
+
if buildTmp:
|
|
271
|
+
if buildTmp.get("artifacts"):
|
|
272
|
+
zip_url = self.pickBuildBoard(buildTmp, jobName)
|
|
273
|
+
else:
|
|
274
|
+
zip_url = self._fallbackFileserver(jobName, buildTmp["number"], buildBoard)
|
|
275
|
+
return str(buildTmp["number"]), zip_url
|
|
276
|
+
else:
|
|
277
|
+
eExit("no build found")
|
|
278
|
+
|
|
279
|
+
def _fallbackFileserver(self, jobName, buildNum, buildBoard):
|
|
280
|
+
"""
|
|
281
|
+
artifact 为空时从文件服务器获取下载 URL
|
|
282
|
+
优先精确匹配 buildBoard,找不到时列出所有 board 让用户选择
|
|
283
|
+
"""
|
|
284
|
+
url = self._getFileserverUrl(jobName, buildNum, buildBoard)
|
|
285
|
+
logd(f"尝试文件服务器: {url}")
|
|
286
|
+
|
|
287
|
+
if self._verifyFileserverUrl(url):
|
|
288
|
+
logi(f"文件服务器找到: {url}")
|
|
289
|
+
return url
|
|
290
|
+
|
|
291
|
+
# 精确匹配失败,列出可用 board 让用户选
|
|
292
|
+
logw(f"文件服务器未找到 {buildBoard},列出可用 board:")
|
|
293
|
+
boards = self._listFileserverBoards(jobName, buildNum)
|
|
294
|
+
|
|
295
|
+
if not boards:
|
|
296
|
+
loge(f"文件服务器 {self.baseUrl}{jobName}/{buildNum}/ 下没有找到任何 board")
|
|
297
|
+
return ""
|
|
298
|
+
|
|
299
|
+
# 先尝试模糊匹配(board 名包含 buildBoard 关键词)
|
|
300
|
+
keyword = buildBoard.split("-")[0] # 如 larkpro_phone
|
|
301
|
+
matched = [b for b in boards if keyword in b]
|
|
302
|
+
|
|
303
|
+
if len(matched) == 1:
|
|
304
|
+
logi(f"自动匹配到: {matched[0]}")
|
|
305
|
+
return self._getFileserverUrl(jobName, buildNum, matched[0])
|
|
306
|
+
|
|
307
|
+
# 需要用户手动选择
|
|
308
|
+
candidates = matched if matched else boards
|
|
309
|
+
if self.isUserDebug:
|
|
310
|
+
candidates = [b for b in candidates if "userdebug" in b] or candidates
|
|
311
|
+
|
|
312
|
+
print(f"\n在文件服务器找到如下 board,请选择:")
|
|
313
|
+
for i, b in enumerate(candidates):
|
|
314
|
+
print(f" {i}: {b}")
|
|
315
|
+
chosen = candidates[inputSerial(0, len(candidates) - 1)]
|
|
316
|
+
return self._getFileserverUrl(jobName, buildNum, chosen)
|
|
317
|
+
|
|
318
|
+
def getHref(self, rooturl):
|
|
319
|
+
headers = {
|
|
320
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
|
321
|
+
}
|
|
322
|
+
sleep(1)
|
|
323
|
+
with requests.Session() as session:
|
|
324
|
+
response = requests.get(rooturl, headers=headers, auth=(self.jenkinsUser, self.jenkinsPassword))
|
|
325
|
+
soup = bs(response.text, features="html.parser")
|
|
326
|
+
result = []
|
|
327
|
+
for link in soup.find_all("a"):
|
|
328
|
+
result.append(link.get("href"))
|
|
329
|
+
return result
|
|
330
|
+
|
|
331
|
+
def getJenkinsBoard(self, rooturl):
|
|
332
|
+
# 默认解码方式utf-8
|
|
333
|
+
logd(rooturl)
|
|
334
|
+
result = self.getHref(rooturl)
|
|
335
|
+
|
|
336
|
+
filtered_list = [s for s in result if "-" in s and "/" in s]
|
|
337
|
+
dict_from_list = {key: index for key, index in enumerate(filtered_list)}
|
|
338
|
+
logd(dict_from_list)
|
|
339
|
+
if dict_from_list == {}:
|
|
340
|
+
eExit(f"No board found in {rooturl}")
|
|
341
|
+
return dict_from_list
|
|
342
|
+
|
|
343
|
+
def browserDownload(self, url, timeOut=600):
|
|
344
|
+
if sysVersion == "Linux":
|
|
345
|
+
browser = "wget"
|
|
346
|
+
else:
|
|
347
|
+
browser = "C:\\Program Files\\Mozilla Firefox\\firefox.exe -headless"
|
|
348
|
+
# browser = "C:\\Program Files\\Mozilla Firefox\\firefox.exe"
|
|
349
|
+
info, err = pCmd(f"{browser} {url}", showCmd=True, showRet=False, timeOut=timeOut, logLevel="info")
|
|
350
|
+
# logi(err)
|
|
351
|
+
if "ERROR 404: Not Found" in err:
|
|
352
|
+
eExit("ERROR 404: Not Found")
|
|
353
|
+
|
|
354
|
+
def downloadByRequest(self, url, save_path, useSession=False):
|
|
355
|
+
"""下载需要认证的文件(支持断点续传)"""
|
|
356
|
+
|
|
357
|
+
total_size = 0
|
|
358
|
+
if os.path.exists(save_path):
|
|
359
|
+
downloaded = os.path.getsize(save_path)
|
|
360
|
+
else:
|
|
361
|
+
downloaded = 0
|
|
362
|
+
open(save_path, "wb").close() # 创建空文件
|
|
363
|
+
|
|
364
|
+
# 设置断点续传头部
|
|
365
|
+
headers = {"Range": f"bytes={downloaded}-"} if downloaded else {}
|
|
366
|
+
CHUNK = 4 * 1024 * 1024 # 4MB per chunk
|
|
367
|
+
if useSession:
|
|
368
|
+
session = requests.Session()
|
|
369
|
+
session.auth = (self.jenkinsUser, self.jenkinsPassword)
|
|
370
|
+
session.headers.update(headers)
|
|
371
|
+
try:
|
|
372
|
+
with session.get(url, stream=True, timeout=30) as response:
|
|
373
|
+
response.raise_for_status() # 检查HTTP错误
|
|
374
|
+
if response.status_code == 401:
|
|
375
|
+
raise PermissionError("认证失败,请检查账户密码")
|
|
376
|
+
|
|
377
|
+
# 获取文件总大小(考虑断点续传)
|
|
378
|
+
total_size = int(response.headers.get("content-length", 0)) + downloaded
|
|
379
|
+
with open(save_path, "ab") as f:
|
|
380
|
+
for chunk in response.iter_content(chunk_size=CHUNK):
|
|
381
|
+
if chunk: # 过滤空块
|
|
382
|
+
f.write(chunk)
|
|
383
|
+
downloaded += len(chunk)
|
|
384
|
+
progress = downloaded / total_size * 100 if total_size > 0 else 0
|
|
385
|
+
sys.stdout.write(
|
|
386
|
+
f"\r下载进度: {progress:.1f}% ({downloaded/1024/1024:.1f}/{total_size/1024/1024:.1f} MB)"
|
|
387
|
+
)
|
|
388
|
+
sys.stdout.flush()
|
|
389
|
+
print("\r")
|
|
390
|
+
logd(f"\n文件已保存至: {os.path.abspath(save_path)}")
|
|
391
|
+
|
|
392
|
+
except requests.exceptions.RequestException as e:
|
|
393
|
+
loge(f"下载失败: {str(e)}")
|
|
394
|
+
if os.path.getsize(save_path) == 0:
|
|
395
|
+
os.remove(save_path) # 删除空文件
|
|
396
|
+
exit()
|
|
397
|
+
else:
|
|
398
|
+
try:
|
|
399
|
+
with requests.get(
|
|
400
|
+
url, headers=headers, stream=True, auth=(self.jenkinsUser, self.jenkinsPassword), timeout=30
|
|
401
|
+
) as response:
|
|
402
|
+
response.raise_for_status() # 检查HTTP错误
|
|
403
|
+
if response.status_code == 401:
|
|
404
|
+
raise PermissionError("认证失败,请检查账户密码")
|
|
405
|
+
|
|
406
|
+
# 获取文件总大小(考虑断点续传)
|
|
407
|
+
total_size = int(response.headers.get("content-length", 0)) + downloaded
|
|
408
|
+
with open(save_path, "ab") as f:
|
|
409
|
+
for chunk in response.iter_content(chunk_size=CHUNK):
|
|
410
|
+
if chunk: # 过滤空块
|
|
411
|
+
f.write(chunk)
|
|
412
|
+
downloaded += len(chunk)
|
|
413
|
+
progress = downloaded / total_size * 100 if total_size > 0 else 0
|
|
414
|
+
sys.stdout.write(
|
|
415
|
+
f"\r下载进度: {progress:.1f}% ({downloaded/1024/1024:.1f}/{total_size/1024/1024:.1f} MB)"
|
|
416
|
+
)
|
|
417
|
+
sys.stdout.flush()
|
|
418
|
+
print("\r")
|
|
419
|
+
logd(f"\n文件已保存至: {os.path.abspath(save_path)}")
|
|
420
|
+
|
|
421
|
+
except requests.exceptions.RequestException as e:
|
|
422
|
+
loge(f"下载失败: {str(e)}")
|
|
423
|
+
if os.path.getsize(save_path) == 0:
|
|
424
|
+
os.remove(save_path) # 删除空文件
|
|
425
|
+
exit()
|
|
426
|
+
return total_size
|
|
427
|
+
|
|
428
|
+
def downloadByRequestMutithread(self, url, save_path):
|
|
429
|
+
"""下载需要认证的文件(支持断点续传 + 多线程分块)"""
|
|
430
|
+
auth = (self.jenkinsUser, self.jenkinsPassword)
|
|
431
|
+
|
|
432
|
+
# 先获取文件总大小,顺便验证认证
|
|
433
|
+
head = requests.head(url, auth=auth, timeout=10)
|
|
434
|
+
if head.status_code == 401:
|
|
435
|
+
raise PermissionError("认证失败,请检查账户密码")
|
|
436
|
+
head.raise_for_status()
|
|
437
|
+
|
|
438
|
+
total_size = int(head.headers.get("content-length", 0))
|
|
439
|
+
supports_range = head.headers.get("Accept-Ranges", "") == "bytes"
|
|
440
|
+
|
|
441
|
+
CHUNK = 4 * 1024 * 1024 # 4MB per chunk
|
|
442
|
+
if supports_range and total_size > 0:
|
|
443
|
+
self._download_multithread(url, save_path, total_size, auth, chunk_size=CHUNK)
|
|
444
|
+
else:
|
|
445
|
+
self._download_single(url, save_path, auth, chunk_size=CHUNK)
|
|
446
|
+
|
|
447
|
+
return total_size
|
|
448
|
+
|
|
449
|
+
def _download_multithread(self, url, save_path, total_size, auth, workers=8, chunk_size=1024 * 1024):
|
|
450
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
451
|
+
|
|
452
|
+
"""并发分块下载,最后按顺序写入"""
|
|
453
|
+
chunk_count = (total_size + chunk_size - 1) // chunk_size
|
|
454
|
+
ranges = [(i * chunk_size, min((i + 1) * chunk_size - 1, total_size - 1)) for i in range(chunk_count)]
|
|
455
|
+
|
|
456
|
+
# 预分配文件大小(避免碎片)
|
|
457
|
+
with open(save_path, "wb") as f:
|
|
458
|
+
f.seek(total_size - 1)
|
|
459
|
+
f.write(b"\0")
|
|
460
|
+
|
|
461
|
+
write_lock = threading.Lock()
|
|
462
|
+
downloaded_bytes = [0]
|
|
463
|
+
|
|
464
|
+
def fetch_and_write(idx, start, end):
|
|
465
|
+
headers = {"Range": f"bytes={start}-{end}"}
|
|
466
|
+
r = requests.get(url, headers=headers, auth=auth, timeout=60)
|
|
467
|
+
r.raise_for_status()
|
|
468
|
+
data = r.content
|
|
469
|
+
|
|
470
|
+
# 直接写到正确的文件偏移位置,无需排序等待
|
|
471
|
+
with write_lock:
|
|
472
|
+
with open(save_path, "r+b") as f:
|
|
473
|
+
f.seek(start)
|
|
474
|
+
f.write(data)
|
|
475
|
+
downloaded_bytes[0] += len(data)
|
|
476
|
+
pct = downloaded_bytes[0] / total_size * 100
|
|
477
|
+
print(
|
|
478
|
+
f"\r进度: {pct:.1f}% ({downloaded_bytes[0]/1024/1024:.1f}/{total_size/1024/1024:.1f} MB)",
|
|
479
|
+
end="",
|
|
480
|
+
flush=True,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
with ThreadPoolExecutor(max_workers=workers) as pool:
|
|
484
|
+
futures = [pool.submit(fetch_and_write, i, s, e) for i, (s, e) in enumerate(ranges)]
|
|
485
|
+
for f in as_completed(futures):
|
|
486
|
+
f.result() # 抛出异常(如有)
|
|
487
|
+
print()
|
|
488
|
+
|
|
489
|
+
def _download_single(self, url, save_path, auth, chunk_size=1024 * 1024):
|
|
490
|
+
"""单线程大 chunk 下载(服务端不支持 Range 时回退)"""
|
|
491
|
+
downloaded = os.path.getsize(save_path) if os.path.exists(save_path) else 0
|
|
492
|
+
headers = {"Range": f"bytes={downloaded}-"} if downloaded else {}
|
|
493
|
+
|
|
494
|
+
with requests.get(url, headers=headers, stream=True, auth=auth, timeout=30) as r:
|
|
495
|
+
if r.status_code == 401:
|
|
496
|
+
raise PermissionError("认证失败,请检查账户密码")
|
|
497
|
+
r.raise_for_status()
|
|
498
|
+
total = int(r.headers.get("content-length", 0)) + downloaded
|
|
499
|
+
with open(save_path, "ab") as f:
|
|
500
|
+
for chunk in r.iter_content(chunk_size=chunk_size): # 4MB chunks
|
|
501
|
+
if chunk:
|
|
502
|
+
f.write(chunk)
|
|
503
|
+
downloaded += len(chunk)
|
|
504
|
+
pct = downloaded / total * 100 if total else 0
|
|
505
|
+
print(f"\r下载进度: {pct:.1f}%", end="", flush=True)
|
|
506
|
+
print()
|
|
507
|
+
|
|
508
|
+
def getPackage(self, url, dest_path):
|
|
509
|
+
"""从 Jenkins 下载文件"""
|
|
510
|
+
|
|
511
|
+
logd(f"{url} {dest_path}")
|
|
512
|
+
start = time.time()
|
|
513
|
+
|
|
514
|
+
windowsDownloadPath = r"D:\jenkin"
|
|
515
|
+
if sysVersion == "Linux":
|
|
516
|
+
targetZip = os.path.join(os.getcwd(), self.packageFile)
|
|
517
|
+
else:
|
|
518
|
+
targetZip = os.path.join(windowsDownloadPath, self.packageFile)
|
|
519
|
+
partFile = findFilesBySuffixKeyword(windowsDownloadPath, ".part", "release")
|
|
520
|
+
for i in partFile:
|
|
521
|
+
os.remove(targetZip)
|
|
522
|
+
|
|
523
|
+
if os.path.exists(targetZip):
|
|
524
|
+
os.remove(targetZip)
|
|
525
|
+
|
|
526
|
+
logi(f"download {url}")
|
|
527
|
+
totalSize = self.downloadByRequest(url, targetZip)
|
|
528
|
+
# totalSize = self.downloadByRequestMutithread(url, targetZip)
|
|
529
|
+
shutil.move(targetZip, dest_path)
|
|
530
|
+
end = time.time()
|
|
531
|
+
costTime = end - start
|
|
532
|
+
downloadSpeed = totalSize / 1024 / 1024 / costTime if costTime > 0 else 0
|
|
533
|
+
logi(f"Download cost: {costTime:.2f}秒, 平均速度: {downloadSpeed:.2f} MB/s")
|
|
534
|
+
|
|
535
|
+
def extractPackage(self, file_path, extract_to):
|
|
536
|
+
"""解压下载的文件"""
|
|
537
|
+
fpath = os.path.join(extract_to, file_path[:-4])
|
|
538
|
+
logi(f"Extracting file {os.path.split(file_path)[1]} to {fpath}")
|
|
539
|
+
|
|
540
|
+
createFolder(extract_to)
|
|
541
|
+
|
|
542
|
+
if file_path.endswith(".zip"):
|
|
543
|
+
try:
|
|
544
|
+
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
|
545
|
+
zip_ref.extractall(extract_to)
|
|
546
|
+
except zipfile.BadZipfile:
|
|
547
|
+
eExit(f"{file_path} is a bad zip file")
|
|
548
|
+
else:
|
|
549
|
+
eExit(f"Unsupported file format: {file_path}")
|
|
550
|
+
|
|
551
|
+
def getCfg(self, boardTag, imgPath):
|
|
552
|
+
fileList = os.listdir(imgPath)
|
|
553
|
+
cfgFlag = boardTag.split("-")[0]
|
|
554
|
+
boardList = list()
|
|
555
|
+
for i in fileList:
|
|
556
|
+
if i.endswith(".cfg"):
|
|
557
|
+
if cfgFlag in i:
|
|
558
|
+
boardList.append(i)
|
|
559
|
+
if len(boardList) == 1:
|
|
560
|
+
return boardList[0]
|
|
561
|
+
elif len(boardList) == 0:
|
|
562
|
+
logw(f"get {boardTag} table cfg failed")
|
|
563
|
+
return None
|
|
564
|
+
else:
|
|
565
|
+
print("请选择一个cfg用于烧录版本:")
|
|
566
|
+
for index, value in enumerate(boardList):
|
|
567
|
+
print(f"{index}: {value}")
|
|
568
|
+
return boardList[inputSerial(0, len(boardList) - 1)]
|
|
569
|
+
|
|
570
|
+
def getInfo(self, devSerial):
|
|
571
|
+
if devSerial != None:
|
|
572
|
+
cmd = f"adb -s {devSerial} reboot download"
|
|
573
|
+
else:
|
|
574
|
+
cmd = "adb reboot download"
|
|
575
|
+
rCmd(cmd, showCmd=True, logLevel="debug")
|
|
576
|
+
info = rCmd(f"./{self.aimgTool} portscan", timeOut=5)[0]
|
|
577
|
+
sleep(0.5)
|
|
578
|
+
if "usbdownload" in info:
|
|
579
|
+
return True
|
|
580
|
+
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
def replace_last_Y_with_N(self, filename, search_string):
|
|
584
|
+
"""
|
|
585
|
+
在文件中找到包含 search_string 的行,并将该行最后一个字符 Y 替换为 N。
|
|
586
|
+
(假设行末没有多余空格,且最后一个字符为 Y)
|
|
587
|
+
"""
|
|
588
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
589
|
+
lines = f.readlines()
|
|
590
|
+
|
|
591
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
592
|
+
for line in lines:
|
|
593
|
+
if search_string in line:
|
|
594
|
+
# 去掉末尾换行符,便于操作
|
|
595
|
+
stripped = line.rstrip("\n")
|
|
596
|
+
# 如果最后一个字符是 Y,则替换
|
|
597
|
+
if stripped and stripped[-1] == "Y":
|
|
598
|
+
stripped = stripped[:-1] + "N"
|
|
599
|
+
line = stripped + "\n" # 恢复换行符
|
|
600
|
+
f.write(line)
|
|
601
|
+
|
|
602
|
+
def downloadToBoard(self, boardTag, imgPath, devSerial):
|
|
603
|
+
logd(f"{boardTag} {imgPath}")
|
|
604
|
+
os.chdir(imgPath)
|
|
605
|
+
os.chmod(self.aimgTool, 0o755)
|
|
606
|
+
cfgList = []
|
|
607
|
+
fileList = os.listdir(imgPath)
|
|
608
|
+
for i in fileList:
|
|
609
|
+
if i.endswith(".cfg"):
|
|
610
|
+
cfgList.append(i)
|
|
611
|
+
if boardTag == None:
|
|
612
|
+
if len(cfgList) == 1:
|
|
613
|
+
cfgFIle = cfgList[0]
|
|
614
|
+
else:
|
|
615
|
+
print("请选择一个cfg用于烧录版本:")
|
|
616
|
+
for index, value in enumerate(cfgList):
|
|
617
|
+
print(f"{index}: {value}")
|
|
618
|
+
|
|
619
|
+
cfgFIle = cfgList[inputSerial(0, len(cfgList) - 1)]
|
|
620
|
+
else:
|
|
621
|
+
cfgFIle = self.getCfg(boardTag, imgPath)
|
|
622
|
+
|
|
623
|
+
finalCfg = cfgFIle
|
|
624
|
+
if self.retainUserData:
|
|
625
|
+
logw("retainUserData is True, will replace tzdata,metadata,userdata Y to N in cfg to retain user data")
|
|
626
|
+
if cfgFIle != None:
|
|
627
|
+
finalCfg = cfgFIle.split(".")[0] + "_retain_user_data.cfg"
|
|
628
|
+
shutil.copy2(cfgFIle, finalCfg)
|
|
629
|
+
self.replace_last_Y_with_N(finalCfg, "tzdata")
|
|
630
|
+
self.replace_last_Y_with_N(finalCfg, "metadata")
|
|
631
|
+
self.replace_last_Y_with_N(finalCfg, "userdata")
|
|
632
|
+
else:
|
|
633
|
+
eExit("cfg file not found")
|
|
634
|
+
|
|
635
|
+
logi(f"download {finalCfg}, wait for download port ready ... ")
|
|
636
|
+
refreshWait("usbdownload", 60, 0.3, self.getInfo, devSerial)
|
|
637
|
+
cmd = ""
|
|
638
|
+
if sysVersion == "Linux":
|
|
639
|
+
cmd = f"./{self.aimgTool} download -t {finalCfg}"
|
|
640
|
+
elif sysVersion == "Windows":
|
|
641
|
+
cmd = f"{self.aimgTool} download -t {finalCfg}"
|
|
642
|
+
logi(cmd)
|
|
643
|
+
oCmd(cmd)
|
|
644
|
+
|
|
645
|
+
def joinBackslash(self, *args):
|
|
646
|
+
# 将字符串用/拼接起来,最后一个不加
|
|
647
|
+
return "".join(f"{arg}/" if i < len(args) - 1 else f"{arg}" for i, arg in enumerate(args))
|
|
648
|
+
|
|
649
|
+
def downloadJenkins(self, buildNumber, buildBoard):
|
|
650
|
+
package = self.packageFile
|
|
651
|
+
branch = self.branch
|
|
652
|
+
buildTag = self.buildTag
|
|
653
|
+
pattern = re.compile("adroid" + r"(\d+)")
|
|
654
|
+
adVersion = pattern.findall(branch)[0]
|
|
655
|
+
|
|
656
|
+
ciTag = branch
|
|
657
|
+
if buildTag == "ci":
|
|
658
|
+
ciTag = branch + "_ci"
|
|
659
|
+
elif buildTag == "db":
|
|
660
|
+
ciTag = branch
|
|
661
|
+
|
|
662
|
+
logh(
|
|
663
|
+
f"num={buildNumber} Board={buildBoard} branch={branch} Tag={buildTag} ciOwner={self.ciOwner} adVersion={adVersion} isUserDebug={self.isUserDebug}"
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
buildNumber, targetUrl = self.findTargetBuild(buildNumber, self.ciOwner, ciTag, buildBoard)
|
|
667
|
+
buildBoard = targetUrl.split("/")[-2]
|
|
668
|
+
|
|
669
|
+
logi(f"download {buildNumber} {targetUrl}")
|
|
670
|
+
# exit()
|
|
671
|
+
packageName = buildTag + buildNumber + "android" + adVersion + "_" + buildBoard
|
|
672
|
+
imgName = os.path.join(self.downloadPath, packageName + ".zip")
|
|
673
|
+
createFolder(self.downloadPath)
|
|
674
|
+
|
|
675
|
+
if not os.path.exists(imgName):
|
|
676
|
+
if self.getPackage(targetUrl, imgName) == -1:
|
|
677
|
+
# 如果下载失败,列出所有board,用户选择一个board下载
|
|
678
|
+
boardUrl = f"http://10.1.24.40/{ciTag}/{buildNumber}"
|
|
679
|
+
boardDict = self.getJenkinsBoard(boardUrl)
|
|
680
|
+
logi(f"choose a board from {self.joinBackslash(self.baseUrl,self.branch,buildNumber)}")
|
|
681
|
+
for k, v in boardDict.items():
|
|
682
|
+
print(k, v[:-1])
|
|
683
|
+
buildBoard = boardDict[inputSerial(0, len(boardDict) - 1)]
|
|
684
|
+
buildBoard = buildBoard[:-1]
|
|
685
|
+
logi(f"choose board {buildBoard} to download")
|
|
686
|
+
targetUrl = self.joinBackslash(boardUrl, buildBoard, package)
|
|
687
|
+
packageName = buildTag + buildNumber + "android" + adVersion + "_" + buildBoard
|
|
688
|
+
imgName = os.path.join(self.downloadPath, packageName + ".zip")
|
|
689
|
+
if not os.path.exists(imgName):
|
|
690
|
+
if self.getPackage(targetUrl, imgName) == -1:
|
|
691
|
+
exit()
|
|
692
|
+
else:
|
|
693
|
+
logi(f"{packageName}.zip already exist, skip download from jenkins")
|
|
694
|
+
|
|
695
|
+
imgPath = os.path.join(self.extractPath, packageName)
|
|
696
|
+
if not os.path.exists(imgPath):
|
|
697
|
+
self.extractPackage(imgName, self.extractPath)
|
|
698
|
+
os.rename(os.path.join(self.extractPath, "release"), imgPath)
|
|
699
|
+
else:
|
|
700
|
+
logi(f"{imgPath} already exist, skip extract")
|
|
701
|
+
|
|
702
|
+
out, error = rCmd("adb devices -l")
|
|
703
|
+
serial = None
|
|
704
|
+
if len(out.split("\n")) == 3:
|
|
705
|
+
boardTag = None
|
|
706
|
+
else:
|
|
707
|
+
dev = DevOp()
|
|
708
|
+
serial = dev.serial
|
|
709
|
+
soc = dev.getSoc()
|
|
710
|
+
if soc == "larkl":
|
|
711
|
+
boardTag = "larkl_"
|
|
712
|
+
elif soc == "lark":
|
|
713
|
+
boardTag = "lark_"
|
|
714
|
+
elif soc == "dove":
|
|
715
|
+
boardTag = "dove_"
|
|
716
|
+
elif soc == "larkpro":
|
|
717
|
+
boardTag = "larkpro_"
|
|
718
|
+
else:
|
|
719
|
+
boardTag = dev.slectBoard() + "_"
|
|
720
|
+
|
|
721
|
+
# pCmd("adb reboot download", showCmd=True, waitDone=False)
|
|
722
|
+
|
|
723
|
+
self.downloadToBoard(boardTag, imgPath, serial)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def CLI_downJKS(argsList):
|
|
727
|
+
parser = argparse.ArgumentParser(prog="dj", description="自动下载并烧录版本")
|
|
728
|
+
parser.add_argument("-n", "--num", type=int, default=0, help="jenkins上的编译号,0表示最新编译结果")
|
|
729
|
+
parser.add_argument("-b", "--board", type=str, default="larkpro_phone-sp01-userdebug", help="烧录板卡")
|
|
730
|
+
parser.add_argument("-t", "--type", type=str, default="db", choices=["db", "ci"], help="编译类型 db/ci")
|
|
731
|
+
parser.add_argument(
|
|
732
|
+
"-a", "--android", type=str, default="16", choices=["16", "15", "16sdk", "15sdk"], help="安卓分支"
|
|
733
|
+
)
|
|
734
|
+
parser.add_argument("-u", "--user", type=str, default="taotaoli", help="触发用户")
|
|
735
|
+
parser.add_argument("-d", "--debug", type=str, default="1", choices=["0", "1"], help="是否烧录debug版本")
|
|
736
|
+
parser.add_argument("-r", "--ratain", type=str, default="0", choices=["0", "1"], help="烧录是否保留userdata")
|
|
737
|
+
parser.add_argument("--username", type=str, default=None, help="jenkins用户名")
|
|
738
|
+
parser.add_argument("--password", type=str, default=None, help="jenkins密码或API Token")
|
|
739
|
+
args = parser.parse_args(argsList)
|
|
740
|
+
buildBranch = ("16", "15", "16sdk", "15sdk")
|
|
741
|
+
ad = args.android
|
|
742
|
+
if ad not in buildBranch:
|
|
743
|
+
eExit(f"{ad} not in {buildBranch}")
|
|
744
|
+
branchDict = {
|
|
745
|
+
"15sdk": "adroid15_sdk_r1",
|
|
746
|
+
"15": "adroid15.0",
|
|
747
|
+
"16": "adroid16.0",
|
|
748
|
+
"16sdk": "adroid16_sdk_r1",
|
|
749
|
+
}
|
|
750
|
+
branch = branchDict.get(ad)
|
|
751
|
+
jenks = jenkinsTool(branch, args.type, args.user, args.debug, args.username, args.password)
|
|
752
|
+
jenks.retainUserData = True if args.ratain == "1" else False
|
|
753
|
+
jenks.downloadJenkins(args.num, args.board)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def getInfo(acmd):
|
|
757
|
+
rCmd("adb reboot download", showCmd=True)
|
|
758
|
+
info = rCmd(f"{acmd} portscan", timeOut=5)[0]
|
|
759
|
+
sleep(0.5)
|
|
760
|
+
return "usbdownload" in info
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def CLI_aimgDownload(args):
|
|
764
|
+
if sysVersion == "Linux":
|
|
765
|
+
aimgTool = "aimgtool"
|
|
766
|
+
acmd = "./aimgtool"
|
|
767
|
+
else:
|
|
768
|
+
aimgTool = "aimgtool.exe"
|
|
769
|
+
acmd = "aimgtool.exe"
|
|
770
|
+
aimg = os.path.join(os.path.curdir, aimgTool)
|
|
771
|
+
if not os.path.exists(aimg):
|
|
772
|
+
eExit(f"get {aimg} failed")
|
|
773
|
+
os.chmod(aimgTool, 0o755)
|
|
774
|
+
rCmd("adb reboot download")
|
|
775
|
+
fileList = os.listdir(os.path.curdir)
|
|
776
|
+
cfgList = [i for i in fileList if i.endswith(".cfg")]
|
|
777
|
+
if not cfgList:
|
|
778
|
+
eExit("get table cfg failed")
|
|
779
|
+
if len(cfgList) == 1:
|
|
780
|
+
cfgFIle = cfgList[0]
|
|
781
|
+
else:
|
|
782
|
+
print("Please select the target cfg:")
|
|
783
|
+
for index, value in enumerate(cfgList):
|
|
784
|
+
print(f"{index}: {value}")
|
|
785
|
+
cfgFIle = cfgList[inputSerial(0, len(cfgList) - 1)]
|
|
786
|
+
refreshWait("usbdownload", 60, 0.3, getInfo, acmd)
|
|
787
|
+
cmd = f"{acmd} download -t {cfgFIle}"
|
|
788
|
+
logi(cmd)
|
|
789
|
+
oCmd(cmd)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
_COMMAND_REGISTRY = {}
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def printHelp():
|
|
796
|
+
print("[flash]")
|
|
797
|
+
print("adown :download and flash aimg")
|
|
798
|
+
print("dj -h :download and flash jenkins build")
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
CLI_aimgDownload = register_command("adown", registry=_COMMAND_REGISTRY)(CLI_aimgDownload)
|
|
802
|
+
CLI_downJKS = register_command("dj", registry=_COMMAND_REGISTRY)(CLI_downJKS)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def flashMain(args):
|
|
806
|
+
debugCtrl(1) if "debug" in args else debugCtrl(0)
|
|
807
|
+
args.remove("debug") if "debug" in args else args
|
|
808
|
+
args = normalize_args(args)
|
|
809
|
+
if len(args) < 1 or args[0] in {"help", "-h", "--help"}:
|
|
810
|
+
printHelp()
|
|
811
|
+
return
|
|
812
|
+
cmd = args[0]
|
|
813
|
+
handler = _COMMAND_REGISTRY.get(cmd)
|
|
814
|
+
if handler is None:
|
|
815
|
+
loge(f"unsupport command {cmd}")
|
|
816
|
+
return
|
|
817
|
+
handler(args[1:])
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
if __name__ == "__main__":
|
|
821
|
+
debugCtrl(1) if "debug" in sys.argv else debugCtrl(0)
|
|
822
|
+
sys.argv.remove("debug") if "debug" in sys.argv else sys.argv
|
|
823
|
+
flashMain(sys.argv)
|