app.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. #!/usr/bin/env python3
  2. import os
  3. import subprocess
  4. import json
  5. import pathlib
  6. import shutil
  7. import glob
  8. import time
  9. import fcntl
  10. import hashlib
  11. from distutils.dir_util import copy_tree
  12. from flask import Flask, render_template, request, send_from_directory, render_template_string
  13. from threading import Thread, Lock
  14. from dataclasses import dataclass
  15. # run at lower priority
  16. os.nice(20)
  17. #BOARDS = [ 'BeastF7', 'BeastH7' ]
  18. appdir = os.path.dirname(__file__)
  19. VEHICLES = [ 'Copter', 'Plane', 'Rover', 'Sub' ]
  20. default_vehicle = 'Copter'
  21. def get_boards():
  22. '''return a list of boards to build'''
  23. import importlib.util
  24. spec = importlib.util.spec_from_file_location("build_binaries.py",
  25. os.path.join(sourcedir,
  26. 'Tools', 'scripts',
  27. 'board_list.py'))
  28. mod = importlib.util.module_from_spec(spec)
  29. spec.loader.exec_module(mod)
  30. default_board = mod.AUTOBUILD_BOARDS[11]
  31. return (mod.AUTOBUILD_BOARDS, default_board)
  32. @dataclass
  33. class Feature:
  34. category: str
  35. label: str
  36. define: str
  37. description: str
  38. default: int
  39. # list of build options to offer
  40. BUILD_OPTIONS = [
  41. Feature('AHRS', 'EKF3', 'HAL_NAVEKF3_AVAILABLE', 'Enable EKF3', 1),
  42. Feature('AHRS', 'EKF2', 'HAL_NAVEKF2_AVAILABLE', 'Enable EKF2', 0),
  43. Feature('AHRS', 'AHRS_EXT', 'HAL_EXTERNAL_AHRS_ENABLED', 'Enable External AHRS', 0),
  44. Feature('AHRS', 'TEMPCAL', 'HAL_INS_TEMPERATURE_CAL_ENABLE', 'Enable IMU Temperature Calibration', 0),
  45. Feature('AHRS', 'VISUALODOM', 'HAL_VISUALODOM_ENABLED', 'Enable Visual Odomotry', 0),
  46. Feature('Safety', 'PARACHUTE', 'HAL_PARACHUTE_ENABLED', 'Enable Parachute', 0),
  47. Feature('Safety', 'PROXIMITY', 'HAL_PROXIMITY_ENABLED', 'Enable Proximity', 0),
  48. Feature('Other', 'MOUNT', 'HAL_MOUNT_ENABLED', 'Enable Mount', 0),
  49. Feature('Other', 'SOARING', 'HAL_SOARING_ENABLED', 'Enable Soaring', 0),
  50. Feature('Other', 'DEEPSTAL', 'HAL_LANDING_DEEPSTALL_ENABLED', 'Enable Deepstall Landing', 0),
  51. Feature('Other', 'DSP', 'HAL_WITH_DSP', 'Enable DSP', 0),
  52. Feature('Other', 'SPRAYER', 'HAL_SPRAYER_ENABLED', 'Enable Sprayer', 0),
  53. Feature('Other', 'TORQEEDO', 'HAL_TORQEEDO_ENABLED', 'Enable Torqeedo Motors', 0),
  54. Feature('Other', 'SOLOGIMBAL', 'HAL_SOLO_GIMBAL_ENABLED', 'Enable Solo Gimbal', 0),
  55. Feature('Battery', 'BATTMON_FUEL', 'HAL_BATTMON_FUEL_ENABLE', 'Enable Fuel BatteryMonitor', 0),
  56. Feature('Battery', 'BATTMON_SMBUS', 'HAL_BATTMON_SMBUS_ENABLE', 'Enable SMBUS BatteryMonitor', 0),
  57. Feature('Ident', 'ADSB', 'HAL_ADSB_ENABLED', 'Enable ADSB', 0),
  58. Feature('Ident', 'ADSB_SAGETECH', 'HAL_ADSB_SAGETECH_ENABLED', 'Enable SageTech ADSB', 0),
  59. Feature('Ident', 'ADSB_UAVIONIX', 'HAL_ADSB_UAVIONIX_MAVLINK_ENABLED', 'Enable Uavionix ADSB', 0),
  60. Feature('Ident', 'AIS', 'HAL_AIS_ENABLED', 'Enable AIS', 0),
  61. Feature('Telemetry', 'CRSF', 'HAL_CRSF_ENABLED', 'Enable CRSF', 0),
  62. Feature('Telemetry', 'CRSFText', 'HAL_CRSF_TEXT_SELECTION_ENABLED', 'Enable CRSF Text Selection', 0),
  63. Feature('Telemetry', 'HIGHLAT2', 'HAL_HIGH_LATENCY2_ENABLED', 'Enable HighLatency2 Support', 0),
  64. Feature('Telemetry', 'HOTT', 'HAL_HOTT_TELEM_ENABLED', 'Enable HOTT Telemetry', 0),
  65. Feature('Telemetry', 'MSP', 'HAL_MSP_ENABLED', 'Enable MSP Telemetry', 0),
  66. Feature('Telemetry', 'SPEKTRUM', 'HAL_SPEKTRUM_TELEM_ENABLED', 'Enable Spektrum Telemetry', 0),
  67. Feature('ICE', 'EFI', 'HAL_EFI_ENABLED', 'Enable EFI Monitoring', 0),
  68. Feature('ICE', 'EFI_NMPWU', 'HAL_EFI_NWPWU_ENABLED', 'Enable EFI NMPMU', 0),
  69. Feature('OSD', 'PLUSCODE', 'HAL_PLUSCODE_ENABLE', 'Enable PlusCode', 0),
  70. Feature('OSD', 'RUNCAM', 'HAL_RUNCAM_ENABLED', 'Enable RunCam', 0),
  71. Feature('OSD', 'SMARTAUDIO', 'HAL_SMARTAUDIO_ENABLED', 'Enable SmartAudio', 0),
  72. Feature('CAN', 'PICCOLOCAN', 'HAL_PICCOLO_CAN_ENABLE', 'Enable PiccoloCAN', 0),
  73. Feature('CAN', 'MPPTCAN', 'HAL_MPPT_PACKETDIGITAL_CAN_ENABLE', 'Enable MPPT CAN', 0),
  74. Feature('Other', 'MODE_ZIGZAG', 'MODE_ZIGZAG_ENABLED', 'Enable Mode zigzag', 0),
  75. Feature('Other', 'RPM', 'RPM_ENABLED', 'Enable Rpm', 0),
  76. Feature('Other', 'TORQEEDO', 'HAL_TORQEEDO_ENABLED', 'Enable Torqeedo', 0),
  77. Feature('Other', 'DISPLAY', 'HAL_DISPLAY_ENABLED', 'Enable Display', 0),
  78. Feature('Other', 'EFI', 'HAL_EFI_ENABLED', 'Enable Efi', 0),
  79. Feature('Other', 'LANDING_DEEPSTALL', 'HAL_LANDING_DEEPSTALL_ENABLED', 'Enable Landing deepstall', 0),
  80. Feature('Other', 'GRIPPER', 'GRIPPER_ENABLED', 'Enable Gripper', 0),
  81. Feature('Other', 'EFI_NWPWU', 'HAL_EFI_NWPWU_ENABLED', 'Enable Efi nwpwu', 0),
  82. Feature('Other', 'SPRAYER', 'HAL_SPRAYER_ENABLED', 'Enable Sprayer', 0),
  83. Feature('Other', 'OSD', 'OSD_ENABLED', 'Enable Osd', 0),
  84. Feature('Other', 'CRSF_TELEM', 'HAL_CRSF_TELEM_ENABLED', 'Enable Crsf telem', 0),
  85. Feature('Other', 'MSP_SENSORS', 'HAL_MSP_SENSORS_ENABLED', 'Enable Msp sensors', 0),
  86. Feature('Other', 'SOLO_GIMBAL', 'HAL_SOLO_GIMBAL_ENABLED', 'Enable Solo gimbal', 0),
  87. Feature('Other', 'HOTT_TELEM', 'HAL_HOTT_TELEM_ENABLED', 'Enable Hott telem', 0),
  88. Feature('Other', 'SPEKTRUM_TELEM', 'HAL_SPEKTRUM_TELEM_ENABLED', 'Enable Spektrum telem', 0),
  89. Feature('Other', 'OSD_PARAM', 'OSD_PARAM_ENABLED', 'Enable Osd param', 0),
  90. Feature('Other', 'MODE_SYSTEMID', 'MODE_SYSTEMID_ENABLED', 'Enable Mode systemid', 0),
  91. Feature('Other', 'MSP_OPTICALFLOW', 'HAL_MSP_OPTICALFLOW_ENABLED', 'Enable Msp opticalflow', 0),
  92. Feature('Other', 'MSP', 'HAL_MSP_ENABLED', 'Enable Msp', 0),
  93. Feature('Other', 'BEACON', 'BEACON_ENABLED', 'Enable Beacon', 0),
  94. Feature('Other', 'LANDING_GEAR', 'LANDING_GEAR_ENABLED', 'Enable Landing gear', 0),
  95. Feature('Other', 'MODE_SPORT', 'MODE_SPORT_ENABLED', 'Enable Mode sport', 0),
  96. Feature('Other', 'EXTERNAL_AHRS', 'HAL_EXTERNAL_AHRS_ENABLED', 'Enable External ahrs', 0),
  97. Feature('Other', 'PROXIMITY', 'HAL_PROXIMITY_ENABLED', 'Enable Proximity', 0),
  98. Feature('Other', 'SMARTAUDIO', 'HAL_SMARTAUDIO_ENABLED', 'Enable Smartaudio', 0),
  99. Feature('Other', 'ADSB', 'HAL_ADSB_ENABLED', 'Enable Adsb', 0),
  100. Feature('Other', 'MODE_AUTOROTATE', 'MODE_AUTOROTATE_ENABLED', 'Enable Mode autorotate', 0),
  101. Feature('Other', 'NMEA_OUTPUT', 'HAL_NMEA_OUTPUT_ENABLED', 'Enable Nmea output', 0),
  102. Feature('Other', 'VISUALODOM', 'HAL_VISUALODOM_ENABLED', 'Enable Visualodom', 0),
  103. Feature('Other', 'AIS', 'HAL_AIS_ENABLED', 'Enable Ais', 0),
  104. Feature('Other', 'MSP_RANGEFINDER', 'HAL_MSP_RANGEFINDER_ENABLED', 'Enable Msp rangefinder', 0),
  105. Feature('Other', 'RUNCAM', 'HAL_RUNCAM_ENABLED', 'Enable Runcam', 0),
  106. Feature('Other', 'QAUTOTUNE', 'QAUTOTUNE_ENABLED', 'Enable Qautotune', 0),
  107. Feature('Other', 'MODE_FOLLOW', 'MODE_FOLLOW_ENABLED', 'Enable Mode follow', 0),
  108. Feature('Other', 'BARO_WIND_COMP', 'HAL_BARO_WIND_COMP_ENABLED', 'Enable Baro wind comp', 0),
  109. Feature('Other', 'SOARING', 'HAL_SOARING_ENABLED', 'Enable Soaring', 0),
  110. Feature('Other', 'MODE_TURTLE', 'MODE_TURTLE_ENABLED', 'Enable Mode turtle', 0),
  111. Feature('Other', 'MODE_GUIDED_NOGPS', 'MODE_GUIDED_NOGPS_ENABLED', 'Enable Mode guided nogps', 0),
  112. Feature('Other', 'GENERATOR', 'GENERATOR_ENABLED', 'Enable Generator', 0),
  113. Feature('Other', 'AC_OAPATHPLANNER', 'AC_OAPATHPLANNER_ENABLED', 'Enable Ac oapathplanner', 0),
  114. Feature('Other', 'CRSF_TELEM_TEXT_SELECTION', 'HAL_CRSF_TELEM_TEXT_SELECTION_ENABLED', 'Enable Crsf telem text selection', 0),
  115. Feature('Other', 'MOUNT', 'HAL_MOUNT_ENABLED', 'Enable Mount', 0),
  116. Feature('Other', 'WINCH', 'WINCH_ENABLED', 'Enable Winch', 0),
  117. ]
  118. BUILD_OPTIONS.sort(key=lambda x: x.category)
  119. queue_lock = Lock()
  120. from logging.config import dictConfig
  121. dictConfig({
  122. 'version': 1,
  123. 'formatters': {'default': {
  124. 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
  125. }},
  126. 'handlers': {'wsgi': {
  127. 'class': 'logging.StreamHandler',
  128. 'stream': 'ext://flask.logging.wsgi_errors_stream',
  129. 'formatter': 'default'
  130. }},
  131. 'root': {
  132. 'level': 'INFO',
  133. 'handlers': ['wsgi']
  134. }
  135. })
  136. def remove_directory_recursive(dirname):
  137. '''remove a directory recursively'''
  138. app.logger.info('Removing directory ' + dirname)
  139. if not os.path.exists(dirname):
  140. return
  141. f = pathlib.Path(dirname)
  142. if f.is_file():
  143. f.unlink()
  144. else:
  145. shutil.rmtree(f, True)
  146. def create_directory(dir_path):
  147. '''create a directory, don't fail if it exists'''
  148. app.logger.info('Creating ' + dir_path)
  149. pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
  150. def run_build(task, tmpdir, outdir, logpath):
  151. '''run a build with parameters from task'''
  152. remove_directory_recursive(tmpdir_parent)
  153. create_directory(tmpdir)
  154. if not os.path.isfile(os.path.join(outdir, 'extra_hwdef.dat')):
  155. app.logger.error('Build aborted, missing extra_hwdef.dat')
  156. app.logger.info('Appending to build.log')
  157. with open(logpath, 'a') as log:
  158. # setup PATH to point at our compiler
  159. env = os.environ.copy()
  160. bindir1 = os.path.abspath(os.path.join(appdir, "..", "bin"))
  161. bindir2 = os.path.abspath(os.path.join(appdir, "..", "gcc", "bin"))
  162. cachedir = os.path.abspath(os.path.join(appdir, "..", "cache"))
  163. env["PATH"] = bindir1 + ":" + bindir2 + ":" + env["PATH"]
  164. env['CCACHE_DIR'] = cachedir
  165. app.logger.info('Running waf configure')
  166. subprocess.run(['python3', './waf', 'configure',
  167. '--board', task['board'],
  168. '--out', tmpdir,
  169. '--extra-hwdef', task['extra_hwdef']],
  170. cwd = task['sourcedir'],
  171. env=env,
  172. stdout=log, stderr=log)
  173. app.logger.info('Running clean')
  174. subprocess.run(['python3', './waf', 'clean'],
  175. cwd = task['sourcedir'],
  176. env=env,
  177. stdout=log, stderr=log)
  178. app.logger.info('Running build')
  179. subprocess.run(['python3', './waf', task['vehicle']],
  180. cwd = task['sourcedir'],
  181. env=env,
  182. stdout=log, stderr=log)
  183. def sort_json_files(reverse=False):
  184. json_files = list(filter(os.path.isfile,
  185. glob.glob(os.path.join(outdir_parent,
  186. '*', 'q.json'))))
  187. json_files.sort(key=lambda x: os.path.getmtime(x), reverse=reverse)
  188. return json_files
  189. def check_queue():
  190. '''thread to continuously run queued builds'''
  191. queue_lock.acquire()
  192. json_files = sort_json_files()
  193. queue_lock.release()
  194. if len(json_files) == 0:
  195. return
  196. # remove multiple build requests from same ip address (keep newest)
  197. queue_lock.acquire()
  198. ip_list = []
  199. for f in json_files:
  200. file = json.loads(open(f).read())
  201. ip_list.append(file['ip'])
  202. seen = set()
  203. ip_list.reverse()
  204. for index, value in enumerate(ip_list):
  205. if value in seen:
  206. file = json.loads(open(json_files[-index-1]).read())
  207. outdir_to_delete = os.path.join(outdir_parent, file['token'])
  208. remove_directory_recursive(outdir_to_delete)
  209. else:
  210. seen.add(value)
  211. queue_lock.release()
  212. if len(json_files) == 0:
  213. return
  214. # open oldest q.json file
  215. json_files = sort_json_files()
  216. taskfile = json_files[0]
  217. app.logger.info('Opening ' + taskfile)
  218. task = json.loads(open(taskfile).read())
  219. app.logger.info('Removing ' + taskfile)
  220. os.remove(taskfile)
  221. outdir = os.path.join(outdir_parent, task['token'])
  222. tmpdir = os.path.join(tmpdir_parent, task['token'])
  223. logpath = os.path.abspath(os.path.join(outdir, 'build.log'))
  224. app.logger.info("LOGPATH: %s" % logpath)
  225. try:
  226. # run build and rename build directory
  227. run_build(task, tmpdir, outdir, logpath)
  228. app.logger.info('Copying build files from %s to %s',
  229. os.path.join(tmpdir, task['board']),
  230. outdir)
  231. copy_tree(os.path.join(tmpdir, task['board'], 'bin'), outdir)
  232. app.logger.info('Build successful!')
  233. remove_directory_recursive(tmpdir)
  234. # remove extra_hwdef.dat and q.json
  235. app.logger.info('Removing ' +
  236. os.path.join(outdir, 'extra_hwdef.dat'))
  237. os.remove(os.path.join(outdir, 'extra_hwdef.dat'))
  238. except Exception as ex:
  239. app.logger.info(ex)('Build failed: ', ex)
  240. pass
  241. open(logpath,'a').write("\nBUILD_FINISHED\n")
  242. def file_age(fname):
  243. '''return file age in seconds'''
  244. return time.time() - os.stat(fname).st_mtime
  245. def remove_old_builds():
  246. '''as a cleanup, remove any builds older than 24H'''
  247. for f in os.listdir(outdir_parent):
  248. bdir = os.path.join(outdir_parent, f)
  249. if os.path.isdir(bdir) and file_age(bdir) > 24 * 60 * 60:
  250. remove_directory_recursive(bdir)
  251. time.sleep(5)
  252. def queue_thread():
  253. while True:
  254. try:
  255. check_queue()
  256. remove_old_builds()
  257. except Exception as ex:
  258. app.logger.error(ex)('Failed queue: ', ex)
  259. pass
  260. def get_build_status():
  261. '''return build status tuple list
  262. returns tuples of form (status,age,board,vehicle,genlink)
  263. '''
  264. ret = []
  265. # get list of directories
  266. blist = []
  267. for b in os.listdir(outdir_parent):
  268. if os.path.isdir(os.path.join(outdir_parent,b)):
  269. blist.append(b)
  270. blist.sort(key=lambda x: os.path.getmtime(os.path.join(outdir_parent,x)), reverse=True)
  271. for b in blist:
  272. a = b.split(':')
  273. if len(a) < 2:
  274. continue
  275. vehicle = a[0].capitalize()
  276. board = a[1]
  277. link = "/view?token=%s" % b
  278. age_min = int(file_age(os.path.join(outdir_parent,b))/60.0)
  279. age_str = "%u:%02u" % ((age_min // 60), age_min % 60)
  280. feature_file = os.path.join(outdir_parent, b, 'selected_features.json')
  281. app.logger.info('Opening ' + feature_file)
  282. selected_features_dict = json.loads(open(feature_file).read())
  283. selected_features = selected_features_dict['selected_features']
  284. git_hash_short = selected_features_dict['git_hash_short']
  285. features = ''
  286. for feature in selected_features:
  287. if features == '':
  288. features = features + feature
  289. else:
  290. features = features + ", " + feature
  291. if os.path.exists(os.path.join(outdir_parent,b,'q.json')):
  292. status = "Pending"
  293. elif not os.path.exists(os.path.join(outdir_parent,b,'build.log')):
  294. status = "Error"
  295. else:
  296. build = open(os.path.join(outdir_parent,b,'build.log')).read()
  297. if build.find("'%s' finished successfully" % vehicle.lower()) != -1:
  298. status = "Finished"
  299. elif build.find('The configuration failed') != -1 or build.find('Build failed') != -1:
  300. status = "Failed"
  301. elif build.find('BUILD_FINISHED') == -1:
  302. status = "Running"
  303. else:
  304. status = "Failed"
  305. ret.append((status,age_str,board,vehicle,link,features,git_hash_short))
  306. return ret
  307. def create_status():
  308. '''create status.html'''
  309. build_status = get_build_status()
  310. tmpfile = os.path.join(outdir_parent, "status.tmp")
  311. statusfile = os.path.join(outdir_parent, "status.html")
  312. f = open(tmpfile, "w")
  313. app2 = Flask("status")
  314. with app2.app_context():
  315. f.write(render_template_string(open(os.path.join(appdir, 'templates', 'status.html')).read(),
  316. build_status=build_status))
  317. f.close()
  318. os.replace(tmpfile, statusfile)
  319. def status_thread():
  320. while True:
  321. try:
  322. create_status()
  323. except Exception as ex:
  324. app.logger.info(ex)
  325. pass
  326. time.sleep(3)
  327. def update_source():
  328. '''update submodules and ardupilot git tree'''
  329. app.logger.info('Fetching ardupilot upstream')
  330. subprocess.run(['git', 'fetch', 'upstream'],
  331. cwd=sourcedir)
  332. app.logger.info('Updating ardupilot git tree')
  333. subprocess.run(['git', 'reset', '--hard',
  334. 'upstream/master'],
  335. cwd=sourcedir)
  336. app.logger.info('Updating submodules')
  337. subprocess.run(['git', 'submodule',
  338. 'update', '--recursive',
  339. '--force', '--init'],
  340. cwd=sourcedir)
  341. import optparse
  342. parser = optparse.OptionParser("app.py")
  343. parser.add_option("", "--basedir", type="string",
  344. default=os.path.abspath(os.path.join(os.path.dirname(__file__),"..","base")),
  345. help="base directory")
  346. cmd_opts, cmd_args = parser.parse_args()
  347. # define directories
  348. basedir = os.path.abspath(cmd_opts.basedir)
  349. sourcedir = os.path.abspath(os.path.join(basedir, 'ardupilot'))
  350. outdir_parent = os.path.join(basedir, 'builds')
  351. tmpdir_parent = os.path.join(basedir, 'tmp')
  352. app = Flask(__name__, template_folder='templates')
  353. if not os.path.isdir(outdir_parent):
  354. create_directory(outdir_parent)
  355. try:
  356. lock_file = open(os.path.join(basedir, "queue.lck"), "w")
  357. fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
  358. app.logger.info("Got queue lock")
  359. # we only want one set of threads
  360. thread = Thread(target=queue_thread, args=())
  361. thread.daemon = True
  362. thread.start()
  363. status_thread = Thread(target=status_thread, args=())
  364. status_thread.daemon = True
  365. status_thread.start()
  366. except IOError:
  367. app.logger.info("No queue lock")
  368. @app.route('/generate', methods=['GET', 'POST'])
  369. def generate():
  370. try:
  371. update_source()
  372. # fetch features from user input
  373. extra_hwdef = []
  374. feature_list = []
  375. selected_features = []
  376. app.logger.info('Fetching features from user input')
  377. # add all undefs at the start
  378. for f in BUILD_OPTIONS:
  379. extra_hwdef.append('undef %s' % f.define)
  380. for f in BUILD_OPTIONS:
  381. if f.label not in request.form:
  382. continue
  383. if request.form[f.label] == '1':
  384. extra_hwdef.append('define %s 1' % f.define)
  385. feature_list.append(f.description)
  386. selected_features.append(f.label)
  387. extra_hwdef = '\n'.join(extra_hwdef)
  388. spaces = '\n'
  389. feature_list = spaces.join(feature_list)
  390. selected_features_dict = {}
  391. selected_features_dict['selected_features'] = selected_features
  392. queue_lock.acquire()
  393. # create extra_hwdef.dat file and obtain md5sum
  394. app.logger.info('Creating ' +
  395. os.path.join(outdir_parent, 'extra_hwdef.dat'))
  396. file = open(os.path.join(outdir_parent, 'extra_hwdef.dat'), 'w')
  397. app.logger.info('Writing\n' + extra_hwdef)
  398. file.write(extra_hwdef)
  399. file.close()
  400. md5sum = hashlib.md5(extra_hwdef.encode('utf-8')).hexdigest()
  401. app.logger.info('Removing ' +
  402. os.path.join(outdir_parent, 'extra_hwdef.dat'))
  403. os.remove(os.path.join(outdir_parent, 'extra_hwdef.dat'))
  404. # obtain git-hash of source
  405. app.logger.info('Getting git hash')
  406. git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
  407. cwd = sourcedir,
  408. encoding = 'utf-8')
  409. git_hash_short = git_hash[:10]
  410. git_hash = git_hash[:len(git_hash)-1]
  411. app.logger.info('Git hash = ' + git_hash)
  412. selected_features_dict['git_hash_short'] = git_hash_short
  413. # create directories using concatenated token
  414. # of vehicle, board, git-hash of source, and md5sum of hwdef
  415. vehicle = request.form['vehicle']
  416. if not vehicle in VEHICLES:
  417. raise Exception("bad vehicle")
  418. board = request.form['board']
  419. if board not in get_boards()[0]:
  420. raise Exception("bad board")
  421. token = vehicle.lower() + ':' + board + ':' + git_hash + ':' + md5sum
  422. app.logger.info('token = ' + token)
  423. global outdir
  424. outdir = os.path.join(outdir_parent, token)
  425. if os.path.isdir(outdir):
  426. app.logger.info('Build already exists')
  427. else:
  428. create_directory(outdir)
  429. # create build.log
  430. build_log_info = ('Vehicle: ' + vehicle +
  431. '\nBoard: ' + board +
  432. '\nSelected Features:\n' + feature_list +
  433. '\n\nWaiting for build to start...\n\n')
  434. app.logger.info('Creating build.log')
  435. build_log = open(os.path.join(outdir, 'build.log'), 'w')
  436. build_log.write(build_log_info)
  437. build_log.close()
  438. # create hwdef.dat
  439. app.logger.info('Opening ' +
  440. os.path.join(outdir, 'extra_hwdef.dat'))
  441. file = open(os.path.join(outdir, 'extra_hwdef.dat'),'w')
  442. app.logger.info('Writing\n' + extra_hwdef)
  443. file.write(extra_hwdef)
  444. file.close()
  445. # fill dictionary of variables and create json file
  446. task = {}
  447. task['token'] = token
  448. task['sourcedir'] = sourcedir
  449. task['extra_hwdef'] = os.path.join(outdir, 'extra_hwdef.dat')
  450. task['vehicle'] = vehicle.lower()
  451. task['board'] = board
  452. task['ip'] = request.remote_addr
  453. app.logger.info('Opening ' + os.path.join(outdir, 'q.json'))
  454. jfile = open(os.path.join(outdir, 'q.json'), 'w')
  455. app.logger.info('Writing task file to ' +
  456. os.path.join(outdir, 'q.json'))
  457. jfile.write(json.dumps(task, separators=(',\n', ': ')))
  458. jfile.close()
  459. # create selected_features.dat for status table
  460. feature_file = open(os.path.join(outdir, 'selected_features.json'), 'w')
  461. app.logger.info('Writing\n' + os.path.join(outdir, 'selected_features.json'))
  462. feature_file.write(json.dumps(selected_features_dict))
  463. feature_file.close()
  464. queue_lock.release()
  465. base_url = request.url_root
  466. app.logger.info(base_url)
  467. app.logger.info('Rendering generate.html')
  468. return render_template('generate.html', token=token)
  469. except Exception as ex:
  470. app.logger.error(ex)
  471. return render_template('generate.html', error='Error occured: ', ex=ex)
  472. @app.route('/view', methods=['GET'])
  473. def view():
  474. '''view a build from status'''
  475. token=request.args['token']
  476. app.logger.info("viewing %s" % token)
  477. return render_template('generate.html', token=token)
  478. def get_build_options(category):
  479. return [f for f in BUILD_OPTIONS if f.category == category]
  480. def get_build_categories():
  481. return sorted(list(set([f.category for f in BUILD_OPTIONS])))
  482. def get_vehicles():
  483. return (VEHICLES, default_vehicle)
  484. @app.route('/')
  485. def home():
  486. app.logger.info('Rendering index.html')
  487. return render_template('index.html',
  488. get_boards=get_boards,
  489. get_vehicles=get_vehicles,
  490. get_build_options=get_build_options,
  491. get_build_categories=get_build_categories)
  492. @app.route("/builds/<path:name>")
  493. def download_file(name):
  494. app.logger.info('Downloading %s' % name)
  495. return send_from_directory(os.path.join(basedir,'builds'), name, as_attachment=False)
  496. if __name__ == '__main__':
  497. app.run()