build.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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 unit_test is None and code_test is None:
  91. unit_test = '*'
  92. code_test = '*'
  93. if unit_test:
  94. unit_tests = os.path.join(root, 'test', 'test_unit_run.js')
  95. args = [unit_tests]
  96. if unit_test != '*':
  97. args += [unit_test]
  98. run_node(args, js_search_dirs)
  99. if code_test:
  100. compile_tests = os.path.join(root, 'test', 'test_compile.js')
  101. args = [compile_tests]
  102. if code_test != '*':
  103. args += [code_test]
  104. run_node(args, js_search_dirs, cwd=os.path.join(root, 'test'))
  105. print('<-tests')
  106. def recompile(bin):
  107. print('recompile oberon sources using "%s"...' % bin)
  108. compiler = os.path.join(root, 'src', 'oc_nodejs.js')
  109. sources = ['ContextConst.ob', 'ContextIdentdef', 'ContextType.ob', 'EberonSymbols.ob', 'EberonCast.ob',
  110. 'EberonConstructor.ob', 'EberonOperator.ob', 'EberonScope.ob',
  111. 'OberonSymbols.ob', 'Lexer.ob', 'Module.ob']
  112. result = os.path.join(root, 'bin.recompile')
  113. cleanup(result)
  114. os.mkdir(result)
  115. out = os.path.join(result, 'js')
  116. os.mkdir(out)
  117. run_node([compiler,
  118. '--include=src/ob;src/eberon;src/oberon',
  119. '--out-dir=%s' % out,
  120. '--import-dir=js',
  121. '--timing=true']
  122. + sources,
  123. make_js_search_dirs(bin))
  124. return result
  125. def compile_using_snapshot(src):
  126. out = os.path.join(root, 'bin', 'js')
  127. compiler = os.path.join(snapshot_root, 'oc_nodejs.js')
  128. js_search_dirs = [snapshot_root]
  129. run_node([ compiler,
  130. '--include=src/ob;src/eberon;src/oberon',
  131. '--out-dir=%s' % out,
  132. '--import-dir=js',
  133. src],
  134. js_search_dirs,
  135. cwd=root)
  136. def build_html(options):
  137. version = None
  138. if options.set_version:
  139. print(run(['git', 'pull']))
  140. version = run(['git', 'log', '-1', '--format="%ci%n%H"'])
  141. out = options.out
  142. build_version = None
  143. build_version_path = os.path.join(out, 'version.txt')
  144. try:
  145. with open(build_version_path) as f:
  146. build_version = f.read()
  147. except:
  148. pass
  149. if (not build_version is None) and build_version == version:
  150. print("current html is up to date, do nothing")
  151. return
  152. if not options.do_not_unpack_compiled:
  153. print('unpacking compiled js to %s...' % package.root)
  154. package.unpack()
  155. if not os.path.exists(out):
  156. os.mkdir(out)
  157. link(['oc.js', 'oberon/oberon_grammar.js', 'eberon/eberon_grammar.js', 'test_unit.js'],
  158. os.path.join(out, 'oc.js'),
  159. ['src', 'bin', 'test'],
  160. version)
  161. copy('browser/oberonjs.html', out)
  162. for d in ['codemirror', 'jslibs']:
  163. copytree(os.path.join('browser', d), os.path.join(out, d))
  164. if version is None:
  165. if os.path.exists(build_version_path):
  166. os.remove(build_version_path)
  167. else:
  168. with open(build_version_path, 'w') as f:
  169. f.write(version)
  170. def recompile_with_replace(bin, skip_tests = False):
  171. recompiled = recompile(bin)
  172. if not skip_tests:
  173. run_tests(recompiled)
  174. print('%s -> %s' % (recompiled, bin))
  175. cleanup(bin)
  176. os.rename(recompiled, bin)
  177. def pre_commit_check(options):
  178. bin = os.path.join(root, 'bin')
  179. run_tests(bin)
  180. recompile_with_replace(bin)
  181. print('packaging compiled js to %s...' % package.root)
  182. package.pack()
  183. class compile_target(object):
  184. name = 'compile'
  185. description = 'compile oberon source file using the snapshot'
  186. @staticmethod
  187. def setup_options(parser):
  188. parser.add_option('--file', help='file to compile')
  189. def __init__(self, options):
  190. compile_using_snapshot(options.file)
  191. class self_recompile_target(object):
  192. name = 'self-recompile'
  193. description = 'compile itself using current sources'
  194. @staticmethod
  195. def setup_options(parser):
  196. parser.add_option('--skip-tests', help='do not run test after recompile')
  197. def __init__(self, options):
  198. bin = os.path.join(root, 'bin')
  199. recompile_with_replace(bin, options.skip_tests)
  200. class html_target(object):
  201. name = 'html'
  202. description = 'build html page'
  203. @staticmethod
  204. def setup_options(parser):
  205. parser.add_option('--out', help='output directory, default: "_out"', default='_out')
  206. parser.add_option('--set-version', action="store_true", help='include version in built html')
  207. parser.add_option('--do-not-unpack-compiled', action="store_true", help='do unpack already compiled "binaries", use current')
  208. def __init__(self, options):
  209. build_html(options)
  210. class tests_target(object):
  211. name = 'tests'
  212. description = 'run tests'
  213. @staticmethod
  214. def setup_options(parser):
  215. parser.add_option('--unit', help='run specific unit test, use "*" to run all unit tests')
  216. parser.add_option('--code', help='run specific code generator test, use "*" to run all generator tests')
  217. def __init__(self, options):
  218. bin = os.path.join(root, 'bin')
  219. run_tests(bin, options.unit, options.code)
  220. class pre_commit_target(object):
  221. name = 'pre-commit'
  222. description = 'run tests, recompile oberon sources, run tests against just recompiled sources, pack compiled sources and build html'
  223. @staticmethod
  224. def setup_options(parser):
  225. pass
  226. def __init__(self, options):
  227. pre_commit_check(options)
  228. class snapshot_target(object):
  229. name = 'snapshot'
  230. description = 'make snapshot - current compiled compiler (set of *.js) to use for compiling oberon sources'
  231. @staticmethod
  232. def setup_options(parser):
  233. pass
  234. def __init__(self, options):
  235. new_dir = snapshot_root + '.new'
  236. cleanup(new_dir)
  237. shutil.copytree(os.path.join(root, 'src'), new_dir)
  238. shutil.copytree(os.path.join(root, 'bin', 'js'), os.path.join(new_dir, 'js'))
  239. if os.path.exists(snapshot_root):
  240. old_dir = snapshot_root + '.bak'
  241. cleanup(old_dir)
  242. os.rename(snapshot_root, old_dir)
  243. os.rename(new_dir, snapshot_root)
  244. targets = [compile_target, self_recompile_target, html_target, tests_target, pre_commit_target, snapshot_target]
  245. def build(target, options):
  246. targets[target](options)
  247. if __name__ == '__main__':
  248. description = 'Targets: %s' % '|'.join([t.name for t in targets])
  249. parser = optparse.OptionParser(
  250. description=description,
  251. usage='%prog [options] <target>'
  252. )
  253. for t in targets:
  254. group = optparse.OptionGroup(parser, 'target "%s"' % t.name, t.description)
  255. t.setup_options(group)
  256. parser.add_option_group(group)
  257. (options, args) = parser.parse_args()
  258. if len(args) != 1:
  259. parser.print_help();
  260. exit(-1)
  261. target_name = args[0]
  262. target = None
  263. for t in targets:
  264. if t.name == target_name:
  265. target = t
  266. break
  267. if target is None:
  268. print('uknown target: "%s"' % target_name)
  269. exit(-1)
  270. target(options)