Skip to content

Commit a3d9892

Browse files
cortinicofacebook-github-bot
authored andcommitted
Build Hermes from Source (#33396)
Summary: Pull Request resolved: #33396 This commit fully unplugs the `ReactAndroid` from using hermes from the NPM package and plugs the usage of Hermes via the `packages/hermes-engine` Gradle build. I've used prefab to share the .so between the two builds, so we don't need any extra machinery to make this possible. Moreover, I've added a `buildHermesFromSource` property, which defaults to false when RN is imported, but is set to true when RN is opened for local development. This should allow us to distribute the `react-native` NPM package and users could potentially toggle which source to use (but see below). Changelog: [Android] [Changed] - Build Hermes from Source Reviewed By: hramos Differential Revision: D34389875 fbshipit-source-id: 107cbe3686daf7607a1f0f75202f24cd80ce64bb
1 parent d34a75e commit a3d9892

File tree

16 files changed

+134
-135
lines changed

16 files changed

+134
-135
lines changed

.circleci/Dockerfiles/Dockerfile.android

+2-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# and build a Android application that can be used to run the
1515
# tests specified in the scripts/ directory.
1616
#
17-
FROM reactnativecommunity/react-native-android:5.2
17+
FROM reactnativecommunity/react-native-android:5.4
1818

1919
LABEL Description="React Native Android Test Image"
2020
LABEL maintainer="Héctor Ramos <[email protected]>"
@@ -53,6 +53,4 @@ ADD . /app
5353

5454
RUN yarn
5555

56-
RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
57-
58-
RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1
56+
RUN ./gradlew :ReactAndroid:assembleDebug

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ executors:
6767
reactnativeandroid:
6868
<<: *defaults
6969
docker:
70-
- image: reactnativecommunity/react-native-android:5.2
70+
- image: reactnativecommunity/react-native-android:5.4
7171
resource_class: "large"
7272
environment:
7373
- TERM: "dumb"

ReactAndroid/build.gradle

+20-27
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH")
4545
// Setup build type for NDK, supported values: {debug, release}
4646
def nativeBuildType = System.getenv("NATIVE_BUILD_TYPE") ?: "release"
4747

48+
// We put the publishing version from gradle.properties inside ext. so other
49+
// subprojects can access it as well.
50+
ext.publishing_version = VERSION_NAME
51+
4852
task createNativeDepsDirectories {
4953
downloadsDir.mkdirs()
5054
thirdPartyNdkDir.mkdirs()
@@ -127,24 +131,6 @@ final def prepareLibevent = tasks.register("prepareLibevent", PrepareLibeventTas
127131
it.outputDir.set(new File(thirdPartyNdkDir, "libevent"))
128132
}
129133

130-
task prepareHermes(dependsOn: createNativeDepsDirectories, type: Copy) {
131-
def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
132-
if (!hermesPackagePath) {
133-
throw new GradleScriptException("Could not find the hermes-engine npm package", null)
134-
}
135-
136-
def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar")
137-
if (!hermesAAR.exists()) {
138-
throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"", null)
139-
}
140-
141-
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
142-
143-
from soFiles
144-
from "src/main/jni/first-party/hermes/Android.mk"
145-
into "$thirdPartyNdkDir/hermes"
146-
}
147-
148134
task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
149135
src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
150136
onlyIfNewer(true)
@@ -259,18 +245,11 @@ final def extractNativeDependencies = tasks.register('extractNativeDependencies'
259245
it.extractHeadersConfiguration.setFrom(configurations.extractHeaders)
260246
it.extractJniConfiguration.setFrom(configurations.extractJNI)
261247
it.baseOutputDir = project.file("src/main/jni/first-party/")
262-
// Sadly this task as an output folder path that is directly dependent on
263-
// the task input (i.e. src/main/jni/first-party/<package-name>/...
264-
// This means that this task is using the parent folder (first-party/) as
265-
// @OutputFolder. The `prepareHermes` task will also output inside that
266-
// folder and if the two tasks happen to be inside the same run, we want
267-
// `extractNativeDependencies` to run after `prepareHermes` to do not
268-
// invalidate the input/output calculation for this task.
269-
it.mustRunAfter(prepareHermes)
270248
}
271249

272250
task installArchives {
273251
dependsOn("publishReleasePublicationToNpmRepository")
252+
dependsOn(":ReactAndroid:hermes-engine:installArchives")
274253
}
275254

276255
// Creating sources with comments
@@ -314,6 +293,7 @@ android {
314293
"REACT_COMMON_DIR=$projectDir/../ReactCommon",
315294
"REACT_GENERATED_SRC_DIR=$buildDir/generated/source",
316295
"REACT_SRC_DIR=$projectDir/src/main/java/com/facebook/react",
296+
"APP_STL=c++_shared",
317297
"-j${ndkBuildJobs()}"
318298

319299
if (Os.isFamily(Os.FAMILY_MAC)) {
@@ -333,7 +313,7 @@ android {
333313
}
334314
}
335315

336-
preBuild.dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractNativeDependencies)
316+
preBuild.dependsOn(prepareJSC, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractNativeDependencies)
337317
preBuild.dependsOn("generateCodegenArtifactsFromSchema")
338318

339319
sourceSets.main {
@@ -366,6 +346,10 @@ android {
366346
extractJNI
367347
javadocDeps.extendsFrom api
368348
}
349+
350+
buildFeatures {
351+
prefab true
352+
}
369353
}
370354

371355
dependencies {
@@ -388,6 +372,15 @@ dependencies {
388372
extractHeaders("com.facebook.fbjni:fbjni:0.2.2:headers")
389373
extractJNI("com.facebook.fbjni:fbjni:0.2.2")
390374

375+
// It's up to the consumer to decide if hermes should be included or not.
376+
// Therefore hermes-engine is a compileOnly dependency.
377+
// Moreover, you can toggle where to get the dependency with `buildHermesFromSource`.
378+
if (project.getProperties().getOrDefault("buildHermesFromSource", "false").toBoolean()) {
379+
compileOnly(project(":ReactAndroid:hermes-engine"))
380+
} else {
381+
compileOnly("com.facebook.react:hermes-engine:${VERSION_NAME}")
382+
}
383+
391384
javadocDeps("com.squareup:javapoet:1.13.0")
392385

393386
testImplementation("junit:junit:${JUNIT_VERSION}")

ReactAndroid/hermes-engine/.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Make sure we never publish the build folders to npm.
2+
build/
3+
.cxx/

ReactAndroid/hermes-engine/build.gradle

+33-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ if (hermesVersionFile.exists()) {
2222
hermesVersion = hermesVersionFile.text
2323
}
2424
def ndkBuildJobs = Runtime.runtime.availableProcessors().toString()
25+
def prefabHeadersDir = new File("$buildDir/prefab-headers")
2526

2627
// We inject the JSI directory used inside the Hermes build with the -DJSI_DIR config.
2728
def jsiDir = rootProject.file("ReactCommon/jsi")
@@ -42,6 +43,10 @@ task downloadNdkBuildDependencies() {
4243
dependsOn(downloadHermes)
4344
}
4445

46+
task installArchives {
47+
dependsOn("publishAllPublicationsToNpmRepository")
48+
}
49+
4550
task unzipHermes(dependsOn: downloadHermes, type: Copy) {
4651
from(tarTree(downloadHermes.dest)) {
4752
eachFile { file ->
@@ -56,7 +61,7 @@ task unzipHermes(dependsOn: downloadHermes, type: Copy) {
5661
}
5762

5863

59-
task configureNinjaForHermes(dependsOn: unzipHermes, type: org.gradle.api.tasks.Exec) {
64+
task configureNinjaForHermes(dependsOn: unzipHermes, type: Exec) {
6065
workingDir(hermesDir)
6166
commandLine(windowsAwareCommandLine(
6267
"python3",
@@ -67,11 +72,19 @@ task configureNinjaForHermes(dependsOn: unzipHermes, type: org.gradle.api.tasks.
6772
))
6873
}
6974

70-
task buildNinjaForHermes(dependsOn: configureNinjaForHermes, type: org.gradle.api.tasks.Exec) {
75+
task buildNinjaForHermes(dependsOn: configureNinjaForHermes, type: Exec) {
7176
workingDir(hermesDir)
7277
commandLine(windowsAwareCommandLine("cmake", "--build", "./ninja_build", "--target", "hermesc", "-j", ndkBuildJobs))
7378
}
7479

80+
task prepareHeadersForPrefab(dependsOn: unzipHermes, type: Copy) {
81+
from("$hermesDir/API")
82+
from("$hermesDir/public")
83+
include("**/*.h")
84+
exclude("jsi/**")
85+
into(prefabHeadersDir)
86+
}
87+
7588
static def windowsAwareCommandLine(String... commands) {
7689
def newCommands = []
7790
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
@@ -88,6 +101,7 @@ def reactNativeArchitectures() {
88101

89102
android {
90103
compileSdkVersion 31
104+
buildToolsVersion = "31.0.0"
91105

92106
// Used to override the NDK path & version on internal CI
93107
if (System.getenv("ANDROID_NDK") != null && System.getenv("LOCAL_ANDROID_NDK_VERSION") != null) {
@@ -180,12 +194,28 @@ android {
180194
allVariants()
181195
}
182196
}
197+
198+
buildFeatures {
199+
prefabPublishing true
200+
}
201+
202+
prefab {
203+
libhermes {
204+
headers prefabHeadersDir.absolutePath
205+
libraryName "libhermes"
206+
}
207+
}
183208
}
184209

185210
afterEvaluate {
186211
preBuild.dependsOn(buildNinjaForHermes)
212+
preBuild.dependsOn(prepareHeadersForPrefab)
187213
// Needed as some of the native sources needs to be downloaded
188214
// before configureCMakeRelease/configureCMakeMinSizeRel could be executed.
215+
reactNativeArchitectures().each { architecture ->
216+
tasks.named("configureCMakeMinSizeRel[${architecture}]") { dependsOn(preBuild) }
217+
tasks.named("configureCMakeRelease[${architecture}]") { dependsOn(preBuild) }
218+
}
189219
configureCMakeRelease.dependsOn(preBuild)
190220
configureCMakeMinSizeRel.dependsOn(preBuild)
191221

@@ -206,4 +236,4 @@ afterEvaluate {
206236
}
207237

208238
group = "com.facebook.react"
209-
version = VERSION_NAME
239+
version = parent.publishing_version
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
VERSION_NAME=1000.0.0-main
2-
android.disableAutomaticComponentCreation=true
1+
android.disableAutomaticComponentCreation=true

ReactAndroid/src/main/java/com/facebook/hermes/instrumentation/Android.mk

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
LOCAL_PATH := $(call my-dir)
88
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
99

10-
include $(REACT_NATIVE)/ReactCommon/common.mk
1110
include $(CLEAR_VARS)
1211

1312
LOCAL_MODULE := jsijniprofiler
1413

1514
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
1615

17-
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(call find-node-module,$(LOCAL_PATH),hermes-engine)/android/include
16+
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi
1817

1918
LOCAL_CPP_FEATURES := exceptions
2019

@@ -29,5 +28,3 @@ LOCAL_SHARED_LIBRARIES := \
2928

3029
include $(BUILD_SHARED_LIBRARY)
3130

32-
33-
include $(CLEAR_VARS)

ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/Android.mk

+32-32
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,47 @@
66
LOCAL_PATH := $(call my-dir)
77
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
88

9-
include $(REACT_NATIVE)/ReactCommon/common.mk
10-
include $(CLEAR_VARS)
9+
ifeq ($(APP_OPTIM),debug)
10+
include $(CLEAR_VARS)
1111

12-
LOCAL_MODULE := hermes-executor-release
12+
LOCAL_MODULE := hermes-executor-debug
13+
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
1314

14-
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
15+
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
1516

16-
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(call find-node-module,$(LOCAL_PATH),hermes-engine)/android/include
17+
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi
1718

18-
LOCAL_CPP_FEATURES := exceptions
19+
LOCAL_CPP_FEATURES := exceptions
1920

20-
LOCAL_STATIC_LIBRARIES := libjsireact libhermes-executor-common-release
21-
LOCAL_SHARED_LIBRARIES := \
22-
libfb \
23-
libfbjni \
24-
libfolly_json \
25-
libhermes \
26-
libjsi \
27-
libreactnativejni
21+
LOCAL_STATIC_LIBRARIES := libjsireact libhermes-executor-common-debug
22+
LOCAL_SHARED_LIBRARIES := \
23+
libfb \
24+
libfbjni \
25+
libfolly_json \
26+
libhermes \
27+
libjsi \
28+
libreactnativejni
2829

29-
include $(BUILD_SHARED_LIBRARY)
30+
include $(BUILD_SHARED_LIBRARY)
31+
else
32+
include $(CLEAR_VARS)
3033

34+
LOCAL_MODULE := hermes-executor-release
3135

32-
include $(CLEAR_VARS)
36+
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
3337

34-
LOCAL_MODULE := hermes-executor-debug
35-
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
38+
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi
3639

37-
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
40+
LOCAL_CPP_FEATURES := exceptions
3841

39-
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(call find-node-module,$(LOCAL_PATH),hermes-engine)/android/include
42+
LOCAL_STATIC_LIBRARIES := libjsireact libhermes-executor-common-release
43+
LOCAL_SHARED_LIBRARIES := \
44+
libfb \
45+
libfbjni \
46+
libfolly_json \
47+
libhermes \
48+
libjsi \
49+
libreactnativejni
4050

41-
LOCAL_CPP_FEATURES := exceptions
42-
43-
LOCAL_STATIC_LIBRARIES := libjsireact libhermes-executor-common-debug
44-
LOCAL_SHARED_LIBRARIES := \
45-
libfb \
46-
libfbjni \
47-
libfolly_json \
48-
libhermes \
49-
libjsi \
50-
libreactnativejni
51-
52-
include $(BUILD_SHARED_LIBRARY)
51+
include $(BUILD_SHARED_LIBRARY)
52+
endif

ReactAndroid/src/main/jni/first-party/hermes/Android.mk

-10
This file was deleted.

ReactAndroid/src/main/jni/react/jni/Android.mk

+8-1
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,18 @@ $(call import-module,jsiexecutor)
143143
$(call import-module,logger)
144144
$(call import-module,callinvoker)
145145
$(call import-module,reactperflogger)
146-
$(call import-module,hermes)
147146
$(call import-module,runtimeexecutor)
148147
$(call import-module,react/renderer/runtimescheduler)
149148
$(call import-module,react/nativemodule/core)
150149

150+
# This block is needed only because we build the project on NDK r17 internally.
151+
ifneq ($(call ndk-major-at-least,21),true)
152+
$(call import-add-path,$(NDK_GRADLE_INJECTED_IMPORT_PATH))
153+
endif
154+
155+
$(call import-module,prefab/hermes-engine)
156+
157+
151158
include $(REACT_SRC_DIR)/reactperflogger/jni/Android.mk
152159
# TODO (T48588859): Restructure this target to align with dir structure: "react/nativemodule/..."
153160
# Note: Update this only when ready to minimize breaking changes.

ReactCommon/common.mk

-29
This file was deleted.

0 commit comments

Comments
 (0)