Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cross-platform, cross-compile build toolchain #280

Merged
merged 25 commits into from Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9686139
added CMake build scripts (replaces VS .sln)
drywolf Feb 22, 2017
b57b921
changed NodeJS dependency default path
drywolf Feb 22, 2017
d89202e
Merge remote-tracking branch 'upstream/master'
drywolf May 3, 2017
9c75430
MacOS build working (some Node.js tests failing)
drywolf May 12, 2017
45e487d
Merge remote-tracking branch 'upstream/master'
drywolf May 12, 2017
bf02e87
MacOS linking fix & script cleanups
drywolf May 13, 2017
076ffe2
split up python build-scripts
drywolf May 13, 2017
f3e69cc
moved python build utils to separate directory
drywolf May 13, 2017
6d2bbb8
split cmake generation and jni compile
drywolf May 13, 2017
1ff1de4
updated win32 node link libs (node v7.4.0+)
drywolf May 14, 2017
54fdc43
fixed win32 build for 7.4.0 + module linking sanity checks
drywolf May 14, 2017
d0deb23
win32 build-steps config
drywolf May 15, 2017
3954205
adding android build support
drywolf May 16, 2017
18c7b37
android builds working (arm & x86) ... no automatic bundling yet
drywolf May 21, 2017
9cd7c6a
linux build support, android fixes & build-output reuse
drywolf May 29, 2017
7f2e8cf
docker android testing support
drywolf Jun 3, 2017
2f66df8
fixed hardcoded build_cwd for node.js build artifact reuse
drywolf Jun 4, 2017
9e45f55
fixed all open TODOs & refactored build-system code
drywolf Jun 11, 2017
8297dab
added some more build-system features & utils
drywolf Jun 14, 2017
63ac9d1
build-system refactoring & code cleanups
drywolf Jun 15, 2017
da5d322
Merge remote-tracking branch 'upstream/master'
drywolf Jun 15, 2017
de97277
updated build documentation in README.md
drywolf Jun 15, 2017
80f16de
improved --help message & fixed macos native lib copy
drywolf Jun 15, 2017
9359bb8
fixes based on feedback in PR #280 by matiwinnetou
drywolf Jun 20, 2017
d339049
Merge remote-tracking branch 'upstream/master'
drywolf Jun 20, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Expand Up @@ -21,5 +21,11 @@ hs_err*.log
*.iws
.idea

# Build output.
# python binaries
*.pyc

# Build input/output.
node
build.out
cmake.out
node.out
183 changes: 183 additions & 0 deletions CMakeLists.txt
@@ -0,0 +1,183 @@

# NOTE: this works around the windows docker / msbuild bug, that causes the
# VC++ linker to fail creating PDB debug files in a shared volume
# (see: https://social.msdn.microsoft.com/Forums/en-US/bd281c61-8e04-4f56-aec2-5ad565271d7f/windowsservercorelatest-vc-2015-build-tools-throws-linker-error-lnk1318-unexpected-pdb?forum=windowscontainers)
if(J2V8_CROSS_COMPILE AND J2V8_WIN32_PDB_DOCKER_FIX)
#{
# TODO: remove this if the Win32 Docker PDB bug is fixed
# see: https://stackoverflow.com/a/39109288
message("CMAKE-WARNING: applied workaround for windows docker PDB bug")
message("see: https://social.msdn.microsoft.com/Forums/en-US/bd281c61-8e04-4f56-aec2-5ad565271d7f/windowsservercorelatest-vc-2015-build-tools-throws-linker-error-lnk1318-unexpected-pdb?forum=windowscontainers")
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
#}
endif()

cmake_minimum_required(VERSION 3.6)
project(j2v8)

# adding cmake directory for includes
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

# set up the module path
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)

include(BuildUtils)
include(NodeJsUtils)
include(Policies)

#-----------------------------------------------------------------------
# BUILD PLATFORM SETUP & VARIABLES
#-----------------------------------------------------------------------

