app.py 8.9 KB

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