first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
/*
* 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.gradle.api.tasks.testing.logging.TestExceptionFormat
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)
id("java-gradle-plugin")
}
repositories {
google()
mavenCentral()
}
gradlePlugin {
plugins {
create("react.settings") {
id = "com.facebook.react.settings"
implementationClass = "com.facebook.react.ReactSettingsPlugin"
}
}
}
group = "com.facebook.react"
dependencies {
implementation(project(":shared"))
implementation(gradleApi())
implementation(libs.gson)
implementation(libs.guava)
implementation(libs.javapoet)
testImplementation(libs.junit)
testImplementation(libs.assertj)
testImplementation(project(":shared-testutil"))
}
// We intentionally don't build for Java 17 as users will see a cryptic bytecode version
// error first. Instead we produce a Java 11-compatible Gradle Plugin, so that AGP can print their
// nice message showing that JDK 11 (or 17) is required first
java { targetCompatibility = JavaVersion.VERSION_11 }
kotlin { jvmToolchain(17) }
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
apiVersion.set(KotlinVersion.KOTLIN_1_8)
// See comment above on JDK 11 support
jvmTarget.set(JvmTarget.JVM_11)
allWarningsAsErrors.set(
project.properties["enableWarningsAsErrors"]?.toString()?.toBoolean() ?: false
)
}
}
tasks.withType<Test>().configureEach {
testLogging {
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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.model.ModelAutolinkingConfigJson
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.windowsAwareCommandLine
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
import javax.inject.Inject
import kotlin.math.min
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.initialization.Settings
import org.gradle.api.logging.Logging
abstract class ReactSettingsExtension @Inject constructor(val settings: Settings) {
private val outputFile =
settings.layout.rootDirectory.file("build/generated/autolinking/autolinking.json").asFile
private val outputFolder =
settings.layout.rootDirectory.file("build/generated/autolinking/").asFile
private val defaultConfigCommand: List<String> =
windowsAwareCommandLine(listOf("npx", "@react-native-community/cli", "config")).map {
it.toString()
}
/**
* Utility function to autolink libraries using an external command as source of truth.
*
* This should be invoked inside the `settings.gradle` file, and will make sure the Gradle project
* is loading all the discovered libraries.
*
* @param command The command to execute to get the autolinking configuration. Default is
* `npx @react-native-community/cli config`.
* @param workingDirectory The directory where the command should be executed.
* @param lockFiles The list of lock files to check for changes (if lockfiles are not changed, the
* command will not be executed).
*/
@JvmOverloads
public fun autolinkLibrariesFromCommand(
command: List<String> = defaultConfigCommand,
workingDirectory: File? = settings.layout.rootDirectory.dir("../").asFile,
lockFiles: FileCollection =
settings.layout.rootDirectory
.dir("../")
.files("yarn.lock", "package-lock.json", "package.json", "react-native.config.js"),
) {
outputFile.parentFile.mkdirs()
val updateConfig =
object : GenerateConfig {
override fun command(): List<String> = command
override fun execute(): Int {
val execResult =
settings.providers.exec { exec ->
exec.commandLine(command)
exec.workingDir = workingDirectory
}
outputFile.writeText(execResult.standardOutput.asText.get())
return execResult.result.get().exitValue
}
}
checkAndUpdateCache(updateConfig, outputFile, outputFolder, lockFiles)
linkLibraries(getLibrariesToAutolink(outputFile))
}
/**
* Utility function to autolink libraries using an external file as source of truth.
*
* The file should be a JSON file with the same structure as the one generated by the
* `npx @react-native-community/cli config` command.
*
* @param autolinkConfigFile The file to read the autolinking configuration from.
*/
public fun autolinkLibrariesFromConfigFile(
autolinkConfigFile: File,
) {
// We copy the file to the build directory so that the various Gradle tasks can access it.
autolinkConfigFile.copyTo(outputFile, overwrite = true)
linkLibraries(getLibrariesToAutolink(autolinkConfigFile))
}
/**
* Utility function so that for each tuple :project-name -> project-dir, it instructs Gradle to
* lad this extra module.
*/
private fun linkLibraries(input: Map<String, File>) {
input.forEach { (path, projectDir) ->
settings.include(path)
settings.project(path).projectDir = projectDir
}
}
internal interface GenerateConfig {
fun command(): List<String>
fun execute(): Int
}
companion object {
private val md = MessageDigest.getInstance("SHA-256")
/**
* Determine if our cache is out-of-date
*
* @param cacheJsonConfig Our current cached autolinking.json config, which may exist
* @param cacheFolder The folder we store our cached SHAs and config
* @param lockFiles The [FileCollection] of the lockfiles to check.
* @return `true` if the cache needs to be rebuilt, `false` otherwise
*/
internal fun isCacheDirty(
cacheJsonConfig: File,
cacheFolder: File,
lockFiles: FileCollection,
): Boolean {
if (cacheJsonConfig.exists().not() || cacheJsonConfig.length() == 0L) {
return true
}
val lockFilesChanged = checkAndUpdateLockfiles(lockFiles, cacheFolder)
if (lockFilesChanged) {
return true
}
return isConfigModelInvalid(JsonUtils.fromAutolinkingConfigJson(cacheJsonConfig))
}
/**
* Utility function to update the settings cache only if it's entries are dirty
*
* @param updateJsonConfig A [GenerateConfig] to update the project's autolinking config
* @param cacheJsonConfig Our current cached autolinking.json config, which may exist
* @param cacheFolder The folder we store our cached SHAs and config
* @param lockFiles The [FileCollection] of the lockfiles to check.
*/
internal fun checkAndUpdateCache(
updateJsonConfig: GenerateConfig,
cacheJsonConfig: File,
cacheFolder: File,
lockFiles: FileCollection,
) {
if (isCacheDirty(cacheJsonConfig, cacheFolder, lockFiles)) {
val exitValue = updateJsonConfig.execute()
if (exitValue != 0) {
val prefixCommand =
"ERROR: autolinkLibrariesFromCommand: process ${updateJsonConfig.command().joinToString(" ")}"
val message = "$prefixCommand exited with error code: $exitValue"
val logger = Logging.getLogger("ReactSettingsExtension")
logger.error(message)
if (cacheJsonConfig.length() != 0L) {
logger.error(
cacheJsonConfig.readText().substring(0, min(1024, cacheJsonConfig.length().toInt()))
)
}
cacheJsonConfig.delete()
throw GradleException(message)
} else {
// If cache was dirty, we executed the command and we need to update the lockfiles sha.
checkAndUpdateLockfiles(lockFiles, cacheFolder)
}
}
}
/**
* Utility function to check if the provided lockfiles have been updated or not. This function
* will both check and update the lockfiles hashes if necessary.
*
* @param lockFiles The [FileCollection] of the lockfiles to check.
* @param outputFolder The folder where the hashes will be stored.
* @return `true` if the lockfiles have been updated, `false` otherwise.
*/
internal fun checkAndUpdateLockfiles(lockFiles: FileCollection, outputFolder: File): Boolean {
var changed = false
lockFiles.forEach { lockFile ->
if (lockFile.exists()) {
val sha = computeSha256(lockFile)
val shaFile = File(outputFolder, "${lockFile.name}.sha")
if (shaFile.exists().not() || shaFile.readText() != sha) {
shaFile.writeText(sha)
changed = true
}
}
}
return changed
}
internal fun getLibrariesToAutolink(buildFile: File): Map<String, File> {
val model = JsonUtils.fromAutolinkingConfigJson(buildFile)
return model
?.dependencies
?.values
// We handle scenarios where there are deps that are
// iOS-only or missing the Android configs.
?.filter { it.platforms?.android?.sourceDir != null }
// We want to skip dependencies that are pure C++ as they won't contain a .gradle file.
?.filterNot { it.platforms?.android?.isPureCxxDependency == true }
?.associate { deps ->
":${deps.nameCleansed}" to File(deps.platforms?.android?.sourceDir)
} ?: emptyMap()
}
internal fun computeSha256(lockFile: File) =
String.format("%032x", BigInteger(1, md.digest(lockFile.readBytes())))
internal fun isConfigModelInvalid(model: ModelAutolinkingConfigJson?) =
model?.project?.android?.packageName == null
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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 org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
/**
* This is the settings.gradle plugin for React Native.
*
* It just registers the [ReactSettingsExtension] extension, so that utility functions over there
* can be called to support autolinking.
*/
class ReactSettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
settings.extensions.create("reactSettings", ReactSettingsExtension::class.java, settings)
}
}

