aboutsummaryrefslogtreecommitdiff
path: root/tools/shared.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/shared.py')
-rw-r--r--tools/shared.py162
1 files changed, 155 insertions, 7 deletions
diff --git a/tools/shared.py b/tools/shared.py
index 04d0fe57..7bafd51d 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -1,4 +1,4 @@
-import shutil, time, os, sys, json, tempfile, copy, shlex, atexit, subprocess
+import shutil, time, os, sys, json, tempfile, copy, shlex, atexit, subprocess, md5, cPickle
from subprocess import Popen, PIPE, STDOUT
from tempfile import mkstemp
@@ -59,8 +59,6 @@ class WindowsPopen:
# Install our replacement Popen handler if we are running on Windows to avoid python spawn process function.
if os.name == 'nt':
Popen = WindowsPopen
-
-import js_optimizer
__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
def path_from_root(*pathelems):
@@ -1006,8 +1004,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
return opts
@staticmethod
- def js_optimizer(filename, passes):
- return js_optimizer.run(filename, passes, NODE_JS)
+ def js_optimizer(filename, passes, jcache):
+ return js_optimizer.run(filename, passes, NODE_JS, jcache)
@staticmethod
def closure_compiler(filename):
@@ -1088,16 +1086,17 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
@staticmethod
def ensure_relooper():
if os.path.exists(RELOOPER): return
+ curr = os.getcwd()
try:
ok = False
print >> sys.stderr, '======================================='
print >> sys.stderr, 'bootstrapping relooper...'
Cache.ensure()
- RELOOPER_DIR = path_from_root('src', 'relooper')
+ os.chdir(path_from_root('src'))
def make(opt_level):
raw = RELOOPER + '.raw.js'
- Building.emcc(os.path.join(RELOOPER_DIR, 'Relooper.cpp'), ['-I' + os.path.join(RELOOPER_DIR), '--post-js', os.path.join(RELOOPER_DIR, 'emscripten', 'glue.js'), '-s', 'TOTAL_MEMORY=52428800', '-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["memcpy", "memset", "malloc", "free", "puts"]', '-O' + str(opt_level), '--closure', '0'], raw)
+ Building.emcc(os.path.join('relooper', 'Relooper.cpp'), ['-I' + os.path.join('relooper'), '--post-js', os.path.join('relooper', 'emscripten', 'glue.js'), '-s', 'TOTAL_MEMORY=52428800', '-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["memcpy", "memset", "malloc", "free", "puts"]', '-O' + str(opt_level), '--closure', '0'], raw)
f = open(RELOOPER, 'w')
f.write("// Relooper, (C) 2012 Alon Zakai, MIT license, https://github.com/kripken/Relooper\n")
f.write("var Relooper = (function() {\n");
@@ -1116,6 +1115,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
print >> sys.stderr, '======================================='
ok = True
finally:
+ os.chdir(curr)
if not ok:
print >> sys.stderr, 'bootstrapping relooper failed. You may need to manually create src/relooper.js by compiling it, see src/relooper/emscripten'
1/0
@@ -1151,6 +1151,152 @@ class Cache:
shutil.copyfile(creator(), cachename)
return cachename
+# JS-specific cache. We cache the results of compilation and optimization,
+# so that in incremental builds we can just load from cache.
+# We cache reasonably-large-sized chunks
+class JCache:
+ dirname = os.path.join(Cache.dirname, 'jcache')
+
+ @staticmethod
+ def ensure():
+ Cache.ensure()
+ if not os.path.exists(JCache.dirname):
+ os.makedirs(JCache.dirname)
+
+ @staticmethod
+ def get_shortkey(keys):
+ if type(keys) not in [list, tuple]:
+ keys = [keys]
+ ret = ''
+ for key in keys:
+ assert type(key) == str
+ ret += md5.md5(key).hexdigest()
+ return ret
+
+ @staticmethod
+ def get_cachename(shortkey):
+ return os.path.join(JCache.dirname, shortkey)
+
+ # Returns a cached value, if it exists. Make sure the full key matches
+ @staticmethod
+ def get(shortkey, keys):
+ #if DEBUG: print >> sys.stderr, 'jcache get?', shortkey
+ cachename = JCache.get_cachename(shortkey)
+ if not os.path.exists(cachename):
+ #if DEBUG: print >> sys.stderr, 'jcache none at all'
+ return
+ data = cPickle.Unpickler(open(cachename, 'rb')).load()
+ if len(data) != 2:
+ #if DEBUG: print >> sys.stderr, 'jcache error in get'
+ return
+ oldkeys = data[0]
+ if len(oldkeys) != len(keys):
+ #if DEBUG: print >> sys.stderr, 'jcache collision (a)'
+ return
+ for i in range(len(oldkeys)):
+ if oldkeys[i] != keys[i]:
+ #if DEBUG: print >> sys.stderr, 'jcache collision (b)'
+ return
+ #if DEBUG: print >> sys.stderr, 'jcache win'
+ return data[1]
+
+ # Sets the cached value for a key (from get_key)
+ @staticmethod
+ def set(shortkey, keys, value):
+ cachename = JCache.get_cachename(shortkey)
+ cPickle.Pickler(open(cachename, 'wb')).dump([keys, value])
+ #if DEBUG:
+ # for i in range(len(keys)):
+ # open(cachename + '.key' + str(i), 'w').write(keys[i])
+ # open(cachename + '.value', 'w').write(value)
+
+ # Given a set of functions of form (ident, text), and a preferred chunk size,
+ # generates a set of chunks for parallel processing and caching.
+ # It is very important to generate similar chunks in incremental builds, in
+ # order to maximize the chance of cache hits. To achieve that, we save the
+ # chunking used in the previous compilation of this phase, and we try to
+ # generate the same chunks, barring big differences in function sizes that
+ # violate our chunk size guideline. If caching is not used, chunking_file
+ # should be None
+ @staticmethod
+ def chunkify(funcs, chunk_size, chunking_file):
+ previous_mapping = None
+ if chunking_file:
+ chunking_file = JCache.get_cachename(chunking_file)
+ if os.path.exists(chunking_file):
+ try:
+ previous_mapping = cPickle.Unpickler(open(chunking_file, 'rb')).load() # maps a function identifier to the chunk number it will be in
+ except:
+ pass
+ chunks = []
+ if previous_mapping:
+ # initialize with previous chunking
+ news = []
+ for func in funcs:
+ ident, data = func
+ if not ident in previous_mapping:
+ news.append(func)
+ else:
+ n = previous_mapping[ident]
+ while n >= len(chunks): chunks.append([])
+ chunks[n].append(func)
+ # add news and adjust for new sizes
+ spilled = news
+ for chunk in chunks:
+ size = sum([len(func[1]) for func in chunk])
+ while size > 1.5*chunk_size and len(chunk) > 0:
+ spill = chunk.pop()
+ spilled.append(spill)
+ size -= len(spill[1])
+ for chunk in chunks:
+ size = sum([len(func[1]) for func in chunk])
+ while size < 0.66*chunk_size and len(spilled) > 0:
+ spill = spilled.pop()
+ chunk.append(spill)
+ size += len(spill[1])
+ chunks = filter(lambda chunk: len(chunk) > 0, chunks) # might have empty ones, eliminate them
+ funcs = spilled # we will allocate these into chunks as if they were normal inputs
+ # initialize reasonably, the rest of the funcs we need to split out
+ curr = []
+ total_size = 0
+ for i in range(len(funcs)):
+ func = funcs[i]
+ curr_size = len(func[1])
+ if total_size + curr_size < chunk_size:
+ curr.append(func)
+ total_size += curr_size
+ else:
+ chunks.append(curr)
+ curr = [func]
+ total_size = curr_size
+ if curr:
+ chunks.append(curr)
+ curr = None
+ if chunking_file:
+ # sort within each chunk, to keep the order identical
+ for chunk in chunks:
+ chunk.sort(key=lambda func: func[0])
+ # save new mapping info
+ new_mapping = {}
+ for i in range(len(chunks)):
+ chunk = chunks[i]
+ for ident, data in chunk:
+ new_mapping[ident] = i
+ cPickle.Pickler(open(chunking_file, 'wb')).dump(new_mapping)
+ #if DEBUG:
+ # if previous_mapping:
+ # for ident in set(previous_mapping.keys() + new_mapping.keys()):
+ # if previous_mapping.get(ident) != new_mapping.get(ident):
+ # print >> sys.stderr, 'mapping inconsistency', ident, previous_mapping.get(ident), new_mapping.get(ident)
+ # for key, value in new_mapping.iteritems():
+ # print >> sys.stderr, 'mapping:', key, value
+ return [''.join([func[1] for func in chunk]) for chunk in chunks] # remove function names
+
+class JS:
+ @staticmethod
+ def to_nice_ident(ident): # limited version of the JS function toNiceIdent
+ return ident.replace('%', '$').replace('@', '_');
+
# Compression of code and data for smaller downloads
class Compression:
on = False
@@ -1189,3 +1335,5 @@ def unsuffixed(name):
def unsuffixed_basename(name):
return os.path.basename(unsuffixed(name))
+import js_optimizer
+