Skip to content

Instantly share code, notes, and snippets.

@autonomousapps
Last active April 22, 2022 23:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save autonomousapps/09b4737e5bb860d9388976354bf54673 to your computer and use it in GitHub Desktop.
Save autonomousapps/09b4737e5bb860d9388976354bf54673 to your computer and use it in GitHub Desktop.
Provider for single APK using latest AGP APIs
import org.gradle.api.file.Directory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.InputFiles
import java.io.File
import javax.inject.Inject
/**
* This essentially encapsulates the pattern described in
* [ExamplePlugin in AGP's "Gradle Recipes" repo](https://github.com/android/gradle-recipes/blob/agp-7.0/BuildSrc/getApksTest/buildSrc/src/main/kotlin/ExamplePlugin.kt#L45-L48).
*
* Instances should be created with the [ObjectFactory][org.gradle.api.model.ObjectFactory]. See
* `Artifacts.findApk()`. nb: We cannot encapsulate the construction of this type here because this module
* _shall not_ have any Android dependencies, by design.
*
* This should be injected into tasks like so:
* ```
* @get:Nested
* abstract val androidApks: Property<AndroidApks>
* ```
*/
abstract class AndroidApks @Inject constructor(
@get:InputFiles val apkDirectory: Provider<Directory>,
private val loader: Property<FileSystemLoader>
) {
/**
* Causes the [loader] to load the APKs from the [apkDirectory] provider, returning a provider to the
* list of APKs. In principle, there can be zero, one, or multiple APKs, but in practice there should
* always be exactly one. At the time of writing, the task that produces the primary production APK
* is named `package<<variant>>` (e.g., `packageDebug`).
*/
fun load(): Provider<List<File>> = loader.flatMap { it.load(apkDirectory) }
/**
* Resolves the provider chain immediately and returns the single APK it resolves to. Throws if the
* underlying collection is empty or has more than one element.
*/
fun singleFile(): File = load().get().single()
}
import com.android.build.api.artifact.Artifacts
import com.android.build.api.artifact.SingleArtifact.APK
class AndroidGradlePlugin7_0(
private val project: Project
) : AndroidGradlePlugin {
fun Artifacts.findApk(): AndroidApks = project.objects.newInstanceOf(
get(APK),
project.objects.property(FileSystemLoader::class.java).apply {
set(project.objects.newInstanceOf<FileSystemLoader7_0>(getBuiltArtifactsLoader()))
disallowChanges()
}
)
}
inline fun <reified T> ObjectFactory.newInstanceOf(vararg arguments: Any?): T {
return newInstance(T::class.java, *arguments)
}
import com.android.build.api.variant.BuiltArtifactsLoader
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import java.io.File
import javax.inject.Inject
interface FileSystemLoader {
fun load(directory: Provider<Directory>): Provider<List<File>>
}
abstract class FileSystemLoader7_0 @Inject constructor(
private val delegate: BuiltArtifactsLoader
) : FileSystemLoader {
override fun load(directory: Provider<Directory>): Provider<List<File>> {
return directory.map { dir ->
delegate.load(dir) ?: error("Expected built artifacts in ${dir.asFile.absolutePath}")
}.map { builtArtifacts ->
builtArtifacts.elements.map { builtArtifact ->
File(builtArtifact.outputFile)
}
}
}
}
abstract class MyTask : DefaultTask() {
@get:Nested
abstract val androidApks: Property<AndroidApks>
@TaskAction fun action() {
val apk = androidApks.get().singleFile()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment