build: update buildsrc
authorDa Risk <da_risk@geekorum.com>
Tue, 29 Nov 2022 14:05:44 -0400
changeset 19 91a3ad3b1b9c
parent 18 3ccb29f83309
child 20 5d8a0555733d
build: update buildsrc
buildSrc/build.gradle.kts
buildSrc/src/main/kotlin/AndroidJavaVersion.kt
buildSrc/src/main/kotlin/AndroidPlayStorePublisher.kt
buildSrc/src/main/kotlin/AndroidSigning.kt
buildSrc/src/main/kotlin/AndroidTests.kt
buildSrc/src/main/kotlin/Avdl.kt
buildSrc/src/main/kotlin/RepositoryChangeset.kt
buildSrc/src/main/kotlin/SourceLicenseChecker.kt
geekdroid-firebase/build.gradle.kts
geekdroid/build.gradle.kts
gradle/wrapper/gradle-wrapper.properties
--- a/buildSrc/build.gradle.kts	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/build.gradle.kts	Tue Nov 29 14:05:44 2022 -0400
@@ -28,27 +28,39 @@
 
 version = "1.0"
 
+kotlin{
+    sourceSets {
+        all {
+            languageSettings {
+                optIn("kotlin.RequiresOptIn")
+            }
+        }
+    }
+}
 repositories {
-    gradlePluginPortal()
+    google {
+        content {
+            includeGroupByRegex("""android\.arch\..*""")
+            includeGroupByRegex("""androidx\..*""")
+            includeGroupByRegex("""com\.android\..*""")
+            includeGroupByRegex("""com\.google\..*""")
+            includeGroup("com.crashlytics.sdk.android")
+            includeGroup("io.fabric.sdk.android")
+            includeGroup("org.chromium.net")
+            includeGroup("zipflinger")
+            includeGroup("com.android")
+    	}
+    }
     mavenCentral()
-    google()
-    maven {
-        // Workaround for genymotion plugin not working on gradle 5.0
-        // we publish 1.4.2 version with fixes
-        url = uri("https://raw.githubusercontent.com/fbarthelery/genymotion-gradle-plugin/master/repo/")
-    }
-    maven {
-        url = uri("https://kotlin.bintray.com/kotlinx")
-    }
+    gradlePluginPortal()
 }
 
 dependencies {
-    implementation("com.android.tools.build:gradle:7.0.2")
-    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71")
-    implementation("com.genymotion:plugin:1.4.2")
-    implementation("gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.15.0")
-    implementation("com.github.triplet.gradle:play-publisher:2.7.2")
+    implementation("com.android.tools.build:gradle:7.3.1")
+    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
+    implementation("gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1")
+    implementation("com.github.triplet.gradle:play-publisher:3.7.0")
 
-    implementation("com.geekorum.gradle.avdl:plugin:0.0.2")
-    implementation("com.geekorum.gradle.avdl:flydroid:0.0.2")
+    implementation("com.geekorum.gradle.avdl:plugin:0.0.3")
+    implementation("com.geekorum.gradle.avdl:flydroid:0.0.3")
 }
--- a/buildSrc/src/main/kotlin/AndroidJavaVersion.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/AndroidJavaVersion.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -21,6 +21,8 @@
  */
 package com.geekorum.build
 
+import com.android.build.api.dsl.CommonExtension
+import com.android.build.api.dsl.DefaultConfig
 import com.android.build.gradle.BaseExtension
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
@@ -31,9 +33,15 @@
 /**
  * Configure java version compile options based on minSdkVersion value
  */
