diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 5994a02f3fe..6a00b15402b 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -284,7 +284,7 @@ def getNdkBuildFullPath() { } def buildReactNdkLib = tasks.register("buildReactNdkLib", Exec) { - dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractAARHeaders, extractJNIFiles) + dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFmt, prepareFolly, prepareGlog, prepareLibevent, extractNativeDependencies) dependsOn("generateCodegenArtifactsFromSchema"); inputs.dir("$projectDir/../ReactCommon") @@ -343,32 +343,18 @@ def packageReactNdkLibsForBuck = tasks.register("packageReactNdkLibsForBuck", Co into("src/main/jni/prebuilt/lib") } -task extractAARHeaders { - doLast { - configurations.extractHeaders.files.each { - def file = it.absoluteFile - def packageName = file.name.tokenize('-')[0] - copy { - from zipTree(file) - into "$projectDir/src/main/jni/first-party/$packageName/headers" - include "**/*.h" - } - } - } -} - -task extractJNIFiles { - doLast { - configurations.extractJNI.files.each { - def file = it.absoluteFile - def packageName = file.name.tokenize('-')[0] - copy { - from zipTree(file) - into "$projectDir/src/main/jni/first-party/$packageName/" - include "jni/**/*" - } - } - } +final def extractNativeDependencies = tasks.register('extractNativeDependencies', ExtractJniAndHeadersTask) { + it.extractHeadersConfiguration.setFrom(configurations.extractHeaders) + it.extractJniConfiguration.setFrom(configurations.extractJNI) + it.baseOutputDir = project.file("src/main/jni/first-party/") + // Sadly this task as an output folder path that is directly dependent on + // the task input (i.e. src/main/jni/first-party//... + // This means that this task is using the parent folder (first-party/) as + // @OutputFolder. The `prepareHermes` task will also output inside that + // folder and if the two tasks happen to be inside the same run, we want + // `extractNativeDependencies` to run after `prepareHermes` to do not + // invalidate the input/output calculation for this task. + it.mustRunAfter(prepareHermes) } task installArchives { diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTask.kt new file mode 100644 index 00000000000..b7ffce9a3f7 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTask.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) Facebook, Inc. and its 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.internal + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.* + +/** + * A task that takes care of extracting JNIs and Headers from a custom Gradle configuration into an + * output folder. Users are most likely not going to use this task but it will be used when building + * the React Native project. + */ +abstract class ExtractJniAndHeadersTask : DefaultTask() { + + @get:InputFiles abstract val extractHeadersConfiguration: ConfigurableFileCollection + + @get:InputFiles abstract val extractJniConfiguration: ConfigurableFileCollection + + @get:OutputDirectory abstract val baseOutputDir: DirectoryProperty + + @TaskAction + fun taskAction() { + extractJniConfiguration.files.forEach { + val file = it.absoluteFile + val packageName = file.name.split("-", ".").first() + project.copy { copySpec -> + copySpec.from(project.zipTree(file)) + copySpec.into(baseOutputDir.dir(packageName)) + copySpec.include("jni/**/*") + } + } + extractHeadersConfiguration.files.forEach { + val file = it.absoluteFile + val packageName = file.name.split("-", ".").first() + project.copy { copySpec -> + copySpec.from(project.zipTree(file)) + copySpec.into(baseOutputDir.get().dir("$packageName/headers")) + copySpec.include("**/*.h") + } + } + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTaskTest.kt new file mode 100644 index 00000000000..c207d6daae0 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/ExtractJniAndHeadersTaskTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Facebook, Inc. and its 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.internal + +import com.facebook.react.tests.createProject +import com.facebook.react.tests.createTestTask +import com.facebook.react.tests.zipFiles +import java.io.* +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class ExtractJniAndHeadersTaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun extractJniAndHeadersTask_extractsHeadersCorrectly() { + val project = createProject() + val aarFile = File(project.projectDir, "libheader.aar") + val headerFile = tempFolder.newFile("justaheader.h") + val output = tempFolder.newFolder("output") + zipFiles(aarFile, listOf(headerFile)) + + val task = + createTestTask(project = project) { + it.extractHeadersConfiguration.setFrom(aarFile) + it.baseOutputDir.set(output) + } + + task.taskAction() + + assertTrue(File(output, "libheader/headers/justaheader.h").exists()) + } + + @Test + fun extractJniAndHeadersTask_extractsJniCorrectly() { + val project = createProject() + val aarFile = File(project.projectDir, "something.aar") + File(tempFolder.root, "jni/libsomething.so").apply { + parentFile.mkdirs() + createNewFile() + } + val output = tempFolder.newFolder("output") + ZipOutputStream(BufferedOutputStream(FileOutputStream(aarFile.absolutePath))).use { out -> + FileInputStream(aarFile).use { fi -> + BufferedInputStream(fi).use { origin -> + out.putNextEntry(ZipEntry("jni/")) + out.putNextEntry(ZipEntry("jni/libsomething.so")) + origin.copyTo(out, 1024) + } + } + } + val task = + createTestTask(project = project) { + it.extractJniConfiguration.setFrom(aarFile) + it.baseOutputDir.set(output) + } + + task.taskAction() + + assertTrue(File(output, "something/jni/libsomething.so").exists()) + } +}