Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ plugins {

tasks.register("build") {
dependsOn(
":react-native-gradle-plugin-shared:build",
":react-native-library-plugin:build",
":react-native-gradle-plugin:build",
":settings-plugin:build",
":shared-testutil:build",
Expand All @@ -21,6 +23,8 @@ tasks.register("build") {

tasks.register("clean") {
dependsOn(
":react-native-gradle-plugin-shared:clean",
":react-native-library-plugin:clean",
":react-native-gradle-plugin:clean",
":settings-plugin:clean",
":shared-testutil:clean",
Expand All @@ -30,6 +34,8 @@ tasks.register("clean") {

tasks.named("ktfmtCheck") {
dependsOn(
":react-native-gradle-plugin-shared:ktfmtCheck",
":react-native-library-plugin:ktfmtCheck",
":react-native-gradle-plugin:ktfmtCheck",
":settings-plugin:ktfmtCheck",
":shared-testutil:ktfmtCheck",
Expand All @@ -39,6 +45,8 @@ tasks.named("ktfmtCheck") {

tasks.named("ktfmtFormat") {
dependsOn(
":react-native-gradle-plugin-shared:ktfmtFormat",
":react-native-library-plugin:ktfmtFormat",
":react-native-gradle-plugin:ktfmtFormat",
":settings-plugin:ktfmtFormat",
":shared-testutil:ktfmtFormat",
Expand Down
3 changes: 3 additions & 0 deletions packages/gradle-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"gradlew",
"gradlew.bat",
"README.md",
"react-native-gradle-plugin-shared",
"react-native-library-plugin",
"!react-native-library-plugin/src/test",
"react-native-gradle-plugin",
"!react-native-gradle-plugin/src/test",
"settings-plugin",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
}

repositories {
google()
mavenCentral()
}

group = "com.facebook.react"

dependencies {
implementation(project(":shared"))
implementation(gradleApi())
implementation(libs.android.gradle.plugin)
}

java { targetCompatibility = JavaVersion.VERSION_11 }

kotlin { jvmToolchain(17) }

tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
apiVersion.set(KotlinVersion.KOTLIN_2_0)
jvmTarget.set(JvmTarget.JVM_11)
allWarningsAsErrors.set(
project.properties["enableWarningsAsErrors"]?.toString()?.toBoolean() ?: false
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react

import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.findPackageJsonFile
import java.io.File
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider

@Suppress("UnstableApiUsage")
object ReactCodegenConfigurator {
fun configureCodegen(
project: Project,
localExtension: ReactExtension,
rootExtension: PrivateReactExtension,
isLibrary: Boolean,
needsCodegenFromPackageJson: (Project, PrivateReactExtension) -> Boolean,
) {
val generatedSrcDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/source/codegen")

if (isLibrary) {
localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
} else {
localExtension.jsRootDir.convention(localExtension.root)
}

val generateCodegenArtifactsTask =
registerCodegenTasks(
project = project,
rootExtension = rootExtension,
generatedSrcDir = generatedSrcDir,
packageJsonFile = { findPackageJsonFile(project, rootExtension.root) },
schemaTaskName = "generateCodegenSchemaFromJavaScript",
artifactsTaskName = "generateCodegenArtifactsFromSchema",
configureJsRoot = { task, packageJson ->
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir

if (packageJson != null && jsSrcsDirInPackageJson != null) {
task.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
} else {
task.jsRootDir.set(localExtension.jsRootDir)
}
},
configureCodegenArtifacts = { task, _ ->
task.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
task.libraryName.set(localExtension.libraryName)
},
onlyIf = { packageJson ->
val needsCodegenFromPackageJson =
needsCodegenFromPackageJson(project, rootExtension)
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val includesGeneratedCode =
parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
(isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode
},
)

if (isLibrary) {
project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
->
ext.sourceSets
.getByName("main")
.java
.directories
.add(generatedSrcDir.get().dir("java").asFile.path)
}
} else {
project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl {
ext ->
ext.sourceSets
.getByName("main")
.java
.directories
.add(generatedSrcDir.get().dir("java").asFile.path)
}
}

project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
}

fun registerCodegenTasks(
project: Project,
rootExtension: PrivateReactExtension,
generatedSrcDir: Provider<Directory>,
packageJsonFile: () -> File?,
schemaTaskName: String,
artifactsTaskName: String,
configureJsRoot: (GenerateCodegenSchemaTask, File?) -> Unit,
configureCodegenArtifacts: (GenerateCodegenArtifactsTask, File?) -> Unit,
onlyIf: (File?) -> Boolean = { true },
): TaskProvider<GenerateCodegenArtifactsTask> {
val generateCodegenSchemaTask =
project.tasks.register(
schemaTaskName,
GenerateCodegenSchemaTask::class.java,
) { task ->
val packageJson = packageJsonFile()

task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
task.codegenDir.set(rootExtension.codegenDir)
task.generatedSrcDir.set(generatedSrcDir)
task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

configureJsRoot(task, packageJson)

task.jsInputFiles.set(
project.fileTree(task.jsRootDir) { tree ->
tree.include("**/*.js")
tree.include("**/*.jsx")
tree.include("**/*.ts")
tree.include("**/*.tsx")

tree.exclude("node_modules/**/*")
tree.exclude("**/*.d.ts")
tree.exclude("**/build/**/*")
}
)
val shouldRunTask = onlyIf(packageJson)
task.onlyIf { shouldRunTask }
}

return project.tasks.register(
artifactsTaskName,
GenerateCodegenArtifactsTask::class.java,
) { task ->
val packageJson = packageJsonFile()

task.dependsOn(generateCodegenSchemaTask)
task.reactNativeDir.set(rootExtension.reactNativeDir)
task.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
task.generatedSrcDir.set(generatedSrcDir)
task.packageJsonFile.set(packageJson)
task.nodeWorkingDir.set(project.layout.projectDirectory.asFile.absolutePath)

configureCodegenArtifacts(task, packageJson)

val shouldRunTask = onlyIf(packageJson)
task.onlyIf { shouldRunTask }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react

import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.utils.LibraryAgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.LibraryAgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.ProjectCodegenUtils.needsCodegenFromPackageJson
import org.gradle.api.Project

object ReactLibraryConfigurator {
fun configure(
project: Project,
extension: ReactExtension,
rootExtension: PrivateReactExtension,
) {
configureBuildConfigFieldsForLibraries(project)
configureNamespaceForLibraries(project)
ReactCodegenConfigurator.configureCodegen(
project = project,
localExtension = extension,
rootExtension = rootExtension,
isLibrary = true,
needsCodegenFromPackageJson = { currentProject, privateExtension ->
currentProject.needsCodegenFromPackageJson(privateExtension.root)
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.utils

import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Project

private const val AGP_BUILT_IN_KOTLIN_MAJOR = 9

object KotlinPluginUtils {
fun applyKotlinAndroidPluginIfNeeded(project: Project) {
if (!project.hasBuiltInKotlinSupport()) {
// AGP 9 comes with built-in Kotlin support, so applying `kotlin-android` alongside it causes
// both plugins to register the same `kotlin` extension.
project.applyPluginIfNeeded("kotlin-android")
}
}

/**
* Returns whether the project uses AGP's built-in Kotlin support. It is enabled by default on AGP
* 9 and later, and can be disabled with `android.builtInKotlin=false`.
*/
private fun Project.hasBuiltInKotlinSupport(): Boolean {
val androidComponents = extensions.findByType(AndroidComponentsExtension::class.java)
?: return false
if (androidComponents.pluginVersion.major < AGP_BUILT_IN_KOTLIN_MAJOR) {
return false
}
return findProperty("android.builtInKotlin")?.toString()?.toBoolean() ?: true
}

private fun Project.applyPluginIfNeeded(pluginId: String) {
if (!pluginManager.hasPlugin(pluginId)) {
pluginManager.apply(pluginId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.utils

import com.android.build.api.variant.LibraryAndroidComponentsExtension
import java.io.File
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import org.gradle.api.Project
import org.w3c.dom.Element

@Suppress("UnstableApiUsage")
object LibraryAgpConfiguratorUtils {
fun configureBuildConfigFieldsForLibraries(project: Project) {
project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
->
ext.buildFeatures.buildConfig = true
}
}

fun configureNamespaceForLibraries(project: Project) {
project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { ext
->
if (ext.namespace == null) {
val manifestFile =
project.layout.projectDirectory.file("src/main/AndroidManifest.xml").asFile
manifestFile
.takeIf { it.exists() }
?.let { file ->
getPackageNameFromManifest(file)?.let { packageName ->
ext.namespace = packageName
}
}
}
}
}
}

fun getPackageNameFromManifest(manifest: File): String? {
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
val builder: DocumentBuilder = factory.newDocumentBuilder()

try {
val xmlDocument = builder.parse(manifest)

val manifestElement = xmlDocument.getElementsByTagName("manifest").item(0) as? Element
val packageName = manifestElement?.getAttribute("package")

return if (packageName.isNullOrEmpty()) null else packageName
} catch (e: Exception) {
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ gradlePlugin {
group = "com.facebook.react"

dependencies {
implementation(project(":react-native-gradle-plugin-shared"))
implementation(project(":shared"))

implementation(gradleApi())
Expand Down
Loading
Loading