Skip to content

Commit

Permalink
Build macOS framework and add CocoaPods podspec (facebook#285)
Browse files Browse the repository at this point in the history
Summary:
Supersedes facebook#239

Currently used in our macOS fork of React Native microsoft/react-native-macos#473. (Although we’re using a build of Hermes v0.4.1, as we’re at RN 0.62.0.)

* On Apple platforms build a [dynamic] framework bundle when `HERMES_BUILD_APPLE_FRAMEWORK` is set. When set to `FALSE` it will produce a `dylib`, like previously. Defaults to `TRUE`.
* On Apple platforms create a debugging symbols bundle.
* Add `HERMES_ENABLE_FUZZING`, which is enabled by default.
* Add `HERMES_ENABLE_TEST_SUITE`, which is enabled by default.
* Add a CocoaPods podspec that can build from source or use a binary.

A standalone macOS app that pulls in Hermes as a CocoaPods pod can be found here https://github.com/alloy/TestHermesMaster.

```
$ ./src/utils/build/configure.py --distribute --cmake-flags='-DCMAKE_INSTALL_PREFIX:PATH=../destroot_release' build
$ cd build && ninja install && cd ..
$ tree destroot_release/
destroot_release/
├── Library
│   └── Frameworks
│       ├── hermes.framework
│       │   ├── Headers -> Versions/Current/Headers
│       │   ├── Resources -> Versions/Current/Resources
│       │   ├── Versions
│       │   │   ├── 0
│       │   │   │   ├── Headers
│       │   │   │   │   ├── CompileJS.h
│       │   │   │   │   ├── DebuggerAPI.h
│       │   │   │   │   ├── Public
│       │   │   │   │   │   ├── Buffer.h
│       │   │   │   │   │   ├── CrashManager.h
│       │   │   │   │   │   ├── CtorConfig.h
│       │   │   │   │   │   ├── DebuggerTypes.h
│       │   │   │   │   │   ├── GCConfig.h
│       │   │   │   │   │   ├── GCTripwireContext.h
│       │   │   │   │   │   └── RuntimeConfig.h
│       │   │   │   │   ├── SynthTrace.h
│       │   │   │   │   ├── SynthTraceParser.h
│       │   │   │   │   ├── TraceInterpreter.h
│       │   │   │   │   ├── TracingRuntime.h
│       │   │   │   │   ├── hermes.h
│       │   │   │   │   └── hermes_tracing.h
│       │   │   │   ├── Resources
│       │   │   │   │   └── Info.plist
│       │   │   │   └── hermes
│       │   │   └── Current -> 0
│       │   └── hermes -> Versions/Current/hermes
│       └── hermes.framework.dSYM
│           └── Contents
│               ├── Info.plist
│               └── Resources
│                   └── DWARF
│                       └── hermes
├── bin
│   ├── hbcdump
│   ├── hdb
│   ├── hermes
│   ├── hermesc
│   └── hvm
└── include
    ├── hermes
    │   ├── CompileJS.h
    │   ├── DebuggerAPI.h
    │   ├── Public
    │   │   ├── Buffer.h
    │   │   ├── CrashManager.h
    │   │   ├── CtorConfig.h
    │   │   ├── DebuggerTypes.h
    │   │   ├── GCConfig.h
    │   │   ├── GCTripwireContext.h
    │   │   └── RuntimeConfig.h
    │   ├── SynthTrace.h
    │   ├── SynthTraceParser.h
    │   ├── TraceInterpreter.h
    │   ├── TracingRuntime.h
    │   ├── hermes.h
    │   └── hermes_tracing.h
    └── jsi
        ├── JSIDynamic.h
        ├── decorator.h
        ├── instrumentation.h
        ├── jsi-inl.h
        ├── jsi.h
        ├── jsilib.h
        └── threadsafe.h
```

```
$ ./src/utils/build/configure.py --distribute --cmake-flags='-DHERMES_BUILD_APPLE_FRAMEWORK:BOOLEAN=false -DCMAKE_INSTALL_PREFIX:PATH=../destroot_release' build
$ cd build && ninja install && cd ..
$ tree destroot_release/
destroot_release/
├── bin
│   ├── hbcdump
│   ├── hdb
│   ├── hermes
│   ├── hermesc
│   └── hvm
├── include
│   ├── hermes
│   │   ├── CompileJS.h
│   │   ├── DebuggerAPI.h
│   │   ├── Public
│   │   │   ├── Buffer.h
│   │   │   ├── CrashManager.h
│   │   │   ├── CtorConfig.h
│   │   │   ├── DebuggerTypes.h
│   │   │   ├── GCConfig.h
│   │   │   ├── GCTripwireContext.h
│   │   │   └── RuntimeConfig.h
│   │   ├── SynthTrace.h
│   │   ├── SynthTraceParser.h
│   │   ├── TraceInterpreter.h
│   │   ├── TracingRuntime.h
│   │   ├── hermes.h
│   │   └── hermes_tracing.h
│   └── jsi
│       ├── JSIDynamic.h
│       ├── decorator.h
│       ├── instrumentation.h
│       ├── jsi-inl.h
│       ├── jsi.h
│       ├── jsilib.h
│       └── threadsafe.h
└── lib
    ├── libhermes.dylib
    └── libhermes.dylib.dSYM
        └── Contents
            ├── Info.plist
            └── Resources
                └── DWARF
                    └── libhermes.dylib
```

Pull Request resolved: facebook#285

Reviewed By: willholen

Differential Revision: D22398354

Pulled By: mhorowitz

fbshipit-source-id: 732524275cf273866171fc6e2ac2acb062185fbd
  • Loading branch information
willholen committed Jul 30, 2020
1 parent 2b688bb commit 1a04aee
Show file tree
Hide file tree
Showing 29 changed files with 1,514 additions and 59 deletions.
23 changes: 22 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ workflows:
- windows
- test-linux
- test-macos
- test-macos-podspec
- test-e2e:
requires:
- npm
Expand Down Expand Up @@ -251,13 +252,33 @@ jobs:
hermes/utils/build/configure.py
cmake --build ./build --target check-hermes
test-macos-podspec:
macos:
xcode: "10.3.0"
environment:
- TERM: dumb
# Homebrew currently breaks while updating:
# https://discuss.circleci.com/t/brew-install-fails-while-updating/32992
- HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- checkout
- run:
name: Install dependencies
command: |
brew install cmake ninja
- run:
name: Run CocoaPods installation and test framework
command: |
cd test/ApplePlatformsIntegrationTestApp
./run.sh
windows:
executor:
name: win/preview-default
shell: powershell.exe
environment:
- HERMES_WS_DIR: 'C:\tmp\hermes'
- ICU_URL: 'https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip'
- ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
- MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
- CMAKE_DIR: 'C:\Program Files\CMake\bin'
steps:
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ buck-out

# Mac
.DS_Store

# CocoaPods testing
build
destroot
test/ApplePlatformsIntegrationTestApp/Podfile.lock
test/ApplePlatformsIntegrationTestApp/Pods
test/ApplePlatformsIntegrationTestApp/**/xcuserdata
test/ApplePlatformsIntegrationTestApp/ApplePlatformsIntegrationTestApp.xcworkspace
68 changes: 66 additions & 2 deletions API/hermes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ set(api_sources
DebuggerAPI.cpp
)

file(GLOB api_headers ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
file(GLOB api_public_headers ${PROJECT_SOURCE_DIR}/public/hermes/Public/*.h)

if (HERMESVM_API_TRACE)
list(APPEND api_sources
hermes_tracing.cpp
Expand Down Expand Up @@ -63,12 +66,13 @@ add_hermes_library(compileJS STATIC CompileJS.cpp)
set(HERMES_ENABLE_EH ON)
set(HERMES_ENABLE_RTTI ON)

add_library(libhermes SHARED ${api_sources})
add_library(libhermes SHARED ${api_sources} ${api_headers} ${api_public_headers})
target_link_libraries(libhermes
jsi
hermesVMRuntime
${CORE_FOUNDATION}
)
hermes_link_icu(libhermes)

if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR
"${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
Expand All @@ -93,5 +97,65 @@ set_target_properties(libhermes PROPERTIES
CXX_STANDARD_REQUIRED 14
# Avoid becoming liblibhermes (and there's already a target called 'hermes')
OUTPUT_NAME hermes
)

if(APPLE AND HERMES_BUILD_APPLE_FRAMEWORK)
set_target_properties(libhermes PROPERTIES
FRAMEWORK TRUE
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION}
FRAMEWORK_VERSION ${PROJECT_VERSION_MAJOR}
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PROJECT_VERSION}
MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_FRAMEWORK_IDENTIFIER dev.hermesengine.${CMAKE_SYSTEM_NAME}
)
hermes_link_icu(libhermes)
# Install headers into `Headers` while keeping required directory structure
set_source_files_properties(${api_headers} PROPERTIES
MACOSX_PACKAGE_LOCATION Headers
)
set_source_files_properties(${api_public_headers} PROPERTIES
MACOSX_PACKAGE_LOCATION Headers/Public
)
endif()

install(TARGETS libhermes
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
FRAMEWORK DESTINATION Library/Frameworks
)
# Install headers into `include` while keeping required directory structure
install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/hermes" DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN "synthtest" EXCLUDE)

# Create debug symbols (dSYM) bundle for Apple platform dylibs/frameworks
# Largely inspired by https://github.com/llvm/llvm-project/blob/6701993027f8af172d7ba697884459261b00e3c6/llvm/cmake/modules/AddLLVM.cmake#L1934-L1986
if(APPLE AND CMAKE_CXX_FLAGS MATCHES "-gdwarf")
if(CMAKE_CXX_FLAGS MATCHES "-flto")
set(lto_object ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/libhermes-lto.o)
set_property(TARGET libhermes APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-object_path_lto,${lto_object}")
endif()

get_target_property(DSYM_PATH libhermes LOCATION)
if(HERMES_BUILD_APPLE_FRAMEWORK)
get_filename_component(DSYM_PATH ${DSYM_PATH} DIRECTORY)
endif()
set(DSYM_PATH "${DSYM_PATH}.dSYM")

if(NOT CMAKE_DSYMUTIL)
set(CMAKE_DSYMUTIL xcrun dsymutil)
endif()
add_custom_command(TARGET libhermes POST_BUILD
COMMAND ${CMAKE_DSYMUTIL} $<TARGET_FILE:libhermes> --out ${DSYM_PATH}
BYPRODUCTS ${DSYM_PATH}
)

if(HERMES_BUILD_APPLE_FRAMEWORK)
install(DIRECTORY ${DSYM_PATH} DESTINATION Library/Frameworks)
else()
install(DIRECTORY ${DSYM_PATH} DESTINATION lib)
endif()
endif()

hermes_strip_apple_debug_symbols(libhermes)
4 changes: 4 additions & 0 deletions API/jsi/jsi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")
list(APPEND jsi_compile_flags "/EHsc")
endif()
target_compile_options(jsi PUBLIC ${jsi_compile_flags})

install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/jsi/" DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN "test" EXCLUDE)
146 changes: 92 additions & 54 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,17 @@ if (POLICY CMP0023)
cmake_policy(SET CMP0023 OLD)
endif()

# Allow reading the LOCATION property of a target to determine the eventual
# location of build targets. This is needed when building the debugging symbols
# bundles for Apple platforms.
if (POLICY CMP0026)
cmake_policy(SET CMP0026 OLD)
endif()

# This must be consistent with the release_version in
# android/build.gradle and npm/package.json
# This must be consistent with the release_version in:
# - android/build.gradle
# - npm/package.json
# - hermes.podspec
project(Hermes
VERSION 0.5.0
LANGUAGES C CXX)
Expand Down Expand Up @@ -176,7 +184,20 @@ set(HERMES_MSVC_MP ON CACHE STRING
set(EMSCRIPTEN_FASTCOMP ON CACHE BOOL
"Emscripten is using the fastcomp backend instead of the LLVM one")

if (HERMES_IS_ANDROID)
set(HERMES_ENABLE_INTL OFF CACHE BOOL
"Enable JS Intl support (WIP)")

set(HERMES_ENABLE_TEST_SUITE ON CACHE BOOL
"Enable the test suite")

set(HERMES_BUILD_APPLE_FRAMEWORK ON CACHE BOOL
"Whether to build the libhermes target as a framework bundle or dylib on Apple platforms")

if (NOT HERMES_IS_MOBILE_BUILD AND APPLE)
# Always emit debugging symbols (in the DWARF) format, as these will be
# stripped out of each target post-build.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -gdwarf")
elseif (HERMES_IS_ANDROID)
set(HERMES_IS_MOBILE_BUILD TRUE)

add_definitions(-DHERMES_PLATFORM_UNICODE=HERMES_PLATFORM_UNICODE_JAVA)
Expand Down Expand Up @@ -429,6 +450,20 @@ function(hermes_link_icu target_name)
endif()
endfunction()

# Declare a function that strips debugging symbols from any target built for
# Apple platforms, which we do build initially so they can be extracted into a
# dSYM bundle.
function(hermes_strip_apple_debug_symbols target_name)
if(APPLE)
if(NOT CMAKE_STRIP)
set(CMAKE_STRIP xcrun strip)
endif()
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_STRIP} -S $<TARGET_FILE:${target_name}>
)
endif()
endfunction()

if (APPLE)
find_library(CORE_FOUNDATION CoreFoundation)
else()
Expand Down Expand Up @@ -509,8 +544,8 @@ add_subdirectory(utils/hermes-lit)
add_subdirectory(tools)
add_subdirectory(include)
add_subdirectory(lib)
add_subdirectory(public)
add_subdirectory(external)
add_subdirectory(unittests)
add_subdirectory(API)

# Make sure JSI is compiled with PIC
Expand All @@ -525,56 +560,59 @@ endif()

# Configure the test suites
#
list(APPEND HERMES_TEST_DEPS
HermesUnitTests
hermes
hermesc
hvm
interp-dispatch-bench
hdb
hbcdump
hermes-repl
hbc-attribute
hbc-deltaprep
hbc-diff
)

set(HERMES_LIT_TEST_PARAMS
test_exec_root=${HERMES_BINARY_DIR}/test
unittests_dir=${HERMES_BINARY_DIR}/unittests
debugger_enabled=${HERMES_ENABLE_DEBUGGER}
use_flowparser=${HERMES_USE_FLOWPARSER}
jit_enabled=${HERMESVM_JIT}
jit_disassembler_enabled=${HERMESVM_JIT_DISASSEMBLER}
hbc_deltaprep=${HERMES_TOOLS_OUTPUT_DIR}/hbc-deltaprep
FileCheck=${HERMES_TOOLS_OUTPUT_DIR}//FileCheck
hermes=${HERMES_TOOLS_OUTPUT_DIR}/hermes
hermesc=${HERMES_TOOLS_OUTPUT_DIR}/hermesc
hdb=${HERMES_TOOLS_OUTPUT_DIR}/hdb
hbcdump=${HERMES_TOOLS_OUTPUT_DIR}/hbcdump
repl=${HERMES_TOOLS_OUTPUT_DIR}/hermes-repl
hbc-deltaprep=${HERMES_TOOLS_OUTPUT_DIR}/hbc-deltaprep
hbc_diff=${HERMES_TOOLS_OUTPUT_DIR}/hbc-diff
build_mode=${HERMES_ASSUMED_BUILD_MODE_IN_LIT_TEST}
exception_on_oom_enabled=${HERMESVM_EXCEPTION_ON_OOM}
serialize_enabled=${HERMESVM_SERIALIZE}
profiler=${HERMES_PROFILER_MODE_IN_LIT_TEST}
use_js_library_implementation=${HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION}
gc=${HERMESVM_GCKIND}
# TODO: Figure out how to tell if CMake is doing a UBSAN build.
ubsan=OFF
)

set(LLVH_LIT_ARGS "-sv")

add_lit_testsuite(check-hermes "Running the Hermes regression tests"
${HERMES_SOURCE_DIR}/test
${HERMES_SOURCE_DIR}/unittests
PARAMS ${HERMES_LIT_TEST_PARAMS}
DEPENDS ${HERMES_TEST_DEPS}
ARGS ${HERMES_TEST_EXTRA_ARGS}
)
set_target_properties(check-hermes PROPERTIES FOLDER "Hermes regression tests")
if(HERMES_ENABLE_TEST_SUITE)
add_subdirectory(unittests)

list(APPEND HERMES_TEST_DEPS
HermesUnitTests
hermes
hermes-repl
hermesc
hvm
interp-dispatch-bench
hdb
hbcdump
hbc-attribute
hbc-deltaprep
hbc-diff
)

set(HERMES_LIT_TEST_PARAMS
test_exec_root=${HERMES_BINARY_DIR}/test
unittests_dir=${HERMES_BINARY_DIR}/unittests
debugger_enabled=${HERMES_ENABLE_DEBUGGER}
use_flowparser=${HERMES_USE_FLOWPARSER}
jit_enabled=${HERMESVM_JIT}
jit_disassembler_enabled=${HERMESVM_JIT_DISASSEMBLER}
hbc_deltaprep=${HERMES_TOOLS_OUTPUT_DIR}/hbc-deltaprep
FileCheck=${HERMES_TOOLS_OUTPUT_DIR}//FileCheck
hermes=${HERMES_TOOLS_OUTPUT_DIR}/hermes
hermesc=${HERMES_TOOLS_OUTPUT_DIR}/hermesc
repl=${HERMES_TOOLS_OUTPUT_DIR}/hermes-repl
hdb=${HERMES_TOOLS_OUTPUT_DIR}/hdb
hbcdump=${HERMES_TOOLS_OUTPUT_DIR}/hbcdump
hbc-deltaprep=${HERMES_TOOLS_OUTPUT_DIR}/hbc-deltaprep
hbc_diff=${HERMES_TOOLS_OUTPUT_DIR}/hbc-diff
build_mode=${HERMES_ASSUMED_BUILD_MODE_IN_LIT_TEST}
exception_on_oom_enabled=${HERMESVM_EXCEPTION_ON_OOM}
serialize_enabled=${HERMESVM_SERIALIZE}
profiler=${HERMES_PROFILER_MODE_IN_LIT_TEST}
use_js_library_implementation=${HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION}
gc=${HERMESVM_GCKIND}
ubsan=${HERMES_ENABLE_UNDEFINED_BEHAVIOR_SANITIZER}
)

set(LLVH_LIT_ARGS "-sv")

add_lit_testsuite(check-hermes "Running the Hermes regression tests"
${HERMES_SOURCE_DIR}/test
${HERMES_SOURCE_DIR}/unittests
PARAMS ${HERMES_LIT_TEST_PARAMS}
DEPENDS ${HERMES_TEST_DEPS}
ARGS ${HERMES_TEST_EXTRA_ARGS}
)
set_target_properties(check-hermes PROPERTIES FOLDER "Hermes regression tests")
endif()

# This is how github release files are built.

Expand Down
50 changes: 50 additions & 0 deletions hermes.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

module HermesHelper
# BUILD_TYPE = :debug
BUILD_TYPE = :release

def self.command_exists?(bin)
"command -v #{bin} > /dev/null 2>&1"
end

def self.configure_command
"./utils/build/configure.py #{BUILD_TYPE == :release ? "--distribute" : "--build-type=Debug"} --cmake-flags='-DHERMES_ENABLE_DEBUGGER:BOOLEAN=true -DHERMES_ENABLE_FUZZING:BOOLEAN=false -DHERMES_ENABLE_TEST_SUITE:BOOLEAN=false -DCMAKE_INSTALL_PREFIX:PATH=../destroot' build"
end
end

Pod::Spec.new do |spec|
spec.name = "hermes"
spec.version = "0.5.0"
spec.summary = "Hermes is a small and lightweight JavaScript engine optimized for running React Native."
spec.description = "Hermes is a JavaScript engine optimized for fast start-up of React Native apps. It features ahead-of-time static optimization and compact bytecode."
spec.homepage = "https://hermesengine.dev"
spec.license = { type: "MIT", file: "LICENSE" }
spec.author = "Facebook"
spec.source = { git: "https://github.com/facebook/hermes.git", tag: "v#{spec.version}" }
spec.platforms = { :osx => "10.14" }

spec.preserve_paths = ["destroot/bin/*"].concat(HermesHelper::BUILD_TYPE == :debug ? ["**/*.{h,c,cpp}"] : [])
spec.source_files = "destroot/include/**/*.h"
spec.header_mappings_dir = "destroot/include"
spec.vendored_frameworks = "destroot/Library/Frameworks/hermes.framework"
spec.xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", "CLANG_CXX_LIBRARY" => "compiler-default", "GCC_PREPROCESSOR_DEFINITIONS" => "HERMES_ENABLE_DEBUGGER=1" }

spec.prepare_command = <<-EOS
if [ ! -d destroot/Library/Frameworks/hermes.framework ]; then
if #{HermesHelper.command_exists?("cmake")}; then
if #{HermesHelper.command_exists?("ninja")}; then
#{HermesHelper.configure_command} --build-system='Ninja' && cd build && ninja install
else
#{HermesHelper.configure_command} --build-system='Unix Makefiles' && cd build && make install
fi
else
echo >&2 'CMake is required to install Hermes, install it with: brew install cmake'
exit 1
fi
fi
EOS
end
2 changes: 1 addition & 1 deletion public/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

add_subdirectory(hermes)
add_subdirectory(hermes/Public)
Loading

0 comments on commit 1a04aee

Please sign in to comment.