diff --git a/build.gradle.kts b/build.gradle.kts index 6399e89..0b4bf68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import com.saveourtool.osv4k.buildutils.configureDiktat +import com.saveourtool.osv4k.buildutils.configureVersioning import com.saveourtool.osv4k.buildutils.createDetektTask plugins { @@ -14,7 +15,7 @@ repositories { } // version generation -// configureVersioning() +configureVersioning() // checks and validations configureDiktat() @@ -38,7 +39,6 @@ kotlin { api(libs.kotlinx.datetime) } } - @Suppress("UNUSED_VARIABLE") val commonTest by getting { dependencies { implementation(kotlin("test")) @@ -62,6 +62,7 @@ kotlin { api(libs.jackson.databind) } } + @Suppress("UNUSED_VARIABLE") val jvmTest by getting { dependsOn(commonTest) dependencies { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1076f69..0d89dee 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { implementation(libs.diktat.gradle.plugin) implementation(libs.detekt.gradle.plugin) implementation(libs.kotlin.plugin.serialization) - // implementation("io.github.gradle-nexus:publish-plugin:1.3.0") - implementation("org.ajoberstar.reckon:reckon-gradle:0.13.2") - // implementation("org.ajoberstar.reckon:reckon-gradle:0.18.0") + implementation("io.github.gradle-nexus:publish-plugin:1.3.0") + implementation("org.ajoberstar.reckon:reckon-gradle:0.18.0") } diff --git a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/DetektConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/DetektConfiguration.kt index e263c96..d371285 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/DetektConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/DetektConfiguration.kt @@ -18,7 +18,7 @@ import org.gradle.kotlin.dsl.withType fun Project.configureDetekt() { apply() configure { - config = rootProject.files("detekt.yml") + config.from(rootProject.files("detekt.yml")) buildUponDefaultConfig = true } } diff --git a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/JacocoConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/JacocoConfiguration.kt new file mode 100644 index 0000000..d480135 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/JacocoConfiguration.kt @@ -0,0 +1,65 @@ +/** + * Configure JaCoCo for code coverage calculation + */ + +@file:Suppress("FILE_WILDCARD_IMPORTS") + +package com.saveourtool.osv4k.buildutils + +import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.* +import org.gradle.testing.jacoco.plugins.JacocoPlugin +import org.gradle.testing.jacoco.plugins.JacocoPluginExtension +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension +import org.gradle.testing.jacoco.tasks.JacocoReport +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +/** + * Calculate code coverage from JVM test executions. + */ +fun Project.configureJacoco() { + apply() + + configure { + toolVersion = "0.8.8" + } + + val kotlin: KotlinMultiplatformExtension = extensions.getByType() + val jvmTestTask by tasks.named("jvmTest") { + configure { + // this is needed to generate jacoco/jvmTest.exec + isEnabled = true + } + } + + val configure: JacocoReport.() -> Unit = { + executionData(jvmTestTask.extensions.getByType(JacocoTaskExtension::class.java).destinationFile) + // todo: include platform-specific source sets + additionalSourceDirs( + kotlin.sourceSets["commonMain"].kotlin.sourceDirectories + + kotlin.sourceSets["commonNonJvmMain"].kotlin.sourceDirectories + ) + classDirectories.setFrom(fileTree("$buildDir/classes/kotlin/jvm/main").apply { + exclude("**/*\$\$serializer.class") + }) + reports { + xml.required.set(true) + html.required.set(true) + } + } + + // `application` plugin creates jacocoTestReport task in plugin section (this is definitely incorrect behavior) + // AFTER that in "com.saveourtool.save.buildutils.kotlin-library" we try to register this task once again and fail + // so the order of plugins in `apply` is critically important + val jacocoTestReportTask = if (project.name == "save-cli") { + val jacocoTestReportTask by tasks.named("jacocoTestReport", configure) + jacocoTestReportTask + } else { + val jacocoTestReportTask by tasks.register("jacocoTestReport", configure) + jacocoTestReportTask + } + + jvmTestTask.finalizedBy(jacocoTestReportTask) + jacocoTestReportTask.dependsOn(jvmTestTask) +} diff --git a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/PublishingConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/PublishingConfiguration.kt new file mode 100644 index 0000000..cd76917 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/PublishingConfiguration.kt @@ -0,0 +1,162 @@ +/** + * Publishing configuration file. + */ + +package com.saveourtool.osv4k.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.publish.maven.tasks.PublishToMavenRepository +import org.gradle.api.tasks.bundling.Jar +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.gradle.plugins.signing.Sign +import org.gradle.plugins.signing.SigningExtension +import org.gradle.plugins.signing.SigningPlugin + +@Suppress( + "MISSING_KDOC_ON_FUNCTION", + "MISSING_KDOC_TOP_LEVEL", + "TOO_LONG_FUNCTION" +) +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("GPG_SEC")?.let { + extra.set("signingKey", it) + } + System.getenv("GPG_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 + afterEvaluate { + val publicationsFromMainHost = listOf( + "jvm", + "js", + "linuxX64", "mingwX64", "macosX64", + "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") + } + } + } + } + } + } + } +} + +@Suppress("TOO_LONG_FUNCTION", "GENERIC_VARIABLE_WRONG_DECLARATION") +private fun Project.configurePublications() { + val dokkaJarProvider = tasks.register("dokkaJar") { + group = "documentation" + archiveClassifier.set("javadoc") + from(tasks.findByName("dokkaHtml")) + } + configure { + repositories { + mavenLocal() + } + publications.withType().forEach { publication -> + publication.artifact(dokkaJarProvider) + publication.pom { + name.set(project.name) + description.set(project.description ?: project.name) + url.set("https://github.com/saveourtool/save") + licenses { + license { + name.set("MIT License") + url.set("http://www.opensource.org/licenses/mit-license.php") + distribution.set("repo") + } + } + developers { + developer { + id.set("petertrr") + name.set("Petr Trifanov") + email.set("peter.trifanov@gmail.com") + } + developer { + id.set("akuleshov7") + name.set("Andrey Kuleshov") + email.set("andrewkuleshov7@gmail.com") + } + } + scm { + url.set("https://github.com/saveourtool/save") + connection.set("scm:git:git://github.com/saveourtool/save.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()) + } + + tasks.withType().configureEach { + // Workaround for the problem described at https://github.com/saveourtool/save-cli/pull/501#issuecomment-1439705340. + // We have a single Javadoc artifact shared by all platforms, hence all publications depend on signing of this artifact. + // This causes weird implicit dependencies, like `publishJsPublication...` depends on `signJvmPublication`. + dependsOn(tasks.withType()) + } +} + +private 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) + } + } + } +} diff --git a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/VersioningConfiguration.kt b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/VersioningConfiguration.kt index 0c3e90e..b8f52b3 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/VersioningConfiguration.kt +++ b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/VersioningConfiguration.kt @@ -4,6 +4,8 @@ package com.saveourtool.osv4k.buildutils +import org.ajoberstar.reckon.core.Scope +import org.ajoberstar.reckon.gradle.ReckonExtension import org.ajoberstar.reckon.gradle.ReckonPlugin import org.eclipse.jgit.api.Git import org.eclipse.jgit.internal.storage.file.FileRepository @@ -11,6 +13,7 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure /** * Configures how project version is determined. @@ -19,6 +22,18 @@ import org.gradle.kotlin.dsl.apply */ fun Project.configureVersioning() { apply() + + configure { + setDefaultInferredScope(Scope.MINOR) + snapshots() + setScopeCalc(calcScopeFromProp()) + setStageCalc(calcStageFromProp()) + } + + val isRelease = hasProperty("reckon.stage") && property("reckon.stage") == "final" + if (isRelease) { + failOnUncleanTree() + } } private fun Project.failOnUncleanTree() { diff --git a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/kotlin-library.gradle.kts b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/kotlin-library.gradle.kts index e118244..96497d0 100644 --- a/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/kotlin-library.gradle.kts +++ b/buildSrc/src/main/kotlin/com/saveourtool/osv4k/buildutils/kotlin-library.gradle.kts @@ -7,7 +7,6 @@ package com.saveourtool.osv4k.buildutils -import org.gradle.kotlin.dsl.kotlin import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest @@ -112,7 +111,7 @@ kotlin { } // configureJacoco() -// configurePublishing() +configurePublishing() configureDiktat() configureDetekt()