Avoid using internal AGP APIs in Stable AIDL plugin
Package headers using an AAR transformation, rather than obtaining the
directory from AGP's AidlCompile task.
Obtain the AIDL executable and framework paths by constructing them
ourselves, rather than pulling them from AGP's internal classes.
Bug: 253427301
Test: StableAidlPackageApiTest
Change-Id: Ia7cac47bdef33ab933361e577a38fc349666f85e
diff --git a/stableaidl/stableaidl-gradle-plugin/build.gradle b/stableaidl/stableaidl-gradle-plugin/build.gradle
index 4d56951..9066c1c 100644
--- a/stableaidl/stableaidl-gradle-plugin/build.gradle
+++ b/stableaidl/stableaidl-gradle-plugin/build.gradle
@@ -32,6 +32,7 @@
implementation(libs.androidToolsRepository)
implementation(libs.androidToolsSdkCommon)
implementation(libs.apacheCommonIo)
+ implementation(libs.apacheAnt)
implementation(libs.guava)
implementation(libs.kotlinStdlib)
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
index ea681e8..a0a6848 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
@@ -17,6 +17,7 @@
package androidx.stableaidl
import androidx.stableaidl.tasks.StableAidlCompile
+import com.android.build.api.dsl.SdkComponents
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.DslExtension
import com.android.build.gradle.AppExtension
@@ -27,11 +28,13 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
+import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
-private const val PLUGIN_DIRNAME = "stable-aidl"
+private const val PLUGIN_DIRNAME = "stable_aidl"
private const val GENERATED_PATH = "generated/source/$PLUGIN_DIRNAME"
+private const val INTERMEDIATES_PATH = "intermediates/${PLUGIN_DIRNAME}_parcelable"
@Suppress("unused", "UnstableApiUsage")
abstract class StableAidlPlugin : Plugin<Project> {
@@ -41,6 +44,8 @@
?: throw GradleException("Stable AIDL plugin requires Android Gradle Plugin")
val base = project.extensions.getByType(BaseExtension::class.java)
?: throw GradleException("Stable AIDL plugin requires Android Gradle Plugin")
+ val aidlExecutable = androidComponents.sdkComponents.aidl(base)
+ val aidlFramework = androidComponents.sdkComponents.aidlFramework(base)
// Extend the android sourceSet.
androidComponents.registerSourceType(SOURCE_TYPE_STABLE_AIDL)
@@ -74,7 +79,11 @@
androidComponents.onVariants { variant ->
val sourceDir = variant.sources.getByName(SOURCE_TYPE_STABLE_AIDL)
val importsDir = variant.sources.getByName(SOURCE_TYPE_STABLE_AIDL_IMPORTS)
- val outputDir = project.layout.buildDirectory.dir("$GENERATED_PATH/${variant.name}")
+ val outputDir = project.layout.buildDirectory.dir(
+ "$GENERATED_PATH/${variant.name}")
+ val packagedDir = project.layout.buildDirectory.dir(
+ "$INTERMEDIATES_PATH/${variant.name}/out")
+
val apiDirName = "$API_DIR/aidl${variant.name.usLocaleCapitalize()}"
val builtApiDir = project.layout.buildDirectory.dir(apiDirName)
val lastReleasedApiDir =
@@ -84,16 +93,28 @@
val compileAidlApiTask = registerCompileAidlApi(
project,
- base,
variant,
+ aidlExecutable,
+ aidlFramework,
sourceDir,
+ packagedDir,
importsDir,
outputDir
)
+
+ // To avoid using the same output directory as AGP's AidlCompile task, we need to
+ // register a post-processing task to copy packaged parcelable headers into the AAR.
+ registerPackageAidlApi(
+ project,
+ variant,
+ compileAidlApiTask
+ )
+
val generateAidlApiTask = registerGenerateAidlApi(
project,
- base,
variant,
+ aidlExecutable,
+ aidlFramework,
sourceDir,
importsDir,
builtApiDir,
@@ -101,16 +122,18 @@
)
val checkAidlApiReleaseTask = registerCheckApiAidlRelease(
project,
- base,
variant,
+ aidlExecutable,
+ aidlFramework,
importsDir,
lastReleasedApiDir,
generateAidlApiTask
)
registerCheckAidlApi(
project,
- base,
variant,
+ aidlExecutable,
+ aidlFramework,
importsDir,
lastCheckedInApiDir,
generateAidlApiTask,
@@ -172,3 +195,21 @@
"androidx.stableaidl plugin must be used with Android app, library or feature plugin"
)
}
+
+internal fun SdkComponents.aidl(baseExtension: BaseExtension): Provider<RegularFile> =
+ sdkDirectory.map {
+ it.dir("build-tools").dir(baseExtension.buildToolsVersion).file(
+ if (java.lang.System.getProperty("os.name").startsWith("Windows")) {
+ "aidl.exe"
+ } else {
+ "aidl"
+ }
+ )
+ }
+
+internal fun SdkComponents.aidlFramework(baseExtension: BaseExtension): Provider<RegularFile> =
+ sdkDirectory.map {
+ it.dir("platforms")
+ .dir(baseExtension.compileSdkVersion!!)
+ .file("framework.aidl")
+ }
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
index ff8b9ee..733c9eb 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlTasks.kt
@@ -16,17 +16,19 @@
package androidx.stableaidl
+import androidx.stableaidl.tasks.StableAidlPackageApi
import androidx.stableaidl.tasks.StableAidlCheckApi
import androidx.stableaidl.tasks.StableAidlCompile
import androidx.stableaidl.tasks.UpdateStableAidlApiTask
+import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.SourceDirectories
import com.android.build.api.variant.Variant
-import com.android.build.gradle.BaseExtension
import com.android.utils.usLocaleCapitalize
import java.io.File
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
+import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
@@ -35,9 +37,11 @@
@Suppress("UnstableApiUsage") // SourceDirectories.Flat
fun registerCompileAidlApi(
project: Project,
- baseExtension: BaseExtension,
variant: Variant,
+ aidlExecutable: Provider<RegularFile>,
+ aidlFramework: Provider<RegularFile>,
sourceDir: SourceDirectories.Flat,
+ packagedDir: Provider<Directory>,
importsDir: SourceDirectories.Flat,
outputDir: Provider<Directory>
): TaskProvider<StableAidlCompile> = project.tasks.register(
@@ -47,10 +51,11 @@
task.group = TASK_GROUP_API
task.description = "Compiles AIDL source code"
task.variantName = variant.name
- task.configureBuildToolsFrom(baseExtension)
- task.configurePackageDirFrom(project, variant)
+ task.aidlExecutable.set(aidlExecutable)
+ task.aidlFrameworkProvider.set(aidlFramework)
task.sourceDirs.set(sourceDir.all)
task.sourceOutputDir.set(outputDir)
+ task.packagedDir.set(packagedDir)
task.importDirs.set(importsDir.all)
task.extraArgs.set(
listOf(
@@ -59,11 +64,30 @@
)
}
+fun registerPackageAidlApi(
+ project: Project,
+ variant: Variant,
+ compileAidlApiTask: TaskProvider<StableAidlCompile>
+): TaskProvider<StableAidlPackageApi> = project.tasks.register(
+ computeTaskName("package", variant, "AidlApi"),
+ StableAidlPackageApi::class.java
+) { task ->
+ task.packagedDir.set(compileAidlApiTask.flatMap { it.packagedDir })
+}.also { taskProvider ->
+ variant.artifacts.use(taskProvider)
+ .wiredWithFiles(
+ StableAidlPackageApi::aarFile,
+ StableAidlPackageApi::updatedAarFile,
+ )
+ .toTransform(SingleArtifact.AAR)
+}
+
@Suppress("UnstableApiUsage") // SourceDirectories.Flat
fun registerGenerateAidlApi(
project: Project,
- baseExtension: BaseExtension,
variant: Variant,
+ aidlExecutable: Provider<RegularFile>,
+ aidlFramework: Provider<RegularFile>,
sourceDir: SourceDirectories.Flat,
importsDir: SourceDirectories.Flat,
builtApiDir: Provider<Directory>,
@@ -75,8 +99,8 @@
task.group = TASK_GROUP_API
task.description = "Generates API files from AIDL source code"
task.variantName = variant.name
- task.configureBuildToolsFrom(baseExtension)
- task.configurePackageDirFrom(project, variant)
+ task.aidlExecutable.set(aidlExecutable)
+ task.aidlFrameworkProvider.set(aidlFramework)
task.sourceDirs.set(sourceDir.all)
task.sourceOutputDir.set(builtApiDir)
task.importDirs.set(importsDir.all)
@@ -95,8 +119,9 @@
@Suppress("UnstableApiUsage") // SourceDirectories.Flat
fun registerCheckApiAidlRelease(
project: Project,
- baseExtension: BaseExtension,
variant: Variant,
+ aidlExecutable: Provider<RegularFile>,
+ aidlFramework: Provider<RegularFile>,
importsDir: SourceDirectories.Flat,
lastReleasedApiDir: Directory,
generateAidlTask: Provider<StableAidlCompile>
@@ -108,7 +133,8 @@
task.description = "Checks the AIDL source code API surface against the " +
"stabilized AIDL API files"
task.variantName = variant.name
- task.configureBuildToolsFrom(baseExtension)
+ task.aidlExecutable.set(aidlExecutable)
+ task.aidlFrameworkProvider.set(aidlFramework)
task.importDirs.set(importsDir.all)
task.checkApiMode.set(StableAidlCheckApi.MODE_COMPATIBLE)
task.expectedApiDir.set(lastReleasedApiDir)
@@ -123,8 +149,9 @@
@Suppress("UnstableApiUsage") // SourceDirectories.Flat
fun registerCheckAidlApi(
project: Project,
- baseExtension: BaseExtension,
variant: Variant,
+ aidlExecutable: Provider<RegularFile>,
+ aidlFramework: Provider<RegularFile>,
importsDir: SourceDirectories.Flat,
lastCheckedInApiFile: Directory,
generateAidlTask: Provider<StableAidlCompile>,
@@ -137,7 +164,8 @@
task.description = "Checks the AIDL source code API surface against the checked-in " +
"AIDL API files"
task.variantName = variant.name
- task.configureBuildToolsFrom(baseExtension)
+ task.aidlExecutable.set(aidlExecutable)
+ task.aidlFrameworkProvider.set(aidlFramework)
task.importDirs.set(importsDir.all)
task.checkApiMode.set(StableAidlCheckApi.MODE_EQUAL)
task.expectedApiDir.set(lastCheckedInApiFile)
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCheckApi.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCheckApi.kt
index da56750..5b52a95 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCheckApi.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCheckApi.kt
@@ -18,9 +18,6 @@
import androidx.stableaidl.internal.LoggerWrapper
import androidx.stableaidl.internal.process.GradleProcessExecutor
-import com.android.build.gradle.BaseExtension
-import com.android.build.gradle.internal.BuildToolsExecutableInput
-import com.android.build.gradle.internal.services.getBuildService
import com.android.ide.common.process.LoggedProcessOutputHandler
import com.google.common.annotations.VisibleForTesting
import java.io.File
@@ -33,14 +30,12 @@
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
@@ -66,10 +61,13 @@
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val importDirs: ListProperty<Directory>
- @InputFile
- @PathSensitive(PathSensitivity.NONE)
- fun getAidlFrameworkProvider(): Provider<File> =
- buildTools.aidlFrameworkProvider()
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.NONE)
+ abstract val aidlFrameworkProvider: RegularFileProperty
+
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.NONE)
+ abstract val aidlExecutable: RegularFileProperty
// We cannot use InputDirectory here because the directory may not exist yet.
@get:InputFiles
@@ -83,9 +81,6 @@
@get:Input
abstract val checkApiMode: Property<String>
- @get:Nested
- abstract val buildTools: BuildToolsExecutableInput
-
@get:Input
@get:Optional
abstract val failOnMissingExpected: Property<Boolean>
@@ -97,23 +92,8 @@
@get:Inject
abstract val workerExecutor: WorkerExecutor
- /**
- * Configures build tools based on AGP's [BaseExtension].
- */
- fun configureBuildToolsFrom(baseExtension: BaseExtension) {
- buildTools.buildToolsRevision.set(baseExtension.buildToolsRevision)
- buildTools.compileSdkVersion.set(baseExtension.compileSdkVersion)
- buildTools.sdkBuildService.set(getBuildService(project.gradle.sharedServices))
- }
-
@TaskAction
fun checkApi() {
- val aidlExecutable = buildTools
- .aidlExecutableProvider()
- .get()
- .absoluteFile
- val frameworkLocation = getAidlFrameworkProvider().get().absoluteFile
-
val checkApiMode = checkApiMode.get()
val expectedApiDir = expectedApiDir.get()
val actualApiDir = actualApiDir.get()
@@ -133,8 +113,8 @@
aidlCheckApiDelegate(
workerExecutor,
- aidlExecutable,
- frameworkLocation,
+ aidlExecutable.get().asFile,
+ aidlFrameworkProvider.get().asFile,
extraArgs,
importDirs.get()
)
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCompile.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCompile.kt
index dfc62a9..6dd4c09 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCompile.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlCompile.kt
@@ -19,16 +19,12 @@
import androidx.stableaidl.internal.DirectoryWalker
import androidx.stableaidl.internal.LoggerWrapper
import androidx.stableaidl.internal.process.GradleProcessExecutor
-import com.android.build.api.variant.Variant
-import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.BuildToolsExecutableInput
import com.android.build.gradle.internal.services.getBuildService
-import com.android.build.gradle.tasks.AidlCompile
import com.android.builder.compiling.DependencyFileProcessor
import com.android.builder.internal.incremental.DependencyData
import com.android.ide.common.process.LoggedProcessOutputHandler
import com.android.utils.FileUtils
-import com.android.utils.usLocaleCapitalize
import com.google.common.annotations.VisibleForTesting
import java.io.File
import java.io.IOException
@@ -36,20 +32,17 @@
import java.nio.file.Path
import javax.inject.Inject
import org.gradle.api.DefaultTask
-import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
@@ -93,10 +86,13 @@
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val importDirs: ListProperty<Directory>
- @InputFile
- @PathSensitive(PathSensitivity.NONE)
- fun getAidlFrameworkProvider(): Provider<File> =
- buildTools.aidlFrameworkProvider()
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.NONE)
+ abstract val aidlFrameworkProvider: RegularFileProperty
+
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.NONE)
+ abstract val aidlExecutable: RegularFileProperty
/**
* Directory for storing AIDL-generated Java sources.
@@ -106,16 +102,11 @@
/**
* Directory for storing Parcelable headers for consumers.
- *
- * These are copied directly from AIDL sources in [sourceDirs].
*/
@get:OutputDirectory
@get:Optional
abstract val packagedDir: DirectoryProperty
- @get:Nested
- abstract val buildTools: BuildToolsExecutableInput
-
@get:Input
@get:Optional
abstract val extraArgs: ListProperty<String>
@@ -129,40 +120,10 @@
}
}
- /**
- * Configures packaged output directory based on AGP's [AidlCompile] task for the [variant].
- */
- fun configurePackageDirFrom(project: Project, variant: Variant) {
- val compileAidlTask = project.tasks.named(
- "compile${variant.name.usLocaleCapitalize()}Aidl", AidlCompile::class.java)
- // Packaged output directory is configured in AGP using:
- // if (creationConfig.componentType.isAar) {
- // creationConfig.artifacts.setInitialProvider(
- // taskProvider,
- // aidlCompile::packagedDir
- // ).withName("out").on(InternalArtifactType.AIDL_PARCELABLE)
- // }
- packagedDir.set(compileAidlTask.flatMap { it.packagedDir })
- }
-
- /**
- * Configures build tools based on AGP's [BaseExtension].
- */
- fun configureBuildToolsFrom(baseExtension: BaseExtension) {
- // These are all required by aidlExecutableProvider().
- buildTools.buildToolsRevision.set(baseExtension.buildToolsRevision)
- buildTools.compileSdkVersion.set(baseExtension.compileSdkVersion)
- buildTools.sdkBuildService.set(getBuildService(project.gradle.sharedServices))
- }
-
@TaskAction
fun compile() {
- // this is full run, clean the previous output'
- val aidlExecutable = buildTools
- .aidlExecutableProvider()
- .get()
- .absoluteFile
- val frameworkLocation = getAidlFrameworkProvider().get().absoluteFile
+ // this is full run, clean the previous output
+ // TODO: Is this actually necessary?
val destinationDir = sourceOutputDir.get().asFile
FileUtils.cleanOutputDir(destinationDir)
if (!destinationDir.exists()) {
@@ -179,8 +140,8 @@
aidlCompileDelegate(
workerExecutor,
- aidlExecutable,
- frameworkLocation,
+ aidlExecutable.get().asFile,
+ aidlFrameworkProvider.get().asFile,
destinationDir,
parcelableDir?.asFile,
extraArgs.get(),
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlPackageApi.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlPackageApi.kt
new file mode 100644
index 0000000..218b300
--- /dev/null
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/tasks/StableAidlPackageApi.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.stableaidl.tasks
+
+import java.io.File
+import java.nio.file.Files
+import org.apache.tools.zip.ZipEntry
+import org.apache.tools.zip.ZipFile
+import org.apache.tools.zip.ZipOutputStream
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Transforms an AAR by adding parcelable headers.
+ */
+abstract class StableAidlPackageApi : DefaultTask() {
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val aarFile: RegularFileProperty
+
+ @get:InputDirectory
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val packagedDir: DirectoryProperty
+
+ @get:OutputFile
+ abstract val updatedAarFile: RegularFileProperty
+
+ @TaskAction
+ fun taskAction() {
+ aidlPackageApiDelegate(
+ aarFile.get().asFile,
+ updatedAarFile.get().asFile,
+ packagedDir.get().asFileTree,
+ name
+ )
+ }
+}
+
+internal fun aidlPackageApiDelegate(
+ aar: File,
+ updatedAar: File,
+ packagedTree: FileTree,
+ name: String,
+) {
+ val tempDir = Files.createTempDirectory("${name}Unzip").toFile()
+ tempDir.deleteOnExit()
+
+ ZipFile(aar).use { aarFile ->
+ aarFile.unzipTo(tempDir)
+ }
+
+ val aidlRoot = File(tempDir, "aidl")
+ if (!aidlRoot.exists()) {
+ aidlRoot.mkdir()
+ }
+
+ // Copy the directory structure and files.
+ packagedTree.visit { details ->
+ val target = File(aidlRoot, details.relativePath.pathString)
+ if (details.isDirectory) {
+ target.mkdir()
+ } else {
+ details.copyTo(target)
+ }
+ }
+
+ tempDir.zipTo(updatedAar)
+ tempDir.deleteRecursively()
+}
+
+internal fun ZipFile.unzipTo(tempDir: File) {
+ entries.iterator().forEach { entry ->
+ if (entry.isDirectory) {
+ File(tempDir, entry.name).mkdirs()
+ } else {
+ val file = File(tempDir, entry.name)
+ file.parentFile.mkdirs()
+ getInputStream(entry).use { stream ->
+ file.writeBytes(stream.readBytes())
+ }
+ }
+ }
+}
+
+internal fun File.zipTo(outZip: File) {
+ ZipOutputStream(outZip.outputStream()).use { stream ->
+ listFiles()!!.forEach { file ->
+ stream.addFileRecursive(null, file)
+ }
+ }
+}
+
+internal fun ZipOutputStream.addFileRecursive(parentPath: String?, file: File) {
+ val entryPath = if (parentPath != null) "$parentPath/${file.name}" else file.name
+ val entry = ZipEntry(file, entryPath)
+
+ // Reset creation time of entry to make it deterministic.
+ entry.time = 0
+ entry.creationTime = java.nio.file.attribute.FileTime.fromMillis(0)
+
+ if (file.isFile) {
+ putNextEntry(entry)
+ file.inputStream().use { stream ->
+ stream.copyTo(this)
+ }
+ closeEntry()
+ } else if (file.isDirectory) {
+ val listFiles = file.listFiles()
+ if (!listFiles.isNullOrEmpty()) {
+ putNextEntry(entry)
+ closeEntry()
+ listFiles.forEach { containedFile ->
+ addFileRecursive(entryPath, containedFile)
+ }
+ }
+ }
+}
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCheckApiTest.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCheckApiTest.kt
index 74fd01a..db34a74 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCheckApiTest.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCheckApiTest.kt
@@ -21,7 +21,6 @@
import com.android.build.gradle.internal.fixtures.FakeGradleWorkExecutor
import com.android.build.gradle.internal.fixtures.FakeInjectableService
import com.google.common.truth.Truth
-import java.io.File
import kotlin.reflect.jvm.javaMethod
import org.gradle.api.DefaultTask
import org.gradle.testfixtures.ProjectBuilder
@@ -43,12 +42,6 @@
private lateinit var workers: WorkerExecutor
private lateinit var instantiatorTask: DefaultTask
- private fun createFile(name: String, parent: File): File {
- val newFile = parent.resolve(name)
- newFile.createNewFile()
- return newFile
- }
-
@Before
fun setup() {
with(ProjectBuilder.builder().withProjectDir(temporaryFolder.newFolder()).build()) {
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCompileTest.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCompileTest.kt
index 25c4133..6477c47 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCompileTest.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlCompileTest.kt
@@ -22,7 +22,6 @@
import com.android.build.gradle.internal.fixtures.FakeGradleWorkExecutor
import com.android.build.gradle.internal.fixtures.FakeInjectableService
import com.google.common.truth.Truth
-import java.io.File
import kotlin.reflect.jvm.javaMethod
import org.gradle.api.DefaultTask
import org.gradle.testfixtures.ProjectBuilder
@@ -44,12 +43,6 @@
private lateinit var workers: WorkerExecutor
private lateinit var instantiatorTask: DefaultTask
- private fun createFile(name: String, parent: File): File {
- val newFile = parent.resolve(name)
- newFile.createNewFile()
- return newFile
- }
-
@Before
fun setup() {
with(ProjectBuilder.builder().withProjectDir(temporaryFolder.newFolder()).build()) {
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlPackageApiTest.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlPackageApiTest.kt
new file mode 100644
index 0000000..418bb76
--- /dev/null
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/StableAidlPackageApiTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.stableaidl.tasks
+
+import java.util.zip.ZipFile
+import kotlin.test.assertContains
+import org.apache.tools.zip.ZipOutputStream
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class StableAidlPackageApiTest {
+ @get:Rule
+ val temporaryFolder = TemporaryFolder()
+
+ @Test
+ fun testPackageDirectoryIntoAar() {
+ val aarSourceDir = temporaryFolder.newFolder("aarSourceDir")
+ val aarContents = createFile("AndroidManifest.xml", aarSourceDir)
+
+ val packagedDir = temporaryFolder.newFolder("packagedDir")
+ createFile("android/os/MyParcelable.aidl", packagedDir)
+
+ val outputDir = temporaryFolder.newFolder("outputDir")
+ val aarFile = createFile("aarFile.aar", outputDir)
+ ZipOutputStream(aarFile.outputStream()).use { stream ->
+ stream.addFileRecursive(null, aarContents)
+ }
+ val updatedAarFile = createFile("updatedAarFile.aar", outputDir)
+
+ with(ProjectBuilder.builder().withProjectDir(temporaryFolder.newFolder()).build()) {
+ aidlPackageApiDelegate(
+ aarFile,
+ updatedAarFile,
+ project.fileTree(packagedDir),
+ "test"
+ )
+ }
+
+ ZipFile(updatedAarFile).use { zip ->
+ val zipEntryNames = zip.getEntryNames()
+ assertContains(zipEntryNames, "aidl/android/os/MyParcelable.aidl")
+ assertContains(zipEntryNames, "AndroidManifest.xml")
+ }
+ }
+}
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/TestUtils.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/TestUtils.kt
new file mode 100644
index 0000000..dd222cd
--- /dev/null
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/androidx/stableaidl/tasks/TestUtils.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.stableaidl.tasks
+
+import java.io.File
+import java.util.zip.ZipFile
+
+internal fun createFile(name: String, parent: File): File {
+ val newFile = parent.resolve(name)
+ newFile.parentFile.mkdirs()
+ newFile.createNewFile()
+ return newFile
+}
+
+internal fun ZipFile.getEntryNames(): List<String> {
+ val flattened = mutableListOf<String>()
+ entries().iterator().forEach { entry ->
+ flattened += entry.name
+ }
+ return flattened
+}