1231 строка
51 KiB
Python
Executable File
1231 строка
51 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
'''
|
|
emcc - compiler helper script
|
|
=============================
|
|
|
|
emcc is a drop-in replacement for a compiler like gcc or clang.
|
|
|
|
Tell your build system to use this instead of the compiler, and similarly
|
|
use emar, emld and emranlib instead of the same command without 'em'.
|
|
|
|
Example uses:
|
|
|
|
* For configure, instead of ./configure, cmake, etc., run emconfigure.py
|
|
with that command as an argument, for example
|
|
|
|
emconfigure.py ./configure [options]
|
|
|
|
emconfigure.py is a tiny script that just sets some environment vars
|
|
as a convenience. The command just shown is equivalent to
|
|
|
|
EMMAKEN_JUST_CONFIGURE=1 RANLIB=PATH/emranlib AR=PATH/emar CXX=PATH/em++ CC=PATH/emcc ./configure [options]
|
|
|
|
where PATH is the path to this file.
|
|
|
|
EMMAKEN_JUST_CONFIGURE tells emcc that it is being run in ./configure,
|
|
so it should relay everything to gcc/g++. You should not define that when
|
|
running make, of course.
|
|
|
|
* With CMake, the same command will work (with cmake instead of ./configure). You may also be
|
|
able to do the following in your CMakeLists.txt:
|
|
|
|
SET(CMAKE_C_COMPILER "PATH/emcc")
|
|
SET(CMAKE_CXX_COMPILER "PATH/em++")
|
|
SET(CMAKE_LINKER "PATH/emld")
|
|
SET(CMAKE_CXX_LINKER "PATH/emld")
|
|
SET(CMAKE_C_LINK_EXECUTABLE "PATH/emld")
|
|
SET(CMAKE_CXX_LINK_EXECUTABLE "PATH/emld")
|
|
SET(CMAKE_AR "PATH/emar")
|
|
SET(CMAKE_RANLIB "PATH/emranlib")
|
|
|
|
* For SCons the shared.py can be imported like so:
|
|
__file__ = str(Dir('#/project_path_to_emscripten/dummy/dummy'))
|
|
__rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
def path_from_root(*pathelems):
|
|
return os.path.join(__rootpath__, *pathelems)
|
|
sys.path += [path_from_root('')]
|
|
from tools.shared import *
|
|
|
|
For using the Emscripten compilers/linkers/etc. you can do:
|
|
env = Environment()
|
|
...
|
|
env.Append(CCFLAGS = COMPILER_OPTS)
|
|
env.Replace(LINK = LLVM_LD)
|
|
env.Replace(LD = LLVM_LD)
|
|
TODO: Document all relevant setup changes
|
|
|
|
After setting that up, run your build system normally.
|
|
|
|
Note the appearance of em++ instead of emcc
|
|
for the C++ compiler. This is needed for cases where we get
|
|
a C++ file with a C extension, in which case CMake can be told
|
|
to run g++ on it despite the .c extension, see
|
|
|
|
https://github.com/kripken/emscripten/issues/6
|
|
|
|
(If a similar situation occurs with ./configure, you can do the same there too.)
|
|
|
|
emcc can be influenced by a few environment variables:
|
|
|
|
EMMAKEN_NO_SDK - Will tell emcc *not* to use the emscripten headers. Instead
|
|
your system headers will be used.
|
|
|
|
EMMAKEN_COMPILER - The compiler to be used, if you don't want the default clang.
|
|
'''
|
|
|
|
import os, sys, shutil, tempfile, subprocess, shlex
|
|
from subprocess import PIPE, STDOUT
|
|
from tools import shared
|
|
|
|
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
|
|
|
|
# Mapping of emcc opt levels to llvm opt levels. We use llvm opt level 3 in emcc opt
|
|
# levels 2 and 3 (emcc 3 is unsafe opts, so unsuitable for the only level to get
|
|
# llvm opt level 3, and speed-wise emcc level 2 is already the slowest/most optimizing
|
|
# level)
|
|
LLVM_OPT_LEVEL = {
|
|
0: 0,
|
|
1: 1,
|
|
2: 3,
|
|
3: 3,
|
|
}
|
|
|
|
DEBUG = os.environ.get('EMCC_DEBUG')
|
|
TEMP_DIR = os.environ.get('EMCC_TEMP_DIR')
|
|
LEAVE_INPUTS_RAW = os.environ.get('EMCC_LEAVE_INPUTS_RAW') # Do not compile .ll files into .bc, just compile them with emscripten directly
|
|
# Not recommended, this is mainly for the test runner, or if you have some other
|
|
# specific need.
|
|
# One major limitation with this mode is that dlmalloc and libc++ cannot be
|
|
# added in. Also, LLVM optimizations will not be done, nor dead code elimination
|
|
AUTODEBUG = os.environ.get('EMCC_AUTODEBUG') # If set to 1, we will run the autodebugger (the automatic debugging tool, see tools/autodebugger).
|
|
# Note that this will disable inclusion of libraries. This is useful because including
|
|
# dlmalloc makes it hard to compare native and js builds
|
|
EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS') # Additional compiler flags that we treat as if they were passed to us on the commandline
|
|
|
|
if DEBUG: print >> sys.stderr, '\nemcc invocation: ', ' '.join(sys.argv), (' + ' + EMCC_CFLAGS if EMCC_CFLAGS else '')
|
|
if EMCC_CFLAGS: sys.argv.append(EMCC_CFLAGS)
|
|
|
|
if DEBUG and LEAVE_INPUTS_RAW: print >> sys.stderr, 'emcc: leaving inputs raw'
|
|
|
|
stdout = PIPE if not DEBUG else None # suppress output of child processes
|
|
stderr = PIPE if not DEBUG else None # unless we are in DEBUG mode
|
|
|
|
shared.check_sanity()
|
|
|
|
# Handle some global flags
|
|
|
|
if len(sys.argv) == 1:
|
|
print 'emcc: no input files'
|
|
exit(0)
|
|
|
|
if sys.argv[1] == '--version':
|
|
print '''emcc (Emscripten GCC-like replacement) 2.0
|
|
Copyright (C) 2011 the Emscripten authors.
|
|
This is free and open source software under the MIT license.
|
|
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
'''
|
|
exit(0)
|
|
elif sys.argv[1] == '--help':
|
|
this = os.path.basename('em++' if os.environ.get('EMMAKEN_CXX') else 'emcc')
|
|
|
|
print '''%s [options] file...
|
|
|
|
Most normal gcc/g++ options will work, for example:
|
|
--help Display this information
|
|
--version Display compiler version information
|
|
|
|
Options that are modified or new in %s include:
|
|
-O0 No optimizations (default)
|
|
-O1 Simple optimizations, including LLVM -O1
|
|
optimizations, and no runtime assertions
|
|
or C++ exception catching (to re-enable
|
|
C++ exception catching, use
|
|
-s DISABLE_EXCEPTION_CATCHING=0 ).
|
|
Note: Optimizations are only done when
|
|
compiling to JavaScript, not to intermediate
|
|
bitcode.
|
|
-O2 As -O1, plus the relooper (loop recreation),
|
|
plus closure compiler advanced opts, plus
|
|
LLVM -O2 optimizations
|
|
Warning: Compiling with this takes a long time!
|
|
-O3 As -O2, plus dangerous optimizations that may
|
|
break the generated code! If that happens, try
|
|
-O2 and then adding dangerous optimizations one
|
|
by one.
|
|
|
|
-s OPTION=VALUE JavaScript code generation option passed
|
|
into the emscripten compiler. For the
|
|
available options, see src/settings.js
|
|
|
|
--typed-arrays <mode> 0: No typed arrays
|
|
1: Parallel typed arrays
|
|
2: Shared (C-like) typed arrays (default)
|
|
|
|
--llvm-opts <level> 0: No LLVM optimizations (default in -O0)
|
|
1: -O1 LLVM optimizations (default in -O1)
|
|
2: -O2 LLVM optimizations
|
|
3: -O3 LLVM optimizations (default in -O2+)
|
|
|
|
--llvm-lto <level> 0: No LLVM LTO (default in -O0)
|
|
1: LLVM LTO (default in -O1+)
|
|
Note: If LLVM optimizations are not run
|
|
(see --llvm-opts), setting this to 1 has no
|
|
effect.
|
|
|
|
--closure <on> 0: No closure compiler (default in -O0, -O1)
|
|
1: Run closure compiler (default in -O2, -O3)
|
|
|
|
--js-transform <cmd> <cmd> will be called on the generated code
|
|
before it is optimized. This lets you modify
|
|
the JavaScript, for example adding some code
|
|
or removing some code, in a way that those
|
|
modifications will be optimized together with
|
|
the generated code properly. <cmd> will be
|
|
called with the filename of the generated
|
|
code as a parameter; to modify the code, you
|
|
can read the original data and then append to
|
|
it or overwrite it with the modified data.
|
|
<cmd> is interpreted as a space-separated
|
|
list of arguments, for example, <cmd> of
|
|
"python processor.py" will cause a python
|
|
script to be run.
|
|
|
|
--pre-js <file> A file whose contents are added before the
|
|
generated code. This is done *before*
|
|
optimization, so it will be minified
|
|
properly if closure compiler is run.
|
|
|
|
--post-js <file> A file whose contents are added after the
|
|
generated code This is done *before*
|
|
optimization, so it will be minified
|
|
properly if closure compiler is run.
|
|
|
|
--embed-file <file> A file to embed inside the generated
|
|
JavaScript. The compiled code will be able
|
|
to access the file in the current directory
|
|
with the same basename as given here (that is,
|
|
just the filename, without a path to it).
|
|
If a directory is passed here, its entire
|
|
contents will be embedded.
|
|
|
|
--preload-file <name> A file to preload before running the
|
|
compiled code asynchronously. Otherwise
|
|
similar to --embed-file, except that this
|
|
option is only relevant when generating
|
|
HTML (it uses asynchronous binary XHRs).
|
|
If a directory is passed here, its entire
|
|
contents will be preloaded.
|
|
|
|
--compression <codec> Compress both the compiled code and embedded/
|
|
preloaded files. <codec> should be a triple,
|
|
|
|
<native_encoder>,<js_decoder>,<js_name>
|
|
|
|
where native_encoder is a native executable
|
|
that compresses stdin to stdout (the simplest
|
|
possible interface), js_decoder is a
|
|
JavaScript file that implements a decoder,
|
|
and js_name is the name of the function to
|
|
call in the decoder file (which should
|
|
receive an array/typed array and return
|
|
an array/typed array.
|
|
Compression only works when generating HTML.
|
|
When compression is on, all filed specified
|
|
to be preloaded are compressed in one big
|
|
archive, which is given the same name as the
|
|
output HTML but with suffix .data.compress
|
|
|
|
--minify <on> 0: Do not minify the generated JavaScript's
|
|
whitespace (default if closure compiler
|
|
will not be run)
|
|
1: Minify the generated JavaScript's
|
|
whitespace (default if closure compiler
|
|
will be run). Note that this by itself
|
|
will not minify the code (closure does
|
|
that)
|
|
|
|
--ignore-dynamic-linking Normally emcc will treat dynamic linking like
|
|
static linking, by linking in the code from
|
|
the dynamic library. This fails if the same
|
|
dynamic library is linked more than once.
|
|
With this option, dynamic linking is ignored,
|
|
which allows the build system to proceed without
|
|
errors. However, you will need to manually
|
|
link to the shared libraries later on yourself.
|
|
|
|
--shell-file <path> The path name to a skeleton HTML file used
|
|
when generating HTML output. The shell file
|
|
used needs to have this token inside it:
|
|
{{{ SCRIPT_CODE }}}
|
|
Note that this argument is ignored if a
|
|
target other than HTML is specified using
|
|
the -o option.
|
|
|
|
--js-library <lib> A JavaScript library to use in addition to
|
|
those in Emscripten's src/library_*
|
|
|
|
-v Turns on verbose output. This will pass
|
|
-v to Clang, and also enable EMCC_DEBUG
|
|
to details emcc's operations
|
|
|
|
The target file, if specified (-o <target>), defines what will
|
|
be generated:
|
|
|
|
<name>.js JavaScript (default)
|
|
<name>.html HTML with embedded JavaScript
|
|
<name>.bc LLVM bitcode
|
|
<name>.o LLVM bitcode (same as .bc)
|
|
|
|
The -c option (which tells gcc not to run the linker) will
|
|
cause LLVM bitcode to be generated, as %s only generates
|
|
JavaScript in the final linking stage of building.
|
|
|
|
The input file(s) can be either source code files that
|
|
Clang can handle (C or C++), LLVM bitcode in binary form,
|
|
or LLVM assembly files in human-readable form.
|
|
|
|
emcc is affected by several environment variables. For details, view
|
|
the source of emcc (search for 'os.environ').
|
|
|
|
''' % (this, this, this)
|
|
exit(0)
|
|
|
|
# If this is a configure-type thing, do not compile to JavaScript, instead use clang
|
|
# to compile to a native binary (using our headers, so things make sense later)
|
|
CONFIGURE_CONFIG = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in sys.argv
|
|
CMAKE_CONFIG = 'CMakeFiles/cmTryCompileExec.dir' in ' '.join(sys.argv)# or 'CMakeCCompilerId' in ' '.join(sys.argv)
|
|
if CONFIGURE_CONFIG or CMAKE_CONFIG:
|
|
compiler = shared.CLANG
|
|
if not ('CXXCompiler' in ' '.join(sys.argv) or os.environ.get('EMMAKEN_CXX')):
|
|
compiler = shared.to_cc(compiler)
|
|
cmd = [compiler] + shared.EMSDK_OPTS + ['-DEMSCRIPTEN'] + sys.argv[1:]
|
|
if DEBUG: print >> sys.stderr, 'emcc, just configuring: ', ' '.join(cmd)
|
|
exit(subprocess.call(cmd))
|
|
|
|
if os.environ.get('EMMAKEN_COMPILER'):
|
|
CXX = os.environ['EMMAKEN_COMPILER']
|
|
else:
|
|
CXX = shared.CLANG
|
|
|
|
CC = shared.to_cc(CXX)
|
|
|
|
# If we got here from a redirection through emmakenxx.py, then force a C++ compiler here
|
|
if os.environ.get('EMMAKEN_CXX'):
|
|
CC = CXX
|
|
|
|
CC_ADDITIONAL_ARGS = shared.COMPILER_OPTS # + ['-g']?
|
|
|
|
EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS')
|
|
if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += shlex.split(EMMAKEN_CFLAGS)
|
|
|
|
# ---------------- Utilities ---------------
|
|
|
|
SOURCE_SUFFIXES = ('.c', '.cpp', '.cxx', '.cc')
|
|
BITCODE_SUFFIXES = ('.bc', '.o')
|
|
DYNAMICLIB_SUFFIXES = ('.dylib', '.so', '.dll')
|
|
STATICLIB_SUFFIXES = ('.a',)
|
|
ASSEMBLY_SUFFIXES = ('.ll',)
|
|
LIB_PREFIXES = ('', 'lib')
|
|
|
|
IMAGE_SUFFIXES = ('.jpg', '.png', '.bmp')
|
|
AUDIO_SUFFIXES = ('.ogg', '.wav', '.mp3')
|
|
AUDIO_MIMETYPES = { 'ogg': 'audio/ogg', 'wav': 'audio/wav', 'mp3': 'audio/mpeg' }
|
|
|
|
def suffix(name):
|
|
return name.split('.')[-1]
|
|
|
|
def unsuffixed(name):
|
|
return '.'.join(name.split('.')[:-1])
|
|
|
|
def unsuffixed_basename(name):
|
|
return os.path.basename(unsuffixed(name))
|
|
|
|
seen_names = {}
|
|
def unsuffixed_uniquename(name):
|
|
ret = unsuffixed_basename(name)
|
|
if name not in seen_names:
|
|
seen_names[name] = str(len(seen_names))
|
|
return ret + '_' + seen_names[name]
|
|
|
|
# ---------------- End configs -------------
|
|
|
|
if len(sys.argv) == 1 or sys.argv[1] in ['x', 't']:
|
|
# noop ar
|
|
if DEBUG: print >> sys.stderr, 'emcc, just ar'
|
|
sys.exit(0)
|
|
|
|
use_cxx = True
|
|
header = False # pre-compiled headers. We fake that by just copying the file
|
|
|
|
for i in range(1, len(sys.argv)):
|
|
arg = sys.argv[i]
|
|
if not arg.startswith('-'):
|
|
if arg.endswith('.c'):
|
|
use_cxx = False
|
|
if arg.endswith('.h') and sys.argv[i-1] != '-include':
|
|
header = True
|
|
|
|
if '-M' in sys.argv or '-MM' in sys.argv:
|
|
# Just output dependencies, do not compile. Warning: clang and gcc behave differently with -MF! (clang seems to not recognize it)
|
|
cmd = [CC] + shared.COMPILER_OPTS + sys.argv[1:]
|
|
if DEBUG: print >> sys.stderr, 'emcc, just dependencies: ', ' '.join(cmd)
|
|
exit(subprocess.call(cmd))
|
|
|
|
# Check if a target is specified
|
|
target = None
|
|
for i in range(len(sys.argv)-1):
|
|
if sys.argv[i].startswith('-o='):
|
|
raise Exception('Invalid syntax: do not use -o=X, use -o X')
|
|
|
|
if sys.argv[i] == '-o':
|
|
target = sys.argv[i+1]
|
|
sys.argv = sys.argv[:i] + sys.argv[i+2:]
|
|
break
|
|
|
|
if header: # header or such
|
|
if len(sys.argv) >= 3: # if there is a source and a target, then copy, otherwise do nothing
|
|
sys.argv = filter(lambda arg: not arg.startswith('-I'), sys.argv)
|
|
if DEBUG: print >> sys.stderr, 'Just copy:', sys.argv[-1], target
|
|
shutil.copy(sys.argv[-1], target)
|
|
else:
|
|
if DEBUG: print >> sys.stderr, 'No-op.'
|
|
exit(0)
|
|
|
|
if TEMP_DIR:
|
|
temp_dir = TEMP_DIR
|
|
if os.path.exists(temp_dir):
|
|
shutil.rmtree(temp_dir) # clear it
|
|
os.makedirs(temp_dir)
|
|
else:
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
|
def in_temp(name):
|
|
return os.path.join(temp_dir, name)
|
|
|
|
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
|
|
|
|
try:
|
|
call = CXX if use_cxx else CC
|
|
|
|
## Parse args
|
|
|
|
newargs = sys.argv[1:]
|
|
|
|
opt_level = 0
|
|
llvm_opts = None
|
|
llvm_lto = None
|
|
closure = None
|
|
js_transform = None
|
|
pre_js = None
|
|
post_js = None
|
|
minify_whitespace = None
|
|
data_files = []
|
|
compression = None
|
|
ignore_dynamic_linking = False
|
|
shell_path = shared.path_from_root('src', 'shell.html')
|
|
js_libraries = []
|
|
|
|
def check_bad_eq(arg):
|
|
assert '=' not in arg, 'Invalid parameter (do not use "=" with "--" options)'
|
|
|
|
for i in range(len(newargs)):
|
|
if newargs[i].startswith('-O'):
|
|
try:
|
|
opt_level = int(newargs[i][2])
|
|
assert 0 <= opt_level <= 3
|
|
except:
|
|
raise Exception('Invalid optimization level: ' + newargs[i])
|
|
newargs[i] = ''
|
|
elif newargs[i].startswith('--llvm-opts'):
|
|
check_bad_eq(newargs[i])
|
|
llvm_opts = eval(newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--llvm-lto'):
|
|
check_bad_eq(newargs[i])
|
|
llvm_lto = eval(newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--closure'):
|
|
check_bad_eq(newargs[i])
|
|
closure = int(newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--js-transform'):
|
|
check_bad_eq(newargs[i])
|
|
js_transform = newargs[i+1]
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--pre-js'):
|
|
check_bad_eq(newargs[i])
|
|
pre_js = open(newargs[i+1]).read()
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--post-js'):
|
|
check_bad_eq(newargs[i])
|
|
post_js = open(newargs[i+1]).read()
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--minify'):
|
|
check_bad_eq(newargs[i])
|
|
minify_whitespace = int(newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--embed-file'):
|
|
check_bad_eq(newargs[i])
|
|
data_files.append({ 'name': newargs[i+1], 'mode': 'embed' })
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--preload-file'):
|
|
check_bad_eq(newargs[i])
|
|
data_files.append({ 'name': newargs[i+1], 'mode': 'preload' })
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--compression'):
|
|
check_bad_eq(newargs[i])
|
|
parts = newargs[i+1].split(',')
|
|
assert len(parts) == 3, '--compression requires specifying native_encoder,js_decoder,js_name - see emcc --help. got: %s' % newargs[i+1]
|
|
Compression.encoder = parts[0]
|
|
Compression.decoder = parts[1]
|
|
Compression.js_name = parts[2]
|
|
assert os.path.exists(Compression.encoder), 'native encoder %s does not exist' % Compression.encoder
|
|
assert os.path.exists(Compression.decoder), 'js decoder %s does not exist' % Compression.decoder
|
|
Compression.on = True
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i] == '--ignore-dynamic-linking':
|
|
ignore_dynamic_linking = True
|
|
newargs[i] = ''
|
|
elif newargs[i] == '-v':
|
|
shared.COMPILER_OPTS += ['-v']
|
|
DEBUG = 1
|
|
newargs[i] = ''
|
|
elif newargs[i].startswith('--shell-file'):
|
|
check_bad_eq(newargs[i])
|
|
shell_path = newargs[i+1]
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
elif newargs[i].startswith('--js-library'):
|
|
check_bad_eq(newargs[i])
|
|
js_libraries.append(newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
newargs = [ arg for arg in newargs if arg is not '' ]
|
|
|
|
if llvm_opts is None: llvm_opts = LLVM_OPT_LEVEL[opt_level]
|
|
if llvm_lto is None: llvm_lto = llvm_opts > 0
|
|
if closure is None: closure = 1 if opt_level >= 2 else 0
|
|
if minify_whitespace is None:
|
|
minify_whitespace = closure # if closure is run, minify whitespace
|
|
|
|
if closure:
|
|
assert os.path.exists(shared.CLOSURE_COMPILER), 'emcc: fatal: Closure compiler (%s) does not exist' % shared.CLOSURE_COMPILER
|
|
|
|
settings_changes = []
|
|
for i in range(len(newargs)):
|
|
if newargs[i] == '-s':
|
|
assert '=' in newargs[i+1], 'Incorrect syntax for -s (use -s OPT=VAL): ' + newargs[i+1]
|
|
settings_changes.append(newargs[i+1])
|
|
newargs[i] = newargs[i+1] = ''
|
|
elif newargs[i].startswith('--typed-arrays'):
|
|
assert '=' not in newargs[i], 'Invalid typed arrays parameter (do not use "=")'
|
|
settings_changes.append('USE_TYPED_ARRAYS=' + newargs[i+1])
|
|
newargs[i] = ''
|
|
newargs[i+1] = ''
|
|
newargs = [ arg for arg in newargs if arg is not '' ]
|
|
|
|
# Find input files
|
|
|
|
input_files = []
|
|
has_source_inputs = False
|
|
lib_dirs = [shared.path_from_root('system', 'lib')]
|
|
libs = []
|
|
for i in range(len(newargs)): # find input files XXX this a simple heuristic. we should really analyze based on a full understanding of gcc params,
|
|
# right now we just assume that what is left contains no more |-x OPT| things
|
|
arg = newargs[i]
|
|
|
|
if i > 0:
|
|
prev = newargs[i-1]
|
|
if prev in ['-MT', '-install_name']: continue # ignore this gcc-style argument
|
|
|
|
if arg.endswith(SOURCE_SUFFIXES + BITCODE_SUFFIXES + DYNAMICLIB_SUFFIXES + ASSEMBLY_SUFFIXES) or shared.Building.is_ar(arg): # we already removed -o <target>, so all these should be inputs
|
|
newargs[i] = ''
|
|
if os.path.exists(arg):
|
|
if arg.endswith(SOURCE_SUFFIXES):
|
|
input_files.append(arg)
|
|
has_source_inputs = True
|
|
else:
|
|
# this should be bitcode, make sure it is valid
|
|
if arg.endswith(ASSEMBLY_SUFFIXES) or shared.Building.is_bitcode(arg):
|
|
input_files.append(arg)
|
|
elif arg.endswith(STATICLIB_SUFFIXES + DYNAMICLIB_SUFFIXES):
|
|
# if it's not, and it's a library, just add it to libs to find later
|
|
l = unsuffixed_basename(arg)
|
|
for prefix in LIB_PREFIXES:
|
|
if not prefix: continue
|
|
if l.startswith(prefix):
|
|
l = l[len(prefix):]
|
|
break;
|
|
libs.append(l)
|
|
newargs[i] = ''
|
|
else:
|
|
print >> sys.stderr, 'emcc: %s: warning: Not valid LLVM bitcode' % arg
|
|
else:
|
|
print >> sys.stderr, 'emcc: %s: warning: No such file or directory' % arg
|
|
elif arg.startswith('-L'):
|
|
lib_dirs.append(arg[2:])
|
|
newargs[i] = ''
|
|
elif arg.startswith('-l'):
|
|
libs.append(arg[2:])
|
|
newargs[i] = ''
|
|
|
|
newargs = [ arg for arg in newargs if arg is not '' ]
|
|
|
|
# Find library files
|
|
for lib in libs:
|
|
if DEBUG: print >> sys.stderr, 'emcc: looking for library "%s"' % lib
|
|
found = False
|
|
for prefix in LIB_PREFIXES:
|
|
for suff in STATICLIB_SUFFIXES + DYNAMICLIB_SUFFIXES:
|
|
name = prefix + lib + suff
|
|
for lib_dir in lib_dirs:
|
|
path = os.path.join(lib_dir, name)
|
|
if os.path.exists(path):
|
|
if DEBUG: print >> sys.stderr, 'emcc: found library "%s" at %s' % (lib, path)
|
|
input_files.append(path)
|
|
found = True
|
|
break
|
|
if found: break
|
|
if found: break
|
|
|
|
if ignore_dynamic_linking:
|
|
input_files = filter(lambda input_file: not input_file.endswith(DYNAMICLIB_SUFFIXES), input_files)
|
|
|
|
if len(input_files) == 0:
|
|
print >> sys.stderr, 'emcc: no input files'
|
|
print >> sys.stderr, 'note that input files without a known suffix are ignored, make sure your input files end with one of: ' + str(SOURCE_SUFFIXES + BITCODE_SUFFIXES + DYNAMICLIB_SUFFIXES + STATICLIB_SUFFIXES + ASSEMBLY_SUFFIXES)
|
|
exit(0)
|
|
|
|
newargs += CC_ADDITIONAL_ARGS
|
|
|
|
specified_target = target
|
|
target = specified_target if specified_target is not None else 'a.out.js' # specified_target is the user-specified one, target is what we will generate
|
|
|
|
target_basename = unsuffixed_basename(target)
|
|
|
|
# -c means do not link in gcc, and for us, the parallel is to not go all the way to JS, but stop at bitcode
|
|
has_dash_c = '-c' in newargs
|
|
if has_dash_c:
|
|
assert has_source_inputs, 'Must have source code inputs to use -c'
|
|
target = target_basename + '.o'
|
|
|
|
if '.' in target:
|
|
final_suffix = target.split('.')[-1]
|
|
else:
|
|
final_suffix = ''
|
|
|
|
assert not (Compression.on and final_suffix != 'html'), 'Compression only works when generating HTML'
|
|
|
|
# Apply optimization level settings
|
|
shared.Settings.apply_opt_level(opt_level, noisy=True)
|
|
|
|
# Apply -s settings in newargs here (after optimization levels, so they can override them)
|
|
for change in settings_changes:
|
|
key, value = change.split('=')
|
|
exec('shared.Settings.' + key + ' = ' + value)
|
|
|
|
## Compile source code to bitcode
|
|
|
|
if DEBUG: print >> sys.stderr, 'emcc: compiling to bitcode'
|
|
|
|
temp_files = []
|
|
|
|
# First, generate LLVM bitcode. For each input file, we get base.o with bitcode
|
|
for input_file in input_files:
|
|
if input_file.endswith(SOURCE_SUFFIXES):
|
|
if DEBUG: print >> sys.stderr, 'emcc: compiling source file: ', input_file
|
|
output_file = in_temp(unsuffixed_uniquename(input_file) + '.o')
|
|
temp_files.append(output_file)
|
|
args = newargs + ['-emit-llvm', '-c', input_file, '-o', output_file]
|
|
if DEBUG: print >> sys.stderr, "emcc running:", call, ' '.join(args)
|
|
execute([call] + args) # let compiler frontend print directly, so colors are saved (PIPE kills that)
|
|
if not os.path.exists(output_file):
|
|
print >> sys.stderr, 'emcc: compiler frontend failed to generate LLVM bitcode, halting'
|
|
sys.exit(1)
|
|
else: # bitcode
|
|
if input_file.endswith(BITCODE_SUFFIXES):
|
|
if DEBUG: print >> sys.stderr, 'emcc: copying bitcode file: ', input_file
|
|
temp_file = in_temp(unsuffixed_uniquename(input_file) + '.o')
|
|
shutil.copyfile(input_file, temp_file)
|
|
temp_files.append(temp_file)
|
|
elif input_file.endswith(DYNAMICLIB_SUFFIXES) or shared.Building.is_ar(input_file):
|
|
if DEBUG: print >> sys.stderr, 'emcc: copying library file: ', input_file
|
|
temp_file = in_temp(os.path.basename(input_file))
|
|
shutil.copyfile(input_file, temp_file)
|
|
temp_files.append(temp_file)
|
|
else: #.ll
|
|
if not LEAVE_INPUTS_RAW:
|
|
# Note that by assembling the .ll file, then disassembling it later, we will
|
|
# remove annotations which is a good thing for compilation time
|
|
if DEBUG: print >> sys.stderr, 'emcc: assembling assembly file: ', input_file
|
|
temp_file = in_temp(unsuffixed_uniquename(input_file) + '.o')
|
|
shared.Building.llvm_as(input_file, temp_file)
|
|
temp_files.append(temp_file)
|
|
|
|
if not LEAVE_INPUTS_RAW: assert len(temp_files) == len(input_files)
|
|
|
|
# If we were just asked to generate bitcode, stop there
|
|
if final_suffix not in ['js', 'html']:
|
|
if llvm_opts > 0:
|
|
print >> sys.stderr, 'emcc: warning: -Ox flags ignored, since not generating JavaScript'
|
|
if not specified_target:
|
|
for input_file in input_files:
|
|
shutil.move(in_temp(unsuffixed_uniquename(input_file) + '.o'), unsuffixed_basename(input_file) + '.' + final_suffix)
|
|
else:
|
|
if len(input_files) == 1:
|
|
shutil.move(in_temp(unsuffixed_uniquename(input_files[0]) + '.o'), specified_target)
|
|
else:
|
|
assert not has_dash_c, 'fatal error: cannot specify -o with -c with multiple files' + str(sys.argv)
|
|
# We have a specified target (-o <target>), which is not JavaScript or HTML, and
|
|
# we have multiple files: Link them TODO: llvm link-time opts?
|
|
ld_args = temp_files + ['-b', specified_target]
|
|
#[arg.split('-Wl,')[1] for arg in filter(lambda arg: arg.startswith('-Wl,'), sys.argv)]
|
|
if DEBUG: print >> sys.stderr, 'emcc: link: ' + str(ld_args)
|
|
execute([shared.LLVM_LD, '-disable-opt'] + ld_args)
|
|
exit(0)
|
|
|
|
## Continue on to create JavaScript
|
|
|
|
if DEBUG: print >> sys.stderr, 'emcc: will generate JavaScript'
|
|
|
|
extra_files_to_link = []
|
|
|
|
if not LEAVE_INPUTS_RAW and not AUTODEBUG:
|
|
# Check if we need to include some libraries that we compile. (We implement libc ourselves in js, but
|
|
# compile a malloc implementation and stdlibc++.)
|
|
# Note that we assume a single symbol is enough to know if we have/do not have dlmalloc etc. If you
|
|
# include just a few symbols but want the rest, this will not work.
|
|
|
|
# dlmalloc
|
|
def create_dlmalloc():
|
|
if DEBUG: print >> sys.stderr, 'emcc: building dlmalloc for cache'
|
|
execute(shared.ENV_PREFIX + ['python', shared.EMCC, shared.path_from_root('system', 'lib', 'dlmalloc.c'), '-g', '-o', in_temp('dlmalloc.o')], stdout=stdout, stderr=stderr)
|
|
# we include the libc++ new stuff here, so that the common case of using just new/delete is quick to link
|
|
execute(shared.ENV_PREFIX + ['python', shared.EMXX, shared.path_from_root('system', 'lib', 'libcxx', 'new.cpp'), '-g', '-o', in_temp('new.o')], stdout=stdout, stderr=stderr)
|
|
shared.Building.link([in_temp('dlmalloc.o'), in_temp('new.o')], in_temp('dlmalloc_full.o'))
|
|
return in_temp('dlmalloc_full.o')
|
|
def fix_dlmalloc():
|
|
# dlmalloc needs some sign correction. # If we are in mode 0, switch to 2. We will add our lines
|
|
try:
|
|
if shared.Settings.CORRECT_SIGNS == 0: raise Exception('we need to change to 2')
|
|
except: # we fail if equal to 0 - so we need to switch to 2 - or if CORRECT_SIGNS is not even in Settings
|
|
shared.Settings.CORRECT_SIGNS = 2
|
|
if shared.Settings.CORRECT_SIGNS == 2:
|
|
shared.Settings.CORRECT_SIGNS_LINES = [shared.path_from_root('src', 'dlmalloc.c') + ':' + str(i+4) for i in [4816, 4191, 4246, 4199, 4205, 4235, 4227]]
|
|
# If we are in mode 1, we are correcting everything anyhow. If we are in mode 3, we will be corrected
|
|
# so all is well anyhow too.
|
|
# XXX We also need to add libc symbols that use malloc, for example strdup. It's very rare to use just them and not
|
|
# a normal malloc symbol (like free, after calling strdup), so we haven't hit this yet, but it is possible.
|
|
dlmalloc_symbols = open(shared.path_from_root('system', 'lib', 'dlmalloc.symbols')).read().split('\n')
|
|
|
|
# libcxx
|
|
def create_libcxx():
|
|
if DEBUG: print >> sys.stderr, 'emcc: building libcxx for cache'
|
|
shared.Building.build_library('libcxx', shared.EMSCRIPTEN_TEMP_DIR, shared.EMSCRIPTEN_TEMP_DIR, ['libcxx.bc'], configure=None, copy_project=True, source_dir=shared.path_from_root('system', 'lib', 'libcxx'))
|
|
return os.path.join(shared.EMSCRIPTEN_TEMP_DIR, 'libcxx', 'libcxx.bc')
|
|
def fix_libcxx():
|
|
assert shared.Settings.QUANTUM_SIZE == 4, 'We do not support libc++ with QUANTUM_SIZE == 1'
|
|
# libcxx might need corrections, so turn them all on. TODO: check which are actually needed
|
|
shared.Settings.CORRECT_SIGNS = shared.Settings.CORRECT_OVERFLOWS = shared.Settings.CORRECT_ROUNDINGS = 1
|
|
print >> sys.stderr, 'emcc: warning: using libcxx turns on CORRECT_* options'
|
|
libcxx_symbols = map(lambda line: line.strip().split(' ')[1], open(shared.path_from_root('system', 'lib', 'libcxx', 'symbols')).readlines())
|
|
libcxx_symbols = filter(lambda symbol: symbol not in dlmalloc_symbols, libcxx_symbols)
|
|
libcxx_symbols = set(libcxx_symbols)
|
|
|
|
# libcxxabi - just for dynamic_cast for now
|
|
def create_libcxxabi():
|
|
if DEBUG: print >> sys.stderr, 'emcc: building libcxxabi for cache'
|
|
shared.Building.build_library('libcxxabi', shared.EMSCRIPTEN_TEMP_DIR, shared.EMSCRIPTEN_TEMP_DIR, ['libcxxabi.bc'], configure=None, copy_project=True, source_dir=shared.path_from_root('system', 'lib', 'libcxxabi'))
|
|
return os.path.join(shared.EMSCRIPTEN_TEMP_DIR, 'libcxxabi', 'libcxxabi.bc')
|
|
def fix_libcxxabi():
|
|
assert shared.Settings.QUANTUM_SIZE == 4, 'We do not support libc++abi with QUANTUM_SIZE == 1'
|
|
print >> sys.stderr, 'emcc: warning: using libcxxabi, this may need CORRECT_* options'
|
|
#shared.Settings.CORRECT_SIGNS = shared.Settings.CORRECT_OVERFLOWS = shared.Settings.CORRECT_ROUNDINGS = 1
|
|
libcxxabi_symbols = map(lambda line: line.strip().split(' ')[1], open(shared.path_from_root('system', 'lib', 'libcxxabi', 'symbols')).readlines())
|
|
libcxxabi_symbols = filter(lambda symbol: symbol not in dlmalloc_symbols, libcxxabi_symbols)
|
|
libcxxabi_symbols = set(libcxxabi_symbols)
|
|
|
|
force = False # If we have libcxx, we must force inclusion of dlmalloc, since libcxx uses new internally. Note: this is kind of hacky
|
|
|
|
for name, create, fix, library_symbols in [('libcxx', create_libcxx, fix_libcxx, libcxx_symbols),
|
|
('libcxxabi', create_libcxxabi, fix_libcxxabi, libcxxabi_symbols),
|
|
('dlmalloc', create_dlmalloc, fix_dlmalloc, dlmalloc_symbols)]:
|
|
need = []
|
|
has = []
|
|
for temp_file in temp_files:
|
|
symbols = shared.Building.llvm_nm(temp_file)
|
|
for library_symbol in library_symbols:
|
|
if library_symbol in symbols.undefs:
|
|
need.append(library_symbol)
|
|
if library_symbol in symbols.defs:
|
|
has.append(library_symbol)
|
|
if DEBUG: print >> sys.stderr, 'emcc: considering including %s: we need |%s| and have |%s|' % (name, str(need), str(has))
|
|
if force or (need and not has):
|
|
# We need to build and link the library in
|
|
if DEBUG: print >> sys.stderr, 'emcc: including %s' % name
|
|
extra_files_to_link.append(shared.Cache.get(name, create))
|
|
force = True
|
|
if fix:
|
|
fix()
|
|
|
|
# First, combine the bitcode files if there are several. We must also link if we have a singleton .a
|
|
if len(input_files) + len(extra_files_to_link) > 1 or \
|
|
(not LEAVE_INPUTS_RAW and not (suffix(temp_files[0]) in BITCODE_SUFFIXES or suffix(temp_files[0]) in DYNAMICLIB_SUFFIXES) and shared.Building.is_ar(temp_files[0])):
|
|
linker_inputs = temp_files + extra_files_to_link
|
|
if DEBUG: print >> sys.stderr, 'emcc: linking: ', linker_inputs
|
|
shared.Building.link(linker_inputs,
|
|
in_temp(target_basename + '.bc'))
|
|
final = in_temp(target_basename + '.bc')
|
|
else:
|
|
if not LEAVE_INPUTS_RAW:
|
|
shutil.move(temp_files[0], in_temp(target_basename + '.bc'))
|
|
final = in_temp(target_basename + '.bc')
|
|
else:
|
|
final = input_files[0]
|
|
|
|
if DEBUG:
|
|
print >> sys.stderr, 'emcc: saving intermediate processing steps to %s' % shared.EMSCRIPTEN_TEMP_DIR
|
|
|
|
intermediate_counter = 0
|
|
def save_intermediate(name=None, suffix='js'):
|
|
global intermediate_counter
|
|
shutil.copyfile(final, os.path.join(shared.EMSCRIPTEN_TEMP_DIR, 'emcc-%d%s.%s' % (intermediate_counter, '' if name is None else '-' + name, suffix)))
|
|
intermediate_counter += 1
|
|
|
|
if not LEAVE_INPUTS_RAW: save_intermediate('basebc', 'bc')
|
|
|
|
# Optimize, if asked to
|
|
if llvm_opts > 0 and not LEAVE_INPUTS_RAW:
|
|
if DEBUG: print >> sys.stderr, 'emcc: LLVM -O%d' % llvm_opts
|
|
shared.Building.llvm_opt(in_temp(target_basename + '.bc'), llvm_opts)
|
|
if DEBUG: save_intermediate('opt', 'bc')
|
|
# Do LTO in a separate pass to work around LLVM bug XXX (see failure e.g. in cubescript)
|
|
if llvm_lto and shared.Building.can_use_unsafe_opts() and shared.Building.can_build_standalone():
|
|
lto_opts = []
|
|
if not shared.Building.can_inline(): lto_opts.append('-disable-inlining')
|
|
lto_opts.append('-std-link-opts')
|
|
if DEBUG: print >> sys.stderr, 'emcc: LLVM LTO:', lto_opts
|
|
shared.Building.llvm_opt(in_temp(target_basename + '.bc'), lto_opts)
|
|
if DEBUG: save_intermediate('lto', 'bc')
|
|
else:
|
|
# If possible, remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it)
|
|
if not LEAVE_INPUTS_RAW and shared.Building.can_build_standalone():
|
|
if DEBUG: print >> sys.stderr, 'emcc: LLVM dead globals elimination'
|
|
shared.Building.llvm_opt(in_temp(target_basename + '.bc'), ['-internalize', '-globaldce'])
|
|
if DEBUG: save_intermediate('dce', 'bc')
|
|
|
|
# Prepare .ll for Emscripten
|
|
if not LEAVE_INPUTS_RAW:
|
|
final = shared.Building.llvm_dis(final, final + '.ll')
|
|
else:
|
|
assert len(input_files) == 1
|
|
if DEBUG: save_intermediate('ll', 'll')
|
|
|
|
if AUTODEBUG:
|
|
if DEBUG: print >> sys.stderr, 'emcc: autodebug'
|
|
execute(shared.ENV_PREFIX + ['python', shared.AUTODEBUGGER, final, final + '.ad.ll'])
|
|
final += '.ad.ll'
|
|
if DEBUG: save_intermediate('autodebug', 'll')
|
|
|
|
# Emscripten
|
|
if DEBUG: print >> sys.stderr, 'emcc: LLVM => JS'
|
|
extra_args = [] if not js_libraries else ['--libraries', ','.join(map(os.path.abspath, js_libraries))]
|
|
final = shared.Building.emscripten(final, append_ext=False, extra_args=extra_args)
|
|
if DEBUG: save_intermediate('original')
|
|
|
|
# Embed and preload files
|
|
if len(data_files) > 0:
|
|
if DEBUG: print >> sys.stderr, 'emcc: setting up files'
|
|
code = ''
|
|
|
|
if final_suffix == 'html':
|
|
code += '''
|
|
var BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : (typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : console.log("warning: cannot build blobs"));
|
|
var URLObject = typeof window != "undefined" ? (window.URL ? window.URL : window.webkitURL) : console.log("warning: cannot create object URLs");
|
|
var hasBlobConstructor;
|
|
try {
|
|
new Blob();
|
|
hasBlobConstructor = true;
|
|
} catch(e) {
|
|
hasBlobConstructor = false;
|
|
console.log("warning: no blob constructor, cannot create blobs with mimetypes");
|
|
}
|
|
'''
|
|
|
|
code += 'var preloadedImages = {}; // maps url to image data\n'
|
|
code += 'var preloadedAudios = {}; // maps url to audio data\n'
|
|
|
|
# Expand directories into individual files
|
|
def add(mode, dirname, names):
|
|
for name in names:
|
|
fullname = os.path.join(dirname, name)
|
|
if not os.path.isdir(fullname):
|
|
data_files.append({ 'name': fullname, 'mode': mode })
|
|
|
|
for file_ in data_files:
|
|
if os.path.isdir(file_['name']):
|
|
os.path.walk(file_['name'], add, file_['mode'])
|
|
data_files = filter(lambda file_: not os.path.isdir(file_['name']), data_files)
|
|
|
|
for file_ in data_files:
|
|
file_['name'] = file_['name'].replace(os.path.sep, '/')
|
|
file_['net_name'] = file_['name']
|
|
|
|
data_target = unsuffixed(target) + '.data'
|
|
|
|
# Set up folders
|
|
partial_dirs = []
|
|
for file_ in data_files:
|
|
dirname = os.path.dirname(file_['name'])
|
|
if dirname != '' and dirname != '/':
|
|
parts = dirname.split('/')
|
|
for i in range(len(parts)):
|
|
partial = '/'.join(parts[:i+1])
|
|
if partial not in partial_dirs:
|
|
code += '''FS.createFolder('/%s', '%s', true, false);\n''' % ('/'.join(parts[:i]), parts[i])
|
|
partial_dirs.append(partial)
|
|
|
|
if final_suffix == 'html':
|
|
# Bundle all datafiles into one archive. Avoids doing lots of simultaneous XHRs which has overhead.
|
|
data = open(data_target, 'wb')
|
|
start = 0
|
|
for file_ in data_files:
|
|
file_['data_start'] = start
|
|
curr = open(file_['name'], 'rb').read()
|
|
file_['data_end'] = start + len(curr)
|
|
start += len(curr)
|
|
data.write(curr)
|
|
data.close()
|
|
if Compression.on:
|
|
Compression.compress(data_target)
|
|
|
|
# Data requests - for getting a block of data out of the big archive - have a similar API to XHRs
|
|
code += '''
|
|
function DataRequest() {}
|
|
DataRequest.prototype = {
|
|
requests: {},
|
|
open: function(mode, name) {
|
|
this.requests[name] = this;
|
|
},
|
|
send: function() {}
|
|
};
|
|
'''
|
|
|
|
counter = 0
|
|
for file_ in data_files:
|
|
filename = file_['name']
|
|
if file_['mode'] == 'embed':
|
|
# Embed
|
|
code += '''FS.createDataFile('/', '%s', %s, true, true);\n''' % (os.path.basename(filename), str(map(ord, open(filename, 'rb').read())))
|
|
elif file_['mode'] == 'preload':
|
|
# Preload
|
|
assert final_suffix == 'html', 'Can only preload files when generating HTML'
|
|
|
|
varname = 'filePreload%d' % counter
|
|
counter += 1
|
|
image = filename.endswith(IMAGE_SUFFIXES)
|
|
audio = filename.endswith(AUDIO_SUFFIXES)
|
|
|
|
if image:
|
|
finish = '''
|
|
var bb = new BlobBuilder();
|
|
bb.append(byteArray.buffer);
|
|
var b = bb.getBlob();
|
|
var url = URLObject.createObjectURL(b);
|
|
var img = new Image();
|
|
img.onload = function() {
|
|
assert(img.complete, 'Image %(filename)s could not be decoded');
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.drawImage(img, 0, 0);
|
|
preloadedImages['%(filename)s'] = canvas;
|
|
URLObject.revokeObjectURL(url);
|
|
removeRunDependency();
|
|
};
|
|
img.onerror = function(event) {
|
|
console.log('Image %(filename)s could not be decoded');
|
|
};
|
|
img.src = url;
|
|
''' % { 'filename': filename }
|
|
elif audio:
|
|
# Need actual blob constructor here, to set the mimetype or else audios fail to decode
|
|
finish = '''
|
|
if (hasBlobConstructor) {
|
|
var b = new Blob([byteArray.buffer], { type: '%(mimetype)s' });
|
|
var url = URLObject.createObjectURL(b); // XXX we never revoke this!
|
|
var audio = new Audio();
|
|
audio['oncanplaythrough'] = function() { // XXX string for closure
|
|
audio['oncanplaythrough'] = null;
|
|
preloadedAudios['%(filename)s'] = audio;
|
|
removeRunDependency();
|
|
};
|
|
audio.onerror = function(event) {
|
|
console.log('Audio %(filename)s could not be decoded');
|
|
};
|
|
audio.src = url;
|
|
} else {
|
|
preloadedAudios['%(filename)s'] = new Audio(); // empty shim
|
|
removeRunDependency();
|
|
}
|
|
''' % { 'filename': filename, 'mimetype': AUDIO_MIMETYPES[suffix(filename)] }
|
|
else:
|
|
finish = 'removeRunDependency();\n'
|
|
|
|
code += '''
|
|
var %(varname)s = new %(request)s();
|
|
%(varname)s.open('GET', '%(netname)s', true);
|
|
%(varname)s.responseType = 'arraybuffer';
|
|
%(varname)s.onload = function() {
|
|
var arrayBuffer = %(varname)s.response;
|
|
assert(arrayBuffer, 'Loading file %(filename)s failed.');
|
|
var byteArray = arrayBuffer.byteLength ? new Uint8Array(arrayBuffer) : arrayBuffer;
|
|
FS.createDataFile('/%(dirname)s', '%(basename)s', byteArray, true, true);
|
|
%(finish)s
|
|
};
|
|
addRunDependency();
|
|
%(varname)s.send(null);
|
|
''' % {
|
|
'request': 'DataRequest', # In the past we also supported XHRs here
|
|
'varname': varname,
|
|
'filename': filename,
|
|
'netname': file_['net_name'],
|
|
'dirname': os.path.dirname(filename),
|
|
'basename': os.path.basename(filename),
|
|
'finish': finish
|
|
}
|
|
else:
|
|
assert 0
|
|
|
|
if final_suffix == 'html':
|
|
# Get the big archive and split it up
|
|
use_data = ''
|
|
for file_ in data_files:
|
|
if file_['mode'] == 'preload':
|
|
use_data += '''
|
|
curr = DataRequest.prototype.requests['%s'];
|
|
curr.response = byteArray.subarray(%d,%d);
|
|
curr.onload();
|
|
''' % (file_['name'], file_['data_start'], file_['data_end'])
|
|
use_data += ' removeRunDependency();\n'
|
|
|
|
if Compression.on:
|
|
use_data = '''
|
|
Module["decompress"](byteArray, function(decompressed) {
|
|
byteArray = new Uint8Array(decompressed);
|
|
%s
|
|
});
|
|
''' % use_data
|
|
|
|
code += '''
|
|
var dataFile = new XMLHttpRequest();
|
|
dataFile.open('GET', '%s', true);
|
|
dataFile.responseType = 'arraybuffer';
|
|
dataFile.onload = function() {
|
|
var arrayBuffer = dataFile.response;
|
|
assert(arrayBuffer, 'Loading data file failed.');
|
|
var byteArray = new Uint8Array(arrayBuffer);
|
|
var curr;
|
|
%s
|
|
};
|
|
addRunDependency();
|
|
dataFile.send(null);
|
|
if (Module['setStatus']) Module['setStatus']('Downloading...');
|
|
''' % (Compression.compressed_name(data_target) if Compression.on else data_target, use_data)
|
|
|
|
src = open(final).read().replace('// {{PRE_RUN_ADDITIONS}}', code)
|
|
final += '.files.js'
|
|
open(final, 'w').write(src)
|
|
if DEBUG: save_intermediate('files')
|
|
|
|
# Apply pre and postjs files
|
|
if pre_js or post_js:
|
|
if DEBUG: print >> sys.stderr, 'emcc: applying pre/postjses'
|
|
src = open(final).read()
|
|
final += '.pp.js'
|
|
open(final, 'w').write((pre_js or '') + src + (post_js or ''))
|
|
if DEBUG: save_intermediate('pre-post')
|
|
|
|
# Apply a source code transformation, if requested
|
|
if js_transform:
|
|
shutil.copyfile(final, final + '.tr.js')
|
|
final += '.tr.js'
|
|
posix = True if not shared.WINDOWS else False
|
|
if DEBUG: print >> sys.stderr, 'emcc: applying transform: %s' % js_transform
|
|
execute(shlex.split(js_transform, posix=posix) + [os.path.abspath(final)])
|
|
if DEBUG: save_intermediate('transformed')
|
|
|
|
# It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing
|
|
js_optimizer_queue = []
|
|
def flush_js_optimizer_queue():
|
|
global final, js_optimizer_queue
|
|
if len(js_optimizer_queue) > 0:
|
|
if not DEBUG:
|
|
final = shared.Building.js_optimizer(final, js_optimizer_queue)
|
|
else:
|
|
for name in js_optimizer_queue:
|
|
print >> sys.stderr, 'emcc: applying js optimization pass:', name
|
|
final = shared.Building.js_optimizer(final, [name])
|
|
save_intermediate(name)
|
|
js_optimizer_queue = []
|
|
|
|
if opt_level >= 1:
|
|
if DEBUG: print >> sys.stderr, 'emcc: running pre-closure post-opts'
|
|
|
|
if DEBUG:
|
|
# Clean up the syntax a bit
|
|
final = shared.Building.js_optimizer(final, [])
|
|
if DEBUG: save_intermediate('pretty')
|
|
|
|
if shared.Settings.RELOOP:
|
|
js_optimizer_queue += ['hoistMultiples', 'loopOptimizer']
|
|
|
|
flush_js_optimizer_queue()
|
|
|
|
# eliminator
|
|
if DEBUG: print >> sys.stderr, 'emcc: running variable eliminator'
|
|
final = shared.Building.eliminator(final)
|
|
if DEBUG: save_intermediate('eliminator')
|
|
|
|
# js optimizer pre-pass
|
|
js_optimizer_queue += ['simplifyExpressionsPre']
|
|
if shared.Settings.RELOOP:
|
|
js_optimizer_queue += ['optimizeShiftsAggressive'] # aggressive shifts optimization requires loops, it breaks on switches
|
|
flush_js_optimizer_queue()
|
|
final = shared.Building.eliminator(final) # aggressive shifts optimization introduces some new variables, remove ones that we can
|
|
if DEBUG: save_intermediate('eliminator')
|
|
|
|
if closure:
|
|
flush_js_optimizer_queue()
|
|
|
|
if DEBUG: print >> sys.stderr, 'emcc: running closure'
|
|
final = shared.Building.closure_compiler(final)
|
|
if DEBUG: save_intermediate('closure')
|
|
|
|
if opt_level >= 1:
|
|
# js optimizer post-pass
|
|
if DEBUG: print >> sys.stderr, 'emcc: running post-closure post-opts'
|
|
js_optimizer_queue += ['simplifyExpressionsPost']
|
|
|
|
if minify_whitespace:
|
|
js_optimizer_queue += ['compress']
|
|
|
|
flush_js_optimizer_queue()
|
|
|
|
# If we were asked to also generate HTML, do that
|
|
if final_suffix == 'html':
|
|
if DEBUG: print >> sys.stderr, 'emcc: generating HTML'
|
|
shell = open(shell_path).read()
|
|
html = open(target, 'w')
|
|
if not Compression.on:
|
|
html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(final).read()))
|
|
else:
|
|
# Compress the main code
|
|
js_target = unsuffixed(target) + '.js'
|
|
shutil.move(final, js_target)
|
|
Compression.compress(js_target)
|
|
|
|
# Run the decompressor in a worker, and add code to
|
|
# 1. download the compressed file
|
|
# 2. decompress to a typed array
|
|
# 3. convert to a string of source code
|
|
# 4. insert a script element with that source code (more effective than eval)
|
|
decoding = '''
|
|
var decompressWorker = new Worker('decompress.js');
|
|
var decompressCallbacks = [];
|
|
var decompressions = 0;
|
|
Module["decompress"] = function(data, callback) {
|
|
var id = decompressCallbacks.length;
|
|
decompressCallbacks.push(callback);
|
|
decompressWorker.postMessage({ data: data, id: id });
|
|
if (Module['setStatus']) {
|
|
decompressions++;
|
|
Module['setStatus']('Decompressing...');
|
|
}
|
|
};
|
|
decompressWorker.onmessage = function(event) {
|
|
decompressCallbacks[event.data.id](event.data.data);
|
|
decompressCallbacks[event.data.id] = null;
|
|
if (Module['setStatus']) {
|
|
decompressions--;
|
|
if (decompressions == 0) {
|
|
Module['setStatus']('');
|
|
}
|
|
}
|
|
};
|
|
var compiledCodeXHR = new XMLHttpRequest();
|
|
compiledCodeXHR.open('GET', '%s', true);
|
|
compiledCodeXHR.responseType = 'arraybuffer';
|
|
compiledCodeXHR.onload = function() {
|
|
var arrayBuffer = compiledCodeXHR.response;
|
|
if (!arrayBuffer) throw('Loading compressed code failed.');
|
|
var byteArray = new Uint8Array(arrayBuffer);
|
|
Module.decompress(byteArray, function(decompressed) {
|
|
var source = Array.prototype.slice.apply(decompressed).map(function(x) { return String.fromCharCode(x) }).join(''); // createObjectURL instead?
|
|
var scriptTag = document.createElement('script');
|
|
scriptTag.setAttribute('type', 'text/javascript');
|
|
scriptTag.innerHTML = source;
|
|
document.body.appendChild(scriptTag);
|
|
});
|
|
};
|
|
compiledCodeXHR.send(null);
|
|
''' % Compression.compressed_name(js_target)
|
|
html.write(shell.replace('{{{ SCRIPT_CODE }}}', decoding))
|
|
|
|
# Add decompressor with web worker glue code
|
|
decompressor = open('decompress.js', 'w')
|
|
decompressor.write(open(Compression.decoder).read())
|
|
decompressor.write('''
|
|
onmessage = function(event) {
|
|
postMessage({ data: %s(event.data.data), id: event.data.id });
|
|
};
|
|
''' % Compression.js_name)
|
|
decompressor.close()
|
|
|
|
html.close()
|
|
else:
|
|
# copy final JS to output
|
|
shutil.move(final, target)
|
|
|
|
finally:
|
|
if not TEMP_DIR:
|
|
try:
|
|
shutil.rmtree(temp_dir)
|
|
except:
|
|
pass
|
|
else:
|
|
print >> sys.stderr, 'emcc saved files are in:', temp_dir
|
|
|