Skip to content

Commit

Permalink
[android] Add @shopify/react-native-skia vendoring support for Expo Go (
Browse files Browse the repository at this point in the history
expo#17964)

# Why

like expo#17704 but for android
close ENG-4889

# How

- introduce vendor modules autolinking for expo go.
previously, reanimated is the only vendor module having cxx code, we had a fixed versioning script for reanimated. because react-native-skia also has cxx code, i'm trying to keep the versioning more modularized and like what we did for ios versioning. ideally, a vendor module should have a dedicated gradle project. that's why i introduced a vendor module autolinking here.
- vendoring tool: add react-native-skia support
- versioning tool: trying to add a minimum support for new style versioning like ios.
currently, after transforming the code, i still prebuild the *.so to `jniLibs`. in the future when we commit `android/versioned-react-native/ABI46_0_0` to git, we can always build the versioned *.so from source. 

# Test Plan

- android unversioned expo go + NCL skia
- android versioned expo go + NCL skia

Co-authored-by: Tomasz Sapeta <[email protected]>
  • Loading branch information
Kudo and tsapeta authored Jul 6, 2022
1 parent 00afe14 commit 1833af4
Show file tree
Hide file tree
Showing 17 changed files with 615 additions and 37 deletions.
3 changes: 3 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,6 @@
##### Segment #####
-keep class com.segment.analytics.** { *; }
-keep class androidx.lifecycle.DefaultLifecycleObserver

##### skia #####
-keep class com.shopify.reactnative.skia.** { *; }
2 changes: 2 additions & 0 deletions android/expoview/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
apply plugin: 'de.undercouch.download'
apply plugin: 'kotlin-kapt'
apply from: new File(rootDir, "versioning_linking.gradle")

// WHEN_VERSIONING_REMOVE_FROM_HERE
//maven repository info
Expand Down Expand Up @@ -421,6 +422,7 @@ dependencies {
implementation "androidx.work:work-runtime:2.7.1"
}

useVendoredModulesForExpoView('unversioned')

// WHEN_PREPARING_REANIMATED_REMOVE_FROM_HERE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import com.shopify.reactnative.skia.RNSkiaPackage
import expo.modules.adapters.react.ReactModuleRegistryProvider
import expo.modules.core.interfaces.Package
import expo.modules.core.interfaces.SingletonModule
Expand Down Expand Up @@ -135,6 +136,7 @@ class ExponentPackage : ReactPackage {
nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
nativeModules.addAll(stripePackage.createNativeModules(reactContext))
nativeModules.addAll(skiaPackage.createNativeModules(reactContext))

// Call to create native modules has to be at the bottom --
// -- ExpoModuleRegistryAdapter uses the list of native modules
Expand Down Expand Up @@ -178,7 +180,8 @@ class ExponentPackage : ReactPackage {
RNCPickerPackage(),
ReactSliderPackage(),
PagerViewPackage(),
stripePackage
stripePackage,
skiaPackage
)
)
viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
Expand Down Expand Up @@ -209,8 +212,9 @@ class ExponentPackage : ReactPackage {
private val singletonModules = mutableListOf<SingletonModule>()
private val singletonModulesClasses = mutableSetOf<Class<*>>()

// Need to avoid initializing 2 StripeSdkPackages
// Need to avoid initializing duplicated packages
private val stripePackage = StripeSdkPackage()
private val skiaPackage = RNSkiaPackage()

fun kernelExponentPackage(
context: Context,
Expand Down
4 changes: 4 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pluginManagement {
include ':app'

apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
apply from: new File(rootDir, "versioning_linking.gradle")

/* UNCOMMENT WHEN DISTRIBUTING
useExpoModules([
Expand Down Expand Up @@ -68,4 +69,7 @@ useExpoModules([
'expo-dev-client'
]
])

useVendoredModulesForSettingsGradle('unversioned')

// WHEN_DISTRIBUTING_REMOVE_TO_HERE
34 changes: 34 additions & 0 deletions android/versioning_linking.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import java.nio.file.Paths


def normalizeProjectName(String projectRoot) {
return projectRoot.replace('/', '_')
}

/**
* Link vendored modules into settings.gradle
*/
ext.useVendoredModulesForSettingsGradle = { String sdkVersion ->
def vendoredRoot = Paths.get(rootDir.toString(), 'vendored', sdkVersion).toString()
fileTree(dir: vendoredRoot, include: '**/build.gradle').each { gradleFile ->
def projectRoot = rootDir.toPath().relativize(gradleFile.toPath()).getParent().getParent().toString()
def projectName = normalizeProjectName(projectRoot)

include(":${projectName}")
project(":${projectName}").projectDir = gradleFile.getParentFile()
}
}


/**
* Link vendored modules into settings.gradle
*/
ext.useVendoredModulesForExpoView = { String sdkVersion ->
def vendoredRoot = Paths.get(rootDir.toString(), 'vendored', sdkVersion).toString()
fileTree(dir: vendoredRoot, include: '**/build.gradle').each { gradleFile ->
def projectRoot = rootDir.toPath().relativize(gradleFile.toPath()).getParent().getParent().toString()
def projectName = normalizeProjectName(projectRoot)

dependencies.add('implementation', project(":${projectName}"))
}
}
26 changes: 26 additions & 0 deletions tools/src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,29 @@ export function arrayize<T>(value: T | T[]): T[] {
}
return value != null ? [value] : [];
}

/**
* Execute `patch` command for given patch content
*/
export async function applyPatchAsync(options: {
patchContent: string;
cwd: string;
reverse?: boolean;
stripPrefixNum?: number;
}) {
const args: string[] = [];
if (options.stripPrefixNum != null) {
// -pN passing to the `patch` command for striping slashed prefixes
args.push(`-p${options.stripPrefixNum}`);
}
if (options.reverse) {
args.push('-R');
}

const procPromise = spawnAsync('patch', args, {
cwd: options.cwd,
});
procPromise.child.stdin?.write(options.patchContent);
procPromise.child.stdin?.end();
await procPromise;
}
4 changes: 3 additions & 1 deletion tools/src/commands/AddSDKVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ async function action(options: ActionOptions) {
await IosVersioning.reinstallPodsAsync(options.reinstall, options.preventReinstall);
return;
case 'android':
return AndroidVersioning.addVersionAsync(sdkVersion);
await AndroidVersioning.addVersionAsync(sdkVersion);
await AndroidVersioning.versionVendoredModulesAsync(sdkNumber, null);
return;
default:
throw new Error(`Platform '${options.platform}' is not supported.`);
}
Expand Down
2 changes: 2 additions & 0 deletions tools/src/vendoring/AndroidVendoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ export async function vendorAsync(
targetDirectory,
transforms: config?.transforms ?? {},
});

await config.postCopyFilesHookAsync?.(sourceDirectory, targetDirectory);
}
34 changes: 34 additions & 0 deletions tools/src/vendoring/config/expoGoConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import fs from 'fs-extra';
import path from 'path';

import { Podspec } from '../../CocoaPods';
import { EXPOTOOLS_DIR } from '../../Constants';
import logger from '../../Logger';
import { applyPatchAsync } from '../../Utils';
import { VendoringTargetConfig } from '../types';

const config: VendoringTargetConfig = {
Expand Down Expand Up @@ -307,6 +310,37 @@ const config: VendoringTargetConfig = {
'"$(PODS_ROOT)/Headers/Private/React-bridging/react/bridging" "$(PODS_CONFIGURATION_BUILD_DIR)/React-bridging/react_bridging.framework/Headers"';
},
},
android: {
includeFiles: ['android/**', 'cpp/**'],
async postCopyFilesHookAsync(sourceDirectory, targetDirectory) {
// copy skia static libraries to common directory
const commonLibsDir = path.join(targetDirectory, '..', '..', '..', 'common', 'libs');
await fs.ensureDir(commonLibsDir);
await fs.copy(path.join(sourceDirectory, 'libs', 'android'), commonLibsDir);

// patch gradle and cmake files
const patchFile = path.join(
EXPOTOOLS_DIR,
'src',
'vendoring',
'config',
'react-native-skia.patch'
);
const patchContent = await fs.readFile(patchFile, 'utf8');
try {
await applyPatchAsync({
patchContent,
cwd: targetDirectory,
stripPrefixNum: 0,
});
} catch (e) {
logger.error(
`Failed to apply patch: \`patch -p0 -d '${targetDirectory}' < ${patchFile}\``
);
throw e;
}
},
},
},
},
};
Expand Down
139 changes: 139 additions & 0 deletions tools/src/vendoring/config/react-native-skia.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
--- android/CMakeLists.txt
+++ android/CMakeLists.txt
@@ -34,14 +34,14 @@ add_library(
"${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkDrawViewImpl.cpp"
"${PROJECT_SOURCE_DIR}/cpp/rnskia-android/SkiaOpenGLRenderer.cpp"

- "${PROJECT_SOURCE_DIR}/cpp/jsi/JsiHostObject.cpp"
+ "${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiHostObject.cpp"

- "${PROJECT_SOURCE_DIR}/cpp/rnskia/RNSkManager.cpp"
- "${PROJECT_SOURCE_DIR}/cpp/rnskia/RNSkDrawView.cpp"
- "${PROJECT_SOURCE_DIR}/cpp/rnskia/RNSkDispatchQueue.cpp"
+ "${PROJECT_SOURCE_DIR}/../cpp/rnskia/RNSkManager.cpp"
+ "${PROJECT_SOURCE_DIR}/../cpp/rnskia/RNSkDrawView.cpp"
+ "${PROJECT_SOURCE_DIR}/../cpp/rnskia/RNSkDispatchQueue.cpp"


- "${PROJECT_SOURCE_DIR}/cpp/api/third_party/CSSColorParser.cpp"
+ "${PROJECT_SOURCE_DIR}/../cpp/api/third_party/CSSColorParser.cpp"

)

