nsdk_builder.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import time
  5. import copy
  6. import glob
  7. import serial
  8. import tempfile
  9. import json
  10. import argparse
  11. from threading import Thread
  12. import subprocess
  13. from nsdk_utils import *
  14. VALID_MAKEFILE_NAMES = ['Makefile', 'makefile', "GNUMakefile"]
  15. class nsdk_builder(object):
  16. def __init__(self):
  17. pass
  18. @staticmethod
  19. def is_app(appdir):
  20. if os.path.isdir(appdir) == False:
  21. return False
  22. appdir = os.path.realpath(appdir)
  23. for mkname in VALID_MAKEFILE_NAMES:
  24. mkfile_path = os.path.join(appdir, mkname)
  25. if os.path.isfile(mkfile_path):
  26. return True
  27. return False
  28. @staticmethod
  29. def get_objects(appdir):
  30. if nsdk_builder.is_app(appdir) == False:
  31. return None
  32. def find_app_object(pattern):
  33. files = find_files(appdir, pattern)
  34. return (files[0] if len(files) > 0 else "")
  35. build_objects = dict()
  36. build_objects["elf"] = find_app_object("*.elf")
  37. build_objects["map"] = find_app_object("*.map")
  38. build_objects["dump"] = find_app_object("*.dump")
  39. build_objects["dasm"] = find_app_object("*.dasm")
  40. build_objects["verilog"] = find_app_object("*.verilog")
  41. return build_objects
  42. def build_target_only(self, appdir, make_options="", target="clean", show_output=True, logfile=None, parallel=""):
  43. if self.is_app(appdir) == False:
  44. return COMMAND_NOTAPP, 0
  45. # Parallel must start with -j
  46. if isinstance(parallel, str):
  47. parallel = parallel.strip()
  48. if parallel != "" and parallel.startswith("-j") == False:
  49. parallel = ""
  50. else:
  51. parallel = ""
  52. if parallel != "": # need to split targets
  53. build_targets = target.strip().split()
  54. else:
  55. build_targets = [target]
  56. if os.path.isfile(logfile):
  57. os.remove(logfile)
  58. total_ticks = 0
  59. for btg in build_targets:
  60. build_cmd = "make %s -C %s %s %s" % (parallel, appdir, make_options, btg)
  61. if not ((show_output == False) and (btg == "info")):
  62. print("Build application %s, with target: %s" % (appdir, btg))
  63. print("Build command: %s" % (build_cmd))
  64. ret, ticks = run_command(build_cmd, show_output, logfile=logfile, append=True)
  65. if not ((show_output == False) and (btg == "info")):
  66. print("Build command return value: %s" % (ret))
  67. total_ticks += ticks
  68. if ret != 0: # if one target failed, then stop
  69. break
  70. return ret, ticks
  71. def get_build_info(self, appdir, make_options=""):
  72. infolog = tempfile.mktemp()
  73. ret, _ = self.build_target_only(appdir, make_options, "info", False, infolog)
  74. build_info = None
  75. if ret != COMMAND_RUNOK:
  76. os.remove(infolog)
  77. return build_info
  78. with open(infolog, "r") as inf:
  79. for line in inf.readlines():
  80. line = line.strip()
  81. INFO_TAG = "Current Configuration:"
  82. if line.startswith(INFO_TAG):
  83. build_info = dict()
  84. infos = line.strip(INFO_TAG).split()
  85. for info in infos:
  86. splits = info.split("=")
  87. if len(splits) == 2:
  88. build_info[splits[0]] = splits[1]
  89. os.remove(infolog)
  90. return build_info
  91. def build_target(self, appdir, make_options="", target="clean", show_output=True, logfile=None, parallel=""):
  92. if self.is_app(appdir) == False:
  93. return False, None
  94. build_status = dict()
  95. ret, ticks = self.build_target_only(appdir, make_options, target, show_output, logfile, parallel)
  96. cmdsts = True
  97. if ret == COMMAND_INTERRUPTED:
  98. print("%s: Exit program due to CTRL - C pressed" % (sys._getframe().f_code.co_name))
  99. sys.exit(1)
  100. elif ret == COMMAND_RUNOK:
  101. cmdsts = True
  102. else:
  103. cmdsts = False
  104. build_status["app"] = { "path": appdir, \
  105. "make_options": make_options, \
  106. "target": target }
  107. build_status["status"] = {"build": cmdsts}
  108. build_status["status_code"] = {"build": ret}
  109. build_status["logs"] = {"build": logfile}
  110. build_status["time"] = {"build": round(ticks, 2)}
  111. build_status["objects"] = nsdk_builder.get_objects(appdir)
  112. build_status["info"] = self.get_build_info(appdir, make_options)
  113. build_status["size"] = get_elfsize(build_status["objects"].get("elf", ""))
  114. return cmdsts, build_status
  115. def clean_app(self, appdir, make_options="", show_output=True, logfile=None):
  116. return self.build_target(appdir, make_options, "clean", show_output, logfile)
  117. def compile_app(self, appdir, make_options="", show_output=True, logfile=None, parallel=""):
  118. return self.build_target(appdir, make_options, "all", show_output, logfile, parallel)
  119. def upload_app(self, appdir, make_options="", show_output=True, logfile=None):
  120. if logfile is None:
  121. uploadlog = tempfile.mktemp()
  122. else:
  123. uploadlog = logfile
  124. cmdsts, build_status = self.build_target(appdir, make_options, "upload", show_output, uploadlog)
  125. if cmdsts:
  126. upload_sts = False
  127. with open(uploadlog, 'r') as uf:
  128. for line in uf.readlines():
  129. if "Start address" in line:
  130. upload_sts = True
  131. break
  132. if upload_sts == False: # actually not upload successfully
  133. cmdsts = False
  134. if logfile is None:
  135. os.remove(uploadlog)
  136. print("Upload application %s status: %s" % (appdir, cmdsts))
  137. return cmdsts, build_status
  138. class MonitorThread(Thread):
  139. def __init__(self, port:str, baudrate:str, timeout:int, checks:dict, checktime=time.time(), sdk_check=False, logfile=None, show_output=False):
  140. super().__init__()
  141. self.port = port
  142. self.baudrate = baudrate
  143. self.timeout = timeout
  144. self.checks = checks
  145. self.checktime = checktime
  146. self.sdk_check = sdk_check
  147. self.logfile = logfile
  148. self.show_output = show_output
  149. self._exit_req = False
  150. pass
  151. def get_result(self):
  152. try:
  153. return self.result
  154. except Exception:
  155. return None
  156. def exit_request(self):
  157. self._exit_req = True
  158. pass
  159. def run(self):
  160. start_time = time.time()
  161. serial_log = ""
  162. check_status = False
  163. pass_checks = self.checks.get("PASS", [])
  164. fail_checks = self.checks.get("FAIL", [])
  165. def test_in_check(string, checks):
  166. if type(checks) == list:
  167. for check in checks:
  168. if check in string:
  169. return True
  170. return False
  171. print("Read serial log from %s, baudrate %s" %(self.port, self.baudrate))
  172. NSDK_CHECK_TAG = "Nuclei SDK Build Time:"
  173. print("Checker used: ", self.checks)
  174. check_finished = False
  175. try:
  176. ser = None
  177. ser = serial.Serial(self.port, self.baudrate, timeout=5)
  178. while (time.time() - start_time) < self.timeout:
  179. if self._exit_req:
  180. break
  181. # Remove '\r' in serial read line
  182. sline = ser.readline()
  183. line = str(try_decode_bytes(sline)).replace('\r', '')
  184. if self.sdk_check == True:
  185. if self.show_output:
  186. print("XXX Check " + line, end='')
  187. if NSDK_CHECK_TAG in line:
  188. timestr = line.split(NSDK_CHECK_TAG)[-1].strip()
  189. cur_time = time.mktime(time.strptime(timestr, "%b %d %Y, %H:%M:%S"))
  190. if int(cur_time) >= int(self.checktime):
  191. self.sdk_check = False
  192. line = NSDK_CHECK_TAG + " " + timestr + "\n"
  193. serial_log = serial_log + str(line)
  194. else:
  195. serial_log = serial_log + str(line)
  196. if self.show_output:
  197. print(line, end='')
  198. if check_finished == False:
  199. if test_in_check(line, fail_checks):
  200. check_status = False
  201. check_finished = True
  202. if test_in_check(line, pass_checks):
  203. check_status = True
  204. check_finished = True
  205. if check_finished:
  206. # record another 2 seconds by reset start_time and timeout to 2
  207. start_time = time.time()
  208. self.timeout = 2
  209. except serial.serialutil.SerialException:
  210. # https://stackoverflow.com/questions/21050671/how-to-check-if-device-is-connected-pyserial
  211. print("serial port %s might not exist or in use" % self.port)
  212. except:
  213. print("Some error happens during serial operations")
  214. finally:
  215. if ser:
  216. ser.close()
  217. if self.logfile:
  218. with open(self.logfile, 'w') as lf:
  219. lf.write(serial_log)
  220. self.result = check_status
  221. return check_status
  222. def monitor_serial_and_check(port:str, baudrate:str, timeout:int, checks:dict, checktime=time.time(), sdk_check=False, logfile=None, show_output=False):
  223. start_time = time.time()
  224. serial_log = ""
  225. check_status = False
  226. pass_checks = checks.get("PASS", [])
  227. fail_checks = checks.get("FAIL", [])
  228. def test_in_check(string, checks):
  229. if type(checks) == list:
  230. for check in checks:
  231. if check in string:
  232. return True
  233. return False
  234. print("Read serial log from %s, baudrate %s" %(port, baudrate))
  235. NSDK_CHECK_TAG = "Nuclei SDK Build Time:"
  236. print("Checker used: ", checks)
  237. check_finished = False
  238. try:
  239. ser = None
  240. ser = serial.Serial(port, baudrate, timeout=5)
  241. while (time.time() - start_time) < timeout:
  242. # Remove '\r' in serial read line
  243. sline = ser.readline()
  244. line = str(try_decode_bytes(sline)).replace('\r', '')
  245. if sdk_check == True:
  246. if show_output:
  247. print("XXX Check " + line, end='')
  248. if NSDK_CHECK_TAG in line:
  249. timestr = line.split(NSDK_CHECK_TAG)[-1].strip()
  250. cur_time = time.mktime(time.strptime(timestr, "%b %d %Y, %H:%M:%S"))
  251. if int(cur_time) >= int(checktime):
  252. sdk_check = False
  253. line = NSDK_CHECK_TAG + " " + timestr + "\n"
  254. serial_log = serial_log + str(line)
  255. else:
  256. serial_log = serial_log + str(line)
  257. if show_output:
  258. print(line, end='')
  259. if check_finished == False:
  260. if test_in_check(line, fail_checks):
  261. check_status = False
  262. check_finished = True
  263. if test_in_check(line, pass_checks):
  264. check_status = True
  265. check_finished = True
  266. if check_finished:
  267. # record another 2 seconds by reset start_time and timeout to 2
  268. start_time = time.time()
  269. timeout = 2
  270. except serial.serialutil.SerialException:
  271. # https://stackoverflow.com/questions/21050671/how-to-check-if-device-is-connected-pyserial
  272. print("serial port %s might not exist or in use" % port)
  273. except:
  274. print("Some error happens during serial operations")
  275. finally:
  276. if ser:
  277. ser.close()
  278. if logfile:
  279. with open(logfile, 'w') as lf:
  280. lf.write(serial_log)
  281. return check_status
  282. class nsdk_runner(nsdk_builder):
  283. def __init__(self):
  284. super().__init__()
  285. pass
  286. @staticmethod
  287. def find_apps(rootdir):
  288. subdirectories = [x[0] for x in os.walk(rootdir)]
  289. appdirs = []
  290. for subdir in subdirectories:
  291. if nsdk_runner.is_app(subdir):
  292. appdirs.append(os.path.normpath(subdir))
  293. return appdirs
  294. def build_target_in_directory(self, rootdir, make_options="", target="", \
  295. show_output=True, logdir=None, stoponfail=False):
  296. appdirs = self.find_apps(rootdir)
  297. if len(appdirs) == 0:
  298. return False, None
  299. cmdsts = True
  300. build_status = dict()
  301. createlog = False
  302. if isinstance(logdir, str):
  303. createlog = True
  304. if os.path.isdir(logdir) == False:
  305. os.makedirs(logdir)
  306. for appdir in appdirs:
  307. appdir = appdir.replace("\\", "/") # Change windows \\ path to /
  308. applogfile = None
  309. if createlog:
  310. applogfile = get_logfile(appdir, rootdir, logdir, "build.log")
  311. appcmdsts, appsts = self.build_target(appdir, make_options, \
  312. target, show_output, logfile=applogfile)
  313. build_status[appdir] = appsts
  314. if appcmdsts == False:
  315. cmdsts = appcmdsts
  316. if stoponfail == True:
  317. print("Stop build directory due to fail on application %s" %(appdir))
  318. return cmdsts, build_status
  319. return cmdsts, build_status
  320. def analyze_runlog(self, logfile):
  321. result = {"type": "unknown", "value": {}}
  322. result_lines = open(logfile).readlines()
  323. program_found, result_parsed = parse_benchmark_runlog(result_lines)
  324. if program_found != PROGRAM_UNKNOWN:
  325. result = {"type": program_found, "value": result_parsed}
  326. return result
  327. def run_app_onhw(self, appdir, runcfg:dict(), show_output=True, logfile=None):
  328. app_runcfg = runcfg.get("run_config", dict())
  329. app_runchecks = runcfg.get("checks", dict())
  330. make_options = runcfg["misc"]["make_options"]
  331. checktime = runcfg["misc"]["build_time"]
  332. hwconfig = app_runcfg.get("hardware", None)
  333. serport = None
  334. timeout = 60
  335. baudrate = 115200
  336. if hwconfig and "serport" in hwconfig:
  337. serport = hwconfig.get("serport", "/dev/ttyUSB1")
  338. baudrate = hwconfig.get("baudrate", 115200)
  339. timeout = hwconfig.get("timeout", 60)
  340. ser_thread = None
  341. try:
  342. if serport:
  343. #ser_thread = NThread(monitor_serial_and_check, \
  344. # (serport, baudrate, timeout, app_runchecks, checktime, True, logfile, show_output))
  345. ser_thread = MonitorThread(serport, baudrate, timeout, app_runchecks, checktime, True, logfile, show_output)
  346. ser_thread.start()
  347. cmdsts, _ = self.upload_app(appdir, make_options, show_output, None)
  348. status = True
  349. if ser_thread:
  350. if cmdsts == False:
  351. ser_thread.exit_request()
  352. while ser_thread.is_alive():
  353. ser_thread.join(1)
  354. status = ser_thread.get_result()
  355. del ser_thread
  356. except (KeyboardInterrupt, SystemExit):
  357. print("%s: Exit program due to CTRL - C pressed or SystemExit" % (sys._getframe().f_code.co_name))
  358. if ser_thread:
  359. ser_thread.exit_request()
  360. sys.exit(1)
  361. final_status = cmdsts and status
  362. return final_status
  363. def build_app_with_config(self, appdir, appconfig:dict, show_output=True, logfile=None):
  364. build_config = appconfig.get("build_config", None)
  365. target = appconfig.get("build_target", "all")
  366. parallel = appconfig.get("parallel", "")
  367. make_options = ""
  368. if isinstance(build_config, dict):
  369. for key, value in build_config.items():
  370. value = str(value).strip()
  371. if " " in key:
  372. continue
  373. if " " in value:
  374. make_options += " %s=\"%s\""%(key, value)
  375. else:
  376. make_options += " %s=%s"%(key, value)
  377. appcmdsts, appsts = self.build_target(appdir, make_options, target, show_output, logfile, parallel)
  378. buildtime = appsts["time"]["build"]
  379. print("Build application %s, time cost %s seconds, passed: %s" %(appdir, buildtime, appcmdsts))
  380. appsts["config"] = appconfig
  381. return appcmdsts, appsts
  382. def run_app_with_config(self, appdir, appconfig:dict, show_output=True, buildlog=None, runlog=None):
  383. appconfig["build_target"] = "clean dasm"
  384. # build application
  385. build_cktime = time.time()
  386. appcmdsts, appsts = self.build_app_with_config(appdir, appconfig, show_output, buildlog)
  387. # run application
  388. if appcmdsts == False:
  389. print("Failed to build application %s, so we can't run it!" % (appdir))
  390. return appcmdsts, appsts
  391. # get run config
  392. app_runcfg = appconfig.get("run_config", dict())
  393. app_runtarget = app_runcfg.get("target", "hardware")
  394. # get run checks
  395. DEFAULT_CHECKS = { "PASS": [ ], "FAIL": [ "MCAUSE:" ] }
  396. app_runchecks = appconfig.get("checks", DEFAULT_CHECKS)
  397. misc_config = {"make_options": appsts["app"]["make_options"], "build_time": build_cktime}
  398. runcfg = {"run_config": app_runcfg, "checks": app_runchecks, "misc": misc_config}
  399. print("Run application on %s" % app_runtarget)
  400. runstarttime = time.time()
  401. runstatus = False
  402. appsts["status_code"]["run"] = RUNSTATUS_NOTSTART
  403. if app_runtarget == "hardware":
  404. runstatus = self.run_app_onhw(appdir, runcfg, show_output, runlog)
  405. # If run successfully, then do log analyze
  406. if runlog and runstatus:
  407. appsts["result"] = self.analyze_runlog(runlog)
  408. appsts["logs"]["run"] = runlog
  409. appsts["status_code"]["run"] = RUNSTATUS_OK if runstatus else RUNSTATUS_FAIL
  410. runtime = round(time.time() - runstarttime, 2)
  411. print("Run application %s on %s, time cost %s seconds, passed: %s" %(appdir, app_runtarget, runtime, runstatus))
  412. appsts["status"]["run"] = runstatus
  413. appsts["time"]["run"] = runtime
  414. return runstatus, appsts
  415. def build_apps_with_config(self, config:dict, show_output=True, logdir=None, stoponfail=False):
  416. # Build all the applications, each application only has one configuration
  417. # "app" : { the_only_config }
  418. cmdsts = True
  419. build_status = dict()
  420. apps_config = copy.deepcopy(config)
  421. for appdir in apps_config:
  422. appconfig = apps_config[appdir]
  423. applogs = appconfig.get("logs", dict())
  424. applogfile = applogs.get("build", None)
  425. appcmdsts, appsts = self.build_app_with_config(appdir, appconfig, show_output, applogfile)
  426. build_status[appdir] = appsts
  427. if appcmdsts == False:
  428. cmdsts = appcmdsts
  429. if stoponfail == True:
  430. print("Stop build apps with config due to fail on application %s" %(appdir))
  431. return cmdsts, build_status
  432. return cmdsts, build_status
  433. def build_apps_with_configs(self, config:dict, show_output=True, logdir=None, stoponfail=False):
  434. # Build all the applications, each application has more than one configuration
  435. # "app" : {"configs": {"case1": case1_config}}
  436. cmdsts = True
  437. build_status = dict()
  438. apps_config = copy.deepcopy(config)
  439. for appdir in apps_config:
  440. appconfigs = apps_config[appdir]
  441. if "configs" not in appconfigs:
  442. continue
  443. build_status[appdir] = dict()
  444. app_allconfigs = appconfigs["configs"]
  445. for cfgname in app_allconfigs:
  446. appconfig = app_allconfigs[cfgname] # get configuration for each case for single app
  447. applogs = appconfig.get("logs", dict())
  448. applogfile = applogs.get("build", None)
  449. appcmdsts, appsts = self.build_app_with_config(appdir, appconfig, show_output, applogfile)
  450. build_status[appdir][cfgname] = appsts
  451. if appcmdsts == False:
  452. cmdsts = appcmdsts
  453. if stoponfail == True:
  454. print("Stop build apps with config due to fail on application %s" %(appdir))
  455. return cmdsts, build_status
  456. return cmdsts, build_status
  457. def run_apps_with_config(self, config:dict, show_output=True, stoponfail=False):
  458. # Run all the applications, each application only has one configuration
  459. # "app" : { the_only_config }
  460. cmdsts = True
  461. build_status = dict()
  462. apps_config = copy.deepcopy(config)
  463. for appdir in apps_config:
  464. appconfig = apps_config[appdir]
  465. applogs = appconfig.get("logs", dict())
  466. app_buildlogfile = applogs.get("build", None)
  467. app_runlogfile = applogs.get("run", None)
  468. appcmdsts, appsts = self.run_app_with_config(appdir, appconfig, show_output, app_buildlogfile, app_runlogfile)
  469. build_status[appdir] = appsts
  470. if appcmdsts == False:
  471. cmdsts = appcmdsts
  472. if stoponfail == True:
  473. print("Stop run apps with config due to fail on application %s" %(appdir))
  474. return cmdsts, build_status
  475. return cmdsts, build_status
  476. def run_apps_with_configs(self, config:dict, show_output=True, stoponfail=False):
  477. # Run all the applications, each application has more than one configuration
  478. # "app" : {"configs": {"case1": case1_config}}
  479. cmdsts = True
  480. build_status = dict()
  481. apps_config = copy.deepcopy(config)
  482. for appdir in apps_config:
  483. appconfigs = apps_config[appdir]
  484. if "configs" not in appconfigs:
  485. continue
  486. build_status[appdir] = dict()
  487. app_allconfigs = appconfigs["configs"]
  488. for cfgname in app_allconfigs:
  489. appconfig = app_allconfigs[cfgname] # get configuration for each case for single app
  490. applogs = appconfig.get("logs", dict())
  491. app_buildlogfile = applogs.get("build", None)
  492. app_runlogfile = applogs.get("run", None)
  493. appcmdsts, appsts = self.run_app_with_config(appdir, appconfig, show_output, app_buildlogfile, app_runlogfile)
  494. build_status[appdir][cfgname] = appsts
  495. if appcmdsts == False:
  496. cmdsts = appcmdsts
  497. if stoponfail == True:
  498. print("Stop run apps with config due to fail on application %s" %(appdir))
  499. return cmdsts, build_status
  500. return cmdsts, build_status