+@Suppress("UNCHECKED_CAST")
 fun BaseExtension.configureJavaVersion() {
-    val api = defaultConfig.minSdkVersion?.apiLevel ?: 0
+    (this as CommonExtension<*, *, DefaultConfig, *>).configureJavaVersion()
+}
+
+fun CommonExtension<*, *, DefaultConfig, *>.configureJavaVersion() {
+    val api = defaultConfig.minSdk ?: 1
     val version = when {
+        api >= 30 -> JavaVersion.VERSION_11
         api >= 24 -> JavaVersion.VERSION_1_8
         api >= 19 -> JavaVersion.VERSION_1_7
         else -> JavaVersion.VERSION_1_6
@@ -51,7 +59,7 @@
 }
 
 /**
- * Add missing annotation processord dependencies to build on Java 11
+ * Add missing annotation processor dependencies to build on Java 11
  */
 fun Project.configureAnnotationProcessorDeps() {
     dependencies {
@@ -62,11 +70,8 @@
                     add(name, "com.sun.xml.bind:jaxb-core:2.3.0.1")
                     add(name, "com.sun.xml.bind:jaxb-impl:2.3.2")
                 }
+
                 "annotationProcessor" -> add(name, "javax.xml.bind:jaxb-api:2.3.1")
-                // I guess that on AGP 4.x+ testAnnotationProcessor inherit from annotationProcessor
-                // not on 3.6.x
-                "testAnnotationProcessor" -> add(name, "javax.xml.bind:jaxb-api:2.3.1")
-                "androidTestAnnotationProcessor" -> add(name, "javax.xml.bind:jaxb-api:2.3.1")
             }
         }
     }