@@ -56,28 +56,28 @@ target_include_directories(
"${NODE_MODULES_DIR}/react-native/ReactCommon/react/nativemodule/core"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni"

- cpp/skia/include/config/
- cpp/skia/include/core/
- cpp/skia/include/effects/
- cpp/skia/include/utils/
- cpp/skia/include/pathops/
- cpp/skia/modules/
- cpp/skia/include/
- cpp/skia
-
- cpp/api
- cpp/jsi
- cpp/jni/include
- cpp/rnskia-android
- cpp/rnskia
- cpp/rnskia/values
- cpp/utils
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/config/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/core/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/effects/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/utils/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/pathops/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/modules/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia/include/
+ ${PROJECT_SOURCE_DIR}/../cpp/skia
+
+ ${PROJECT_SOURCE_DIR}/../cpp/api
+ ${PROJECT_SOURCE_DIR}/../cpp/jsi
+ ${PROJECT_SOURCE_DIR}/cpp/jni/include
+ ${PROJECT_SOURCE_DIR}/cpp/rnskia-android
+ ${PROJECT_SOURCE_DIR}/../cpp/rnskia
+ ${PROJECT_SOURCE_DIR}/../cpp/rnskia/values
+ ${PROJECT_SOURCE_DIR}/../cpp/utils

${libfbjni_include_DIRS}
)

