Skip to content

Commit 48243b8

Browse files
authored
Binders refactor, adding Android full-support (#227)
1 parent e542622 commit 48243b8

15 files changed

Lines changed: 365 additions & 182 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.gradle/
22
.idea/
33
.kotlin/
4+
local.properties
45
build/
56
out/
67
kotlin-js-store/

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:Suppress("UnstableApiUsage")
2+
13
plugins {
24
base
35
`maven-publish`
@@ -10,6 +12,7 @@ plugins {
1012
}
1113

1214
dependencies {
15+
//noinspection UseTomlInstead
1316
jacocoAggregation("com.github.gmazzo.buildconfig:plugin")
1417
}
1518

demo-project/kts-android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ android {
3939
buildConfigField("MAGIC_NUMBERS", intArrayOf(1, 2, 3, 4))
4040

4141
buildConfigField<Boolean>("IS_DEBUG", this@variant.buildType.isDebuggable)
42-
buildConfigField<String>("BRAND", this@variant.productFlavors.single().name)
42+
buildConfigField<String>("BRAND", this@variant.flavorName)
4343
}
4444
}
4545
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ kotlin = "2.1.20"
55
[libraries]
66
javapoet = { module = "com.squareup:javapoet", version = "1.13.0" }
77
junit = { module = "junit:junit", version = "4.13.2" }
8+
junit5-bom = { module = "org.junit:junit-bom", version = "5.12.1" }
9+
junit5-params = { module = "org.junit.jupiter:junit-jupiter-params" }
810
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
911
kotlinpoet = { module = "com.squareup:kotlinpoet", version = "2.1.0" }
1012
mockk = { module = "io.mockk:mockk", version = "1.13.17" }

plugin/build.gradle.kts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:Suppress("UnstableApiUsage")
2+
13
plugins {
24
alias(libs.plugins.kotlin.jvm)
35
alias(libs.plugins.axion.release)
@@ -13,7 +15,7 @@ description =
1315
"A plugin for generating BuildConstants for any kind of Gradle projects: Java, Kotlin, Groovy, etc. Designed for KTS scripts."
1416
version = scmVersion.version
1517

16-
java.toolchain.languageVersion = JavaLanguageVersion.of(8)
18+
java.toolchain.languageVersion = JavaLanguageVersion.of(libs.versions.java.get())
1719
kotlin.compilerOptions.freeCompilerArgs.add("-Xjvm-default=all")
1820

1921
dependencies {
@@ -22,15 +24,18 @@ dependencies {
2224

2325
compileOnly(gradleKotlinDsl())
2426
compileOnly(plugin(libs.plugins.kotlin.jvm))
27+
compileOnly(plugin(libs.plugins.android))
2528

2629
implementation(libs.javapoet)
2730
implementation(libs.kotlinpoet)
2831

2932
testImplementation(gradleTestKit())
3033
testImplementation(gradleKotlinDsl())
34+
testImplementation(platform(libs.junit5.bom))
35+
testImplementation(libs.junit5.params)
36+
testImplementation(plugin(libs.plugins.android))
3137
testImplementation(libs.kotlin.test)
3238
testImplementation(libs.mockk)
33-
testImplementation("org.junit.jupiter:junit-jupiter-params")
3439
}
3540

3641
val originUrl = providers
@@ -83,13 +88,10 @@ mavenPublishing {
8388
}
8489
}
8590

86-
tasks.withType<Test> {
91+
tasks.test {
8792
workingDir = temporaryDir
8893
useJUnitPlatform()
89-
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(libs.versions.java.get()) }
90-
}
91-
92-
tasks.test {
94+
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } // required by AGP
9395
finalizedBy(tasks.jacocoTestReport)
9496
}
9597

plugin/src/main/kotlin/com/github/gmazzo/buildconfig/BuildConfigPlugin.kt

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@ import com.github.gmazzo.buildconfig.generators.BuildConfigJavaGenerator
44
import com.github.gmazzo.buildconfig.internal.BuildConfigSourceSetInternal
55
import com.github.gmazzo.buildconfig.internal.DefaultBuildConfigExtension
66
import com.github.gmazzo.buildconfig.internal.DefaultBuildConfigSourceSet
7-
import com.github.gmazzo.buildconfig.internal.bindings.JavaHandler
8-
import com.github.gmazzo.buildconfig.internal.bindings.KotlinHandler
9-
import com.github.gmazzo.buildconfig.internal.bindings.KotlinMultiplatformHandler
10-
import com.github.gmazzo.buildconfig.internal.bindings.PluginBindingHandler
7+
import com.github.gmazzo.buildconfig.internal.bindings.JavaBinder
8+
import com.github.gmazzo.buildconfig.internal.bindings.KotlinBinder
119
import org.gradle.api.Action
12-
import org.gradle.api.NamedDomainObjectContainer
1310
import org.gradle.api.Plugin
1411
import org.gradle.api.Project
15-
import org.gradle.api.plugins.ExtensionAware
1612
import org.gradle.api.plugins.PluginContainer
1713
import org.gradle.api.tasks.SourceSet
18-
import org.gradle.kotlin.dsl.add
14+
import org.gradle.kotlin.dsl.com.github.gmazzo.buildconfig.internal.bindings.AndroidBinder
1915
import org.gradle.kotlin.dsl.domainObjectContainer
2016
import org.gradle.kotlin.dsl.newInstance
2117
import org.gradle.kotlin.dsl.register
@@ -25,7 +21,7 @@ import org.gradle.util.GradleVersion
2521
class BuildConfigPlugin : Plugin<Project> {
2622

2723
companion object {
28-
const val MIN_GRADLE_VERSION = "7.0"
24+
const val MIN_GRADLE_VERSION = "7.3"
2925
}
3026

3127
override fun apply(project: Project) = with(project) {
@@ -45,55 +41,51 @@ class BuildConfigPlugin : Plugin<Project> {
4541
defaultSS,
4642
)
4743

48-
sourceSets.configureEach { configureSourceSet(it, defaultSS) }
44+
sourceSets.all { configureSourceSet(it, defaultSS) }
4945

5046
extension.generateAtSync
51-
.convention(findProperty("com.github.gmazzo.buildconfig.generateAtSync")?.toString()?.toBoolean() ?: true)
47+
.convention(findProperty("com.github.gmazzo.buildconfig.generateAtSync")?.toString()?.toBoolean() != false)
5248
.finalizeValueOnRead()
5349

5450
// generate at sync
5551
afterEvaluate {
56-
if (extension.generateAtSync.get()) {
52+
if (extension.generateAtSync.get() && isGradleSync) {
5753
tasks.maybeCreate("prepareKotlinIdeaImport").dependsOn(tasks.withType<BuildConfigTask>())
5854
}
5955
}
6056

6157
plugins.withId("java") {
62-
JavaHandler(project, extension).configure(sourceSets)
58+
with(JavaBinder) { configure(extension) }
6359
}
6460

6561
plugins.withAnyId(
66-
"org.jetbrains.kotlin.android",
6762
"org.jetbrains.kotlin.jvm",
6863
"org.jetbrains.kotlin.js",
6964
"kotlin2js",
7065
) {
71-
KotlinHandler(project, extension).configure(sourceSets)
66+
with(KotlinBinder) { configure(extension) }
7267
}
7368

7469
plugins.withId("org.jetbrains.kotlin.multiplatform") {
75-
KotlinMultiplatformHandler(KotlinHandler(project, extension)).configure(sourceSets)
70+
with(KotlinBinder.Multiplatform) { configure(extension) }
7671
}
77-
}
78-
79-
private fun <SourceSet> PluginBindingHandler<SourceSet>.configure(
80-
specs: NamedDomainObjectContainer<out BuildConfigSourceSetInternal>
81-
) {
82-
onBind()
83-
84-
sourceSets.configureEach { ss ->
85-
val spec = specs.maybeCreate(nameOf(ss))
8672

87-
onSourceSetAdded(ss, spec)
88-
89-
(ss as? ExtensionAware)?.extensions?.add(BuildConfigSourceSet::class, "buildConfig", spec)
73+
plugins.withId("com.android.base") {
74+
with(AndroidBinder) { configure(extension) }
9075
}
9176
}
9277

78+
private val isGradleSync
79+
get() = System.getProperty("idea.sync.active") == "true"
80+
9381
private fun Project.configureSourceSet(
9482
sourceSet: BuildConfigSourceSetInternal,
9583
defaultSS: BuildConfigSourceSetInternal,
9684
) {
85+
check(sourceSet.name.matches("\\w+".toRegex())) {
86+
"Invalid name '$name': only alphanumeric characters are allowed"
87+
}
88+
9789
val prefix = when (sourceSet) {
9890
defaultSS -> ""
9991
else -> sourceSet.name.replaceFirstChar { it.titlecaseChar() }
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package org.gradle.kotlin.dsl.com.github.gmazzo.buildconfig.internal.bindings
2+
3+
import com.github.gmazzo.buildconfig.BuildConfigExtension
4+
import com.github.gmazzo.buildconfig.BuildConfigTask
5+
import com.github.gmazzo.buildconfig.generators.BuildConfigJavaGenerator
6+
import com.github.gmazzo.buildconfig.generators.BuildConfigKotlinGenerator
7+
import com.github.gmazzo.buildconfig.internal.bindings.JavaBinder.registerExtension
8+
import org.gradle.api.Action
9+
import org.gradle.api.Named
10+
import org.gradle.api.NamedDomainObjectContainer
11+
import org.gradle.api.Project
12+
import org.gradle.api.Task
13+
import org.gradle.api.file.Directory
14+
import org.gradle.api.file.DirectoryProperty
15+
import org.gradle.api.plugins.ExtensionAware
16+
import org.gradle.api.provider.Provider
17+
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
18+
import org.gradle.api.tasks.SourceSet.TEST_SOURCE_SET_NAME
19+
import org.gradle.api.tasks.TaskProvider
20+
21+
internal object AndroidBinder {
22+
23+
fun Project.configure(extension: BuildConfigExtension) {
24+
androidSourceSets.all {
25+
val spec = extension.sourceSets.maybeCreate(it.name)
26+
27+
(it as ExtensionAware).registerExtension(spec)
28+
it.javaSrcDir(spec.generateTask.flatMap { it.outputDir })
29+
}
30+
31+
androidComponentsOnVariants { variant ->
32+
val unitTest = variant.unitTest
33+
val androidTest = variant.androidTest
34+
35+
for (component in listOfNotNull(variant, unitTest, androidTest)) {
36+
val specNames = when(component) {
37+
variant -> sequenceOf(MAIN_SOURCE_SET_NAME, component.name, component.buildType, component.flavorName) +
38+
component.productFlavors.asSequence().map { (_, flavor) -> flavor }
39+
unitTest -> sequenceOf(TEST_SOURCE_SET_NAME, component.name)
40+
androidTest -> sequenceOf("androidTest", component.name)
41+
else -> error("Unsupported component $component")
42+
}
43+
val specs = specNames
44+
.filterNotNull()
45+
.filter(String::isNotBlank)
46+
.map(extension.sourceSets::maybeCreate)
47+
48+
for (spec in specs) {
49+
component.sourcesJavaAddGeneratedSourceDirectory(spec.generateTask, BuildConfigTask::outputDir)
50+
}
51+
}
52+
}
53+
54+
extension.generator.convention(project.provider {
55+
if (plugins.hasPlugin("org.jetbrains.kotlin.android")) BuildConfigKotlinGenerator()
56+
else BuildConfigJavaGenerator()
57+
})
58+
}
59+
60+
// project.androidComponents.onVariants
61+
private fun Project.androidComponentsOnVariants(onVariant: Action<Any>) =
62+
with(extensions.getByName("androidComponents")) {
63+
val selectorsAll = with(javaClass.getMethod("selector").invoke(this)) {
64+
javaClass.getMethod("all").invoke(this)
65+
}
66+
val selectorInterface = selectorsAll.javaClass.superclass.interfaces[0]
67+
68+
@Suppress("UNCHECKED_CAST")
69+
javaClass.getMethod("onVariants", selectorInterface, Action::class.java)
70+
.invoke(this, selectorsAll, onVariant)
71+
}
72+
73+
// Variant.unitTest
74+
private val Any.unitTest
75+
get() = try {
76+
javaClass.getMethod("getUnitTest").invoke(this)
77+
78+
} catch (_: NoSuchMethodException) {
79+
null
80+
}
81+
82+
// Variant.androidTest
83+
private val Any.androidTest
84+
get() = try {
85+
javaClass.getMethod("getAndroidTest").invoke(this)
86+
87+
} catch (_: NoSuchMethodException) {
88+
null
89+
}
90+
91+
// Component.name
92+
private val Any.name
93+
get() = javaClass.getMethod("getName").invoke(this) as String?
94+
95+
// Component.buildType
96+
private val Any.buildType
97+
get() = javaClass.getMethod("getBuildType").invoke(this) as String?
98+
99+
// Component.flavorName
100+
private val Any.flavorName
101+
get() = javaClass.getMethod("getFlavorName").invoke(this) as String?
102+
103+
// Component.productFlavors
104+
@Suppress("UNCHECKED_CAST")
105+
private val Any.productFlavors
106+
get() = javaClass.getMethod("getProductFlavors").invoke(this) as List<Pair<String, String>>
107+
108+
// Component.sources.java!!.addGeneratedSourceDirectory
109+
private fun <Type : Task> Any.sourcesJavaAddGeneratedSourceDirectory(
110+
task: TaskProvider<out Type>,
111+
wiredWith: (Type) -> DirectoryProperty,
112+
) = with(javaClass.getMethod("getSources").invoke(this)) {
113+
with(javaClass.getMethod("getJava").invoke(this)) {
114+
javaClass.getMethod("addGeneratedSourceDirectory", TaskProvider::class.java, Function1::class.java)
115+
.invoke(this, task, wiredWith)
116+
}
117+
}
118+
119+
// project.android.sourceSets
120+
private val Project.androidSourceSets
121+
get() = with(extensions.getByName("android")) {
122+
@Suppress("UNCHECKED_CAST")
123+
javaClass.getMethod("getSourceSets")
124+
.invoke(this) as NamedDomainObjectContainer<Named>
125+
}
126+
127+
// AndroidSourceSet.java.srcDir(dir)
128+
private fun Named.javaSrcDir(dir: Provider<Directory>) {
129+
with(javaClass.getMethod("getJava").invoke(this)) {
130+
javaClass.getMethod("srcDir", Any::class.java)
131+
.invoke(this, dir)
132+
}
133+
}
134+
135+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.github.gmazzo.buildconfig.internal.bindings
2+
3+
import com.github.gmazzo.buildconfig.BuildConfigExtension
4+
import com.github.gmazzo.buildconfig.BuildConfigSourceSet
5+
import com.github.gmazzo.buildconfig.generators.BuildConfigJavaGenerator
6+
import org.gradle.api.Project
7+
import org.gradle.api.plugins.ExtensionAware
8+
import org.gradle.api.tasks.SourceSetContainer
9+
import org.gradle.kotlin.dsl.add
10+
import org.gradle.kotlin.dsl.getValue
11+
import org.gradle.kotlin.dsl.provideDelegate
12+
13+
internal object JavaBinder {
14+
15+
fun Project.configure(extension: BuildConfigExtension) {
16+
val sourceSets: SourceSetContainer by extensions
17+
18+
sourceSets.configureEach { sourceSet ->
19+
val spec = extension.sourceSets.maybeCreate(sourceSet.name)
20+
21+
sourceSet.registerExtension(spec)
22+
sourceSet.java.srcDir(spec)
23+
}
24+
25+
extension.generator.convention(BuildConfigJavaGenerator())
26+
}
27+
28+
internal fun ExtensionAware.registerExtension(sourceSet: BuildConfigSourceSet) =
29+
extensions.add(BuildConfigSourceSet::class, "buildConfig", sourceSet)
30+
31+
}

plugin/src/main/kotlin/com/github/gmazzo/buildconfig/internal/bindings/JavaHandler.kt

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)