View File

@@ -0,0 +1,475 @@
/*
* 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.ReactSettingsExtension.Companion.checkAndUpdateCache
import com.facebook.react.ReactSettingsExtension.Companion.checkAndUpdateLockfiles
import com.facebook.react.ReactSettingsExtension.Companion.computeSha256
import com.facebook.react.ReactSettingsExtension.Companion.getLibrariesToAutolink
import com.facebook.react.ReactSettingsExtension.GenerateConfig
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testfixtures.ProjectBuilder
import org.intellij.lang.annotations.Language
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class ReactSettingsExtensionTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test
fun computeSha256_worksCorrectly() {
val validFile =
createJsonFile(
"""
{
"value": "¯\\_(ツ)_/¯"
}
"""
.trimIndent()
)
assertThat(computeSha256(validFile))
.isEqualTo("838aa9a72a16fdd55b0d49b510a82e264a30f59333b5fdd97c7798a29146f6a8")
}
@Test
fun getLibrariesToAutolink_withEmptyFile_returnsEmptyMap() {
val validJsonFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0"
}
"""
.trimIndent()
)
val map = getLibrariesToAutolink(validJsonFile)
assertThat(map.keys).isEmpty()
}
@Test
fun getLibrariesToAutolink_withLibraryToAutolink_returnsValidMap() {
val validJsonFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0",
"dependencies": {
"@react-native/oss-library-example": {
"root": "./node_modules/@react-native/oss-library-example",
"name": "@react-native/oss-library-example",
"platforms": {
"ios": {
"podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec",
"version": "0.0.1",
"configurations": [],
"scriptPhases": []
},
"android": {
"sourceDir": "./node_modules/@react-native/oss-library-example/android",
"packageImportPath": "import com.facebook.react.osslibraryexample.OSSLibraryExamplePackage;",
"packageInstance": "new OSSLibraryExamplePackage()",
"buildTypes": ["staging", "debug", "release"],
"libraryName": "OSSLibraryExampleSpec",
"componentDescriptors": [
"SampleNativeComponentComponentDescriptor"
],
"cmakeListsPath": "./node_modules/@react-native/oss-library-example/android/build/generated/source/codegen/jni/CMakeLists.txt",
"cxxModuleCMakeListsModuleName": null,
"cxxModuleCMakeListsPath": null,
"cxxModuleHeaderName": null,
"dependencyConfiguration": "implementation",
"isPureCxxDependency": false
}
}
}
}
}
"""
.trimIndent()
)
val map = getLibrariesToAutolink(validJsonFile)
assertThat(map.keys).containsExactly(":react-native_oss-library-example")
assertThat(map[":react-native_oss-library-example"])
.isEqualTo(File("./node_modules/@react-native/oss-library-example/android"))
}
@Test
fun getLibrariesToAutolink_withiOSOnlyLibrary_returnsEmptyMap() {
val validJsonFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0",
"dependencies": {
"@react-native/oss-library-example": {
"root": "./node_modules/@react-native/oss-library-example",
"name": "@react-native/oss-library-example",
"platforms": {
"ios": {
"podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec",
"version": "0.0.1",
"configurations": [],
"scriptPhases": []
}
}
}
}
}
"""
.trimIndent()
)
val map = getLibrariesToAutolink(validJsonFile)
assertThat(map.keys).isEmpty()
}
@Test
fun checkAndUpdateLockfiles_withNothingToCheck_returnsFalse() {
val project = ProjectBuilder.builder().build()
val noFiles = project.files()
assertThat(checkAndUpdateLockfiles(noFiles, tempFolder.root)).isFalse()
}
@Test
fun checkAndUpdateLockfiles_withOneLockfileNoHash_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder = tempFolder.newFolder("build")
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
assertThat(checkAndUpdateLockfiles(lockfileCollection, buildFolder)).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").exists()).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").readText())
.isEqualTo("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
@Test
fun checkAndUpdateLockfiles_withOneLockfileInvalidHash_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha").writeText("Just a stale hash")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
assertThat(checkAndUpdateLockfiles(lockfileCollection, buildFolder)).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").exists()).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").readText())
.isEqualTo("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
@Test
fun checkAndUpdateLockfiles_withOneLockfileValidHash_returnsFalse() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
assertThat(checkAndUpdateLockfiles(lockfileCollection, buildFolder)).isFalse()
assertThat(File(buildFolder, "yarn.lock.sha").exists()).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").readText())
.isEqualTo("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
@Test
fun checkAndUpdateLockfiles_withMultipleLockfilesInvalidHash_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha").writeText("I'm an invalid hash")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
tempFolder.newFile("package-lock.json").apply { writeText("and I'm another lockfile") }
val lockfileCollection = project.files("yarn.lock", "package-lock.json")
assertThat(checkAndUpdateLockfiles(lockfileCollection, buildFolder)).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").exists()).isTrue()
assertThat(File(buildFolder, "package-lock.json.sha").exists()).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").readText())
.isEqualTo("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
assertThat(File(buildFolder, "package-lock.json.sha").readText())
.isEqualTo("9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b")
}
@Test
fun checkAndUpdateLockfiles_withMultipleLockfilesValidHash_returnsFalse() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
File(this, "package-lock.json.sha")
.writeText("9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
tempFolder.newFile("package-lock.json").apply { writeText("and I'm another lockfile") }
val lockfileCollection = project.files("yarn.lock", "package-lock.json")
assertThat(checkAndUpdateLockfiles(lockfileCollection, buildFolder)).isFalse()
assertThat(File(buildFolder, "yarn.lock.sha").exists()).isTrue()
assertThat(File(buildFolder, "package-lock.json.sha").exists()).isTrue()
assertThat(File(buildFolder, "yarn.lock.sha").readText())
.isEqualTo("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
assertThat(File(buildFolder, "package-lock.json.sha").readText())
.isEqualTo("9be5bca432b81becf4f54451aea021add68376330581eaa93ab9a0b3e4e29a3b")
}
@Test
fun skipUpdateIfConfigInCacheIsValid() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder = tempFolder.newFolder("build")
val generatedFolder = tempFolder.newFolder("build", "generated")
val outputFile =
File(generatedFolder, "autolinking.json").apply {
writeText(
"""
{
"root": "/",
"reactNativePath": "/node_modules/react-native",
"reactNativeVersion": "0.75",
"dependencies": {},
"healthChecks": [],
"platforms": {
"ios": {},
"android": {}
},
"assets": [],
"project": {
"ios": {},
"android": {
"sourceDir": "/",
"appName": "app",
"packageName": "com.TestApp",
"applicationId": "com.TestApp",
"mainActivity": ".MainActivity",
"assets": []
}
}
}
"""
.trimIndent()
)
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
// Prebuild the shas with the invalid empty autolinking.json
checkAndUpdateLockfiles(lockfileCollection, buildFolder)
val monitoredUpdateConfig = createMonitoredUpdateConfig()
checkAndUpdateCache(monitoredUpdateConfig, outputFile, buildFolder, lockfileCollection)
// The autolinking.json file is valid, SHA's are untouched therefore config should NOT be
// refreshed
assertThat(monitoredUpdateConfig.run).isFalse()
}
@Test
fun checkAndUpdateConfigIfEmpty() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder = tempFolder.newFolder("build")
val generatedFolder = tempFolder.newFolder("build", "generated")
val outputFile = File(generatedFolder, "autolinking.json").apply { writeText("") }
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
// Prebuild the shas with the invalid empty autolinking.json
checkAndUpdateLockfiles(lockfileCollection, buildFolder)
val monitoredUpdateConfig = createMonitoredUpdateConfig()
checkAndUpdateCache(monitoredUpdateConfig, outputFile, buildFolder, lockfileCollection)
// The autolinking.json file is invalid and should be refreshed
assertThat(monitoredUpdateConfig.run).isTrue()
}
@Test
fun checkAndUpdateConfigIfCachedConfigInvalid() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder = tempFolder.newFolder("build")
val generatedFolder = tempFolder.newFolder("build", "generated")
val outputFile =
File(generatedFolder, "autolinking.json").apply {
writeText(
"""
{
"project": {
"ios": {},
"android": {}
}
}
"""
.trimIndent()
)
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfileCollection = project.files("yarn.lock")
// Prebuild the shas with the invalid empty autolinking.json
checkAndUpdateLockfiles(lockfileCollection, buildFolder)
val monitoredUpdateConfig = createMonitoredUpdateConfig()
checkAndUpdateCache(monitoredUpdateConfig, outputFile, buildFolder, lockfileCollection)
// The autolinking.json file is invalid and should be refreshed
assertThat(monitoredUpdateConfig.run).isTrue()
}
@Test
fun isCacheDirty_withMissingAutolinkingFile_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfiles = project.files("yarn.lock")
val emptyConfigFile = File(tempFolder.newFolder("build", "autolinking"), "autolinking.json")
assertThat(ReactSettingsExtension.isCacheDirty(emptyConfigFile, buildFolder, lockfiles))
.isTrue()
}
@Test
fun isCacheDirty_withInvalidAutolinkingFile_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfiles = project.files("yarn.lock")
val invalidConfigFile =
createJsonFile(
"""
{}
"""
.trimIndent()
)
assertThat(ReactSettingsExtension.isCacheDirty(invalidConfigFile, buildFolder, lockfiles))
.isTrue()
}
@Test
fun isCacheDirty_withMissingDependenciesInJson_returnsFalse() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfiles = project.files("yarn.lock")
val invalidConfigFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0"
}
"""
.trimIndent()
)
assertThat(ReactSettingsExtension.isCacheDirty(invalidConfigFile, buildFolder, lockfiles))
.isTrue()
}
@Test
fun isCacheDirty_withExistingEmptyDependenciesInJson_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfiles = project.files("yarn.lock")
val invalidConfigFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0",
"dependencies": {}
}
"""
.trimIndent()
)
assertThat(ReactSettingsExtension.isCacheDirty(invalidConfigFile, buildFolder, lockfiles))
.isTrue()
}
@Test
fun isCacheDirty_withExistingDependenciesInJson_returnsTrue() {
val project = ProjectBuilder.builder().withProjectDir(tempFolder.root).build()
val buildFolder =
tempFolder.newFolder("build").apply {
File(this, "yarn.lock.sha")
.writeText("76046b72442ee7eb130627e56c3db7c9907eef4913b17ad130335edc0eb702a8")
}
tempFolder.newFile("yarn.lock").apply { writeText("I'm a lockfile") }
val lockfiles = project.files("yarn.lock")
val invalidConfigFile =
createJsonFile(
"""
{
"reactNativeVersion": "1000.0.0",
"dependencies": {
"@react-native/oss-library-example": {
"root": "./node_modules/@react-native/oss-library-example",
"name": "@react-native/oss-library-example",
"platforms": {
"ios": {
"podspecPath": "./node_modules/@react-native/oss-library-example/OSSLibraryExample.podspec",
"version": "0.0.1",
"configurations": [],
"scriptPhases": []
}
}
}
}
}
"""
.trimIndent()
)
assertThat(ReactSettingsExtension.isCacheDirty(invalidConfigFile, buildFolder, lockfiles))
.isTrue()
}
private fun createJsonFile(@Language("JSON") input: String) =
tempFolder.newFile().apply { writeText(input) }
private fun createMonitoredUpdateConfig() =
object : GenerateConfig {
var run = false
override fun execute(): Int {
run = true
return 0
}
override fun command(): List<String> = listOf("true")
}
}