Skip to content

Commit

Permalink
refactor(android): reorganize Gradle scripts (#2257)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Sep 27, 2024
1 parent b750d97 commit 9c6a666
Show file tree
Hide file tree
Showing 15 changed files with 446 additions and 424 deletions.
3 changes: 2 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
buildscript {
def androidDir = "${buildscript.sourceFile.getParent()}/../"
apply(from: "${androidDir}/test-app-util.gradle")
apply(from: "${androidDir}/autolink.gradle")
apply(from: "${androidDir}/dependencies.gradle")
apply(from: "${androidDir}/manifest.gradle")
}

plugins {
Expand Down
18 changes: 18 additions & 0 deletions android/autolink.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import groovy.json.JsonSlurper

ext.autolinkModules = { File projectRoot, File output, String testAppDir ->
String[] autolink = ["node", "${testAppDir}/android/autolink.mjs", projectRoot.toString(), output.toString()]
def stderr = new StringBuffer()
def proc = Runtime.runtime.exec(autolink, null, projectRoot)
proc.waitForProcessOutput(null, stderr)
if (proc.exitValue() != 0) {
throw new RuntimeException("Failed to autolink:\n${stderr}")
}

return new JsonSlurper().parseText(output.text)
}

ext.autolinkingInfo = { File buildDir ->
def autolinking = file("${buildDir}/generated/rnta/autolinking.json")
return new JsonSlurper().parseText(autolinking.text)
}
15 changes: 15 additions & 0 deletions android/config-plugins.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apply(from: "${buildscript.sourceFile.getParent()}/node.gradle")

ext.applyConfigPlugins = { File rootDir, String testAppDir ->
if (!findNodeModulesPath("@expo/config-plugins", rootDir)) {
return
}

String[] patch = ["node", "${testAppDir}/scripts/apply-config-plugins.mjs", "--android"]
def stderr = new StringBuffer()
def proc = Runtime.runtime.exec(patch, null, rootDir)
proc.waitForProcessOutput(null, stderr)
if (proc.exitValue() != 0) {
throw new RuntimeException("Failed to apply config plugins:\n${stderr}")
}
}
62 changes: 39 additions & 23 deletions android/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
ext {
apply(from: "${buildscript.sourceFile.getParent()}/test-app-util.gradle")
apply(from: "${buildscript.sourceFile.getParent()}/node.gradle")
apply(from: "${buildscript.sourceFile.getParent()}/react-native.gradle")

/**
* Returns the recommended Gradle plugin version for the specified React Native
* version.
*/
def getDefaultGradlePluginVersion = { reactNativeVersion ->
// Gradle plugin version can be found in the template:
// https://github.com/facebook/react-native/blob/main/packages/react-native/template/android/build.gradle
if (reactNativeVersion == 0 || reactNativeVersion >= v(0, 73, 0)) {
return ""
} else if (reactNativeVersion >= v(0, 71, 0)) {
return "7.3.1"
} else {
return "7.2.2"
}
/**
* Returns the recommended Gradle plugin version for the specified React Native
* version.
*/
def getDefaultGradlePluginVersion = { int reactNativeVersion ->
// Gradle plugin version can be found in the template:
// https://github.com/facebook/react-native/blob/main/packages/react-native/template/android/build.gradle
if (reactNativeVersion == 0 || reactNativeVersion >= v(0, 73, 0)) {
return ""
} else if (reactNativeVersion >= v(0, 71, 0)) {
return "7.3.1"
} else {
return "7.2.2"
}
}

// TODO: Bump `minSdkVersion` to 24 in 4.0
def getDefaultMinSdkVersion = { reactNativeVersion ->
if (reactNativeVersion >= v(0, 76, 0)) {
return 24
} else {
return 23
}
// TODO: Bump `minSdkVersion` to 24 in 4.0
def getDefaultMinSdkVersion = { int reactNativeVersion ->
if (reactNativeVersion >= v(0, 76, 0)) {
return 24
} else {
return 23
}
}

def getKotlinVersion = { File baseDir ->
def fallbackVersion = "1.7.21"

def packagePath = findNodeModulesPath("react-native", baseDir)
def versionCatalog = file("${packagePath}/gradle/libs.versions.toml")
if (!versionCatalog.exists()) {
return fallbackVersion
}

def m = versionCatalog.text =~ /kotlin = "([.0-9]+)"/
def version = m.size() > 0 ? m[0][1] : fallbackVersion
logger.info("Detected Kotlin version: ${version}")
return version
}

ext {
reactNativeVersion = getPackageVersionNumber("react-native", rootDir)

compileSdkVersion = rootProject.findProperty("react.compileSdkVersion") ?: 34
Expand Down
2 changes: 1 addition & 1 deletion android/gradle-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const {
const INT_MAX = 2 ** 31 - 1;

/**
* This table is also used by `android/test-app-util.gradle`!
* This table is also used by `android/utils.gradle`!
*
* We have two implementations because there are currently two ways to build the
* Android app. If built via `@react-native-community/cli`, this script will be
Expand Down
128 changes: 128 additions & 0 deletions android/manifest.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import groovy.json.JsonSlurper
import java.nio.file.Paths

def _manifest = null

ext.getAppName = {
def manifest = getManifest()
if (manifest != null) {
def displayName = manifest["displayName"]
if (displayName instanceof String) {
return displayName
}

def name = manifest["name"]
if (name instanceof String) {
return name
}
}

return "ReactTestApp"
}

ext.getApplicationId = {
def manifest = getManifest()
if (manifest != null) {
def config = manifest["android"]
if (config instanceof Object && config.containsKey("package")) {
return config["package"]
}
}

return "com.microsoft.reacttestapp"
}

ext.getArchitectures = { Project project ->
def archs = project.findProperty("react.nativeArchitectures")
?: project.findProperty("reactNativeArchitectures")
return archs != null
? archs.split(",")
: ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

ext.getManifest = {
if (_manifest == null) {
def manifestFile = findFile("app.json")
if (manifestFile == null) {
return null
}

_manifest = new JsonSlurper().parseText(manifestFile.text)
}
return _manifest
}

ext.getSigningConfigs = {
def safeSetMap = { varName, map, prop, defaultVal ->
map[varName] = prop.containsKey(varName) ? prop.get(varName) : defaultVal
}

def definedConfigs = new LinkedHashMap<String, Object>()
def manifestFile = findFile("app.json")
if (manifestFile != null) {
def manifest = new JsonSlurper().parseText(manifestFile.text)

if (!manifest["android"]) {
return definedConfigs
}

def signingConfigs = manifest["android"]["signingConfigs"]
if (signingConfigs) {
signingConfigs.each { config ->
def configName = config.key
def props = config.value
def pathStoreFile = props.containsKey("storeFile")
? Paths.get(manifestFile.getParent(), props.get("storeFile")).normalize().toAbsolutePath()
: null
if (pathStoreFile == null || !file(pathStoreFile).exists() || !file(pathStoreFile).isFile()) {
throw new FileNotFoundException("Signing storeFile for flavor ${configName} is missing: " + pathStoreFile)
}

def signConfig = new LinkedHashMap<String, Object>()
safeSetMap("keyAlias", signConfig, props, "androiddebugkey")
safeSetMap("keyPassword", signConfig, props, "android")
safeSetMap("storePassword", signConfig, props, "android")
signConfig["storeFile"] = pathStoreFile.toFile()
definedConfigs[configName] = signConfig
}
}
}

return definedConfigs
}

ext.getSingleAppMode = {
def manifest = getManifest()
if (manifest != null) {
def singleApp = manifest["singleApp"]
if (singleApp instanceof String) {
return singleApp
}
}

return false
}

ext.getVersionCode = {
def manifest = getManifest()
if (manifest != null) {
def config = manifest["android"]
if (config instanceof Object && config.containsKey("versionCode")) {
return config["versionCode"]
}
}

return 1
}

ext.getVersionName = {
def manifest = getManifest()
if (manifest != null) {
def version = manifest["version"]
if (version instanceof String) {
return version
}
}

return "1.0"
}
29 changes: 29 additions & 0 deletions android/media-types.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
ext.isFontFile = { File file ->
// https://github.com/facebook/react-native/blob/3dfedbc1aec18a4255e126fde96d5dc7b1271ea7/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java#L28
return [".otf", ".ttf"].any { file.name.endsWith(it) }
}

ext.isMediaFile = { File file ->
// https://developer.android.com/media/platform/supported-formats
return [
".3gp",
".aac",
".amr",
".flac",
".imy",
".m4a",
".mid",
".mkv",
".mp3",
".mp4",
".mxmf",
".ogg",
".ota",
".rtttl",
".rtx",
".ts",
".wav",
".webm",
".xmf",
].any { file.name.endsWith(it) }
}
74 changes: 74 additions & 0 deletions android/node.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import groovy.json.JsonSlurper
import java.nio.file.Paths

apply(from: "${buildscript.sourceFile.getParent()}/utils.gradle")

def _dependencies = [:]

/**
* Finds the path of the installed npm package with the given name using Node's
* module resolution algorithm, which searches "node_modules" directories up to
* the file system root. This handles various cases, including:
*
* - Working in the open-source RN repo:
* Gradle: /path/to/react-native/ReactAndroid
* Node module: /path/to/react-native/node_modules/[package]
*
* - Installing RN as a dependency of an app and searching for hoisted
* dependencies:
* Gradle: /path/to/app/node_modules/react-native/ReactAndroid
* Node module: /path/to/app/node_modules/[package]
*
* - Working in a larger repo (e.g., Facebook) that contains RN:
* Gradle: /path/to/repo/path/to/react-native/ReactAndroid
* Node module: /path/to/repo/node_modules/[package]
*
* The search begins at the given base directory (a File object). The returned
* path is a string.
*/
ext.findNodeModulesPath = { String packageName, File baseDir ->
def module = _dependencies[packageName]
if (module != null) {
return module.path
}

def basePath = baseDir.toPath().normalize()

// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
// Resolve the real path in case we're dealing with symlinks
def resolvedPath = candidatePath.toRealPath().toString()
_dependencies[packageName] = [path: resolvedPath]
return resolvedPath
}

basePath = basePath.getParent()
}

return null
}

ext.getPackageManifest = { String packageName, File baseDir ->
def module = _dependencies[packageName]
if (module != null && module.manifest != null) {
return module.manifest
}

def packagePath = findNodeModulesPath(packageName, baseDir)
def packageJson = file("${packagePath}/package.json")
def manifest = new JsonSlurper().parseText(packageJson.text)
_dependencies[packageName].manifest = manifest
return manifest
}

ext.getPackageVersion = { String packageName, File baseDir ->
def manifest = getPackageManifest(packageName, baseDir)
return manifest["version"]
}

ext.getPackageVersionNumber = { String packageName, File baseDir ->
return toVersionNumber(getPackageVersion(packageName, baseDir))
}
Loading

0 comments on commit 9c6a666

Please sign in to comment.