Skip to content

Commit

Permalink
Merge pull request #4 from klarna/packaging-options-helper
Browse files Browse the repository at this point in the history
Packaging options helper
  • Loading branch information
OleksandrKucherenko committed Oct 1, 2019
2 parents 25b83bf + 47dd7e9 commit 743d7b8
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- [Usage](#usage)
- [Minimalistic](#minimalistic)
- [Extended](#extended)
- [Compatibility](#compatibility)
- [| Old React Native | Our Plugin |](#old-react-native--our-plugin)
- [Contribute](#contribute)
- [Enable Git Hooks](#enable-git-hooks)
- [Publishing](#publishing)
Expand Down Expand Up @@ -75,22 +77,68 @@ After applying plugin all tasks will be created automatically and attached to th
apply plugin: "com.android.application"
apply plugin: "com.klarna.gradle.reactnative"
/* https://developer.android.com/studio/build/index.html */
android {
/* ... Configuration of the Android app ... */
}
react {
/* declare custom configuration options for each build type */
buildTypes {
debug {
/* ... */
}
release {
/* ... */
}
/* ... more build types ... */
}
/* declare custom configuration options for each flavor */
productFlavors {
local {
/* ... */
}
/* ... more flavors ... */
}
/* Reconfigure `android.packagingOptions{...}` for supporting well JSC integration. */
applyJscPackagingOptions()
}
```

Build types and Flavors are corresponding the Android Build Plugin - [android_build_variants]

### Compatibility

Our plugin is designed for replacing the old React Native `react.gradle` code by properly maintained and heavily tested code. Just for that purpose everything in plugin configuration is backward compatible with old approaches.

We are targeting to replace RN 0.61 [build scripts](https://github.com/facebook/react-native/blob/0.61-stable/react.gradle).

Those lines of code below are equal:

```groovy
project.ext.react = [
entryFile : "index.js",
enableHermes: false, // clean and rebuild if changing
]
// replaced by
react {
entryFile = "index.js"
enableHermes = false
}
```

When you use the plugin DSL for configuration, plugin will take care for reflecting
any defined configuration in `project.ext.react` array/collection. In other words you
can combine old and approach and current plugin in one project at the same time.

The biggest advantage of the plugin that all the variables definitions are type
safe and validated in the moment of gradle script running/editing (depends on IDE you use).


## Contribute

### Enable Git Hooks
Expand Down Expand Up @@ -214,3 +262,4 @@ References:
- <https://github.com/FRI-DAY/elasticmq-gradle-plugin>

[kotlin_dsl]: https://github.com/gradle/kotlin-dsl
[android_build_variants]: https://developer.android.com/studio/build/build-variants
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* */
package com.klarna.gradle.reactnative

import kotlin.test.BeforeTest
Expand Down Expand Up @@ -115,7 +114,7 @@ class GradleReactNativePluginFunctionalTest() : CommonFunctionalTest() {
android {
$ANDROID_SECTION
}
react {
${ReactNativeExtension.EXTENSION} {
root "../.."
bundleAssetName "index.android.bundle"
entryFile "index.android.js"
Expand Down Expand Up @@ -147,7 +146,7 @@ class GradleReactNativePluginFunctionalTest() : CommonFunctionalTest() {
$ANDROID_BUILD_TYPES_SECTION
$ANDROID_FLAVORS_SECTION
}
react {
${ReactNativeExtension.EXTENSION} {
root "${DOLLAR}buildDir/../.."
bundleAssetName "index.bundle"
entryFile "index.js"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ open class BuildTypes
* By default only `debug` build types do not need including of the bundle.
* */
open var bundleIn: Boolean = (name != DEBUG)
/** Is debug information generation required for a specific build type. */
open var devDisabled = true
/** Should developer mode be disabled for this flavor or not. */
open var devDisabledIn: Boolean = (name != DEBUG)
/** Enable `Hermes` JSC addition. */
open var enableHermes: Boolean = false
/** Destination directory of the JavaScript Bundle composed by bundler. */
Expand All @@ -50,10 +50,14 @@ open class BuildTypes
project?.logger?.info("'buildTypes' extension registered to $name")
}

/** Get capitalized name of the build type. */
@Suppress("DefaultLocale")
val title: String = name.capitalize()

/** dump class configuration in groovy format */
override fun toString(): String = "$name " +
"{ bundleIn = $bundleIn" +
", devDisabled = $devDisabled" +
", devDisabledIn = $devDisabledIn" +
", enableHermes = $enableHermes" +
", jsBundleDir = \"$jsBundleDir\"" +
", resourcesDir = \"$resourcesDir\"" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@ open class FlavorTypes
/** Project reference. */
open var project: Project? = null
/** Should specific flavor include JavaScript bundle into final binary or not. */
open var bundleIn: Boolean = false
open var bundleIn: Boolean? = null
/** Should developer mode be disabled for this flavor or not. */
open var devDisabledIn: Boolean? = null
/** Should we do `hermes` post processing for the JavaScript bundle. */
open var enableHermes: Boolean = false
open var enableHermes: Boolean? = null

/** initialize the instance. */
init {
project?.logger?.info("'productFlavors' extension registered to $name")
}

/** Get capitalized name of the flavor. */
@Suppress("DefaultLocale")
val title: String = name.capitalize()

/** dump class configuration in groovy format */
override fun toString(): String = "$name " +
"{ bundleIn = $bundleIn" +
", devDisabledIn = $devDisabledIn" +
", enableHermes = $enableHermes" +
" }"

Expand All @@ -38,5 +45,7 @@ open class FlavorTypes
const val serialVersionUID = 1L
/** Extension name. Mostly used for testing. */
const val EXTENSION = EXTENSION_FLAVOR_TYPES_NAME
/** Empty flavor type */
val Empty = FlavorTypes("")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
package com.klarna.gradle.reactnative

import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.utils.toImmutableList
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.extra
import com.klarna.gradle.reactnative.ReactNativeExtension as RnConfig

/** Unique plugin name. */
Expand Down Expand Up @@ -41,14 +43,74 @@ open class GradleReactNativePlugin : Plugin<Project> {
project.logger.info("configuration extracted: $react")

// extract android plugin extension
val android = project.extensions.findByName("android") as? AppExtension
checkNotNull(android) { EXCEPTION_NO_ANDROID_PLUGIN }
val android = getAndroidConfiguration(project)
checkNotNull(android.buildTypes) { EXCEPTION_NO_BUILD_TYPES }
check(android.buildTypes.size != 0) { EXCEPTION_NO_BUILD_TYPES }

// synchronize build types and flavors
synchronizeBuildTypes(project, android, react)
synchronizeProductFlavors(project, android, react)

if (react.enableCompatibility) {
composeCompatibilityExtensionConfig(project, android, react)
}
}

/** Compose `project.ext.react = [ ... ]` array of settings for compatibility with old build scripts. */
@Suppress("UNCHECKED_CAST")
private fun composeCompatibilityExtensionConfig(
project: Project,
android: AppExtension,
react: RnConfig
) {
project.logger.info("~> react: ${project.name} / $android / $react")

// https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html
if (!project.extra.has(REACT)) {
project.extra.set(REACT, mutableMapOf<String, Any?>())
}

val extraReact = (project.extra[REACT] as Map<String, *>).toMutableMap()
extraReact["root"] = react.root
extraReact["bundleAssetName"] = react.bundleAssetName
extraReact["entryFile"] = react.entryFile
extraReact["bundleCommand"] = react.bundleCommand
extraReact["enableCompatibility"] = react.enableCompatibility

iterateVariants(react) { bt, fl ->
val variant = fl.title + bt.title
extraReact["bundleIn$variant"] = fl.bundleIn ?: bt.bundleIn
extraReact["devDisabledIn$variant"] = fl.devDisabledIn ?: bt.devDisabledIn
extraReact["enableHermes$variant"] = fl.enableHermes ?: bt.enableHermes
}

react.buildTypes.forEach { bt ->
val buildName = bt.title
extraReact["jsBundleDir$buildName"] = bt.jsBundleDir
extraReact["resourcesDir$buildName"] = bt.resourcesDir
}

extraReact["inputExcludes"] = react.inputExcludes.toImmutableList()
extraReact["nodeExecutableAndArgs"] = react.nodeExecutableAndArgs.toImmutableList()
extraReact["extraPackagerArgs"] = react.extraPackagerArgs.toImmutableList()

// update project by our copy
project.extra[REACT] = extraReact
}

/** Iterate via all possible variants of builds. */
private fun iterateVariants(react: RnConfig, predicate: (BuildTypes, FlavorTypes) -> Unit) {
react.buildTypes.forEach { bt ->
val flavors = if (react.productFlavors.size > 0) {
react.productFlavors
} else {
listOf(FlavorTypes.Empty)
}

flavors.forEach { fl ->
predicate(bt, fl)
}
}
}

/** For each android.productFlavor create/use corresponding react.productFlavor configuration */
Expand All @@ -71,7 +133,11 @@ open class GradleReactNativePlugin : Plugin<Project> {
}

/** For each android.buildType create/use corresponding react.buildType configuration */
private fun synchronizeBuildTypes(project: Project, android: AppExtension, react: RnConfig) {
private fun synchronizeBuildTypes(
project: Project,
android: AppExtension,
react: RnConfig
) {
// create corresponding build types
android.buildTypes.forEach {
project.logger.info("android build types: ${it.name}")
Expand All @@ -91,6 +157,8 @@ open class GradleReactNativePlugin : Plugin<Project> {
const val PLUGIN = PLUGIN_NAME_ID
/** Dependent android plugin name. */
const val ANDROID_APP_PLUGIN = "com.android.application"
/** Name of project.ext.react property. */
const val REACT = "react"

/** Exception. Raised when plugin cannot find any android plugin attached to the project. */
const val EXCEPTION_NO_ANDROID_PLUGIN = "Expected android application plugin"
Expand All @@ -108,5 +176,13 @@ open class GradleReactNativePlugin : Plugin<Project> {
/** Extract plugin configuration. */
fun getConfiguration(project: Project): RnConfig =
project.extensions.getByType(RnConfig::class.java)

/** Extract android application project configuration. */
fun getAndroidConfiguration(project: Project): AppExtension {
val android = project.extensions.findByName("android") as? AppExtension
checkNotNull(android) { EXCEPTION_NO_ANDROID_PLUGIN }

return android
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,21 @@ open class ReactNativeExtension
var entryFile: String? = "index.android.js"
/** Type of the react native bundle that we build. */
var bundleCommand: String? = "ram-bundle"
/** Enable compatibility mode with old RN build scripts. */
var enableCompatibility: Boolean = true
/** Collection of the build types. */
var buildTypes: DlsContainer<BuildTypes> =
project.container(BuildTypes::class.java)
/** Collection of the flavors. */
var productFlavors: DlsContainer<FlavorTypes> =
project.container(FlavorTypes::class.java)
/** Excludes from inputs used for detecting JS code changes */
var inputExcludes: List<String> = listOf("android/**", "ios/**")
/** Default node tool arguments. Override which node gets called and with what
* additional arguments. */
var nodeExecutableAndArgs: List<String> = listOf("node")
/** Supply additional arguments to the packager */
var extraPackagerArgs: List<String> = emptyList()

/** Initialize class instance. */
init {
Expand Down Expand Up @@ -87,6 +96,36 @@ open class ReactNativeExtension
fun productFlavors(configuration: Closure<in FlavorTypes>): DlsContainer<FlavorTypes> =
productFlavors.configure(configuration)

/**
* Allows in one line apply common packing configuration required for non-conflict JSC usage.
* ```gradle
* android {
* packagingOptions {
* pickFirst '** /armeabi-v7a/libc++_shared.so'
* pickFirst '** /x86/libc++_shared.so'
* pickFirst '** /arm64-v8a/libc++_shared.so'
* pickFirst '** /x86_64/libc++_shared.so'
* pickFirst '** /x86/libjsc.so'
* pickFirst '** /armeabi-v7a/libjsc.so'
* }
* }
* ```
* */
fun applyJscPackagingOptions() {
val android = GradleReactNativePlugin.getAndroidConfiguration(project)

/* Troubleshoot: https://github.com/react-native-community/jsc-android-buildscripts */
android.packagingOptions.apply {
pickFirst("**/x86/libjsc.so")
pickFirst("**/armeabi-v7a/libjsc.so")

pickFirst("**/x86/libc++_shared.so")
pickFirst("**/x86_64/libc++_shared.so")
pickFirst("**/armeabi-v7a/libc++_shared.so")
pickFirst("**/arm64-v8a/libc++_shared.so")
}
}

companion object {
/** Serialization UID. */
const val serialVersionUID = 1L
Expand Down
Loading

0 comments on commit 743d7b8

Please sign in to comment.