Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing your own Gradle Plugin: A Deep Dive (dr...

Writing your own Gradle Plugin: A Deep Dive (droidcon NYC 2024)

Want to improve some aspect of your Gradle builds? Don't know how?

In this session, we'll talk about:
* Configurations and variants and why understanding them is important
* How to architect your plugin around dependent plugins and other aspects of the build
* What a few well-known plugins do well and what we can learn from them

You'll come out of this session with a deeper understanding of how plugins work and be inspired to write your own!

Video: coming soon!

John Rodriguez

September 19, 2024
Tweet

More Decks by John Rodriguez

Other Decks in Technology

Transcript

  1. apply plugin: ‘java-gradle-plugin' apply plugin: 'org.jetbrains.kotlin.jvm' gradlePlugin { plugins {

    droidcon { id = 'nyc.jrod.droidcon.gradle' implementationClass = 'nyc.jrod.droidcon.gradle.ExamplePlugin' } } } dependencies {@ compileOnly gradleApi() compileOnly platform(libs.kotlin.bom) }@
  2. apply plugin: ‘java-gradle-plugin' apply plugin: 'org.jetbrains.kotlin.jvm' gradlePlugin { plugins {

    droidcon { id = 'nyc.jrod.droidcon.gradle' implementationClass = 'nyc.jrod.droidcon.gradle.ExamplePlugin' } } } dependencies {@ compileOnly gradleApi() compileOnly platform(libs.kotlin.bom) }@
  3. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'org.jetbrains.kotlin.plugin.compose' android

    { compileSdkVersion versions.compileSdk defaultConfig { minSdkVersion versions.minSdk } buildFeatures { compose true } compileOptions { sourceCompatibility versions.sourceCompatibility targetCompatibility versions.targetCompatibility } lintOptions { lintConfig file('lint.xml') textOutput 'stdout' textReport true // https: // github.com/square/okhttp/issues/896 ignore 'InvalidPackage' } } dependencies {a compileOnly deps.androidxAnnotations implementation deps.androidxCore implementation deps.androidxCursorAdapter implementation deps.androidxFragment implementation deps.androidxStartup implementation deps.drawablePainter implementation deps.composeUi implementation deps.runtime implementation deps.foundation implementation deps.material }a
  4. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'org.jetbrains.kotlin.plugin.compose' android

    { compileSdkVersion versions.compileSdk defaultConfig { minSdkVersion versions.minSdk } buildFeatures { compose true } compileOptions { sourceCompatibility versions.sourceCompatibility
  5. compose true } compileOptions { sourceCompatibility versions.sourceCompatibility targetCompatibility versions.targetCompatibility }

    lintOptions { lintConfig file('lint.xml') textOutput 'stdout' textReport true // https: // github.com/square/okhttp/issues/896 ignore 'InvalidPackage' } } dependencies {a compileOnly deps.androidxAnnotations
  6. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'org.jetbrains.kotlin.plugin.compose' android

    { compileSdkVersion versions.compileSdk defaultConfig { minSdkVersion versions.minSdk } buildFeatures { compose true } compileOptions { sourceCompatibility versions.sourceCompatibility
  7. apply plugin: 'droidcon.conventions.android' dependencies {a compileOnly deps.androidxAnnotations implementation deps.androidxCore implementation

    deps.androidxCursorAdapter implementation deps.androidxFragment implementation deps.androidxStartup implementation deps.drawablePainter implementation deps.composeUi implementation deps.runtime implementation deps.foundation implementation deps.material }a
  8. private fun Project.configureAndroid() { plugins.withId("com.android.base") { valBandroidB=Bextensions.getByName("android")BasBBaseExtension android.apply { compileSdkVersion(34)

    compileOptions { it.sourceCompatibility = JavaVersion.VERSION_1_8 it.targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { it.minSdk = 21 it.targetSdk = 33 buildFeatures.compose = false } lintOptions { it.isCheckDependencies = true it.isCheckReleaseBuilds = false }
  9. } plugins.withId("com.android.application") { val android = extensions.getByName("android") as BaseExtension android.packagingOptions.apply

    { // Keep native symbols for diagnosing sample application crashes. doNotStrip("**/*.so") } val androidComponents = extensions.getByType(AndroidComponentsExtension :: class.java) androidComponents.beforeVariants { // Disable for sample apps because we never need it. if (it.buildType == "release") { it.enable = false } } }
  10. class DroidconBuildPlugin : Plugin<Project> { override fun apply(target: Project) {

    target.extensions.add( DroidconBuildExtension :: class.java, "droidconBuild", DroidconBuildExtensionImpl(target), ) target.configureAndroid() target.configureKotlin() }
  11. interface DroidconBuildExtension { fun enableCompose() } private class DroidconBuildExtensionImpl( private

    val project: Project ) : DroidconBuildExtension { override fun enableCompose() { plugins.withId("com.android.base") { val android = extensions.getByName("android") as BaseExtension android.apply { buildFeatures.compose = true } } } }
  12. apply plugin: 'droidcon.conventions.android' dependencies {a compileOnly deps.androidxAnnotations implementation deps.androidxCore implementation

    deps.androidxCursorAdapter implementation deps.androidxStartup implementation deps.drawablePainter implementation deps.composeUi implementation deps.runtime implementation deps.foundation implementation deps.material }a
  13. apply plugin: 'droidcon.conventions.android' droidconBuild { enableCompose() } dependencies {a compileOnly

    deps.androidxAnnotations implementation deps.androidxCore implementation deps.androidxCursorAdapter implementation deps.androidxStartup implementation deps.drawablePainter implementation deps.composeUi implementation deps.runtime implementation deps.foundation implementation deps.material }a
  14. class DroidconBuildPlugin : Plugin<Project> { override fun apply(target: Project) {

    target.configureAndroid() target.configureKotlin() } fun Project.configureAndroid() { ... } fun Project.configureKotlin() { ... } }
  15. class DroidconBuildPlugin : Plugin<Project> { override fun apply(target: Project) {

    target.configureAndroid() target.configureKotlin() target.configureSpotless() target.configureTesting() } fun Project.configureAndroid() { ... } fun Project.configureKotlin() { ... } fun Project.configureSpotless() { ... } fun Project.configureTesting() { ... } }
  16. object PluginId { object Convention { const val DROIDCON_BASE_PLUGIN =

    “ ... ” const val DROIDCON_ANDROID_APP_PLUGIN = “ ... ” const val DROIDCON_ANDROID_LIB_PLUGIN = “ ... ” const val DROIDCON_DOMAIN_PRESENTERS_PLUGIN = “ ... ” const val DROIDCON_DOMAIN_VIEWS_PLUGIN = “ ... ” const val DROIDCON_SAMPLE_APPLICATION_PLUGIN = “ ... ” } object External { const val KOTLIN_ANDROID_PLUGIN = "org.jetbrains.kotlin.android" const val AGP_ANDROID_APP_PLUGIN = "com.android.application" const val AGP_ANDROID_LIBRARY_PLUGIN = "com.android.library" const val PAPARAZZI_PLUGIN = "app.cash.paparazzi" const val JACOCO_PLUGIN = "jacoco" } }
  17. class AndroidApplicationPlugin : Plugin<Project> { companion object { const val

    ID = DROIDCON_ANDROID_APP_PLUGIN } override fun apply(target: Project): Unit = target.run { pluginManager.apply(Convention.DROIDCON_ANDROID_BASE_PLUGIN) pluginManager.apply(External.AGP_ANDROID_APPLICATION_PLUGIN) pluginManager.apply(External.KOTLIN_ANDROID_PLUGIN) // configure stuff here } }
  18. class AndroidPresentersPlugin : Plugin<Project> { companion object { const val

    ID = DROIDCON_PRESENTERS_PLUGIN } override fun apply(target: Project): Unit = target.run { pluginManager.apply(Convention.DROIDCON_ANDROID_LIB_PLUGIN) pluginManager.apply(External.JACOCO_PLUGIN) // configure stuff here } }
  19. class AndroidViewsPlugin : Plugin<Project> { companion object { const val

    ID = DROIDCON_VIEWS_PLUGIN } override fun apply(target: Project): Unit = target.run { pluginManager.apply(Convention.DROIDCON_ANDROID_LIB_PLUGIN) pluginManager.apply(External.PAPARAZZI_PLUGIN) // configure stuff here } }
  20. include("app") include("list") include("utilities") defaults { androidApplication { jdkVersion = 11

    compileSdk = 34 minSdk = 30 applicationId = "org.gradle.experimental.android.app" } androidLibrary { jdkVersion = 11 compileSdk = 34 minSdk = 30 } } // settings.gradle.decl
  21. abstract class Producer : DefaultTask() { @get:OutputFile abstract val outputFile:

    RegularFileProperty @TaskAction fun produce() { outputFile.get().asFile.writeText(" ... ") } } abstract class Consumer : DefaultTask() { @get:InputFile abstract val inputFile: RegularFileProperty @TaskAction fun consume() { val message = inputFile.get().asFile.readText() } }
  22. val producer = project.tasks.register<Producer>("producer") val consumer = project.tasks.register<Consumer>("consumer") consumer.configure {

    inputFile.set(producer.flatMap { it.outputFile }) } producer.configure { outputFile.set(project.layout.buildDirectory.file(“file.txt”)) dependsOn(consumer) }
  23. val producer = project.tasks.register<Producer>("producer") val consumer = project.tasks.register<Consumer>("consumer") consumer.configure {

    inputFile.set(producer.flatMap { it.outputFile }) } producer.configure { outputFile.set(project.layout.buildDirectory.file(“file.txt”)) dependsOn(consumer) }
  24. val producer = project.tasks.register<Producer>("producer") val consumer = project.tasks.register<Consumer>("consumer") consumer.configure {

    inputFile.set(producer.flatMap { it.outputFile }) } producer.configure { outputFile.set(project.layout.buildDirectory.file(“file.txt”)) }
  25. val computeVariants = project.tasks.register("computeAll") extension.onVariants { variant -> val variantSlug

    = variant.name.capitalize(Locale.US) val computeVariant = project.tasks.register("compute$variantSlug") computeVariants.configure { it.dependsOn(computeVariant) } }
  26. val generateSources = project.tasks.register("generateSources") project.plugins.withType(JavaBasePlugin :: class.java) { project.tasks.named {

    it == "compile${variantSlug}JavaWithJavac" } .configureEach { it.dependsOn(generateSources) } } project.plugins.withType(KotlinMultiplatformPluginWrapper :: class.java) { project.tasks.named { it == "compile${variantSlug}KotlinAndroid" } .configureEach { it.dependsOn(generateSources) } } project.plugins.withType(KotlinAndroidPluginWrapper :: class.java) { project.tasks.named { it == "compile${variantSlug}Kotlin" } .configureEach { it.dependsOn(generateSources) } }
  27. val generateSources = project.tasks.register("generateSources") project.plugins.withType(JavaBasePlugin :: class.java) { project.tasks.named {

    it == "compile${variantSlug}JavaWithJavac" } .configureEach { it.dependsOn(generateSources) } } project.plugins.withType(KotlinMultiplatformPluginWrapper :: class.java) { project.tasks.named { it == "compile${variantSlug}KotlinAndroid" } .configureEach { it.dependsOn(generateSources) } } project.plugins.withType(KotlinAndroidPluginWrapper :: class.java) { project.tasks.named { it == "compile${variantSlug}Kotlin" } .configureEach { it.dependsOn(generateSources) } }
  28. configurations { sample { canBeResolved = false canBeConsumed = true

    } } Exposing artifacts & dependencies (producers)
  29. configurations { sample { canBeResolved = false canBeConsumed = true

    } } Gradle 8.4+ configurations { sample { canBeResolved = false canBeConsumed = false } } configurations { sample { canBeResolved = true canBeConsumed = false } }
  30. 
 ------------------------------------------------------------
 Project ':sample'
 ------------------------------------------------------------
 
 implementation - Implementation only

    dependencies for 'main'. (n)
 +--- org.jetbrains.kotlin:kotlin-bom:2.0.20 (n)
 +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 (n)
 +--- com.android.tools.build:gradle:8.4.2 (n)
 +--- com.android.tools:sdk-common:31.4.2 (n)
 +--- com.squareup.moshi:moshi:1.15.1 (n)
 +--- com.squareup.moshi:moshi-adapters:1.15.1 (n)
 \--- com.squareup.moshi:moshi-kotlin:1.15.1 (n)
 
 (n) - A dependency or dependency configuration that cannot be resolved. $ gw -q sample:dependencies --configuration implementation
  31. $ gw -q sample:dependencies --configuration implementation 
 ------------------------------------------------------------
 Project ':sample'


    ------------------------------------------------------------
 
 implementation - Implementation only dependencies for 'main'. (n)
 +--- org.jetbrains.kotlin:kotlin-bom:2.0.20 (n)
 +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 (n)
 +--- com.android.tools.build:gradle:8.4.2 (n)
 +--- com.android.tools:sdk-common:31.4.2 (n)
 +--- com.squareup.moshi:moshi:1.15.1 (n)
 +--- com.squareup.moshi:moshi-adapters:1.15.1 (n)
 \--- com.squareup.moshi:moshi-kotlin:1.15.1 (n)
 
 (n) - A dependency or dependency configuration that cannot be resolved.
  32. ------------------------------------------------------------
 Project ':sample'
 ------------------------------------------------------------ compileClasspath - Compile classpath for 'main'.

    +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 | +--- org.jetbrains:annotations:13.0 | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0 -> 2.0.20 (c) | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0 -> 2.0.20 (c) +--- org.jetbrains.kotlin:kotlin-bom:2.0.20 | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (c) | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20 (c) | +--- org.jetbrains.kotlin:kotlin-reflect:2.0.20 (c) | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.20 (c) +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 | +--- org.jetbrains.kotlin:kotlin-gradle-plugins-bom:2.0.20 | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-model:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-annotations:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-native-utils:2.0.20 (c) $ gw -q sample:dependencies --configuration compileClasspath
  33. -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature.

    Capabilities - droidcon-gradle-plugin-howto:jvm:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime - org.jetbrains.kotlin.platform.type = jvm Artifacts - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes $ gw -q producer:outgoingVariants
  34. -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature.

    Capabilities - droidcon-gradle-plugin-howto:jvm:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime - org.jetbrains.kotlin.platform.type = jvm Artifacts - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes $ gw -q producer:outgoingVariants
  35. -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature.

    Capabilities - droidcon-gradle-plugin-howto:jvm:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime - org.jetbrains.kotlin.platform.type = jvm Artifacts - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes $ gw -q producer:outgoingVariants
  36. -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature.

    Capabilities - droidcon-gradle-plugin-howto:jvm:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime - org.jetbrains.kotlin.platform.type = jvm Artifacts - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes $ gw -q producer:outgoingVariants
  37. -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature.

    Capabilities - droidcon-gradle-plugin-howto:jvm:unspecified (default capability) AttributesA - org.gradle.categoryA = libraryA - org.gradle.dependency.bundling = externalA - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11A - org.gradle.libraryelementsA = jarA - org.gradle.usageA = java-runtimeA - org.jetbrains.kotlin.platform.typeA= jvmA ArtifactsA - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes $ gw -q producer:outgoingVariants
  38. - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary

    Variant classes -------------------------------------------------- Directories containing compiled class files for main. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-runtime - org.jetbrains.kotlin.platform.type = jvm Artifacts - build/classes/java/main (artifactType = java-classes-directory) - build/classes/kotlin/main (artifactType = java-classes-directory) -------------------------------------------------- Secondary Variant resources -------------------------------------------------- Directories containing assembled resource files for main. Attributes $ gw -q producer:outgoingVariants
  39. - build/libs/jvm.jar (artifactType = jar) Secondary Variants (*) Secondary Variant

    classes Attributes - org.gradle.libraryelements = classes Artifacts - build/classes/java/main (artifactType = java-classes-directory) - build/classes/kotlin/main (artifactType = java-classes-directory) Secondary Variant resources Attributes - org.gradle.libraryelements = resources Artifacts - build/resources/main (artifactType = java-resources-directory) $ gw -q producer:outgoingVariants
  40. -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Compile classpath for 'main'. Attributes -

    org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api - org.jetbrains.kotlin.platform.type = jvm Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Runtime classpath of 'main'. $ gw -q consumer:resolvableConfigurations
  41. -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Compile classpath for 'main'. Attributes -

    org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api - org.jetbrains.kotlin.platform.type = jvm Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Runtime classpath of 'main'. $ gw -q consumer:resolvableConfigurations
  42. -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Compile classpath for 'main'. Attributes -

    org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api - org.jetbrains.kotlin.platform.type = jvm Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Runtime classpath of 'main'. $ gw -q consumer:resolvableConfigurations
  43. -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Compile classpath for 'main'. Attributes -

    org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api - org.jetbrains.kotlin.platform.type = jvm Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Runtime classpath of 'main'. $ gw -q consume r​ :resolvableConfigurations
  44. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer

    producer Variant: runtimeElements[classes] Configuration: debugCompileClasspath $ gw consumer:assembleDebug
  45. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer

    producer 8.4.2 debug library android java-api androidJvm library external standard-jvm 11 classes java-runtime jvm com.android.build.api.attributes.AgpVersionAttr com.android.build.api.attributes.BuildTypeAttr org.gradle.category org.gradle.dependency.bundling org.gradle.jvm.environment org.gradle.jvm.version org.gradle.libraryelements org.gradle.usage org.jetbrains.kotlin.platform.type $ gw -q producer:outgoingVariants $ gw -q consumer:resolvableConfigurations Variant: runtimeElements[classes] Configuration: debugCompileClasspath
  46. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer

    producer $ gw consumer:assemble Variant: debugRuntimeElements [android-classes-directory] Configuration: compileClasspath
  47. apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer

    producer $ gw consumer:assemble Variant: debugRuntimeElements [android-classes-directory] Configuration: compileClasspath library external standard-jvm 11 classes java-api jvm 8.4.2 debug library android classes java-runtime androidJvm com.android.build.api.attributes.AgpVersionAttr com.android.build.api.attributes.BuildTypeAttr org.gradle.category org.gradle.dependency.bundling org.gradle.jvm.environment org.gradle.jvm.version org.gradle.libraryelements org.gradle.usage org.jetbrains.kotlin.platform.type
  48. 8.4.2 debug library android classes java-runtime androidJvm 8.4.2 debug library

    android java-api androidJvm library external standard-jvm 11 classes java-runtime jvm library external standard-jvm 11 classes java-api jvm android => jvm jvm => android
  49. androidJvm androidJvm jvm jvm android-lib => jvm jvm => android-lib

    - Variant 'debugRuntimeElements' declares a library for use during runtime, preferably optimized for Android: - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm'
  50. fun configureKotlinPlatformAttribute(configs: List<Configuration>, project: Project) { val ktPTAttr = Attribute.of("org.jetbrains.kotlin.platform.type",

    String :: class.java) configs.forEach { it.attributes.attribute(ktPTAttr, ANDROID_JVM_TYPE) } project.dependencies.attributesSchema.attribute(ktPTAttr).also { it.compatibilityRules.add(KotlinPlatformCompatibilityRule :: class.java) } } class KotlinPlatformCompatibilityRule : AttributeCompatibilityRule<String> { fun execute(details: CompatibilityCheckDetails<String>) = with(details) { if (producerValue == JVM_TYPE && consumerValue == ANDROID_JVM_TYPE) compatible() } } // build-system/gradle-core/src/main/java/com/android/build/gradle/internal/ dependency/KotlinPlatformAttribute.kt
  51. if (producerValue == JVM_TYPE && consumerValue == ANDROID_JVM_TYPE) compatible() //

    build-system/gradle-core/src/main/java/com/android/build/gradle/internal/ dependency/KotlinPlatformAttribute.kt
  52. jar +--- META-INF | +--- services | \--- ... +---

    droidcon | +--- gradle | \--- Utils.class | \--- ... ... aar +--- META-INF | +--- services | \--- ... +--- AndroidManifest.xml +--- assets | \--- ... +--- baseline-profile.txt +--- classes.jar +--- jni | \--- ... +--- lint.jar +--- proguard.txt +--- public.txt +--- R.txt \--- res +--- values \--- values.xml
  53. jar +--- META-INF | +--- services | \--- ... +---

    droidcon | +--- gradle | \--- Utils.class | \--- ... ... aar +--- META-INF | +--- services | \--- ... +--- AndroidManifest.xml +--- assets | \--- ... +--- baseline-profile.txt +--- classes.jar +--- jni | \--- ... +--- lint.jar +--- proguard.txt +--- public.txt +--- R.txt \--- res +--- values \--- values.xml
  54. val aarAsJar = project.configurations .resolvable("implementationAarAsJar") { it.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "release") it.attributes.attribute( Usage.USAGE_ATTRIBUTE,

    project.objects.named(Usage :: class.java, Usage.JAVA_API) ) } .incoming .artifactView { it.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "aarAsJar") } .files
  55. public fun snapshot( name: String? = null, composable: @Composable ()

    -> Unit ) { createFrameHandler(name).use { handler -> frameHandler = handler sdk.snapshot(composable) } } @Test fun compose() { paparazzi.snapshot { HelloPaparazzi() } }
  56. "Project Printer" Print all subproject paths in a multi-module project

    Design Take 1: Each project produces its path; root project consumes and aggregates all paths
  57. internal abstract class ProduceProjectTask : DefaultTask() { @get:Input abstract val

    projectPath: Property<String> @get:OutputFile abstract val output: RegularFileProperty @TaskAction fun writeProjectPath() { val out = output.get().asFile out.delete() val text = projectPath.get() out.writeText(text) } }
  58. internal abstract class PrintProjectsTask : DefaultTask() { @get:PathSensitive(PathSensitivity.RELATIVE) @get:InputFiles internal

    abstract val resolvedProjects: ConfigurableFileCollection @TaskAction fun printProjects() { println("Resolved project paths:") resolvedProjects.files.forEach { println(" ${it.readText()}") } } }
  59. class ProjectAggregationPlugin : Plugin<Project> { override fun apply(project: Project): Unit

    = project.run { if (this == rootProject) { configureRootProject() } configureProject() } ... }
  60. private fun Project.configureProject() { val produceProjectTask = tasks.register("produceProject", ProduceProjectTask) {

    task -> task.projectPath.set(path) task.output.set(buildDir.file("droidcon/project.txt")) } ... }
  61. private fun Project.configureProject() { val produceProjectTask = ... configurations.consumable("${declaredConfiguration}Elements") {

    c -> c.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } ... } } private val declaredConfiguration = "resolvedProjects"
  62. private fun Project.configureProject() { val produceProjectTask = ... configurations.consumable("${declaredConfiguration}Elements") {

    c -> c.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } c.outgoing.artifact(produceProjectTask.flatMap { it.output }) } } private val declaredConfiguration = "resolvedProjects"
  63. private fun Project.configureProject() { val produceProjectTask = ... ... attribute(

    PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) ... } internal interface Projects : Named { companion object { val PROJECT_ARTIFACTS_ATTRIBUTE: Attribute<Projects> = Attribute.of("projects", Projects :: class.java) } }
  64. private fun Project.configureRootProject() { val declarable = ... val internal

    = configurations.resolvable("${declaredConfiguration}ClassPath") { r -> r.extendsFrom(declarable) r.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } } ... }
  65. private fun Project.configureRootProject() { val declarable = ... val internal

    = ... tasks.register("printProjects", PrintProjectsTask) { task -> task.resolvedProjects.setFrom(internal) } ... }
  66. private fun Project.configureRootProject() { val declarable = ... val internal

    = ... tasks.register("printProjects", PrintProjectsTask) { task -> task.resolvedProjects.setFrom(internal) } allprojects.forEach { p -> dependencies.add(declaredConfiguration, project(p.path)) } }
  67. I am root: :jvm-sample me: :jvm-sample, subprojects: [:jvm-sample:app, :jvm-sample:lib1, :jvm-sample:lib2,

    :jvm- sample:lib3, :jvm-sample:lib4] I am project: :jvm-sample > Configure project :jvm-sample:app I am project: :jvm-sample:app > Configure project :jvm-sample:lib1 I am project: :jvm-sample:lib1 > Configure project :jvm-sample:lib2 I am project: :jvm-sample:lib2 > Configure project :jvm-sample:lib3 I am project: :jvm-sample:lib3 > Configure project :jvm-sample:lib4 I am project: :jvm-sample:lib4 > Task :jvm-sample:lib4:produceProjectName Writing project path: :jvm-sample:lib4 $ gw jvm-sample:printProjects
  68. > Task :jvm-sample:app:produceProjectName Writing project path: :jvm-sample:app > Task :jvm-sample:lib3:produceProjectName

    Writing project path: :jvm-sample:lib3 > Task :jvm-sample:produceProjectName Writing project path: :jvm-sample > Task :jvm-sample:printProjects Resolved project paths: /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/build/droidcon/project.txt :jvm-sample /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/app/build/droidcon/project.txt :jvm-sample:app /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib1/build/droidcon/project.txt :jvm-sample:lib1 /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib2/build/droidcon/project.txt :jvm-sample:lib2 /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib3/build/droidcon/project.txt :jvm-sample:lib3 /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib4/build/droidcon/project.txt :jvm-sample:lib4 $ gw jvm-sample:printProjects
  69. * What went wrong: Cannot locate tasks that match 'jvm-sample:lib2:printProjects'

    as task 'printProjects' not found in project ':jvm-sample:lib2'. $ gw jvm-sample:lib2:printProjects
  70. "Project Printer" Print all subproject paths in a multi-module project

    Design Take 1: Each project produces its path; root project consumes and aggregates all paths
  71. "Project Printer" Print all subproject paths in a multi-module project

    Design Take 1: Each project produces its path; root project consumes and aggregates all paths Design Take 2: Each project produces its path; relative root project consumes and aggregates all paths
  72. class ProjectAggregationPlugin : Plugin<Project> { override fun apply(project: Project): Unit

    = project.run { if (this == rootProject) { configureRootProject() } configureProject() } ... }
  73. class ProjectAggregationPlugin : Plugin<Project> { override fun apply(project: Project): Unit

    = project.run { configureResolvableProject() configureConsumableProject() } ... }
  74. private fun Project.configureConsumableProject() { val produceProjectTask = ... configurations.consumable("${declaredConfiguration}Elements") {

    c -> c.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } ... } } private val declaredConfiguration = "resolvedProjects"
  75. private fun Project.configureConsumableProject() { val produceProjectTask = ... configurations.consumable("${declaredConfiguration}Elements") {

    c -> c.extendsFrom(configurations.getByName("implementation")) c.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } ... } } private val declaredConfiguration = "resolvedProjects"
  76. private fun Project.configureResolvableProject() { val declarable = ... val internal

    = configurations.resolvable("${declaredConfiguration}ClassPath") { r -> r.extendsFrom(declarable) r.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } } ... }
  77. private fun Project.configureResolvableProject() { val internal = configurations.resolvable("${declaredConfiguration}ClassPath") { r

    -> r.extendsFrom(configurations.getByName("implementation")) r.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } } ... }
  78. /Users/jrod/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/ 2.0.20/7388d355f7cceb002cd387ccb7ab3850e4e0a07f/kotlin-stdlib-2.0.20.jar A META-INF/A0ŊMETA-INF/MANIFEST.MFm= 1F@ LXWH.idI&7"{>0= A\M1!K\K ?+IDgY"1TY"`ގv`PX(!rZ337C+ߢ) $META-INF/kotlin-stdlib.kotlin_modulerȑH -4|FY^zw%)ʢM*rQA䈄

    Y+|F> odTR9 Tg{bsXLt(tt͐ ;i6/$ دK֗ GQlQ6ي=m4n8x@eW7MkG;7@-3<Ml=k˶- 1iB^m;W>!Nr!0zC4˖^,ͰQUj1< Us4V-1`<nk=rNPC].Z[0sb Lx Dٝ mM˗H1x9H$J&aJu*H4o ۮNmcKSr1 ۹ 㲮63ʅ9b.aS ,qK=Sq2+,EAI'8l)< oV'%2 3Ph%LQBmu֩ $0mFZ GTC>$5;ޞpu}uN_kp,~\E1=mPY xٍ ^4')zضYm]Í^kF|?]hmx2ޅu~y1 h)ErRɆ*h7%n؉\JiZ[+3('By|~}O ҉}H l xϕA&u:ث $B ϸ+9x0 n& *0{  4ZYF#SWM"i"Xjϱ*N<u0-G7^C riwRhϝhWhsJSW:U_yAژ&v7(?>"P ލ bѬs=2T]NUsaO>vԾPֿ Z7<e^l#[jԈeu6- CsX_h{1)ୄ6s^Sc*JpāO.)D)ҞVSfclUgHR4@1BkKZұ0~ bh$ȦÈnژĐN!ŰRáM Ke;Fe}k&g (+a:B|draAF7V EHʪ6?V ѩPƛ)qA#9Rux~u $ gw jvm-sample:lib1:printProjects
  79. Resolved project paths: /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib2/build/libs/lib2.jar K-*ϳR03L3Y AMETA-INF/L3Y)META-INF/MANIFEST.MFPKyv /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib4/build/libs/lib4.jar K-*ϳR03L3Y AMETA-INF/L3Y)META-INF/MANIFEST.MFPKyv /Users/jrod/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/

    2.0.20/7388d355f7cceb002cd387ccb7ab3850e4e0a07f/kotlin-stdlib-2.0.20.jar A META-INF/A0ŊMETA-INF/MANIFEST.MFm= 1F@ LXWH.idI&7"{>0= A\M1!K\K ?+IDgY"1TY"`ގv`PX(!rZ337C+ߢ) $META-INF/kotlin-stdlib.kotlin_modulerȑH -4|FY^zw%)ʢM*rQA䈄 Y+|F> odTR9 Tg{bsXLt(tt͐ ;i6/$ دK֗ GQlQ6ي=m4n8x@eW7MkG;7@-3<Ml=k˶- 1iB^m;W>!Nr!0zC4˖^,ͰQUj1< Us4V-1`<nk=rNPC].Z[0sb Lx Dٝ mM˗H1x9H$J&aJu*H4o ۮNmcKSr1 ۹ 㲮63ʅ9b.aS ,qK=Sq2+,EAI'8l)< oV'%2 3Ph%LQBmu֩ $0mFZ GTC>$5;ޞpu}uN_kp,~\E1=mPY xٍ ^4')zضYm]Í^kF|?]hmx2ޅu~y1 h)ErRɆ*h7%n؉\JiZ[+3('By|~}O ҉}H l xϕA&u:ث $B ϸ+9x0 n& $ gw jvm-sample:lib1:printProjects
  80. private fun Project.configureResolvableProject() { val internal = configurations.resolvable("${declaredConfiguration}ClassPath") { r

    -> r.extendsFrom(configurations.getByName("implementation")) r.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } } ... }
  81. ------------------------------------------------------------
 Project ':sample'
 ------------------------------------------------------------ compileClasspath - Compile classpath for 'main'.

    +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 | +--- org.jetbrains:annotations:13.0 | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0 -> 2.0.20 (c) | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0 -> 2.0.20 (c) +--- org.jetbrains.kotlin:kotlin-bom:2.0.20 | +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.20 (c) | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20 (c) | +--- org.jetbrains.kotlin:kotlin-reflect:2.0.20 (c) | \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.20 (c) +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 | +--- org.jetbrains.kotlin:kotlin-gradle-plugins-bom:2.0.20 | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-model:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-gradle-plugin-annotations:2.0.20 (c) | | +--- org.jetbrains.kotlin:kotlin-native-utils:2.0.20 (c) $ gw -q sample:dependencies --configuration compileClasspath
  82. private fun Project.configureResolvableProject() { val internal = configurations.resolvable("${declaredConfiguration}ClassPath") { r

    -> r.extendsFrom(configurations.getByName("implementation")) r.attributes.run { attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } } ... }
  83. private fun Project.configureResolvableProject() { ... tasks.register( ... ) { task

    -> task.resolvedProjects.setFrom( internal.map { c -> c.incoming.artifactView { vc -> vc.attributes { it.attribute( PROJECT_ARTIFACTS_ATTRIBUTE, objects.named(Projects :: class.java, "resolved-projects") ) } vc.componentFilter { id -> id is ProjectComponentIdentifier } }.artifacts.artifactFiles } ) } }
  84. > Task :jvm-sample:app:produceProjectName Writing project path: :jvm-sample:app > Task :jvm-sample:lib3:produceProjectName

    > Task :jvm-sample:lib2:printProjects Resolved project paths: /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib4/build/droidcon/project.txt :jvm-sample:lib4 $ gw jvm-sample:lib2:printProjects
  85. > Task :jvm-sample:app:produceProjectName Writing project path: :jvm-sample:app > Task :jvm-sample:lib3:produceProjectName

    > Task :jvm-sample:lib2:printProjects Resolved project paths: /Users/jrod/Development/droidcon-gradle-plugin-howto/jvm-sample/lib4/build/droidcon/project.txt :jvm-sample:lib4 $ gw jvm-sample:lib2:printProjects A module does not appear in its own dependency graph!
  86. Recap Plugins are great for consolidating boilerplate build con fi

    guration! Plugins are also great for extracting domain logic out of your build scripts 👀 Android and Kotlin Extra credit: generalize functionality and open source!
  87. Recap Plugins are great for consolidating boilerplate build con fi

    guration! Plugins are also great for extracting domain logic out of your build scripts Isolated Projects and Declarative Gradle should make this even easier! 👀 Android and Kotlin Extra credit: generalize functionality and open source!
  88. Recap Plugins are great for consolidating boilerplate build con fi

    guration! Plugins are also great for extracting domain logic out of your build scripts Isolated Projects and Declarative Gradle should make this even easier! ...else you're in for lots of maintenance burden.... 👀 Android and Kotlin Extra credit: generalize functionality and open source! Wiring tasks in your own plugin OR well-documented anchor tasks ONLY! Plugin authors: make your plugins extensible!
  89. Recap Plugins are great for consolidating boilerplate build con fi

    guration! Plugins are also great for extracting domain logic out of your build scripts Isolated Projects and Declarative Gradle should make this even easier! ...else you're in for lots of maintenance burden.... 👀 Android and Kotlin Extra credit: generalize functionality and open source! Wiring tasks in your own plugin OR well-documented anchor tasks ONLY! Plugin authors: make your plugins extensible! Con fi gurations and variants are key to understanding Gradle's dark magic!