RNGP - Setup tasks to cleanup the Apk/Aab from unused .so

Summary:
This is part of a series of tasks to make the React Native Gradle Plugin (RNGP) variant-aware.

Here I'm creating some tasks that will take care of doing the .so cleanup of the Apk/AppBundle.

Historically we used to use a error prone mechanism which was relying on assumptions on file paths and was breaking the variant-aware assumptions.

I'm going to use those tasks in a subsequent diff.

Changelog:
[Internal] [Changed] - RNGP - RNGP - Setup tasks to cleanup the Apk/Aab from unused .so

Reviewed By: cipolleschi

Differential Revision: D40512100

fbshipit-source-id: 649999dc8d126e7c939b61a5cec608d39cd7f688
This commit is contained in:
Nicola Corti
2022-10-19 10:59:56 -07:00
committed by Facebook GitHub Bot
parent 31df147d64
commit f5bbca50c5
7 changed files with 570 additions and 0 deletions
@@ -0,0 +1,40 @@
/*
* 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.SoCleanerUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class NativeLibraryAabCleanupTask : DefaultTask() {
@get:InputFile abstract val inputBundle: RegularFileProperty
@get:OutputFile abstract val outputBundle: RegularFileProperty
@get:Input abstract val enableHermes: Property<Boolean>
@get:Input abstract val debuggableVariant: Property<Boolean>
@TaskAction
fun run() {
val inputBundleFile = inputBundle.get().asFile
val outputBundleFile = outputBundle.get().asFile
SoCleanerUtils.clean(
input = inputBundleFile,
prefix = "base/lib",
enableHermes = enableHermes.get(),
debuggableVariant = debuggableVariant.get())
inputBundleFile.copyTo(outputBundleFile, overwrite = true)
}
}
@@ -0,0 +1,41 @@
/*
* 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.SoCleanerUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class NativeLibraryApkCleanupTask : DefaultTask() {
@get:InputDirectory abstract val inputApkDirectory: DirectoryProperty
@get:OutputDirectory abstract val outputApkDirectory: DirectoryProperty
@get:Input abstract val enableHermes: Property<Boolean>
@get:Input abstract val debuggableVariant: Property<Boolean>
@TaskAction
fun run() {
inputApkDirectory.get().asFile.walk().forEach {
if (it.name.endsWith(".apk")) {
SoCleanerUtils.clean(
input = it,
prefix = "lib/",
enableHermes = enableHermes.get(),
debuggableVariant = debuggableVariant.get())
}
}
}
}
@@ -0,0 +1,58 @@
/*
* 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.utils
import java.io.File
import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Files
internal object SoCleanerUtils {
private val zipProperties = mapOf("create" to "false")
private val archs = listOf("x86", "x86_64", "armeabi-v7a", "arm64-v8a")
fun clean(input: File, prefix: String, enableHermes: Boolean, debuggableVariant: Boolean) {
val zipDisk: URI = URI.create("jar:file:${input.absolutePath}")
FileSystems.newFileSystem(zipDisk, zipProperties).use { zipfs ->
buildList {
if (enableHermes) {
add("libjsc.so")
add("libjscexecutor.so")
if (debuggableVariant) {
add("libhermes-executor-release.so")
} else {
add("libhermes-executor-debug.so")
}
} else {
add("libhermes.so")
add("libhermes-executor-debug.so")
add("libhermes-executor-release.so")
}
}
.forEach { removeSoFiles(zipfs, prefix, archs, it) }
}
}
fun removeSoFiles(
zipfs: FileSystem,
prefix: String,
archs: List<String>,
libraryToRemove: String
) {
archs.forEach { arch ->
try {
Files.delete(zipfs.getPath("$prefix/$arch/$libraryToRemove"))
} catch (e: Exception) {
// File was already missing due to ABI split, nothing to do here.
}
}
}
}
@@ -0,0 +1,165 @@
/*
* 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.createTestTask
import com.facebook.react.tests.createZip
import java.io.File
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files.exists
import org.gradle.api.tasks.*
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class NativeLibraryAabCleanupTaskTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test
fun nativeLibraryAabCleanupTask_withHermesDebug_cleansCorrectly() {
val inputAab = File(tempFolder.root, "input.aab")
val outputAab = File(tempFolder.root, "output.aab")
createZip(
inputAab,
listOf(
"base/lib/x86/libhermes.so",
"base/lib/x86/libhermes-executor-debug.so",
"base/lib/x86/libhermes-executor-release.so",
"base/lib/x86/libjsc.so",
"base/lib/x86/libjscexecutor.so",
))
val task =
createTestTask<NativeLibraryAabCleanupTask> {
it.inputBundle.set(inputAab)
it.outputBundle.set(outputAab)
it.enableHermes.set(true)
it.debuggableVariant.set(true)
}
task.run()
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${inputAab.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertTrue(exists(it.getPath("base/lib/x86/libhermes.so")))
assertTrue(exists(it.getPath("base/lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-release.so")))
assertFalse(exists(it.getPath("base/lib/x86/libjsc.so")))
assertFalse(exists(it.getPath("base/lib/x86/libjscexecutor.so")))
}
}
@Test
fun nativeLibraryAabCleanupTask_withHermesRelease_cleansCorrectly() {
val inputAab = File(tempFolder.root, "input.aab")
val outputAab = File(tempFolder.root, "output.aab")
createZip(
inputAab,
listOf(
"base/lib/x86/libhermes.so",
"base/lib/x86/libhermes-executor-debug.so",
"base/lib/x86/libhermes-executor-release.so",
"base/lib/x86/libjsc.so",
"base/lib/x86/libjscexecutor.so",
))
val task =
createTestTask<NativeLibraryAabCleanupTask> {
it.inputBundle.set(inputAab)
it.outputBundle.set(outputAab)
it.enableHermes.set(true)
it.debuggableVariant.set(false)
}
task.run()
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${inputAab.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertTrue(exists(it.getPath("base/lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-debug.so")))
assertTrue(exists(it.getPath("base/lib/x86/libhermes-executor-release.so")))
assertFalse(exists(it.getPath("base/lib/x86/libjsc.so")))
assertFalse(exists(it.getPath("base/lib/x86/libjscexecutor.so")))
}
}
@Test
fun nativeLibraryAabCleanupTask_withJscDebug_cleansCorrectly() {
val inputAab = File(tempFolder.root, "input.aab")
val outputAab = File(tempFolder.root, "output.aab")
createZip(
inputAab,
listOf(
"base/lib/x86/libhermes.so",
"base/lib/x86/libhermes-executor-debug.so",
"base/lib/x86/libhermes-executor-release.so",
"base/lib/x86/libjsc.so",
"base/lib/x86/libjscexecutor.so",
))
val task =
createTestTask<NativeLibraryAabCleanupTask> {
it.inputBundle.set(inputAab)
it.outputBundle.set(outputAab)
it.enableHermes.set(false)
it.debuggableVariant.set(true)
}
task.run()
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${inputAab.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertFalse(exists(it.getPath("base/lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-release.so")))
assertTrue(exists(it.getPath("base/lib/x86/libjsc.so")))
assertTrue(exists(it.getPath("base/lib/x86/libjscexecutor.so")))
}
}
@Test
fun nativeLibraryAabCleanupTask_withJscRelease_cleansCorrectly() {
val inputAab = File(tempFolder.root, "input.aab")
val outputAab = File(tempFolder.root, "output.aab")
createZip(
inputAab,
listOf(
"base/lib/x86/libhermes.so",
"base/lib/x86/libhermes-executor-debug.so",
"base/lib/x86/libhermes-executor-release.so",
"base/lib/x86/libjsc.so",
"base/lib/x86/libjscexecutor.so",
))
val task =
createTestTask<NativeLibraryAabCleanupTask> {
it.inputBundle.set(inputAab)
it.outputBundle.set(outputAab)
it.enableHermes.set(false)
it.debuggableVariant.set(false)
}
task.run()
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${inputAab.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertFalse(exists(it.getPath("base/lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("base/lib/x86/libhermes-executor-release.so")))
assertTrue(exists(it.getPath("base/lib/x86/libjsc.so")))
assertTrue(exists(it.getPath("base/lib/x86/libjscexecutor.so")))
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.createTestTask
import com.facebook.react.tests.createZip
import java.io.File
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files.exists
import org.gradle.api.tasks.*
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class NativeLibraryApkCleanupTaskTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test
fun nativeLibraryApkCleanupTask_runWithAppApk() {
val tempApk = File(tempFolder.root, "app.apk")
createZip(
tempApk,
listOf(
"lib/x86/libhermes.so",
"lib/x86/libhermes-executor-debug.so",
"lib/x86/libhermes-executor-release.so",
"lib/x86/libjsc.so",
"lib/x86/libjscexecutor.so",
))
val task =
createTestTask<NativeLibraryApkCleanupTask> {
it.inputApkDirectory.set(tempFolder.root)
it.outputApkDirectory.set(tempFolder.root)
it.enableHermes.set(true)
it.debuggableVariant.set(true)
}
task.run()
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempApk.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertTrue(exists(it.getPath("lib/x86/libhermes.so")))
assertTrue(exists(it.getPath("lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-release.so")))
assertFalse(exists(it.getPath("lib/x86/libjsc.so")))
assertFalse(exists(it.getPath("lib/x86/libjscexecutor.so")))
}
}
}
@@ -8,6 +8,9 @@
package com.facebook.react.tests
import java.io.*
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import org.gradle.api.Project
@@ -49,3 +52,18 @@ internal fun zipFiles(destination: File, contents: List<File>) {
}
}
}
/** A util function to create a zip given a list of dummy files path. */
internal fun createZip(dest: File, paths: List<String>) {
val env = mapOf("create" to "true")
val uri = URI.create("jar:file:$dest")
FileSystems.newFileSystem(uri, env).use { zipfs ->
paths.forEach {
val zipEntryPath = zipfs.getPath(it)
val zipEntryFolder = zipEntryPath.subpath(0, zipEntryPath.nameCount - 1)
Files.createDirectories(zipEntryFolder)
Files.createFile(zipEntryPath)
}
}
}
@@ -0,0 +1,189 @@
/*
* 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.utils
import com.facebook.react.tests.createZip
import com.facebook.react.utils.SoCleanerUtils.clean
import com.facebook.react.utils.SoCleanerUtils.removeSoFiles
import java.io.File
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files.*
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.Test.None
import org.junit.rules.TemporaryFolder
class SoCleanerUtilsTest {
@get:Rule val tempFolder = TemporaryFolder()
@Test
fun clean_withEnableHermesAndDebuggableVariant_removesCorrectly() {
val tempZip =
File(tempFolder.root, "app.apk").apply {
createZip(
this,
listOf(
"lib/x86/libhermes.so",
"lib/x86/libhermes-executor-debug.so",
"lib/x86/libhermes-executor-release.so",
"lib/x86/libjsc.so",
"lib/x86/libjscexecutor.so",
))
}
clean(tempZip, "lib", enableHermes = true, debuggableVariant = true)
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertTrue(exists(it.getPath("lib/x86/libhermes.so")))
assertTrue(exists(it.getPath("lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-release.so")))
assertFalse(exists(it.getPath("lib/x86/libjsc.so")))
assertFalse(exists(it.getPath("lib/x86/libjscexecutor.so")))
}
}
@Test
fun clean_withEnableHermesAndNonDebuggableVariant_removesCorrectly() {
val tempZip =
File(tempFolder.root, "app.apk").apply {
createZip(
this,
listOf(
"lib/x86/libhermes.so",
"lib/x86/libhermes-executor-debug.so",
"lib/x86/libhermes-executor-release.so",
"lib/x86/libjsc.so",
"lib/x86/libjscexecutor.so",
))
}
clean(tempZip, "lib", enableHermes = true, debuggableVariant = false)
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertTrue(exists(it.getPath("lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-debug.so")))
assertTrue(exists(it.getPath("lib/x86/libhermes-executor-release.so")))
assertFalse(exists(it.getPath("lib/x86/libjsc.so")))
assertFalse(exists(it.getPath("lib/x86/libjscexecutor.so")))
}
}
@Test
fun clean_withJscAndDebuggableVariant_removesCorrectly() {
val tempZip =
File(tempFolder.root, "app.apk").apply {
createZip(
this,
listOf(
"lib/x86/libhermes.so",
"lib/x86/libhermes-executor-debug.so",
"lib/x86/libhermes-executor-release.so",
"lib/x86/libjsc.so",
"lib/x86/libjscexecutor.so",
))
}
clean(tempZip, "lib", enableHermes = false, debuggableVariant = true)
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertFalse(exists(it.getPath("lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-release.so")))
assertTrue(exists(it.getPath("lib/x86/libjsc.so")))
assertTrue(exists(it.getPath("lib/x86/libjscexecutor.so")))
}
}
@Test
fun clean_withJscAndNonDebuggableVariant_removesCorrectly() {
val tempZip =
File(tempFolder.root, "app.apk").apply {
createZip(
this,
listOf(
"lib/x86/libhermes.so",
"lib/x86/libhermes-executor-debug.so",
"lib/x86/libhermes-executor-release.so",
"lib/x86/libjsc.so",
"lib/x86/libjscexecutor.so",
))
}
clean(tempZip, "lib", enableHermes = false, debuggableVariant = false)
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
fs.use {
assertFalse(exists(it.getPath("lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-debug.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes-executor-release.so")))
assertTrue(exists(it.getPath("lib/x86/libjsc.so")))
assertTrue(exists(it.getPath("lib/x86/libjscexecutor.so")))
}
}
@Test(expected = None::class)
fun removeSoFiles_withEmptyZip_doesNothing() {
val tempZip = File(tempFolder.root, "app.apk")
createZip(tempZip, emptyList())
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
val archs = listOf("x86")
val libraryToRemove = "libhello.so"
removeSoFiles(fs, "lib", archs, libraryToRemove)
}
@Test
fun removeSoFiles_withValidFiles_filtersThemCorrectly() {
val tempZip = File(tempFolder.root, "app.apk")
createZip(
tempZip,
listOf(
"base/lib/x86_64/libhermes.so",
"base/lib/x86/libhermes.so",
"lib/arm64-v8a/libhermes.so",
"lib/armeabi-v7a/libhermes.so",
"lib/x86/libhermes.so",
"lib/x86_64/libhermes.so",
))
val fs =
FileSystems.newFileSystem(
URI.create("jar:file:${tempZip.absoluteFile}"), mapOf("create" to "false"))
val archs = listOf("x86", "x86_64")
val libraryToRemove = "libhermes.so"
removeSoFiles(fs, "lib", archs, libraryToRemove)
fs.use {
assertTrue(exists(it.getPath("base/lib/x86/libhermes.so")))
assertTrue(exists(it.getPath("base/lib/x86_64/libhermes.so")))
assertTrue(exists(it.getPath("lib/arm64-v8a/libhermes.so")))
assertTrue(exists(it.getPath("lib/armeabi-v7a/libhermes.so")))
assertFalse(exists(it.getPath("lib/x86/libhermes.so")))
assertFalse(exists(it.getPath("lib/x86_64/libhermes.so")))
}
}
}