Slide 1

Slide 1 text

Writing your own Gradle Plugin: A Deep Dive John Rodriguez

Slide 2

Slide 2 text

Setup

Slide 3

Slide 3 text

apply plugin: 'java-gradle-plugin' dependencies {@ compileOnly gradleApi() }@

Slide 4

Slide 4 text

apply plugin: 'java-gradle-plugin' apply plugin: 'org.jetbrains.kotlin.jvm' dependencies {@ compileOnly gradleApi() compileOnly platform(libs.kotlin.bom) }@

Slide 5

Slide 5 text

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) }@

Slide 6

Slide 6 text

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) }@

Slide 7

Slide 7 text

Your first plugin squareup/wire cashapp/redwood dropbox/focus vanniktech/gradle-maven-publish-plugin autonomousapps/dependency-analysis-gradle-plugin cashapp/paparazzi squareup/poopsi diffplug/spotless

Slide 8

Slide 8 text

Convention Plugins https://tinyurl.com/y3yeem2x

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

class DroidconBuildPlugin : PluginA{A override fun apply(target: Project)A{A target.configureAndroid() target.configureKotlin() A}A }A

Slide 15

Slide 15 text

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 }

Slide 16

Slide 16 text

} 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 } } }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

class DroidconBuildPlugin : Plugin { override fun apply(target: Project) { target.extensions.add( DroidconBuildExtension :: class.java, "droidconBuild", DroidconBuildExtensionImpl(target), ) target.configureAndroid() target.configureKotlin() }

Slide 19

Slide 19 text

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 } } } }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

class DroidconBuildPlugin : Plugin { 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() { ... } }

Slide 24

Slide 24 text

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" } }

Slide 25

Slide 25 text

class AndroidApplicationPlugin : Plugin { 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 } }

Slide 26

Slide 26 text

class AndroidPresentersPlugin : Plugin { 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 } }

Slide 27

Slide 27 text

class AndroidViewsPlugin : Plugin { 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 } }

Slide 28

Slide 28 text

apply plugin: 'droidcon.conventions.android' // some-feature/build.gradle

Slide 29

Slide 29 text

subprojects { apply plugin: 'droidcon.conventions.android' ... } // build.gradle https://docs.gradle.org/current/userguide/isolated_projects.html

Slide 30

Slide 30 text

Declarative Gradle DSL https://blog.gradle.org/declarative-gradle-first-eap https://github.com/gradle/declarative-samples-android-app

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

androidLibrary { namespace = "org.gradle.experimental.android.utilities" dependencies { api(project(":list")) } } // utilities/build.gradle.decl

Slide 33

Slide 33 text

Task Wiring

Slide 34

Slide 34 text

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() } }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

dependsOn()?

Slide 39

Slide 39 text

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) } }

Slide 40

Slide 40 text

project.tasks.named(CHECK_TASK_NAME).configure { it.dependsOn(apiCheck) } project.tasks.named(ASSEMBLE_TASK_NAME).configure { it.dependsOn(generateSources) } tasks.withType(Test).configureEach { dependsOn(generateTestConfig) } tasks.withType(Test).configureEach { dependsOn(publishMavenPublication) }

Slide 41

Slide 41 text

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) } }

Slide 42

Slide 42 text

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) } }

Slide 43

Slide 43 text

check apiCheck :secret:feature

Slide 44

Slide 44 text

check apiCheck check apiCheck feature1 feature2

Slide 45

Slide 45 text

// feature1/build.gradle project(":feature2").tasks.named(CHECK_TASK_NAME).configure { it.dependsOn(apiCheck) }

Slide 46

Slide 46 text

check apiCheck check apiCheck feature1 feature2

Slide 47

Slide 47 text

Configurations

Slide 48

Slide 48 text

Configurations are for :

Slide 49

Slide 49 text

Configurations are for : Declaring dependencies

Slide 50

Slide 50 text

Configurations are for : Declaring dependencies Resolving dependencies

Slide 51

Slide 51 text

Configurations are for : Declaring dependencies Resolving dependencies Exposing artifacts & dependencies

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Configurations are for : Declaring dependencies Resolving dependencies Exposing artifacts & dependencies

Slide 54

Slide 54 text

Configurations are for : Declaring dependencies Resolving dependencies Exposing artifacts & dependencies

Slide 55

Slide 55 text

Declaring dependencies dependencies { implementation(platform(libs.kotlin.bom)) implementation(platform(libs.okio.bom)) }

Slide 56

Slide 56 text