if(CMAKE_SYSTEM_NAME STREQUAL "Android")
#{
set(JAVA_PLATFORM_NAME "android")

# output library filename
set(J2V8_LIB_PLATFORM_NAME "android")
set(J2V8_LIB_PREFIX "")
set(J2V8_LIB_ARCH_NAME ${CMAKE_ANDROID_ARCH_ABI})
#}
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
#{
set(JAVA_PLATFORM_NAME "linux")

# output library filename
set(J2V8_LIB_PLATFORM_NAME "linux")
set(J2V8_LIB_PREFIX "")
set(J2V8_LIB_ARCH_NAME "x86")
#}
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
#{
set(JAVA_PLATFORM_NAME "darwin")

# output library filename
set(J2V8_LIB_PLATFORM_NAME "macosx")
set(J2V8_LIB_PREFIX "")
set(J2V8_LIB_ARCH_NAME "x86")
#}
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
#{
set(JAVA_PLATFORM_NAME "win32")

# output library filename
set(J2V8_LIB_PLATFORM_NAME "win32")
set(J2V8_LIB_PREFIX "lib")
set(J2V8_LIB_ARCH_NAME "x86")
#}
endif()

#-----------------------------------------------------------------------
# DEPENDENCY SETTINGS / CMAKE OPTIONS
#-----------------------------------------------------------------------

# look for dependencies
find_package(Java)

# j2v8 dependency options
set(J2V8_JDK_DIR ${Java_ROOT} CACHE STRING "Path to the Java JDK dependency")
set(J2V8_NODEJS_DIR "${CMAKE_SOURCE_DIR}/node" CACHE STRING "Path to the Node.js dependency")

# get the required Node.js link libraries
get_njs_libs(${J2V8_NODEJS_DIR} "Debug")
get_njs_libs(${J2V8_NODEJS_DIR} "Release")

# j2v8 build options
option(J2V8_NODE_COMPATIBLE "Build the J2V8 native bridge with Node.js support enabled" ON)
option(J2V8_BUILD_ONLY_DEBUG_RELEASE "Generate only Debug and Release configurations (exclude RelWithDebInfo and MinSizeRel)" ON)

if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND MSVC)
#{
option(J2V8_LINK_WITH_STATIC_MSVCRT "Link against the static version of the Microsoft Visual C++ Common Runtime (will link against the dynamic DLL version if this option is disabled)" ON)
#}
endif()

#-----------------------------------------------------------------------
# INCLUDE DIRECTORIES & SOURCE FILES
#-----------------------------------------------------------------------

# project include directories
set(include_dirs
${J2V8_NODEJS_DIR}
${J2V8_NODEJS_DIR}/src
${J2V8_NODEJS_DIR}/deps/v8
${J2V8_NODEJS_DIR}/deps/v8/include
)

# do not use JAVA_HOME for Java include files for Android
# (instead the Android NDK toolchain will introduce them automatically)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android")
#{
set(include_dirs
${include_dirs}
${J2V8_JDK_DIR}/include
${J2V8_JDK_DIR}/include/${JAVA_PLATFORM_NAME}
)
#}
endif()

# project source files
set(src_files
jni/com_eclipsesource_v8_V8Impl.cpp
jni/com_eclipsesource_v8_V8Impl.h
)

source_group("" FILES ${src_files})

#-----------------------------------------------------------------------
# BUILD SETTINGS & COMPILATION
#-----------------------------------------------------------------------

# tell gcc/clang to use the c++11 standard
set(CMAKE_CXX_STANDARD 11)

if(CMAKE_SYSTEM_NAME STREQUAL "Android")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-unused-function -Wno-unused-variable -O3 -funroll-loops -ftree-vectorize -ffast-math -fpermissive -fPIC ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-function -Wno-unused-variable -O3 -funroll-loops -ftree-vectorize -ffast-math -fpermissive -fPIC ")
endif()

