aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--cmake/Platform/Emscripten.cmake8
-rwxr-xr-xemcc3
-rw-r--r--src/deps_info.json1
-rw-r--r--src/library.js1
-rw-r--r--src/library_browser.js33
-rw-r--r--src/library_glfw.js7
-rw-r--r--src/library_sdl.js2
-rw-r--r--src/preamble.js2
-rw-r--r--system/include/emscripten/emscripten.h24
-rw-r--r--tests/cmake/target_library/CMakeLists.txt43
-rw-r--r--tests/cmake/target_library/srcfile.cmake6
-rw-r--r--tests/core/test_simd4.in39
-rw-r--r--tests/core/test_simd4.out1
-rw-r--r--tests/glfw.c4
-rw-r--r--tests/sdl_image.c10
-rw-r--r--tests/test_core.py9
-rw-r--r--tests/test_other.py16
-rwxr-xr-xtools/ffdb.py276
-rw-r--r--tools/js-optimizer.js42
-rw-r--r--tools/shared.py7
-rw-r--r--tools/system_libs.py8
-rw-r--r--tools/test-js-optimizer-asm-last-output.js26
-rw-r--r--tools/test-js-optimizer-asm-last.js32
24 files changed, 548 insertions, 53 deletions
diff --git a/AUTHORS b/AUTHORS
index 8f38c6fb..d356a05b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -143,4 +143,5 @@ a license to everyone to use it as detailed in LICENSE.)
* Ryan Sturgell <ryan.sturgell@gmail.com> (copyright owned by Google, Inc.)
* Jason Green <jason@transgaming.com> (copyright owned by TransGaming, Inc.)
* Ningxin Hu <ningxin.hu@intel.com> (copyright owned by Intel)
+* Nicolas Guillemot <nlguillemot@gmail.com>
diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake
index f9d8c773..362a552d 100644
--- a/cmake/Platform/Emscripten.cmake
+++ b/cmake/Platform/Emscripten.cmake
@@ -98,6 +98,8 @@ set(CMAKE_SYSTEM_INCLUDE_PATH "${EMSCRIPTEN_ROOT_PATH}/system/include")
#SET(CMAKE_FIND_LIBRARY_PREFIXES "")
#SET(CMAKE_FIND_LIBRARY_SUFFIXES ".bc")
+SET(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES 1)
+SET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES 1)
SET(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1)
SET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1)
SET(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1)
@@ -107,10 +109,8 @@ set(CMAKE_C_RESPONSE_FILE_LINK_FLAG "@")
set(CMAKE_CXX_RESPONSE_FILE_LINK_FLAG "@")
# Specify the program to use when building static libraries. Force Emscripten-related command line options to clang.
-set(CMAKE_CXX_ARCHIVE_CREATE "${CMAKE_AR} rc <TARGET> ${CMAKE_START_TEMP_FILE} <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}")
-set(CMAKE_C_ARCHIVE_CREATE "${CMAKE_AR} rc <TARGET> ${CMAKE_START_TEMP_FILE} <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}")
-set(CMAKE_CXX_ARCHIVE_APPEND "${CMAKE_AR} r <TARGET> ${CMAKE_START_TEMP_FILE} <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}")
-set(CMAKE_C_ARCHIVE_APPEND "${CMAKE_AR} r <TARGET> ${CMAKE_START_TEMP_FILE} <LINK_FLAGS> <OBJECTS>${CMAKE_END_TEMP_FILE}")
+set(CMAKE_C_CREATE_STATIC_LIBRARY "<CMAKE_AR> rc <TARGET> <LINK_FLAGS> <OBJECTS>")
+set(CMAKE_CXX_CREATE_STATIC_LIBRARY "<CMAKE_AR> rc <TARGET> <LINK_FLAGS> <OBJECTS>")
# Set a global EMSCRIPTEN variable that can be used in client CMakeLists.txt to detect when building using Emscripten.
set(EMSCRIPTEN 1 CACHE BOOL "If true, we are targeting Emscripten output.")
diff --git a/emcc b/emcc
index 37a17ab1..0dfa5d86 100755
--- a/emcc
+++ b/emcc
@@ -1347,6 +1347,7 @@ try:
if shared.Settings.MAIN_MODULE:
assert not shared.Settings.SIDE_MODULE
shared.Settings.INCLUDE_FULL_LIBRARY = 1
+ shared.Settings.EXPORT_ALL = 1
elif shared.Settings.SIDE_MODULE:
assert not shared.Settings.MAIN_MODULE
@@ -1375,6 +1376,8 @@ try:
if shared.Settings.ASM_JS and shared.Settings.DLOPEN_SUPPORT:
assert shared.Settings.DISABLE_EXCEPTION_CATCHING, 'no exceptions support with dlopen in asm yet'
+ assert not (bind and shared.Settings.NO_DYNAMIC_EXECUTION), 'NO_DYNAMIC_EXECUTION disallows embind'
+
if proxy_to_worker:
shared.Settings.PROXY_TO_WORKER = 1
diff --git a/src/deps_info.json b/src/deps_info.json
index 029a20e1..e0983064 100644
--- a/src/deps_info.json
+++ b/src/deps_info.json
@@ -3,6 +3,7 @@
"SDL_Init": ["malloc", "free"],
"SDL_GL_GetProcAddress": ["emscripten_GetProcAddress"],
"eglGetProcAddress": ["emscripten_GetProcAddress"],
+ "glfwGetProcAddress": ["emscripten_GetProcAddress"],
"emscripten_GetProcAddress": ["strstr"]
}
diff --git a/src/library.js b/src/library.js
index 120def05..c17952b3 100644
--- a/src/library.js
+++ b/src/library.js
@@ -3353,6 +3353,7 @@ LibraryManager.library = {
return 0;
} else {
var size = Math.min(4095, absolute.path.length); // PATH_MAX - 1.
+ if (resolved_name === 0) resolved_name = _malloc(size+1);
for (var i = 0; i < size; i++) {
{{{ makeSetValue('resolved_name', 'i', 'absolute.path.charCodeAt(i)', 'i8') }}};
}
diff --git a/src/library_browser.js b/src/library_browser.js
index 4ef7c577..57ca5a24 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -1175,6 +1175,39 @@ mergeInto(LibraryManager.library, {
var info = Browser.workers[id];
if (!info) return -1;
return info.awaited;
+ },
+
+ emscripten_get_preloaded_image_data: function(path, w, h) {
+ if (typeof path === "number") {
+ path = Pointer_stringify(path);
+ }
+
+ path = PATH.resolve(path);
+
+ var canvas = Module["preloadedImages"][path];
+ if (canvas) {
+ var ctx = canvas.getContext("2d");
+ var image = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ var buf = _malloc(canvas.width * canvas.height * 4);
+
+ HEAPU8.set(image.data, buf);
+
+ {{{ makeSetValue('w', '0', 'canvas.width', 'i32') }}};
+ {{{ makeSetValue('h', '0', 'canvas.height', 'i32') }}};
+ return buf;
+ }
+
+ return 0;
+ },
+
+ emscripten_get_preloaded_image_data_from_FILE__deps: ['emscripten_get_preloaded_image_data'],
+ emscripten_get_preloaded_image_data_from_FILE: function(file, w, h) {
+ var stream = FS.getStreamFromPtr(file);
+ if (stream) {
+ return _emscripten_get_preloaded_image_data(stream.path, w, h);
+ }
+
+ return 0;
}
});
diff --git a/src/library_glfw.js b/src/library_glfw.js
index 6d539326..6dfea101 100644
--- a/src/library_glfw.js
+++ b/src/library_glfw.js
@@ -417,6 +417,9 @@ var LibraryGLFW = {
glfwSetWindowSizeCallback: function(cbfun) {
GLFW.resizeFunc = cbfun;
+ if (GLFW.resizeFunc) {
+ Runtime.dynCall('vii', GLFW.resizeFunc, [Module['canvas'].width, Module['canvas'].height]);
+ }
},
glfwSetWindowCloseCallback: function(cbfun) {
@@ -507,9 +510,9 @@ var LibraryGLFW = {
return Module.ctx.getSupportedExtensions().indexOf(Pointer_stringify(extension)) > -1;
},
- glfwGetProcAddress__deps: ['glfwGetProcAddress'],
+ glfwGetProcAddress__deps: ['emscripten_GetProcAddress'],
glfwGetProcAddress: function(procname) {
- return _getProcAddress(procname);
+ return _emscripten_GetProcAddress(procname);
},
glfwGetGLVersion: function(major, minor, rev) {
diff --git a/src/library_sdl.js b/src/library_sdl.js
index a01b3c6c..7145a7ba 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -2601,7 +2601,7 @@ var LibrarySDL = {
if (info && info.audio) {
info.audio.pause();
} else {
- Module.printErr('Mix_Pause: no sound found for channel: ' + channel);
+ //Module.printErr('Mix_Pause: no sound found for channel: ' + channel);
}
},
diff --git a/src/preamble.js b/src/preamble.js
index 431a3c27..ae58e7e0 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -401,7 +401,7 @@ var cwrap, ccall;
return ret;
}
- var sourceRegex = /^function\s\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;
+ var sourceRegex = /^function\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;
function parseJSFunc(jsfunc) {
// Match the body and the return value of a javascript function source
var parsed = jsfunc.toString().match(sourceRegex).slice(1);
diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h
index 8a08aabb..b495c0f5 100644
--- a/system/include/emscripten/emscripten.h
+++ b/system/include/emscripten/emscripten.h
@@ -14,6 +14,8 @@
extern "C" {
#endif
+#include <stdio.h>
+
#if !__EMSCRIPTEN__
#include <SDL/SDL.h> /* for SDL_Delay in async_call */
#endif
@@ -53,8 +55,8 @@ typedef double __attribute__((aligned(1))) emscripten_align1_double;
* does a function call to reach it). It supports newlines,
*
* EM_ASM(
- * window.alert('hai'));
- * window.alert('bai'));
+ * window.alert('hai');
+ * window.alert('bai');
* )
*
* Notes: Double-quotes (") are not supported, but you can use
@@ -515,6 +517,24 @@ int emscripten_get_compiler_setting(const char *name);
*/
void emscripten_debugger();
+/*
+ * Get preloaded image data and the size of the image.
+ *
+ * Returns pointer to loaded image or NULL.
+ * width/height of image are written to w/h if data is valid.
+ * Pointer should be free()'d
+ */
+char *emscripten_get_preloaded_image_data(const char *path, int *w, int *h);
+
+/*
+ * Get preloaded image data from a c FILE *.
+ *
+ * Returns pointer to loaded image or NULL.
+ * width/height of image are written to w/h if data is valid.
+ * Pointer should be free()'d
+ */
+char *emscripten_get_preloaded_image_data_from_FILE(FILE *file, int *w, int *h);
+
/* ===================================== */
/* Internal APIs. Be careful with these. */
diff --git a/tests/cmake/target_library/CMakeLists.txt b/tests/cmake/target_library/CMakeLists.txt
new file mode 100644
index 00000000..c7023192
--- /dev/null
+++ b/tests/cmake/target_library/CMakeLists.txt
@@ -0,0 +1,43 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(test_cmake)
+
+option(BUILD_SHARED_LIBS "Build with shared libraries." OFF)
+
+if (CMAKE_BUILD_TYPE STREQUAL Debug)
+ SET(linkFlags "-g4")
+else() # Either MinSizeRel, RelWithDebInfo or Release, all which run with optimizations enabled.
+ SET(linkFlags "-O2")
+endif()
+
+set(MAX_SRC_FILE_INDEX 30)
+SET(TEST_SRC_FILE_BASE_NAME "this_is_a_test_src_file_with_a_quite_lengthy_name_to_simulate_very_long_command_line_length_problems_on_windows_")
+
+
+foreach(i RANGE ${MAX_SRC_FILE_INDEX})
+ set (TEST_FUNCTION_NAME "FooBar_${i}")
+ configure_file("srcfile.cmake" "${TEST_SRC_FILE_BASE_NAME}${i}.c")
+ configure_file("srcfile.cmake" "${TEST_SRC_FILE_BASE_NAME}${i}.cpp")
+ list(APPEND TEST_SOURCES "${TEST_SRC_FILE_BASE_NAME}${i}.c" "${TEST_SRC_FILE_BASE_NAME}${i}.cpp")
+endforeach()
+
+add_library(test_cmake ${TEST_SOURCES})
+
+if (WIN32)
+ message(FATAL_ERROR "WIN32 should not be defined when cross-compiling!")
+endif()
+
+if (APPLE)
+ message(FATAL_ERROR "APPLE should not be defined when cross-compiling!")
+endif()
+
+if (NOT EMSCRIPTEN)
+ message(FATAL_ERROR "EMSCRIPTEN should be defined when cross-compiling!")
+endif()
+
+if (NOT CMAKE_C_SIZEOF_DATA_PTR)
+ message(FATAL_ERROR "CMAKE_C_SIZEOF_DATA_PTR was not defined!")
+endif()
+
+# GOTCHA: If your project has custom link flags, these must be set *before* calling any of the em_link_xxx functions!
+set_target_properties(test_cmake PROPERTIES LINK_FLAGS "${linkFlags}")
diff --git a/tests/cmake/target_library/srcfile.cmake b/tests/cmake/target_library/srcfile.cmake
new file mode 100644
index 00000000..10e9e6f8
--- /dev/null
+++ b/tests/cmake/target_library/srcfile.cmake
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+void @TEST_FUNCTION_NAME@()
+{
+ printf("@TEST_FUNCTION_NAME@");
+}
diff --git a/tests/core/test_simd4.in b/tests/core/test_simd4.in
new file mode 100644
index 00000000..b597d8a3
--- /dev/null
+++ b/tests/core/test_simd4.in
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <xmmintrin.h>
+
+static __inline__ __m128 __attribute__((__always_inline__))
+_mm_load_ps(const float *__p)
+{
+ return *(__m128*)__p;
+}
+
+float simdAverage(float *src, int len) {
+ __m128 sumx4 = _mm_setzero_ps();
+ for (int i = 0; i < len; i += 4) {
+ __m128 v = _mm_load_ps(src);
+ sumx4 = _mm_add_ps(sumx4, v);
+ src += 4;
+ }
+ float sumx4_mem[4];
+ float *sumx4_ptr = sumx4_mem;
+ _mm_store_ps(sumx4_ptr, sumx4);
+ return (sumx4_mem[0] + sumx4_mem[1] +
+ sumx4_mem[2] + sumx4_mem[3])/len;
+}
+
+void initArray(float *src, int len) {
+ for (int i = 0; i < len; ++i) {
+ src[i] = 0.1 * i;
+ }
+}
+
+int main() {
+ const int len = 100000;
+ float src[len];
+ float result = 0.0;
+
+ initArray(src, len);
+
+ result = simdAverage(src, len);
+ printf("averagex4 result: %.1f\n", result);
+} \ No newline at end of file
diff --git a/tests/core/test_simd4.out b/tests/core/test_simd4.out
new file mode 100644
index 00000000..99449772
--- /dev/null
+++ b/tests/core/test_simd4.out
@@ -0,0 +1 @@
+averagex4 result: 4999.9 \ No newline at end of file
diff --git a/tests/glfw.c b/tests/glfw.c
index 79199d9a..cbdc81fe 100644
--- a/tests/glfw.c
+++ b/tests/glfw.c
@@ -376,6 +376,10 @@ void PullInfo(){
extension = "GL_EXT_framebuffer_object";
printf("'%s' extension is %s.\n", extension, glfwExtensionSupported(extension) ? "supported" : "not supported");
+ extension = "glBindBuffer";
+ void* proc_addr = glfwGetProcAddress(extension);
+ printf("'%s' extension proc address is %p.\n", extension, proc_addr);
+
printf("Sleeping 1 sec...\n");
glfwSleep(1);
printf("...Done.\n");
diff --git a/tests/sdl_image.c b/tests/sdl_image.c
index 523f8903..9639ea37 100644
--- a/tests/sdl_image.c
+++ b/tests/sdl_image.c
@@ -4,6 +4,7 @@
#include <assert.h>
#include <emscripten.h>
#include <unistd.h>
+#include <stdlib.h>
int testImage(SDL_Surface* screen, const char* fileName) {
SDL_Surface *image = IMG_Load(fileName);
@@ -18,7 +19,16 @@ int testImage(SDL_Surface* screen, const char* fileName) {
int result = image->w;
SDL_BlitSurface (image, NULL, screen, NULL);
+
+ int w, h;
+ char *data = emscripten_get_preloaded_image_data(fileName, &w, &h);
+
+ assert(data);
+ assert(w == image->w);
+ assert(h == image->h);
+
SDL_FreeSurface (image);
+ free(data);
return result;
}
diff --git a/tests/test_core.py b/tests/test_core.py
index bcb03830..b9057f4e 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -4892,6 +4892,15 @@ return malloc(size);
self.do_run_from_file(src, output)
+ def test_simd4(self):
+ # test_simd4 is to test phi node handling of SIMD path
+ if Settings.ASM_JS: Settings.ASM_JS = 2 # does not validate
+
+ test_path = path_from_root('tests', 'core', 'test_simd4')
+ src, output = (test_path + s for s in ('.in', '.out'))
+
+ self.do_run_from_file(src, output)
+
def test_gcc_unmangler(self):
if os.environ.get('EMCC_FAST_COMPILER') == '0': Settings.NAMED_GLOBALS = 1 # test coverage for this; fastcomp never names globals
diff --git a/tests/test_other.py b/tests/test_other.py
index 665832ee..39796b71 100644
--- a/tests/test_other.py
+++ b/tests/test_other.py
@@ -207,7 +207,8 @@ Options that are modified or new in %s include:
(['-O2'], lambda generated: '// The Module object' not in generated, 'with opts, no comments in shell code'),
(['-O2', '-g2'], lambda generated: '// The Module object' not in generated, 'with -g2, no comments in shell code'),
(['-O2', '-g3'], lambda generated: '// The Module object' in generated, 'with -g3, yes comments in shell code'),
- (['-O2', '-profiling'], lambda generated: '// The Module object' in generated, 'with -profiling, yes comments in shell code'),
+ (['-O2', '-profiling'], lambda generated: '// The Module object' in generated or os.environ.get('EMCC_FAST_COMPILER') == '0', 'with -profiling, yes comments in shell code (in fastcomp)'),
+
]:
print params, text
self.clear()
@@ -354,9 +355,10 @@ f.close()
except KeyError:
postbuild = None
- cmake_cases = ['target_js', 'target_html']
- cmake_outputs = ['test_cmake.js', 'hello_world_gles.html']
- for i in range(0, 2):
+ cmake_cases = ['target_js', 'target_html', 'target_library', 'target_library']
+ cmake_outputs = ['test_cmake.js', 'hello_world_gles.html', 'libtest_cmake.a', 'libtest_cmake.so']
+ cmake_arguments = ['', '', '-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=ON']
+ for i in range(0, len(cmake_cases)):
for configuration in ['Debug', 'Release']:
# CMake can be invoked in two ways, using 'emconfigure cmake', or by directly running 'cmake'.
# Test both methods.
@@ -374,11 +376,11 @@ f.close()
if invoke_method == 'cmake':
# Test invoking cmake directly.
cmd = ['cmake', '-DCMAKE_TOOLCHAIN_FILE='+path_from_root('cmake', 'Platform', 'Emscripten.cmake'),
- '-DCMAKE_BUILD_TYPE=' + configuration, '-G', generator, cmakelistsdir]
+ '-DCMAKE_BUILD_TYPE=' + configuration, cmake_arguments[i], '-G', generator, cmakelistsdir]
else:
# Test invoking via 'emconfigure cmake'
- cmd = [emconfigure, 'cmake', '-DCMAKE_BUILD_TYPE=' + configuration, '-G', generator, cmakelistsdir]
-
+ cmd = [emconfigure, 'cmake', '-DCMAKE_BUILD_TYPE=' + configuration, cmake_arguments[i], '-G', generator, cmakelistsdir]
+
ret = Popen(cmd, stdout=None if verbose_level >= 2 else PIPE, stderr=None if verbose_level >= 1 else PIPE).communicate()
if len(ret) > 1 and ret[1] != None and len(ret[1].strip()) > 0:
logging.error(ret[1]) # If there were any errors, print them directly to console for diagnostics.
diff --git a/tools/ffdb.py b/tools/ffdb.py
index c22fd9db..497f0162 100755
--- a/tools/ffdb.py
+++ b/tools/ffdb.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python
-import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess
+import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess, re, base64, struct, imghdr
+ADB = 'adb' # Path to the adb executable
LOG_VERBOSE = False # Verbose printing enabled with --verbose
HOST = 'localhost' # The remote host to connect to the B2G device
PORT = 6000 # The port on the host on which the B2G device listens on
@@ -18,7 +19,13 @@ def sizeof_fmt(num):
return "%3.1f%s" % (num, 'TB')
def zipdir(path, zipfilename):
- zipf = zipfile.ZipFile(zipfilename, 'w')
+ try:
+ import zlib
+ zip_mode = zipfile.ZIP_DEFLATED
+ except:
+ zip_mode = zipfile.ZIP_STORED
+
+ zipf = zipfile.ZipFile(zipfilename, 'w', zip_mode)
files_to_compress = []
for root, dirs, files in os.walk(path):
for file in files:
@@ -29,9 +36,10 @@ def zipdir(path, zipfilename):
(root, file) = tuple
filename = os.path.join(root, file)
filesize = os.path.getsize(filename)
- print 'Compressing ' + str(n) + '/' + str(len(files_to_compress)) + ': "' + os.path.relpath(filename, path) + '" (' + sizeof_fmt(filesize) + ')...'
+ path_in_archive = os.path.relpath(filename, path)
+ print 'Compressing ' + str(n) + '/' + str(len(files_to_compress)) + ': "' + path_in_archive + '" (' + sizeof_fmt(filesize) + ')...'
n += 1
- zipf.write(os.path.join(root, file))
+ zipf.write(os.path.join(root, file), path_in_archive)
zipf.close()
print 'Done. '
@@ -46,16 +54,27 @@ def format_html(msg):
# Prints a verbose log message to stdout channel. Only shown if run with --verbose.
def logv(msg):
if LOG_VERBOSE:
- sys.stdout.write(format_html(msg))
+ sys.stdout.write(format_html(msg) + '\n')
sys.stdout.flush()
# Reads data from the socket, and tries to parse what we have got so far as a JSON message.
# The messages are of form "bytelength:{jsondict}", where bytelength tells how many bytes
# there are in the data that comes after the colon.
# Returns a JSON dictionary of the received message.
-def read_b2g_response():
+def read_b2g_response(print_errors_to_console = True):
global read_queue, b2g_socket
- read_queue += b2g_socket.recv(65536*2)
+ try:
+ read_queue += b2g_socket.recv(65536*2)
+ except KeyboardInterrupt:
+ print ' Aborted by user'
+ sys.exit(1)
+ except Exception, e:
+ if e[0] == 57: # Socket is not connected
+ print 'Error! Failed to receive data from the device: socket is not connected!'
+ sys.exit(1)
+ else:
+ raise
+ payload = ''
while ':' in read_queue:
semicolon = read_queue.index(':')
payload_len = int(read_queue[:semicolon])
@@ -66,10 +85,13 @@ def read_b2g_response():
read_queue = read_queue[semicolon+1+payload_len:]
logv('Read a message of size ' + str(payload_len) + 'b from socket.')
payload = json.loads(payload)
+ # Log received errors immediately to console
+ if print_errors_to_console and 'error' in payload:
+ print >> sys.stderr, 'Received error "' + payload['error'] + '"! Reason: ' + payload['message']
return payload
# Sends a command to the B2G device and waits for the response and returns it as a JSON dict.
-def send_b2g_cmd(to, cmd, data = {}):
+def send_b2g_cmd(to, cmd, data = {}, print_errors_to_console = True):
global b2g_socket
msg = { 'to': to, 'type': cmd}
msg = dict(msg.items() + data.items())
@@ -78,7 +100,7 @@ def send_b2g_cmd(to, cmd, data = {}):
msg = str(len(msg))+':'+msg
logv('Sending cmd:' + cmd + ' to:' + to)
b2g_socket.sendall(msg)
- return read_b2g_response()
+ return read_b2g_response(print_errors_to_console)
def escape_bytes(b):
return str(b)
@@ -102,7 +124,16 @@ def send_b2g_data_chunk(to, data_blob):
i += 1
message = '{"to":"'+to+'","type":"chunk","chunk":"' + ''.join(byte_str) + '"}'
message = str(len(message)) + ':' + message
+ logv('{"to":"'+to+'","type":"chunk","chunk":"<data>"}')
b2g_socket.sendall(message)
+ return read_b2g_response()
+
+def send_b2g_bulk_data(to, data_blob):
+ message = 'bulk ' + to + ' stream ' + str(len(data_blob)) + ':'
+ logv(message)
+ b2g_socket.sendall(message)
+ b2g_socket.sendall(data_blob)
+ # It seems that B2G doesn't send any response JSON back after a bulk transfer is finished, so no read_b2g_response() here.
# Queries the device for a list of all installed apps.
def b2g_get_appslist():
@@ -130,8 +161,60 @@ def print_applist(applist, running_app_manifests, print_removable):
num_printed += 1
return num_printed
+def adb_devices():
+ try:
+ devices = subprocess.check_output([ADB, 'devices'])
+ devices = devices.strip().split('\n')[1:]
+ devices = map(lambda x: x.strip().split('\t'), devices)
+ return devices
+ except Exception, e:
+ return []
+
+def b2g_get_prefs_filename():
+ return subprocess.check_output([ADB, 'shell', 'echo', '-n', '/data/b2g/mozilla/*.default/prefs.js'])
+
+def b2g_get_prefs_data():
+ return subprocess.check_output([ADB, 'shell', 'cat', '/data/b2g/mozilla/*.default/prefs.js'])
+
+def b2g_get_pref(sub):
+ prefs_data = b2g_get_prefs_data().split('\n')
+ # Filter to find all prefs that have the substring 'sub' in them.
+ r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)')
+ for line in prefs_data:
+ m = r.match(line)
+ if m and (sub is None or sub in m.group(1)):
+ print m.group(1) + ': ' + m.group(2).strip()
+
+def b2g_set_pref(pref, value):
+ prefs_data = b2g_get_prefs_data().split('\n')
+ # Remove any old value of this pref.
+ r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)')
+ new_prefs_data = []
+ for line in prefs_data:
+ m = r.match(line)
+ if not m or m.group(1) != pref:
+ new_prefs_data += [line]
+
+ if value != None:
+ print 'Setting pref "' + pref + '" = ' + value
+ new_prefs_data += ['user_pref("' + pref + '", ' + value + ');']
+ else:
+ print 'Unsetting pref "' + pref + '"'
+ (oshandle, tempfilename) = tempfile.mkstemp(suffix='.js', prefix='ffdb_temp_')
+ os.write(oshandle, '\n'.join(new_prefs_data));
+
+ # Write the new pref
+ subprocess.check_output([ADB, 'shell', 'stop', 'b2g'])
+ subprocess.check_output([ADB, 'push', tempfilename, b2g_get_prefs_filename()])
+ subprocess.check_output([ADB, 'shell', 'start', 'b2g'])
+ print 'Rebooting phone...'
+
+ def delete_temp_file():
+ os.remove(tempfilename)
+ atexit.register(delete_temp_file)
+
def main():
- global b2g_socket, webappsActorName
+ global b2g_socket, webappsActorName, HOST, PORT, VERBOSE, ADB
if len(sys.argv) < 2 or '--help' in sys.argv or 'help' in sys.argv or '-v' in sys.argv:
print '''Firefox OS Debug Bridge, a tool for automating FFOS device tasks from the command line.
@@ -148,22 +231,74 @@ def main():
log <app> [--clear]: Starts a persistent log listener that reads web console messages from the given application.
If --clear is passed, the message log for that application is cleared instead.
navigate <url>: Opens the given web page in the B2G browser.
+ screenshot [filename.png]: Takes a screenshot of the current contents displayed on the device. If an optional
+ filename is specified, the screenshot is saved to that file. Otherwise the filename
+ will be autogenerated.
+ get <pref>: Fetches the value of the given developer pref option from the FFOS device and prints it to console.
+ set <pref> <value>: Writes the given pref option to the FFOS device and restarts the B2G process on it for the change to take effect.
+ unset <pref>: Removes the given pref option from the FFOS device and restarts the B2G process on it for the change to take effect.
+
+ hide-prompt: Permanently removes the remote debugging connection dialog from showing up, and reboots the phone. This command is
+ provided for conveniency, and is the same as calling './ffdb.py set devtools.debugger.prompt-connection false'
+ restore-prompt: Restores the remote debugging connection dialog prompt to its default state.
+
+ Options: Additionally, the following options may be passed to control FFDB execution:
+
+ --host <hostname>: Specifies the target network address to connect to. Default: 'localhost'.
+ --port <number>: Specifies the network port to connect to. Default: 6000.
+ --verbose: Enables verbose printing, mostly useful for debugging.
+ --simulator: Signal that we will be connecting to a FFOS simulator and not a real device.
In the above, whenever a command requires an <app> to be specified, either the human-readable name,
localId or manifestURL of the application can be used.'''
sys.exit(0)
+ connect_to_simulator = False
+
+ options_with_value = ['--host', '--port']
+ options = options_with_value + ['--verbose', '--simulator']
+ # Process options
+ for i in range(0, len(sys.argv)):
+ if sys.argv[i] in options_with_value:
+ if i+1 >= sys.argv or sys.argv[i+1].startswith('-'):
+ print >> sys.stderr, "Missing value for option " + sys.argv[i] +'!'
+ sys.exit(1)
+ if sys.argv[i] == '--host':
+ HOST = sys.argv[i+1]
+ elif sys.argv[i] == '--port':
+ PORT = int(sys.argv[i+1])
+ elif sys.argv[i] == '--verbose':
+ VERBOSE = True
+ elif sys.argv[i] == '--simulator':
+ connect_to_simulator = True
+
+ # Clear the processed options so that parsing the commands below won't trip up on these.
+ if sys.argv[i] in options: sys.argv[i] = ''
+ if sys.argv[i] in options_with_value: sys.argv[i+1] = ''
+
+ sys.argv = filter(lambda x: len(x) > 0, sys.argv)
+
+ # Double-check that the device is found via adb:
+ if (HOST == 'localhost' or HOST == '127.0.0.1') and not connect_to_simulator:
+ devices = adb_devices()
+ if len(devices) == 0:
+ print 'Error! Failed to connect to B2G device debugger socket at address ' + HOST + ':' + str(PORT) + ' and no devices were detected via adb. Please double-check the following and try again: '
+ print ' 1) The device is powered on and connected to the computer with an USB cable.'
+ print ' 2) ADB and DevTools debugging is enabled on the device. (Settings -> Developer -> Debugging via USB: "ADB and DevTools"'
+ print ' 3) The device is listed when you run "adb devices" on the command line.'
+ print ' 4) When launching ffdb, remember to acknowledge the "incoming debug connection" dialog if it pops up on the device.'
+ sys.exit(1)
b2g_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
b2g_socket.connect((HOST, PORT))
except Exception, e:
if e[0] == 61: # Connection refused
- if HOST == 'localhost' or HOST == '127.0.0.1':
- cmd = ['adb', 'forward', 'tcp:'+str(PORT), 'localfilesystem:/data/local/debugger-socket']
+ if (HOST == 'localhost' or HOST == '127.0.0.1') and not connect_to_simulator:
+ cmd = [ADB, 'forward', 'tcp:'+str(PORT), 'localfilesystem:/data/local/debugger-socket']
print 'Connection to ' + HOST + ':' + str(PORT) + ' refused, attempting to forward device debugger-socket to local address by calling ' + str(cmd) + ':'
else:
- print 'Error! Failed to connect to B2G device debugger socket at address ' + HOST + ':' + str(PORT) + '!'
+ print 'Error! Failed to connect to B2G ' + ('simulator' if connect_to_simulator else 'device') + ' debugger socket at address ' + HOST + ':' + str(PORT) + '!'
sys.exit(1)
try:
retcode = subprocess.check_call(cmd)
@@ -187,6 +322,9 @@ def main():
logv('Connected. Handshake: ' + str(handshake))
data = send_b2g_cmd('root', 'listTabs')
+ if not 'deviceActor' in data:
+ print 'Error! Debugging connection was not available. Make sure that the "Remote debugging" developer option on the device is set to "ADB and Devtools".'
+ sys.exit(1)
deviceActorName = data['deviceActor']
logv('deviceActor: ' + deviceActorName)
webappsActorName = data['webappsActor']
@@ -241,28 +379,35 @@ def main():
print 'Uploading application package "' + target_app_path + '"...'
print 'Size of compressed package: ' + sizeof_fmt(os.path.getsize(target_app_path)) + '.'
- uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage')
- packageUploadActor = uploadResponse['actor']
app_file = open(target_app_path, 'rb')
data = app_file.read()
file_size = len(data)
- chunk_size = 4*1024*1024
- i = 0
+
+ uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage', { 'bulk': 'true'}, print_errors_to_console = False) # This may fail if on old device.
start_time = time.time()
- while i < file_size:
- chunk = data[i:i+chunk_size]
-
- send_b2g_data_chunk(packageUploadActor, chunk)
- i += chunk_size
- bytes_uploaded = min(i, file_size)
- cur_time = time.time()
- secs_elapsed = cur_time - start_time
- percentage_done = bytes_uploaded * 1.0 / file_size
- total_time = secs_elapsed / percentage_done
- time_left = total_time - secs_elapsed
- print sizeof_fmt(bytes_uploaded) + " uploaded, {:5.1f} %