Declaring dependencies dependencies { implementation(platform(libs.kotlin.bom)) implementation(platform(libs.okio.bom)) api(platform(libs.kotlin.bom)) api(platform(libs.okio.bom)) compileOnly(platform(libs.kotlin.bom)) compileOnly(platform(libs.okio.bom)) compileOnlyApi(platform(libs.kotlin.bom)) compileOnlyApi(platform(libs.okio.bom)) runtimeOnly(platform(libs.kotlin.bom)) runtimeOnly(platform(libs.okio.bom)) }

Slide 57

Slide 57 text

Declaring dependencies configurations { sample }

Slide 58

Slide 58 text

Declaring dependencies configurations { sample { canBeResolved = false canBeConsumed = false } }

Slide 59

Slide 59 text

configurations { sample { canBeResolved = true canBeConsumed = false } } Resolving dependencies (consumers)

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

canBeResolved canBeConsumed Declaring dependencies FALSE FALSE Resolve TRUE FALSE Exposed to consumers FALSE TRUE

Slide 62

Slide 62 text

canBeResolved canBeConsumed Declaring dependencies FALSE FALSE Resolve TRUE FALSE Exposed to consumers FALSE TRUE default, don't do! TRUE TRUE

Slide 63

Slide 63 text

configurations { sample { canBeResolved = false canBeConsumed = true } } Gradle 8.4+ configurations { sample { canBeResolved = false canBeConsumed = false } } configurations { sample { canBeResolved = true canBeConsumed = false } }

Slide 64

Slide 64 text

Gradle 8.4+ configurations { consumable("sample") } configurations { dependencyScope("sample") } configurations { resolvable("sample") }

Slide 65

Slide 65 text

consumer producer // consume r​ /build.gradle dependencies { implementation producer }

Slide 66

Slide 66 text

consumer producer compileClasspath runtimeElements runtimeClasspath implementation

Slide 67

Slide 67 text

consumer producer compileClasspath runtimeElements runtimeClasspath

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text


 ------------------------------------------------------------
 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

Slide 71

Slide 71 text

$ 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.

Slide 72

Slide 72 text

------------------------------------------------------------
 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

Slide 73

Slide 73 text

Variants

Slide 74

Slide 74 text

• Declaring dependencies • Resolving dependencies (consumers) • Exposing artifacts & dependencies (producers)

Slide 75

Slide 75 text

• Declaring dependencies • Resolving dependencies (consumers) • Exposing artifacts & dependencies (producers)

Slide 76

Slide 76 text

-------------------------------------------------- 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

Slide 77

Slide 77 text

-------------------------------------------------- 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

Slide 78

Slide 78 text

-------------------------------------------------- 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

Slide 79

Slide 79 text

-------------------------------------------------- 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

Slide 80

Slide 80 text

-------------------------------------------------- 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

Slide 81

Slide 81 text

- 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

Slide 82

Slide 82 text

- 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

Slide 83

Slide 83 text

-------------------------------------------------- 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

Slide 84

Slide 84 text

-------------------------------------------------- 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

Slide 85

Slide 85 text

-------------------------------------------------- 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

Slide 86

Slide 86 text

-------------------------------------------------- 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

Slide 87

Slide 87 text

Variant Selection

Slide 88

Slide 88 text

consumer producer implementation

Slide 89

Slide 89 text

apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer producer

Slide 90

Slide 90 text

apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer producer $ gw consumer:assembleDebug

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer producer

Slide 94

Slide 94 text

apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer producer

Slide 95

Slide 95 text

apply plugin: 'com.android.library' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: ‘org.jetbrains.kotlin.jvm' consumer producer $ gw consumer:assemble

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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'

Slide 100

Slide 100 text

fun configureKotlinPlatformAttribute(configs: List, 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 { fun execute(details: CompatibilityCheckDetails) = 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

Slide 101

Slide 101 text

if (producerValue == JVM_TYPE && consumerValue == ANDROID_JVM_TYPE) compatible() // build-system/gradle-core/src/main/java/com/android/build/gradle/internal/ dependency/KotlinPlatformAttribute.kt

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

dependencies.registerTransform(IdentityTransform :: class.java) { it.from.attribute(AndroidArtifacts.ARTIFACT_TYPE, "jar") it.to.attribute(AndroidArtifacts.ARTIFACT_TYPE, "aarAsJar") } dependencies.registerTransform(ExtractClassesTransform :: class.java) { it.from.attribute(AndroidArtifacts.ARTIFACT_TYPE, "aar") it.to.attribute(AndroidArtifacts.ARTIFACT_TYPE, "aarAsJar") }

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

project.configurations .getByName("implementation") .dependencies .add(project.dependencies.create(aarAsJar)) // consumer/build.gradle dependencies { implementationAarAsJar libs.androidx.compose.ui.android implementationAarAsJar libs.androidx.activity }

Slide 107

Slide 107 text

public fun snapshot( name: String? = null, composable: @Composable () -> Unit ) { createFrameHandler(name).use { handler -> frameHandler = handler sdk.snapshot(composable) } } @Test fun compose() { paparazzi.snapshot { HelloPaparazzi() } }

Slide 108

Slide 108 text

Example Plugin

Slide 109

Slide 109 text

"Project Printer" Print all subproject paths in a multi-module project

Slide 110

Slide 110 text

"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

Slide 111

Slide 111 text

app lib1 lib3 lib2 lib4

Slide 112

Slide 112 text

internal abstract class ProduceProjectTask : DefaultTask() { @get:Input abstract val projectPath: Property @get:OutputFile abstract val output: RegularFileProperty @TaskAction fun writeProjectPath() { val out = output.get().asFile out.delete() val text = projectPath.get() out.writeText(text) } }

Slide 113

Slide 113 text

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()}") } } }

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

private fun Project.configureProject() { }

Slide 116

Slide 116 text

private fun Project.configureProject() { val produceProjectTask = tasks.register("produceProject", ProduceProjectTask) { task -> task.projectPath.set(path) task.output.set(buildDir.file("droidcon/project.txt")) } ... }

Slide 117

Slide 117 text

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"

Slide 118

Slide 118 text

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"

Slide 119

Slide 119 text

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 = Attribute.of("projects", Projects :: class.java) } }

Slide 120

Slide 120 text

private fun Project.configureRootProject() { }

Slide 121

Slide 121 text

private fun Project.configureRootProject() { val declarable = configurations.dependencyScope(declaredConfiguration).get() ... } private val declaredConfiguration = "resolvedProjects"

Slide 122

Slide 122 text

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") ) } } ... }

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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)) } }

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

> 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

Slide 127

Slide 127 text

No content

Slide 128

Slide 128 text

* 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

Slide 129

Slide 129 text

"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

Slide 130

Slide 130 text

"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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

class ProjectAggregationPlugin : Plugin { override fun apply(project: Project): Unit = project.run { configureResolvableProject() configureConsumableProject() } ... }

Slide 133

Slide 133 text

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"

Slide 134

Slide 134 text

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"

Slide 135

Slide 135 text

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") ) } } ... }

Slide 136

Slide 136 text

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") ) } } ... }

Slide 137

Slide 137 text

/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!Nr!0zC4˖^,ͰQUj1< Us4V-1`$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"P ލ bѬs=2T]NUsaO>vԾPֿ Z7

Slide 138

Slide 138 text

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!Nr!0zC4˖^,ͰQUj1< Us4V-1`$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

Slide 139

Slide 139 text

dependencies { implementation(project(":lib1")) implementation(project(":lib2")) } // app/build.gradle.kts

Slide 140

Slide 140 text

dependencies { implementation(project(":lib1")) implementation(project(":lib2")) resolvedProjects(project(":lib1")) resolvedProjects(project(":lib2")) } // app/build.gradle.kts

Slide 141

Slide 141 text

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") ) } } ... }

Slide 142

Slide 142 text

------------------------------------------------------------
 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

Slide 143

Slide 143 text

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") ) } } ... }

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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 } ) } }

Slide 146

Slide 146 text

> 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

Slide 147

Slide 147 text

> 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!

Slide 148

Slide 148 text

generateSources compile your app AGP variant.sources.assets? .addGeneratedSourceDirectory( assetCreationTask, AssetCreatorTask :: outputDirectory )

Slide 149

Slide 149 text

Recap Plugins are great for consolidating boilerplate build con fi guration! 👀 Android and Kotlin

Slide 150

Slide 150 text

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!

Slide 151

Slide 151 text

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!

Slide 152

Slide 152 text

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!

Slide 153

Slide 153 text

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!

Slide 154

Slide 154 text

References https://github.com/autonomousapps/dependency-analysis-gradle-plugin https://github.com/cashapp/redwood/tree/trunk/build-support https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build- system/gradle-core/ https://github.com/vanniktech/gradle-maven-publish-plugin https://github.com/android/gradle-recipes

Slide 155

Slide 155 text

Writing your own Gradle Plugin: A Deep Dive @jrodbx