--- a/buildSrc/src/main/kotlin/AndroidPlayStorePublisher.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/AndroidPlayStorePublisher.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -41,10 +41,10 @@
 internal fun Project.configureAndroidPlayStorePublisher(): Unit {
     apply(plugin = "com.github.triplet.play")
     configure<PlayPublisherExtension> {
-        defaultToAppBundles = true
-        serviceAccountCredentials = file(properties["PLAY_STORE_JSON_KEY_FILE"]!!)
-        track = properties.getOrDefault("PLAY_STORE_TRACK", "internal") as String
-        fromTrack = properties.getOrDefault("PLAY_STORE_FROM_TRACK", "internal") as String
+        defaultToAppBundles.set(true)
+        track.set(properties.getOrDefault("PLAY_STORE_TRACK", "internal") as String)
+        fromTrack.set(properties.getOrDefault("PLAY_STORE_FROM_TRACK", "internal") as String)
+        serviceAccountCredentials.set(file(properties["PLAY_STORE_JSON_KEY_FILE"]!!))
     }
 
     val android = the<AppExtension>() as ExtensionAware
@@ -53,7 +53,7 @@
         register("publishToGooglePlayStore") {
             group = "Continuous Delivery"
             description = "Publish project to Google play store"
-            dependsOn(named("publish"))
+            dependsOn(named("publishApps"))
         }
 
         // only there for consistent naming scheme
--- a/buildSrc/src/main/kotlin/AndroidSigning.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/AndroidSigning.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -21,16 +21,22 @@
  */
 package com.geekorum.build
 
-import com.android.build.gradle.BaseExtension
+import com.android.build.api.dsl.*
 import org.gradle.api.Project
 
+private typealias AppExtensionWithSigning = CommonExtension<*, ApplicationBuildType, *, *>
+private typealias LibExtensionWithSigning = CommonExtension<*, LibraryBuildType, *, *>
+private typealias TestExtensionWithSigning = CommonExtension<*, TestBuildType, *, *>
+
+// TODO  This implicitly supports only the AppPlugin
+//  should we support other android plugins: LibraryPlugin TestPlugin ?
 internal fun Project.configureReleaseSigningConfig() {
     val releaseStoreFile = findProperty("RELEASE_STORE_FILE") as? String ?: ""
     val releaseStorePassword = findProperty("RELEASE_STORE_PASSWORD") as? String ?: ""
     val releaseKeyAlias= findProperty("RELEASE_KEY_ALIAS") as? String ?: ""
     val releaseKeyPassword= findProperty("RELEASE_KEY_PASSWORD") as? String ?: ""
 
-    extensions.configure<BaseExtension>("android") {
+    extensions.configure<AppExtensionWithSigning>("android") {
         signingConfigs {
             register("release") {
                 storeFile =  file(releaseStoreFile)
--- a/buildSrc/src/main/kotlin/AndroidTests.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/AndroidTests.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -21,6 +21,8 @@
  */
 package com.geekorum.build
 
+import com.android.build.api.dsl.CommonExtension
+import com.android.build.api.dsl.DefaultConfig
 import com.android.build.gradle.BaseExtension
 import com.android.build.gradle.internal.dsl.TestOptions
 import org.gradle.api.Project
@@ -34,17 +36,19 @@
 import org.gradle.kotlin.dsl.dependencies
 import org.gradle.kotlin.dsl.kotlin
 
-const val espressoVersion = "3.2.0"
-const val androidxTestRunnerVersion = "1.3.0-alpha05"
-const val androidxTestCoreVersion = "1.3.0-alpha05"
-const val robolectricVersion = "4.6.1"
+const val espressoVersion = "3.5.0-alpha07" // alpha for this bug https://github.com/robolectric/robolectric/issues/6593
+const val androidxTestRunnerVersion = "1.4.0"
+const val androidxTestCoreVersion = "1.4.0"
+const val robolectricVersion = "4.8.2"
 
+private typealias BaseExtension = CommonExtension<*, *, DefaultConfig, *>
 
 /*
  * Configuration for espresso and robolectric usage in an Android project
  */
+@Suppress("UnstableApiUsage")
 internal fun Project.configureTests() {
-    extensions.configure<BaseExtension> {
+    extensions.configure<BaseExtension>("android") {
         defaultConfig {
             testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
             testInstrumentationRunnerArguments += mapOf(
@@ -57,9 +61,9 @@
             execution = "ANDROIDX_TEST_ORCHESTRATOR"
             animationsDisabled = true
 
-            unitTests(closureOf<TestOptions.UnitTestOptions> {
+            unitTests {
                 isIncludeAndroidResources = true
-            })
+            }
         }
     }
 
@@ -88,8 +92,8 @@
         dualTestImplementation("androidx.test.ext:truth:1.3.0-alpha01")
 
         // mock
-        testImplementation("io.mockk:mockk:1.12.0")
-        androidTestImplementation("io.mockk:mockk-android:1.12.0")
+        testImplementation("io.mockk:mockk:1.13.2")
+        androidTestImplementation("io.mockk:mockk-android:1.13.2")
         testImplementation("org.robolectric:robolectric:$robolectricVersion")
 
         constraints {
--- a/buildSrc/src/main/kotlin/Avdl.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/Avdl.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -46,6 +46,34 @@
 fun Project.configureAvdlDevices(flydroidUrl: String, flydroidKey: String) {
     apply<FlydroidPlugin>()
 
+    // as FlydroidPlugin add some repositories to look for dependencies
+    // the repositories set in settings.gradle.kts via dependencyResolutionManagement
+    // are ignored because we are in mode PREFER_PROJECT
+    // to fix that we add the missing repository here too
+    repositories { // this mirror contents in settings.gradle.kts
+        google {
+            content {
+                includeGroupByRegex("""android\.arch\..*""")
+                includeGroupByRegex("""androidx\..*""")
+                includeGroupByRegex("""com\.android\..*""")
+                includeGroupByRegex("""com\.google\..*""")
+                includeGroup("com.crashlytics.sdk.android")
+                includeGroup("io.fabric.sdk.android")
+                includeGroup("org.chromium.net")
+                includeGroup("zipflinger")
+                includeGroup("com.android")
+            }
+        }
+        mavenCentral()
+        // for geekdroid
+        flatDir {
+            dirs("$rootDir/libs")
+        }
+        maven {
+            url = uri("https://jitpack.io")
+        }
+
+    }
     val oneInstrumentedTestService = gradle.sharedServices.registerIfAbsent(
             "oneInstrumentedTest", OneInstrumentedTestService::class.java) {
         maxParallelUsages.set(1)
@@ -57,12 +85,12 @@
     configure<AvdlExtension> {
         devices {
             android.testVariants.all {
-                register("android-n-${project.path}-$baseName") {
+                register("android-p-${project.path}-$baseName") {
                     setup = flydroid {
                         url = flydroidUrl
                         this.flydroidKey = flydroidKey
                         // android-q images fail, don't manage to start the tests
-                        image = "android-n"
+                        image = "android-p"
                          useTunnel = true
                     }
                 }
@@ -75,7 +103,7 @@
         var lastTestTask: TaskProvider<out Task>? = null
         android.testVariants.all {
             val (startTask, stopTask ) =
-                registerAvdlDevicesTaskForVariant(this, listOf("android-n-${project.path}-$baseName"))
+                registerAvdlDevicesTaskForVariant(this, listOf("android-p-${project.path}-$baseName"))
             listOf(startTask, stopTask).forEach {
                 it.configure {
                     usesService(oneInstrumentedTestService)
--- a/buildSrc/src/main/kotlin/RepositoryChangeset.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/RepositoryChangeset.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -21,23 +21,35 @@
  */
 package com.geekorum.build
 
+import com.android.build.api.variant.ApplicationAndroidComponentsExtension
+import com.android.build.api.variant.BuildConfigField
+import com.android.build.api.variant.VariantOutputConfiguration.OutputType
+import org.gradle.api.DefaultTask
 import org.gradle.api.Project
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.configurationcache.extensions.capitalized
+import org.gradle.kotlin.dsl.register
+import org.gradle.process.ExecOperations
+import java.io.ByteArrayOutputStream
 import java.io.File
-import java.io.IOException
-import java.util.concurrent.TimeUnit
-
-internal fun Project.getGitSha1(): String? = runCommand("git rev-parse HEAD", workingDir = projectDir)?.trim()
-
-internal fun Project.getHgSha1(): String? = runCommand("hg id --debug -i -r .", workingDir = projectDir)?.trim()
+import javax.inject.Inject
 
-internal fun Project.getHgLocalRevisionNumber(): String? = runCommand("hg id -n -r .", workingDir = projectDir)?.trim()
+internal fun ExecOperations.getGitSha1(projectDir: File): String? = runCommand("git rev-parse HEAD", workingDir = projectDir)?.trim()
+
+internal fun ExecOperations.getHgSha1(projectDir: File): String? = runCommand("hg id --debug -i -r .", workingDir = projectDir)?.trim()
+
+internal fun ExecOperations.getHgLocalRevisionNumber(projectDir: File): String? = runCommand("hg id -n -r .", workingDir = projectDir)?.trim()
 
-fun Project.getChangeSet(): String {
-    val git = rootProject.file(".git")
-    val hg = rootProject.file(".hg")
+private fun ExecOperations.getChangeSet(projectDir: File): String {
+    val git = File(projectDir, ".git")
+    val hg = File(projectDir, ".hg")
     return when {
-        git.exists() -> "git:${getGitSha1()}"
-        hg.exists() -> "hg:${getHgSha1()}"
+        git.exists() -> "git:${getGitSha1(projectDir)}"
+        hg.exists() -> "hg:${getHgSha1(projectDir)}"
         else -> "unknown"
     }
 }
@@ -47,27 +59,98 @@
  * M is major, mm is minor, P is patch
  * BBB is build version number from hg
  */
-fun Project.computeChangesetVersionCode(major: Int = 0, minor: Int = 0, patch: Int = 0): Int {
+private fun ExecOperations.computeChangesetVersionCode(projectDir: File, major: Int = 0, minor: Int = 0, patch: Int = 0): Int {
     val base = (major * 1000000) + (minor * 10000) + (patch * 1000)
-    return base + (getHgLocalRevisionNumber()?.trim()?.toIntOrNull() ?: 0)
+    return base + (getHgLocalRevisionNumber(projectDir)?.trim()?.toIntOrNull() ?: 0)
+}
+
+private fun ExecOperations.runCommand(
+    command: String,
+    workingDir: File = File(".")
+): String? {
+    val output = ByteArrayOutputStream()
+    val result = exec {
+        commandLine(command.split("\\s".toRegex()))
+        setWorkingDir(workingDir)
+        setStandardOutput(output)
+        setErrorOutput(output)
+    }
+    result.rethrowFailure()
+    return output.toString(Charsets.UTF_8)
 }
 
-private fun Project.runCommand(
-    command: String,
-    workingDir: File = File("."),
-    timeoutAmount: Long = 60,
-    timeoutUnit: TimeUnit = TimeUnit.MINUTES
-): String? {
-    return try {
-        ProcessBuilder(*command.split("\\s".toRegex()).toTypedArray())
-            .directory(workingDir)
-            .redirectOutput(ProcessBuilder.Redirect.PIPE)
-            .redirectError(ProcessBuilder.Redirect.PIPE)
-            .start().apply {
-                waitFor(timeoutAmount, timeoutUnit)
-            }.inputStream.bufferedReader().readText()
-    } catch (e: IOException) {
-        logger.info("Unable to run command", e)
-        null
+abstract class VersionCodeTask : DefaultTask() {
+
+    @get:OutputFile
+    abstract val versionCodeOutputFile: RegularFileProperty
+
+    @get:OutputFile
+    abstract val changesetOutputFile: RegularFileProperty
+
+    @get:Input
+    abstract val repositoryDirectory: Property<String>
+
+    @get:Input
+    abstract val major: Property<Int>
+
+    @get:Input
+    abstract val minor: Property<Int>
+
+    @get:Input
+    abstract val patch: Property<Int>
+
+    @get:Inject
+    abstract val exec: ExecOperations
+
+    @TaskAction
+    fun computeVersionCode() {
+        val projectDir = File(repositoryDirectory.get())
+        val versionCode = exec.computeChangesetVersionCode(projectDir, major.getOrElse(0), minor.getOrElse(0), patch.getOrElse(0))
+        versionCodeOutputFile.get().asFile.writeText("$versionCode")
+    }
+
+    @TaskAction
+    fun computeChangeset() {
+        val projectDir = File(repositoryDirectory.get())
+        val changeset = exec.getChangeSet(projectDir)
+        changesetOutputFile.get().asFile.writeText(changeset)
     }
 }
+
+/**
+ * @param versionNameSuffix extra string to add to version name
+ */
+fun ApplicationAndroidComponentsExtension.configureVersionChangeset(project: Project, major: Int, minor: Int, patch: Int, versionNameSuffix: String = "") {
+    // Note: Everything in there is incubating.
+
+    // onVariantProperties registers an action that configures variant properties during
+    // variant computation (which happens during afterEvaluate)
+    onVariants {
+        // Because app module can have multiple output when using mutli-APK, versionCode/Name
+        // are only available on the variant output.
+        // Here gather the output when we are in single mode (ie no multi-apk)
+        val mainOutput = it.outputs.single { it.outputType == OutputType.SINGLE }
+
+        // create version Code generating task
+        val versionCodeTask = project.tasks.register<VersionCodeTask>("computeVersionCodeFor${it.name.capitalized()}") {
+            this.major.set(major)
+            this.minor.set(minor)
+            this.patch.set(patch)
+            repositoryDirectory.set(project.rootDir.absolutePath)
+            versionCodeOutputFile.set(project.layout.buildDirectory.file("intermediates/versionCode.txt"))
+            changesetOutputFile.set(project.layout.buildDirectory.file("intermediates/changeset.txt"))
+        }
+
+        // wire version code from the task output
+        // map will create a lazy Provider that
+        // 1. runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
+        //    and therefore the file is created.
+        // 2. contains task dependency information so that the consumer(s) run after the producer.
+        mainOutput.versionCode.set(versionCodeTask.map { it.versionCodeOutputFile.get().asFile.readText().toInt() })
+        mainOutput.versionName.set("$major.$minor.$patch$versionNameSuffix")
+
+        it.buildConfigFields.put("REPOSITORY_CHANGESET", versionCodeTask.map {
+            BuildConfigField("String", "\"${it.changesetOutputFile.get().asFile.readText()}\"", "Repository changeset")
+        })
+    }
+}
--- a/buildSrc/src/main/kotlin/SourceLicenseChecker.kt	Wed Oct 06 16:05:47 2021 -0400
+++ b/buildSrc/src/main/kotlin/SourceLicenseChecker.kt	Tue Nov 29 14:05:44 2022 -0400
@@ -21,48 +21,145 @@
  */
 package com.geekorum.build
 
+import com.android.build.api.dsl.AndroidSourceSet
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.DynamicFeaturePlugin
 import com.hierynomus.gradle.license.LicenseBasePlugin
 import com.hierynomus.gradle.license.tasks.LicenseCheck
 import com.hierynomus.gradle.license.tasks.LicenseFormat
+import nl.javadude.gradle.plugins.license.License
 import nl.javadude.gradle.plugins.license.LicenseExtension
 import nl.javadude.gradle.plugins.license.LicensePlugin
+import org.gradle.api.NamedDomainObjectContainer
 import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.kotlin.dsl.apply
-import org.gradle.kotlin.dsl.configure
-import org.gradle.kotlin.dsl.invoke
-import org.gradle.kotlin.dsl.named
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinJsPluginWrapper
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
+import java.util.*
 
-internal fun Project.configureSourceLicenseChecker(): Unit {
+internal fun Project.configureSourceLicenseChecker() {
     apply<LicensePlugin>()
 
     configure<LicenseExtension> {
         header = file("$rootDir/config/license/header.txt")
-        // ignore failures for now until we set the final license
-        ignoreFailures = true
+        mapping("java", "SLASHSTAR_STYLE")
+        mapping("kt", "SLASHSTAR_STYLE")
 
         excludes(listOf("**/*.webp", "**/*.png"))
     }
 
-    tasks {
-        val checkKotlinFilesLicenseTask = register("checkKotlinFilesLicense", LicenseCheck::class.java) {
-            source = fileTree("src").apply {
-                include("**/*.kt")
-            }
-        }
+    // the LicensePlugin doesn't configure itself properly on DynamicFeaturePlugin
+    // Copied the code to configure it
+    plugins.withType(DynamicFeaturePlugin::class.java) {
+        configureAndroid()
+    }
+    // make the license tasks looks for kotlin files in an Android project
+    plugins.withType(KotlinAndroidPluginWrapper::class.java) {
+        configureKotlinAndroid()
+    }
+
+    // make the license tasks for kotlin js project
+    plugins.withType(KotlinJsPluginWrapper::class.java) {
+        configureKotlin()
+    }
+
+    plugins.withType(KotlinMultiplatformPluginWrapper::class.java) {
+        configureKotlin()
+    }
+}
 
-        val formatKotlinFilesLicenseTask = register("formatKotlinFilesLicense", LicenseFormat::class.java) {
-            source = fileTree("src").apply {
-                include("**/*.kt")
-            }
+@OptIn(ExperimentalStdlibApi::class)
+private fun Project.configureKotlin() {
+    val kotlin = the<KotlinProjectExtension>()
+    val taskInfix = ""
+    kotlin.sourceSets.configureEach {
+        val kotlinSource = this
+        val sourceSetTaskName =
+            "${LicenseBasePlugin.getLICENSE_TASK_BASE_NAME()}${taskInfix}${name.capitalize(Locale.ROOT)}"
+        logger.info("Adding $sourceSetTaskName task for sourceSet ${kotlinSource.name}")
+        if (sourceSetTaskName in tasks.names) {
+            // tasks may have already been added by configuration for the Android plugin
+            logger.info("Tasks $sourceSetTaskName already exists. Skip")
+            return@configureEach
         }
-
-        named<Task>(LicenseBasePlugin.getLICENSE_TASK_BASE_NAME()) {
-            dependsOn(checkKotlinFilesLicenseTask)
+        tasks.register(sourceSetTaskName, LicenseCheck::class.java) {
+            source(kotlinSource.kotlin)
         }
-
-        named<Task>(LicenseBasePlugin.getFORMAT_TASK_BASE_NAME()) {
-            dependsOn(formatKotlinFilesLicenseTask)
+        val sourceSetFormatTaskName =
+            "${LicenseBasePlugin.getFORMAT_TASK_BASE_NAME()}${taskInfix}${name.capitalize(Locale.ROOT)}"
+        tasks.register(sourceSetFormatTaskName, LicenseFormat::class.java) {
+            source(kotlinSource.kotlin)
         }
     }
 }
+
+@OptIn(ExperimentalStdlibApi::class)
+private fun Project.configureKotlinAndroid() {
+    val kotlin = the<KotlinProjectExtension>()
+    val android = the<BaseExtension>()
+    val taskInfix = "Android"
+    android.sourceSets.configureEach {
+        val kotlinSource = kotlin.sourceSets[name]
+        logger.info("Adding kotlin sources from sourceSet $name to License plugin tasks")
+        val sourceSetTaskName =
+            "${LicenseBasePlugin.getLICENSE_TASK_BASE_NAME()}${taskInfix}${name.capitalize(Locale.ROOT)}"
+        tasks.named(sourceSetTaskName, LicenseCheck::class.java) {
+            source(kotlinSource.kotlin, manifest.srcFile)
+        }
+        val sourceSetFormatTaskName =
+            "${LicenseBasePlugin.getFORMAT_TASK_BASE_NAME()}${taskInfix}${name.capitalize(Locale.ROOT)}"
+        tasks.named(sourceSetFormatTaskName, LicenseFormat::class.java) {
+            source(kotlinSource.kotlin, manifest.srcFile)
+        }
+    }
+}
+
+
+private fun Project.configureAndroid() {
+    val android = the<BaseExtension>()
+    configureSourceSetRule(android.sourceSets, "Android") { ss ->
+        @Suppress("DEPRECATION")
+        when (ss) {
+            // the dsl.AndroidSourceSet don't expose any getter, so we still need to cast it
+            is com.android.build.gradle.api.AndroidSourceSet -> {
+        ss.java.getSourceFiles() + ss.res.getSourceFiles() + fileTree(ss.manifest.srcFile)
+            }
+            else -> fileTree()
+        }
+    }
+}
+
+/**
+ * Dynamically create a task for each sourceSet, and register with check
+ */
+@Suppress("DefaultLocale")
+private fun Project.configureSourceSetRule(androidSourceSetContainer: NamedDomainObjectContainer<out AndroidSourceSet>,
+                                           taskInfix: String, sourceSetSources: (AndroidSourceSet) -> FileTree) {
+    // This follows the other check task pattern
+    androidSourceSetContainer.configureEach {
+        val sourceSetTaskName = "${LicenseBasePlugin.getLICENSE_TASK_BASE_NAME()}${taskInfix}${name.capitalize()}"
+        logger.info("Adding $sourceSetTaskName task for sourceSet $name")
+
+        val checkTask = tasks.register(sourceSetTaskName, LicenseCheck::class.java)
+        configureForSourceSet(this, checkTask, sourceSetSources)
+
+        // Add independent license task, which will perform format
+        val sourceSetFormatTaskName = "${LicenseBasePlugin.getFORMAT_TASK_BASE_NAME()}${taskInfix}${name.capitalize()}"
+        val formatTask = tasks.register(sourceSetFormatTaskName, LicenseFormat::class.java)
+        configureForSourceSet(this, formatTask, sourceSetSources)
+    }
+}
+
+private fun configureForSourceSet(sourceSet: AndroidSourceSet, task: TaskProvider<out License>, sourceSetSources: (AndroidSourceSet) -> FileTree) {
+    task.configure {
+        // Explicitly set description
+        description = "Scanning license on ${sourceSet.name} files"
+
+        // Default to all source files from SourceSet
+        source = sourceSetSources(sourceSet)
+    }
+}
--- a/geekdroid-firebase/build.gradle.kts	Wed Oct 06 16:05:47 2021 -0400
+++ b/geekdroid-firebase/build.gradle.kts	Tue Nov 29 14:05:44 2022 -0400
@@ -14,6 +14,7 @@
 android {
     val compileSdkInt: Int by rootProject.extra
     compileSdk = compileSdkInt
+    namespace = "com.geekorum.geekdroid.firebase"
 
     defaultConfig {
         minSdk = 24
@@ -30,7 +31,7 @@
     }
 
     lint {
-        isAbortOnError = false
+        abortOnError = false
     }
 
 }
--- a/geekdroid/build.gradle.kts	Wed Oct 06 16:05:47 2021 -0400
+++ b/geekdroid/build.gradle.kts	Tue Nov 29 14:05:44 2022 -0400
@@ -37,6 +37,7 @@
 android {
     val compileSdkInt: Int by rootProject.extra
     compileSdk = compileSdkInt
+    namespace = "com.geekorum.geekdroid"
 
     defaultConfig {
         minSdk = 24
@@ -53,11 +54,11 @@
     }
 
     lint {
-        isAbortOnError = false
+        abortOnError = false
     }
 
     dataBinding {
-        isEnabled = true
+        enable = true
     }
 
 }
--- a/gradle/wrapper/gradle-wrapper.properties	Wed Oct 06 16:05:47 2021 -0400
+++ b/gradle/wrapper/gradle-wrapper.properties	Tue Nov 29 14:05:44 2022 -0400
@@ -2,4 +2,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip