diff options
Diffstat (limited to 'tools/shared.py')
-rw-r--r-- | tools/shared.py | 282 |
1 files changed, 222 insertions, 60 deletions
diff --git a/tools/shared.py b/tools/shared.py index 532f561f..997c0ad9 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1,4 +1,4 @@ -import shutil, time, os, sys, json, tempfile, copy, shlex +import shutil, time, os, sys, json, tempfile, copy, shlex, atexit, subprocess from subprocess import Popen, PIPE, STDOUT from tempfile import mkstemp @@ -6,15 +6,22 @@ __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def path_from_root(*pathelems): return os.path.join(__rootpath__, *pathelems) -# Config file +# Emscripten configuration is done through the EM_CONFIG environment variable. +# If the string value contained in this environment variable contains newline +# separated definitions, then these definitions will be used to configure +# Emscripten. Otherwise, the string is understood to be a path to a settings +# file that contains the required definitions. EM_CONFIG = os.environ.get('EM_CONFIG') if not EM_CONFIG: EM_CONFIG = '~/.emscripten' -CONFIG_FILE = os.path.expanduser(EM_CONFIG) -if not os.path.exists(CONFIG_FILE): - shutil.copy(path_from_root('settings.py'), CONFIG_FILE) - print >> sys.stderr, ''' +if '\n' in EM_CONFIG: + CONFIG_FILE = None +else: + CONFIG_FILE = os.path.expanduser(EM_CONFIG) + if not os.path.exists(CONFIG_FILE): + shutil.copy(path_from_root('settings.py'), CONFIG_FILE) + print >> sys.stderr, ''' ============================================================================== Welcome to Emscripten! @@ -28,11 +35,12 @@ make sure LLVM_ROOT and NODE_JS are correct. This command will now exit. When you are done editing those paths, re-run it. ============================================================================== ''' % (EM_CONFIG, CONFIG_FILE) - sys.exit(0) + sys.exit(0) try: - exec(open(CONFIG_FILE, 'r').read()) + config_text = open(CONFIG_FILE, 'r').read() if CONFIG_FILE else EM_CONFIG + exec(config_text) except Exception, e: - print >> sys.stderr, 'Error in evaluating %s (at %s): %s' % (EM_CONFIG, CONFIG_FILE, str(e)) + print >> sys.stderr, 'Error in evaluating %s (at %s): %s, text: %s' % (EM_CONFIG, CONFIG_FILE, str(e), config_text) sys.exit(1) # Check that basic stuff we need (a JS engine to compile, Node.js, and Clang and LLVM) @@ -43,6 +51,8 @@ except Exception, e: def check_sanity(force=False): try: if not force: + if not CONFIG_FILE: + return # config stored directly in EM_CONFIG => skip sanity checks settings_mtime = os.stat(CONFIG_FILE).st_mtime sanity_file = CONFIG_FILE + '_sanity' try: @@ -52,6 +62,9 @@ def check_sanity(force=False): except: pass + print >> sys.stderr, '(Emscripten: Config file changed, clearing cache)' # LLVM may have changed, etc. + Cache.erase() + print >> sys.stderr, '(Emscripten: Running sanity checks)' if not check_engine(COMPILER_ENGINE): @@ -68,6 +81,11 @@ def check_sanity(force=False): print >> sys.stderr, 'FATAL: Cannot find %s, check the paths in %s' % (cmd, EM_CONFIG) sys.exit(0) + try: + subprocess.call([JAVA, '-version'], stdout=PIPE, stderr=PIPE) + except: + print >> sys.stderr, 'WARNING: java does not seem to exist, required for closure compiler. -O2 and above will fail. You need to define JAVA in ~/.emscripten (see settings.py)' + if not os.path.exists(CLOSURE_COMPILER): print >> sys.stderr, 'WARNING: Closure compiler (%s) does not exist, check the paths in %s. -O2 and above will fail' % (CLOSURE_COMPILER, EM_CONFIG) @@ -89,7 +107,6 @@ CLANG_CC=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang')) CLANG_CPP=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang++')) CLANG=CLANG_CPP LLVM_LINK=os.path.join(LLVM_ROOT, 'llvm-link') -LLVM_LD=os.path.join(LLVM_ROOT, 'llvm-ld') LLVM_AR=os.path.join(LLVM_ROOT, 'llvm-ar') LLVM_OPT=os.path.expanduser(os.path.join(LLVM_ROOT, 'opt')) LLVM_AS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-as')) @@ -97,6 +114,7 @@ LLVM_DIS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-dis')) LLVM_NM=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-nm')) LLVM_INTERPRETER=os.path.expanduser(os.path.join(LLVM_ROOT, 'lli')) LLVM_COMPILER=os.path.expanduser(os.path.join(LLVM_ROOT, 'llc')) +LLVM_EXTRACT=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-extract')) COFFEESCRIPT = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee') EMSCRIPTEN = path_from_root('emscripten.py') @@ -105,23 +123,31 @@ NAMESPACER = path_from_root('tools', 'namespacer.py') EMCC = path_from_root('emcc') EMXX = path_from_root('em++') EMAR = path_from_root('emar') -EMLD = path_from_root('emld') EMRANLIB = path_from_root('emranlib') EMLIBTOOL = path_from_root('emlibtool') +EMCONFIG = path_from_root('em-config') EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') EXEC_LLVM = path_from_root('tools', 'exec_llvm.py') VARIABLE_ELIMINATOR = path_from_root('tools', 'eliminator', 'eliminator.coffee') JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js') +FILE_PACKAGER = path_from_root('tools', 'file_packager.py') # Temp dir. Create a random one, unless EMCC_DEBUG is set, in which case use TEMP_DIR/emscripten_temp +try: + TEMP_DIR +except: + print >> sys.stderr, 'TEMP_DIR not defined in ~/.emscripten, using /tmp' + TEMP_DIR = '/tmp' + +CANONICAL_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp') EMSCRIPTEN_TEMP_DIR = None if os.environ.get('EMCC_DEBUG'): try: - EMSCRIPTEN_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp') + EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR if not os.path.exists(EMSCRIPTEN_TEMP_DIR): os.makedirs(EMSCRIPTEN_TEMP_DIR) except: @@ -129,6 +155,9 @@ if os.environ.get('EMCC_DEBUG'): if not EMSCRIPTEN_TEMP_DIR: EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_') + def clean_temp(): + try_delete(EMSCRIPTEN_TEMP_DIR) + atexit.register(clean_temp) # EM_CONFIG stuff @@ -146,6 +175,12 @@ try: except: CLOSURE_COMPILER = path_from_root('third_party', 'closure-compiler', 'compiler.jar') +try: + JAVA +except: + print >> sys.stderr, 'JAVA not defined in ~/.emscripten, using "java"' + JAVA = 'java' + # Additional compiler options try: @@ -155,7 +190,7 @@ except: # Force a simple, standard target as much as possible: target 32-bit linux, and disable various flags that hint at other platforms COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-U__i386__', '-U__x86_64__', '-U__i386', '-U__x86_64', '-U__SSE__', '-U__SSE2__', '-U__MMX__', '-UX87_DOUBLE_ROUNDING', '-UHAVE_GCC_ASM_FOR_X87', '-DEMSCRIPTEN', '-U__STRICT_ANSI__', '-U__CYGWIN__', - '-D__STDC__', '-Xclang', '-triple=i386-pc-linux-gnu'] + '-D__STDC__', '-Xclang', '-triple=i386-pc-linux-gnu', '-D__IEEE_LITTLE_ENDIAN'] USE_EMSDK = not os.environ.get('EMMAKEN_NO_SDK') @@ -163,8 +198,10 @@ USE_EMSDK = not os.environ.get('EMMAKEN_NO_SDK') if USE_EMSDK: # Disable system C and C++ include directories, and add our own (using -idirafter so they are last, like system dirs, which # allows projects to override them) - EMSDK_OPTS = ['-nostdinc', '-nostdinc++', '-Xclang', '-nobuiltininc', '-Xclang', '-nostdinc++', '-Xclang', '-nostdsysteminc', + # Note that -nostdinc++ is not needed, since -nostdinc implies that! + EMSDK_OPTS = ['-nostdinc', '-Xclang', '-nobuiltininc', '-Xclang', '-nostdsysteminc', '-Xclang', '-isystem' + path_from_root('system', 'include'), + '-Xclang', '-isystem' + path_from_root('system', 'include', 'emscripten'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'bsd'), # posix stuff '-Xclang', '-isystem' + path_from_root('system', 'include', 'libc'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'libcxx'), @@ -172,7 +209,7 @@ if USE_EMSDK: '-Xclang', '-isystem' + path_from_root('system', 'include', 'net'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'SDL'), ] + [ - '-U__APPLE__' + '-U__APPLE__', '-U__linux__' ] COMPILER_OPTS += EMSDK_OPTS else: @@ -180,13 +217,25 @@ else: # Engine tweaks -#if 'strict' not in str(SPIDERMONKEY_ENGINE): # XXX temporarily disable strict mode until we sort out some stuff -# SPIDERMONKEY_ENGINE += ['-e', "options('strict')"] # Strict mode in SpiderMonkey. With V8 we check that fallback to non-strict works too - -if 'gcparam' not in str(SPIDERMONKEY_ENGINE): - SPIDERMONKEY_ENGINE += ['-e', "gcparam('maxBytes', 1024*1024*1024);"] # Our very large files need lots of gc heap - -WINDOWS = sys.platform.startswith ('win') +try: + if 'gcparam' not in str(SPIDERMONKEY_ENGINE): + if type(SPIDERMONKEY_ENGINE) is str: + SPIDERMONKEY_ENGINE = [SPIDERMONKEY_ENGINE] + SPIDERMONKEY_ENGINE += ['-e', "gcparam('maxBytes', 1024*1024*1024);"] # Our very large files need lots of gc heap +except NameError: + pass + +WINDOWS = sys.platform.startswith('win') + +# If we have 'env', we should use that to find python, because |python| may fail while |env python| may work +# (For example, if system python is 3.x while we need 2.x, and env gives 2.x if told to do so.) +ENV_PREFIX = [] +if not WINDOWS: + try: + assert 'Python' in Popen(['env', 'python', '-V'], stdout=PIPE, stderr=STDOUT).communicate()[0] + ENV_PREFIX = ['env'] + except: + pass # Temp file utilities @@ -228,6 +277,8 @@ class TempFiles: def check_engine(engine): # TODO: we call this several times, perhaps cache the results? try: + if not CONFIG_FILE: + return True # config stored directly in EM_CONFIG => skip engine check return 'hello, world!' in run_js(path_from_root('tests', 'hello_world.js'), engine) except Exception, e: print 'Checking JS engine %s failed. Check %s. Details: %s' % (str(engine), EM_CONFIG, str(e)) @@ -243,11 +294,14 @@ def timeout_run(proc, timeout, note='unnamed process'): raise Exception("Timed out: " + note) return proc.communicate()[0] +EM_DEBUG = os.environ.get('EM_DEBUG') + def run_js(filename, engine=None, args=[], check_timeout=False, stdout=PIPE, stderr=None, cwd=None): if engine is None: engine = JS_ENGINES[0] if type(engine) is not list: engine = [engine] - return timeout_run(Popen(engine + [filename] + (['--'] if 'd8' in engine[0] else []) + args, - stdout=stdout, stderr=stderr, cwd=cwd), 15*60 if check_timeout else None, 'Execution') + command = engine + [filename] + (['--'] if 'd8' in engine[0] else []) + args + if EM_DEBUG: print >> sys.stderr, 'run_js: ' + ' '.join(command) + return timeout_run(Popen(command, stdout=stdout, stderr=stderr, cwd=cwd), 15*60 if check_timeout else None, 'Execution') def to_cc(cxx): # By default, LLVM_GCC and CLANG are really the C++ versions. This gets an explicit C version @@ -342,11 +396,12 @@ class Settings: if opt_level >= 2: Settings.RELOOP = 1 if opt_level >= 3: + Settings.INLINING_LIMIT = 0 + Settings.DOUBLE_MODE = 0 + Settings.PRECISE_I64_MATH = 0 Settings.CORRECT_SIGNS = 0 Settings.CORRECT_OVERFLOWS = 0 Settings.CORRECT_ROUNDINGS = 0 - Settings.DOUBLE_MODE = 0 - Settings.PRECISE_I64_MATH = 0 if noisy: print >> sys.stderr, 'Warning: Applying some potentially unsafe optimizations! (Use -O2 if this fails.)' global Settings @@ -461,24 +516,95 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e if configure: # Useful in debugging sometimes to comment this out (and the lines below up to and including the |link| call) Building.configure(configure + configure_args, stdout=open(os.path.join(project_dir, 'configure_'), 'w'), stderr=open(os.path.join(project_dir, 'configure_err'), 'w'), env=env) - Building.make(make + make_args, stdout=open(os.path.join(project_dir, 'make_'), 'w'), - stderr=open(os.path.join(project_dir, 'make_err'), 'w'), env=env) - if cache is not None: - cache[cache_name] = [] - for f in generated_libs: - basename = os.path.basename(f) - cache[cache_name].append((basename, open(f, 'rb').read())) + for i in range(2): # workaround for some build systems that need to be run twice to succeed (e.g. poppler) + Building.make(make + make_args, stdout=open(os.path.join(project_dir, 'make_' + str(i)), 'w'), + stderr=open(os.path.join(project_dir, 'make_err' + str(i)), 'w'), env=env) + try: + if cache is not None: + cache[cache_name] = [] + for f in generated_libs: + basename = os.path.basename(f) + cache[cache_name].append((basename, open(f, 'rb').read())) + break + except: + if i > 0: raise Exception('could not build library ' + name) if old_dir: os.chdir(old_dir) return generated_libs @staticmethod - def link(files, target): + def link(files, target, remove_duplicates=False): + actual_files = [] + unresolved_symbols = set() # necessary for .a linking, see below + resolved_symbols = set() + temp_dir = None + for f in files: + if not Building.is_ar(f): + if Building.is_bitcode(f): + new_symbols = Building.llvm_nm(f) + resolved_symbols = resolved_symbols.union(new_symbols.defs) + unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs) + actual_files.append(f) + else: + # Extract object files from ar archives, and link according to gnu ld semantics + # (link in an entire .o from the archive if it supplies symbols still unresolved) + cwd = os.getcwd() + try: + temp_dir = os.path.join(EMSCRIPTEN_TEMP_DIR, 'ar_output_' + str(os.getpid())) + if not os.path.exists(temp_dir): + os.makedirs(temp_dir) + os.chdir(temp_dir) + contents = filter(lambda x: len(x) > 0, Popen([LLVM_AR, 't', f], stdout=PIPE).communicate()[0].split('\n')) + if len(contents) == 0: + print >> sys.stderr, 'Warning: Archive %s appears to be empty (recommendation: link an .so instead of .a)' % f + else: + for content in contents: # ar will silently fail if the directory for the file does not exist, so make all the necessary directories + dirname = os.path.dirname(content) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + Popen([LLVM_AR, 'x', f], stdout=PIPE).communicate() # if absolute paths, files will appear there. otherwise, in this directory + contents = map(lambda content: os.path.join(temp_dir, content), contents) + contents = filter(os.path.exists, map(os.path.abspath, contents)) + needed = False # We add or do not add the entire archive. We let llvm dead code eliminate parts we do not need, instead of + # doing intra-dependencies between archive contents + for content in contents: + new_symbols = Building.llvm_nm(content) + # Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld) + if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1: + needed = True + if needed: + for content in contents: + if Building.is_bitcode(content): + new_symbols = Building.llvm_nm(content) + resolved_symbols = resolved_symbols.union(new_symbols.defs) + unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs) + actual_files.append(content) + finally: + os.chdir(cwd) try_delete(target) - stub = os.path.join(EMSCRIPTEN_TEMP_DIR, 'stub_deleteme') - output = Popen([LLVM_LD, '-disable-opt'] + files + ['-b', target, '-o', stub], stdout=PIPE).communicate()[0] - try_delete(stub) # clean up stub left by the linker - assert os.path.exists(target) and (output is None or 'Could not open input file' not in output), 'Linking error: ' + output + + if remove_duplicates: + # Remove duplicate symbols. This is a workaround for how we compile .a files, we try to + # emulate ld behavior which is permissive TODO: cache llvm-nm results + seen_symbols = set() + print >> sys.stderr, actual_files + for actual in actual_files: + symbols = Building.llvm_nm(actual) + dupes = seen_symbols.intersection(symbols.defs) + if len(dupes) > 0: + print >> sys.stderr, 'emcc: warning: removing duplicates in', actual + for dupe in dupes: + print >> sys.stderr, 'emcc: warning: removing duplicate', dupe + Popen([LLVM_EXTRACT, actual, '-delete', '-glob=' + dupe, '-o', actual], stderr=PIPE).communicate() + Popen([LLVM_EXTRACT, actual, '-delete', '-func=' + dupe, '-o', actual], stderr=PIPE).communicate() + Popen([LLVM_EXTRACT, actual, '-delete', '-glob=.str', '-o', actual], stderr=PIPE).communicate() # garbage that appears here + seen_symbols = seen_symbols.union(symbols.defs) + + # Finish link + output = Popen([LLVM_LINK] + actual_files + ['-o', target], stdout=PIPE).communicate()[0] + assert os.path.exists(target) and (output is None or 'Could not open input file' not in output), 'Linking error: ' + output + '\nemcc: If you get duplicate symbol errors, try --remove-duplicates' + if temp_dir: + try_delete(temp_dir) # Emscripten optimizations that we run on the .ll file @staticmethod @@ -507,12 +633,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e shutil.move(filename + '.o', filename + '.o.pre') output = Popen([LLVM_OPT, filename + '.o.pre'] + Building.LLVM_OPT_OPTS + ['-o=' + filename + '.o'], stdout=PIPE).communicate()[0] assert os.path.exists(filename + '.o'), 'Failed to run llvm optimizations: ' + output - #if Building.LLVM_OPTS == 2: - # print 'Unsafe LD!' - # shutil.move(filename + '.o', filename + '.o.pre') - # output = Popen([LLVM_LD, filename + '.o.pre', '-o=' + filename + '.tmp'], stdout=PIPE).communicate()[0] - # assert os.path.exists(filename + '.tmp.bc'), 'Failed to run llvm optimizations: ' + output - # shutil.move(filename + '.tmp.bc', filename + '.o') @staticmethod def llvm_dis(input_filename, output_filename=None): @@ -548,13 +668,15 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e commons = [] for line in output.split('\n'): if len(line) == 0: continue - status, symbol = filter(lambda seg: len(seg) > 0, line.split(' ')) - if status == 'U': - ret.undefs.append(symbol) - elif status != 'C': - ret.defs.append(symbol) - else: - ret.commons.append(symbol) + parts = filter(lambda seg: len(seg) > 0, line.split(' ')) + if len(parts) == 2: # ignore lines with absolute offsets, these are not bitcode anyhow (e.g. |00000630 t d_source_name|) + status, symbol = parts + if status == 'U': + ret.undefs.append(symbol) + elif status != 'C': + ret.defs.append(symbol) + else: + ret.commons.append(symbol) ret.defs = set(ret.defs) ret.undefs = set(ret.undefs) ret.commons = set(ret.commons) @@ -565,13 +687,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e if output_filename is None: output_filename = filename + '.o' try_delete(output_filename) - Popen(['python', EMCC, filename] + args + ['-o', output_filename], stdout=stdout, stderr=stderr, env=env).communicate() + Popen(ENV_PREFIX + ['python', EMCC, filename] + args + ['-o', output_filename], stdout=stdout, stderr=stderr, env=env).communicate() assert os.path.exists(output_filename), 'emcc could not create output file' @staticmethod def emar(action, output_filename, filenames, stdout=None, stderr=None, env=None): try_delete(output_filename) - Popen(['python', EMAR, action, output_filename] + filenames, stdout=stdout, stderr=stderr, env=env).communicate() + Popen(ENV_PREFIX + ['python', EMAR, action, output_filename] + filenames, stdout=stdout, stderr=stderr, env=env).communicate() if 'c' in action: assert os.path.exists(output_filename), 'emar could not create output file' @@ -582,7 +704,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # Run Emscripten settings = Settings.serialize() - compiler_output = timeout_run(Popen(['python', EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling') + compiler_output = timeout_run(Popen(ENV_PREFIX + ['python', EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling') #print compiler_output # Detect compilation crashes and errors @@ -713,8 +835,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e if type(passes) == str: passes = [passes] # XXX Disable crankshaft to work around v8 bug 1895 - output, err = Popen([NODE_JS, '--nocrankshaft', JS_OPTIMIZER, filename] + passes, stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate() - assert len(output) > 0 and not output.startswith('Assertion failed'), 'Error in js optimizer: ' + err + '\n\n' + output + output = Popen([NODE_JS, '--nocrankshaft', JS_OPTIMIZER, filename] + passes, stdout=PIPE).communicate()[0] + assert len(output) > 0 and not output.startswith('Assertion failed'), 'Error in js optimizer: ' + output filename += '.jo.js' f = open(filename, 'w') f.write(output) @@ -729,8 +851,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e coffee = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee') eliminator = path_from_root('tools', 'eliminator', 'eliminator.coffee') input = open(filename, 'r').read() - output, err = Popen([NODE_JS, coffee, eliminator], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(input) - assert len(output) > 0, 'Error in eliminator: ' + err + '\n\n' + output + output = Popen([NODE_JS, coffee, eliminator, filename], stdout=PIPE).communicate()[0] + assert len(output) > 0, 'Error in eliminator: ' + output filename += '.el.js' f = open(filename, 'w') f.write(output) @@ -744,7 +866,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # Something like this (adjust memory as needed): # java -Xmx1024m -jar CLOSURE_COMPILER --compilation_level ADVANCED_OPTIMIZATIONS --variable_map_output_file src.cpp.o.js.vars --js src.cpp.o.js --js_output_file src.cpp.o.cc.js - args = ['java', + args = [JAVA, '-Xmx1024m', '-jar', CLOSURE_COMPILER, '--compilation_level', 'ADVANCED_OPTIMIZATIONS', @@ -810,7 +932,9 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # Permanent cache for dlmalloc and stdlibc++ class Cache: - dirname = os.path.expanduser(os.path.join('~', '.emscripten_cache')) + dirname = os.environ.get('EM_CACHE') + if not dirname: + dirname = os.path.expanduser(os.path.join('~', '.emscripten_cache')) @staticmethod def erase(): @@ -832,3 +956,41 @@ class Cache: shutil.copyfile(creator(), cachename) return cachename +# Compression of code and data for smaller downloads +class Compression: + on = False + + @staticmethod + def compressed_name(filename): + return filename + '.compress' + + @staticmethod + def compress(filename): + execute(Compression.encoder, stdin=open(filename, 'rb'), stdout=open(Compression.compressed_name(filename), 'wb')) + + @staticmethod + def worth_it(original, compressed): + return compressed < original - 1500 # save at least one TCP packet or so + +def execute(cmd, *args, **kw): + try: + return subprocess.Popen(cmd, *args, **kw).communicate() # let compiler frontend print directly, so colors are saved (PIPE kills that) + except: + if not isinstance(cmd, str): + cmd = ' '.join(cmd) + print >> sys.stderr, 'Invoking Process failed: <<< ' + cmd + ' >>>' + raise + +def suffix(name): + parts = name.split('.') + if len(parts) > 1: + return parts[-1] + else: + return None + +def unsuffixed(name): + return '.'.join(name.split('.')[:-1]) + +def unsuffixed_basename(name): + return os.path.basename(unsuffixed(name)) + |