app.py 24 KB

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