app.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. from flask import Flask, render_template, request, flash
  16. from threading import Thread, Lock
  17. queue_lock = Lock()
  18. '''
  19. Directory structure:
  20. - all paths relative to where the app starts
  21. - waf builds go into build
  22. - json queued build files go into buildqueue
  23. - resulting builds go into done
  24. - ardupilot is in ardupilot directory
  25. - templates in CustomBuild/templates
  26. '''
  27. def get_template(filename):
  28. return render_template(filename)
  29. def remove_directory_recursive(dirname):
  30. '''remove a directory recursively'''
  31. if not os.path.exists(dirname):
  32. return
  33. f = pathlib.Path(dirname)
  34. if f.is_file():
  35. f.unlink()
  36. else:
  37. for child in f.iterdir():
  38. shutil.rmtree(child)
  39. f.rmdir()
  40. def create_directory(dir_path):
  41. '''create a directory, don't fail if it exists'''
  42. pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
  43. def run_build(taskfile, builddir, done_dir):
  44. # run a build with parameters from task
  45. task = json.loads(open(taskfile).read())
  46. remove_directory_recursive(os.path.join(sourcedir, done_dir))
  47. remove_directory_recursive(os.path.join(sourcedir, builddir))
  48. create_directory(os.path.join(sourcedir, done_dir))
  49. create_directory(os.path.join(sourcedir, builddir))
  50. with open(os.path.join(sourcedir, done_dir, 'build.log'), "wb") as log:
  51. subprocess.run(['git', 'submodule',
  52. 'update', '--recursive',
  53. '--force', '--init'], stdout=log, stderr=log)
  54. subprocess.run(['./waf', 'configure',
  55. '--board', task['board'],
  56. '--out', builddir,
  57. '--extra-hwdef', task['extra_hwdef']],
  58. cwd = task['sourcedir'],
  59. stdout=log, stderr=log)
  60. subprocess.run(['./waf', 'clean'], cwd = task['sourcedir'],
  61. stdout=log, stderr=log)
  62. subprocess.run(['./waf', task['vehicle']], cwd = task['sourcedir'],
  63. stdout=log, stderr=log)
  64. # background thread to check for queued build requests
  65. def check_queue():
  66. while(1):
  67. queue_lock.acquire()
  68. listing = os.listdir('buildqueue')
  69. queue_lock.release()
  70. if listing:
  71. for token in listing:
  72. builddir = 'build'
  73. done_dir = os.path.join('done', token)
  74. buildqueue_dir = os.path.join('buildqueue', token)
  75. # check if build exists
  76. if os.path.isdir(os.path.join(sourcedir, done_dir)):
  77. print("Build already exists")
  78. else:
  79. # run build and rename build directory
  80. f = open(os.path.join(buildqueue_dir, 'q.json'))
  81. task = json.load(f)
  82. run_build(os.path.join(buildqueue_dir, 'q.json'),
  83. builddir, done_dir)
  84. copy_tree(os.path.join(sourcedir, builddir, task['board']),
  85. os.path.join(sourcedir, done_dir))
  86. # remove working files
  87. os.remove(os.path.join(buildqueue_dir, 'extra_hwdef.dat'))
  88. os.remove(os.path.join(buildqueue_dir, 'q.json'))
  89. os.rmdir(os.path.join(buildqueue_dir))
  90. print("Build successful")
  91. # define source and app directories
  92. sourcedir = os.path.abspath(os.path.join('..', 'ardupilot'))
  93. appdir = os.path.abspath(os.curdir)
  94. if not os.path.isdir('buildqueue'):
  95. os.mkdir('buildqueue')
  96. thread = Thread(target=check_queue, args=())
  97. thread.daemon = True
  98. thread.start()
  99. # Directory of this file
  100. this_path = os.path.dirname(os.path.realpath(__file__))
  101. # Where the user requested tile are stored
  102. output_path = os.path.join(this_path, '..', 'userRequestFirmware')
  103. # Where the data database is
  104. tile_path = os.path.join(this_path, '..', 'data', 'tiles')
  105. # The output folder for all gzipped build requests
  106. app = Flask(__name__, static_url_path='/builds', static_folder=output_path, template_folder='templates')
  107. #def compressFiles(fileList, uuidkey):
  108. # create a zip file comprised of dat.gz tiles
  109. #zipthis = os.path.join(output_path, uuidkey + '.zip')
  110. # create output dirs if needed
  111. #try:
  112. # os.makedirs(output_path)
  113. #except OSError:
  114. # pass
  115. #try:
  116. # os.makedirs(tile_path)
  117. #except OSError:
  118. # pass
  119. #try:
  120. # with zipfile.ZipFile(zipthis, 'w') as terrain_zip:
  121. # for fn in fileList:
  122. # if not os.path.exists(fn):
  123. # #download if required
  124. # print("Downloading " + os.path.basename(fn))
  125. # g = urllib.request.urlopen('https://terrain.ardupilot.org/data/tiles/' +
  126. # os.path.basename(fn))
  127. # print("Downloaded " + os.path.basename(fn))
  128. # with open(fn, 'b+w') as f:
  129. # f.write(g.read())
  130. # need to decompress file and pass to zip
  131. # with gzip.open(fn, 'r') as f_in:
  132. # myio = BytesIO(f_in.read())
  133. # print("Decomp " + os.path.basename(fn))
  134. # and add file to zip
  135. # terrain_zip.writestr(os.path.basename(fn)[:-3], myio.read(),
  136. # compress_type=zipfile.ZIP_DEFLATED)
  137. #except Exception as ex:
  138. # print("Unexpected error: {0}".format(ex))
  139. # return False
  140. #return True
  141. @app.route('/')
  142. def index():
  143. return get_template('index.html')
  144. @app.route('/generate', methods=['GET', 'POST'])
  145. def generate():
  146. #if request.method == 'POST':
  147. features = []
  148. task = {}
  149. # fetch features from user input
  150. for i in range(1,8):
  151. value = request.form["option" + str(i)]
  152. features.append(value)
  153. undefine = "undef " + value.split()[1]
  154. features.insert(0,undefine)
  155. extra_hwdef = '\n'.join(features)
  156. #print("features: ", features)
  157. queue_lock.acquire()
  158. # create extra_hwdef.dat file and obtain md5sum
  159. file = open(os.path.join('buildqueue', 'extra_hwdef.dat'),"w")
  160. file.write(extra_hwdef)
  161. file.close()
  162. md5sum = subprocess.check_output(['md5sum', 'buildqueue/extra_hwdef.dat'],
  163. encoding = 'utf-8')
  164. md5sum = md5sum[:len(md5sum)-29]
  165. os.remove(os.path.join('buildqueue', 'extra_hwdef.dat'))
  166. # obtain git-hash of source
  167. git_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
  168. cwd = sourcedir,
  169. encoding = 'utf-8')
  170. git_hash = git_hash[:len(git_hash)-1]
  171. # create directories using concatenated token of git-hash and md5sum of hwdef
  172. token = git_hash + "-" + md5sum
  173. buildqueue_dir = os.path.join(appdir, 'buildqueue', token)
  174. if not os.path.isdir(buildqueue_dir):
  175. os.mkdir(buildqueue_dir)
  176. file = open(os.path.join(buildqueue_dir, 'extra_hwdef.dat'),"w")
  177. file.write(extra_hwdef)
  178. file.close()
  179. # fill dictionary of variables and create json file
  180. task['hwdef_md5sum'] = md5sum
  181. task['git_hash'] = git_hash
  182. task['token'] = token
  183. task['sourcedir'] = sourcedir
  184. task['extra_hwdef'] = os.path.join(buildqueue_dir, 'extra_hwdef.dat')
  185. task['board'] = request.form["board"]
  186. task['vehicle'] = request.form["vehicle"]
  187. jfile = open(os.path.join(buildqueue_dir, 'q.json'), "w")
  188. jfile.write(json.dumps(task))
  189. jfile.close()
  190. queue_lock.release()
  191. #print(task)
  192. apache_build_dir = "http://localhost:8080/" + token
  193. apache_build_log = "http://localhost:8080/" + token + "/build.log"
  194. return render_template('generate.html',
  195. apache_build_dir=apache_build_dir,
  196. apache_build_log=apache_build_log,
  197. token=token,)
  198. # remove duplicates
  199. #filelist = list(dict.fromkeys(filelist))
  200. #print(filelist)
  201. #compress
  202. #success = compressFiles(filelist, uuidkey)
  203. # as a cleanup, remove any generated terrain older than 24H
  204. #for f in os.listdir(output_path):
  205. # if os.stat(os.path.join(output_path, f)).st_mtime < time.time() - 24 * 60 * 60:
  206. # print("Removing old file: " + str(os.path.join(output_path, f)))
  207. # os.remove(os.path.join(output_path, f))
  208. #if success:
  209. # print("Generated " + "/terrain/" + uuidkey + ".zip")
  210. # return get_template('generate.html', urlkey="/terrain/" + uuidkey + ".zip",
  211. # uuidkey=uuidkey, outsideLat=outsideLat)
  212. #else:
  213. # print("Failed " + "/terrain/" + uuidkey + ".zip")
  214. # return get_template('generate.html', error="Cannot generate terrain",
  215. # uuidkey=uuidkey)
  216. #else:
  217. # print("Bad get")
  218. # return get_template('generate.html', error="Need to use POST, not GET")
  219. @app.route('/home', methods=['POST'])
  220. def home():
  221. return get_template('index.html')
  222. if __name__ == "__main__":
  223. app.run()