app.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #!/usr/bin/env python3
  2. import uuid
  3. import os
  4. import sys
  5. import subprocess
  6. import zipfile
  7. import urllib.request
  8. import gzip
  9. from io import BytesIO
  10. import time
  11. import json
  12. import pathlib
  13. import shutil
  14. from distutils.dir_util import copy_tree
  15. import logging
  16. from flask import Flask, render_template, request, flash
  17. from threading import Thread, Lock
  18. queue_lock = Lock()
  19. from logging.config import dictConfig
  20. dictConfig({
  21. 'version': 1,
  22. 'formatters': {'default': {
  23. 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
  24. }},
  25. 'handlers': {'wsgi': {
  26. 'class': 'logging.StreamHandler',
  27. 'stream': 'ext://flask.logging.wsgi_errors_stream',
  28. 'formatter': 'default'
  29. }},
  30. 'root': {
  31. 'level': 'INFO',
  32. 'handlers': ['wsgi']
  33. }
  34. })
  35. '''
  36. Directory structure:
  37. - all paths relative to where the app starts
  38. - waf builds go into build
  39. - json queued build files go into buildqueue
  40. - resulting builds go into done
  41. - ardupilot is in ardupilot directory
  42. - templates in CustomBuild/templates
  43. '''
  44. #def get_template(filename):
  45. # return render_template(filename)
  46. def remove_directory_recursive(dirname):
  47. # remove a directory recursively
  48. app.logger.info('removing directory ' + dirname)
  49. if not os.path.exists(dirname):
  50. return
  51. f = pathlib.Path(dirname)
  52. if f.is_file():
  53. f.unlink()
  54. else:
  55. shutil.rmtree(f, True)
  56. def create_directory(dir_path):
  57. # create a directory, don't fail if it exists
  58. app.logger.info('creating ' + dir_path)
  59. pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
  60. def run_build(taskfile, builddir, done_dir):
  61. # run a build with parameters from task
  62. app.logger.info('Opening q.json')
  63. task = json.loads(open(taskfile).read())
  64. remove_directory_recursive(os.path.join(sourcedir, done_dir))
  65. remove_directory_recursive(os.path.join(sourcedir, builddir))
  66. create_directory(os.path.join(sourcedir, done_dir))
  67. create_directory(os.path.join(sourcedir, builddir))
  68. app.logger.info('Creating build.log')
  69. with open(os.path.join(sourcedir, done_dir, 'build.log'), "wb") as log:
  70. app.logger.info('Submodule update')
  71. subprocess.run(['git', 'submodule',
  72. 'update', '--recursive',
  73. '--force', '--init'], stdout=log, stderr=log)
  74. app.logger.info('Running waf configure')
  75. subprocess.run(['./waf', 'configure',
  76. '--board', task['board'],
  77. '--out', builddir,
  78. '--extra-hwdef', task['extra_hwdef']],
  79. cwd = task['sourcedir'],
  80. stdout=log, stderr=log)
  81. app.logger.info('Running clean')
  82. subprocess.run(['./waf', 'clean'], cwd = task['sourcedir'],
  83. stdout=log, stderr=log)
  84. app.logger.info('Running build')
  85. subprocess.run(['./waf', task['vehicle']], cwd = task['sourcedir'],
  86. stdout=log, stderr=log)
  87. # background thread to check for queued build requests
  88. def check_queue():
  89. while(1):
  90. queue_lock.acquire()
  91. listing = os.listdir('buildqueue')
  92. queue_lock.release()
  93. if listing:
  94. for token in listing:
  95. builddir = 'build'
  96. done_dir = os.path.join('done', token)
  97. buildqueue_dir = os.path.join('buildqueue', token)
  98. # check if build exists
  99. if os.path.isdir(os.path.join(sourcedir, done_dir)):
  100. app.logger.info('Build already exists')
  101. else:
  102. try:
  103. # run build and rename build directory
  104. app.logger.info('Creating ' +
  105. os.path.join(buildqueue_dir, 'q.json'))
  106. f = open(os.path.join(buildqueue_dir, 'q.json'))
  107. app.logger.info('Loading ' +
  108. os.path.join(buildqueue_dir, 'q.json'))
  109. task = json.load(f)
  110. run_build(os.path.join(buildqueue_dir, 'q.json'),
  111. builddir, done_dir)
  112. app.logger.info('Copying build files from %s to %s',
  113. os.path.join(sourcedir, builddir, task['board']),
  114. os.path.join(sourcedir, done_dir))
  115. copy_tree(os.path.join(sourcedir, builddir, task['board']),
  116. os.path.join(sourcedir, done_dir))
  117. app.logger.info('Build successful!')
  118. except:
  119. app.logger.info('Build failed')
  120. continue
  121. # remove working files
  122. app.logger.info('Removing ' +
  123. os.path.join(buildqueue_dir, 'extra_hwdef.dat'))
  124. os.remove(os.path.join(buildqueue_dir, 'extra_hwdef.dat'))
  125. app.logger.info('Removing ' +
  126. os.path.join(buildqueue_dir, 'q.json'))
  127. os.remove(os.path.join(buildqueue_dir, 'q.json'))
  128. app.logger.info('Removing ' +
  129. os.path.join(buildqueue_dir))
  130. os.rmdir(os.path.join(buildqueue_dir))
  131. # define source and app directories
  132. sourcedir = os.path.abspath(os.path.join('..', 'ardupilot'))
  133. appdir = os.path.abspath(os.curdir)
  134. if not os.path.isdir('buildqueue'):
  135. os.mkdir('buildqueue')
  136. thread = Thread(target=check_queue, args=())
  137. thread.daemon = True
  138. thread.start()
  139. # Directory of this file
  140. this_path = os.path.dirname(os.path.realpath(__file__))
  141. # Where the user requested tile are stored
  142. output_path = os.path.join(this_path, '..', 'userRequestFirmware')
  143. # Where the data database is
  144. tile_path = os.path.join(this_path, '..', 'data', 'tiles')
  145. # The output folder for all gzipped build requests
  146. app = Flask(__name__, static_url_path='/builds',
  147. static_folder=output_path, template_folder='templates')
  148. @app.route('/')
  149. def index():
  150. app.logger.info('Rendering index.html')
  151. return render_template('index.html')
  152. @app.route('/generate', methods=['GET', 'POST'])
  153. def generate():
  154. #if request.method == 'POST':
  155. features = []
  156. task = {}
  157. try:
  158. # fetch features from user input
  159. app.logger.info('Fetching features from user input')
  160. for i in range(1,8):
  161. value = request.form["option" + str(i)]
  162. features.append(value)
  163. undefine = "undef " + value.split()[1]
  164. features.insert(0,undefine)
  165. extra_hwdef = '\n'.join(features)
  166. #print("features: ", features)
  167. queue_lock.acquire()
  168. # create extra_hwdef.dat file and obtain md5sum
  169. app.logger.info('Creating ' + os.path.join('buildqueue', 'extra_hwdef.dat'))
  170. file = open(os.path.join('buildqueue', 'extra_hwdef.dat'),"w")
  171. app.logger.info('Writing\n' + extra_hwdef)
  172. file.write(extra_hwdef)
  173. file.close()
  174. app.logger.info('Getting md5sum')
  175. md5sum = subprocess.check_output(['md5sum', 'buildqueue/extra_hwdef.dat'],
  176. encoding = 'utf-8')
  177. md5sum = md5sum[:len(md5sum)-29]
  178. app.logger.info('md5sum = ' + md5sum)
  179. app.logger.info('Removing ' + os.path.join('buildqueue', 'extra_hwdef.dat'))
  180. os.remove(os.path.join('buildqueue', 'extra_hwdef.dat'))
  181. # obtain git-hash of source
  182. app.logger.info('Getting git hash')
  183. git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
  184. cwd = sourcedir,
  185. encoding = 'utf-8')
  186. git_hash = git_hash[:len(git_hash)-1]
  187. app.logger.info('Git hash = ' + git_hash)
  188. # create directories using concatenated token of git-hash and md5sum of hwdef
  189. token = git_hash + "-" + md5sum
  190. app.logger.info('token = ' + token)
  191. buildqueue_dir = os.path.join(appdir, 'buildqueue', token)
  192. if not os.path.isdir(buildqueue_dir):
  193. app.logger.info('Creating ' + buildqueue_dir)
  194. os.mkdir(buildqueue_dir)
  195. app.logger.info('Opening ' + os.path.join(buildqueue_dir, 'extra_hwdef.dat'))
  196. file = open(os.path.join(buildqueue_dir, 'extra_hwdef.dat'),"w")
  197. app.logger.info('Writing\n' + extra_hwdef)
  198. file.write(extra_hwdef)
  199. file.close()
  200. # fill dictionary of variables and create json file
  201. task['hwdef_md5sum'] = md5sum
  202. task['git_hash'] = git_hash
  203. task['token'] = token
  204. task['sourcedir'] = sourcedir
  205. task['extra_hwdef'] = os.path.join(buildqueue_dir, 'extra_hwdef.dat')
  206. task['board'] = request.form["board"]
  207. task['vehicle'] = request.form["vehicle"]
  208. app.logger.info('Opening ' + os.path.join(buildqueue_dir, 'q.json'))
  209. jfile = open(os.path.join(buildqueue_dir, 'q.json'), "w")
  210. app.logger.info('Writing task file to ' + os.path.join(buildqueue_dir, 'q.json'))
  211. jfile.write(json.dumps(task))
  212. jfile.close()
  213. queue_lock.release()
  214. #print(task)
  215. apache_build_dir = "http://localhost:8080/" + token
  216. apache_build_log = "http://localhost:8080/" + token + "/build.log"
  217. app.logger.info('Rendering generate.html')
  218. return render_template('generate.html',
  219. apache_build_dir=apache_build_dir,
  220. apache_build_log=apache_build_log,
  221. token=token)
  222. except:
  223. return render_template('generate.html', error='Error occured')
  224. @app.route('/home', methods=['POST'])
  225. def home():
  226. app.logger.info('Rendering index.html')
  227. return render_template('index.html')
  228. if __name__ == "__main__":
  229. app.run()