build.py 11 KB

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