# remove the MinSizeRel and RelWithDebInfo configurations
if(J2V8_BUILD_ONLY_DEBUG_RELEASE)
#{
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited configs" FORCE)
#}
endif()

# link against the static MS C++ runtime libraries
if(J2V8_LINK_WITH_STATIC_MSVCRT)
link_static_crt()
endif()

# create the j2v8 library
add_library(j2v8 SHARED ${src_files})

# enable Node.js if requested by the build options above
if(J2V8_NODE_COMPATIBLE)
#{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are curly braces commented out?

Copy link
Contributor Author

@drywolf drywolf Jul 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use those just by convention to make it easier for the eye to quickly read where the scope of a if-elif-else block starts and ends. This being said, for the next PR I have removed those where only a single line is inside the branch scope. From now on they are only used for if-elif-else blocks that contain multiple lines in their respective blocks (which was the original intent). Thanks for the feedback

set_property(TARGET j2v8 PROPERTY COMPILE_DEFINITIONS ${COMPILE_DEFINITIONS} NODE_COMPATIBLE=1)
#}
endif()

# build output directory
set(LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

# set the include directories
include_directories(${include_dirs})

# link the necessary libraries
target_link_libraries(j2v8
debug "${njs_Debug_libs}"
optimized "${njs_Release_libs}"
)

#-----------------------------------------------------------------------
# OUTPUT SETTINGS & POST-BUILD
#-----------------------------------------------------------------------

# apply lib suffix if building a 64 bit target
if(CMAKE_CL_64 OR CMAKE_SIZEOF_VOID_P EQUAL 8)
set(J2V8_LIB_ARCH_NAME "${J2V8_LIB_ARCH_NAME}_64")
endif()

# set library output filename
set_target_properties(j2v8 PROPERTIES OUTPUT_NAME "${PROJECT_NAME}_${J2V8_LIB_PLATFORM_NAME}_${J2V8_LIB_ARCH_NAME}")
81 changes: 29 additions & 52 deletions README.md
Expand Up @@ -10,58 +10,35 @@ We developed J2V8 as a high performance engine for our multi-platform mobile too

Building J2V8
=============
Building J2V8 requires building both the native parts and the Java library (.jar file). To build the native parts we first build node.js as a library and then statically link J2V8 to that. The Java parts are built with maven.

Building on MacOS
-----------------
```
sh ./build-node.sh
sh ./buildJ2V8_macos.sh
mvn clean verify
```

Building on Linux
-----------------
```
export CCFLAGS="${CCFLAGS} -fPIC"
export CXXFLAGS="${CXXFLAGS} -fPIC"
export CPPFLAGS="${CPPFLAGS} -fPIC"
#sh ./build-node.sh
cp -r /data/jenkins/node .
cd jni
g++ -I../node -I../node/deps/v8 -I../node/deps/v8/include \
-I../node/src -I /data/jenkins/tools/hudson.model.JDK/jdk-7/include/ \
-I /data/jenkins/tools/hudson.model.JDK/jdk-7/include/linux \
com_eclipsesource_v8_V8Impl.cpp -std=c++11 -fPIC -shared -o libj2v8_linux_x86_64.so \
-Wl,--whole-archive ../node/out/Release/libnode.a -Wl,--no-whole-archive \
-Wl,--start-group \
../node/out/Release/libv8_libbase.a \
../node/out/Release/libv8_libplatform.a \
../node/out/Release/libv8_base.a \
../node/out/Release/libv8_nosnapshot.a \
../node/out/Release/libuv.a \
../node/out/Release/libopenssl.a \
../node/out/Release/libhttp_parser.a \
../node/out/Release/libgtest.a \
../node/out/Release/libzlib.a \
../node/out/Release/libcares.a \
-Wl,--end-group \
-lrt -D NODE_COMPATIBLE=1
mvn clean verify
```

This will build J2V8 with node.js support. To disable this support, remove the `-D NODE_COMPATIBLE=1` option.

Building for Android
-----------------
Building J2V8 for Android requires Docker.

```
./docker/build.sh
./gradlew assembleRelease
```

This will build J2V8 as an AAR for API 19 minimum with node.js support.
Building J2V8 requires building both the native parts and the Java library (.jar/.aar file). To build the native parts we first build node.js as a library and then statically link J2V8 to that. The Java parts are built with maven/gradle.

J2V8 uses a cross-platform, cross-compiling build-system written in Python.

Follow these steps to build J2V8 from source:

1) clone the Node.js source code
- `python prepare_build.py`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is worth to mention that this works only for python2 (tried with python3 but doesn't work), was there any reason why you choose python over lets say bash scripts? I am just thinking out loud here that not everyone has python2 installed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also when I already had node folder checked out the build failed:

[mati@winnetou-pc J2V8]$ python2 prepare_build.py
Node.js is already cloned & checked out
Traceback (most recent call last):
  File "prepare_build.py", line 20, in <module>
    branch = utils.get_node_branch_version()
  File "/home/mati/Devel/OpenSource/J2V8/build_system/build_utils.py", line 14, in get_node_branch_version
    out = execute_to_str("git branch", "node")
  File "/home/mati/Devel/OpenSource/J2V8/build_system/build_utils.py", line 40, in execute_to_str
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=cwd)
  File "/usr/lib/python2.7/subprocess.py", line 390, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1024, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even most simple build didn't work on my latest manjaro (arch) linux:
