diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1e8fc3..f01df8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,8 +9,10 @@ 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 }} jobs: build: @@ -126,6 +128,7 @@ jobs: gradle-version: wrapper arguments: | publish + -Preckon.stage=snapshot env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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..2590d6f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ /.run/ /.settings/ /build/ +/gradle/plugins/.gradle/ +/gradle/plugins/build/ +/core/build/ diff --git a/build.gradle.kts b/build.gradle.kts index d678cfa..d8f53a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,243 +1,20 @@ -@file:Suppress( - "UnstableApiUsage", - "KDocMissingDocumentation", -) +import com.saveourtool.buildutils.* -import com.saveourtool.buildutils.configureDetekt -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 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" + kotlin("multiplatform") apply false + id("com.saveourtool.buildutils.publishing-configuration") + id("com.saveourtool.diktat") version "2.0.0" } -group = "com.saveourtool" -description = "A set of extensions to Okio" +configureVersioning() -repositories { - mavenCentral() -} - -kotlin { - jvm() - jvmToolchain(jdkVersion = 8) - - mingwX64() - linuxX64() - macosX64() - macosArm64() - - @Suppress( - "UnusedPrivateMember", - "UNUSED_VARIABLE", - ) - sourceSets { - val commonMain by getting { - dependencies { - api("com.squareup.okio:okio:3.7.0") - } - } - - val commonTest by getting { - dependencies { - api(kotlin("test")) - api("io.kotest:kotest-assertions-core:5.8.0") - } - } - } -} - -tasks.withType { - testLogging { - showStandardStreams = true - showCauses = true - showExceptions = true - showStackTraces = true - exceptionFormat = FULL - events("passed", "skipped") - } -} - -/* - * This is expected to work some day (but currently it does not), - * see https://youtrack.jetbrains.com/issue/KT-32608. - */ -tasks.withType { - reports.junitXml.required.set(true) -} - -tasks.withType { - reports.junitXml.required.set(true) -} - -tasks.withType { - dependsOn(tasks.withType()) -} - -tasks.withType { - dependsOn(tasks.check) -} - -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") - } - } - } +allprojects { + repositories { + mavenCentral() } -} -/** - * 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() - } - } + configureDetekt() } -/** - * @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) - -/** - * 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 - */ -fun Project.hasProperties(vararg propertyNames: String): Boolean = - propertyNames.asSequence().all(this::hasProperty) +createDetektTask() diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ecb5a1b..9e57dd6 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,4 +11,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") implementation("org.gradle.kotlin:gradle-kotlin-dsl-plugins:4.3.0") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.5") + 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/saveourtool/buildutils/DetektConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/buildutils/DetektConfiguration.kt index 114c9aa..1d42bc0 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/buildutils/DetektConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/saveourtool/buildutils/DetektConfiguration.kt @@ -1,118 +1,34 @@ +/** + * Configuration for detekt static analysis + */ + package com.saveourtool.buildutils -import io.gitlab.arturbosch.detekt.Detekt -import org.gradle.api.DefaultTask +import io.gitlab.arturbosch.detekt.DetektPlugin +import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Project -import org.gradle.kotlin.dsl.invoke -import org.gradle.kotlin.dsl.named -import org.gradle.kotlin.dsl.register -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile -import java.io.File - -private const val PREFIX = "detekt" +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure /** - * Configures _Detekt_ for this project. + * Configure Detekt for a single project */ fun Project.configureDetekt() { - tasks.withType { - parallel = true - autoCorrect = hasProperty("detektAutoCorrect") - config.setFrom(file(projectDir / "config" / "detekt" / "detekt.yml")) - include("**/*.kt") - reports { - val reportDir = buildDir / "reports" / "detekt" - - sarif { - required.set(true) - outputLocation.set(file(reportDir / "$name.sarif")) - } - - html { - required.set(true) - outputLocation.set(file(reportDir / "$name.html")) - } - - sequenceOf(xml, txt, md).forEach { report -> - report.required.set(false) - } - } - } - - detekt("detektCommonMain") - detekt("detektCommonTest") - detekt("detektJvmMain") - detekt("detektJvmTest") - detekt("detektNativeMain") - detekt("detektNativeTest") - - tasks.register("detektAll") { - group = "verification" - - dependsOn( - tasks.named("detektCommonMain"), - tasks.named("detektCommonTest"), - tasks.named("detektJvmMain"), - tasks.named("detektJvmTest"), - tasks.named("detektNativeMain"), - tasks.named("detektNativeTest"), - ) + apply() + configure { + config = rootProject.files("detekt.yml") + buildUponDefaultConfig = true + debug = true } } /** - * Configures a _Detekt_ task, creating it if necessary. - * - * @param configuration extra configuration, may be empty. + * Register a unified detekt task */ -private fun Project.detekt( - name: String, - configuration: Detekt.() -> Unit = {}, -) { - val taskProvider = when (tasks.findByName(name)) { - null -> tasks.register(name) - else -> tasks.named(name) - } - - taskProvider { - val sourceSetName = name.sourceSetName - - source = fileTree(projectDir / "src" / sourceSetName) - - val isTest = sourceSetName.endsWith("Test") - val dependencyNamePrefix = when { - isTest -> "compileTestKotlin" - else -> "compileKotlin" - } - - val isNative = sourceSetName.startsWith("native") - when { - isNative -> dependsOn( - tasks.named(dependencyNamePrefix + "MingwX64"), - tasks.named(dependencyNamePrefix + "LinuxX64"), - tasks.named(dependencyNamePrefix + "MacosX64"), - ) - - else -> dependsOn(tasks.named(dependencyNamePrefix + "Jvm")) +fun Project.createDetektTask() { + tasks.register("detektAll") { + allprojects { + this@register.dependsOn(tasks.getByName("detekt")) } } - taskProvider(configuration) } - -private val String.sourceSetName: String - get() { - val suffix = when { - startsWith(PREFIX) -> substring(PREFIX.length).decapitalize() - else -> null - } - - return when { - suffix.isNullOrEmpty() -> this - else -> suffix - } - } - -private operator fun File.div(relative: String): File = - resolve(relative) diff --git a/buildSrc/src/main/kotlin/com/saveourtool/buildutils/VersioningConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/buildutils/VersioningConfiguration.kt new file mode 100644 index 0000000..6589bfe --- /dev/null +++ b/buildSrc/src/main/kotlin/com/saveourtool/buildutils/VersioningConfiguration.kt @@ -0,0 +1,60 @@ +/** + * Version configuration file. + */ + +package com.saveourtool.buildutils + +import org.ajoberstar.grgit.Grgit +import org.ajoberstar.reckon.gradle.ReckonExtension +import org.ajoberstar.reckon.gradle.ReckonPlugin +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure + +@Suppress("MISSING_KDOC_ON_FUNCTION", "MISSING_KDOC_TOP_LEVEL") +/** + * Configures how project version is determined. + * + * @throws GradleException if there was an attempt to run release build with dirty working tree + */ +fun Project.configureVersioning() { + apply() + + val isSnapshot = hasProperty("reckon.stage") && property("reckon.stage") == "snapshot" + configure { + scopeFromProp() + if (isSnapshot) { + // we should build snapshots only for snapshot publishing, so it requires explicit parameter + snapshotFromProp() + } else { + stageFromProp("alpha", "rc", "final") + } + } + + // to activate release, provide `-Prelease` or `-Prelease=true`. To deactivate, either omit the property, or set `-Prelease=false`. + val isRelease = hasProperty("release") && (property("release") as String != "false") + if (isRelease) { + val grgit = project.findProperty("grgit") as Grgit // grgit property is added by reckon plugin + val status = grgit.repository.jgit.status().call() + if (!status.isClean) { + throw GradleException( + "Release build will be performed with not clean git tree; aborting. " + + "Untracked files: ${status.untracked}, uncommitted changes: ${status.uncommittedChanges}" + ) + } + } + if (isSnapshot) { + val grgit = project.findProperty("grgit") as Grgit // grgit property is added by reckon plugin + // A terrible hack to remove all pre-release tags. Because in semver `0.1.0-SNAPSHOT` < `0.1.0-alpha`, in snapshot mode + // we remove tags like `0.1.0-alpha`, and then reckoned version will still be `0.1.0-SNAPSHOT` and it will be compliant. + val preReleaseTagNames = grgit.tag.list() + .sortedByDescending { it.commit.dateTime } + .takeWhile { + // take latest tags that are pre-release + !it.name.matches(Regex("""^v\d+\.\d+\.\d+$""")) + } + .map { it.name } + grgit.tag.remove { this.names = preReleaseTagNames } + } +} 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..6e60132 --- /dev/null +++ b/gradle/plugins/src/main/kotlin/com/saveourtool/buildutils/PublishingConfiguration.kt @@ -0,0 +1,169 @@ +/** + * 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.extra +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/okio-extras/build.gradle.kts b/okio-extras/build.gradle.kts new file mode 100644 index 0000000..3c5da0b --- /dev/null +++ b/okio-extras/build.gradle.kts @@ -0,0 +1,69 @@ +@file:Suppress( + "UnstableApiUsage", + "KDocMissingDocumentation", +) + + +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL +import com.saveourtool.buildutils.* + +plugins { + kotlin("multiplatform") + id("com.saveourtool.buildutils.publishing-configuration") +} + +kotlin { + iosArm64() + iosSimulatorArm64() + iosX64() + jvm() + jvmToolchain(jdkVersion = 8) + linuxX64() + linuxArm64() + macosArm64() + macosX64() + mingwX64() + + @Suppress( + "UnusedPrivateMember", + "UNUSED_VARIABLE", + ) + sourceSets { + val commonMain by getting { + dependencies { + api("com.squareup.okio:okio:3.7.0") + } + } + + val commonTest by getting { + dependencies { + api(kotlin("test")) + api("io.kotest:kotest-assertions-core:5.8.0") + } + } + } +} + +publishing { + publications { + create("maven") { + groupId = "com.saveourtool" + artifactId = "okio-extras" + version = version + from(components["kotlin"]) + } + } +} + +configureSigning() + +tasks.withType { + testLogging { + showStandardStreams = true + showCauses = true + showExceptions = true + showStackTraces = true + exceptionFormat = FULL + events("passed", "skipped") + } +} diff --git a/src/commonMain/kotlin/com/saveourtool/okio/FileSystem.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/FileSystem.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/FileSystem.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/FileSystem.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/PathUtils.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/PathUtils.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/PathUtils.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/PathUtils.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UnixUriToPathConverter.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UnixUriToPathConverter.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/UnixUriToPathConverter.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UnixUriToPathConverter.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/Uri.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/Uri.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/Uri.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/Uri.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UriBuilder.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriBuilder.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/UriBuilder.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriBuilder.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt similarity index 99% rename from src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt index 937dc57..340caef 100644 --- a/src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt +++ b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriNormalizationUtils.kt @@ -281,7 +281,7 @@ private fun String.hasDotAtEnd(index: Int): Boolean = private fun String.hasTwoDotsAtEnd(index: Int): Boolean = index == length - 2 && - endsWith("..") + endsWith("") private fun String.hasDotBeforeSlash(index: Int): Boolean = index <= length - 2 && diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UriParser.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriParser.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/UriParser.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriParser.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UriToPathConverter.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriToPathConverter.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/UriToPathConverter.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriToPathConverter.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/UriUtils.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriUtils.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/UriUtils.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/UriUtils.kt diff --git a/src/commonMain/kotlin/com/saveourtool/okio/WindowsUriToPathConverter.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/okio/WindowsUriToPathConverter.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/okio/WindowsUriToPathConverter.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/okio/WindowsUriToPathConverter.kt diff --git a/src/commonMain/kotlin/com/saveourtool/system/OsFamily.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/system/OsFamily.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/system/OsFamily.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/system/OsFamily.kt diff --git a/src/commonMain/kotlin/com/saveourtool/text/Char.kt b/okio-extras/src/commonMain/kotlin/com/saveourtool/text/Char.kt similarity index 100% rename from src/commonMain/kotlin/com/saveourtool/text/Char.kt rename to okio-extras/src/commonMain/kotlin/com/saveourtool/text/Char.kt diff --git a/src/commonTest/kotlin/com/saveourtool/okio/UriTest.kt b/okio-extras/src/commonTest/kotlin/com/saveourtool/okio/UriTest.kt similarity index 100% rename from src/commonTest/kotlin/com/saveourtool/okio/UriTest.kt rename to okio-extras/src/commonTest/kotlin/com/saveourtool/okio/UriTest.kt diff --git a/src/jvmMain/kotlin/com/saveourtool/okio/FileSystem.kt b/okio-extras/src/jvmMain/kotlin/com/saveourtool/okio/FileSystem.kt similarity index 100% rename from src/jvmMain/kotlin/com/saveourtool/okio/FileSystem.kt rename to okio-extras/src/jvmMain/kotlin/com/saveourtool/okio/FileSystem.kt diff --git a/src/jvmMain/kotlin/com/saveourtool/system/OsFamily.kt b/okio-extras/src/jvmMain/kotlin/com/saveourtool/system/OsFamily.kt similarity index 100% rename from src/jvmMain/kotlin/com/saveourtool/system/OsFamily.kt rename to okio-extras/src/jvmMain/kotlin/com/saveourtool/system/OsFamily.kt diff --git a/src/jvmMain/kotlin/com/saveourtool/text/Char.kt b/okio-extras/src/jvmMain/kotlin/com/saveourtool/text/Char.kt similarity index 100% rename from src/jvmMain/kotlin/com/saveourtool/text/Char.kt rename to okio-extras/src/jvmMain/kotlin/com/saveourtool/text/Char.kt diff --git a/src/nativeMain/kotlin/com/saveourtool/okio/FileSystemNative.kt b/okio-extras/src/nativeMain/kotlin/com/saveourtool/okio/FileSystemNative.kt similarity index 100% rename from src/nativeMain/kotlin/com/saveourtool/okio/FileSystemNative.kt rename to okio-extras/src/nativeMain/kotlin/com/saveourtool/okio/FileSystemNative.kt diff --git a/src/nativeMain/kotlin/com/saveourtool/system/OsFamily.kt b/okio-extras/src/nativeMain/kotlin/com/saveourtool/system/OsFamily.kt similarity index 100% rename from src/nativeMain/kotlin/com/saveourtool/system/OsFamily.kt rename to okio-extras/src/nativeMain/kotlin/com/saveourtool/system/OsFamily.kt diff --git a/src/nativeMain/kotlin/com/saveourtool/text/Char.kt b/okio-extras/src/nativeMain/kotlin/com/saveourtool/text/Char.kt similarity index 100% rename from src/nativeMain/kotlin/com/saveourtool/text/Char.kt rename to okio-extras/src/nativeMain/kotlin/com/saveourtool/text/Char.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 46599a2..40b753d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,65 +1,4 @@ -import org.ajoberstar.reckon.core.Scope.PATCH -import org.ajoberstar.reckon.gradle.ReckonExtension -import java.util.Optional - rootProject.name = "okio-extras" -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("com.gradle.enterprise") version "3.16.2" - id("org.ajoberstar.reckon.settings") version "0.18.2" -} - -configure { - /* - * Only two stages are used, `snapshot` and `final`, `snapshot` being the - * default one. - */ - snapshots() - - /* - * The stage is calculated from the `reckon.stage` property. - */ - setStageCalc(calcStageFromProp()) - - /*- - * How the version is incremented. - * - * MINOR: 1.0.0 -> 1.1.0-SNAPSHOT - * PATCH: 1.0.0 -> 1.0.1-SNAPSHOT - */ - setScopeCalc { - Optional.of(PATCH) - } - - /*- - * The version is incremented on PATCH version by default. - * - * PATCH: 1.0.0 -> 1.0.1-SNAPSHOT - */ - setDefaultInferredScope(PATCH) - - /* - * Run `./gradlew -Preckon.stage=final reckonTagCreate` when the Git - * repository is clean. - */ - setTagWriter { version -> - "v$version" - } -} - -gradleEnterprise { - if (System.getenv("CI") != null) { - buildScan { - publishAlways() - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" - } - } -} +includeBuild("gradle/plugins") +include("okio-extras")