diff --git a/.agents/_TOC.md b/.agents/_TOC.md new file mode 100644 index 0000000..065df13 --- /dev/null +++ b/.agents/_TOC.md @@ -0,0 +1,16 @@ +# Table of Contents + +1. [Quick Reference Card](quick-reference-card.md) +2. [Project overview](project-overview.md) +3. [Coding guidelines](coding-guidelines.md) +4. [Documentation & comments](documentation-guidelines.md) +5. [Documentation tasks](documentation-tasks.md) +6. [Running builds](running-builds.md) +7. [Version policy](version-policy.md) +8. [Project structure expectations](project-structure-expectations.md) +9. [Testing](testing.md) +10. [Safety rules](safety-rules.md) +11. [Advanced safety rules](advanced-safety-rules.md) +12. [Refactoring guidelines](refactoring-guidelines.md) +13. [Common tasks](common-tasks.md) +14. [Java to Kotlin conversion](java-kotlin-conversion.md) diff --git a/.agents/advanced-safety-rules.md b/.agents/advanced-safety-rules.md new file mode 100644 index 0000000..e410581 --- /dev/null +++ b/.agents/advanced-safety-rules.md @@ -0,0 +1,6 @@ +# ๐Ÿšจ Advanced safety rules + +- Do **not** auto-update external dependencies without explicit request. +- Do **not** inject analytics or telemetry code. +- Flag any usage of unsafe constructs (e.g., reflection, I/O on the main thread). +- Avoid generating blocking calls inside coroutines. diff --git a/.agents/coding-guidelines.md b/.agents/coding-guidelines.md new file mode 100644 index 0000000..3297d8a --- /dev/null +++ b/.agents/coding-guidelines.md @@ -0,0 +1,39 @@ +# ๐Ÿงพ Coding guidelines + +## Core principles + +- Adhere to [Spine Event Engine Documentation][spine-docs] for coding style. +- Generate code that compiles cleanly and passes static analysis. +- Respect existing architecture, naming conventions, and project structure. +- Write clear, incremental commits with descriptive messages. +- Include automated tests for any code change that alters functionality. + +## Kotlin best practices + +### โœ… Prefer +- **Kotlin idioms** over Java-style approaches: + - Extension functions + - `when` expressions + - Smart casts + - Data classes and sealed classes + - Immutable data structures +- **Simple nouns** over composite nouns (`user` > `userAccount`) +- **Generic parameters** over explicit variable types (`val list = mutableList()`) +- **Java interop annotations** only when needed (`@file:JvmName`, `@JvmStatic`) +- **Kotlin DSL** for Gradle files + +### โŒ Avoid +- Mutable data structures +- Java-style verbosity (builders with setters) +- Redundant null checks (`?.let` misuse) +- Using `!!` unless clearly justified +- Type names in variable names (`userObject`, `itemList`) +- String duplication (use constants in companion objects) +- Mixing Groovy and Kotlin DSLs in build logic +- Reflection unless specifically requested + +## Text formatting + - โœ… Remove double empty lines in the code. + - โœ… Remove trailing space characters in the code. + +[spine-docs]: https://github.com/SpineEventEngine/documentation/wiki diff --git a/.agents/common-tasks.md b/.agents/common-tasks.md new file mode 100644 index 0000000..5ee954d --- /dev/null +++ b/.agents/common-tasks.md @@ -0,0 +1,6 @@ +# ๐Ÿ“‹ Common tasks + +- **Adding a new dependency**: Update relevant files in `buildSrc` directory. +- **Creating a new module**: Follow existing module structure patterns. +- **Documentation**: Use KDoc style for public and internal APIs. +- **Testing**: Create comprehensive tests using Kotest assertions. diff --git a/.agents/documentation-guidelines.md b/.agents/documentation-guidelines.md new file mode 100644 index 0000000..914dcc8 --- /dev/null +++ b/.agents/documentation-guidelines.md @@ -0,0 +1,14 @@ +# Documentation & comments + +## Commenting guidelines +- Avoid inline comments in production code unless necessary. +- Inline comments are helpful in tests. +- When using TODO comments, follow the format on the [dedicated page][todo-comments]. +- File and directory names should be formatted as code. + +## Avoid widows, runts, orphans, or rivers + +Agents should **AVOID** text flow patters illustrated +on [this diagram](widow-runt-orphan-river.jpg). + +[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments diff --git a/.agents/documentation-tasks.md b/.agents/documentation-tasks.md new file mode 100644 index 0000000..8ac4660 --- /dev/null +++ b/.agents/documentation-tasks.md @@ -0,0 +1,20 @@ +# ๐Ÿ“„ Documentation tasks + +1. Ensure all public and internal APIs have KDoc examples. +2. Add in-line code blocks for clarity in tests. +3. Convert inline API comments in Java to KDoc in Kotlin: + ```java + // Literal string to be inlined whenever a placeholder references a non-existent argument. + private final String missingArgumentMessage = "[MISSING ARGUMENT]"; + ``` + transforms to: + ```kotlin + /** + * Literal string to be inlined whenever a placeholder references a non-existent argument. + */ + private val missingArgumentMessage = "[MISSING ARGUMENT]" + ``` + +4. Javadoc -> KDoc conversion tasks: + - Remove `

` tags in the line with text: `"

This"` -> `"This"`. + - Replace `

` with empty line if the tag is the only text in the line. diff --git a/.agents/java-kotlin-conversion.md b/.agents/java-kotlin-conversion.md new file mode 100644 index 0000000..95cf929 --- /dev/null +++ b/.agents/java-kotlin-conversion.md @@ -0,0 +1,43 @@ +# ๐Ÿช„ Converting Java code to Kotlin + +* Java code API comments are Javadoc format. +* Kotlin code API comments are in KDoc format. + +## Javadoc to KDoc conversion + +* The wording of original Javadoc comments must be preserved. + +## Treating nullability + +* Use nullable Kotlin type only if the type in Java is annotated as `@Nullable`. + +## Efficient Conversion Workflow + +* First, analyze the entire Java file structure before beginning conversion to understand dependencies and class relationships. +* Convert Java code to Kotlin systematically: imports first, followed by class definitions, methods, and finally expressions. +* Preserve all existing functionality and behavior during conversion. +* Maintain original code structure and organization to ensure readability. + +## Common Java to Kotlin Patterns + +* Convert Java getters/setters to Kotlin properties with appropriate visibility modifiers. +* Transform Java static methods to companion object functions or top-level functions as appropriate. +* Replace Java anonymous classes with Kotlin lambda expressions when possible. +* Convert Java interfaces with default methods to Kotlin interfaces with implementations. +* Transform Java builders to Kotlin DSL patterns when appropriate. + +## Error Prevention + +* Pay special attention to Java's checked exceptions versus Kotlin's unchecked exceptions. +* Be cautious with Java wildcards (`? extends`, `? super`) conversion to Kotlin's `out` and `in` type parameters. +* Ensure proper handling of Java static initialization blocks in Kotlin companion objects. +* Verify that Java overloaded methods convert correctly with appropriate default parameter values in Kotlin. +* Remember that Kotlin has smart casts which can eliminate explicit type casting needed in Java. + +## Documentation Conversion + +* Convert `@param` to `@param` with the same description. +* Convert `@return` to `@return` with the same description. +* Convert `@throws` to `@throws` with the same description. +* Convert `{@link}` to `[name][fully.qualified.Name]` format. +* Convert `{@code}` to inline code with backticks (`). diff --git a/.agents/project-overview.md b/.agents/project-overview.md new file mode 100644 index 0000000..dfac73f --- /dev/null +++ b/.agents/project-overview.md @@ -0,0 +1,7 @@ +# ๐Ÿ› ๏ธ Project overview + +- **Languages**: Kotlin (primary), Java (secondary). +- **Build tool**: Gradle with Kotlin DSL. +- **Static analysis**: detekt, ErrorProne, Checkstyle, PMD. +- **Testing**: JUnit 5, Kotest Assertions, Codecov. +- **Tools used**: Gradle plugins, IntelliJ IDEA Platform, KSP, KotlinPoet, Dokka. diff --git a/.agents/project-structure-expectations.md b/.agents/project-structure-expectations.md new file mode 100644 index 0000000..81b8e1a --- /dev/null +++ b/.agents/project-structure-expectations.md @@ -0,0 +1,21 @@ +# ๐Ÿ“ Project structure expectations + +```yaml +.github +buildSrc/ + + src/ + โ”œโ”€โ”€ main/ + โ”‚ โ”œโ”€โ”€ kotlin/ # Kotlin source files + โ”‚ โ””โ”€โ”€ java/ # Legacy Java code + โ”œโ”€โ”€ test/ + โ”‚ โ””โ”€โ”€ kotlin/ # Unit and integration tests + build.gradle.kts # Kotlin-based build configuration + + +build.gradle.kts # Kotlin-based build configuration +settings.gradle.kts # Project structure and settings +README.md # Project overview +AGENTS.md # Entry point for LLM agent instructions +version.gradle.kts # Declares the project version. +``` diff --git a/.agents/quick-reference-card.md b/.agents/quick-reference-card.md new file mode 100644 index 0000000..6c25b9a --- /dev/null +++ b/.agents/quick-reference-card.md @@ -0,0 +1,10 @@ +# ๐Ÿ“ Quick Reference Card + +``` +๐Ÿ”‘ Key Information: +- Kotlin/Java project with CQRS architecture +- Use ChatGPT for documentation, Codex for code generation, GPT-4o for complex analysis +- Follow coding guidelines in Spine Event Engine docs +- Always include tests with code changes +- Version bump required for all PRs +``` diff --git a/.agents/refactoring-guidelines.md b/.agents/refactoring-guidelines.md new file mode 100644 index 0000000..191db49 --- /dev/null +++ b/.agents/refactoring-guidelines.md @@ -0,0 +1,3 @@ +# โš™๏ธ Refactoring guidelines + +- Do NOT replace Kotest assertions with standard Kotlin's built-in test assertions. diff --git a/.agents/running-builds.md b/.agents/running-builds.md new file mode 100644 index 0000000..db0338d --- /dev/null +++ b/.agents/running-builds.md @@ -0,0 +1,18 @@ +# Running builds + +1. When modifying code, run: + ```bash + ./gradlew build + ``` + +2. If Protobuf (`.proto`) files are modified run: + ```bash + ./gradlew clean build + ``` + +3. Documentation-only changes in Kotlin or Java sources run: + ```bash + ./gradlew dokka + ``` + +4. Documentation-only changes do not require running tests! diff --git a/.agents/safety-rules.md b/.agents/safety-rules.md new file mode 100644 index 0000000..08e9b33 --- /dev/null +++ b/.agents/safety-rules.md @@ -0,0 +1,7 @@ +# Safety rules + +- โœ… All code must compile and pass static analysis. +- โœ… Do not auto-update external dependencies. +- โŒ Never use reflection or unsafe code without an explicit approval. +- โŒ No analytics or telemetry code. +- โŒ No blocking calls inside coroutines. diff --git a/.agents/testing.md b/.agents/testing.md new file mode 100644 index 0000000..f81bdbf --- /dev/null +++ b/.agents/testing.md @@ -0,0 +1,8 @@ +# ๐Ÿงช Testing + +- Do not use mocks, use stubs. +- Prefer [Kotest assertions][kotest-assertions] over assertions from JUnit or Google Truth. +- Generate unit tests for APIs (handles edge cases/scenarios). +- Supply scaffolds for typical Kotlin patterns (`when`, sealed classes). + +[kotest-assertions]: https://kotest.io/docs/assertions/assertions.html diff --git a/.agents/version-policy.md b/.agents/version-policy.md new file mode 100644 index 0000000..65dc457 --- /dev/null +++ b/.agents/version-policy.md @@ -0,0 +1,30 @@ +# Version policy + +## We use semver +The version of the project is kept in the `version.gradle.kts` file in the root of the project. + +The version numbers in these files follow the conventions of +[Semantic Versioning 2.0.0](https://semver.org/). + +## Quick checklist for versioning +1. Increment the patch version in `version.gradle.kts`. + Retain zero-padding if applicable: + - Example: `"2.0.0-SNAPSHOT.009"` โ†’ `"2.0.0-SNAPSHOT.010"` +2. Commit the version bump separately with this comment: + ```text + Bump version โ†’ `$newVersion` + ``` +3. Rebuild using `./gradlew clean build`. +4. Update `pom.xml`, `dependencies.md` and commit changes with: `Update dependency reports` + +Remember: PRs without version bumps will fail CI (conflict resolution detailed above). + +## Resolving conflicts in `version.gradle.kts` +A branch conflict over the version number should be resolved as described below. + * If a merged branch has a number which is less than that of the current branch, the version of + the current branch stays. + * If the merged branch has the number which is greater or equal to that of the current branch, + the number should be increased by one. + +## When to bump the version? + - When a new branch is created. diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 4b46e96..c515672 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -4,6 +4,6 @@