python2 build.py -t linux -a x64

most likely I ran into this issue: gcc7 doesn't compile:

make[1]: *** [deps/v8/src/v8_base.target.mk:554: /home/mati/Devel/OpenSource/J2V8/node/out/Release/obj.target/v8_base/deps/v8/src/heap/heap.o] Error 1
make[1]: *** [deps/v8/src/v8_base.target.mk:554: /home/mati/Devel/OpenSource/J2V8/node/out/Release/obj.target/v8_base/deps/v8/src/heap/mark-compact.o] Error 1
make[1]: Leaving directory '/home/mati/Devel/OpenSource/J2V8/node/out'
make: *** [Makefile:73: node] Error 2
Traceback (most recent call last):
  File "build.py", line 311, in <module>
    execute_build(args)
  File "build.py", line 307, in execute_build
    execute_build_step(target_compiler, target_step)
  File "build.py", line 266, in execute_build_step
    compiler.build(build_step)
  File "/home/mati/Devel/OpenSource/J2V8/build_system/cross_build.py", line 60, in build
    self.exec_build(config)
  File "/home/mati/Devel/OpenSource/J2V8/build_system/shell_build.py", line 26, in exec_build
    self.exec_cmd(shell_str, config)
  File "/home/mati/Devel/OpenSource/J2V8/build_system/cross_build.py", line 79, in exec_cmd
    utils.execute(cmd, dir)
  File "/home/mati/Devel/OpenSource/J2V8/build_system/build_utils.py", line 37, in execute
    raise subprocess.CalledProcessError(return_code, cmd)
subprocess.CalledProcessError: Command 'cd /home/mati/Devel/OpenSource/J2V8 && cd ./node && ./configure                          --without-intl                      --without-inspector                 --dest-cpu=x64                    --without-snapshot                  --enable-static && CFLAGS=-fPIC CXXFLAGS=-fPIC make -j4' returned non-zero exit status 2

nodejs/help#664

this makes me think that perhaps docker should always be used...

Copy link
Contributor Author

@drywolf drywolf Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was there any reason why you choose python over lets say bash scripts? I am just thinking out loud here that not everyone has python2 installed.

There are several reasons why I would not use bash scripts for more complex scenarios

  • the composability of bash/shell is relatively limited compared to python's (functional reuse, data reuse, ...)
  • bash is only natively available on MacOS / Linux environments (python covers all platforms and also delivers platform independent APIs for OS functions, e.g. file access)
  • IDE code maintenance & refactoring support

