2
0

build.py 10 KB

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