diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0e3ac8..ba0ba38 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,6 @@ jobs: uses: actions/checkout@v4 with: token: ${{ secrets.github-token }} - ref: develop - name: Bump version id: bump diff --git a/README.md b/README.md index fed6454..fe2c015 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,18 @@ configurations to ensure consistency across Stream's Android libraries and appli > **Note:** These plugins are designed specifically for Stream's projects and workflows. They aren't > intended for general-purpose use and may include Stream-specific configurations and conventions. -## Overview - -This repository contains reusable Gradle convention plugins that encapsulate common build logic, -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 +- **`io.getstream.android.library`** - Android library modules +- **`io.getstream.android.application`** - Android application modules +- **`io.getstream.android.test`** - Android test modules +- **`io.getstream.java.library`** - Java/Kotlin JVM library modules +- **`io.getstream.java.platform`** - Java/Kotlin JVM platform modules ## Usage -### 1. Root Project Configuration +### Root Project Setup Apply the root plugin in your root `build.gradle.kts`: @@ -55,22 +51,61 @@ streamProject { // Additional Sonar coverage exclusion patterns for file paths (default: empty) sonarCoverageExclusions = listOf("**/io/getstream/some/package/**") } + + publishing { + // Required: Description for all published artifacts + description = "Magical Stream SDK" + + // Optional: Override artifact IDs for specific modules + moduleArtifactIdOverrides = mapOf( + "my-module" to "custom-artifact-id" + ) + } } ``` -### 2. Module Configuration +### Module Setup Apply the appropriate plugin to each module: ```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") + // or: id("io.getstream.android.application") + // or: id("io.getstream.android.test") + // or: id("io.getstream.java.library") + // or: id("io.getstream.java.platform") } ``` +Library and platform plugins automatically configure Maven publishing. + +## Versioning + +Version is read from the project version. Can be set, for example, by adding it to +`gradle.properties`: + +```properties +version=1.0.0 +``` + +All published modules use this version. For snapshot builds, set the `SNAPSHOT` environment +variable: + +```bash +SNAPSHOT=true ./gradlew publish +``` + +This produces timestamped snapshot versions: `1.0.0-yyyyMMddHHmm-SNAPSHOT` + +## Publishing + +Published artifacts use: + +- **Group ID**: `io.getstream` +- **Artifact ID**: Module name (or override via `moduleArtifactIdOverrides`) +- **Version**: From `gradle.properties` + ## License See [LICENSE](LICENSE) file for details. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 98ea927..dab829b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,21 +3,23 @@ agp = "8.11.1" kotlin = "2.0.21" detekt = "1.23.8" spotless = "8.0.0" -kotlinDokka = "2.0.0" +kotlinDokka = "1.9.20" mavenPublish = "0.34.0" sonarqube = "6.0.1.5171" kover = "0.9.3" [libraries] android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } +dokka-gradle-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "kotlinDokka" } kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +kover-gradle-plugin = { group = "org.jetbrains.kotlinx", name = "kover-gradle-plugin", version.ref = "kover" } +maven-publish-gradle-plugin = { group = "com.vanniktech", name = "gradle-maven-publish-plugin", version.ref = "mavenPublish" } spotless-gradle-plugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" } sonarqube-gradle-plugin = { group = "org.sonarsource.scanner.gradle", name = "sonarqube-gradle-plugin", version.ref = "sonarqube" } -kover-gradle-plugin = { group = "org.jetbrains.kotlinx", name = "kover-gradle-plugin", version.ref = "kover" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -dokka = { id = "org.jetbrains.dokka-javadoc", version.ref = "kotlinDokka" } +dokka = { id = "org.jetbrains.dokka", version.ref = "kotlinDokka" } maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 40d56c6..2bd0e57 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -23,9 +23,11 @@ dependencies { compileOnly(gradleKotlinDsl()) compileOnly(libs.android.gradle.plugin) compileOnly(libs.kotlin.gradle.plugin) - implementation(libs.spotless.gradle.plugin) - implementation(libs.sonarqube.gradle.plugin) + implementation(libs.dokka.gradle.plugin) implementation(libs.kover.gradle.plugin) + implementation(libs.maven.publish.gradle.plugin) + implementation(libs.sonarqube.gradle.plugin) + implementation(libs.spotless.gradle.plugin) } val repoId = "GetStream/stream-build-conventions-android" @@ -72,12 +74,19 @@ gradlePlugin { description = "Convention plugin for Stream Java/Kotlin JVM library modules" tags = listOf("java", "library", "convention", "stream", "kotlin") } + create("javaPlatform") { + id = "io.getstream.java.platform" + implementationClass = "io.getstream.android.JavaPlatformConventionPlugin" + displayName = "Stream Java Platform Convention Plugin" + description = "Convention plugin for Stream Java/Kotlin JVM platform modules" + tags = listOf("java", "platform", "convention", "stream", "kotlin") + } } } mavenPublishing { publishToMavenCentral(automaticRelease = true) - configure(GradlePlugin(javadocJar = JavadocJar.Javadoc(), sourcesJar = true)) + configure(GradlePlugin(javadocJar = JavadocJar.Dokka("dokkaJavadoc"), sourcesJar = true)) pom { name.set("Stream Build Conventions") diff --git a/plugin/src/main/kotlin/io/getstream/android/GradleUtils.kt b/plugin/src/main/kotlin/io/getstream/android/GradleUtils.kt new file mode 100644 index 0000000..254e6b3 --- /dev/null +++ b/plugin/src/main/kotlin/io/getstream/android/GradleUtils.kt @@ -0,0 +1,26 @@ +/* + * 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 org.gradle.api.Task +import org.gradle.api.tasks.TaskContainer +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register + +internal inline fun TaskContainer.findOrRegister( + name: String, + noinline configuration: T.() -> Unit, +) = findByName(name)?.let { named(name) } ?: register(name, configuration) diff --git a/plugin/src/main/kotlin/io/getstream/android/StreamConventionExtensions.kt b/plugin/src/main/kotlin/io/getstream/android/StreamConventionExtensions.kt index b89676d..a0d0a06 100644 --- a/plugin/src/main/kotlin/io/getstream/android/StreamConventionExtensions.kt +++ b/plugin/src/main/kotlin/io/getstream/android/StreamConventionExtensions.kt @@ -16,6 +16,7 @@ package io.getstream.android import io.getstream.android.coverage.CoverageOptions +import io.getstream.android.publishing.PublishingOptions import io.getstream.android.spotless.SpotlessOptions import javax.inject.Inject import org.gradle.api.Action @@ -50,6 +51,12 @@ constructor(project: Project, objects: ObjectFactory) { /** Configure code coverage */ fun coverage(action: Action) = action.execute(coverage) + + /** Publishing configuration */ + val publishing: PublishingOptions = objects.newInstance() + + /** Configure publishing */ + fun publishing(action: Action) = action.execute(publishing) } internal fun Project.createProjectExtension(): StreamProjectExtension = diff --git a/plugin/src/main/kotlin/io/getstream/android/StreamConventionPlugins.kt b/plugin/src/main/kotlin/io/getstream/android/StreamConventionPlugins.kt index 3f3f6e0..b1eab67 100644 --- a/plugin/src/main/kotlin/io/getstream/android/StreamConventionPlugins.kt +++ b/plugin/src/main/kotlin/io/getstream/android/StreamConventionPlugins.kt @@ -20,6 +20,7 @@ import com.android.build.api.dsl.LibraryExtension import com.android.build.api.dsl.TestExtension import io.getstream.android.coverage.configureCoverageModule import io.getstream.android.coverage.configureCoverageRoot +import io.getstream.android.publishing.configurePublishing import io.getstream.android.spotless.configureSpotless import org.gradle.api.Plugin import org.gradle.api.Project @@ -63,6 +64,7 @@ class AndroidLibraryConventionPlugin : Plugin { configureKotlin() configureSpotless() configureCoverageModule() + configurePublishing() } } } @@ -88,6 +90,20 @@ class JavaLibraryConventionPlugin : Plugin { configureKotlin() configureSpotless() configureCoverageModule() + configurePublishing() + } + } +} + +class JavaPlatformConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("java-platform") + + configureJava() + configureKotlin() + configureSpotless() + configurePublishing() } } } diff --git a/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingConfiguration.kt b/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingConfiguration.kt new file mode 100644 index 0000000..2f65261 --- /dev/null +++ b/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingConfiguration.kt @@ -0,0 +1,195 @@ +/* + * 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.publishing + +import com.vanniktech.maven.publish.AndroidSingleVariantLibrary +import com.vanniktech.maven.publish.JavaPlatform +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.Platform +import io.getstream.android.StreamProjectExtension +import io.getstream.android.findOrRegister +import io.getstream.android.requireStreamProjectExtension +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.findByType + +private const val groupId = "io.getstream" + +internal fun Project.configurePublishing() { + val projectExtension = requireStreamProjectExtension() + val artifactId = getArtifactId(projectExtension.publishing) + + pluginManager.apply("com.vanniktech.maven.publish") + pluginManager.apply("org.jetbrains.dokka") + + this.group = groupId + this.version = computeVersion() + + pluginManager.withPlugin("com.vanniktech.maven.publish") { + extensions.configure { + publishToMavenCentral(automaticRelease = true) + + coordinates(groupId = groupId, artifactId = artifactId, version = version.toString()) + + configure(computeArtifactPlatform()) + + configurePom(projectExtension, artifactId) + } + } + + rootProject.registerPrintAllArtifactsTask() +} + +// Get the overridden artifact ID if present or use the project name as default +private fun Project.getArtifactId(publishing: PublishingOptions): String = + publishing.moduleArtifactIdOverrides.get().getOrDefault(name, name) + +private fun Project.computeVersion(): String { + val isSnapshot = System.getenv("SNAPSHOT")?.toBoolean() == true + return if (isSnapshot) { + val timestamp = + DateTimeFormatter.ofPattern("yyyyMMddHHmm") + .withZone(ZoneOffset.UTC) + .format(Instant.now()) + + "$version-$timestamp-SNAPSHOT" + } else version.toString() +} + +// Discover the artifact platform based on the applied plugins +private fun Project.computeArtifactPlatform(): Platform = + when { + pluginManager.hasPlugin("com.android.library") -> { + AndroidSingleVariantLibrary( + variant = "release", + sourcesJar = true, + publishJavadocJar = true, + ) + } + + pluginManager.hasPlugin("java-library") -> { + if (!pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")) { + throw IllegalStateException( + "The 'kotlin-jvm' plugin must be applied before the " + + "'stream.java.library' plugin" + ) + } + + KotlinJvm(sourcesJar = true, javadocJar = JavadocJar.Dokka("dokkaJavadoc")) + } + + pluginManager.hasPlugin("java-platform") -> { + JavaPlatform() + } + + else -> + error( + "Unsupported project type for publishing. The project must apply either " + + "'com.android.library', 'java-library' or 'java-platform' plugin." + ) + } + +private fun MavenPublishBaseExtension.configurePom( + projectExtension: StreamProjectExtension, + artifactId: String, +) { + pom { + name.set(artifactId) + description.set(projectExtension.publishing.description) + url.set(projectExtension.repositoryName.map { "https://github.com/GetStream/$it" }) + + licenses { + license { + name.set("Stream License") + url.set( + projectExtension.repositoryName.map { + "https://github.com/GetStream/$it/blob/main/LICENSE" + } + ) + } + } + + developers { + developer { + id.set("aleksandar-apostolov") + name.set("Aleksandar Apostolov") + email.set("aleksandar.apostolov@getstream.io") + } + developer { + id.set("VelikovPetar") + name.set("Petar Velikov") + email.set("petar.velikov@getstream.io") + } + developer { + id.set("andremion") + name.set("André Mion") + email.set("andre.rego@getstream.io") + } + developer { + id.set("rahul-lohra") + name.set("Rahul Kumar Lohra") + email.set("rahul.lohra@getstream.io") + } + developer { + id.set("PratimMallick") + name.set("Pratim Mallick") + email.set("pratim.mallick@getstream.io") + } + developer { + id.set("gpunto") + name.set("Gianmarco David") + email.set("gianmarco.david@getstream.io") + } + } + + scm { + url.set(projectExtension.repositoryName.map { "https://github.com/GetStream/$it" }) + connection.set( + projectExtension.repositoryName.map { "scm:git:git://github.com/GetStream/$it.git" } + ) + developerConnection.set( + projectExtension.repositoryName.map { "scm:git:ssh://github.com:GetStream/$it.git" } + ) + } + } +} + +private fun Project.registerPrintAllArtifactsTask() { + tasks.findOrRegister("printAllArtifacts") { + group = "publishing" + description = "Prints all artifacts that will be published" + + doLast { + subprojects.forEach { subproject -> + subproject.plugins.withId("com.vanniktech.maven.publish") { + subproject.extensions + .findByType() + ?.publications + ?.filterIsInstance() + ?.forEach { println("${it.groupId}:${it.artifactId}:${it.version}") } + } + } + } + } +} diff --git a/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingOptions.kt b/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingOptions.kt new file mode 100644 index 0000000..d3161a0 --- /dev/null +++ b/plugin/src/main/kotlin/io/getstream/android/publishing/PublishingOptions.kt @@ -0,0 +1,35 @@ +/* + * 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.publishing + +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property + +abstract class PublishingOptions @Inject constructor(objects: ObjectFactory) { + /** Description of the project. Used for published artifacts. */ + val description: Property = objects.property() + + /** + * Map of module names to custom artifact IDs. Use this to override the default artifact ID for + * specific modules. + */ + val moduleArtifactIdOverrides: MapProperty = + objects.mapProperty().convention(emptyMap()) +} diff --git a/plugin/src/main/kotlin/io/getstream/android/spotless/SpotlessConfiguration.kt b/plugin/src/main/kotlin/io/getstream/android/spotless/SpotlessConfiguration.kt index 9604863..8be35f9 100644 --- a/plugin/src/main/kotlin/io/getstream/android/spotless/SpotlessConfiguration.kt +++ b/plugin/src/main/kotlin/io/getstream/android/spotless/SpotlessConfiguration.kt @@ -17,13 +17,10 @@ package io.getstream.android.spotless import com.diffplug.gradle.spotless.SpotlessExtension import io.getstream.android.GenerateLicenseFileTask +import io.getstream.android.findOrRegister import io.getstream.android.requireStreamProjectExtension import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.TaskContainer import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.register private const val KTLINT_VERSION = "0.50.0" @@ -136,8 +133,3 @@ private fun Project.registerLicenseGenerationTask( val outputDir = rootProject.layout.buildDirectory.dir("stream-spotless-config") outputFile.set(outputDir.map { it.file(fileName) }) } - -private inline fun TaskContainer.findOrRegister( - name: String, - noinline configuration: T.() -> Unit, -) = findByName(name)?.let { named(name) } ?: register(name, configuration)