build.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. #!/usr/bin/python
  2. from browser.linkjs import link
  3. import optparse
  4. import os
  5. import shutil
  6. import stat
  7. import subprocess
  8. import sys
  9. import zipfile
  10. class package( object ):
  11. root = os.path.join('bin', 'js')
  12. archive = os.path.join('bin', 'compiled.zip')
  13. @staticmethod
  14. def pack():
  15. file = zipfile.ZipFile(package.archive, 'w')
  16. for f in os.listdir(package.root):
  17. file.write(os.path.join(package.root, f), f)
  18. @staticmethod
  19. def unpack():
  20. cleanup(package.root)
  21. file = zipfile.ZipFile(package.archive, 'r')
  22. file.extractall(package.root)
  23. # http://stackoverflow.com/questions/1889597/deleting-directory-in-python
  24. def remove_readonly(fn, path, excinfo):
  25. if fn is os.rmdir:
  26. os.chmod(path, stat.S_IWRITE)
  27. os.rmdir(path)
  28. elif fn is os.remove:
  29. os.chmod(path, stat.S_IWRITE)
  30. os.remove(path)
  31. def norm_path(path):
  32. return os.path.normcase(os.path.normpath(os.path.realpath(os.path.abspath(path))))
  33. def is_parent_for(parent, child):
  34. parent = norm_path(parent)
  35. child = norm_path(child)
  36. while True:
  37. if parent == child:
  38. return True
  39. next = os.path.dirname(child)
  40. if next == child:
  41. return False
  42. child = next
  43. def cleanup(dir):
  44. if not os.path.exists(dir):
  45. return
  46. this_dir = os.path.dirname(__file__)
  47. if is_parent_for(dir, this_dir):
  48. raise Exception("cannot delete itself: %s" % this_dir)
  49. shutil.rmtree(dir, onerror=remove_readonly)
  50. def copy(src, dst_dir):
  51. dst = os.path.join(dst_dir, os.path.basename(src))
  52. if os.path.exists(dst):
  53. os.chmod(dst, stat.S_IWRITE)
  54. print('%s -> %s' % (src, dst))
  55. shutil.copy(src, dst)
  56. def copytree(src, dst):
  57. cleanup(dst)
  58. print('%s -> %s' % (src, dst))
  59. shutil.copytree(src, dst)
  60. def run(cmd, env=None, cwd=None, print_output=False):
  61. p = subprocess.Popen(
  62. cmd,
  63. stdout=None if print_output else subprocess.PIPE,
  64. stderr=subprocess.STDOUT,
  65. #shell = True,
  66. env=env,
  67. cwd=cwd)
  68. result = None if print_output else p.stdout.read().decode()
  69. rc = p.wait()
  70. if rc:
  71. if result:
  72. print(result)
  73. print('"%s" failed with exit code %d' % (' '.join(cmd), rc))
  74. exit(rc)
  75. return result
  76. root = os.path.dirname(os.path.abspath(__file__))
  77. snapshot_root = os.path.join(root, 'snapshot')
  78. def make_js_search_dirs(bin):
  79. return [os.path.join(root, 'test'),
  80. os.path.join(root, 'src'),
  81. bin];
  82. def run_node(args, js_search_dirs, cwd=None):
  83. node_exe, path_separator = ('node.exe', ';') if os.name == 'nt' else ('node', ':')
  84. node_env = dict(list(os.environ.items())
  85. + [('NODE_PATH', path_separator.join(js_search_dirs))])
  86. run([node_exe] + args, node_env, cwd, print_output=True)
  87. def run_tests(bin, unit_test=None, code_test=None):
  88. print('run tests using "%s" ->' % bin)
  89. js_search_dirs = make_js_search_dirs(bin)
  90. if not code_test:
  91. unit_tests = os.path.join(root, 'test', 'test_unit.js')
  92. args = [unit_tests]
  93. if unit_test:
  94. args += [unit_test]
  95. run_node(args, js_search_dirs)
  96. if not unit_test:
  97. compile_tests = os.path.join(root, 'test', 'test_compile.js')
  98. args = [compile_tests]
  99. if code_test:
  100. args += [code_test]
  101. run_node(args, js_search_dirs, cwd=os.path.join(root, 'test'))
  102. print('<-tests')
  103. def recompile(bin):
  104. print('recompile oberon sources using "%s"...' % bin)
  105. compiler = os.path.join(root, 'src', 'oc_nodejs.js')
  106. sources = ['EberonSymbols.ob', 'EberonCast.ob', 'EberonOperator.ob',
  107. 'OberonSymbols.ob', 'Lexer.ob', 'Module.ob']
  108. result = os.path.join(root, 'bin.recompile')
  109. cleanup(result)
  110. os.mkdir(result)
  111. out = os.path.join(result, 'js')
  112. os.mkdir(out)
  113. run_node([compiler,
  114. '--include=src/ob;src/eberon;src/oberon',
  115. '--out-dir=%s' % out,
  116. '--import-dir=js',
  117. '--timing=true']
  118. + sources,
  119. make_js_search_dirs(bin))
  120. return result
  121. def compile_using_snapshot(src):
  122. out = os.path.join(root, 'bin', 'js')
  123. compiler = os.path.join(snapshot_root, 'oc_nodejs.js')
  124. js_search_dirs = [snapshot_root]
  125. run_node([ compiler,
  126. '--include=src/ob;src/eberon;src/oberon',
  127. '--out-dir=%s' % out,
  128. '--import-dir=js',
  129. src],
  130. js_search_dirs,
  131. cwd=root)
  132. def build_html(options):
  133. version = None
  134. if options.set_version:
  135. print(run(['git', 'pull']))
  136. version = run(['git', 'log', '-1', '--format="%ci%n%H"'])
  137. out = options.out
  138. build_version = None
  139. build_version_path = os.path.join(out, 'version.txt')
  140. try:
  141. with open(build_version_path) as f:
  142. build_version = f.read()
  143. except:
  144. pass
  145. if (not build_version is None) and build_version == version:
  146. print("current html is up to date, do nothing")
  147. return
  148. print('unpacking compiled js to %s...' % package.root)
  149. package.unpack()
  150. if not os.path.exists(out):
  151. os.mkdir(out)
  152. link(['oc.js', 'oberon/oberon_grammar.js', 'eberon/eberon_grammar.js'],
  153. os.path.join(out, 'oc.js'),
  154. ['src', 'bin'],
  155. version)
  156. copy('browser/oberonjs.html', out)
  157. for d in ['codemirror', 'jslibs']:
  158. copytree(os.path.join('browser', d), os.path.join(out, d))
  159. if version is None:
  160. if os.path.exists(build_version_path):
  161. os.remove(build_version_path)
  162. else:
  163. with open(build_version_path, 'w') as f:
  164. f.write(version)
  165. def pre_commit_check(options):
  166. bin = os.path.join(root, 'bin')
  167. run_tests(bin)
  168. recompiled = recompile(bin)
  169. run_tests(recompiled)
  170. print('%s -> %s' % (recompiled, bin))
  171. cleanup(bin)
  172. os.rename(recompiled, bin)
  173. print('packaging compiled js to %s...' % package.root)
  174. package.pack()
  175. class compile_target(object):
  176. name = 'compile'
  177. description = 'compile oberon source file using the snapshot'
  178. @staticmethod
  179. def setup_options(parser):
  180. parser.add_option('--file', help='file to compile')
  181. def __init__(self, options):
  182. compile_using_snapshot(options.file)
  183. class html_target(object):
  184. name = 'html'
  185. description = 'build html page'
  186. @staticmethod
  187. def setup_options(parser):
  188. parser.add_option('--out', help='output directory, default: "_out"', default='_out')
  189. parser.add_option('--set-version', action="store_true", help='include version in built html')
  190. def __init__(self, options):
  191. build_html(options)
  192. class tests_target(object):
  193. name = 'tests'
  194. description = 'run tests'
  195. @staticmethod
  196. def setup_options(parser):
  197. parser.add_option('--unit', help='run specific unit test')
  198. parser.add_option('--code', help='run specific code generator test')
  199. def __init__(self, options):
  200. bin = os.path.join(root, 'bin')
  201. run_tests(bin, options.unit, options.code)
  202. class pre_commit_target(object):
  203. name = 'pre-commit'
  204. description = 'run tests, recompile oberon sources, run tests against just recompiled sources, pack compiled sources and build html'
  205. @staticmethod
  206. def setup_options(parser):
  207. pass
  208. def __init__(self, options):
  209. pre_commit_check(options)
  210. class snapshot_target(object):
  211. name = 'snapshot'
  212. description = 'make snapshot - current compiled compiler (set of *.js) to use for compiling oberon sources'
  213. @staticmethod
  214. def setup_options(parser):
  215. pass
  216. def __init__(self, options):
  217. new_dir = snapshot_root + '.new'
  218. cleanup(new_dir)
  219. shutil.copytree(os.path.join(root, 'src'), new_dir)
  220. shutil.copytree(os.path.join(root, 'bin', 'js'), os.path.join(new_dir, 'js'))
  221. if os.path.exists(snapshot_root):
  222. old_dir = snapshot_root + '.bak'
  223. cleanup(old_dir)
  224. os.rename(snapshot_root, old_dir)
  225. os.rename(new_dir, snapshot_root)
  226. targets = [compile_target, html_target, tests_target, pre_commit_target, snapshot_target]
  227. def build(target, options):
  228. targets[target](options)
  229. if __name__ == '__main__':
  230. description = 'Targets: %s' % '|'.join([t.name for t in targets])
  231. parser = optparse.OptionParser(
  232. description=description,
  233. usage='%prog [options] <target>'
  234. )
  235. for t in targets:
  236. group = optparse.OptionGroup(parser, 'target "%s"' % t.name, t.description)
  237. t.setup_options(group)
  238. parser.add_option_group(group)
  239. (options, args) = parser.parse_args()
  240. if len(args) != 1:
  241. parser.print_help();
  242. exit(-1)
  243. target_name = args[0]
  244. target = None
  245. for t in targets:
  246. if t.name == target_name:
  247. target = t
  248. break
  249. if target is None:
  250. print('uknown target: "%s"' % target_name)
  251. exit(-1)
  252. target(options)