Skip to content

Commit 0cd9ab8

Browse files
authored
Add Spotless configuration & introduce test plugin (#2)
* Add Spotless configuration to plugins * Preselect minor in release workflow * Introduce Android test Stream plugin * Update readme * Bump publish plugin * Group Spotless-specific configuration
1 parent 348636b commit 0cd9ab8

File tree

11 files changed

+416
-14
lines changed

11 files changed

+416
-14
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- patch
1212
- minor
1313
- major
14+
default: 'minor'
1415
version-properties-file:
1516
description: 'Path to properties file containing version'
1617
required: false

README.md

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,52 @@ dependencies, and configurations used across Stream's Android projects.
1313

1414
## Available Plugins
1515

16+
- **`io.getstream.project`** - Root project configuration (apply to root `build.gradle.kts`)
1617
- **`io.getstream.android.library`** - For Android library modules
1718
- **`io.getstream.android.application`** - For Android application modules
19+
- **`io.getstream.android.test`** - For Android test modules
1820
- **`io.getstream.java.library`** - For Java/Kotlin JVM library modules
1921

2022
## Usage
2123

22-
Add the plugin to your project's build file:
24+
### 1. Root Project Configuration
25+
26+
Apply the root plugin in your root `build.gradle.kts`:
2327

2428
```kotlin
2529
plugins {
26-
id("io.getstream.android.library") version "<version>"
27-
// or
28-
id("io.getstream.android.application") version "<version>"
29-
// or
30-
id("io.getstream.java.library") version "<version>"
30+
id("io.getstream.project") version "<version>"
31+
}
32+
33+
streamProject {
34+
// Repository name for GitHub URLs and license headers (default: project name)
35+
repositoryName = "stream-chat-android"
36+
37+
spotless {
38+
// Choose formatter (default: false = ktlint)
39+
useKtfmt = false
40+
41+
// Exclude specific modules from Spotless formatting (default: empty)
42+
ignoredModules = setOf("some-module")
43+
44+
// Exclude file patterns from Spotless formatting (default: empty)
45+
excludePatterns = setOf("**/generated/**")
46+
}
3147
}
3248
```
3349

34-
## Distribution
50+
### 2. Module Configuration
3551

36-
These plugins are published to:
52+
Apply the appropriate plugin to each module:
3753

38-
- [Maven Central](https://central.sonatype.com/)
39-
- [Gradle Plugin Portal](https://plugins.gradle.org/)
54+
```kotlin
55+
plugins {
56+
id("io.getstream.android.library")
57+
// or id("io.getstream.android.application")
58+
// or id("io.getstream.android.test")
59+
// or id("io.getstream.java.library")
60+
}
61+
```
4062

4163
## License
4264

gradle/libs.versions.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
agp = "8.11.1"
33
kotlin = "2.0.21"
44
detekt = "1.23.8"
5-
spotless = "7.2.1"
5+
spotless = "8.0.0"
66
kotlinDokka = "2.0.0"
77
gradlePluginPublish = "2.0.0"
8-
mavenPublish = "0.32.0"
8+
mavenPublish = "0.34.0"
99

1010
[libraries]
1111
android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
1212
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
13+
spotless-gradle-plugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" }
1314

1415
[plugins]
1516
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

plugin/build.gradle.kts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import com.vanniktech.maven.publish.GradlePlugin
22
import com.vanniktech.maven.publish.JavadocJar
3-
import com.vanniktech.maven.publish.SonatypeHost
43
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
54

65
plugins {
@@ -24,6 +23,7 @@ dependencies {
2423
compileOnly(gradleKotlinDsl())
2524
compileOnly(libs.android.gradle.plugin)
2625
compileOnly(libs.kotlin.gradle.plugin)
26+
implementation(libs.spotless.gradle.plugin)
2727
}
2828

2929
val repoId = "GetStream/stream-build-conventions-android"
@@ -34,6 +34,14 @@ gradlePlugin {
3434
vcsUrl = repoUrl
3535

3636
plugins {
37+
create("root") {
38+
id = "io.getstream.project"
39+
implementationClass = "io.getstream.android.RootConventionPlugin"
40+
displayName = "Stream Root Convention Plugin"
41+
description =
42+
"Root convention plugin for Stream projects - configures project-wide settings"
43+
tags = listOf("stream", "conventions", "configuration")
44+
}
3745
create("androidLibrary") {
3846
id = "io.getstream.android.library"
3947
implementationClass = "io.getstream.android.AndroidLibraryConventionPlugin"
@@ -48,6 +56,13 @@ gradlePlugin {
4856
description = "Convention plugin for Stream Android application modules"
4957
tags = listOf("android", "application", "convention", "stream", "kotlin")
5058
}
59+
create("androidTest") {
60+
id = "io.getstream.android.test"
61+
implementationClass = "io.getstream.android.AndroidTestConventionPlugin"
62+
displayName = "Stream Android Test Convention Plugin"
63+
description = "Convention plugin for Stream Android test modules"
64+
tags = listOf("android", "test", "convention", "stream", "kotlin")
65+
}
5166
create("javaLibrary") {
5267
id = "io.getstream.java.library"
5368
implementationClass = "io.getstream.android.JavaLibraryConventionPlugin"
@@ -59,7 +74,7 @@ gradlePlugin {
5974
}
6075

6176
mavenPublishing {
62-
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
77+
publishToMavenCentral(automaticRelease = true)
6378
configure(GradlePlugin(javadocJar = JavadocJar.Javadoc(), sourcesJar = true))
6479

6580
pom {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-build-conventions-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.getstream.android
17+
18+
import java.io.BufferedReader
19+
import org.gradle.api.DefaultTask
20+
import org.gradle.api.file.ConfigurableFileCollection
21+
import org.gradle.api.file.RegularFileProperty
22+
import org.gradle.api.provider.Property
23+
import org.gradle.api.tasks.CacheableTask
24+
import org.gradle.api.tasks.Classpath
25+
import org.gradle.api.tasks.Input
26+
import org.gradle.api.tasks.OutputFile
27+
import org.gradle.api.tasks.TaskAction
28+
29+
/**
30+
* Task that generates license header files from templates bundled in the plugin.
31+
*
32+
* This task uses [@Classpath] to track the plugin JAR that contains the template resources. When
33+
* the plugin is updated with new template content, Gradle will automatically detect the change and
34+
* regenerate the license files.
35+
*/
36+
@CacheableTask
37+
abstract class GenerateLicenseFileTask : DefaultTask() {
38+
@get:Input abstract val repositoryName: Property<String>
39+
40+
@get:Input abstract val templateName: Property<String>
41+
42+
/**
43+
* The classpath containing the plugin resources (the plugin JAR itself). Used to track changes
44+
* to the template files so the task is re-run.
45+
*/
46+
@get:Classpath abstract val pluginClasspath: ConfigurableFileCollection
47+
48+
@get:OutputFile abstract val outputFile: RegularFileProperty
49+
50+
@TaskAction
51+
fun generate() {
52+
val templateContent =
53+
javaClass.classLoader
54+
.getResourceAsStream(templateName.get())
55+
?.bufferedReader()
56+
?.use(BufferedReader::readText)
57+
?: throw IllegalStateException(
58+
"Could not find bundled ${templateName.get()} resource"
59+
)
60+
61+
outputFile
62+
.get()
63+
.asFile
64+
.writeText(templateContent.replace("\$PROJECT", repositoryName.get()))
65+
}
66+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-build-conventions-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.getstream.android
17+
18+
import io.getstream.android.spotless.SpotlessOptions
19+
import javax.inject.Inject
20+
import org.gradle.api.Action
21+
import org.gradle.api.Project
22+
import org.gradle.api.model.ObjectFactory
23+
import org.gradle.api.provider.Property
24+
import org.gradle.kotlin.dsl.create
25+
import org.gradle.kotlin.dsl.findByType
26+
import org.gradle.kotlin.dsl.property
27+
28+
/**
29+
* Extension for configuring Stream project-wide settings. Apply the `io.getstream.project` plugin
30+
* to the root project to use this extension.
31+
*/
32+
abstract class StreamProjectExtension
33+
@Inject
34+
constructor(project: Project, objects: ObjectFactory) {
35+
36+
/** The repository name used for inferring the repository URL. Example: "stream-core-android" */
37+
val repositoryName: Property<String> =
38+
objects.property<String>().convention(project.provider { project.rootProject.name })
39+
40+
/** Spotless formatting configuration */
41+
val spotless: SpotlessOptions = objects.newInstance(SpotlessOptions::class.java)
42+
43+
/** Configure Spotless formatting */
44+
fun spotless(action: Action<SpotlessOptions>) = action.execute(spotless)
45+
}
46+
47+
internal fun Project.createProjectExtension(): StreamProjectExtension =
48+
extensions.create<StreamProjectExtension>("streamProject")
49+
50+
internal fun Project.requireStreamProjectExtension(): StreamProjectExtension =
51+
requireNotNull(rootProject.extensions.findByType<StreamProjectExtension>()) {
52+
"${StreamProjectExtension::class.simpleName} not found. " +
53+
"Apply the io.getstream.project plugin to the root project"
54+
}

plugin/src/main/kotlin/io/getstream/android/StreamConventionPlugins.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,35 @@ package io.getstream.android
1717

1818
import com.android.build.api.dsl.ApplicationExtension
1919
import com.android.build.api.dsl.LibraryExtension
20+
import com.android.build.api.dsl.TestExtension
21+
import io.getstream.android.spotless.configureSpotless
2022
import org.gradle.api.Plugin
2123
import org.gradle.api.Project
2224

25+
/**
26+
* Root-level convention plugin for Stream projects. Apply this plugin to the root project to
27+
* configure project-wide settings.
28+
*/
29+
class RootConventionPlugin : Plugin<Project> {
30+
override fun apply(target: Project) {
31+
with(target) {
32+
require(this == rootProject) {
33+
"The io.getstream.project plugin should be applied to the root project only"
34+
}
35+
36+
createProjectExtension()
37+
}
38+
}
39+
}
40+
2341
class AndroidApplicationConventionPlugin : Plugin<Project> {
2442
override fun apply(target: Project) {
2543
with(target) {
2644
pluginManager.apply("com.android.application")
2745

2846
configureAndroid<ApplicationExtension>()
2947
configureKotlin()
48+
configureSpotless()
3049
}
3150
}
3251
}
@@ -38,6 +57,19 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
3857

3958
configureAndroid<LibraryExtension>()
4059
configureKotlin()
60+
configureSpotless()
61+
}
62+
}
63+
}
64+
65+
class AndroidTestConventionPlugin : Plugin<Project> {
66+
override fun apply(target: Project) {
67+
with(target) {
68+
pluginManager.apply("com.android.test")
69+
70+
configureAndroid<TestExtension>()
71+
configureKotlin()
72+
configureSpotless()
4173
}
4274
}
4375
}
@@ -49,6 +81,7 @@ class JavaLibraryConventionPlugin : Plugin<Project> {
4981

5082
configureJava()
5183
configureKotlin()
84+
configureSpotless()
5285
}
5386
}
5487
}

0 commit comments

Comments
 (0)