I targeted Python 2 primarily because you already need it if you want to build Node.js from source. But I will look into how much work it would take to make it compatible for Python 3 (but I don't think this will be a priority, since Python 2 is still very widely adopted)

About the error you get from running prepare_build.py ... this is mainly a utility script that is intended to get you started from a fresh checkout of the J2V8 repository. For the case when there is already a node directory checked out this script should not try to override anything you already have there, but I will add a check for that and print some more helpful error message, thanks for pointing this out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You did so much amazing work I have to say. I think you have a point here, I was just thinking from a scenario that one does git clone and invokes command to build, the rest would happen in docker container. One of biggest advantages of docker as I understood this is to eliminate various linux distrobution flavours, e.g. gcc version, kernel version, different packages versions, python2 vs python3. In that respect it would be perhaps better to fork of python2 invoking a build inside a docker container (in theory) -> that way probability that build would work is higher.

On another hand, if this is intended to be used by Travis to create releases (as most people may not care to build snapshots), then it doesn't really matter. As long as maintainers of this project know how to run this, then it is fine.

- This will download & prepare the latest compatible Node.js version for use in J2V8
- The Node.js source code will be cloned into the local `node` sub-directory, which is the expected default location for the J2V8 build
2) build Node.js and the J2V8 library
- `python build.py --target linux --arch x64 --node-enabled --cross-compile`
- or shorthand
- `python build.py -t linux -a x64 -ne -x`

For all available options, supported platforms and architectures you can consult the build-script help:

`python build.py --help`

Cross-Compiling
---------------

For cross-compiling J2V8 uses [Docker](https://www.docker.com/) (android, linux, windows) and [Vagrant](https://www.vagrantup.com/) (macos).
The full source-code (of both J2V8 and Node.js) on the build-host are just shared via mounted volumes with the Docker / Vagrant machines, so you can quickly make changes and perform builds fast.

To invoke a cross-compile build, simply invoke the `build.py` script as usual but add the `--cross-compile`, `-x` flag.
This will automatically provision and run the necessary virtualization to run the requested build fully independent of your local environment.

<b>Note:</b> using Docker / Vagrant for cross-compiliation requires many gigabytes of harddrive space as well as downloading the required images & tools.

Tutorials
==========
Expand Down
29 changes: 21 additions & 8 deletions build.gradle
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'signing'

group = 'com.eclipsesource.j2v8'
archivesBaseName = 'j2v8'
version = '4.8.0-SNAPSHOT'
version = System.env.J2V8_FULL_VERSION

configurations {
archives {
Expand All @@ -28,25 +28,38 @@ repositories {

dependencies {
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.9.5'
// testCompile 'org.mockito:mockito-all:1.9.5'

androidTestCompile 'junit:junit:4.12'
// androidTestCompile 'org.mockito:mockito-core:1.9.5'
androidTestCompile 'org.mockito:mockito-android:2.6.3' // https://jeroenmols.com/blog/2017/01/17/mockitoandroid/
androidTestCompile 'com.android.support:support-annotations:24.0.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}

android {
compileSdkVersion 10
compileSdkVersion 19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why 19? We should try to keep this as low as possible.

Copy link
Contributor Author

@drywolf drywolf Jul 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think at some point while I worked on how I could get the Android emulator testing integration working, it was required by some dependency that might now be gone. If the build & tests work fine with version 10 then we can safely revert it back

buildToolsVersion '24.0.3'

defaultConfig {
minSdkVersion 10
targetSdkVersion 10
minSdkVersion 19
targetSdkVersion 19

ndk {
abiFilters 'armeabi'
}
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

lintOptions {
abortOnError false
}

sourceSets {
androidTest {
java.srcDirs = ['src/test']
}

// androidTest.setRoot('src/test')
}
}


Expand Down