From 0c5101b8933ff4b3f65031d295f0ad91f8ffda53 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Fri, 9 Feb 2024 16:34:09 +0300 Subject: [PATCH] Adding release configurations --- .github/workflows/publish-macosArm64.yml | 45 ----- .github/workflows/release.yml | 92 +++------- .gitignore | 2 + build.gradle.kts | 146 +-------------- diktat-analysis.yml | 114 ++++++++++++ gradle/plugins/build.gradle.kts | 14 ++ .../buildutils/PublishingConfiguration.kt | 168 ++++++++++++++++++ .../publishing-configuration.gradle.kts | 37 ++++ settings.gradle.kts | 2 + 9 files changed, 371 insertions(+), 249 deletions(-) delete mode 100644 .github/workflows/publish-macosArm64.yml create mode 100644 diktat-analysis.yml create mode 100644 gradle/plugins/build.gradle.kts create mode 100644 gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/PublishingConfiguration.kt create mode 100644 gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/publishing-configuration.gradle.kts diff --git a/.github/workflows/publish-macosArm64.yml b/.github/workflows/publish-macosArm64.yml deleted file mode 100644 index 8614526..0000000 --- a/.github/workflows/publish-macosArm64.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: 'Publish macosArm64' - -on: - push: - tags: - - 'v*' - workflow_dispatch: - -env: - GRADLE_OPTS: -Dorg.gradle.daemon=true -Dorg.gradle.parallel=true -Dorg.gradle.welcome=never - GPG_SEC: ${{ secrets.PGP_SEC }} - GPG_PASSWORD: ${{ secrets.PGP_PASSWORD }} - -jobs: - publish: - name: 'Publish macosArm64' - runs-on: [self-hosted, macOS, ARM64] - steps: - - uses: actions/checkout@v4 - with: - # Fetch Git tags, so that semantic version can be calculated. - # Alternatively, run `git fetch --prune --unshallow --tags` as the - # next step, see - # https://github.com/actions/checkout/issues/206#issuecomment-607496604. - fetch-depth: 0 - # Assumption that Java 17 is already present on self-hosted runner - - name: 'Run unit tests' - id: test - uses: gradle/gradle-build-action@v3 - with: - gradle-version: wrapper - arguments: | - macosArm64Test - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: 'Publish a release (macosArm64 target)' - id: publish-macosArm64 - uses: gradle/gradle-build-action@v3 - with: - gradle-version: wrapper - arguments: | - publishMacosArm64PublicationToGitHubRepository - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37e34f9..5b23513 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,83 +7,49 @@ on: env: GRADLE_OPTS: -Dorg.gradle.daemon=true -Dorg.gradle.parallel=true -Dorg.gradle.welcome=never - GPG_SEC: ${{ secrets.PGP_SEC }} - GPG_PASSWORD: ${{ secrets.PGP_PASSWORD }} + PGP_SEC: ${{ secrets.PGP_SEC }} + PGP_PASSWORD: ${{ secrets.PGP_PASSWORD }} + SONATYPE_USER: ${{ secrets.SONATYPE_USER }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: release: - name: 'Release' + name: Build release runs-on: ${{ matrix.os }} - strategy: - fail-fast: false matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] - + os: [ macos-latest ] steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: - # Fetch Git tags, so that semantic version can be calculated. - # Alternatively, run `git fetch --prune --unshallow --tags` as the - # next step, see - # https://github.com/actions/checkout/issues/206#issuecomment-607496604. + # release workflow should have access to all tags fetch-depth: 0 - - - name: 'Set up Java 17' - uses: actions/setup-java@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v3 with: - java-version: 17 + java-version: 11 distribution: zulu - java-package: jdk+fx - - - name: 'Cache ~/.konan' - id: cache-konan - uses: actions/cache@v4 - with: - path: | - ~/.konan - key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts', '**/gradle-wrapper.properties') }}-release - restore-keys: | - ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts', '**/gradle-wrapper.properties') }}- - ${{ runner.os }}-konan- - - - name: 'Publish a release (Linux)' - id: publish-linux - if: ${{ runner.os == 'Linux' }} - uses: gradle/gradle-build-action@v3 - with: - gradle-version: wrapper - arguments: | - build - publishJvmPublicationToGitHubRepository - publishKotlinMultiplatformPublicationToGitHubRepository - publishLinuxX64PublicationToGitHubRepository - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: 'Publish a release (Windows)' - id: publish-windows - if: ${{ runner.os == 'Windows' }} - uses: gradle/gradle-build-action@v3 + - name: Status git before + run: git status + - uses: burrunan/gradle-cache-action@v1 with: gradle-version: wrapper - arguments: | - build - publishMingwX64PublicationToGitHubRepository - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Until https://github.com/burrunan/gradle-cache-action/issues/42 is addressed, gradle should be run as a separate step + - 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 + --console=rich + -Prelease + -PgprUser=${{ github.actor }} + -PgprKey=${{ secrets.GITHUB_TOKEN }} + publishToSonatype + - name: Status git after + if: ${{ always() }} + run: git status - - name: 'Publish a release (Mac OS X)' - id: publish-macosx - if: ${{ runner.os == 'macOS' }} - uses: gradle/gradle-build-action@v3 - with: - gradle-version: wrapper - arguments: | - build - publishMacosX64PublicationToGitHubRepository - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} github_release: needs: release diff --git a/.gitignore b/.gitignore index 8216480..92afe76 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /.run/ /.settings/ /build/ +/gradle/plugins/.gradle/ +/gradle/plugins/build/ diff --git a/build.gradle.kts b/build.gradle.kts index d678cfa..1179f74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,10 @@ ) import com.saveourtool.buildutils.configureDetekt +import com.saveourtool.buildutils.configureSigning import org.ajoberstar.reckon.gradle.ReckonCreateTagTask import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL 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.support.serviceOf import org.jetbrains.kotlin.gradle.tasks.KotlinTest @@ -16,17 +15,17 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinTest plugins { kotlin("multiplatform") eclipse - `maven-publish` - signing id("org.jetbrains.dokka") version "1.9.10" - id("io.github.gradle-nexus.publish-plugin") version "1.3.0" id("io.gitlab.arturbosch.detekt") - id("org.cqfn.diktat.diktat-gradle-plugin") version "1.2.5" + id("com.saveourtool.diktat") version "2.0.0" + id("com.saveourtool.buildutils.publishing-configuration") } group = "com.saveourtool" description = "A set of extensions to Okio" +configureSigning() + repositories { mavenCentral() } @@ -92,142 +91,7 @@ tasks.withType { } configureDetekt() -configurePublishing() - -fun Project.configurePublishing() { - configureGitHubPublishing() - configurePublications() - configureSigning() -} - -fun Project.configureGitHubPublishing() = - publishing { - repositories { - maven { - name = "GitHub" - url = uri("https://maven.pkg.github.com/saveourtool/okio-extras") - credentials { - username = findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") - password = findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") - } - } - } - } - -fun Project.configurePublications() { - val dokkaJar = tasks.create("dokkaJar") { - group = "documentation" - archiveClassifier.set("javadoc") - from(tasks.findByName("dokkaHtml")) - } - - configure { - publications.withType().configureEach { - this.artifact(dokkaJar) - this.pom { - val project = this@configurePublications - - name.set(project.name) - description.set(project.description ?: project.name) - url.set("https://github.com/saveourtool/${project.name}") - licenses { - license { - name.set("MIT License") - url.set("https://opensource.org/license/MIT") - distribution.set("repo") - } - } - developers { - developer { - id.set("0x6675636b796f75676974687562") - name.set("Andrey Shcheglov") - email.set("shcheglov.av@phystech.edu") - } - } - scm { - url.set("https://github.com/saveourtool/${project.name}") - connection.set("scm:git:https://github.com/saveourtool/${project.name}.git") - developerConnection.set("scm:git:git@github.com:saveourtool/${project.name}.git") - } - } - } - } -} -/** - * Enables signing of the artifacts if the `signingKey` project property is set. - * - * Should be explicitly called after each custom `publishing {}` section. - */ -fun Project.configureSigning() { - System.getenv("GPG_SEC")?.let { - extra.set("signingKey", it) - } - System.getenv("GPG_PASSWORD")?.let { - extra.set("signingPassword", it) - } - - 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() - } - } -} - -/** - * @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 - */ -@Suppress( - "MaxLineLength", - "SpreadOperator", -) -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()) - } -} fun Project.styledOut(logCategory: String): StyledTextOutput = serviceOf().create(logCategory) diff --git a/diktat-analysis.yml b/diktat-analysis.yml new file mode 100644 index 0000000..24ebf15 --- /dev/null +++ b/diktat-analysis.yml @@ -0,0 +1,114 @@ +- name: DIKTAT_COMMON + enabled: true + configuration: + domainName: com.saveourtool.okio + kotlinVersion: 1.8 + srcDirectories: "main,nativeMain,commonNonJsMain" + testDirs: "test,nativeTest,commonTest,jvmTest,commonNonJsTest" +- name: AVOID_NULL_CHECKS + enabled: false +- name: ENUM_VALUE + enabled: true + configuration: + enumStyle: snakeCase +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true + configuration: + versionRegex: \d+\.\d+\.\d+[-.\w\d]* +- name: HEADER_MISSING_OR_WRONG_COPYRIGHT + enabled: true + configuration: + isCopyrightMandatory: false + copyrightText: '' +- name: FILE_IS_TOO_LONG + enabled: true + configuration: + maxSize: 450 + ignoreFolders: '' +- name: FILE_UNORDERED_IMPORTS + enabled: true + configuration: + useRecommendedImportsOrder: true +- name: FILE_WILDCARD_IMPORTS + enabled: true + configuration: + allowedWildcards: "kotlinx.serialization.*" +- name: BRACES_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + openBraceNewline: true + closeBraceNewline: true +- name: WRONG_INDENTATION + enabled: true + configuration: + # Is newline at the end of a file needed + newlineAtEnd: true + # If true: in parameter list when parameters are split by newline they are indented with two indentations instead of one + extendedIndentOfParameters: false + # If true: if first parameter in parameter list is on the same line as opening parenthesis, then other parameters can be aligned with it + alignedParameters: true + # If true: if expression is split by newline after operator like +/-/`*`, then the next line is indented with two indentations instead of one + extendedIndentAfterOperators: true + # If true: when dot qualified expression starts on a new line, this line will be indented with two indentations instead of one + extendedIndentBeforeDot: false + # The indentation size for each file + indentationSize: 4 + extendedIndentForExpressionBodies: true +- name: EMPTY_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + styleEmptyBlockWithNewline: true + allowEmptyBlocks: false +- name: LONG_LINE + enabled: true + configuration: + lineLength: 180 +- name: WRONG_NEWLINES + enabled: true + configuration: + maxParametersInOneLine: 2 +- name: TOO_MANY_CONSECUTIVE_SPACES + enabled: true + configuration: + maxSpaces: 1 + saveInitialFormattingForEnums: false +- name: LONG_NUMERICAL_VALUES_SEPARATED + enabled: true + configuration: + maxNumberLength: 5 + maxBlockLength: 3 +- name: WRONG_DECLARATIONS_ORDER + enabled: true + configuration: + sortEnum: true + sortProperty: true +- name: COMMENT_WHITE_SPACE + enabled: true + configuration: + maxSpacesBeforeComment: 2 + maxSpacesInComment: 1 +- name: TYPE_ALIAS + enabled: true + configuration: + typeReferenceLength: 25 +- name: TOO_LONG_FUNCTION + enabled: true + configuration: + maxFunctionLength: 35 # max length of function + isIncludeHeader: false # count function's header +- name: TOO_MANY_PARAMETERS + enabled: true + configuration: + maxParameterListSize: 5 +- name: NESTED_BLOCK + enabled: true + configuration: + maxNestedBlockQuantity: 4 +- name: TRAILING_COMMA + enabled: false + configuration: + valueArgument: true + valueParameter: true +- name: DEBUG_PRINT + enabled: true + ignoreAnnotated: [ Test ] diff --git a/gradle/plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts new file mode 100644 index 0000000..946352a --- /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/saveourtool/buildutils/PublishingConfiguration.kt b/gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/PublishingConfiguration.kt new file mode 100644 index 0000000..497a6b4 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/PublishingConfiguration.kt @@ -0,0 +1,168 @@ +/** + * Publishing configuration file. + */ + +@file:Suppress( + "MISSING_KDOC_TOP_LEVEL", + "MISSING_KDOC_ON_FUNCTION", +) + +package com.saveourtool.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() + } + } + + 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/saveourtool/okio-extras") + licenses { + license { + name.set("MIT License") + url.set("https://github.com/saveourtool/okio-extras/blob/main/LICENSE") + distribution.set("repo") + } + } + developers { + developer { + id.set("0x6675636b796f75676974687562") + name.set("Andrey Shcheglov") + email.set("andrewbass@gmail.com") + } + } + scm { + url.set("https://github.com/saveourtool/okio-extras") + connection.set("scm:git:git://github.com/saveourtool/okio-extras.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/saveourtool/buildutils/publishing-configuration.gradle.kts b/gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/publishing-configuration.gradle.kts new file mode 100644 index 0000000..5df3035 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/publishing-configuration.gradle.kts @@ -0,0 +1,37 @@ +package com.saveourtool.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/settings.gradle.kts b/settings.gradle.kts index 46599a2..4cfad81 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,8 @@ import java.util.Optional rootProject.name = "okio-extras" +includeBuild("gradle/plugins") + pluginManagement { repositories { mavenCentral()