{@link Runtime#exec(String[], String[], File) Executes} the given {@code String} array as - * a CLI command. If the execution is successful, returns the command output. Throws - * an {@link IllegalStateException} otherwise. + * [Executes][Runtime.exec] the given `String` array as a CLI command. + * + * If the execution is successful, returns the command output. + * Throws an {@link IllegalStateException} otherwise. * - * @param command the command to execute - * @return the command line output - * @throws IllegalStateException upon an execution error + * @param command the command to execute. + * @return the command line output. + * @throws IllegalStateException if the execution fails. */ fun execute(vararg command: String): String { val outWriter = StringWriter() diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt index 122a604..bb2a181 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt @@ -56,7 +56,7 @@ object CheckStyleConfig { plugin(CheckstylePlugin::class.java) } - val configDir = project.rootDir.resolve("config/quality/") + val configDir = project.rootDir.resolve("buildSrc/quality/") with(project.the()) { toolVersion = CheckStyle.version diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt index 94ab6ac..ef67c71 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt @@ -26,10 +26,10 @@ package io.spine.gradle.github.pages -import io.spine.gradle.RepoSlug import io.spine.gradle.git.Branch import io.spine.gradle.git.Repository import io.spine.gradle.git.UserInfo +import io.spine.gradle.repo.RepoSlug /** * Clones the current project repository with the branch dedicated to publishing diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/java/Linters.kt b/buildSrc/src/main/kotlin/io/spine/gradle/java/Linters.kt new file mode 100644 index 0000000..fc00555 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/java/Linters.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle.java + +import net.ltgt.gradle.errorprone.errorprone +import org.gradle.api.Project +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.named + +/** + * Disables Java linters in this [Project]. + * + * In particular, the following linters will be disabled: + * + * 1. CheckStyle. + * 2. PMD. + * 3. ErrorProne. + * + * Apply this configuration for modules that have original Flogger sources, + * which have not been migrated to Kotlin yet. They produce a lot of + * errors/warnings failing the build. + * + * Our own sources are mostly in Kotlin (as for `spine-logging` repo), + * so this action seems quite safe. + */ +// TODO:2023-09-22:yevhenii.nadtochii: Remove this piece of configuration. +// See issue: https://github.com/SpineEventEngine/logging/issues/56 +fun Project.disableLinters() { + tasks { + named("checkstyleMain") { enabled = false } + named("pmdMain") { enabled = false } + named("compileJava") { + options.errorprone.isEnabled.set(false) + } + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt index d2e4c90..9616e99 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt @@ -26,11 +26,24 @@ package io.spine.gradle.javadoc +import io.spine.gradle.javadoc.JavadocConfig.tags import java.io.File import org.gradle.api.JavaVersion import org.gradle.api.Project +import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.external.javadoc.StandardJavadocDocletOptions +import productionModules + +/** + * Finds a [Javadoc] Gradle task by the passed name. + */ +fun TaskContainer.javadocTask(named: String) = this.getByName(named) as Javadoc + +/** + * Finds a default [Javadoc] Gradle task. + */ +fun TaskContainer.javadocTask() = this.getByName("javadoc") as Javadoc /** * Javadoc processing settings. @@ -58,17 +71,25 @@ object JavadocConfig { fun applyTo(project: Project) { val javadocTask = project.tasks.javadocTask() + if (!isProductionModule(project)) { + javadocTask.enabled = false + return + } discardJavaModulesInLinks(javadocTask) val docletOptions = javadocTask.options as StandardJavadocDocletOptions configureDoclet(docletOptions) } + private fun isProductionModule(project: Project) = project.run { + rootProject.productionModules.contains(this) + } + /** - * Discards using of Java 9 modules in URL links generated by javadoc for our codebase. + * Discards using of Java 9 modules in URL links generated by Javadoc for our codebase. * * This fixes navigation to classes through the search results. * - * The issue appeared after migration to Java 11. When javadoc is generated for a project + * The issue appeared after migration to Java 11. When Javadoc is generated for a project * that does not declare Java 9 modules, search results contain broken links with appended * `undefined` prefix to the URL. This `undefined` was meant to be a name of a Java 9 module. * @@ -78,9 +99,9 @@ object JavadocConfig { // We ask `Javadoc` task to modify "search.js" and override a method, responsible for // the formation of URL prefixes. We can't specify the option "--no-module-directories", - // because it leads to discarding of all module prefixes in generated links. That means, - // links to the types from the standard library would not work, as they are declared - // within modules since Java 9. + // because it leads to discarding of all module prefixes in generated links. + // That means links to the types from the standard library would not work, + // as they are declared within modules since Java 9. val discardModulePrefix = """ diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt index 65cdebd..ea47967 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt @@ -27,7 +27,7 @@ package io.spine.gradle.kotlin import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension /** @@ -52,12 +52,14 @@ fun KotlinJvmProjectExtension.applyJvmToolchain(version: String) = * Opts-in to experimental features that we use in our codebase. */ @Suppress("unused") -fun KotlinJvmCompilerOptions.setFreeCompilerArgs() { +fun KotlinCommonCompilerOptions.setFreeCompilerArgs() { freeCompilerArgs.addAll( listOf( "-Xskip-prerelease-check", "-Xjvm-default=all", "-Xinline-classes", + "-Xexpect-actual-classes", + "-Xcontext-receivers", "-opt-in=" + "kotlin.contracts.ExperimentalContracts," + "kotlin.io.path.ExperimentalPathApi," + diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt index 6816669..b8439a0 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt @@ -24,6 +24,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +@file:Suppress("unused", "UnusedReceiverParameter") /* Extensions declared in this file + are used in the modules that build proto files without using the Spine Compiler. */ + package io.spine.gradle.protobuf import com.google.protobuf.gradle.GenerateProtoTask @@ -34,6 +37,7 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING +import kotlin.io.path.Path import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.tasks.SourceSet @@ -105,10 +109,10 @@ private fun GenerateProtoTask.generatedDir(language: String = ""): File { fun GenerateProtoTask.setup() { builtins.maybeCreate("kotlin") setupDescriptorSetFileCreation() + excludeProtocOutput() doLast { copyGeneratedFiles() } - excludeProtocOutput() setupKotlinCompile() dependOnProcessResourcesTask() makeDirsForIdeaModule() @@ -225,22 +229,35 @@ private fun GenerateProtoTask.deleteComGoogle(language: String) { */ fun GenerateProtoTask.excludeProtocOutput() { val protocOutputDir = File(outputBaseDir).parentFile - val java: SourceDirectorySet = sourceSet.java - // Filter out directories belonging to `build/generated/source/proto`. - val newSourceDirectories = java.sourceDirectories - .filter { !it.residesIn(protocOutputDir) } - .toSet() - // Make sure we start from scratch. - // Not doing this failed the following, real, assignment sometimes. - java.setSrcDirs(listOf()) - java.srcDirs(newSourceDirectories) + /** + * Filter out directories belonging to `build/generated/source/proto`. + */ + fun filterFor(directorySet: SourceDirectorySet) { + val newSourceDirectories = directorySet.sourceDirectories + .filter { !it.residesIn(protocOutputDir) } + .toSet() + // Make sure we start from scratch. + // Not doing this failed the following, real, assignment sometimes. + directorySet.setSrcDirs(listOf()) + directorySet.srcDirs(newSourceDirectories) + } + val java: SourceDirectorySet = sourceSet.java + filterFor(java) // Add copied files to the Java source set. java.srcDir(generatedDir("java")) - java.srcDir(generatedDir("kotlin")) + + val kotlin = sourceSet.kotlin + filterFor(kotlin) + // Add copied files to the Kotlin source set. + kotlin.srcDir(generatedDir("kotlin")) } +private val SourceSet.kotlin: SourceDirectorySet get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("kotlin") + as SourceDirectorySet + /** * Make sure Kotlin compilation explicitly depends on this `GenerateProtoTask` to avoid racing. */ @@ -325,18 +342,42 @@ fun IdeaModule.printSourceDirectories() { excludeDirs.forEach { println(it) } } +/** + * Obtains the extension of Protobuf Gradle Plugin in the given project. + */ +val Project.protobufExtension: ProtobufExtension? + get() = extensions.findByType(ProtobufExtension::class.java) + /** * Obtains the directory where the Protobuf Gradle Plugin should place the generated code. * - * The directory is fixed to be `$buildDir/generated/source/proto` and cannot be - * changed by the settings of the plugin. Even though [ProtobufExtension] has a property + * The directory is fixed to be `$buildDir/generated/source/proto` in versions pre v0.9.5 + * and cannot be changed by the settings of the plugin. + * In the v0.9.5 the path was changed to + * [`$buildDir/generated/sources/proto`](https://github.com/google/protobuf-gradle-plugin/releases/tag/v0.9.5). + * + * Even though [ProtobufExtension] has a property * [generatedFilesBaseDir][ProtobufExtension.getGeneratedFilesBaseDir], which is supposed - * to be used for this purpose, it is declared with `@PackageScope` and thus cannot be - * accessed from outside the plugin. The Protobuf Gradle Plugin (at v0.9.2) does not - * modify the value of the property either. + * to be used for this purpose, it is declared with `@PackageScope` (again in earlier versions) + * and thus cannot be accessed from outside the plugin. + * The Protobuf Gradle Plugin (at v0.9.2) does not modify the value of the property either. + * Therefore, we try getting the path using the newer version API and resort to the "legacy" + * convention if the call fails. */ val Project.generatedSourceProtoDir: Path - get() = layout.buildDirectory.dir("generated/source/proto").get().asFile.toPath() + get() { + val legacyPath = layout.buildDirectory.dir("generated/source/proto").get().asFile.toPath() + protobufExtension?.let { + return try { + it.generatedFilesBaseDir.let { Path(it) } + } catch (_: Throwable) { + // Probably we're running on an older version of the Protobuf Gradle Plugin + // which has `package-access` for the `getGeneratedFilesDir()` method. + legacyPath + } + } + return legacyPath + } /** * Ensures that the sources generated by Protobuf Gradle Plugin diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt index 9e38068..e47edae 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt @@ -28,7 +28,7 @@ package io.spine.gradle.publish import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES import com.fasterxml.jackson.dataformat.xml.XmlMapper -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository import java.io.FileNotFoundException import java.net.URL import org.gradle.api.DefaultTask @@ -58,10 +58,11 @@ open class CheckVersionIncrement : DefaultTask() { @TaskAction fun fetchAndCheck() { val artifact = "${project.artifactPath()}/${MavenMetadata.FILE_NAME}" - checkInRepo(repository.snapshots, artifact) + val snapshots = repository.target(snapshots = true) + checkInRepo(snapshots, artifact) - if (repository.releases != repository.snapshots) { - checkInRepo(repository.releases, artifact) + if (!repository.hasOneTarget()) { + checkInRepo(repository.target(snapshots = false), artifact) } } @@ -74,9 +75,9 @@ open class CheckVersionIncrement : DefaultTask() { """ The version `$version` is already published to the Maven repository `$repoUrl`. Try incrementing the library version. - All available versions are: ${versions?.joinToString(separator = ", ")}. - - To disable this check, run Gradle with `-x $name`. + All available versions are: ${versions?.joinToString(separator = ", ")}. + + To disable this check, run Gradle with `-x $name`. """.trimIndent() ) } @@ -135,7 +136,7 @@ private data class MavenMetadata(var versioning: Versioning = Versioning()) { return try { val metadata = mapper.readValue(url, MavenMetadata::class.java) metadata - } catch (ignored: FileNotFoundException) { + } catch (_: FileNotFoundException) { null } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt index 1cfa7c2..67716d8 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt @@ -28,8 +28,8 @@ package io.spine.gradle.publish import com.google.auth.oauth2.GoogleCredentials import com.google.cloud.artifactregistry.auth.DefaultCredentialProvider -import io.spine.gradle.Credentials -import io.spine.gradle.Repository +import io.spine.gradle.repo.Credentials +import io.spine.gradle.repo.Repository import java.io.IOException import org.gradle.api.Project @@ -51,13 +51,15 @@ import org.gradle.api.Project * Ordering said hooks is a non-trivial operation and the result is usually quite fragile. * Thus, we choose to do this small piece of configuration manually. */ +@Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names internal object CloudArtifactRegistry { private const val spineRepoLocation = "https://europe-maven.pkg.dev/spine-event-engine" val repository = Repository( - releases = "${spineRepoLocation}/releases", - snapshots = "${spineRepoLocation}/snapshots", + name = "CloudArtifactRegistry", + releases = "$spineRepoLocation/releases", + snapshots = "$spineRepoLocation/snapshots", credentialValues = this::fetchGoogleCredentials ) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt index 7db15aa..624f5cb 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt @@ -26,15 +26,16 @@ package io.spine.gradle.publish -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository /** * CloudRepo Maven repository. * * There is a special treatment for this repository. Usually, fetching and publishing of artifacts * is performed via the same URL. But it is not true for CloudRepo. Fetching is performed via - * the public repository, and publishing via the private one. Their URLs differ in `/public` infix. + * the public repository and publishing via the private one. Their URLs differ in `/public` infix. */ +@Deprecated(message = "Please use `PublishingRepos.cloudArtifactRegistry` instead.") internal object CloudRepo { private const val name = "CloudRepo" diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt index 56a7407..152455d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt @@ -26,7 +26,7 @@ package io.spine.gradle.publish -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository import org.gradle.api.Project import org.gradle.api.publish.maven.MavenPublication @@ -36,7 +36,7 @@ import org.gradle.api.publish.maven.MavenPublication * * Such publications should be treated differently than [StandardJavaPublicationHandler], * which is created for a module. Instead, since the publications are already declared, - * this class only [assigns maven coordinates][copyProjectAttributes]. + * this class only [assigns Maven coordinates][copyProjectAttributes]. * * A module which declares custom publications must be specified in * the [SpinePublishing.modulesWithCustomPublishing] property. @@ -46,13 +46,25 @@ import org.gradle.api.publish.maven.MavenPublication * the [standard][org.gradle.api.publish.maven.MavenPublication] publication, and custom ones. * To have both standard and custom publications, please specify custom artifact IDs or * classifiers for each custom publication. + * + * @see StandardJavaPublicationHandler */ -internal class CustomPublicationHandler(project: Project, destinations: Set) : - PublicationHandler(project, destinations) { +internal class CustomPublicationHandler private constructor( + project: Project, + destinations: Set +) : PublicationHandler(project, destinations) { override fun handlePublications() { project.publications.forEach { (it as MavenPublication).copyProjectAttributes() } } + + companion object : HandlerFactory() { + override fun create( + project: Project, + destinations: Set, + vararg params: Any + ): CustomPublicationHandler = CustomPublicationHandler(project, destinations) + } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt index 1072139..df326b8 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt @@ -26,8 +26,8 @@ package io.spine.gradle.publish -import io.spine.gradle.Credentials -import io.spine.gradle.Repository +import io.spine.gradle.repo.Credentials +import io.spine.gradle.repo.Repository import io.spine.gradle.buildDirectory import net.lingala.zip4j.ZipFile import org.gradle.api.Project @@ -42,12 +42,12 @@ internal object GitHubPackages { */ fun repository(repoName: String): Repository { val githubActor: String = actor() + val url = "https://maven.pkg.github.com/SpineEventEngine/$repoName" return Repository( - name = "GitHub Packages", - releases = "https://maven.pkg.github.com/SpineEventEngine/$repoName", - snapshots = "https://maven.pkg.github.com/SpineEventEngine/$repoName", - credentialValues = { project -> project.credentialsWithToken(githubActor) } - ) + name = "GitHub-Packages", + releases = url, + snapshots = url + ) { project -> project.credentialsWithToken(githubActor) } } private fun actor(): String { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt index a4dbfc1..75c5413 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt @@ -27,12 +27,14 @@ package io.spine.gradle.publish import LicenseSettings -import io.spine.gradle.Repository import io.spine.gradle.isSnapshot +import io.spine.gradle.repo.Repository import org.gradle.api.Project import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.invocation.BuildInvocationDetails import org.gradle.api.publish.maven.MavenPublication import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.support.serviceOf /** * The name of the Maven Publishing Gradle plugin. @@ -42,26 +44,70 @@ private const val MAVEN_PUBLISH = "maven-publish" /** * Abstract base for handlers of publications in a project * with [spinePublishing] settings declared. + * + * @param project The project to which the handler is applied. + * @param destinations The repositories for publishing artifacts of this project. + * In a multi-module project the destinations can be re-defined by + * specifying custom values in + * the [`spinePublishing`][io.spine.gradle.publish.SpinePublishing.destinations] + * extension applied to the subproject. */ internal sealed class PublicationHandler( protected val project: Project, - private val destinations: Set + protected var destinations: Set ) { + /** + * Remembers if the [apply] function was called by this handler. + */ + private var applied: Boolean = false - fun apply() = with(project) { - if (!hasCustomPublishing) { - apply(plugin = MAVEN_PUBLISH) + /** + * Overwrites the [destinations] property with the given set. + */ + fun publishTo(alternativeDestinations: Set) { + if (alternativeDestinations.isEmpty()) { + project.logger.info( + "The project ${project.path} is not going to be published because" + + " the publication handler `${this@PublicationHandler}`" + + " got an empty set of new `destinations`." + ) } + destinations = alternativeDestinations + } - pluginManager.withPlugin(MAVEN_PUBLISH) { - handlePublications() - registerDestinations() - configurePublishTask(destinations) + /** + * Configures the publication of the associated [project]. + */ + fun apply() { + synchronized(project) { + if (applied) { + return + } + project.run { + // We apply the `maven-publish` plugin for modules with standard + // publishing automatically because they don't need custom DSL + // in their `build.gradle.kts` files. + // All the job is done by the `SpinePublishing` extension and + // `StandardPublicationHandler` instance associated with this project. + if (!hasCustomPublishing) { + apply(plugin = MAVEN_PUBLISH) + } + // And we do not apply the plugin for modules with custom publishing + // because they will need the `maven-publish` DSL to tune the publishing. + // Therefore, we only arrange the execution of our code when the plugin + // is applied. + pluginManager.withPlugin(MAVEN_PUBLISH) { + handlePublications() + registerDestinations() + configurePublishTask(destinations) + applied = true + } + } } } /** - * Either handles publications already declared in the given project, + * Either handles publications already declared in the associated [project] * or creates new ones. */ abstract fun handlePublications() @@ -108,6 +154,82 @@ internal sealed class PublicationHandler( } } } + + /** + * The abstract base for factories producing instances of classes + * derived from [io.spine.gradle.publish.PublicationHandler]. + * + * The factory maintains associations between a path of the project to + * its publication handler. + * + * If the handler already exists, its settings are updated when + * the [serving] factory method is called. + * + * Otherwise, a new handler is created and associated with the project. + * + * @param H The type of the publication handlers produced by this repository. + * @see serving + */ + abstract class HandlerFactory { + + /** + * Maps a project path suffixed with build start time to the associated publication handler. + * + * The suffix after the project path is needed to create a new handler + * for each build. We do not use Guava or other cache expecting the small amount + * of memory consumption of each publication handler. + */ + private val handlers = mutableMapOf() + + /** + * Computes the key for a publication handler taking the [project] and + * its build start time. + */ + private fun createKey(project: Project): String { + val buildService = project.gradle.serviceOf() + val buildStartedMillis = buildService.buildStartedTime + val localTime = java.time.Instant.ofEpochMilli(buildStartedMillis) + val key = "${project.path}-at-$localTime" + return key + } + + /** + * Obtains an instance of [PublicationHandler] for the given project. + * + * If the handler for the given [project] was already created, the handler + * gets new [destinations], [overwriting][publishTo] previously specified. + * + * @return the handler for the given project which would handle publishing to + * the specified [destinations]. + */ + fun serving(project: Project, destinations: Set, vararg params: Any): H { + synchronized(handlers) { + val key = createKey(project) + var handler = handlers[key] + if (handler == null) { + handler = create(project, destinations, *params) + handlers[key] = handler + } else { + handler.publishTo(destinations) + } + return handler + } + } + + /** + * Creates a new publication handler for the given project. + * + * @param project The project to which the handler applies. + * @param destinations The repositories for publishing artifacts of this project. + * @param params Optional parameters to be passed as constructor parameters for + * classes of the type [H]. + */ + protected abstract fun create( + project: Project, + destinations: Set, + vararg params: Any + ): H + } } /** @@ -116,10 +238,10 @@ internal sealed class PublicationHandler( */ private fun RepositoryHandler.register(project: Project, repository: Repository) { val isSnapshot = project.version.toString().isSnapshot() - val target = if (isSnapshot) repository.snapshots else repository.releases val credentials = repository.credentials(project.rootProject) maven { - url = project.uri(target) + name = repository.name(isSnapshot) + url = project.uri(repository.target(isSnapshot)) credentials { username = credentials?.username password = credentials?.password diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt index 5c140b6..0a24b56 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt @@ -27,7 +27,8 @@ package io.spine.gradle.publish import dokkaKotlinJar -import io.spine.gradle.Repository +import io.spine.gradle.isSnapshot +import io.spine.gradle.repo.Repository import io.spine.gradle.sourceSets import java.util.* import org.gradle.api.InvalidUserDataException @@ -57,6 +58,13 @@ internal val Project.publishingExtension: PublishingExtension internal val Project.publications: PublicationContainer get() = publishingExtension.publications +/** + * Obtains an instance, if available, of [SpinePublishing] extension + * applied to this project. + */ +internal val Project.localSpinePublishing: SpinePublishing? + get() = extensions.findByType() + /** * Obtains [SpinePublishing] extension from this [Project]. * @@ -65,7 +73,7 @@ internal val Project.publications: PublicationContainer */ internal val Project.spinePublishing: SpinePublishing get() { - val local = this.extensions.findByType() + val local = localSpinePublishing if (local != null) { return local } @@ -78,9 +86,16 @@ internal val Project.spinePublishing: SpinePublishing /** * Tells if this project has custom publishing. + * + * For a multi-module project this is checked by presence of this project + * in the list of [SpinePublishing.modulesWithCustomPublishing] of the root project. + * + * In a single-module project, the value of the [SpinePublishing.customPublishing] + * property is returned. */ internal val Project.hasCustomPublishing: Boolean - get() = spinePublishing.modulesWithCustomPublishing.contains(name) + get() = rootProject.spinePublishing.modulesWithCustomPublishing.contains(name) + || spinePublishing.customPublishing private const val PUBLISH_TASK = "publish" @@ -93,7 +108,7 @@ private const val PUBLISH_TASK = "publish" * Please note, task execution would not copy publications to the local Maven cache. * * @see - * Tasks | Maven Publish Plugin + * Tasks | The Maven Publish Plugin */ internal val TaskContainer.publish: TaskProvider get() = named(PUBLISH_TASK) @@ -169,10 +184,13 @@ private fun TaskContainer.registerCheckCredentialsTask( private fun Task.doLastCredentialsCheck(destinations: Set) { doLast { - val destinationsStr = destinations.joinToString(", ") { it.name } - logger.debug( - "Project '${project.name}': checking the credentials for repos: $destinationsStr." - ) + if (logger.isDebugEnabled) { + val isSnapshot = project.version.toString().isSnapshot() + val destinationsStr = destinations.joinToString(", ") { it.target(isSnapshot) } + logger.debug( + "Project '${project.name}': checking the credentials for repos: $destinationsStr." + ) + } destinations.forEach { it.ensureCredentials(project) } } } @@ -203,8 +221,8 @@ fun TaskContainer.excludeGoogleProtoFromArtifacts() { * Locates or creates `sourcesJar` task in this [Project]. * * The output of this task is a `jar` archive. The archive contains sources from `main` source set. - * The task makes sure that sources from the directories below will be included into - * a resulted archive: + * The task makes sure that sources from the directories below will be included + * in the resulting archive: * * - Kotlin * - Java @@ -248,8 +266,8 @@ internal fun Project.testJar(): TaskProvider = tasks.getOrCreate("testJar") * Locates or creates `javadocJar` task in this [Project]. * * The output of this task is a `jar` archive. The archive contains Javadoc, - * generated upon Java sources from `main` source set. If javadoc for Kotlin is also needed, - * apply Dokka plugin. It tunes `javadoc` task to generate docs upon Kotlin sources as well. + * generated upon Java sources from `main` source set. If Javadoc for Kotlin is also needed, + * apply the Dokka plugin. It tunes `javadoc` task to generate docs upon Kotlin sources as well. */ fun Project.javadocJar(): TaskProvider = tasks.getOrCreate("javadocJar") { archiveClassifier.set("javadoc") diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt index f383e68..eea6dc1 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt @@ -26,7 +26,7 @@ package io.spine.gradle.publish -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository /** * Repositories to which we may publish. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/ShadowJarExts.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ShadowJarExts.kt new file mode 100644 index 0000000..e2e67e0 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ShadowJarExts.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle.publish + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +/** + * Calls [ShadowJar.mergeServiceFiles] for the files we use in the Spine SDK. + */ +fun ShadowJar.handleMergingServiceFiles() { + ServiceFiles.all.forEach { + mergeServiceFiles(it) + } +} + +@Suppress("ConstPropertyName") +private object ServiceFiles { + + /** + * Files containing references to descriptor set files. + */ + private const val descriptorSetReferences = "desc.ref" + + private const val servicesDir = "META-INF/services" + /** + * Providers of custom Protobuf options introduced by the libraries. + */ + private const val optionProviders = "$servicesDir/io.spine.option.OptionsProvider" + + /** + * KSP symbol processor provider. + */ + private const val kspSymbolProcessorProviders = + "$servicesDir/com.google.devtools.ksp.KspSymbolProcessorProvider" + + /** + * Message routing setup classes generated by the Compiler for JVM. + */ + private const val routeSetupPackage = "io.spine.server.route.setup" + private const val routeSetupPrefix = "$servicesDir/$routeSetupPackage" + private const val commandRoutingSetupClasses = "$routeSetupPrefix.CommandRoutingSetup" + private const val eventRoutingSetupClasses = "$routeSetupPrefix.EventRoutingSetup" + private const val stateRoutingSetupClasses = "$routeSetupPrefix.StateRoutingSetup" + + val all = arrayOf( + descriptorSetReferences, + optionProviders, + kspSymbolProcessorProviders, + commandRoutingSetupClasses, + eventRoutingSetupClasses, + stateRoutingSetupClasses + ) +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt index 85c54bd..9dc7d7c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt @@ -28,11 +28,7 @@ package io.spine.gradle.publish -import productionModules -import productionModuleNames -import dokkaJavaJar -import dokkaKotlinJar -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository import org.gradle.api.Project import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.kotlin.dsl.apply @@ -91,6 +87,11 @@ import org.gradle.kotlin.dsl.findByType * modules = productionModuleNames * .minus("my-custom-module") * .toSet() + * + * modulesWithCustomPublishing = setOf( + * "my-custom-module" + * ) + * * // ... * } * ``` @@ -133,8 +134,8 @@ import org.gradle.kotlin.dsl.findByType * and can be disabled via [SpinePublishing.protoJar]. * * 3. [javadocJar] โ€” Javadoc, generated upon Java sources from the `main` source set. - * If Javadoc for Kotlin is also needed, apply the Dokka plugin. It tunes `javadoc` task to generate - * docs upon Kotlin sources as well. + * If Javadoc for Kotlin is also needed, apply the Dokka plugin. + * It tunes the `javadoc` task to generate docs upon Kotlin sources as well. * * 4. [dokkaKotlinJar] โ€” documentation generated by Dokka for Kotlin and Java sources * using the Kotlin API mode. @@ -146,6 +147,7 @@ import org.gradle.kotlin.dsl.findByType * of the `test` source set. Use [SpinePublishing.testJar] to enable its publishing. * * @see [artifacts] + * @see SpinePublishing */ fun Project.spinePublishing(block: SpinePublishing.() -> Unit) { apply() @@ -160,7 +162,16 @@ fun Project.spinePublishing(block: SpinePublishing.() -> Unit) { } /** - * A Gradle extension for setting up publishing of spine modules using `maven-publish` plugin. + * A Gradle extension for setting up publishing of modules of Spine SDK modules + * using `maven-publish` plugin. + * + * ### Implementation Note + * + * This extension is overloaded with responsibilities. + * It basically does what an extension AND a Gradle plugin would normally do. + * + * We [should introduce a plugin class](https://github.com/SpineEventEngine/config/issues/562) + * and move the code related to creating tasks or setting dependencies between them into the plugin. * * @param project The project in which the extension is opened. By default, this project will be * published as long as a [set][modules] of modules to publish is not specified explicitly. @@ -195,18 +206,27 @@ open class SpinePublishing(private val project: Project) { var modules: Set = emptySet() /** - * Controls whether the published module needs standard publications. + * Controls whether the [module][project] needs standard publications. + * + * Default value is `false`. * - * If `true`, the module should configure publications on its own. - * Otherwise, the extension will configure standard [ones][StandardJavaPublicationHandler]. + * In a single module [project], settings this property to `true` it tells + * that the project configures the publication in a specific way and + * [CustomPublicationHandler] should be used. + * Otherwise, the extension will configure the + * [standard publication][StandardJavaPublicationHandler]. * - * This property is analogue of [modulesWithCustomPublishing] for projects, + * This property is an analogue of [modulesWithCustomPublishing] in + * [multi-module][Project.getSubprojects] projects, * for which [spinePublishing] is configured individually. * - * Setting of this property and having a non-empty [modules] will lead - * to an exception. + * Setting of this property to `true` and having a non-empty [modules] property + * in the project to which the extension is applied will lead to [IllegalStateException]. * - * Default value is `false`. + * Settings this property to `true` in a subproject serves only the documentation purposes. + * This subproject still must be listed in the [modulesWithCustomPublishing] property in + * the extension of the [rootProject][Project.getRootProject], so that its publication + * can be configured in a specific way. */ var customPublishing = false @@ -230,9 +250,10 @@ open class SpinePublishing(private val project: Project) { * )} * ``` * - * Empty by default. + * If the property is not initialized, the destinations will be taken from + * the parent project. */ - var destinations: Set = emptySet() + lateinit var destinations: Set /** * A prefix to be added before the name of each artifact. @@ -269,7 +290,7 @@ open class SpinePublishing(private val project: Project) { * } * ``` * - * The resulting artifact is available under "proto" classifier. + * The resulting artifact is available under the "proto" classifier. * For example, in Gradle 7+, one could depend on it like this: * * ``` @@ -308,8 +329,8 @@ open class SpinePublishing(private val project: Project) { * } * ``` * - * The resulting artifact is available under "test" classifier. For example, - * in Gradle 7+, one could depend on it like this: + * The resulting artifact is available under the "test" classifier. + * For example, in Gradle 7+, one could depend on it like this: * * ``` * implementation("io.spine:spine-client:$version@test") @@ -404,21 +425,41 @@ open class SpinePublishing(private val project: Project) { * Let's suppose they are declared in a module's build file. It is a common practice. * But publishing of the module is configured from a root project's build file. * By the time when we need to specify them, we just don't know them. - * As the result, we have to use [Project.afterEvaluate] in order to guarantee that a module - * will be configured by the time we configure publishing for it. + * As the result, we have to use [Project.afterEvaluate] in order to guarantee that + * the module will be configured by the time we configure publishing for it. */ private fun Project.setUpPublishing(jarFlags: JarFlags) { val customPublishing = modulesWithCustomPublishing.contains(name) || customPublishing + val destinations = project.publishTo() val handler = if (customPublishing) { - CustomPublicationHandler(project, destinations) + CustomPublicationHandler.serving(project, destinations) } else { - StandardJavaPublicationHandler(project, jarFlags, destinations) + StandardJavaPublicationHandler.serving(project, destinations, jarFlags) } afterEvaluate { handler.apply() } } + /** + * Obtains the set of repositories for publishing. + * + * If there is a local instance of [io.spine.gradle.publish.SpinePublishing] extension, + * the [destinations] are obtained from this instance. + * Otherwise, the function attempts to obtain it from a [parent project][Project.getParent]. + * If there is no a parent project, an empty set is returned. + * + * The normal execution should end up at the root project of a multi-module project + * if there are no custom destinations specified by the local extension. + */ + private fun Project.publishTo(): Set { + val ext = localSpinePublishing + if (ext != null && ext::destinations.isInitialized) { + return destinations + } + return parent?.publishTo() ?: emptySet() + } + /** * Obtains an artifact ID for the given project. * @@ -437,8 +478,11 @@ open class SpinePublishing(private val project: Project) { private fun ensureProtoJarExclusionsArePublished() { val nonPublishedExclusions = protoJar.exclusions.minus(modules) if (nonPublishedExclusions.isNotEmpty()) { - throw IllegalStateException("One or more modules are marked as `excluded from proto " + - "JAR publication`, but they are not even published: $nonPublishedExclusions") + error( + "One or more modules are marked as" + + " `excluded from proto JAR publication`," + + " but they are not even published: $nonPublishedExclusions." + ) } } @@ -462,7 +506,7 @@ open class SpinePublishing(private val project: Project) { /** * Ensures that publishing of a module is configured only from a single place. * - * We allow configuration of publishing from two places - a root project and module itself. + * We allow configuration of publishing from two places - a root project and the module itself. * Here we verify that publishing of a module is not configured in both places simultaneously. */ private fun ensureModulesNotDuplicated() { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt index c1dde49..06d78c1 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt @@ -26,7 +26,7 @@ package io.spine.gradle.publish -import io.spine.gradle.Repository +import io.spine.gradle.repo.Repository import org.gradle.api.Project import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.TaskProvider @@ -40,32 +40,53 @@ import org.gradle.kotlin.dsl.create * A publication has a name and consists of one or more artifacts plus information about * those artifacts โ€“ the metadata. * - * An instance of this class represents [org.gradle.api.publish.maven.MavenPublication] named "mavenJava". It is generally - * accepted that a publication with this name contains a Java project published to one or - * more Maven repositories. + * An instance of this class represents + * [MavenPublication][org.gradle.api.publish.maven.MavenPublication] + * named [`"mavenJava"`][PUBLICATION_NAME]. + * It is generally accepted that a publication with this name contains a Java project + * published to one or more Maven repositories. * * By default, only a jar with the compilation output of `main` source set and its * metadata files are published. Other artifacts are specified through the - * [constructor parameter][jarFlags]. Please, take a look on [specifyArtifacts] for additional info. + * [constructor parameter][jarFlags]. + * Please take a look on [specifyArtifacts] for additional info. * * @param jarFlags The flags for additional JARs published along with the compilation output. * @param destinations Maven repositories to which the produced artifacts will be sent. * @see * The Maven Publish Plugin | Publications + * @see CustomPublicationHandler */ -internal class StandardJavaPublicationHandler( +internal class StandardJavaPublicationHandler private constructor( project: Project, private val jarFlags: JarFlags, destinations: Set, ) : PublicationHandler(project, destinations) { + companion object : HandlerFactory() { + + /** + * The name of the publication created by [StandardJavaPublicationHandler]. + */ + const val PUBLICATION_NAME = "mavenJava" + + override fun create( + project: Project, + destinations: Set, + vararg params: Any + ): StandardJavaPublicationHandler { + return StandardJavaPublicationHandler(project, params[0] as JarFlags, destinations) + } + } + /** - * Creates a new "mavenJava" [org.gradle.api.publish.maven.MavenPublication] in the given project. + * Creates a new `"mavenJava"` [MavenPublication][org.gradle.api.publish.maven.MavenPublication] + * in the [project] associated with this publication handler. */ override fun handlePublications() { val jars = project.artifacts(jarFlags) val publications = project.publications - publications.create("mavenJava") { + publications.create(PUBLICATION_NAME) { copyProjectAttributes() specifyArtifacts(jars) } @@ -87,11 +108,12 @@ internal class StandardJavaPublicationHandler( * * @see Maven โ€“ POM Reference * @see - * Understanding Gradle Module Metadata + * Understanding Gradle Module Metadata */ private fun MavenPublication.specifyArtifacts(jars: Set>) { - /* "java" component provides a jar with compilation output of "main" source set. + /* + "java" component provides a jar with compilation output of "main" source set. It is NOT defined as another `Jar` task intentionally. Doing that will leave the publication without correct ".pom" and ".module" metadata files generated. */ @@ -100,8 +122,9 @@ internal class StandardJavaPublicationHandler( from(it) } - /* Other artifacts are represented by `Jar` tasks. Those artifacts don't bring any other - metadata in comparison with `Component` (such as dependencies notation). + /* + Other artifacts are represented by `Jar` tasks. Those artifacts do not bring any other + metadata in comparison with `Component` (such as the `dependencies` notation). */ jars.forEach { artifact(it) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt similarity index 77% rename from buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt rename to buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt index 7e5b517..1624b38 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt @@ -24,17 +24,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.gradle.javadoc - -import org.gradle.api.tasks.TaskContainer -import org.gradle.api.tasks.javadoc.Javadoc - -/** - * Finds a [Javadoc] Gradle task by the passed name. - */ -fun TaskContainer.javadocTask(named: String) = this.getByName(named) as Javadoc +package io.spine.gradle.repo /** - * Finds a default [Javadoc] Gradle task. + * Password credentials for a Maven repository. */ -fun TaskContainer.javadocTask() = this.getByName("javadoc") as Javadoc +data class Credentials( + val username: String?, + val password: String? +) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/RepoSlug.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt similarity index 94% rename from buildSrc/src/main/kotlin/io/spine/gradle/RepoSlug.kt rename to buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt index 71fbac3..461c690 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/RepoSlug.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt @@ -24,13 +24,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.gradle +package io.spine.gradle.repo import org.gradle.api.GradleException /** * A name of a repository. */ +@Suppress("unused") class RepoSlug(val value: String) { companion object { @@ -44,7 +45,7 @@ class RepoSlug(val value: String) { /** * Reads `REPO_SLUG` environment variable and returns its value. * - * In case it is not set, a [GradleException] is thrown. + * In case it is not set, a [org.gradle.api.GradleException] is thrown. */ fun fromVar(): RepoSlug { val envValue = System.getenv(environmentVariable) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt new file mode 100644 index 0000000..43abe47 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@file:Suppress("TooManyFunctions") // Deprecated functions will be kept for a while. + +package io.spine.gradle.repo + +import io.spine.gradle.publish.PublishingRepos +import java.net.URI +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.kotlin.dsl.maven + +/** + * Registers the standard set of Maven repositories. + * + * To be used in `buildscript` clauses when a fully-qualified call must be made. + */ +@Suppress("unused") +@Deprecated( + message = "Please use `standardSpineSdkRepositories()`.", + replaceWith = ReplaceWith("standardSpineSdkRepositories()") +) +fun doApplyStandard(repositories: RepositoryHandler) = repositories.standardToSpineSdk() + +/** + * A scrambled version of PAT generated with the only "read:packages" scope. + * + * The scrambling around PAT is necessary because GitHub analyzes commits for the presence + * of tokens and invalidates them. + * + * @see + * How to make GitHub packages to the public + */ +private object Pat { + private const val shade = "_phg->8YlN->MFRA->gxIk->HVkm->eO6g->FqHJ->z8MS->H4zC->ZEPq" + private const val separator = "->" + private val chunks: Int = shade.split(separator).size - 1 + + fun credentials(): Credentials { + val pass = shade.replace(separator, "").splitAndReverse(chunks, "") + return Credentials("public", pass) + } + + /** + * Splits this string to the chunks, reverses each chunk, and joins them + * back to a string using the [separator]. + */ + private fun String.splitAndReverse(numChunks: Int, separator: String): String { + check(length / numChunks >= 2) { + "The number of chunks is too big. Must be <= ${length / 2}." + } + val chunks = chunked(length / numChunks) + val reversedChunks = chunks.map { chunk -> chunk.reversed() } + return reversedChunks.joinToString(separator) + } +} + +/** + * Adds a read-only view to all artifacts of the SpineEventEngine + * GitHub organization. + */ +fun RepositoryHandler.spineArtifacts(): MavenArtifactRepository = maven { + url = URI("https://maven.pkg.github.com/SpineEventEngine/*") + includeSpineOnly() + val pat = Pat.credentials() + credentials { + username = pat.username + password = pat.password + } +} + +val RepositoryHandler.intellijReleases: MavenArtifactRepository + get() = maven("https://www.jetbrains.com/intellij-repository/releases") + +val RepositoryHandler.jetBrainsCacheRedirector: MavenArtifactRepository + get() = maven("https://cache-redirector.jetbrains.com/intellij-dependencies") + +/** + * Applies repositories commonly used by Spine Event Engine projects. + */ +fun RepositoryHandler.standardToSpineSdk() { + spineArtifacts() + + @Suppress("DEPRECATION") // Still use `CloudRepo` for earlier versions. + val spineRepos = listOf( + Repos.spine, + Repos.spineSnapshots, + Repos.artifactRegistry, + Repos.artifactRegistrySnapshots + ) + + spineRepos + .map { URI(it) } + .forEach { + maven { + url = it + includeSpineOnly() + } + } + + intellijReleases + jetBrainsCacheRedirector + + maven { + url = URI(Repos.sonatypeSnapshots) + } + + mavenCentral() + gradlePluginPortal() + mavenLocal().includeSpineOnly() +} + +@Deprecated( + message = "Please use `standardToSpineSdk() instead.", + replaceWith = ReplaceWith("standardToSpineSdk()") +) +fun RepositoryHandler.applyStandard() = this.standardToSpineSdk() + +/** + * Defines names of additional repositories commonly used in the Spine SDK projects. + * + * @see [applyStandard] + */ +@Suppress( + "DEPRECATION" /* Still need to use `CloudRepo` for older versions. */, + "ConstPropertyName" // https://bit.ly/kotlin-prop-names +) +private object Repos { + @Deprecated(message = "Please use `cloudArtifactRegistry.releases` instead.") + val spine = io.spine.gradle.publish.CloudRepo.published.target(snapshots = false) + + @Deprecated(message = "Please use `artifactRegistry.snapshots` instead.") + val spineSnapshots = io.spine.gradle.publish.CloudRepo.published.target(snapshots = true) + + val artifactRegistry = PublishingRepos.cloudArtifactRegistry.target(snapshots = false) + val artifactRegistrySnapshots = PublishingRepos.cloudArtifactRegistry.target(snapshots = true) + + const val sonatypeSnapshots = "https://oss.sonatype.org/content/repositories/snapshots" +} + +/** + * Narrows down the search for this repository to Spine-related artifact groups. + */ +private fun MavenArtifactRepository.includeSpineOnly() { + content { + includeGroupByRegex("io\\.spine.*") + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt new file mode 100644 index 0000000..a586ffd --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle.repo + +import java.io.File +import java.util.Properties +import org.gradle.api.Project + +/** + * A Maven repository. + * + * @param name The human-readable name which is also used in the publishing task names + * for identifying the target repository. + * The name must match the [regex]. + * @param releases The URL for publishing release versions of artifacts. + * @param snapshots The URL for publishing [snapshot][io.spine.gradle.isSnapshot] versions. + * @param credentialsFile The path to the file which contains the credentials for the registry. + * @param credentialValues The function to obtain an instance of [Credentials] from + * a Gradle [Project], if [credentialsFile] is not specified. + */ +data class Repository( + private val name: String, + private val releases: String, + private val snapshots: String, + private val credentialsFile: String? = null, + private val credentialValues: ((Project) -> Credentials?)? = null +) { + + companion object { + val regex = Regex("[A-Za-z0-9_\\-.]+") + } + + init { + require(regex.matches(name)) { + "The repository name `$name` does not match the regex `$regex`." + } + } + + /** + * Obtains the name of the repository. + * + * The name will be primarily used in the publishing tasks. + * + * @param snapshots If `true` this repository is used for publishing snapshots, + * and the suffix `-snapshots` will be added to the value of the [name] property. + * Otherwise, the function returns just [name]. + */ + fun name(snapshots: Boolean): String = name + if (snapshots) "-snapshots" else "" + + /** + * Obtains the target URL of the repository for publishing. + */ + fun target(snapshots: Boolean): String = if (snapshots) this.snapshots else releases + + /** + * Tells if release and snapshot versions are published to the same destination + * of this repository. + */ + fun hasOneTarget() = snapshots == releases + + /** + * Obtains the publishing password credentials to this repository. + * + * If the credentials are represented by a `.properties` file, reads the file and parses + * the credentials. The file must have properties `user.name` and `user.password`, which store + * the username and the password for the Maven repository auth. + */ + fun credentials(project: Project): Credentials? = when { + credentialValues != null -> credentialValues.invoke(project) + credentialsFile != null -> credsFromFile(credentialsFile, project) + else -> throw IllegalArgumentException( + "Credentials file or a supplier function should be passed." + ) + } + + private fun credsFromFile(fileName: String, project: Project): Credentials? { + val file = project.rootProject.file(fileName) + if (file.exists().not()) { + return null + } + + val log = project.logger + log.info("Using credentials from `$fileName`.") + val creds = file.parseCredentials() + log.info("Publishing build as `${creds.username}`.") + return creds + } + + private fun File.parseCredentials(): Credentials { + val properties = Properties().apply { load(inputStream()) } + val username = properties.getProperty("user.name") + val password = properties.getProperty("user.password") + return Credentials(username, password) + } + + override fun equals(other: Any?): Boolean = when { + this === other -> true + other !is Repository -> false + else -> name == other.name && + releases == other.releases && + snapshots == other.snapshots +} + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + releases.hashCode() + result = 31 * result + snapshots.hashCode() + return result + } + + override fun toString(): String { + return name + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ModuleDataExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ModuleDataExtensions.kt index 0aca30f..91247e2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ModuleDataExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ModuleDataExtensions.kt @@ -91,7 +91,6 @@ private fun MarkdownDocument.print( return this } - /** * Prints the URL to the project which provides the dependency. * diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Template.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Template.kt index adda37b..15bd24d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Template.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Template.kt @@ -28,7 +28,7 @@ package io.spine.gradle.report.license import io.spine.docs.MarkdownDocument import io.spine.gradle.artifactId -import java.util.* +import java.util.Date import org.gradle.api.Project /** @@ -40,32 +40,33 @@ internal class Template( ) { private companion object { + @Suppress("ConstPropertyName") private const val longBreak = "\n\n" } - internal fun writeHeader() { + internal fun writeHeader() = with(project) { out.nl() - .h1( - "Dependencies of " + - "`${project.group}:${project.artifactId}:${project.version}`" - ) - .nl() + .h1("Dependencies of `$group:$artifactId:$version`") + .nl() } internal fun writeFooter() { + val currentTime = Date() out.text(longBreak) .text("The dependencies distributed under several licenses, ") .text("are used according their commercial-use-friendly license.") .text(longBreak) .text("This report was generated on ") - .bold("${Date()}") + .bold("$currentTime") .text(" using ") + .nl() .link( "Gradle-License-Report plugin", "https://github.com/jk1/Gradle-License-Report" ) .text(" by Evgeny Naumenko, ") .text("licensed under ") + .nl() .link( "Apache 2.0 License", "https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE" diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomFormatting.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomFormatting.kt index ba673bb..a29d0a4 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomFormatting.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomFormatting.kt @@ -92,7 +92,7 @@ internal object PomFormatting { * Writes the specified lines using the specified [destination], dividing them * by platform-specific line separator. * - * The written lines are also padded with platform's line separator from both sides + * The written lines are also padded with the platform's line separator from both sides. */ internal fun writeBlocks(destination: StringWriter, vararg lines: String) { lines.iterator().forEach { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt index 9a40725..e26c738 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt @@ -57,7 +57,8 @@ import org.gradle.api.plugins.BasePlugin * * By default, those values are taken from the `project` object, which may or may not include * them. If the project does not have these values, and they are not specified in the `ext` - * block, the resulting `pom.xml` file is going to contain empty blocks, e.g. ``. + * block, the resulting `pom.xml` file is going to contain empty blocks, + * e.g., ``. */ @Suppress("unused") object PomGenerator { @@ -68,7 +69,7 @@ object PomGenerator { fun applyTo(project: Project) { /** - * In some cases, the `base` plugin, which by default is added by e.g. `java`, + * In some cases, the `base` plugin, which by default is added by e.g., `java`, * is not yet added. * * The `base` plugin defines the `build` task. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt index 5312219..a0b1ade 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt @@ -35,9 +35,9 @@ import java.io.StringWriter /** * Writes the dependencies of a Gradle project and its subprojects as a `pom.xml` file. * - * The resulting file is not usable for `maven` build tasks, but serves rather as a description - * of the first-level dependencies for each project/subproject. Their transitive dependencies - * are not included into the result. + * The resulting file is not usable for `maven` build tasks but serves as a description + * of the first-level dependencies for each project or subproject. + * Their transitive dependencies are not included in the result. */ internal class PomXmlWriter internal constructor( @@ -51,12 +51,10 @@ internal constructor( *

If a file with the specified location exists, its contents will be substituted * with a new `pom.xml`. * - * @param file a file to write `pom.xml` contents to + * @param file a file to write `pom.xml` contents to. */ fun writeTo(file: File) { - val fileWriter = FileWriter(file) val out = StringWriter() - writeStart(out) writeBlocks( out, @@ -67,8 +65,9 @@ internal constructor( ) PomFormatting.writeEnd(out) - fileWriter.write(out.toString()) - fileWriter.close() + FileWriter(file).use { + it.write(out.toString()) + } } /** @@ -83,4 +82,3 @@ internal constructor( return destination.toString() } } - diff --git a/buildSrc/src/main/kotlin/kmp-module.gradle.kts b/buildSrc/src/main/kotlin/kmp-module.gradle.kts new file mode 100644 index 0000000..b23dc02 --- /dev/null +++ b/buildSrc/src/main/kotlin/kmp-module.gradle.kts @@ -0,0 +1,189 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import io.spine.dependency.boms.BomsPlugin +import io.spine.dependency.local.Reflect +import io.spine.dependency.local.TestLib +import io.spine.dependency.test.JUnit +import io.spine.dependency.test.Jacoco +import io.spine.dependency.test.Kotest +import io.spine.gradle.checkstyle.CheckStyleConfig +import io.spine.gradle.javac.configureJavac +import io.spine.gradle.kotlin.setFreeCompilerArgs +import io.spine.gradle.publish.IncrementGuard +import io.spine.gradle.report.license.LicenseReporter + +/** + * Configures this [Project] as a Kotlin Multiplatform module. + * + * By its nature, this script plugin is similar to `jvm-module`. It performs + * the basic module configuration. + * + * `jvm-module` is based on a mix of Java and Kotlin Gradle plugins. It allows + * usage of Kotlin and Java in a single module that is built for JVM. + * Whereas `kmp-module` is based on a Kotlin Multiplatform plugin. This plugin + * supports different compilation targets within a single module: JVM, IOS, + * Desktop, JS, etc. Also, it allows having some common sources in Kotlin + * that can be shared with target-specific code. They are located in + * `commonMain` and `commonTest` source sets. Each concrete target implicitly + * depends on them. + * + * As for now, this script configures only JVM target, but other targets + * will be added further. + * + * ### JVM target + * + * Sources for this target are placed in `jvmMain` and `jvmTest` directories. + * Java is allowed to be used in `jvm` sources, but Kotlin is a preference. + * Use Java only as a fall-back option where Kotlin is insufficient. + * Due to this, Java linters are not even configured by `kmp-module`. + * + * @see Kotlin Multiplatform docs + */ +@Suppress("unused") +val about = "" + +plugins { + kotlin("multiplatform") + id("detekt-code-analysis") + id("io.kotest.multiplatform") + id("org.jetbrains.kotlinx.kover") + `project-report` +} +apply() +apply() + +project.forceConfigurations() + +fun Project.forceConfigurations() { + with(configurations) { + forceVersions() + all { + resolutionStrategy { + force( + Reflect.lib + ) + } + } + } +} + +/** + * Configures Kotlin Multiplatform plugin. + * + * Please note, this extension DOES NOT configure Kotlin for JVM. + * It configures KMP, in which Kotlin for JVM is only one of + * possible targets. + */ +@Suppress("UNUSED_VARIABLE") // Avoid warnings for source set vars. +kotlin { + // Enables explicit API mode for any Kotlin sources within the module. + explicitApi() + + compilerOptions { + setFreeCompilerArgs() + } + + // Enables and configures JVM target. + jvm { + compilerOptions { + jvmTarget.set(BuildSettings.jvmTarget) + } + } + + // Dependencies are specified per-target. + // Please note, common sources are implicitly available in all targets. + @Suppress("unused") // source set `val`s are used implicitly. + sourceSets { + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + implementation(Kotest.assertions) + implementation(Kotest.frameworkEngine) + implementation(Kotest.datatest) + } + } + val jvmTest by getting { + dependencies { + implementation(dependencies.enforcedPlatform(JUnit.bom)) + implementation(TestLib.lib) + implementation(JUnit.Jupiter.engine) + implementation(Kotest.runnerJUnit5Jvm) + } + } + } +} + +java { + sourceCompatibility = BuildSettings.javaVersionCompat + targetCompatibility = BuildSettings.javaVersionCompat +} + + +/** + * Performs the standard task's configuration. + * + * Here's no difference with `jvm-module`, which does the same. + * + * Kotlin here is configured for both common and JVM-specific sources. + * Java is for JVM only. + * + * Also, Kotlin and Java share the same test executor (JUnit), so tests + * configuration is for both. + */ +tasks { + withType().configureEach { + configureJavac() + } +} + +/** + * Overrides the default location of Kotlin sources. + * + * The default configuration of Detekt assumes presence of Kotlin sources + * in `src/main/kotlin`, which is not the case for KMP. + */ +detekt { + source.setFrom( + "src/commonMain", + "src/jvmMain" + ) +} + +kover { + useJacoco(version = Jacoco.version) + reports { + total { + xml { + onCheck = true + } + } + } +} + +LicenseReporter.generateReportIn(project) +CheckStyleConfig.applyTo(project) diff --git a/buildSrc/src/main/kotlin/kmp-publish.gradle.kts b/buildSrc/src/main/kotlin/kmp-publish.gradle.kts new file mode 100644 index 0000000..53b0a21 --- /dev/null +++ b/buildSrc/src/main/kotlin/kmp-publish.gradle.kts @@ -0,0 +1,75 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.`maven-publish` +import org.gradle.kotlin.dsl.named + +/** + * Configures publications for `kmp-module`. + * + * As for now, [spinePublishing][io.spine.gradle.publish.spinePublishing] + * doesn't support Kotlin Multiplatform modules. So, their publications are + * configured by this script plugin. Other publishing-related configuration + * is still performed by the extension. + * + * To publish a KMP module, one still needs to open and configure + * `spinePublishing` extension. Make sure `spinePublishing.customPublishing` + * property is set to `true`, and this script plugin is applied. + * + * For example: + * + * ``` + * plugins { + * `kmp-module` + * `kmp-publish` + * } + * + * spinePublishing { + * destinations = setOf(...) + * customPublishing = true + * } + * ``` + */ +@Suppress("unused") +val about = "" + +plugins { + `maven-publish` + id("dokka-for-kotlin") +} + +publishing.publications { + named("kotlinMultiplatform") { + // Although, the "common artifact" can't be used independently + // of target artifacts, it is published with documentation. + artifact(project.dokkaKotlinJar()) + } + named("jvm") { + // Includes Kotlin (JVM + common) and Java documentation. + artifact(project.dokkaKotlinJar()) + } +} diff --git a/buildSrc/src/main/kotlin/module-testing.gradle.kts b/buildSrc/src/main/kotlin/module-testing.gradle.kts index ce9dfc0..715852c 100644 --- a/buildSrc/src/main/kotlin/module-testing.gradle.kts +++ b/buildSrc/src/main/kotlin/module-testing.gradle.kts @@ -27,11 +27,24 @@ import io.spine.dependency.lib.Guava import io.spine.dependency.local.TestLib import io.spine.dependency.test.JUnit +import io.spine.dependency.test.JUnit.Jupiter import io.spine.dependency.test.Kotest import io.spine.dependency.test.Truth import io.spine.gradle.testing.configureLogging import io.spine.gradle.testing.registerTestTasks +/** + * This convention plugin applies test dependencies and configures test-related tasks. + * + * The version of the [JUnit] platform must be applied via the [BomsPlugin][io.spine.dependency.boms.BomsPlugin]: + * + * ```kotlin + * apply() + * ``` + */ +@Suppress("unused") +private val about = "" + plugins { `java-library` } @@ -44,7 +57,8 @@ project.run { dependencies { forceJunitPlatform() - testImplementation(JUnit.Jupiter.api) + testImplementation(Jupiter.api) + testImplementation(Jupiter.params) testImplementation(JUnit.pioneer) testImplementation(Guava.testLib) @@ -53,7 +67,7 @@ dependencies { testImplementation(Kotest.assertions) testImplementation(Kotest.datatest) - testRuntimeOnly(JUnit.Jupiter.engine) + testRuntimeOnly(Jupiter.engine) } /** diff --git a/buildSrc/src/main/kotlin/module.gradle.kts b/buildSrc/src/main/kotlin/module.gradle.kts index 464feeb..1b40ec6 100644 --- a/buildSrc/src/main/kotlin/module.gradle.kts +++ b/buildSrc/src/main/kotlin/module.gradle.kts @@ -24,13 +24,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import BuildSettings.javaVersion +import io.spine.dependency.boms.BomsPlugin import io.spine.dependency.build.CheckerFramework +import io.spine.dependency.build.Dokka import io.spine.dependency.build.ErrorProne import io.spine.dependency.build.JSpecify -import io.spine.dependency.kotlinx.Serialization import io.spine.dependency.lib.Guava import io.spine.dependency.lib.Protobuf +import io.spine.dependency.local.Reflect import io.spine.dependency.local.Logging import io.spine.dependency.test.Jacoco import io.spine.gradle.checkstyle.CheckStyleConfig @@ -38,29 +39,30 @@ import io.spine.gradle.github.pages.updateGitHubPages import io.spine.gradle.javac.configureErrorProne import io.spine.gradle.javac.configureJavac import io.spine.gradle.javadoc.JavadocConfig -import io.spine.gradle.kotlin.applyJvmToolchain import io.spine.gradle.kotlin.setFreeCompilerArgs import io.spine.gradle.report.license.LicenseReporter plugins { `java-library` id("net.ltgt.errorprone") - kotlin("jvm") id("pmd-settings") - id("org.jetbrains.kotlinx.kover") id("project-report") - id("detekt-code-analysis") id("dokka-for-java") + kotlin("jvm") + id("io.kotest") + id("detekt-code-analysis") id("dokka-for-kotlin") + id("org.jetbrains.kotlinx.kover") + id("module-testing") } - +apply() LicenseReporter.generateReportIn(project) JavadocConfig.applyTo(project) CheckStyleConfig.applyTo(project) project.run { - configureJava(javaVersion) - configureKotlin(javaVersion) + configureJava() + configureKotlin() addDependencies() forceConfigurations() @@ -72,9 +74,10 @@ project.run { typealias Module = Project -fun Module.configureJava(javaVersion: JavaLanguageVersion) { +fun Module.configureJava() { java { - toolchain.languageVersion.set(javaVersion) + sourceCompatibility = BuildSettings.javaVersionCompat + targetCompatibility = BuildSettings.javaVersionCompat } tasks { @@ -85,9 +88,8 @@ fun Module.configureJava(javaVersion: JavaLanguageVersion) { } } -fun Module.configureKotlin(javaVersion: JavaLanguageVersion) { +fun Module.configureKotlin() { kotlin { - applyJvmToolchain(javaVersion.asInt()) explicitApi() compilerOptions { jvmTarget.set(BuildSettings.jvmTarget) @@ -116,16 +118,13 @@ fun Module.configureKotlin(javaVersion: JavaLanguageVersion) { */ fun Module.addDependencies() = dependencies { errorprone(ErrorProne.core) - api(JSpecify.annotations) + Protobuf.libs.forEach { api(it) } api(Guava.lib) compileOnlyApi(CheckerFramework.annotations) - ErrorProne.annotations.forEach { - compileOnlyApi(it) - } - - implementation(Logging.lib) + api(JSpecify.annotations) + ErrorProne.annotations.forEach { compileOnlyApi(it) } } fun Module.forceConfigurations() { @@ -136,7 +135,8 @@ fun Module.forceConfigurations() { resolutionStrategy { force( Logging.lib, - Serialization.bom + Dokka.BasePlugin.lib, + Reflect.lib, ) } } @@ -169,5 +169,3 @@ fun Module.configureGitHubPages() { rootFolder.set(rootDir) } } - - diff --git a/buildSrc/src/main/kotlin/pmd-settings.gradle.kts b/buildSrc/src/main/kotlin/pmd-settings.gradle.kts index 0373ee0..938a764 100644 --- a/buildSrc/src/main/kotlin/pmd-settings.gradle.kts +++ b/buildSrc/src/main/kotlin/pmd-settings.gradle.kts @@ -42,7 +42,7 @@ pmd { ruleSets = listOf() // Load PMD settings. - val pmdSettings = file("$rootDir/config/quality/pmd.xml") + val pmdSettings = file("$rootDir/buildSrc/quality/pmd.xml") val textResource: TextResource = resources.text.fromFile(pmdSettings) ruleSetConfig = textResource diff --git a/buildSrc/src/main/kotlin/uber-jar-module.gradle.kts b/buildSrc/src/main/kotlin/uber-jar-module.gradle.kts new file mode 100644 index 0000000..29dba3c --- /dev/null +++ b/buildSrc/src/main/kotlin/uber-jar-module.gradle.kts @@ -0,0 +1,202 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@file:Suppress("UnstableApiUsage") // `configurations` block. + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import io.spine.gradle.publish.IncrementGuard +import io.spine.gradle.publish.SpinePublishing +import io.spine.gradle.publish.spinePublishing +import io.spine.gradle.report.license.LicenseReporter + +plugins { + `java-library` + `maven-publish` + id("com.gradleup.shadow") + id("write-manifest") + `project-report` + idea +} +apply() +LicenseReporter.generateReportIn(project) + +spinePublishing { + artifactPrefix = "" + destinations = rootProject.the().destinations + customPublishing = true +} + +/** The ID of the far JAR artifact. */ +private val projectArtifact = project.name.replace(":", "") + +publishing { + val groupName = project.group.toString() + val versionName = project.version.toString() + + publications { + create("fatJar", MavenPublication::class) { + groupId = groupName + artifactId = projectArtifact + version = versionName + artifact(tasks.shadowJar) + } + } +} + +/** + * Declare dependency explicitly to address the Gradle error. + */ +@Suppress("unused") +val publishFatJarPublicationToMavenLocal: Task by tasks.getting { + dependsOn(tasks.shadowJar) +} + +// Disable the `jar` task to free up the name of the resulting archive. +tasks.jar { + enabled = false +} + +tasks.publish { + dependsOn(tasks.shadowJar) +} + +tasks.shadowJar { + excludeFiles() + setZip64(true) /* The archive has way too many items. So using the Zip64 mode. */ + archiveClassifier.set("") /** To prevent Gradle setting something like `osx-x86_64`. */ +} + +/** + * Exclude unwanted directories. + */ +@Suppress("LongMethod") +private fun ShadowJar.excludeFiles() { + exclude( + /* + Exclude IntelliJ Platform images and other resources associated with IntelliJ UI. + We do not call the UI, so they won't be used. + */ + "actions/**", + "chooser/**", + "codeStyle/**", + "codeStylePreview/**", + "codeWithMe/**", + "darcula/**", + "debugger/**", + "diff/**", + "duplicates/**", + "expui/**", + "extensions/**", + "fileTemplates/**", + "fileTypes/**", + "general/**", + "graph/**", + "gutter/**", + "hierarchy/**", + "icons/**", + "ide/**", + "idea/**", + "inlayProviders/**", + "inspectionDescriptions/**", + "inspectionReport/**", + "intentionDescriptions/**", + "javadoc/**", + "javaee/**", + "json/**", + "liveTemplates/**", + "mac/**", + "modules/**", + "nodes/**", + "objectBrowser/**", + "plugins/**", + "postfixTemplates/**", + "preferences/**", + "process/**", + "providers/**", + "runConfigurations/**", + "scope/**", + "search/**", + "toolbar/**", + "toolbarDecorator/**", + "toolwindows/**", + "vcs/**", + "webreferences/**", + "welcome/**", + "windows/**", + "xml/**", + + /* + Exclude `https://github.com/JetBrains/pty4j`. + We don't need the terminal. + */ + "resources/com/pti4j/**", + + /* Exclude the IntelliJ fork of + `http://www.sparetimelabs.com/purejavacomm/purejavacomm.php`. + It is the part of the IDEA's terminal implementation. + */ + "purejavacomm/**", + + /* Exclude IDEA project templates. */ + "resources/projectTemplates/**", + + /* + Exclude dynamic libraries. Should the tool users need them, + they would add them explicitly. + */ + "bin/**", + + /* + Exclude Google Protobuf definitions to avoid duplicates. + */ + "google/**", + "src/google/**", + + /** + * Exclude Spine Protobuf definitions to avoid duplications. + */ + "spine/**", + + /** + * Exclude Kotlin runtime because it will be provided. + */ + "kotlin/**", + "kotlinx/**", + + /** + * Exclude native libraries related to debugging. + */ + "win32-x86/**", + "win32-x86-64/**", + + /** + * Exclude the Windows process management (WinP) libraries. + * See: `https://github.com/jenkinsci/winp`. + */ + "winp.dll", + "winp.x64.dll", + ) +} diff --git a/buildSrc/src/main/resources/dokka/styles/custom-styles.css b/buildSrc/src/main/resources/dokka/styles/custom-styles.css index 8c442fe..ca629f9 100644 --- a/buildSrc/src/main/resources/dokka/styles/custom-styles.css +++ b/buildSrc/src/main/resources/dokka/styles/custom-styles.css @@ -1,11 +1,11 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2025, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Redistribution and use in source and/or binary forms, with or without * modification, must retain the above copyright notice and the following diff --git a/config b/config index 3a8b688..d7539c9 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 3a8b6886e6696185b8c53651c643e43cd508777f +Subproject commit d7539c900d623a9bd9a1c50a10eb40667afa878d diff --git a/dependencies.md b/dependencies.md index c272331..23920c7 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:spine-testlib:2.0.0-SNAPSHOT.202` +# Dependencies of `io.spine.tools:spine-testlib:2.0.0-SNAPSHOT.210` ## Runtime 1. **Group** : com.google.auto.value. **Name** : auto-value-annotations. **Version** : 1.10.2. @@ -45,15 +45,15 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -125,52 +125,64 @@ * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-bom. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) - * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 2.1.20. - * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) - * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : atomicfu. **Version** : 0.29.0. + * **Project URL:** [https://github.com/Kotlin/kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : atomicfu-jvm. **Version** : 0.29.0. + * **Project URL:** [https://github.com/Kotlin/kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-jdk8. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-jdk8. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. + * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. + * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : org.jspecify. **Name** : jspecify. **Version** : 1.0.0. * **Project URL:** [http://jspecify.org/](http://jspecify.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.junit. **Name** : junit-bom. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit. **Name** : junit-bom. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 1.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 1.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) 1. **Group** : org.opentest4j. **Name** : opentest4j. **Version** : 1.3.0. @@ -307,19 +319,19 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.30.2. +1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.31.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -535,10 +547,6 @@ * **License:** [Eclipse Public License v. 2.0](https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt) * **License:** [GNU General Public License, version 2 with the GNU Classpath Exception](https://www.gnu.org/software/classpath/license.html) -1. **Group** : javax.annotation. **Name** : javax.annotation-api. **Version** : 1.3.2. - * **Project URL:** [http://jcp.org/en/jsr/detail?id=250](http://jcp.org/en/jsr/detail?id=250) - * **License:** [CDDL + GPLv2 with classpath exception](https://github.com/javaee/javax.annotation/blob/master/LICENSE) - 1. **Group** : javax.inject. **Name** : javax.inject. **Version** : 1. * **Project URL:** [http://code.google.com/p/atinject/](http://code.google.com/p/atinject/) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -682,11 +690,15 @@ * **Project URL:** [https://github.com/JetBrains/intellij-deps-trove4j](https://github.com/JetBrains/intellij-deps-trove4j) * **License:** [GNU LESSER GENERAL PUBLIC LICENSE 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-api. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-bom. **Version** : 2.1.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-api. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-impl. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-impl. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -694,15 +706,15 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-embeddable. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-embeddable. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-runner. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-runner. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-client. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-client. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -710,15 +722,19 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-klib-commonizer-embeddable. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-klib-commonizer-embeddable. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.0.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -726,39 +742,47 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-common. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-common. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-embeddable. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-embeddable. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-impl-embeddable. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-impl-embeddable. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-jvm. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-jvm. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.1.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 2.1.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 2.1.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 2.0.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -766,45 +790,49 @@ * **Project URL:** [https://github.com/Kotlin/kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.7.3. - * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : atomicfu. **Version** : 0.29.0. + * **Project URL:** [https://github.com/Kotlin/kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : atomicfu-jvm. **Version** : 0.29.0. + * **Project URL:** [https://github.com/Kotlin/kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.7.3. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.6.4. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.7.3. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-jdk8. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-test. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-jdk8. **Version** : 1.8.0. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-test-jvm. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-test. **Version** : 1.8.0. - * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. + * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-test-jvm. **Version** : 1.8.0. - * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. + * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-html-jvm. **Version** : 0.8.1. * **Project URL:** [https://github.com/Kotlin/kotlinx.html](https://github.com/Kotlin/kotlinx.html) @@ -838,36 +866,36 @@ * **Project URL:** [http://jspecify.org/](http://jspecify.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.junit. **Name** : junit-bom. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit. **Name** : junit-bom. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) 1. **Group** : org.junit-pioneer. **Name** : junit-pioneer. **Version** : 2.3.0. * **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 5.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 5.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 1.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 1.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 1.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 1.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) -1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 1.12.2. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 1.13.2. + * **Project URL:** [https://junit.org/](https://junit.org/) * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) 1. **Group** : org.opentest4j. **Name** : opentest4j. **Version** : 1.3.0. @@ -921,4 +949,6 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Apr 22 14:52:09 WEST 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sat Sep 20 23:45:13 WEST 2025** using +[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under +[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 656f9d9..dc44c88 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine.tools testlib -2.0.0-SNAPSHOT.202 +2.0.0-SNAPSHOT.210 2015 @@ -44,19 +44,19 @@ all modules and does not describe the project structure per-subproject. com.google.protobuf protobuf-java - 4.30.2 + 4.31.1 compile com.google.protobuf protobuf-java-util - 4.30.2 + 4.31.1 compile com.google.protobuf protobuf-kotlin - 4.30.2 + 4.31.1 compile @@ -86,13 +86,25 @@ all modules and does not describe the project structure per-subproject. io.spine spine-logging - 2.0.0-SNAPSHOT.242 + 2.0.0-SNAPSHOT.411 + compile + + + org.jetbrains.kotlin + kotlin-bom + 2.1.21 compile org.jetbrains.kotlin kotlin-stdlib - 2.1.20 + 2.1.21 + compile + + + org.jetbrains.kotlinx + kotlinx-coroutines-bom + 1.10.2 compile @@ -104,13 +116,13 @@ all modules and does not describe the project structure per-subproject. org.junit junit-bom - 5.12.2 + 5.13.2 compile org.junit.jupiter junit-jupiter-api - null + 5.13.2 compile @@ -128,19 +140,19 @@ all modules and does not describe the project structure per-subproject. io.spine spine-logging-std-context - 2.0.0-SNAPSHOT.242 + 2.0.0-SNAPSHOT.411 test io.spine.tools spine-logging-testlib - 2.0.0-SNAPSHOT.242 + 2.0.0-SNAPSHOT.411 test io.spine.tools spine-testlib - 2.0.0-SNAPSHOT.185 + 2.0.0-SNAPSHOT.202 test @@ -152,7 +164,13 @@ all modules and does not describe the project structure per-subproject. org.junit.jupiter junit-jupiter-engine - null + 5.13.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.13.2 test @@ -180,7 +198,7 @@ all modules and does not describe the project structure per-subproject. com.google.protobuf protoc - 4.30.2 + 4.31.1 com.puppycrawl.tools @@ -256,22 +274,22 @@ all modules and does not describe the project structure per-subproject. org.jetbrains.kotlin kotlin-build-tools-impl - 2.1.20 + 2.1.21 org.jetbrains.kotlin kotlin-compiler-embeddable - 2.1.20 + 2.1.21 org.jetbrains.kotlin kotlin-klib-commonizer-embeddable - 2.1.20 + 2.1.21 org.jetbrains.kotlin kotlin-scripting-compiler-embeddable - 2.1.20 + 2.1.21 diff --git a/src/main/java/io/spine/testing/Testing.java b/src/main/java/io/spine/testing/Testing.java index 25b2d71..0db9f21 100644 --- a/src/main/java/io/spine/testing/Testing.java +++ b/src/main/java/io/spine/testing/Testing.java @@ -49,7 +49,7 @@ */ public final class Testing { - private static final Logger logger = LoggingFactory.forEnclosingClass(); + private static final Logger logger = LoggingFactory.forEnclosingClass(); /** Prevent instantiation of this utility class. */ private Testing() { diff --git a/src/main/kotlin/io/spine/testing/logging/mute/Muting.kt b/src/main/kotlin/io/spine/testing/logging/mute/Muting.kt index 9d39b05..c803d74 100644 --- a/src/main/kotlin/io/spine/testing/logging/mute/Muting.kt +++ b/src/main/kotlin/io/spine/testing/logging/mute/Muting.kt @@ -45,7 +45,10 @@ public fun withLoggingMutedIn(vararg loggerNames: String, block: () -> Unit) { public fun withLoggingMutedIn(loggerNames: Iterable, block: () -> Unit) { val levels = loggerNames.associateWith { Level.OFF } val logLevelMap = LogLevelMap.create(levels) - ScopedLoggingContext.newContext().withLogLevelMap(logLevelMap).execute { - block() - } + ScopedLoggingContext.getInstance() + .newContext() + .withLogLevelMap(logLevelMap) + .call { + block() + } } diff --git a/src/test/kotlin/io/spine/testing/logging/mute/MuteLoggingExtensionSpec.kt b/src/test/kotlin/io/spine/testing/logging/mute/MuteLoggingExtensionSpec.kt index 927f91c..76a9010 100644 --- a/src/test/kotlin/io/spine/testing/logging/mute/MuteLoggingExtensionSpec.kt +++ b/src/test/kotlin/io/spine/testing/logging/mute/MuteLoggingExtensionSpec.kt @@ -143,7 +143,7 @@ private class LoggingStub { } companion object { - private val logger: Logger<*> = LoggingFactory.forEnclosingClass() + private val logger: Logger = LoggingFactory.forEnclosingClass() } } @@ -159,7 +159,7 @@ private class StubContext(private val executionThrowable: Throwable?) : Extensio override fun getTags(): Set = ImmutableSet.of() override fun getElement(): Optional = Optional.empty() override fun getTestClass(): Optional> = Optional.empty() - override fun getEnclosingTestClasses(): List?>? = emptyList() + override fun getEnclosingTestClasses(): List?> = emptyList() override fun getTestInstanceLifecycle(): Optional = Optional.empty() override fun getTestInstance(): Optional = Optional.empty() override fun getTestInstances(): Optional = Optional.empty() @@ -188,6 +188,11 @@ private class StubContext(private val executionThrowable: Throwable?) : Extensio ) = Unit override fun getStore(namespace: ExtensionContext.Namespace): ExtensionContext.Store? = null + override fun getStore( + scope: ExtensionContext.StoreScope?, + namespace: ExtensionContext.Namespace? + ): ExtensionContext.Store? = null + override fun getExecutionMode(): ExecutionMode = ExecutionMode.SAME_THREAD override fun getExecutableInvoker(): ExecutableInvoker? = null } diff --git a/version.gradle.kts b/version.gradle.kts index 754542f..a98b219 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -24,4 +24,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.202") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.210")