Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RNGP - Autolinking - Add model classes for parsing the config output #44288

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.facebook.react.utils.projectPathToLibraryName
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
Expand Down Expand Up @@ -147,4 +148,35 @@ abstract class ReactExtension @Inject constructor(project: Project) {
*/
val codegenJavaPackageName: Property<String> =
objects.property(String::class.java).convention("com.facebook.fbreact.specs")

/** Auto-linking Config */

/**
* Location of the JSON file used to configure autolinking. This file is the output of the
* `@react-native-community/cli` config command.
*
* If not specified, RNGP will just invoke whatever you pass as [autolinkConfigCommand].
*/
val autolinkConfigFile: RegularFileProperty = objects.fileProperty()

/**
* The command to invoke as source of truth for the autolinking configuration. Default is `["npx",
* "@react-native-community/cli", "config"]`.
*/
val autolinkConfigCommand: ListProperty<String> =
objects
.listProperty(String::class.java)
.convention(listOf("npx", "@react-native-community/cli", "config"))

/**
* Location of the lock files used to consider whether autolinking [autolinkConfigCommand] should
* re-execute or not. If file collection is unchanged, the autolinking command will not be
* re-executed.
*
* If not specified, RNGP will just look for both yarn.lock and package.lock in the [root] folder.
*/
val autolinkLockFiles: Property<FileCollection> =
objects
.property(FileCollection::class.java)
.convention(root.files("../yarn.lock", "../package-lock.json"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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.tasks.RunAutolinkingConfigTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
Expand Down Expand Up @@ -78,6 +79,7 @@ class ReactPlugin : Plugin<Project> {
project.configureReactTasks(variant = variant, config = extension)
}
}
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
}

Expand Down Expand Up @@ -209,4 +211,21 @@ class ReactPlugin : Plugin<Project> {
// This will invoke the codegen before compiling the entire project.
project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
}

/** This function sets up Autolinking for App users */
private fun configureAutolinking(
project: Project,
extension: ReactExtension,
) {
val generatedAutolinkingDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/autolinking")
val configOutputFile = generatedAutolinkingDir.get().file("config-output.json")

project.tasks.register("runAutolinkingConfig", RunAutolinkingConfigTask::class.java) { task ->
task.autolinkConfigCommand.set(extension.autolinkConfigCommand)
task.autolinkConfigFile.set(extension.autolinkConfigFile)
task.autolinkOutputFile.set(configOutputFile)
task.autolinkLockFiles.set(extension.autolinkLockFiles)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.model

data class ModelAutolinkingAndroidProjectJson(
val sourceDir: String,
val appName: String,
val packageName: String,
val applicationId: String,
val mainActivity: String,
val watchModeCommandParams: List<String>?,
val dependencyConfiguration: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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.model

data class ModelAutolinkingConfigJson(
val reactNativeVersion: String,
val dependencies: Map<String, ModelAutolinkingDependenciesJson>?,
val project: ModelAutolinkingProjectJson?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.model

data class ModelAutolinkingDependenciesJson(
val root: String,
val name: String,
val platforms: ModelAutolinkingDependenciesPlatformJson?
) {

val nameCleansed: String
get() = name.replace(Regex("[~*!'()]+"), "_").replace(Regex("^@([\\w-.]+)/"), "$1_")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.model

data class ModelAutolinkingDependenciesPlatformAndroidJson(
val sourceDir: String,
val packageImportPath: String,
val packageInstance: String,
val buildTypes: List<String>,
val libraryName: String,
val componentDescriptors: List<String>,
val cmakeListsPath: String,
val cxxModuleCMakeListsModuleName: String?,
val cxxModuleCMakeListsPath: String?,
val cxxModuleHeaderName: String?,
val dependencyConfiguration: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.model

data class ModelAutolinkingDependenciesPlatformJson(
val android: ModelAutolinkingDependenciesPlatformAndroidJson?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.model

data class ModelAutolinkingProjectJson(val android: ModelAutolinkingAndroidProjectJson?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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.tasks

import com.facebook.react.utils.windowsAwareCommandLine
import java.io.FileOutputStream
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
* A task that will run @react-native-community/cli config if necessary to generate the autolinking
* configuration file.
*/
abstract class RunAutolinkingConfigTask : Exec() {

init {
group = "react"
}

@get:Input abstract val autolinkConfigCommand: ListProperty<String>

/*
* We don't want to re-run config if the lockfiles haven't changed.
* So we have the lockfiles as @InputFiles for this task.
*/
@get:InputFiles abstract val autolinkLockFiles: Property<FileCollection>

@get:InputFile @get:Optional abstract val autolinkConfigFile: RegularFileProperty

@get:OutputFile abstract val autolinkOutputFile: RegularFileProperty

override fun exec() {
wipeOutputDir()
setupCommandLine()
super.exec()
}

internal fun setupCommandLine() {
if (!autolinkConfigFile.isPresent || !autolinkConfigFile.get().asFile.exists()) {
setupConfigCommandLine()
} else {
setupConfigCopyCommandLine()
}
}

internal fun wipeOutputDir() {
autolinkOutputFile.asFile.get().apply {
deleteRecursively()
parentFile.mkdirs()
}
}

internal fun setupConfigCommandLine() {
workingDir(project.projectDir)
standardOutput = FileOutputStream(autolinkOutputFile.get().asFile)
commandLine(
windowsAwareCommandLine(
*autolinkConfigCommand.get().toTypedArray(),
))
}

internal fun setupConfigCopyCommandLine() {
workingDir(project.projectDir)
commandLine(
windowsAwareCommandLine(
"cp",
autolinkConfigFile.get().asFile.absolutePath,
autolinkOutputFile.get().asFile.absolutePath))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.utils

import com.facebook.react.model.ModelAutolinkingConfigJson
import com.facebook.react.model.ModelPackageJson
import com.google.gson.Gson
import java.io.File
Expand All @@ -18,4 +19,10 @@ object JsonUtils {
input.bufferedReader().use {
runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull()
}

fun fromAutolinkingConfigJson(input: File): ModelAutolinkingConfigJson? =
input.bufferedReader().use {
runCatching { gsonConverter.fromJson(it, ModelAutolinkingConfigJson::class.java) }
.getOrNull()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.model

import org.junit.Assert.assertEquals
import org.junit.Test

class ModelAutolinkingDependenciesJsonTest {

@Test
fun nameCleansed_withoutScope() {
assertEquals("name", ModelAutolinkingDependenciesJson("", "name", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react~native", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react*native", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react!native", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react'native", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react(native", null).nameCleansed)
assertEquals(
"react_native", ModelAutolinkingDependenciesJson("", "react)native", null).nameCleansed)
assertEquals(
"react_native",
ModelAutolinkingDependenciesJson("", "react~*!'()native", null).nameCleansed)
}

@Test
fun nameCleansed_withScope() {
assertEquals(
"react-native_package",
ModelAutolinkingDependenciesJson("", "@react-native/package", null).nameCleansed)
assertEquals(
"this_is_a_more_complicated_example_of_weird_packages",
ModelAutolinkingDependenciesJson(
"", "@this*is~a(more)complicated/example!of~weird)packages", null)
.nameCleansed)
}
}