From d462822bebc7e19f8241bed5388875086f42451f Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Wed, 10 May 2023 19:31:22 +0300 Subject: [PATCH] Critical updates to make grade release process work (#218) ### What's done: - kotlinx.serialization is updated to 1.5.0 - kotlin is updated to 1.8.21 - release script is converted into the plugin (as suggested in https://kotlinlang.org/docs/multiplatform-library.html#set-up-the-environment). This is needed because new gradle became too strict and needs an explicit dependency on signing task now - Adding a hack for Gradle 8 with an explicit dependency of sign tasks on publish tasks --- .github/workflows/release.yml | 18 +- LICENSE | 2 +- build.gradle.kts | 3 +- buildSrc/build.gradle.kts | 2 +- .../main/kotlin/com/akuleshov7/Versions.kt | 4 +- .../buildutils/PublishingConfiguration.kt | 146 --------------- gradle/plugins/build.gradle.kts | 14 ++ .../buildutils/PublishingConfiguration.kt | 172 ++++++++++++++++++ .../publishing-configuration.gradle.kts | 37 ++++ ktoml-core/build.gradle.kts | 18 +- ktoml-file/build.gradle.kts | 17 +- ktoml-source/build.gradle.kts | 16 +- settings.gradle.kts | 2 + 13 files changed, 283 insertions(+), 168 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt create mode 100644 gradle/plugins/build.gradle.kts create mode 100644 gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt create mode 100644 gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/publishing-configuration.gradle.kts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f74cfc97..cfe7bab6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,8 +8,8 @@ on: env: PGP_SEC: ${{ secrets.PGP_SEC }} PGP_PASSWORD: ${{ secrets.PGP_PASSWORD }} - OSSRH_USERNAME: ${{ secrets.SONATYPE_USER }} - OSSRH_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USER: ${{ secrets.SONATYPE_USER }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} jobs: release: @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] + os: [ macos-latest ] steps: - name: Checkout uses: actions/checkout@v2.7.0 @@ -37,12 +37,12 @@ jobs: - name: gradle release from tag # if workflow is triggered after push of a tag, deploy full release if: ${{ startsWith(github.ref, 'refs/tags/') }} - run: ./gradlew --build-cache -Prelease publishToSonatype - - name: gradle snapshot release - # if workflow is triggered after push to a branch, deploy snapshot - if: ${{ startsWith(github.ref, 'refs/heads/') }} - run: ./gradlew --build-cache -Prelease -Preckon.stage=snapshot publishToSonatype - shell: bash + run: ./gradlew + --console=rich + -Prelease + -PgprUser=${{ github.actor }} + -PgprKey=${{ secrets.GITHUB_TOKEN }} + publishToSonatype - name: Status git after if: ${{ always() }} run: git status diff --git a/LICENSE b/LICENSE index 2e3f03ec..550d7c05 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Andrey Kuleshov +Copyright (c) 2023 Andrey Kuleshov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build.gradle.kts b/build.gradle.kts index 7a6cedc8..bd3ec41b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ import com.akuleshov7.buildutils.* plugins { kotlin("multiplatform") apply false kotlin("plugin.serialization") version Versions.KOTLIN apply false + id("com.akuleshov7.buildutils.publishing-configuration") } configureVersioning() @@ -11,6 +12,7 @@ allprojects { repositories { mavenCentral() } + configureDiktat() configureDetekt() @@ -22,4 +24,3 @@ allprojects { createDetektTask() installGitHooks() -configurePublishing() diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 13e33762..53e43da4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21") implementation("org.cqfn.diktat:diktat-gradle-plugin:1.2.5") - implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0") + implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.22.0") implementation("io.github.gradle-nexus:publish-plugin:1.1.0") implementation("org.ajoberstar.reckon:reckon-gradle:0.13.0") implementation("org.ajoberstar.grgit:grgit-core:4.1.0") diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt index ba71fe0f..a9f0bba6 100644 --- a/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt +++ b/buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt @@ -4,8 +4,8 @@ "PACKAGE_NAME_INCORRECT_PATH") object Versions { - const val KOTLIN = "1.8.0" + const val KOTLIN = "1.8.21" const val JUNIT = "5.7.1" const val OKIO = "3.1.0" - const val SERIALIZATION = "1.4.1" + const val SERIALIZATION = "1.5.0" } diff --git a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt b/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt deleted file mode 100644 index 856aeebe..00000000 --- a/buildSrc/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Gradle configuration for making a release to maven central - */ - -@file:Suppress("FILE_WILDCARD_IMPORTS", "TOO_LONG_FUNCTION") - -package com.akuleshov7.buildutils - -import io.github.gradlenexus.publishplugin.NexusPublishExtension -import io.github.gradlenexus.publishplugin.NexusPublishPlugin -import org.gradle.api.Project -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.publish.maven.plugins.MavenPublishPlugin -import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven -import org.gradle.api.tasks.bundling.Jar -import org.gradle.kotlin.dsl.* -import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform -import org.gradle.plugins.signing.SigningExtension -import org.gradle.plugins.signing.SigningPlugin - -/** - * configuration for publishing to Nexus (will be used in build.gradle script) - */ -fun Project.configurePublishing() { - // If present, set properties from env variables. If any are absent, release will fail. - System.getenv("OSSRH_USERNAME")?.let { - extra.set("sonatypeUsername", it) - } - System.getenv("OSSRH_PASSWORD")?.let { - extra.set("sonatypePassword", it) - } - System.getenv("PGP_SEC")?.let { - extra.set("signingKey", it) - } - System.getenv("PGP_PASSWORD")?.let { - extra.set("signingPassword", it) - } - - if (this == rootProject) { - apply() - if (hasProperty("sonatypeUsername")) { - configureNexusPublishing() - } - } - - apply() - apply() - - configurePublications() - - if (hasProperty("signingKey")) { - configureSigning() - } - - // https://kotlinlang.org/docs/mpp-publish-lib.html#avoid-duplicate-publications - // `configureNexusPublishing` adds sonatype publication tasks inside `afterEvaluate`. - afterEvaluate { - val publicationsFromMainHost = listOf( - "jvm", - "js", - "linuxX64", - "mingwX64", - "kotlinMultiplatform", - "metadata" - ) - configure { - publications.matching { it.name in publicationsFromMainHost }.all { - val targetPublication = this@all - tasks.withType() - .matching { it.publication == targetPublication } - .configureEach { - onlyIf { - // main publishing CI job is executed on Linux host - DefaultNativePlatform.getCurrentOperatingSystem().isLinux.apply { - if (!this) { - logger.lifecycle("Publication ${(it as AbstractPublishToMaven).publication.name} is skipped on current host") - } - } - } - } - } - } - } -} - -private fun Project.configurePublications() { - val dokkaJar: Jar = tasks.create("dokkaJar") { - group = "documentation" - archiveClassifier.set("javadoc") - from(tasks.findByName("dokkaHtml")) - } - configure { - repositories { - mavenLocal() - } - publications.withType().forEach { publication -> - publication.artifact(dokkaJar) - publication.pom { - name.set(project.name) - description.set(project.description ?: project.name) - url.set("https://github.com/akuleshov7/ktoml") - licenses { - license { - name.set("MIT License") - url.set("https://github.com/akuleshov7/ktoml/blob/main/LICENSE") - distribution.set("repo") - } - } - developers { - developer { - id.set("akuleshov7") - name.set("Andrey Kuleshov") - email.set("andrewkuleshov7@gmail.com") - } - } - scm { - url.set("https://github.com/akuleshov7/ktoml") - connection.set("scm:git:git://github.com/akuleshov7/ktoml.git") - } - } - } - } -} - -private fun Project.configureSigning() { - configure { - useInMemoryPgpKeys(property("signingKey") as String?, property("signingPassword") as String?) - logger.lifecycle("The following publications are getting signed: ${extensions.getByType().publications.map { it.name }}") - sign(*extensions.getByType().publications.toTypedArray()) - } -} - -private fun Project.configureNexusPublishing() { - configure { - repositories { - sonatype { - // only for users registered in Sonatype after 24 Feb 2021 - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - username.set(property("sonatypeUsername") as String) - password.set(property("sonatypePassword") as String) - } - } - } -} diff --git a/gradle/plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts new file mode 100644 index 00000000..946352aa --- /dev/null +++ b/gradle/plugins/build.gradle.kts @@ -0,0 +1,14 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation("io.github.gradle-nexus:publish-plugin:1.1.0") +} diff --git a/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt b/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt new file mode 100644 index 00000000..f31d0ba6 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/PublishingConfiguration.kt @@ -0,0 +1,172 @@ +/** + * Publishing configuration file. + */ + +@file:Suppress( + "MISSING_KDOC_TOP_LEVEL", + "MISSING_KDOC_ON_FUNCTION", +) + +package com.akuleshov7.buildutils + +import io.github.gradlenexus.publishplugin.NexusPublishExtension +import org.gradle.api.Named +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.Jar +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutput.Style.Failure +import org.gradle.internal.logging.text.StyledTextOutput.Style.Success +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.support.serviceOf +import org.gradle.kotlin.dsl.withType +import org.gradle.plugins.signing.SigningExtension + +/** + * Enables signing of the artifacts if the `signingKey` project property is set. + * + * Should be explicitly called after each custom `publishing {}` section. + */ +fun Project.configureSigning() { + if (hasProperty("signingKey")) { + /* + * GitHub Actions. + */ + configureSigningCommon { + useInMemoryPgpKeys(property("signingKey") as String?, findProperty("signingPassword") as String?) + } + } else if ( + hasProperties( + "signing.keyId", + "signing.password", + "signing.secretKeyRingFile", + ) + ) { + /*- + * Pure-Java signing mechanism via `org.bouncycastle.bcpg`. + * + * Requires an 8-digit (short form) PGP key id and a present `~/.gnupg/secring.gpg` + * (for gpg 2.1, run + * `gpg --keyring secring.gpg --export-secret-keys >~/.gnupg/secring.gpg` + * to generate one). + */ + configureSigningCommon() + } else if (hasProperty("signing.gnupg.keyName")) { + /*- + * Use an external `gpg` executable. + * + * On Windows, you may need to additionally specify the path to `gpg` via + * `signing.gnupg.executable`. + */ + configureSigningCommon { + useGpgCmd() + } + } + + // a hack for Gradle 8 (which is now requiring explicit dependency on sequential tasks with a specific order) + // Reason: Task ':ktoml-core:publishKotlinMultiplatformPublicationToSonatypeRepository' uses this output of task + // ':ktoml-core:signMavenPublication' without declaring an explicit or implicit dependency. This can lead to incorrect + // results being produced, depending on what order the tasks are executed. + val signingTasks = tasks.filter { it.name.startsWith("sign") && it.name.endsWith("Publication") } + tasks.matching { it.name.startsWith("publish") }.configureEach { + signingTasks.forEach { + mustRunAfter(it.name) + } + } +} + +@Suppress("TOO_LONG_FUNCTION") +internal fun Project.configurePublications() { + val dokkaJar: Jar = tasks.create("dokkaJar") { + group = "documentation" + archiveClassifier.set("javadoc") + from(tasks.findByName("dokkaHtml")) + } + configure { + repositories { + mavenLocal() + } + publications.withType().configureEach { + /* + * The content of this section will get executed only if + * a particular module has a `publishing {}` section. + */ + this.artifact(dokkaJar) + this.pom { + name.set(project.name) + description.set(project.description ?: project.name) + url.set("https://github.com/akuleshov7/ktoml") + licenses { + license { + name.set("MIT License") + url.set("https://github.com/akuleshov7/ktoml/blob/main/LICENSE") + distribution.set("repo") + } + } + developers { + developer { + id.set("akuleshov7") + name.set("Andrey Kuleshov") + email.set("andrewkuleshov7@gmail.com") + } + } + scm { + url.set("https://github.com/akuleshov7/ktoml") + connection.set("scm:git:git://github.com/akuleshov7/ktoml.git") + } + } + } + } +} + +internal fun Project.configureNexusPublishing() { + configure { + repositories { + sonatype { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + username.set(property("sonatypeUsername") as String) + password.set(property("sonatypePassword") as String) + } + } + } +} + +/** + * @param useKeys the block which configures the PGP keys. Use either + * [SigningExtension.useInMemoryPgpKeys], [SigningExtension.useGpgCmd], or an + * empty lambda. + * @see SigningExtension.useInMemoryPgpKeys + * @see SigningExtension.useGpgCmd + */ +private fun Project.configureSigningCommon(useKeys: SigningExtension.() -> Unit = {}) { + configure { + useKeys() + val publications = extensions.getByType().publications + val publicationCount = publications.size + val message = "The following $publicationCount publication(s) are getting signed: ${publications.map(Named::getName)}" + val style = when (publicationCount) { + 0 -> Failure + else -> Success + } + styledOut(logCategory = "signing").style(style).println(message) + sign(*publications.toTypedArray()) + } +} + +private fun Project.styledOut(logCategory: String): StyledTextOutput = + serviceOf().create(logCategory) + +/** + * Determines if this project has all the given properties. + * + * @param propertyNames the names of the properties to locate. + * @return `true` if this project has all the given properties, `false` otherwise. + * @see Project.hasProperty + */ +private fun Project.hasProperties(vararg propertyNames: String): Boolean = + propertyNames.asSequence().all(this::hasProperty) diff --git a/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/publishing-configuration.gradle.kts b/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/publishing-configuration.gradle.kts new file mode 100644 index 00000000..b070a813 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/akuleshov7/buildutils/publishing-configuration.gradle.kts @@ -0,0 +1,37 @@ +package com.akuleshov7.buildutils + +import io.github.gradlenexus.publishplugin.NexusPublishPlugin +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.extra + +plugins { + `maven-publish` + signing +} + +run { + // If present, set properties from env variables. If any are absent, release will fail. + System.getenv("SONATYPE_USER")?.let { + extra.set("sonatypeUsername", it) + } + System.getenv("SONATYPE_PASSWORD")?.let { + extra.set("sonatypePassword", it) + } + System.getenv("PGP_SEC")?.let { + extra.set("signingKey", it) + } + System.getenv("PGP_PASSWORD")?.let { + extra.set("signingPassword", it) + } + + if (project.path == rootProject.path) { + apply() + if (hasProperty("sonatypeUsername")) { + configureNexusPublishing() + } + } +} + +run { + configurePublications() +} diff --git a/ktoml-core/build.gradle.kts b/ktoml-core/build.gradle.kts index a1834d5b..ee42e357 100644 --- a/ktoml-core/build.gradle.kts +++ b/ktoml-core/build.gradle.kts @@ -1,11 +1,11 @@ -import com.akuleshov7.buildutils.configurePublishing - +import com.akuleshov7.buildutils.configureSigning import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest plugins { kotlin("multiplatform") kotlin("plugin.serialization") + id("com.akuleshov7.buildutils.publishing-configuration") } kotlin { @@ -20,7 +20,6 @@ kotlin { nodejs() } - // building jvm task only on windows jvm { compilations.all { kotlinOptions.jvmTarget = "1.8" @@ -73,7 +72,18 @@ kotlin { } } -configurePublishing() +publishing { + publications { + create("maven") { + groupId = "com.akuleshov7" + artifactId = "ktoml-core" + version = version + from(components["kotlin"]) + } + } +} + +configureSigning() tasks.withType { useJUnitPlatform() diff --git a/ktoml-file/build.gradle.kts b/ktoml-file/build.gradle.kts index a29c24ab..9d61fc4b 100644 --- a/ktoml-file/build.gradle.kts +++ b/ktoml-file/build.gradle.kts @@ -1,9 +1,11 @@ -import com.akuleshov7.buildutils.configurePublishing +import com.akuleshov7.buildutils.configureSigning + import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest plugins { kotlin("multiplatform") kotlin("plugin.serialization") + id("com.akuleshov7.buildutils.publishing-configuration") } kotlin { @@ -60,7 +62,18 @@ kotlin { } } -configurePublishing() +publishing { + publications { + create("maven") { + groupId = "com.akuleshov7" + artifactId = "ktoml-file" + version = version + from(components["kotlin"]) + } + } +} + +configureSigning() tasks.withType { useJUnitPlatform() diff --git a/ktoml-source/build.gradle.kts b/ktoml-source/build.gradle.kts index 2c0f37a4..d01eb898 100644 --- a/ktoml-source/build.gradle.kts +++ b/ktoml-source/build.gradle.kts @@ -1,9 +1,10 @@ -import com.akuleshov7.buildutils.configurePublishing +import com.akuleshov7.buildutils.configureSigning import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest plugins { kotlin("multiplatform") kotlin("plugin.serialization") + id("com.akuleshov7.buildutils.publishing-configuration") } kotlin { @@ -62,7 +63,18 @@ kotlin { } } -configurePublishing() +publishing { + publications { + create("maven") { + groupId = "com.akuleshov7" + artifactId = "ktoml-source" + version = version + from(components["kotlin"]) + } + } +} + +configureSigning() tasks.withType { useJUnitPlatform() diff --git a/settings.gradle.kts b/settings.gradle.kts index 471d9adc..303ba684 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,6 @@ rootProject.name = "ktoml" + +includeBuild("gradle/plugins") include("ktoml-core") include("ktoml-file") include("ktoml-source")