Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- patch
- minor
- major
default: 'minor'
version-properties-file:
description: 'Path to properties file containing version'
required: false
Expand Down
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,52 @@ dependencies, and configurations used across Stream's Android projects.

## Available Plugins

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

## Usage

Add the plugin to your project's build file:
### 1. Root Project Configuration

Apply the root plugin in your root `build.gradle.kts`:

```kotlin
plugins {
id("io.getstream.android.library") version "<version>"
// or
id("io.getstream.android.application") version "<version>"
// or
id("io.getstream.java.library") version "<version>"
id("io.getstream.project") version "<version>"
}

streamProject {
// Repository name for GitHub URLs and license headers (default: project name)
repositoryName = "stream-chat-android"

spotless {
// Choose formatter (default: false = ktlint)
useKtfmt = false

// Exclude specific modules from Spotless formatting (default: empty)
ignoredModules = setOf("some-module")

// Exclude file patterns from Spotless formatting (default: empty)
excludePatterns = setOf("**/generated/**")
}
}
```

## Distribution
### 2. Module Configuration

These plugins are published to:
Apply the appropriate plugin to each module:

- [Maven Central](https://central.sonatype.com/)
- [Gradle Plugin Portal](https://plugins.gradle.org/)
```kotlin
plugins {
id("io.getstream.android.library")
// or id("io.getstream.android.application")
// or id("io.getstream.android.test")
// or id("io.getstream.java.library")
}
```

## License

Expand Down
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
agp = "8.11.1"
kotlin = "2.0.21"
detekt = "1.23.8"
spotless = "7.2.1"
spotless = "8.0.0"
kotlinDokka = "2.0.0"
gradlePluginPublish = "2.0.0"
mavenPublish = "0.32.0"
mavenPublish = "0.34.0"

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

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Expand Down
19 changes: 17 additions & 2 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import com.vanniktech.maven.publish.GradlePlugin
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.SonatypeHost
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
Expand All @@ -24,6 +23,7 @@ dependencies {
compileOnly(gradleKotlinDsl())
compileOnly(libs.android.gradle.plugin)
compileOnly(libs.kotlin.gradle.plugin)
implementation(libs.spotless.gradle.plugin)
}

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

plugins {
create("root") {
id = "io.getstream.project"
implementationClass = "io.getstream.android.RootConventionPlugin"
displayName = "Stream Root Convention Plugin"
description =
"Root convention plugin for Stream projects - configures project-wide settings"
tags = listOf("stream", "conventions", "configuration")
}
create("androidLibrary") {
id = "io.getstream.android.library"
implementationClass = "io.getstream.android.AndroidLibraryConventionPlugin"
Expand All @@ -48,6 +56,13 @@ gradlePlugin {
description = "Convention plugin for Stream Android application modules"
tags = listOf("android", "application", "convention", "stream", "kotlin")
}
create("androidTest") {
id = "io.getstream.android.test"
implementationClass = "io.getstream.android.AndroidTestConventionPlugin"
displayName = "Stream Android Test Convention Plugin"
description = "Convention plugin for Stream Android test modules"
tags = listOf("android", "test", "convention", "stream", "kotlin")
}
create("javaLibrary") {
id = "io.getstream.java.library"
implementationClass = "io.getstream.android.JavaLibraryConventionPlugin"
Expand All @@ -59,7 +74,7 @@ gradlePlugin {
}

mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
publishToMavenCentral(automaticRelease = true)
configure(GradlePlugin(javadocJar = JavadocJar.Javadoc(), sourcesJar = true))

pom {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-build-conventions-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android

import java.io.BufferedReader
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

/**
* Task that generates license header files from templates bundled in the plugin.
*
* This task uses [@Classpath] to track the plugin JAR that contains the template resources. When
* the plugin is updated with new template content, Gradle will automatically detect the change and
* regenerate the license files.
*/
@CacheableTask
abstract class GenerateLicenseFileTask : DefaultTask() {
@get:Input abstract val repositoryName: Property<String>

@get:Input abstract val templateName: Property<String>

/**
* The classpath containing the plugin resources (the plugin JAR itself). Used to track changes
* to the template files so the task is re-run.
*/
@get:Classpath abstract val pluginClasspath: ConfigurableFileCollection

@get:OutputFile abstract val outputFile: RegularFileProperty

@TaskAction
fun generate() {
val templateContent =
javaClass.classLoader
.getResourceAsStream(templateName.get())
?.bufferedReader()
?.use(BufferedReader::readText)
?: throw IllegalStateException(
"Could not find bundled ${templateName.get()} resource"
)

outputFile
.get()
.asFile
.writeText(templateContent.replace("\$PROJECT", repositoryName.get()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-build-conventions-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android

import io.getstream.android.spotless.SpotlessOptions
import javax.inject.Inject
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.property

/**
* Extension for configuring Stream project-wide settings. Apply the `io.getstream.project` plugin
* to the root project to use this extension.
*/
abstract class StreamProjectExtension
@Inject
constructor(project: Project, objects: ObjectFactory) {

/** The repository name used for inferring the repository URL. Example: "stream-core-android" */
val repositoryName: Property<String> =
objects.property<String>().convention(project.provider { project.rootProject.name })

/** Spotless formatting configuration */
val spotless: SpotlessOptions = objects.newInstance(SpotlessOptions::class.java)

/** Configure Spotless formatting */
fun spotless(action: Action<SpotlessOptions>) = action.execute(spotless)
}

internal fun Project.createProjectExtension(): StreamProjectExtension =
extensions.create<StreamProjectExtension>("streamProject")

internal fun Project.requireStreamProjectExtension(): StreamProjectExtension =
requireNotNull(rootProject.extensions.findByType<StreamProjectExtension>()) {
"${StreamProjectExtension::class.simpleName} not found. " +
"Apply the io.getstream.project plugin to the root project"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,35 @@ package io.getstream.android

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.TestExtension
import io.getstream.android.spotless.configureSpotless
import org.gradle.api.Plugin
import org.gradle.api.Project

/**
* Root-level convention plugin for Stream projects. Apply this plugin to the root project to
* configure project-wide settings.
*/
class RootConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
require(this == rootProject) {
"The io.getstream.project plugin should be applied to the root project only"
}

createProjectExtension()
}
}
}

class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.application")

configureAndroid<ApplicationExtension>()
configureKotlin()
configureSpotless()
}
}
}
Expand All @@ -38,6 +57,19 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {

configureAndroid<LibraryExtension>()
configureKotlin()
configureSpotless()
}
}
}

class AndroidTestConventionPlugin : Plugin<Project> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introducing this too as we use com.android.test in chat & video

override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.test")

configureAndroid<TestExtension>()
configureKotlin()
configureSpotless()
}
}
}
Expand All @@ -49,6 +81,7 @@ class JavaLibraryConventionPlugin : Plugin<Project> {

configureJava()
configureKotlin()
configureSpotless()
}
}
}
Loading
Loading