# Import prebuilt SKIA libraries
-set (SKIA_LIBS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../libs/android/${ANDROID_ABI}")
+set (SKIA_LIBS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/libs/${ANDROID_ABI}")
add_library(skia STATIC IMPORTED)
set_property(TARGET skia PROPERTY IMPORTED_LOCATION "${SKIA_LIBS_PATH}/libskia.a")

--- android/build.gradle
+++ android/build.gradle
@@ -43,13 +43,17 @@ static def findNodeModules(baseDir) {
throw new GradleException("React-Native-Skia: Failed to find node_modules/ path!")
}

-def nodeModules = findNodeModules(projectDir)
+def nodeModules = Paths.get(projectDir.getPath(), '../../../../../..', 'react-native-lab').toString()
logger.warn("react-native-skia: node_modules/ found at: ${nodeModules}")

def sourceBuild = false
def defaultDir = null
def androidSourcesDir = null
def androidSourcesName = 'React Native sources'
+def reactNativeArchitectures() {
+ def value = project.getProperties().get("reactNativeArchitectures")
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
+}

def buildType = "debug"
tasks.all({ task ->
@@ -77,7 +81,7 @@ if (!defaultDir.exists()) {
}

def prebuiltDir = sourceBuild
- ? "$nodeModules/react-native/ReactAndroid/src/main/jni/prebuilt/lib"
+ ? Paths.get(findProject(":ReactAndroid").getProjectDir().toString(), "build", "intermediates", "library_*", "*", "jni")
: "$buildDir/react-native-0*/jni"


@@ -119,7 +123,7 @@ android {
externalNativeBuild {
cmake {
cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID"
- abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
+ abiFilters (*reactNativeArchitectures())
arguments '-DANDROID_STL=c++_shared',
"-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}",
"-DNODE_MODULES_DIR=${nodeModules}",
@@ -189,8 +193,10 @@ dependencies {
} else {
// React Native >= 0.69
def rnAarMatcher = "**/react-native/**/*${buildType}.aar"
- def rnAAR = fileTree("${nodeModules}/react-native/android").matching({ it.include rnAarMatcher }).singleFile
- extractJNI(files(rnAAR))
+ def rnAAR = fileTree("${nodeModules}/react-native/android").matching({ it.include rnAarMatcher })
+ if (rnAAR.any()) {
+ extractJNI(files(rnAAR))
+ }
}
}

@@ -255,4 +261,16 @@ def nativeBuildDependsOn(dependsOnTask, variant) {
afterEvaluate {
nativeBuildDependsOn(extractAARHeaders, null)
nativeBuildDependsOn(extractJNIFiles, null)
-}
\ No newline at end of file
+}
+
+tasks.whenTaskAdded { task ->
+ if (!task.name.contains("Clean") && (task.name.contains('externalNativeBuild') || task.name.startsWith('configureCMake'))) {
+ if (sourceBuild) {
+ def currentBuildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
+ task.dependsOn(":ReactAndroid:copy${currentBuildType}JniLibsProjectOnly")
+ }
+ } else if (task.name.startsWith('generateJsonModel') && sourceBuild) {
+ def currentBuildType = task.name.endsWith('Debug') ? 'Debug' : 'Release'
+ task.dependsOn(":ReactAndroid:copy${currentBuildType}JniLibsProjectOnly")
+ }
+}
3 changes: 3 additions & 0 deletions tools/src/vendoring/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export type VendoringModuleConfig = {
android?: VendoringModulePlatformConfig<{
includeFiles?: string | string[];
excludeFiles?: string | string[];

// using this hook to do some customization after copying vendoring files
postCopyFilesHookAsync?: (sourceDirectory: string, targetDirectory: string) => Promise<void>;
}>;
};

Expand Down
12 changes: 11 additions & 1 deletion tools/src/versioning/android/copyExpoview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';

import { copyFileWithTransformsAsync } from '../../Transforms';
import { searchFilesAsync } from '../../Utils';
import { expoviewTransforms } from './expoviewTransforms';
import { expoviewTransforms } from './transforms/expoviewTransforms';

export async function copyExpoviewAsync(sdkVersion: string, androidDir: string): Promise<void> {
const abiVersion = `abi${sdkVersion.replace(/\./g, '_')}`;
Expand Down Expand Up @@ -43,4 +43,14 @@ export async function copyExpoviewAsync(sdkVersion: string, androidDir: string):
)
);
}
const vendoredLinking = `useVendoredModulesForSettingsGradle('sdk${sdkVersion.split('.')[0]}')`;
if (!settingsGradle.match(vendoredLinking)) {
await fs.writeFile(
settingsGradlePath,
settingsGradle.replace(
/(^useVendoredModulesForSettingsGradle\('unversioned'\))/gm,
`$1\n${vendoredLinking}`
)
);
}
}
Loading

0 comments on commit 1833af4

Please sign in to comment.