Skip to content

Commit

Permalink
RNGP - Setup the RunAutolinkingConfigTask to run the config command
Browse files Browse the repository at this point in the history
Summary:
This diff is part of RFC0759
react-native-community/discussions-and-proposals#759

Here I'm creating the `runAutolinkingConfig` task.

This task is responsible of either:
- Invoking the `npx react-native-community/cli config` command (or the one specified by the user)
- Copying the config output file specified by the user (if any).

The task re-executes only if any of the lockfile are actually changed otherwise it just returns as "UP-TO-DATE"

This allows us to

Changelog:
[Internal] [Changed] - RNGP - Setup the RunAutolinkingConfigTask to run the config command

Reviewed By: cipolleschi, blakef

Differential Revision: D55475596

fbshipit-source-id: 3c687f965c59eb82fc447546ebd936ba401f34f2
  • Loading branch information
cortinico authored and facebook-github-bot committed Apr 30, 2024
1 parent 6262158 commit 1df1570
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ abstract class ReactExtension @Inject constructor(project: Project) {
.convention(listOf("npx", "@react-native-community/cli", "config"))

/**
* Location of the lock files used to consider wether autolinking [autolinkConfigCommand] should
* 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.
*
Expand Down
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,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
@@ -0,0 +1,159 @@
/*
* 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.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.File
import java.io.FileOutputStream
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class RunAutolinkingConfigTaskTest {

@get:Rule val tempFolder = TemporaryFolder()

@Test
fun runAutolinkingConfigTask_groupIsSetCorrectly() {
val task = createTestTask<BundleHermesCTask> {}
assertEquals("react", task.group)
}

@Test
fun runAutolinkingConfigTask_staticInputs_areSetCorrectly() {
val project = createProject()

val task =
createTestTask<RunAutolinkingConfigTask> {
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
it.autolinkLockFiles.set(project.files("packager.lock", "another-packager.lock"))
it.autolinkConfigFile.set(tempFolder.newFile("dependencies.json"))
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}

assertEquals(3, task.inputs.files.files.size)
task.autolinkLockFiles.get().files.forEach {
assertTrue(
it.name == "depedencies.json" ||
it.name == "packager.lock" ||
it.name == "another-packager.lock")
}

assertTrue(task.inputs.properties.containsKey("autolinkConfigCommand"))
assertEquals(1, task.outputs.files.files.size)
assertEquals(File(tempFolder.root, "output.json"), task.outputs.files.singleFile)
assertEquals(listOf("rm", "-rf", "/"), task.autolinkConfigCommand.get())

assertEquals(2, task.autolinkLockFiles.get().files.size)
task.autolinkLockFiles.get().files.forEach {
assertTrue(it.name == "packager.lock" || it.name == "another-packager.lock")
}

assertEquals(File(tempFolder.root, "dependencies.json"), task.autolinkConfigFile.get().asFile)
assertEquals(File(tempFolder.root, "output.json"), task.autolinkOutputFile.get().asFile)
}

@Test
fun wipeOutputDir_worksCorrectly() {
val outputDir =
tempFolder.newFolder("output").apply {
File(this, "output.json").createNewFile()
File(this, "NothingToSeeHere.java").createNewFile()
}

val task = createTestTask<RunAutolinkingConfigTask> { it.autolinkOutputFile.set(outputDir) }
task.wipeOutputDir()

assertFalse(outputDir.exists())
}

@Test
fun setupConfigCommandLine_worksCorrectly() {
val project = createProject()

val task =
createTestTask<RunAutolinkingConfigTask>(project) {
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}
task.setupConfigCommandLine()

assertEquals(project.projectDir, task.workingDir)
assertTrue(task.standardOutput is FileOutputStream)
assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
}

@Test
fun setupConfigCopyCommandLine_worksCorrectly() {
val project = createProject()

val task =
createTestTask<RunAutolinkingConfigTask>(project) {
it.autolinkConfigFile.set(tempFolder.newFile("dependencies.json"))
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}
task.setupConfigCopyCommandLine()

assertEquals(project.projectDir, task.workingDir)
assertTrue(task.standardOutput !is FileOutputStream)
assertEquals("cp", task.commandLine[0])
assertEquals(File(tempFolder.root, "dependencies.json").absolutePath, task.commandLine[1])
assertEquals(File(tempFolder.root, "output.json").absolutePath, task.commandLine[2])
}

@Test
fun setupCommandLine_withoutAutolinkConfigFileConfigured_invokesCommand() {
val project = createProject()

val task =
createTestTask<RunAutolinkingConfigTask>(project) {
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}
task.setupCommandLine()

assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
}

@Test
fun setupCommandLine_withoutMissingConfigFile_invokesCommand() {
val project = createProject()

val task =
createTestTask<RunAutolinkingConfigTask>(project) {
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
it.autolinkConfigFile.set(File(tempFolder.root, "dependencies.json"))
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}
task.setupCommandLine()

assertEquals(listOf("rm", "-rf", "/"), task.commandLine)
}

@Test
fun setupCommandLine_withoutExistingConfigFile_invokesCp() {
val project = createProject()
val configFile = tempFolder.newFile("dependencies.json").apply { writeText("¯\\_(ツ)_/¯") }

val task =
createTestTask<RunAutolinkingConfigTask>(project) {
it.autolinkConfigCommand.set(listOf("rm", "-rf", "/"))
it.autolinkConfigFile.set(configFile)
it.autolinkOutputFile.set(tempFolder.newFile("output.json"))
}
task.setupCommandLine()

assertEquals(
listOf("cp", configFile.absolutePath, File(tempFolder.root, "output.json").absolutePath),
task.commandLine)
}
}
4 changes: 4 additions & 0 deletions packages/rn-tester/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ react {
// The hermes compiler command to run. By default it is 'hermesc'
hermesCommand = "$reactNativeDirPath/ReactAndroid/hermes-engine/build/hermes/bin/hermesc"
enableHermesOnlyInVariants = listOf("hermesDebug", "hermesRelease")

/* Autolinking */
// The location of the monorepo lockfiles to `config` is cached correctly.
autolinkLockFiles = files("$rootDir/yarn.lock")
}

/** Run Proguard to shrink the Java bytecode in release builds. */
Expand Down

0 comments on commit 1df1570

Please sign in to comment.