aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2012-11-13 13:32:41 -0800
committerAlon Zakai <alonzakai@gmail.com>2012-11-13 13:32:41 -0800
commit7453def33e4df4225453da2d0407658b3a711cda (patch)
tree1ac4d0adc9bcb3e3b427ce0bd13f4a9dd74e98ae
parent36308403a7bb76edd288e1557dee4acb8f1e8d7b (diff)
parent075317782e3195382741d0bc22ff314235cfeeda (diff)
Merge pull request #703 from juj/cmake
Cmake support.
-rw-r--r--cmake/Platform/Emscripten.cmake44
-rw-r--r--cmake/Platform/Emscripten_unix.cmake13
-rwxr-xr-xemcc2
-rw-r--r--tests/cmake/target_html/CMakeLists.txt14
-rw-r--r--tests/cmake/target_js/CMakeLists.txt14
-rwxr-xr-xtests/runner.py55
-rw-r--r--tools/shared.py60
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)
diff --git a/emcc b/emcc
index 29855f03..510a40dc 100755
--- a/emcc
+++ b/emcc
@@ -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)