diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-11-13 13:32:41 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-11-13 13:32:41 -0800 |
commit | 7453def33e4df4225453da2d0407658b3a711cda (patch) | |
tree | 1ac4d0adc9bcb3e3b427ce0bd13f4a9dd74e98ae | |
parent | 36308403a7bb76edd288e1557dee4acb8f1e8d7b (diff) | |
parent | 075317782e3195382741d0bc22ff314235cfeeda (diff) |
Merge pull request #703 from juj/cmake
Cmake support.
-rw-r--r-- | cmake/Platform/Emscripten.cmake | 44 | ||||
-rw-r--r-- | cmake/Platform/Emscripten_unix.cmake | 13 | ||||
-rwxr-xr-x | emcc | 2 | ||||
-rw-r--r-- | tests/cmake/target_html/CMakeLists.txt | 14 | ||||
-rw-r--r-- | tests/cmake/target_js/CMakeLists.txt | 14 | ||||
-rwxr-xr-x | tests/runner.py | 55 | ||||
-rw-r--r-- | tools/shared.py | 60 |
7 files changed, 198 insertions, 4 deletions
diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake new file mode 100644 index 00000000..e050d8a6 --- /dev/null +++ b/cmake/Platform/Emscripten.cmake @@ -0,0 +1,44 @@ +# This file is a 'toolchain description file' for CMake. +# It teaches CMake about the Emscripten compiler, so that CMake can generate Unix Makefiles +# from CMakeLists.txt that invoke emcc. + +# To use this toolchain file with CMake, invoke CMake with the following command line parameters +# cmake -DEMSCRIPTEN=1 +# -DCMAKE_TOOLCHAIN_FILE=<EmscriptenRoot>/cmake/Platform/Emscripten.cmake +# -DCMAKE_MODULE_PATH=<EmscriptenRoot>/cmake +# -DCMAKE_BUILD_TYPE=<Debug|RelWithDebInfo|Release|MinSizeRel> +# -G "Unix Makefiles" +# <path/to/CMakeLists.txt> # Note, pass in here ONLY the path to the file, not the filename 'CMakeLists.txt' itself. + +# After that, build the generated Makefile with the command 'make'. On Windows, you may download and use 'mingw32-make' instead. + +# the name of the target operating system +set(CMAKE_SYSTEM_NAME Emscripten) +set(CMAKE_SYSTEM_VERSION 1) + +if ("$ENV{EMSCRIPTEN}" STREQUAL "") + message(ERROR "Environment variable EMSCRIPTEN has not been set! Please point it to Emscripten root directory!") +endif() + +set(CMAKE_FIND_ROOT_PATH $ENV{EMSCRIPTEN}) + +# Specify the compilers to use for C and C++ +if ("${CMAKE_C_COMPILER}" STREQUAL "") + set(CMAKE_C_COMPILER "emcc") + set(CMAKE_CXX_COMPILER "em++") + set(CMAKE_AR "emar") + set(CMAKE_RANLIB "emranlib") +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# Specify the program to use when building static libraries. Force Emscripten-related command line options to clang. +set(CMAKE_CXX_ARCHIVE_CREATE "${CMAKE_CXX_COMPILER} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>") +set(CMAKE_C_ARCHIVE_CREATE "${CMAKE_C_COMPILER} -o <TARGET> -emit-llvm <LINK_FLAGS> <OBJECTS>") + +# Set a global EMSCRIPTEN variable that can be used in client CMakeLists.txt to detect when building using Emscripten. +# There seems to be some kind of bug with CMake, so you might need to define this manually on the command line with "-DEMSCRIPTEN=1". +set(EMSCRIPTEN 1) diff --git a/cmake/Platform/Emscripten_unix.cmake b/cmake/Platform/Emscripten_unix.cmake new file mode 100644 index 00000000..5358138f --- /dev/null +++ b/cmake/Platform/Emscripten_unix.cmake @@ -0,0 +1,13 @@ +# On Unix platforms, we must specify the absolute path to emcc for cmake, having emcc in PATH will cause cmake to fail finding it. +# The user must set the EMSCRIPTEN variable to point to the Emscripten root folder. + +if ("$ENV{EMSCRIPTEN}" STREQUAL "") + message(ERROR "Environment variable EMSCRIPTEN has not been set! Please point it to Emscripten root directory!") +endif() + +set(CMAKE_C_COMPILER "$ENV{EMSCRIPTEN}/emcc") +set(CMAKE_CXX_COMPILER "$ENV{EMSCRIPTEN}/em++") +set(CMAKE_AR "$ENV{EMSCRIPTEN}/emar") +set(CMAKE_RANLIB "$ENV{EMSCRIPTEN}/emranlib") + +include($ENV{EMSCRIPTEN}/cmake/Platform/Emscripten.cmake) @@ -727,7 +727,7 @@ try: 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 + if not arg.startswith('-') and (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): diff --git a/tests/cmake/target_html/CMakeLists.txt b/tests/cmake/target_html/CMakeLists.txt new file mode 100644 index 00000000..9f891e71 --- /dev/null +++ b/tests/cmake/target_html/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8) + +project(hello_world_gles.html) + +file(GLOB sourceFiles ../../hello_world_gles.c) + +if (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(linkFlags "") +else() # Either MinSizeRel, RelWithDebInfo or Release, all which run with optimizations enabled. + SET(linkFlags "-O2") +endif() + +add_executable(hello_world_gles.html ${sourceFiles}) +set_target_properties(hello_world_gles.html PROPERTIES LINK_FLAGS "${linkFlags}") diff --git a/tests/cmake/target_js/CMakeLists.txt b/tests/cmake/target_js/CMakeLists.txt new file mode 100644 index 00000000..860b70a9 --- /dev/null +++ b/tests/cmake/target_js/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8) + +project(hello_world.js) + +file(GLOB sourceFiles ../../hello_world.cpp) + +if (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(linkFlags "") +else() # Either MinSizeRel, RelWithDebInfo or Release, all which run with optimizations enabled. + SET(linkFlags "-O2") +endif() + +add_executable(hello_world.js ${sourceFiles}) +set_target_properties(hello_world.js PROPERTIES LINK_FLAGS "${linkFlags}") diff --git a/tests/runner.py b/tests/runner.py index 5b1aef35..dfe435a7 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -7523,6 +7523,61 @@ f.close() # TODO: test normal project linking, static and dynamic: get_library should not need to be told what to link! # TODO: deprecate llvm optimizations, dlmalloc, etc. in emscripten.py. + def test_cmake(self): + # On Windows, we want to build cmake-generated Makefiles with mingw32-make instead of e.g. cygwin make, since mingw32-make + # understands Windows paths, and cygwin make additionally produces a cryptic 'not valid bitcode file' errors on files that + # *are* valid bitcode files. + if os.name == 'nt': + make_command = 'mingw32-make' + emscriptencmaketoolchain = path_from_root('cmake', 'Platform', 'Emscripten.cmake') + else: + make_command = 'make' + emscriptencmaketoolchain = path_from_root('cmake', 'Platform', 'Emscripten_unix.cmake') + + cmake_cases = ['target_js', 'target_html'] + cmake_outputs = ['hello_world.js', 'hello_world_gles.html'] + for i in range(0, 2): + for configuration in ['Debug', 'Release']: + + # Create a temp workspace folder + cmakelistsdir = path_from_root('tests', 'cmake', cmake_cases[i]) + tempdirname = tempfile.mkdtemp(prefix='emscripten_test_' + self.__class__.__name__ + '_', dir=TEMP_DIR) + try: + os.chdir(tempdirname) + + # Run Cmake + cmd = ['cmake', '-DCMAKE_TOOLCHAIN_FILE='+emscriptencmaketoolchain, + '-DCMAKE_BUILD_TYPE=' + configuration, + '-DCMAKE_MODULE_PATH=' + path_from_root('cmake').replace('\\', '/'), + '-G' 'Unix Makefiles', cmakelistsdir] + ret = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate() + if ret[1] != None and len(ret[1].strip()) > 0: + print >> sys.stderr, ret[1] # If there were any errors, print them directly to console for diagnostics. + if 'error' in ret[1].lower(): + print >> sys.stderr, 'Failed command: ' + ' '.join(cmd) + print >> sys.stderr, 'Result:\n' + ret[1] + raise Exception('cmake call failed!') # cmake spams this silly message to stderr, so hide it: "Platform/Emscripten to use this system, please send your config file to cmake@www.cmake.org so it can be added to cmake" + assert os.path.exists(tempdirname + '/Makefile'), 'CMake call did not produce a Makefile!' + + # Build + cmd = [make_command] + ret = Popen(cmd, stdout=PIPE).communicate() + if ret[1] != None and len(ret[1].strip()) > 0: + print >> sys.stderr, ret[1] # If there were any errors, print them directly to console for diagnostics. + if 'error' in ret[0].lower() and not '0 error(s)' in ret[0].lower(): + print >> sys.stderr, 'Failed command: ' + ' '.join(cmd) + print >> sys.stderr, 'Result:\n' + ret[0] + raise Exception('make failed!') + assert os.path.exists(tempdirname + '/' + cmake_outputs[i]), 'Building a cmake-generated Makefile failed to produce an output file %s!' % tempdirname + '/' + cmake_outputs[i] + + # Run through node, if CMake produced a .js file. + if cmake_outputs[i].endswith('.js'): + ret = Popen([NODE_JS, tempdirname + '/' + cmake_outputs[i]], stdout=PIPE).communicate()[0] + assert 'hello, world!' in ret, 'Running cmake-based .js application failed!' + finally: + os.chdir(path_from_root('tests')) # Move away from the directory we are about to remove. + shutil.rmtree(tempdirname) + def test_Os(self): for opt in ['s', '0']: output = Popen(['python', EMCC, path_from_root('tests', 'hello_world.c'), '-O' + opt], stdout=PIPE, stderr=PIPE).communicate() diff --git a/tools/shared.py b/tools/shared.py index b8242db3..6b263fbd 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -2,6 +2,58 @@ import shutil, time, os, sys, json, tempfile, copy, shlex, atexit, subprocess from subprocess import Popen, PIPE, STDOUT from tempfile import mkstemp +# A helper class that is used on Windows to represent the subprocess object that is the return value of Popen. +class MockPopen: + def __init__(self, stdout, stderr, returncode): + self.returncode = returncode + self.output = (stdout, stderr) + def communicate(self): + return self.output + +# On Windows python suffers from a particularly nasty bug if python is spawning new processes while python itself is spawned from some other non-console process. +# Use a custom replacement for Popen on Windows to avoid the "WindowsError: [Error 6] The handle is invalid" errors when emcc is driven through cmake or mingw32-make. +# See http://bugs.python.org/issue3905 +def call_process(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, + shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): + # (stdin, stdout, stderr) store what the caller originally wanted to be done with the streams. + # (stdin_, stdout_, stderr_) will store the fixed set of streams that workaround the bug. + stdin_ = stdin + stdout_ = stdout + stderr_ = stderr + + # If the caller wants one of these PIPEd, we must PIPE them all to avoid the 'handle is invalid' bug. + if stdin_ == PIPE or stdout_ == PIPE or stderr_ == PIPE: + if stdin_ == None: + stdin_ = PIPE + if stdout_ == None: + stdout_ = PIPE + if stderr_ == None: + stderr_ = PIPE + + # Call the process with fixed streams. + process = subprocess.Popen(args, bufsize, executable, stdin_, stdout_, stderr_, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags) + output = process.communicate() + + # If caller never wanted to PIPE stdout or stderr, route the output back to screen to avoid swallowing output. + if stdout == None and stdout_ == PIPE: + print >> sys.stdout, output[0] + if stderr == None and stderr_ == PIPE: + print >> sys.stderr, output[1] + + # Return a mock object to the caller. This works as long as all emscripten code immediately .communicate()s the result, and doesn't + # leave the process object around for longer/more exotic uses. + if stdout == None and stderr == None: + return MockPopen(None, None, process.returncode) + if stdout == None: + return MockPopen(None, output[1], process.returncode) + if stderr == None: + return MockPopen(output[0], None, process.returncode) + return MockPopen(output[0], output[1], process.returncode) + +# Install our replacement Popen handler if we are running on Windows to avoid python spawn process function. +if os.name == 'nt': + Popen = call_process + import js_optimizer __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -988,7 +1040,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e b[6] == '>' and ord(b[7]) == 10 Building._is_ar_cache[filename] = sigcheck return sigcheck - except: + except Exception, e: + print 'shared.Building.is_ar failed to test whether file \'%s\' is a llvm archive file! Failed on exception: %s' % (filename, e) return False @staticmethod @@ -1006,7 +1059,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e test_ll = os.path.join(EMSCRIPTEN_TEMP_DIR, 'test.ll') Building.llvm_dis(filename, test_ll) assert os.path.exists(test_ll) - except: + except Exception, e: + print 'shared.Building.is_bitcode failed to test whether file \'%s\' is a llvm bitcode file! Failed on exception: %s' % (filename, e) return False # look for magic signature @@ -1108,7 +1162,7 @@ class Compression: 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) + return 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) |