Export prepareJSC to an internal task (#32427)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/32427

This diff refactors the `prepareJSC` task to a separate Gradle task in the `.internal` package.

The reason for this change is that `prepareJSC` was just a plain `Task` and not a `Copy` task.
It was defining a top level `doLast` action and would result in being
invalidated whenever the `build.gradle` file would change. This means that the JSC headers/source files
would have been extracted again, effectively invalidating the timestamps for the native build.

Changelog:
[Internal] [Changed] - Export prepareJSC to an internal task

Reviewed By: ShikaSD

Differential Revision: D31682293

fbshipit-source-id: 3d4cd9d9ce2fcd45e61f3c8c6685b69a622a1912
This commit is contained in:
Nicola Corti 2021-10-18 04:32:45 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 52b0cc0355
Коммит 75b2e5cc97
4 изменённых файлов: 200 добавлений и 28 удалений

Просмотреть файл

@ -161,35 +161,11 @@ final def prepareGlog = tasks.register("prepareGlog", PrepareGlogTask) {
}
// Create Android.mk library module based on jsc from npm
task prepareJSC {
doLast {
def jscPackagePath = findNodeModulePath(projectDir, "jsc-android")
if (!jscPackagePath) {
throw new GradleScriptException("Could not find the jsc-android npm package", null)
}
def jscDist = file("$jscPackagePath/dist")
if (!jscDist.exists()) {
throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory", null)
}
def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile
def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" })
def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" })
copy {
from(soFiles)
from(headerFiles)
from("src/main/jni/third-party/jsc/Android.mk")
filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" })
includeEmptyDirs(false)
into("$thirdPartyNdkDir/jsc")
}
}
tasks.register('prepareJSC', PrepareJSCTask) {
it.jscPackagePath.set(findNodeModulePath(projectDir, "jsc-android"))
it.outputDir = project.layout.buildDirectory.dir("third-party-ndk/jsc")
}
task downloadNdkBuildDependencies {
if (!boostPath) {
dependsOn(downloadBoost)

Просмотреть файл

@ -0,0 +1,50 @@
/*
* 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 java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A task that takes care of unbundling JSC and preparing it for be consumed by the Android NDK.
* Specifically it will unbundle shared libs, headers and will copy over the Makefile from
* `src/main/jni/third-party/jsc/`
*/
abstract class PrepareJSCTask : DefaultTask() {
@get:Input abstract val jscPackagePath: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@TaskAction
fun taskAction() {
if (!jscPackagePath.isPresent || jscPackagePath.orNull == null) {
error("Could not find the jsc-android npm package")
}
val jscDist = File(jscPackagePath.get(), "dist")
if (!jscDist.exists()) {
error("The jsc-android npm package is missing its \"dist\" directory")
}
val jscAAR =
project.fileTree(jscDist).matching { it.include("**/android-jsc/**/*.aar") }.singleFile
val soFiles = project.zipTree(jscAAR).matching { it.include("**/*.so") }
val headerFiles = project.fileTree(jscDist).matching { it.include("**/include/*.h") }
project.copy { it ->
it.from(soFiles)
it.from(headerFiles)
it.from(project.file("src/main/jni/third-party/jsc/Android.mk"))
it.filesMatching("**/*.h") { it.path = "JavaScriptCore/${it.name}" }
it.includeEmptyDirs = false
it.into(outputDir)
}
}
}

Просмотреть файл

@ -0,0 +1,128 @@
/*
* 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 org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class PrepareJSCTaskTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test(expected = IllegalStateException::class)
fun prepareJSCTask_withMissingPackage_fails() {
val task = createTestTask<PrepareJSCTask>()
task.taskAction()
}
@Test(expected = IllegalStateException::class)
fun prepareJSCTask_withNullPackage_fails() {
val task = createTestTask<PrepareJSCTask> { it.jscPackagePath.set(null as String?) }
task.taskAction()
}
@Test(expected = IllegalStateException::class)
fun prepareJSCTask_withMissingDistFolder_fails() {
val task =
createTestTask<PrepareJSCTask> { it.jscPackagePath.set(tempFolder.root.absolutePath) }
task.taskAction()
}
@Test
fun prepareJSCTask_ignoresEmptyDirs() {
prepareInputFolder()
val output = tempFolder.newFolder("output")
File(tempFolder.root, "dist/just/an/empty/folders/").apply { mkdirs() }
val task =
createTestTask<PrepareJSCTask> {
it.jscPackagePath.set(tempFolder.root.absolutePath)
it.outputDir.set(output)
}
task.taskAction()
assertFalse(File(output, "just/an/empty/folders/").exists())
}
@Test
fun prepareJSCTask_copiesSoFiles() {
val soFile = tempFolder.newFile("libsomething.so")
prepareInputFolder(aarContent = listOf(soFile))
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareJSCTask> {
it.jscPackagePath.set(tempFolder.root.absolutePath)
it.outputDir.set(output)
}
task.taskAction()
assertEquals("libsomething.so", output.listFiles()?.first()?.name)
}
@Test
fun prepareJSCTask_copiesHeaderFilesToCorrectFolder() {
prepareInputFolder()
File(tempFolder.root, "dist/include/justaheader.h").apply {
parentFile.mkdirs()
createNewFile()
}
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareJSCTask> {
it.jscPackagePath.set(tempFolder.root.absolutePath)
it.outputDir.set(output)
}
task.taskAction()
assertTrue(File(output, "JavaScriptCore/justaheader.h").exists())
}
@Test
fun prepareJSCTask_copiesMakefile() {
val project = createProject()
prepareInputFolder()
File(project.projectDir, "src/main/jni/third-party/jsc/Android.mk").apply {
parentFile.mkdirs()
createNewFile()
}
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareJSCTask>(project = project) {
it.jscPackagePath.set(tempFolder.root.absolutePath)
it.outputDir.set(output)
}
task.taskAction()
assertTrue(File(output, "Android.mk").exists())
}
private fun prepareInputFolder(aarContent: List<File> = listOf(tempFolder.newFile())) {
val dist = tempFolder.newFolder("dist")
File(dist, "android-jsc/android-library.aar").apply {
parentFile.mkdirs()
createNewFile()
}
zipFiles(File(dist, "android-jsc/android-library.aar"), aarContent)
}
}

Просмотреть файл

@ -7,6 +7,9 @@
package com.facebook.react.tests
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.testfixtures.ProjectBuilder
@ -23,3 +26,18 @@ internal inline fun <reified T : Task> createTestTask(
taskName: String = T::class.java.simpleName,
crossinline block: (T) -> Unit = {}
): T = project.tasks.register(taskName, T::class.java) { block(it) }.get()
/** A util function to zip a list of files from [contents] inside the zipfile at [destination]. */
internal fun zipFiles(destination: File, contents: List<File>) {
ZipOutputStream(BufferedOutputStream(FileOutputStream(destination.absolutePath))).use { out ->
for (file in contents) {
FileInputStream(file).use { fi ->
BufferedInputStream(fi).use { origin ->
val entry = ZipEntry(file.name)
out.putNextEntry(entry)
origin.copyTo(out, 1024)
}
}
}
}
}