aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Schneider <lars.schneider@autodesk.com>2012-09-25 14:44:54 +0200
committerLars Schneider <lars.schneider@autodesk.com>2012-09-26 19:30:19 +0200
commit0c33345f226af403d15f5f83811488aead85b71b (patch)
treecc1787e201bb3184fb901500e94175f22d322c6a
parentc74da50989604747e3174429d0538f668b4ff7e7 (diff)
Add emcc option "--split <size>" to split javascript file.
Splits the resulting javascript file into pieces to ease debugging. This option only works if Javascript is generated (target -o <name>.js). Files with function declarations must be loaded before main file upon execution. Without "-g" option: Creates files with function declarations up to the given size with the suffix "_functions.partxxx.js" and a main file with the suffix ".js". With "-g" option: Recreates the directory structure of the C source files and stores function declarations in their respective C files with the suffix ".js". If such a file exceeds the given size, files with the suffix ".partxxx.js" are created. The main file resides in the base directory and has the suffix ".js".
-rw-r--r--AUTHORS1
-rwxr-xr-xemcc38
-rw-r--r--src/jsifier.js17
-rw-r--r--src/modules.js5
-rw-r--r--src/settings.js2
-rwxr-xr-xtests/runner.py183
-rwxr-xr-xtools/split.py94
7 files changed, 338 insertions, 2 deletions
diff --git a/AUTHORS b/AUTHORS
index a46d90d9..e30a10ea 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -37,3 +37,4 @@ a license to everyone to use it as detailed in LICENSE.)
* Benjamin Stover <benjamin.stover@gmail.com>
* Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
* Janus Troelsen <janus.troelsen@stud.tu-darmstadt.de>
+* Lars Schneider <lars.schneider@autodesk.com> (copyright owned by Autodesk, Inc.) \ No newline at end of file
diff --git a/emcc b/emcc
index 97bc2f68..1dc9b756 100755
--- a/emcc
+++ b/emcc
@@ -268,6 +268,27 @@ Options that are modified or new in %s include:
will not minify the code (closure does
that)
+ --split <size> Splits the resulting javascript file into pieces
+ to ease debugging. This option only works if
+ Javascript is generated (target -o <name>.js).
+ Files with function declarations must be loaded
+ before main file upon execution.
+
+ Without "-g" option:
+ Creates files with function declarations up
+ to the given size with the suffix
+ "_functions.partxxx.js" and a main file with
+ the suffix ".js".
+
+ With "-g" option:
+ Recreates the directory structure of the C
+ source files and stores function declarations
+ in their respective C files with the suffix
+ ".js". If such a file exceeds the given size,
+ files with the suffix ".partxxx.js" are created.
+ The main file resides in the base directory and
+ has the suffix ".js".
+
--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
@@ -471,6 +492,7 @@ try:
pre_js = ''
post_js = ''
minify_whitespace = None
+ split_js_file = None
preload_files = []
embed_files = []
compression = None
@@ -527,6 +549,11 @@ try:
minify_whitespace = int(newargs[i+1])
newargs[i] = ''
newargs[i+1] = ''
+ elif newargs[i].startswith('--split'):
+ check_bad_eq(newargs[i])
+ split_js_file = int(newargs[i+1])
+ newargs[i] = ''
+ newargs[i+1] = ''
elif newargs[i].startswith('--embed-file'):
check_bad_eq(newargs[i])
embed_files.append(newargs[i+1])
@@ -601,6 +628,9 @@ try:
newargs[i+1] = ''
newargs = [ arg for arg in newargs if arg is not '' ]
+ if split_js_file:
+ settings_changes.append("PRINT_SPLIT_FILE_MARKER=1")
+
# Find input files
input_files = []
@@ -1071,8 +1101,12 @@ try:
html.close()
else:
- # copy final JS to output
- shutil.move(final, target)
+ if split_js_file:
+ from tools.split import split_javascript_file
+ split_javascript_file(final, unsuffixed(target), split_js_file)
+ else:
+ # copy final JS to output
+ shutil.move(final, target)
finally:
if not TEMP_DIR:
diff --git a/src/jsifier.js b/src/jsifier.js
index 92b39b7d..8021f8a1 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -522,6 +522,11 @@ function JSify(data, functionsOnly, givenFunctions) {
func.JS += ' */\n';
}
+ if (PRINT_SPLIT_FILE_MARKER) {
+ func.JS += '\n//FUNCTION_BEGIN_MARKER\n'
+ var associatedSourceFile = "NO_SOURCE";
+ }
+
func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n';
if (PROFILE) {
@@ -572,6 +577,13 @@ function JSify(data, functionsOnly, givenFunctions) {
if (EXECUTION_TIMEOUT > 0) {
ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n';
}
+
+ if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(line.lineNum)) {
+ // Overwrite the associated source file for every line. The last line should contain the source file associated to
+ // the return value/address of outer most block (the marked function).
+ associatedSourceFile = Debugging.getAssociatedSourceFile(line.lineNum);
+ }
+
// for special labels we care about (for phi), mark that we visited them
return ret + label.lines.map(function(line) { return line.JS + (Debugging.on ? Debugging.getComment(line.lineNum) : '') })
.join('\n')
@@ -653,6 +665,11 @@ function JSify(data, functionsOnly, givenFunctions) {
func.JS += ' return' + (func.returnType !== 'void' ? ' null' : '') + ';\n';
}
func.JS += '}\n';
+
+ if (PRINT_SPLIT_FILE_MARKER) {
+ func.JS += '\n//FUNCTION_END_MARKER_OF_SOURCE_FILE_' + associatedSourceFile + '\n';
+ }
+
if (func.ident in EXPORTED_FUNCTIONS) {
func.JS += 'Module["' + func.ident + '"] = ' + func.ident + ';';
}
diff --git a/src/modules.js b/src/modules.js
index 0f3b483b..cf1b072e 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -127,6 +127,11 @@ var Debugging = {
this.llvmLineToSourceFile[lineNum] + '"' : '';
},
+ getAssociatedSourceFile: function(lineNum) {
+ if (!this.on) return null;
+ return lineNum in this.llvmLineToSourceLine ? this.llvmLineToSourceFile[lineNum] : null;
+ },
+
getIdentifier: function(lineNum) {
if (!this.on) return null;
if (lineNum === undefined) {
diff --git a/src/settings.js b/src/settings.js
index f8a81711..9a1afd69 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -202,6 +202,8 @@ var SHELL_FILE = 0; // set this to a string to override the shell file used
var SHOW_LABELS = 0; // Show labels in the generated code
+var PRINT_SPLIT_FILE_MARKER = 0; // Prints markers in Javascript generation to split the file later on. See emcc --split option.
+
var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library, which
// must be loaded dynamically using dlopen().
// 0 here means this is not a shared lib: It is a main file.
diff --git a/tests/runner.py b/tests/runner.py
index 8d1f0674..a55b5ff5 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -7883,6 +7883,189 @@ elif 'browser' in str(sys.argv):
finally:
os.chdir(cwd)
+ def test_split(self):
+ # test HTML generation.
+ self.reftest(path_from_root('tests', 'htmltest.png'))
+ output = Popen(['python', EMCC, path_from_root('tests', 'hello_world_sdl.cpp'), '-o', 'something.js', '--split', '100']).communicate()
+ assert os.path.exists(os.path.join(self.get_dir(), 'something.js')), 'must be side js'
+ assert os.path.exists(os.path.join(self.get_dir(), 'something_functions.js')), 'must be side js'
+ assert os.path.exists(os.path.join(self.get_dir(), 'something.include.html')), 'must be side js'
+
+ open(os.path.join(self.get_dir(), 'something.html'), 'w').write('''
+
+ <!doctype html>
+ <html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Emscripten-Generated Code</title>
+ <style>
+ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+ canvas.emscripten { border: 1px solid black; }
+ textarea.emscripten { font-family: monospace; width: 80%; }
+ div.emscripten { text-align: center; }
+ </style>
+ </head>
+ <body>
+ <hr/>
+ <div class="emscripten" id="status">Downloading...</div>
+ <div class="emscripten">
+ <progress value="0" max="100" id="progress" hidden=1></progress>
+ </div>
+ <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
+ <hr/>
+ <div class="emscripten"><input type="button" value="fullscreen" onclick="Module.requestFullScreen()"></div>
+ <hr/>
+ <textarea class="emscripten" id="output" rows="8"></textarea>
+ <hr>
+ <script type='text/javascript'>
+ // connect to canvas
+ var Module = {
+ preRun: [],
+ postRun: [],
+ print: (function() {
+ var element = document.getElementById('output');
+ element.value = ''; // clear browser cache
+ return function(text) {
+ // These replacements are necessary if you render to raw HTML
+ //text = text.replace(/&/g, "&amp;");
+ //text = text.replace(/</g, "&lt;");
+ //text = text.replace(/>/g, "&gt;");
+ //text = text.replace('\\n', '<br>', 'g');
+ element.value += text + "\\n";
+ element.scrollTop = 99999; // focus on bottom
+ };
+ })(),
+ printErr: function(text) {
+ if (0) { // XXX disabled for safety typeof dump == 'function') {
+ dump(text + '\\n'); // fast, straight to the real console
+ } else {
+ console.log(text);
+ }
+ },
+ canvas: document.getElementById('canvas'),
+ setStatus: function(text) {
+ if (Module.setStatus.interval) clearInterval(Module.setStatus.interval);
+ var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+ var statusElement = document.getElementById('status');
+ var progressElement = document.getElementById('progress');
+ if (m) {
+ text = m[1];
+ progressElement.value = parseInt(m[2])*100;
+ progressElement.max = parseInt(m[4])*100;
+ progressElement.hidden = false;
+ } else {
+ progressElement.value = null;
+ progressElement.max = null;
+ progressElement.hidden = true;
+ }
+ statusElement.innerHTML = text;
+ },
+ totalDependencies: 0,
+ monitorRunDependencies: function(left) {
+ this.totalDependencies = Math.max(this.totalDependencies, left);
+ Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
+ }
+ };
+ Module.setStatus('Downloading...');
+ </script>''' + open(os.path.join(self.get_dir(), 'something.include.html')).read() + '''
+ </body>
+ </html>
+ ''')
+
+ self.run_browser('something.html', 'You should see "hello, world!" and a colored cube.', '/report_result?0')
+
+ def test_split_in_source_filenames(self):
+ self.reftest(path_from_root('tests', 'htmltest.png'))
+ output = Popen(['python', EMCC, path_from_root('tests', 'hello_world_sdl.cpp'), '-o', 'something.js', '-g', '--split', '100']).communicate()
+ assert os.path.exists(os.path.join(self.get_dir(), 'something.js')), 'must be side js'
+ assert os.path.exists(self.get_dir() + '/something/' + path_from_root('tests', 'hello_world_sdl.cpp.js')), 'must be side js'
+ assert os.path.exists(os.path.join(self.get_dir(), 'something.include.html')), 'must be side js'
+
+ open(os.path.join(self.get_dir(), 'something.html'), 'w').write('''
+
+ <!doctype html>
+ <html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Emscripten-Generated Code</title>
+ <style>
+ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+ canvas.emscripten { border: 1px solid black; }
+ textarea.emscripten { font-family: monospace; width: 80%; }
+ div.emscripten { text-align: center; }
+ </style>
+ </head>
+ <body>
+ <hr/>
+ <div class="emscripten" id="status">Downloading...</div>
+ <div class="emscripten">
+ <progress value="0" max="100" id="progress" hidden=1></progress>
+ </div>
+ <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
+ <hr/>
+ <div class="emscripten"><input type="button" value="fullscreen" onclick="Module.requestFullScreen()"></div>
+ <hr/>
+ <textarea class="emscripten" id="output" rows="8"></textarea>
+ <hr>
+ <script type='text/javascript'>
+ // connect to canvas
+ var Module = {
+ preRun: [],
+ postRun: [],
+ print: (function() {
+ var element = document.getElementById('output');
+ element.value = ''; // clear browser cache
+ return function(text) {
+ // These replacements are necessary if you render to raw HTML
+ //text = text.replace(/&/g, "&amp;");
+ //text = text.replace(/</g, "&lt;");
+ //text = text.replace(/>/g, "&gt;");
+ //text = text.replace('\\n', '<br>', 'g');
+ element.value += text + "\\n";
+ element.scrollTop = 99999; // focus on bottom
+ };
+ })(),
+ printErr: function(text) {
+ if (0) { // XXX disabled for safety typeof dump == 'function') {
+ dump(text + '\\n'); // fast, straight to the real console
+ } else {
+ console.log(text);
+ }
+ },
+ canvas: document.getElementById('canvas'),
+ setStatus: function(text) {
+ if (Module.setStatus.interval) clearInterval(Module.setStatus.interval);
+ var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+ var statusElement = document.getElementById('status');
+ var progressElement = document.getElementById('progress');
+ if (m) {
+ text = m[1];
+ progressElement.value = parseInt(m[2])*100;
+ progressElement.max = parseInt(m[4])*100;
+ progressElement.hidden = false;
+ } else {
+ progressElement.value = null;
+ progressElement.max = null;
+ progressElement.hidden = true;
+ }
+ statusElement.innerHTML = text;
+ },
+ totalDependencies: 0,
+ monitorRunDependencies: function(left) {
+ this.totalDependencies = Math.max(this.totalDependencies, left);
+ Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
+ }
+ };
+ Module.setStatus('Downloading...');
+ </script>''' + open(os.path.join(self.get_dir(), 'something.include.html')).read() + '''
+ </body>
+ </html>
+ ''')
+
+ self.run_browser('something.html', 'You should see "hello, world!" and a colored cube.', '/report_result?0')
+
def test_compression(self):
open(os.path.join(self.get_dir(), 'main.cpp'), 'w').write(self.with_report_result(r'''
#include <stdio.h>
diff --git a/tools/split.py b/tools/split.py
new file mode 100755
index 00000000..ab1e97ca
--- /dev/null
+++ b/tools/split.py
@@ -0,0 +1,94 @@
+import sys
+import os
+
+def split_javascript_file(input_filename, output_filename_prefix, max_part_size_in_bytes):
+
+ try:
+ # Contains the entire Emscripten generated Javascript code
+ input_file = open(input_filename,'r')
+
+ # Javascript main file. On execution, this file needs to be loaded at last (!)
+ output_main_filename = output_filename_prefix + ".js"
+ output_main_file = open(output_main_filename,'w')
+
+ # File with HTML script tags to load the Javascript files in HTML later on
+ output_html_include_file = open(output_filename_prefix + ".include.html",'w')
+
+ # Variable will contain the source of a Javascript function if we find one during parsing
+ js_function = None
+
+ # Dictionary with source file as key and an array of functions associated to that source file as value
+ function_buckets = {};
+
+ output_part_file = None
+
+ # Iterate over Javascript source; write main file; parse function declarations.
+ for line in input_file:
+ if line == "//FUNCTION_BEGIN_MARKER\n":
+ js_function = "//Func\n"
+ elif line.startswith("//FUNCTION_END_MARKER_OF_SOURCE_FILE_"):
+ # At the end of the function marker we get the source file that is associated to that function.
+ associated_source_file_base = line[len("//FUNCTION_END_MARKER_OF_SOURCE_FILE_"):len(line)-1]
+
+ if associated_source_file_base == "NO_SOURCE":
+ # Functions without associated source file are stored in a file in the base directory
+ associated_source_file_base = output_filename_prefix + "_functions";
+ else:
+ # Functions with a known associated source file are stored in a file in the directory `output_filename_prefix`
+ associated_source_file_base = output_filename_prefix + os.path.realpath(associated_source_file_base)
+
+ # Add the function to its respective file
+ if associated_source_file_base not in function_buckets:
+ function_buckets[associated_source_file_base] = []
+ function_buckets[associated_source_file_base] += [js_function]
+
+ # Clear the function read cache
+ js_function = None
+ else:
+ if js_function is None:
+ output_main_file.write(line)
+ else:
+ js_function += line
+
+ # Iterate over all function buckets and write their functions to the associated files
+ # An associated file is split into chunks of `max_part_size_in_bytes`
+ for associated_source_file_base in function_buckets:
+ # At first we try to name the Javascript source file to match the assoicated source file + `.js`
+ js_source_file = associated_source_file_base + ".js"
+
+ # Check if the directory of the Javascript source file exists
+ js_source_dir = os.path.dirname(js_source_file)
+ if len(js_source_dir) > 0 and not os.path.exists(js_source_dir):
+ os.makedirs(js_source_dir)
+
+ output_part_file_counter = 0
+ output_part_file = None
+ for js_function in function_buckets[associated_source_file_base]:
+ if output_part_file is None:
+ output_html_include_file.write("<script type=\"text/javascript\" src=\"" + js_source_file + "\"></script>")
+ output_part_file = open(js_source_file,'w')
+
+ output_part_file.write(js_function)
+
+ if output_part_file is not None and output_part_file.tell() > max_part_size_in_bytes:
+ output_part_file.close()
+ output_part_file = None
+ output_part_file_counter += 1
+ js_source_file = associated_source_file_base + ".part" + str(output_part_file_counter) + ".js"
+
+ if output_part_file is not None:
+ output_part_file.close()
+ output_part_file = None
+
+ # Write the main Javascript file at last to the HTML includes because this file contains the code to start
+ # the execution of the generated Emscripten application and requires all the extracted functions.
+ output_html_include_file.write("<script type=\"text/javascript\" src=\"" + output_main_filename + "\"></script>")
+
+ except Exception, e:
+ print >> sys.stderr, 'error: Splitting of Emscripten generated Javascript failed: %s' % str(e)
+
+ finally:
+ if input_file is not None: input_file.close()
+ if output_main_file is not None: output_main_file.close()
+ if output_part_file is not None: output_part_file.close()
+ if output_html_include_file is not None: output_html_include_file.close() \ No newline at end of file