123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- #!/usr/bin/python
- from browser.linkjs import link
- import argparse
- import os
- import shutil
- import stat
- import subprocess
- import sys
- import tempfile
- import zipfile
- root = os.path.dirname(os.path.abspath(__file__))
- src_dir = os.path.join(root, 'src')
- default_out_dir = os.path.join(root, '_out')
- default_out_js = os.path.join(default_out_dir, 'js')
- class Package( object ):
- bin_dir = os.path.join(root, 'bin')
- js_root = os.path.join(bin_dir, 'js')
- archive = os.path.join(bin_dir, 'compiled.zip')
- @staticmethod
- def pack():
- print('packing current js "%s" -> "%s"...' % (Package.js_root, Package.archive))
- file = zipfile.ZipFile(Package.archive, 'w')
- for f in os.listdir(Package.js_root):
- file.write(os.path.join(Package.js_root, f), f)
- @staticmethod
- def unpacked_bin():
- if not os.path.exists(Package.js_root):
- print('unpacking pre-compiled js "%s" -> "%s"...'
- % (Package.archive, Package.js_root))
- file = zipfile.ZipFile(Package.archive, 'r')
- file.extractall(Package.js_root)
- return Package.bin_dir
- def _remove_readonly(fn, path, excinfo):
- os.chmod(path, stat.S_IWRITE)
- fn(path)
- def norm_path(path):
- return os.path.normcase(os.path.normpath(os.path.realpath(os.path.abspath(path))))
- def is_parent_for(parent, child):
- parent = norm_path(parent)
- child = norm_path(child)
- while True:
- if parent == child:
- return True
- next = os.path.dirname(child)
- if next == child:
- return False
- child = next
- def cleanup(dir):
- if not os.path.exists(dir):
- return
- this_dir = os.path.dirname(__file__)
- if is_parent_for(dir, this_dir):
- raise Exception("cannot delete itself: %s" % this_dir)
- shutil.rmtree(dir, onerror=_remove_readonly)
- def copy(src, dst_dir):
- dst = os.path.join(dst_dir, os.path.basename(src))
- if os.path.exists(dst):
- os.chmod(dst, stat.S_IWRITE)
- print('%s -> %s' % (src, dst))
- shutil.copy(src, dst)
- def copytree(src, dst):
- cleanup(dst)
- print('%s -> %s' % (src, dst))
- shutil.copytree(src, dst)
- def run(cmd, env=None, cwd=None, print_output=False):
- p = subprocess.Popen(
- cmd,
- stdout=None if print_output else subprocess.PIPE,
- stderr=subprocess.STDOUT,
- #shell = True,
- env=env,
- cwd=cwd)
- result = None if print_output else p.stdout.read().decode()
- rc = p.wait()
- if rc:
- if result:
- print(result)
- print('"%s" failed with exit code %d' % (' '.join(cmd), rc))
- exit(rc)
- return result
- snapshot_root = os.path.join(root, 'snapshot')
- def make_js_search_dirs(bin):
- return [os.path.join(root, 'test'), src_dir, bin]
- def run_node(args, js_search_dirs, cwd=None):
- node_exe, path_separator = ('node.exe', ';') if os.name == 'nt' else ('node', ':')
- node_env = dict(list(os.environ.items())
- + [('NODE_PATH', path_separator.join(js_search_dirs))])
- run([node_exe] + args, node_env, cwd, print_output=True)
- def run_tests(bin, unit_test=None, code_test=None):
- print('run tests using "%s" ->' % bin)
- js_search_dirs = make_js_search_dirs(bin)
- if unit_test is None and code_test is None:
- unit_test = '*'
- code_test = '*'
- if unit_test:
- unit_tests = os.path.join(root, 'test', 'test_unit_run.js')
- args = [unit_tests]
- if unit_test != '*':
- args += [unit_test]
- run_node(args, js_search_dirs)
- if code_test:
- compile_tests = os.path.join(root, 'test', 'test_compile.js')
- args = [compile_tests]
- if code_test != '*':
- args += [code_test]
- run_node(args, js_search_dirs, cwd=os.path.join(root, 'test'))
- print('<-tests')
- def _run_compiler(bin_dir, sources, locale, out_dir, timing=False):
- if not os.path.exists(out_dir):
- os.makedirs(out_dir)
- include = ['src/ob', 'src/eberon', 'src/oberon']
- include += [os.path.join(i, locale) for i in include]
- args = [os.path.join(src_dir, 'oc_nodejs.js'),
- '--include=' + ';'.join([os.path.join(root, i) for i in include]),
- '--out-dir=%s' % out_dir,
- '--import-dir=js'
- ]
- if timing:
- args.append('--timing=true')
- args += sources
- run_node(args, [bin_dir, src_dir], cwd=root)
- all_oberon_sources = [
- 'ContextAssignment.ob',
- 'EberonSymbols.ob',
- 'EberonContextCase.ob', 'EberonContextExpression.ob',
- 'EberonContextIdentdef.ob', 'EberonContextIf.ob',
- 'EberonContextInPlace.ob', 'EberonContextProcedure',
- 'EberonContextType.ob', 'EberonContextVar.ob', 'EberonLanguageContext.ob',
- 'OberonContext.ob', 'OberonContextType.ob', 'OberonContextVar.ob',
- 'OberonSymbols.ob', 'Lexer.ob', 'Module.ob']
- def build_html(options):
- version = None
- if options.set_version:
- version = run(['git', 'log', '-1', '--format="%ci%n%H"'])
- out = options.out
- build_version = None
- build_version_path = os.path.join(out, 'version.txt')
- try:
- with open(build_version_path) as f:
- build_version = f.read()
- except:
- pass
- if (not build_version is None) and build_version == version:
- print("current html is up to date, do nothing")
- return
- if not os.path.exists(out):
- os.mkdir(out)
- link(['oc.js', 'oberon/oberon_grammar.js', 'eberon/eberon_grammar.js', 'test_unit.js'],
- os.path.join(out, 'oc.js'),
- ['src', Package.unpacked_bin(), 'test'],
- version)
- copy('browser/oberonjs.html', out)
- for d in ['codemirror', 'jslibs']:
- copytree(os.path.join('browser', d), os.path.join(out, d))
-
- if version is None:
- if os.path.exists(build_version_path):
- os.remove(build_version_path)
- else:
- with open(build_version_path, 'w') as f:
- f.write(version)
- def _recompile_with_replace(locale, skip_tests=False):
- bin_dir = Package.unpacked_bin()
- new_bin = tempfile.mkdtemp(dir=bin_dir)
- try:
- new_js = os.path.join(new_bin, 'js')
- print('recompile all oberon sources to "%s"...' % new_js)
- _run_compiler(bin_dir, all_oberon_sources, locale, new_js)
- if not skip_tests:
- run_tests(new_bin)
- print('replacing: "%s" -> "%s"...' % (new_js, Package.js_root))
- cleanup(Package.js_root)
- os.rename(new_js, Package.js_root)
- finally:
- shutil.rmtree(new_bin)
- print('OK!')
- def pre_commit_check(options):
- _recompile_with_replace(options.locale)
- Package.pack()
- class compile_target(object):
- name = 'compile'
- description = 'compile oberon source file'
- @staticmethod
- def setup_options(parser):
- parser.add_argument(
- 'files', nargs='+', metavar='FILE',
- help='oberon source file(s) to compile')
- parser.add_argument(
- '--out',
- help='output directory, default: "%(default)s"',
- default=default_out_js)
- def __init__(self, options):
- _run_compiler(
- Package.unpacked_bin(), options.files, options.locale, options.out)
- class recompile_target(object):
- name = 'recompile'
- description = 'recompile all oberon source files'
- @staticmethod
- def setup_options(parser):
- parser.add_argument(
- '--out',
- help='output directory, default: "%(default)s"',
- default=default_out_js)
- def __init__(self, options):
- _run_compiler(Package.unpacked_bin(), all_oberon_sources,
- options.locale, options.out)
- class self_recompile_target(object):
- name = 'self-recompile'
- description = 'compile and replace itself using current sources'
- @staticmethod
- def setup_options(parser):
- parser.add_argument('--skip-tests', action='store_true',
- help='do not run test after recompile')
- def __init__(self, options):
- _recompile_with_replace(options.locale, options.skip_tests)
- class html_target(object):
- name = 'html'
- description = 'build html page'
- @staticmethod
- def setup_options(parser):
- parser.add_argument('--out', help='output directory, default: "%(default)s"', default='_out')
- parser.add_argument('--set-version', action="store_true", help='include version in built html')
- def __init__(self, options):
- build_html(options)
- class tests_target(object):
- name = 'tests'
- description = 'run tests'
- @staticmethod
- def setup_options(parser):
- parser.add_argument('--unit', help='run specific unit test, use "*" to run all unit tests')
- parser.add_argument('--code', help='run specific code generator test, use "*" to run all generator tests')
- def __init__(self, options):
- run_tests(Package.unpacked_bin(), options.unit, options.code)
- class pre_commit_target(object):
- name = 'pre-commit'
- description = 'recompile oberon sources, run tests against just recompiled sources, pack compiled sources'
- @staticmethod
- def setup_options(parser):
- pass
- def __init__(self, options):
- pre_commit_check(options)
- targets = [compile_target, recompile_target, self_recompile_target, html_target, tests_target, pre_commit_target]
- if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='Build tool')
- parser.add_argument('--locale', help='use specified localization subfolder, default is "%(default)s"', default='en')
- subparsers = parser.add_subparsers(help='targets')
- for t in targets:
- group = subparsers.add_parser(t.name, help=t.description)
- t.setup_options(group)
- group.set_defaults(func=t)
- args = parser.parse_args()
- args.func(args)
|