From cda348ad8d7c2bc021ab2074bfd9e468d1514e45 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 24 Jul 2025 18:26:39 +0100 Subject: [PATCH 01/51] Add AI agent guidelines --- .agents/_TOC.md | 20 +++ .agents/advanced-safety-rules.md | 6 + .agents/coding-guidelines.md | 39 +++++ .agents/common-tasks.md | 6 + .agents/documentation-guidelines.md | 14 ++ .agents/documentation-tasks.md | 24 +++ .../documentation-typography-and-structure.md | 145 ++++++++++++++++++ .agents/interaction-tips.md | 20 +++ .agents/java-kotlin-conversion.md | 58 +++++++ .agents/llm-goals.md | 20 +++ .agents/project-overview.md | 8 + .agents/project-structure-expectations.md | 21 +++ .agents/purpose.md | 20 +++ .agents/quick-reference-card.md | 9 ++ .agents/refactoring-guidelines.md | 3 + .agents/running-builds.md | 18 +++ .agents/safety-rules.md | 7 + .agents/testing.md | 8 + .agents/version-policy.md | 31 ++++ .agents/widow-runt-orphan-river.jpg | Bin 0 -> 106385 bytes .claude/settings.local.json | 15 ++ .junie/guidelines.md | 21 +++ AGENTS.md | 18 +++ CLAUDE.md | 17 ++ GEMINI.md | 1 + 25 files changed, 549 insertions(+) create mode 100644 .agents/_TOC.md create mode 100644 .agents/advanced-safety-rules.md create mode 100644 .agents/coding-guidelines.md create mode 100644 .agents/common-tasks.md create mode 100644 .agents/documentation-guidelines.md create mode 100644 .agents/documentation-tasks.md create mode 100644 .agents/documentation-typography-and-structure.md create mode 100644 .agents/interaction-tips.md create mode 100644 .agents/java-kotlin-conversion.md create mode 100644 .agents/llm-goals.md create mode 100644 .agents/project-overview.md create mode 100644 .agents/project-structure-expectations.md create mode 100644 .agents/purpose.md create mode 100644 .agents/quick-reference-card.md create mode 100644 .agents/refactoring-guidelines.md create mode 100644 .agents/running-builds.md create mode 100644 .agents/safety-rules.md create mode 100644 .agents/testing.md create mode 100644 .agents/version-policy.md create mode 100644 .agents/widow-runt-orphan-river.jpg create mode 100644 .claude/settings.local.json create mode 100644 .junie/guidelines.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 GEMINI.md diff --git a/.agents/_TOC.md b/.agents/_TOC.md new file mode 100644 index 0000000..9efbf3d --- /dev/null +++ b/.agents/_TOC.md @@ -0,0 +1,20 @@ +# Table of Contents + +1. [Quick Reference Card](quick-reference-card.md) +2. [Purpose](purpose.md) +3. [Project overview](project-overview.md) +4. [Coding guidelines](coding-guidelines.md) +5. [Documentation & comments](documentation-guidelines.md) +6. [Documentation typography & structure](documentation-typography-and-structure.md) +7. [Documentation tasks](documentation-tasks.md) +8. [Running builds](running-builds.md) +9. [Version policy](version-policy.md) +10. [Project structure expectations](project-structure-expectations.md) +11. [Testing](testing.md) +12. [Safety rules](safety-rules.md) +13. [Advanced safety rules](advanced-safety-rules.md) +14. [Refactoring guidelines](refactoring-guidelines.md) +15. [Interaction tips – key to effective collaboration!](interaction-tips.md) +16. [LLM goals](llm-goals.md) + - [Problem-solving framework](llm-goals.md#problem-solving-framework-for-complex-tasks) +17. [Common tasks](common-tasks.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..25e40c9 --- /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..1c8b0bf --- /dev/null +++ b/.agents/documentation-tasks.md @@ -0,0 +1,24 @@ +# πŸ“„ Documentation tasks + +1. Ensure all public and internal APIs have KDoc examples. +2. Add in-line code blocks for clarity in tests. +3. Use `TODO` comments with agent names for unresolved logic sections: + ``` + // TODO(chatgpt): Refactor `EventStore` for better CQRS compliance. + ``` +4. 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]" + ``` + +5. 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/documentation-typography-and-structure.md b/.agents/documentation-typography-and-structure.md new file mode 100644 index 0000000..56526a5 --- /dev/null +++ b/.agents/documentation-typography-and-structure.md @@ -0,0 +1,145 @@ +# Documentation Typography & Structure + +This guide adapts principles from **MIL-STD-961** and **Apple Human Interface Guidelines** for use +with AI agents, with a focus on improving Kotlin KDoc documentation. + +## Goals +- Improve **text flow** and **line width** in KDoc blocks. +- Ensure **consistent formatting**, **clarity**, and **navigability**. +- Enable AI tools to reliably generate or refactor documentation based on structured patterns. + +--- + +## 1. General Formatting Rules + +### 1.1 Code Width +- **Maximum line width for code:** `100 characters` +- **Maximum line width for documentation:** `90 characters` + +### 1.2 Font Style and Presentation +- Use **Markdown-style formatting** within KDoc where supported (e.g., `inline code`). +- Format code blocks with fences and language identifiers: + ```kotlin + fun example() { + // Implementation + } + ``` + +### External links in `@see` tag + +- The `@see` tag is for [referencing API elements](https://kotlinlang.org/docs/kotlin-doc.html#see-identifier). +- External links are [not officially supported](https://github.com/Kotlin/dokka/issues/518). +- External links CAN be added when migrating from Javadoc. +- Format is: + ```kotlin + /** + * Documentation text. + * + * @see Link title + */ + ``` + +### 1.3 Line Wrapping +- Hard-wrap KDoc lines at **90 characters** to improve readability in side-by-side editors and diffs. +- **Provide** text flow **convenient for human readers**. +- **Prefer** starting new sentences or breaking after commands on new lines **over filling** the width. + +--- + +## 2. Sectioning and Navigation + +### 2.1 Structure of a KDoc Block +Use the following standard structure in multi-line KDoc blocks: + +```kotlin +/** + * [Short summary sentence.] + * + * [Detailed description. Wrap at 90 characters.] + * + * @param paramName [Description of the parameter.] + * @return [Description of the return value.] + * @throws SomeException [Reason for throwing.] + */ +``` + +> **Note:** Start `@param`, `@return`, and `@throws` descriptions with a **capital letter** and +> end with a **period**. This follows guidance from Apple, Microsoft, and military documentation +> (MIL-STD-961F), all of which encourage clarity and full-sentence formatting. +> While Java Javadoc traditionally uses lowercase fragments, Kotlin documentation benefits +> from full-sentence style for better readability and tooling support. + +### 2.2 Headers and Subsections +- Do **not** use Markdown headers level 1 and level 2 (`#`, `##`) in KDoc. +- Use spacing and keywords (`@param`, `@return`, `@throws`) to denote sections. + +### 2.3 Ordering for Navigability +Always order tags as follows for consistency: +1. `@param` (in declaration order) +2. `@return` +3. `@throws` +4. `@see` / `@sample` + +--- + +## 3. Clarity and Language + +### 3.1 Sentence Style +- Use **complete sentences** with a subject and verb. +- Start with a **capital letter**. +- End with a **period**, unless it's a list item or tag line. + +### 3.2 Vocabulary and Tone +- Be **neutral**, **precise**, and **audience-aware**. +- Avoid unnecessary jargon. +- Favor active voice: β€œInitializes the engine,” not β€œThe engine is initialized.” + +### 3.3 Lists and Steps +Use hyphen lists (`-`) or numbered lists (`1.`, `2.`) inside KDoc only if supported by tooling. + +```kotlin +/** + * Loads a configuration: + * - Reads from disk. + * - Parses YAML. + * - Caches the result. + */ +``` + +--- + +## 4. Punctuation and Consistency + +- Use **Oxford commas** for clarity in lists. +- Always end descriptive lines with a **period**. +- For `@param`, `@return`, etc., write the description in sentence case. + +```kotlin +@param timeout The time in milliseconds before the operation times out. +``` + +--- + +## 5. AI Agent Instructions + +When generating or modifying Kotlin code with KDoc: + +- **Ensure** documentation for `public` and `internal` API exists. +- **Wrap** text at 90 characters. +- **Preserve** the structure defined above. +- **Correct grammar and punctuation**. +- **Infer tag content** when possible, based on function signature. +- **Avoid duplication** between summary and detailed description. +- **Omit boilerplate** such as β€œThis function...” in summaries. +- **ALWAYS WRAP the modified Kotlin code at 100 characters** or `detekt` build error will occur. + +--- + +## References +- MIL-STD-961F: Department of Defense Standard Practice for Defense Specifications. +- Apple Human Interface Guidelines: [Typography](https://developer.apple.com/design/human-interface-guidelines/foundations/typography/) + +--- + +End of guide. + diff --git a/.agents/interaction-tips.md b/.agents/interaction-tips.md new file mode 100644 index 0000000..20c790f --- /dev/null +++ b/.agents/interaction-tips.md @@ -0,0 +1,20 @@ +# πŸ’¬ Interaction tips – key to effective collaboration! + +- Human programmers may use inline comments to guide agents: + ```kotlin + // ChatGPT: Suggest a refactor for better readability. + // Codex: Complete the missing branches in this `when` block. + // ChatGPT: explain this logic. + // Codex: complete this function. + ``` +- Agents should ensure pull request messages are concise and descriptive: + ```text + feat(chatgpt): suggested DSL refactoring for query handlers + fix(codex): completed missing case in sealed class hierarchy + ``` +- Encourage `// TODO:` or `// FIXME:` comments to be clarified by ChatGPT. + +- When agents or humans add TODO comments, they **must** follow the format described on + the [dedicated page][todo-comments]. + +[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments diff --git a/.agents/java-kotlin-conversion.md b/.agents/java-kotlin-conversion.md new file mode 100644 index 0000000..6841d9e --- /dev/null +++ b/.agents/java-kotlin-conversion.md @@ -0,0 +1,58 @@ +# πŸͺ„ 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. +* Suppress `detekt` build errors using this format: `[Error]` with `@Suppress("Error")`. + +## Documentation Conversion + +* Convert `@param` to `@param` with the same description: + - Start the description on the same line with the `@param` tag + - Break at 90 chars + - Continue with the margin of 7 space chars. + +* 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 (`). + +## Conversion tasks + * Convert end-line comments above methods and fields to KDoc. + * Preserve the content of original comments but convert the format and markup. + * Always use braces `{}` for `if`, `for`, and `while` statements. + * If the converted function has more than two `return` statements, annotate it with `@Suppress("ReturnCount")`. + * Keep inline comments from the original code. + * Ensure a single new line at the end of the produced Kotlin file without asking. + + diff --git a/.agents/llm-goals.md b/.agents/llm-goals.md new file mode 100644 index 0000000..17eae9c --- /dev/null +++ b/.agents/llm-goals.md @@ -0,0 +1,20 @@ +# 🧭 LLM goals + +These goals guide how agents (ChatGPT, Codex) are used in this project to: +- Help developers move faster without sacrificing code quality. +- Provide language-aware guidance on Kotlin/Java idioms. +- Lower the barrier to onboarding new contributors. +- Enable collaborative, explainable, and auditable development with AI. + +## Problem-solving framework for complex tasks + +When faced with complex tasks, follow this framework: + +1. **Decompose**: Break down the problem into smaller, manageable parts. +2. **Analyze**: Understand the architectural implications of each part. +3. **Pattern-Match**: Identify established patterns that apply. +4. **Implement**: Write code that follows project conventions. +5. **Test**: Ensure comprehensive test coverage. +6. **Document**: Provide clear explanations of your solution. + +*This framework helps maintain consistency across contributions from different agents.* diff --git a/.agents/project-overview.md b/.agents/project-overview.md new file mode 100644 index 0000000..69c58eb --- /dev/null +++ b/.agents/project-overview.md @@ -0,0 +1,8 @@ +# πŸ› οΈ Project overview + +- **Languages**: Kotlin (primary), Java (secondary). +- **Build tool**: Gradle with Kotlin DSL. +- **Architecture**: Event-driven Command Query Responsibility Segregation (CQRS). +- **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..1362a51 --- /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 # LLM agent instructions (this file) +version.gradle.kts # Declares the project version. +``` diff --git a/.agents/purpose.md b/.agents/purpose.md new file mode 100644 index 0000000..d6aeebb --- /dev/null +++ b/.agents/purpose.md @@ -0,0 +1,20 @@ +# 🧠 Purpose + +> **EXECUTIVE SUMMARY**: This guide outlines how AI agents (ChatGPT, Codex, Junie, Claude, Gemini) +> collaborate on our Kotlin/Java project. It defines responsibilities, coding standards, +> and workflows to maintain high code quality and architectural integrity. + +It outlines: + +- Agent responsibilities (who does what). +- Coding and architectural guidelines agents must follow. +- Instructions for creating and testing agent-generated outputs. + +Whether you are a developer, tester, or contributor, this guide helps you collaborate +with AI to maintain a high-quality codebase. + +## Terminology +- **LLM**: Refers to the general category of language models (e.g., ChatGPT, Codex, Claude, Junie). +- **Agents**: A broader term for LLMs collaborating on this project. +- Use specific names (**ChatGPT**, **Codex**) when they excel at different tasks + (e.g., scaffolding versus explanation). diff --git a/.agents/quick-reference-card.md b/.agents/quick-reference-card.md new file mode 100644 index 0000000..b0294dc --- /dev/null +++ b/.agents/quick-reference-card.md @@ -0,0 +1,9 @@ +# πŸ“ Quick Reference Card + +``` +πŸ”‘ Key Information: +- Kotlin/Java project +- 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..818a13f --- /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..01988fd --- /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..5a4c347 --- /dev/null +++ b/.agents/safety-rules.md @@ -0,0 +1,7 @@ +# Safety rules +- ❌ DO NOT modify, create, or delete files under the subdirectory named `config/` in the root of a project. +- βœ… 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..152d5d2 --- /dev/null +++ b/.agents/version-policy.md @@ -0,0 +1,31 @@ +# 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. + - Do NOT bump the version when continuing working on the same branch. diff --git a/.agents/widow-runt-orphan-river.jpg b/.agents/widow-runt-orphan-river.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a5b56808ddd8dc67b536afec06675467f916013 GIT binary patch literal 106385 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 |4`R${RU+yfyqxHr@RN8{I2> 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 Date: Thu, 24 Jul 2025 18:27:07 +0100 Subject: [PATCH 02/51] Bump version -> `2.0.0-SNAPSHOT.192` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 0bde0f3..30024af 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.191") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.192") From b523fa34ea74ac2d4f917cc55d7c15556aaa24c4 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 24 Jul 2025 18:48:09 +0100 Subject: [PATCH 03/51] Support finding companion objects --- .../kotlin/io/spine/reflect/CallerFinder.kt | 44 ++++++++----------- .../spine/reflect/StackWalkerStackGetter.kt | 20 ++++++--- .../io/spine/reflect/ThrowableStackGetter.kt | 5 +-- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/CallerFinder.kt b/src/main/kotlin/io/spine/reflect/CallerFinder.kt index c626bf3..42e5c90 100644 --- a/src/main/kotlin/io/spine/reflect/CallerFinder.kt +++ b/src/main/kotlin/io/spine/reflect/CallerFinder.kt @@ -36,44 +36,39 @@ package io.spine.reflect */ public object CallerFinder { - private val STACK_GETTER by lazy { + private val stackGetter by lazy { createBestStackGetter() } /** * Returns the stack trace element of the immediate caller of the specified class. * - * @param target - * the target class whose callers we are looking for. - * @param skip - * the minimum number of calls known to have occurred between the first call to the - * target class and the point at which the specified throwable was created. - * If in doubt, specify zero here to avoid accidentally skipping past the caller. - * This is particularly important for code which might be used in Android, since you - * cannot know whether a tool such as Proguard has merged methods or classes and - * reduced the number of intermediate stack frames. + * @param target The target class whose callers we are looking for. + * @param skip The minimum number of calls known to have occurred between the first call to the + * target class and the point at which the specified throwable was created. + * If in doubt, specify zero here to avoid accidentally skipping past the caller. + * This is particularly important for code which might be used in Android, since you + * cannot know whether a tool such as Proguard has merged methods or classes and + * reduced the number of intermediate stack frames. * @return the stack trace element representing the immediate caller of the specified class, or - * `null` if no caller was found (due to incorrect target, wrong skip count or - * use of JNI). + * `null` if no caller was found (due to incorrect target, wrong skip count or + * use of JNI). */ @JvmStatic public fun findCallerOf(target: Class<*>?, skip: Int): StackTraceElement? { checkSkipCount(skip) - return STACK_GETTER.callerOf(target!!, skip + 1) + return stackGetter.callerOf(target!!, skip + 1) } /** * Returns a synthetic stack trace starting at the immediate caller of the specified target. * - * @param target - * the class who is the caller the returned stack trace will start at. - * @param maxDepth - * the maximum size of the returned stack (pass -1 for the complete stack). - * @param skip - * the minimum number of stack frames to skip before looking for callers. + * @param target The class who is the caller the returned stack trace will start at. + * @param maxDepth The maximum size of the returned stack (pass -1 for the complete stack). + * @param skip The minimum number of stack frames to skip before looking for callers. * @return a synthetic stack trace starting at the immediate caller of the specified target, or - * the empty array if no caller was found (due to incorrect target, wrong skip count or - * use of JNI). + * the empty array if no caller was found (due to incorrect target, wrong skip count or + * use of JNI). */ @JvmStatic public fun stackForCallerOf( @@ -83,7 +78,7 @@ public object CallerFinder { ): Array { require((maxDepth > 0 || maxDepth == -1)) { "invalid maximum depth: $maxDepth." } checkSkipCount(skip) - return STACK_GETTER.stackForCaller(target, maxDepth, skip + 1) + return stackGetter.stackForCaller(target, maxDepth, skip + 1) } /** @@ -93,7 +88,7 @@ public object CallerFinder { private fun createBestStackGetter(): StackGetter { return try { StackWalkerStackGetter() - } catch (ignored: Throwable) { + } catch (_: Throwable) { // We may not be able to create `StackWalkerStackGetter` sometimes, // for example, on Android. This is not a problem because we have // `ThrowableStackGetter` as a fallback option. @@ -101,7 +96,6 @@ public object CallerFinder { } } - private fun checkSkipCount(skip: Int) { + private fun checkSkipCount(skip: Int) = require(skip >= 0) { "skip can't be negative: $skip." } - } } diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 1a85185..6d463c4 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -23,6 +23,7 @@ * (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.reflect import java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES @@ -44,7 +45,7 @@ internal class StackWalkerStackGetter : StackGetter { init { // Due to b/241269335, we check in constructor whether this implementation // crashes in runtime, and CallerFinder should catch any Throwable caused. - @Suppress("UNUSED_VARIABLE") + @Suppress("UNUSED_VARIABLE", "unused") val unused = callerOf(StackWalkerStackGetter::class.java, 0) } @@ -77,14 +78,14 @@ internal class StackWalkerStackGetter : StackGetter { private val STACK_WALKER: StackWalker = StackWalker.getInstance(SHOW_REFLECT_FRAMES) private fun filterStackTraceAfterTarget( - isTargetClass: Predicate, + isTargetClass: (StackFrame) -> Boolean, skipFrames: Int, s: Stream ): Stream { // need to skip + 1 because of the call to the method this method is being called from. return s.skip((skipFrames + 1).toLong()) // Skip all classes which don't match the name we are looking for. - .dropWhile(isTargetClass.negate()) + .dropWhile(Predicate { isTargetClass.invoke(it).not() }) // Then skip all which matches. .dropWhile(isTargetClass) .map { frame -> frame.toStackTraceElement() } @@ -92,5 +93,12 @@ internal class StackWalkerStackGetter : StackGetter { } } -private fun isTargetClass(target: Class<*>): Predicate = - Predicate { frame -> (frame.className == target.name) } +private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { + isTargetClass(it.className, target.name) +} + +/** + * Tells if the given class name matches the target class name or its companion object. + */ +internal fun isTargetClass(className: String?, targetClassName: String): Boolean = + (className == targetClassName || className == "$targetClassName\$Companion") diff --git a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt index e6a2879..f1a9576 100644 --- a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt @@ -76,9 +76,8 @@ internal class ThrowableStackGetter : StackGetter { var foundCaller = false val targetClassName = target.name for (frameIndex in skipFrames.. Date: Thu, 24 Jul 2025 18:50:55 +0100 Subject: [PATCH 04/51] Update dependency reports --- dependencies.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.md b/dependencies.md index 34bda73..36d6c44 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.191` +# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.192` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 01 12:46:52 WET 2024** 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 **Thu Jul 24 18:46:06 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 d737422..1018411 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine reflect -2.0.0-SNAPSHOT.191 +2.0.0-SNAPSHOT.192 2015 From 254163c10cae39bf94e2f78b5d402d3653250819 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 24 Jul 2025 18:52:03 +0100 Subject: [PATCH 05/51] Bump Kotlin --- .idea/kotlinc.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 9bfa224..740d6a9 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,13 +1,9 @@ - - - - From bad71f6d67d73d010146a301de540ebfd362c153 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 24 Jul 2025 18:56:26 +0100 Subject: [PATCH 06/51] Add test for locating companion caller --- .../spine/reflect/AbstractStackGetterSpec.kt | 16 ++++++ .../spine/reflect/given/CompanionLibrary.kt | 55 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index fe2a320..6138d35 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -28,6 +28,8 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.spine.reflect.given.CompanionLibrary +import io.spine.reflect.given.CompanionUserCode import io.spine.reflect.given.LoggerCode import io.spine.reflect.given.UserCode import org.junit.jupiter.api.Test @@ -64,4 +66,18 @@ internal abstract class AbstractStackGetterSpec( code.invokeUserCode() library.caller shouldBe null } + + @Test + fun `find caller of companion object`() { + // Test that CallerFinder can correctly identify callers of companion objects. + // The companion object class name in the stack trace should include "$Companion" suffix. + val companionLibrary = CompanionLibrary.Companion + val userCode = CompanionUserCode(companionLibrary, stackGetter) + + userCode.invokeCompanionMethod() + + companionLibrary.caller shouldNotBe null + companionLibrary.caller!!.className shouldBe CompanionUserCode::class.java.name + companionLibrary.caller!!.methodName shouldBe "invokeCompanionMethod" + } } diff --git a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt new file mode 100644 index 0000000..a4eec78 --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt @@ -0,0 +1,55 @@ +/* + * 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 + * + * 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.reflect.given + +import io.spine.reflect.StackGetter + +/** + * A test library class with a companion object that uses CallerFinder functionality. + */ +internal class CompanionLibrary { + + companion object { + + var caller: StackTraceElement? = null + private set + + fun findCaller(stackGetter: StackGetter) { + caller = stackGetter.callerOf(CompanionLibrary::class.java, 0) + } + } +} + +/** + * A user code class that calls companion object methods. + */ +internal class CompanionUserCode(private val library: CompanionLibrary.Companion, private val stackGetter: StackGetter) { + + fun invokeCompanionMethod() { + library.findCaller(stackGetter) + } +} \ No newline at end of file From 0351ae014195f321e93063141b24e6462518ed34 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 24 Jul 2025 18:56:43 +0100 Subject: [PATCH 07/51] Update Claude settings --- .claude/settings.local.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index db10386..5af0aaa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,8 @@ "Bash(./gradlew:*)", "Bash(find:*)", "Bash(ls:*)", - "Bash(mkdir:*)" + "Bash(mkdir:*)", + "Bash(./gradlew test:*)" ], "deny": [] } From d579bbc3f8bf012582f4d36cae5e94d39fa74969 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:20:37 +0100 Subject: [PATCH 08/51] Ignore `.java-version` --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 625eadc..dfdba85 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ # # Therefore, instructions below are superset of instructions required for all the projects. +.java-version + # IntelliJ IDEA modules and interim config files. *.iml .idea/*.xml From 2fb7ee25610f441d84cafb33c2f14b6eb5941a62 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:33:36 +0100 Subject: [PATCH 09/51] Improve tests Also: * Fix documentation layout. --- .../kotlin/io/spine/reflect/StackGetter.kt | 38 ++++++++----------- .../spine/reflect/AbstractStackGetterSpec.kt | 23 +++++++---- .../spine/reflect/given/CompanionLibrary.kt | 9 +++-- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackGetter.kt b/src/main/kotlin/io/spine/reflect/StackGetter.kt index 6c5ff16..7f37fb6 100644 --- a/src/main/kotlin/io/spine/reflect/StackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackGetter.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024, 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 @@ -23,28 +23,28 @@ * (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.reflect /** * Interface for finding call site information. * * @see - * Original Java code of Google Flogger + * Original Java code of Google Flogger for historical reference. */ internal interface StackGetter { + /** * Returns the first caller of a method on the [target] class that is *not* a member of * the `target` class. * * The caller is obtained by walking back on the stack. * - * @param target - * the class to find the caller of. - * @param skipFrames - * skip this many frames before looking for the caller. - * This can be used for optimization. + * @param target The class to find the caller of. + * @param skipFrames The number of frames to skip before looking for the caller. + * This can be used for optimization. * @return the first caller of the method or `null` if the `target` class - * cannot be found or is the last element of the stack. + * cannot be found or is the last element of the stack. */ fun callerOf(target: Class<*>, skipFrames: Int): StackTraceElement? @@ -53,16 +53,12 @@ internal interface StackGetter { * is a caller of a method on `target` class but is *not* itself a method * on `target` class. * - * @param target - * the class to get the stack from. - * @param maxDepth - * the maximum depth of the stack to return. - * A value of -1 means to return the whole stack. - * @param skipFrames - * skip this many stack frames before looking for the target class. + * @param target The class to get the stack from. + * @param maxDepth The maximum depth of the stack to return. + * A value of `-1` means to return the whole stack. + * @param skipFrames The number of frames to skip before looking for the target class. * Used for optimization. - * @throws IllegalArgumentException - * if `maxDepth` is 0 or < -1 or `skipFrames` is < 0. + * @throws IllegalArgumentException if `maxDepth` is 0 or < -1 or `skipFrames` is < 0. */ fun stackForCaller( target: Class<*>, @@ -71,10 +67,8 @@ internal interface StackGetter { ): Array } -internal fun checkMaxDepth(maxDepth: Int) { +internal fun checkMaxDepth(maxDepth: Int) = require(maxDepth == -1 || maxDepth > 0) { "maxDepth must be > 0 or -1" } -} -internal fun checkSkipFrames(skipFrames: Int) { +internal fun checkSkipFrames(skipFrames: Int) = require(skipFrames >= 0) { "skipFrames must be >= 0" } -} diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index 6138d35..c6c9de3 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -29,7 +29,7 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.spine.reflect.given.CompanionLibrary -import io.spine.reflect.given.CompanionUserCode +import io.spine.reflect.given.CallingCompanion import io.spine.reflect.given.LoggerCode import io.spine.reflect.given.UserCode import org.junit.jupiter.api.Test @@ -37,6 +37,8 @@ import org.junit.jupiter.api.Test /** * An abstract base for testing concrete implementations of [StackGetter]. * + * @property stackGetter The [StackGetter] implementation to test. + * * @see * Original Java code of Google Flogger */ @@ -67,17 +69,22 @@ internal abstract class AbstractStackGetterSpec( library.caller shouldBe null } + /** + * Tests that [StackGetter.callerOf] can find the caller of a companion object method. + */ @Test fun `find caller of companion object`() { - // Test that CallerFinder can correctly identify callers of companion objects. - // The companion object class name in the stack trace should include "$Companion" suffix. val companionLibrary = CompanionLibrary.Companion - val userCode = CompanionUserCode(companionLibrary, stackGetter) + val userCode = CallingCompanion(companionLibrary, stackGetter) userCode.invokeCompanionMethod() - - companionLibrary.caller shouldNotBe null - companionLibrary.caller!!.className shouldBe CompanionUserCode::class.java.name - companionLibrary.caller!!.methodName shouldBe "invokeCompanionMethod" + + companionLibrary.run { + caller shouldNotBe null + caller!!.run { + className shouldBe CallingCompanion::class.java.name + methodName shouldBe "invokeCompanionMethod" + } + } } } diff --git a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt index a4eec78..0405165 100644 --- a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt +++ b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt @@ -29,7 +29,7 @@ package io.spine.reflect.given import io.spine.reflect.StackGetter /** - * A test library class with a companion object that uses CallerFinder functionality. + * A test library class with a companion object that uses [StackGetter] functionality. */ internal class CompanionLibrary { @@ -47,9 +47,12 @@ internal class CompanionLibrary { /** * A user code class that calls companion object methods. */ -internal class CompanionUserCode(private val library: CompanionLibrary.Companion, private val stackGetter: StackGetter) { +internal class CallingCompanion( + private val library: CompanionLibrary.Companion, + private val stackGetter: StackGetter +) { fun invokeCompanionMethod() { library.findCaller(stackGetter) } -} \ No newline at end of file +} From 2e46ccaff60fccdf7e47d65c51fd7f4dcb114644 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:34:34 +0100 Subject: [PATCH 10/51] Suppress detekt in stub class --- src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt index 0405165..7767197 100644 --- a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt +++ b/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt @@ -31,6 +31,7 @@ import io.spine.reflect.StackGetter /** * A test library class with a companion object that uses [StackGetter] functionality. */ +@Suppress("UtilityClassWithPublicConstructor") internal class CompanionLibrary { companion object { From 94afa90bf9c3bfdc3959bb18efcce62bc6897905 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:34:42 +0100 Subject: [PATCH 11/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 36d6c44..ba3f193 100644 --- a/dependencies.md +++ b/dependencies.md @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 18:46:06 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 **Thu Jul 24 22:34:25 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 From 88079a17a7921e3b2ab0c216962c7f99eb0b9517 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:43:19 +0100 Subject: [PATCH 12/51] Improve names in tests --- .../spine/reflect/AbstractStackGetterSpec.kt | 23 +++++++++++------ ...panionLibrary.kt => ClassWithCompanion.kt} | 25 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) rename src/test/kotlin/io/spine/reflect/given/{CompanionLibrary.kt => ClassWithCompanion.kt} (72%) diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index c6c9de3..3ab17f5 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -28,8 +28,8 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe -import io.spine.reflect.given.CompanionLibrary -import io.spine.reflect.given.CallingCompanion +import io.spine.reflect.given.ClassWithCompanion +import io.spine.reflect.given.CallingTheClassWithCompanion import io.spine.reflect.given.LoggerCode import io.spine.reflect.given.UserCode import org.junit.jupiter.api.Test @@ -74,17 +74,26 @@ internal abstract class AbstractStackGetterSpec( */ @Test fun `find caller of companion object`() { - val companionLibrary = CompanionLibrary.Companion - val userCode = CallingCompanion(companionLibrary, stackGetter) + val companionLibrary = ClassWithCompanion.Companion + val userCode = CallingTheClassWithCompanion(companionLibrary, stackGetter) - userCode.invokeCompanionMethod() + userCode.invokeCompanionFun() companionLibrary.run { caller shouldNotBe null caller!!.run { - className shouldBe CallingCompanion::class.java.name - methodName shouldBe "invokeCompanionMethod" + className shouldBe CallingTheClassWithCompanion::class.java.name + methodName shouldBe "invokeCompanionFun" } } + + // Check the caller of anb instance method also works assuming that the + // `companion object` is declared in the class. + val instanceCaller = userCode.invokeInstanceFun() + instanceCaller shouldNotBe null + instanceCaller!!.run { + className shouldBe CallingTheClassWithCompanion::class.java.name + methodName shouldBe "invokeInstanceFun" + } } } diff --git a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt similarity index 72% rename from src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt rename to src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt index 7767197..1249ebb 100644 --- a/src/test/kotlin/io/spine/reflect/given/CompanionLibrary.kt +++ b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt @@ -32,28 +32,35 @@ import io.spine.reflect.StackGetter * A test library class with a companion object that uses [StackGetter] functionality. */ @Suppress("UtilityClassWithPublicConstructor") -internal class CompanionLibrary { - +internal class ClassWithCompanion { + + fun findInstanceCaller(stackGetter: StackGetter): StackTraceElement? { + return stackGetter.callerOf(ClassWithCompanion::class.java, 0) + } + companion object { var caller: StackTraceElement? = null private set fun findCaller(stackGetter: StackGetter) { - caller = stackGetter.callerOf(CompanionLibrary::class.java, 0) + caller = stackGetter.callerOf(ClassWithCompanion::class.java, 0) } } } /** - * A user code class that calls companion object methods. + * A user code class calls companion object and instance functions. */ -internal class CallingCompanion( - private val library: CompanionLibrary.Companion, +internal class CallingTheClassWithCompanion( + private val viaCompanion: ClassWithCompanion.Companion, private val stackGetter: StackGetter ) { - - fun invokeCompanionMethod() { - library.findCaller(stackGetter) + fun invokeCompanionFun() { + viaCompanion.findCaller(stackGetter) + } + + fun invokeInstanceFun(): StackTraceElement? { + return ClassWithCompanion().findInstanceCaller(stackGetter) } } From 20cb77fa8317a67b22919c27fbae5cf3d9acd227 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 22:43:25 +0100 Subject: [PATCH 13/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index ba3f193..75419ba 100644 --- a/dependencies.md +++ b/dependencies.md @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 22:34:25 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 **Thu Jul 24 22:43:05 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 From 3cbca3bc4de580d1d173885272b1daa9b156f1f5 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:00:17 +0100 Subject: [PATCH 14/51] Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index 3ab17f5..087e31a 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -87,7 +87,7 @@ internal abstract class AbstractStackGetterSpec( } } - // Check the caller of anb instance method also works assuming that the + // Check the caller of an instance method also works assuming that the // `companion object` is declared in the class. val instanceCaller = userCode.invokeInstanceFun() instanceCaller shouldNotBe null From 0299c2b2e8af4840e9068cecd332a8115e693792 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:00:43 +0100 Subject: [PATCH 15/51] Improve suppressions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 6d463c4..75bcd06 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -45,7 +45,7 @@ internal class StackWalkerStackGetter : StackGetter { init { // Due to b/241269335, we check in constructor whether this implementation // crashes in runtime, and CallerFinder should catch any Throwable caused. - @Suppress("UNUSED_VARIABLE", "unused") + @Suppress("unused") val unused = callerOf(StackWalkerStackGetter::class.java, 0) } From a4f119bf06bda27ba299d98a6094243afa51998d Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:04:03 +0100 Subject: [PATCH 16/51] Simplify calling check function Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 75bcd06..fffc62f 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -85,7 +85,7 @@ internal class StackWalkerStackGetter : StackGetter { // need to skip + 1 because of the call to the method this method is being called from. return s.skip((skipFrames + 1).toLong()) // Skip all classes which don't match the name we are looking for. - .dropWhile(Predicate { isTargetClass.invoke(it).not() }) + .dropWhile(Predicate { !isTargetClass(it) }) // Then skip all which matches. .dropWhile(isTargetClass) .map { frame -> frame.toStackTraceElement() } From 575dfd5de8b78c8122d2a8dc90afa8982ca2d36f Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:05:07 +0100 Subject: [PATCH 17/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 75419ba..7384aa3 100644 --- a/dependencies.md +++ b/dependencies.md @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 22:43:05 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 **Thu Jul 24 23:05:05 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 From ef4d84012c44cd2d9986b815f681b83186f4b8ec Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:10:00 +0100 Subject: [PATCH 18/51] Avoid using `Predicate` for filtering --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index fffc62f..1ac2eef 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -28,7 +28,6 @@ package io.spine.reflect import java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES import java.lang.StackWalker.StackFrame -import java.util.function.Predicate import java.util.stream.Stream import kotlin.Long.Companion.MAX_VALUE import kotlin.collections.toTypedArray @@ -85,7 +84,7 @@ internal class StackWalkerStackGetter : StackGetter { // need to skip + 1 because of the call to the method this method is being called from. return s.skip((skipFrames + 1).toLong()) // Skip all classes which don't match the name we are looking for. - .dropWhile(Predicate { !isTargetClass(it) }) + .dropWhile({ !isTargetClass(it) }) // Then skip all which matches. .dropWhile(isTargetClass) .map { frame -> frame.toStackTraceElement() } From cd07617a4cb9b122ea7f9a6d268cb9d6957637a5 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:10:57 +0100 Subject: [PATCH 19/51] Improve inline comments language --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 1ac2eef..03ccddc 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -81,11 +81,11 @@ internal class StackWalkerStackGetter : StackGetter { skipFrames: Int, s: Stream ): Stream { - // need to skip + 1 because of the call to the method this method is being called from. + // Need to skip + 1 because of the call to the method this method is being called from. return s.skip((skipFrames + 1).toLong()) - // Skip all classes which don't match the name we are looking for. + // Skip all classes that do not match the name we are looking for. .dropWhile({ !isTargetClass(it) }) - // Then skip all which matches. + // Then skip all that match. .dropWhile(isTargetClass) .map { frame -> frame.toStackTraceElement() } } From fafe382f6dee42d02f46173f71494cd2e329334b Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:11:20 +0100 Subject: [PATCH 20/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 7384aa3..1b7ed4e 100644 --- a/dependencies.md +++ b/dependencies.md @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 23:05:05 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 **Thu Jul 24 23:11:06 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 From 158fb2ed28782c2a3e311ac7008bde59e2a1dac8 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:12:23 +0100 Subject: [PATCH 21/51] Improve lambda call Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 03ccddc..4913acb 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -84,7 +84,7 @@ internal class StackWalkerStackGetter : StackGetter { // Need to skip + 1 because of the call to the method this method is being called from. return s.skip((skipFrames + 1).toLong()) // Skip all classes that do not match the name we are looking for. - .dropWhile({ !isTargetClass(it) }) + .dropWhile { !isTargetClass(it) } // Then skip all that match. .dropWhile(isTargetClass) .map { frame -> frame.toStackTraceElement() } From c954d7d3e65c59b3615ff55eabc01ef68dce989d Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:14:53 +0100 Subject: [PATCH 22/51] Improve KDoc for the test function Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 4913acb..18025d9 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -99,5 +99,12 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { /** * Tells if the given class name matches the target class name or its companion object. */ +/** + * Determines whether the given class name matches the target class name or its companion object. + * + * @param className the name of the class to check. Can be `null`. + * @param targetClassName the name of the target class to match against. + * @return `true` if the class name matches the target class name or its companion object, `false` otherwise. + */ internal fun isTargetClass(className: String?, targetClassName: String): Boolean = (className == targetClassName || className == "$targetClassName\$Companion") From 993202baa683ddfd2305bca6a6e53c6e5dddee0d Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:15:17 +0100 Subject: [PATCH 23/51] Remove extra empty line Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt index 1249ebb..f75da72 100644 --- a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt +++ b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt @@ -39,7 +39,6 @@ internal class ClassWithCompanion { } companion object { - var caller: StackTraceElement? = null private set From c8900e67ea235d5efb065da6927f1ca20fbdb545 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:15:36 +0100 Subject: [PATCH 24/51] Use HTTPS in the (c) header Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackGetter.kt b/src/main/kotlin/io/spine/reflect/StackGetter.kt index 7f37fb6..11f452b 100644 --- a/src/main/kotlin/io/spine/reflect/StackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackGetter.kt @@ -5,7 +5,7 @@ * 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 + * http://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 From d5a5732ff79b4d4106e80a2a63c938620818cbab Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:17:19 +0100 Subject: [PATCH 25/51] Remove duplicated KDoc comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 18025d9..890f38e 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -96,9 +96,7 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { isTargetClass(it.className, target.name) } -/** - * Tells if the given class name matches the target class name or its companion object. - */ + /** * Determines whether the given class name matches the target class name or its companion object. * From 6b97f8507dca4911c164aad673a65fc78ce8e4c7 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:26:42 +0100 Subject: [PATCH 26/51] Improve doc formatting --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 890f38e..944ea12 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -43,7 +43,7 @@ internal class StackWalkerStackGetter : StackGetter { init { // Due to b/241269335, we check in constructor whether this implementation - // crashes in runtime, and CallerFinder should catch any Throwable caused. + // crashes in runtime, and `CallerFinder` should catch any `Throwable` caused. @Suppress("unused") val unused = callerOf(StackWalkerStackGetter::class.java, 0) } From 2f23911933756ce16d18c222671cfb94b7537743 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Thu, 24 Jul 2025 23:26:48 +0100 Subject: [PATCH 27/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 1b7ed4e..5ad2185 100644 --- a/dependencies.md +++ b/dependencies.md @@ -816,4 +816,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 23:11:06 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 **Thu Jul 24 23:26:39 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 From 9dfc541d5dec568eb9986b2e7bfe30471b1c0a51 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:09:09 +0100 Subject: [PATCH 28/51] Add trailing EOL --- .claude/settings.local.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5af0aaa..597b457 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,4 +13,4 @@ ], "deny": [] } -} \ No newline at end of file +} From 2b31788935c0b701df4104094af75dea04d00a51 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:29:12 +0100 Subject: [PATCH 29/51] Migrate to JSpecify Also: * Suppress PMD warning. * Fix nullability annotations. --- src/main/java/io/spine/reflect/Invokables.java | 10 +++++----- src/main/java/io/spine/reflect/PackageGraph.java | 1 + src/test/java/given/reflect/WithParametersJava.java | 2 +- src/test/java/io/spine/reflect/InvokablesTest.java | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/spine/reflect/Invokables.java b/src/main/java/io/spine/reflect/Invokables.java index f79d201..fc9df97 100644 --- a/src/main/java/io/spine/reflect/Invokables.java +++ b/src/main/java/io/spine/reflect/Invokables.java @@ -28,7 +28,7 @@ import com.google.common.reflect.Invokable; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -59,7 +59,7 @@ private Invokables() { * *

The accessibility parameter of the input method, i.e. {@code method.isAccessible()}, is * preserved by this method. However, the attributes may be changes in a non-synchronized - * manner, i.e. the {@code asHandle(..)} is not designed to operate concurrently. + * manner, i.e., the {@code asHandle(..)} is not designed to operate concurrently. */ public static MethodHandle asHandle(Method method) { checkNotNull(method); @@ -71,7 +71,7 @@ public static MethodHandle asHandle(Method method) { "Unable to obtain method handle for `%s`." + " The method's accessibility was probably changed concurrently.", method)); - return result; + return checkNotNull(result); } /** @@ -187,7 +187,7 @@ private static Constructor ensureParameterlessCtor(Class type) { * or another error occurs during the reflective operation execution */ /* catching any runtimes does not hurt here. */ - private static + private static R invokePreservingAccessibility(T reflectiveObject, Function> makeInvokable, ReflectiveFunction fn, @@ -214,7 +214,7 @@ R invokePreservingAccessibility(T reflectiveObject, * @param * function output type */ - private interface ReflectiveFunction { + private interface ReflectiveFunction { R apply(T t) throws ReflectiveOperationException; } diff --git a/src/main/java/io/spine/reflect/PackageGraph.java b/src/main/java/io/spine/reflect/PackageGraph.java index 241556f..bb16f2f 100644 --- a/src/main/java/io/spine/reflect/PackageGraph.java +++ b/src/main/java/io/spine/reflect/PackageGraph.java @@ -286,6 +286,7 @@ public Filter exclude(String packagePrefix) { * */ @Override + @SuppressWarnings("PMD.SimplifyBooleanReturns") public boolean test(Package aPackage) { var packageName = aPackage.getName(); diff --git a/src/test/java/given/reflect/WithParametersJava.java b/src/test/java/given/reflect/WithParametersJava.java index a267dcd..5fba976 100644 --- a/src/test/java/given/reflect/WithParametersJava.java +++ b/src/test/java/given/reflect/WithParametersJava.java @@ -26,7 +26,7 @@ package given.reflect; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.Nullable; /** * The Java class with the constructor accepting several parameters. diff --git a/src/test/java/io/spine/reflect/InvokablesTest.java b/src/test/java/io/spine/reflect/InvokablesTest.java index 2a59a7a..9427ba8 100644 --- a/src/test/java/io/spine/reflect/InvokablesTest.java +++ b/src/test/java/io/spine/reflect/InvokablesTest.java @@ -31,7 +31,7 @@ import io.spine.reflect.given.ConstructorsTestEnv; import io.spine.reflect.given.MethodsTestEnv.ClassWithPrivateMethod; import io.spine.testing.UtilityClassTest; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; From cf6a208ec98b77441af3ff735a8267839a31beb0 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:29:47 +0100 Subject: [PATCH 30/51] Update `config` --- .github/workflows/build-on-ubuntu.yml | 2 +- .github/workflows/build-on-windows.yml | 2 +- .../workflows/gradle-wrapper-validation.yml | 4 +- .github/workflows/increment-guard.yml | 2 +- .github/workflows/publish.yml | 2 +- ...move-obsolete-artifacts-from-packages.yaml | 73 ++++ .idea/codeStyles/Project.xml | 3 +- .idea/dictionaries/common.xml | 4 +- .idea/inspectionProfiles/Project_Default.xml | 26 +- .idea/kotlinc.xml | 2 +- .idea/misc.xml | 2 +- build.gradle.kts | 15 +- buildSrc/build.gradle.kts | 64 ++- buildSrc/settings.gradle.kts | 37 ++ buildSrc/src/main/kotlin/BuildExtensions.kt | 128 ++++-- buildSrc/src/main/kotlin/BuildSettings.kt | 9 +- .../src/main/kotlin/DependencyResolution.kt | 36 +- .../src/main/kotlin/DocumentationSettings.kt | 58 +++ buildSrc/src/main/kotlin/DokkaExts.kt | 134 +++++-- buildSrc/src/main/kotlin/LicenseSettings.kt | 43 ++ buildSrc/src/main/kotlin/Strings.kt | 19 +- .../main/kotlin/compile-protobuf.gradle.kts | 12 +- .../src/main/kotlin/config-tester.gradle.kts | 2 +- .../kotlin/detekt-code-analysis.gradle.kts | 2 +- .../src/main/kotlin/dokka-for-java.gradle.kts | 18 +- .../main/kotlin/dokka-for-kotlin.gradle.kts | 18 +- .../kotlin/io/spine/dependency/Dependency.kt | 116 ++++++ .../kotlin/io/spine/dependency/boms/Boms.kt | 66 ++++ .../io/spine/dependency/boms/BomsPlugin.kt | 185 +++++++++ .../spine/dependency/build/AnimalSniffer.kt | 2 +- .../io/spine/dependency/build/CheckStyle.kt | 2 +- .../dependency/build/CheckerFramework.kt | 2 +- .../kotlin/io/spine/dependency/build/Dokka.kt | 6 +- .../io/spine/dependency/build/ErrorProne.kt | 21 +- .../io/spine/dependency/build/FindBugs.kt | 2 +- .../io/spine/dependency/build/GradleDoctor.kt | 2 +- .../kotlin/io/spine/dependency/build/Ksp.kt | 28 +- .../spine/dependency/build/LicenseReport.kt | 2 +- .../io/spine/dependency/build/OsDetector.kt | 2 +- .../kotlin/io/spine/dependency/build/Pmd.kt | 11 +- .../io/spine/dependency/kotlinx/Coroutines.kt | 54 +++ .../io/spine/dependency/kotlinx/KotlinX.kt | 32 ++ .../spine/dependency/kotlinx/Serialization.kt | 73 ++++ .../kotlin/io/spine/dependency/lib/Aedile.kt | 6 +- .../io/spine/dependency/lib/ApacheHttp.kt | 2 +- .../io/spine/dependency/lib/AppEngine.kt | 2 +- .../kotlin/io/spine/dependency/lib/Asm.kt | 13 +- .../kotlin/io/spine/dependency/lib/Auto.kt | 9 +- .../io/spine/dependency/lib/BouncyCastle.kt | 2 +- .../io/spine/dependency/lib/Caffeine.kt | 8 +- .../kotlin/io/spine/dependency/lib/Clikt.kt | 2 +- .../io/spine/dependency/lib/CommonsCli.kt | 2 +- .../io/spine/dependency/lib/CommonsCodec.kt | 2 +- .../io/spine/dependency/lib/CommonsLogging.kt | 2 +- .../io/spine/dependency/lib/Coroutines.kt | 27 +- .../io/spine/dependency/lib/Firebase.kt | 2 +- .../kotlin/io/spine/dependency/lib/Flogger.kt | 2 +- .../io/spine/dependency/lib/GoogleApis.kt | 2 +- .../io/spine/dependency/lib/GoogleCloud.kt | 2 +- .../kotlin/io/spine/dependency/lib/Grpc.kt | 67 +++- .../io/spine/dependency/lib/GrpcKotlin.kt | 5 +- .../kotlin/io/spine/dependency/lib/Gson.kt | 4 +- .../kotlin/io/spine/dependency/lib/Guava.kt | 9 +- .../io/spine/dependency/lib/HttpClient.kt | 2 +- .../io/spine/dependency/lib/IntelliJ.kt | 2 +- .../kotlin/io/spine/dependency/lib/J2ObjC.kt | 2 +- .../kotlin/io/spine/dependency/lib/Jackson.kt | 110 ++++-- .../io/spine/dependency/lib/JavaDiffUtils.kt | 2 +- .../kotlin/io/spine/dependency/lib/JavaJwt.kt | 2 +- .../io/spine/dependency/lib/JavaPoet.kt | 7 +- .../kotlin/io/spine/dependency/lib/JavaX.kt | 7 +- .../kotlin/io/spine/dependency/lib/Klaxon.kt | 2 +- .../kotlin/io/spine/dependency/lib/Kotlin.kt | 88 ++++- .../io/spine/dependency/lib/KotlinPoet.kt | 35 ++ .../io/spine/dependency/lib/KotlinSemver.kt | 4 +- .../kotlin/io/spine/dependency/lib/KotlinX.kt | 27 +- .../kotlin/io/spine/dependency/lib/Log4j2.kt | 2 +- .../kotlin/io/spine/dependency/lib/Netty.kt | 2 +- .../kotlin/io/spine/dependency/lib/Okio.kt | 2 +- .../kotlin/io/spine/dependency/lib/Plexus.kt | 2 +- .../io/spine/dependency/lib/Protobuf.kt | 10 +- .../kotlin/io/spine/dependency/lib/Roaster.kt | 9 +- .../kotlin/io/spine/dependency/lib/Slf4J.kt | 2 +- .../spine/dependency/local/ArtifactVersion.kt | 40 +- .../kotlin/io/spine/dependency/local/Base.kt | 43 ++ .../local/BaseTypes.kt} | 24 +- .../io/spine/dependency/local/Change.kt | 40 ++ .../io/spine/dependency/local/CoreJava.kt | 16 +- .../io/spine/dependency/local/Logging.kt | 13 +- .../io/spine/dependency/local/McJava.kt | 6 +- .../spine/dependency/local/ModelCompiler.kt | 40 ++ .../io/spine/dependency/local/ProtoData.kt | 14 +- .../io/spine/dependency/local/ProtoTap.kt | 4 +- .../io/spine/dependency/local/Reflect.kt | 40 ++ .../kotlin/io/spine/dependency/local/Spine.kt | 77 +++- .../io/spine/dependency/local/TestLib.kt | 40 ++ .../kotlin/io/spine/dependency/local/Text.kt | 40 ++ .../kotlin/io/spine/dependency/local/Time.kt | 42 ++ .../io/spine/dependency/local/ToolBase.kt | 23 +- .../io/spine/dependency/local/Validation.kt | 13 +- .../io/spine/dependency/test/AssertK.kt | 4 +- .../io/spine/dependency/test/Hamcrest.kt | 4 +- .../kotlin/io/spine/dependency/test/JUnit.kt | 87 +++- .../kotlin/io/spine/dependency/test/Jacoco.kt | 4 +- .../kotlin/io/spine/dependency/test/Kotest.kt | 4 +- .../dependency/test/KotlinCompileTesting.kt | 40 ++ .../kotlin/io/spine/dependency/test/Kover.kt | 4 +- .../io/spine/dependency/test/OpenTest4J.kt | 2 +- .../io/spine/dependency/test/SystemLambda.kt | 2 +- .../io/spine/dependency/test/TestKitTruth.kt | 2 +- .../kotlin/io/spine/dependency/test/Truth.kt | 4 +- .../kotlin/io/spine/docs/MarkdownDocument.kt | 2 +- .../src/main/kotlin/io/spine/gradle/Build.kt | 2 +- .../src/main/kotlin/io/spine/gradle/Clean.kt | 2 +- .../kotlin/io/spine/gradle/ConfigTester.kt | 2 +- .../io/spine/gradle/ProjectExtensions.kt | 13 +- .../kotlin/io/spine/gradle/Repositories.kt | 370 ------------------ .../main/kotlin/io/spine/gradle/RunBuild.kt | 4 +- .../main/kotlin/io/spine/gradle/RunGradle.kt | 4 +- .../main/kotlin/io/spine/gradle/Runtime.kt | 17 +- .../io/spine/gradle/StringExtensions.kt | 2 +- .../main/kotlin/io/spine/gradle/TaskName.kt | 2 +- .../kotlin/io/spine/gradle/VersionWriter.kt | 2 +- .../main/kotlin/io/spine/gradle/base/Tasks.kt | 2 +- .../gradle/checkstyle/CheckStyleConfig.kt | 13 +- .../io/spine/gradle/dart/DartContext.kt | 2 +- .../io/spine/gradle/dart/DartEnvironment.kt | 2 +- .../io/spine/gradle/dart/DartExtension.kt | 2 +- .../spine/gradle/dart/plugin/DartPlugins.kt | 2 +- .../io/spine/gradle/dart/plugin/Protobuf.kt | 2 +- .../kotlin/io/spine/gradle/dart/task/Build.kt | 2 +- .../io/spine/gradle/dart/task/DartTasks.kt | 2 +- .../spine/gradle/dart/task/IntegrationTest.kt | 2 +- .../io/spine/gradle/dart/task/Publish.kt | 2 +- .../io/spine/gradle/dokka/DokkaExtensions.kt | 2 +- .../gradle/dokka/TaskContainerExtensions.kt | 2 +- .../kotlin/io/spine/gradle/fs/LazyTempPath.kt | 2 +- .../main/kotlin/io/spine/gradle/git/Branch.kt | 2 +- .../kotlin/io/spine/gradle/git/Repository.kt | 2 +- .../kotlin/io/spine/gradle/git/UserInfo.kt | 2 +- .../spine/gradle/github/pages/AuthorEmail.kt | 2 +- .../github/pages/RepositoryExtensions.kt | 4 +- .../io/spine/gradle/github/pages/SshKey.kt | 2 +- .../io/spine/gradle/github/pages/TaskName.kt | 2 +- .../io/spine/gradle/github/pages/Update.kt | 2 +- .../gradle/github/pages/UpdateGitHubPages.kt | 2 +- .../pages/UpdateGitHubPagesExtension.kt | 2 +- .../main/kotlin/io/spine/gradle/java/Tasks.kt | 2 +- .../io/spine/gradle/javac/ErrorProne.kt | 3 +- .../kotlin/io/spine/gradle/javac/Javac.kt | 2 +- .../io/spine/gradle/javadoc/Encoding.kt | 2 +- .../gradle/javadoc/ExcludeInternalDoclet.kt | 2 +- .../io/spine/gradle/javadoc/JavadocConfig.kt | 33 +- .../io/spine/gradle/javadoc/JavadocTag.kt | 2 +- .../io/spine/gradle/javascript/JsContext.kt | 7 +- .../spine/gradle/javascript/JsEnvironment.kt | 2 +- .../io/spine/gradle/javascript/JsExtension.kt | 2 +- .../io/spine/gradle/javascript/plugin/Idea.kt | 2 +- .../gradle/javascript/plugin/JsPlugins.kt | 2 +- .../io/spine/gradle/javascript/plugin/McJs.kt | 2 +- .../gradle/javascript/plugin/Protobuf.kt | 4 +- .../spine/gradle/javascript/task/Assemble.kt | 2 +- .../io/spine/gradle/javascript/task/Check.kt | 2 +- .../io/spine/gradle/javascript/task/Clean.kt | 2 +- .../gradle/javascript/task/IntegrationTest.kt | 2 +- .../spine/gradle/javascript/task/JsTasks.kt | 2 +- .../gradle/javascript/task/LicenseReport.kt | 2 +- .../spine/gradle/javascript/task/Publish.kt | 2 +- .../spine/gradle/javascript/task/Webpack.kt | 2 +- .../io/spine/gradle/kotlin/KotlinConfig.kt | 25 +- .../gradle/protobuf/ProtoTaskExtensions.kt | 262 +++++++++---- .../gradle/publish/CheckVersionIncrement.kt | 35 +- .../gradle/publish/CloudArtifactRegistry.kt | 12 +- .../io/spine/gradle/publish/CloudRepo.kt | 7 +- .../publish/CustomPublicationHandler.kt | 70 ++++ .../io/spine/gradle/publish/GitHubPackages.kt | 16 +- .../io/spine/gradle/publish/IncrementGuard.kt | 2 +- .../kotlin/io/spine/gradle/publish/JarDsl.kt | 2 +- .../io/spine/gradle/publish/ProtoExts.kt | 2 +- .../gradle/publish/PublicationHandler.kt | 250 ++++++++++++ .../io/spine/gradle/publish/Publications.kt | 234 ----------- .../io/spine/gradle/publish/PublishingExts.kt | 75 +++- .../spine/gradle/publish/PublishingRepos.kt | 4 +- .../io/spine/gradle/publish/ShadowJarExts.kt | 77 ++++ .../spine/gradle/publish/SpinePublishing.kt | 237 +++++++---- .../publish/StandardJavaPublicationHandler.kt | 133 +++++++ .../io/spine/gradle/repo/Credentials.kt | 35 ++ .../kotlin/io/spine/gradle/repo/RepoSlug.kt | 67 ++++ .../io/spine/gradle/repo/Repositories.kt | 172 ++++++++ .../kotlin/io/spine/gradle/repo/Repository.kt | 138 +++++++ .../gradle/report/coverage/CodebaseFilter.kt | 3 +- .../gradle/report/coverage/FileExtension.kt | 2 +- .../gradle/report/coverage/FileExtensions.kt | 2 +- .../gradle/report/coverage/FileFilter.kt | 2 +- .../gradle/report/coverage/JacocoConfig.kt | 8 +- .../gradle/report/coverage/PathMarker.kt | 2 +- .../spine/gradle/report/coverage/TaskName.kt | 2 +- .../gradle/report/license/Configuration.kt | 2 +- .../gradle/report/license/LicenseReporter.kt | 12 +- .../report/license/MarkdownReportRenderer.kt | 2 +- .../report/license/ModuleDataExtensions.kt | 2 +- .../io/spine/gradle/report/license/Paths.kt | 2 +- .../report/license/ProjectDependencies.kt | 2 +- .../io/spine/gradle/report/license/Tasks.kt | 2 +- .../spine/gradle/report/license/Template.kt | 2 +- .../gradle/report/pom/DependencyScope.kt | 2 +- .../gradle/report/pom/DependencyWriter.kt | 6 +- .../spine/gradle/report/pom/InceptionYear.kt | 2 +- .../gradle/report/pom/MarkupExtensions.kt | 2 +- .../gradle/report/pom/ModuleDependency.kt | 6 +- .../spine/gradle/report/pom/PomFormatting.kt | 4 +- .../spine/gradle/report/pom/PomGenerator.kt | 30 +- .../spine/gradle/report/pom/PomXmlWriter.kt | 18 +- .../gradle/report/pom/ProjectMetadata.kt | 2 +- .../gradle/report/pom/ScopedDependency.kt | 2 +- .../spine/gradle/report/pom/SpineLicense.kt | 2 +- .../kotlin/io/spine/gradle/testing/Logging.kt | 2 +- .../io/spine/gradle/testing/Multiproject.kt | 2 +- .../kotlin/io/spine/gradle/testing/Tasks.kt | 9 +- .../src/main/kotlin/jacoco-kmm-jvm.gradle.kts | 72 ++++ .../main/kotlin/jacoco-kotlin-jvm.gradle.kts | 2 +- .../src/main/kotlin/jvm-module.gradle.kts | 87 ++-- .../src/main/kotlin/kmp-module.gradle.kts | 187 +++++++++ .../src/main/kotlin/kmp-publish.gradle.kts | 75 ++++ .../src/main/kotlin/module-testing.gradle.kts | 121 ++++++ .../src/main/kotlin/pmd-settings.gradle.kts | 2 +- .../main/kotlin/uber-jar-module.gradle.kts | 202 ++++++++++ .../src/main/kotlin/write-manifest.gradle.kts | 4 +- .../resources/dokka/styles/custom-styles.css | 4 +- config | 2 +- gradle.properties | 50 ++- gradle/wrapper/gradle-wrapper.jar | Bin 61624 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 37 +- gradlew.bat | 26 +- .../java/io/spine/reflect/package-info.java | 4 +- .../spine/reflect/StackWalkerStackGetter.kt | 2 +- .../reflect/annotation/package-info.java | 4 +- .../io/spine/reflect/given/package-info.java | 4 +- 239 files changed, 4377 insertions(+), 1552 deletions(-) create mode 100644 .github/workflows/remove-obsolete-artifacts-from-packages.yaml create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/DocumentationSettings.kt create mode 100644 buildSrc/src/main/kotlin/LicenseSettings.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/Coroutines.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/KotlinX.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/Serialization.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt rename buildSrc/src/main/kotlin/io/spine/{gradle/javadoc/TaskContainerExtensions.kt => dependency/local/BaseTypes.kt} (74%) create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/Text.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt delete mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/Repositories.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt delete mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/Publications.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/ShadowJarExts.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt create mode 100644 buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts create mode 100644 buildSrc/src/main/kotlin/kmp-module.gradle.kts create mode 100644 buildSrc/src/main/kotlin/kmp-publish.gradle.kts create mode 100644 buildSrc/src/main/kotlin/module-testing.gradle.kts create mode 100644 buildSrc/src/main/kotlin/uber-jar-module.gradle.kts diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index 028e583..f8c2493 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: zulu cache: gradle diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml index 3627bbf..4e6b57f 100644 --- a/.github/workflows/build-on-windows.yml +++ b/.github/workflows/build-on-windows.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: zulu cache: gradle diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index a13b191..858cebb 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -9,11 +9,11 @@ on: jobs: validation: - name: Validation + name: Gradle Wrapper Validation runs-on: ubuntu-latest steps: - name: Checkout latest code uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml index a1c90c3..1993841 100644 --- a/.github/workflows/increment-guard.yml +++ b/.github/workflows/increment-guard.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: zulu cache: gradle diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 73a0a69..8b5b4d5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: zulu cache: gradle diff --git a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml new file mode 100644 index 0000000..fe8ad84 --- /dev/null +++ b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml @@ -0,0 +1,73 @@ +# +# Periodically removes obsolete artifacts from GitHub Packages. +# +# Only non-release artifactsβ€”those containing "SNAPSHOT" in their version nameβ€”are eligible +# for removal. The latest non-release artifacts will be retained, with the exact number determined +# by the `VERSION_COUNT_TO_KEEP` environment variable. +# +# Please note the following details: +# +# 1. An artifact cannot be deleted if it is public and has been downloaded more than 5,000 times. +# In this scenario, contact GitHub support for further assistance. +# +# 2. This workflow only applies to artifacts published from this repository. +# +# 3. A maximum of 100 artifacts can be removed per run from each package; +# if there are more than 100 obsolete artifacts, either manually restart the workflow +# or wait for the next scheduled removal. +# +# 4. When artifacts with version `x.x.x-SNAPSHOT` are published, GitHub automatically appends +# the current timestamp, resulting in versions like `x.x.x-SNAPSHOT.20241024.173759`. +# All such artifacts are grouped into one package and treated as a single package +# in GitHub Packages with the version `x.x.x-SNAPSHOT`. Consequently, it is not possible +# to remove obsolete versions within a package; only the entire package can be deleted. +# + +name: Remove obsolete Maven artifacts from GitHub Packages + +on: + schedule: + - cron: '0 0 * * *' # Run every day at midnight. + +env: + VERSION_COUNT_TO_KEEP: 5 # Number of most recent SNAPSHOT versions to retain. + +jobs: + retrieve-package-names: + name: Retrieve the package names published from this repository + runs-on: ubuntu-latest + outputs: + package-names: ${{ steps.request-package-names.outputs.package-names }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Retrieve the names of packages + id: request-package-names + shell: bash + run: | + repoName=$(echo ${{ github.repository }} | cut -d '/' -f2) + chmod +x ./config/scripts/request-package-names.sh + ./config/scripts/request-package-names.sh ${{ github.token }} \ + $repoName ${{ github.repository_owner }} ./package-names.json + echo "package-names=$(<./package-names.json)" >> $GITHUB_OUTPUT + + delete-obsolete-artifacts: + name: Remove obsolete artifacts published from this repository to GitHub Packages + needs: retrieve-package-names + runs-on: ubuntu-latest + strategy: + matrix: + package-name: ${{ fromJson(needs.retrieve-package-names.outputs.package-names) }} + steps: + - name: Remove obsolete artifacts from '${{ matrix.package-name }}' package + uses: actions/delete-package-versions@v5 + with: + owner: ${{ github.repository_owner }} + package-name: ${{ matrix.package-name }} + package-type: 'maven' + token: ${{ github.token }} + min-versions-to-keep: ${{ env.VERSION_COUNT_TO_KEEP }} + # Ignores artifacts that do not contain the word "SNAPSHOT". + ignore-versions: '^(?!.+SNAPSHOT).*$' diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 809943c..f60c273 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,6 @@ + - + \ No newline at end of file diff --git a/.idea/dictionaries/common.xml b/.idea/dictionaries/common.xml index e62952a..d1c3a7b 100644 --- a/.idea/dictionaries/common.xml +++ b/.idea/dictionaries/common.xml @@ -24,6 +24,8 @@ handshaker hohpe idempotency + jspecify + kotest lempira liskov melnik @@ -66,4 +68,4 @@ yevsyukov - + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 35bbdff..229f1d3 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -146,13 +146,11 @@ - - @@ -194,7 +192,6 @@ - @@ -258,6 +255,18 @@ + + + + + + - @@ -623,6 +631,9 @@ + + + @@ -690,7 +701,6 @@ - @@ -711,7 +721,6 @@ - - + diff --git a/build.gradle.kts b/build.gradle.kts index 7b718ef..c1667d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,17 +26,18 @@ @file:Suppress("RemoveRedundantQualifierName") // Cannot use imports in some places. +import io.spine.dependency.build.JSpecify import io.spine.dependency.lib.Kotlin import io.spine.dependency.local.Logging -import io.spine.dependency.local.Spine +import io.spine.dependency.local.TestLib import io.spine.gradle.checkstyle.CheckStyleConfig import io.spine.gradle.javadoc.JavadocConfig import io.spine.gradle.publish.IncrementGuard import io.spine.gradle.publish.PublishingRepos import io.spine.gradle.publish.spinePublishing +import io.spine.gradle.repo.standardToSpineSdk import io.spine.gradle.report.license.LicenseReporter import io.spine.gradle.report.pom.PomGenerator -import io.spine.gradle.standardToSpineSdk buildscript { standardSpineSdkRepositories() @@ -47,6 +48,7 @@ repositories.standardToSpineSdk() // Apply some plugins to make type-safe extension accessors available in this script file. plugins { + id("org.jetbrains.dokka") `jvm-module` idea `gradle-doctor` @@ -76,7 +78,8 @@ spinePublishing { dependencies { api(Kotlin.reflect) - testImplementation(Spine.testlib) + api(JSpecify.annotations) + testImplementation(TestLib.lib) } configurations.all { @@ -108,3 +111,9 @@ tasks { JavadocConfig.applyTo(project) LicenseReporter.mergeAllReports(project) PomGenerator.applyTo(project) + +dependencies { + productionModules.forEach { + dokka(it) + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index fa3cd93..b41bfb4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -37,9 +37,6 @@ plugins { // https://github.com/jk1/Gradle-License-Report/releases id("com.github.jk1.dependency-license-report").version("2.7") - - // https://github.com/johnrengelman/shadow/releases - id("com.github.johnrengelman.shadow").version("7.1.2") } repositories { @@ -54,7 +51,7 @@ repositories { * Please keep this value in sync with [io.spine.dependency.lib.Jackson.version]. * It is not a requirement but would be good in terms of consistency. */ -val jacksonVersion = "2.15.3" +val jacksonVersion = "2.18.3" /** * The version of Google Artifact Registry used by `buildSrc`. @@ -71,12 +68,12 @@ val licenseReportVersion = "2.7" val grGitVersion = "4.1.1" /** - * The version of the Kotlin Gradle plugin and Kotlin binaries used by the build process. + * The version of the Kotlin Gradle plugin used by the build process. * * This version may change from the [version of Kotlin][io.spine.dependency.lib.Kotlin.version] * used by the project. */ -val kotlinVersion = "1.8.22" +val kotlinEmbeddedVersion = "2.1.21" /** * The version of Guava used in `buildSrc`. @@ -84,7 +81,7 @@ val kotlinVersion = "1.8.22" * Always use the same version as the one specified in [io.spine.dependency.lib.Guava]. * Otherwise, when testing Gradle plugins, clashes may occur. */ -val guavaVersion = "32.1.3-jre" +val guavaVersion = "33.4.8-jre" /** * The version of ErrorProne Gradle plugin. @@ -94,7 +91,7 @@ val guavaVersion = "32.1.3-jre" * @see * Error Prone Gradle Plugin Releases */ -val errorPronePluginVersion = "3.1.0" +val errorPronePluginVersion = "4.2.0" /** * The version of Protobuf Gradle Plugin. @@ -104,7 +101,7 @@ val errorPronePluginVersion = "3.1.0" * @see * Protobuf Gradle Plugins Releases */ -val protobufPluginVersion = "0.9.4" +val protobufPluginVersion = "0.9.5" /** * The version of Dokka Gradle Plugins. @@ -114,24 +111,29 @@ val protobufPluginVersion = "0.9.4" * @see * Dokka Releases */ -val dokkaVersion = "1.9.20" +val dokkaVersion = "2.0.0" /** * The version of Detekt Gradle Plugin. * * @see Detekt Releases */ -val detektVersion = "1.23.0" +val detektVersion = "1.23.8" /** * @see [io.spine.dependency.test.Kotest] */ val kotestJvmPluginVersion = "0.4.10" +/** + * @see [io.spine.dependency.test.Kotest.MultiplatformGradlePlugin] + */ +val kotestMultiplatformPluginVersion = "5.9.1" + /** * @see [io.spine.dependency.test.Kover] */ -val koverVersion = "0.7.2" +val koverVersion = "0.9.1" /** * The version of the Shadow Plugin. @@ -140,7 +142,7 @@ val koverVersion = "0.7.2" * * @see Shadow Plugin releases */ -val shadowVersion = "7.1.2" +val shadowVersion = "8.3.6" configurations.all { resolutionStrategy { @@ -149,27 +151,16 @@ configurations.all { "com.google.protobuf:protobuf-gradle-plugin:$protobufPluginVersion", // Force Kotlin lib versions avoiding using those bundled with Gradle. - "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion", - "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion", - "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" + "org.jetbrains.kotlin:kotlin-stdlib:$kotlinEmbeddedVersion", + "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinEmbeddedVersion", + "org.jetbrains.kotlin:kotlin-reflect:$kotlinEmbeddedVersion" ) } } -val jvmVersion = JavaLanguageVersion.of(11) - -java { - toolchain.languageVersion.set(jvmVersion) -} - -tasks.withType { - kotlinOptions { - jvmTarget = jvmVersion.toString() - } -} - dependencies { api("com.github.jk1:gradle-license-report:$licenseReportVersion") + api(platform("org.jetbrains.kotlin:kotlin-bom:$kotlinEmbeddedVersion")) dependOnAuthCommon() listOf( @@ -178,17 +169,18 @@ dependencies { "com.github.jk1:gradle-license-report:$licenseReportVersion", "com.google.guava:guava:$guavaVersion", "com.google.protobuf:protobuf-gradle-plugin:$protobufPluginVersion", - "gradle.plugin.com.github.johnrengelman:shadow:${shadowVersion}", + "com.gradleup.shadow:shadow-gradle-plugin:$shadowVersion", "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion", "io.kotest:kotest-gradle-plugin:$kotestJvmPluginVersion", + "io.kotest:kotest-framework-multiplatform-plugin-gradle:$kotestMultiplatformPluginVersion", // https://github.com/srikanth-lingala/zip4j "net.lingala.zip4j:zip4j:2.10.0", - "net.ltgt.gradle:gradle-errorprone-plugin:${errorPronePluginVersion}", - "org.ajoberstar.grgit:grgit-core:${grGitVersion}", - "org.jetbrains.dokka:dokka-base:${dokkaVersion}", - "org.jetbrains.dokka:dokka-gradle-plugin:${dokkaVersion}", - "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion", - "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion", + "net.ltgt.gradle:gradle-errorprone-plugin:$errorPronePluginVersion", + "org.ajoberstar.grgit:grgit-core:$grGitVersion", + "org.jetbrains.dokka:dokka-base:$dokkaVersion", + "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion", + "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinEmbeddedVersion", + "org.jetbrains.kotlin:kotlin-reflect:$kotlinEmbeddedVersion", "org.jetbrains.kotlinx:kover-gradle-plugin:$koverVersion" ).forEach { implementation(it) diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000..8d820ec --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +pluginManagement { + repositories { + gradlePluginPortal() + mavenLocal() + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention").version("1.0.0") +} diff --git a/buildSrc/src/main/kotlin/BuildExtensions.kt b/buildSrc/src/main/kotlin/BuildExtensions.kt index c455e44..4654e36 100644 --- a/buildSrc/src/main/kotlin/BuildExtensions.kt +++ b/buildSrc/src/main/kotlin/BuildExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,13 +29,14 @@ import io.spine.dependency.build.ErrorProne import io.spine.dependency.build.GradleDoctor import io.spine.dependency.build.Ksp +import io.spine.dependency.build.PluginPublishPlugin import io.spine.dependency.lib.Protobuf import io.spine.dependency.local.McJava import io.spine.dependency.local.ProtoData import io.spine.dependency.local.ProtoTap import io.spine.dependency.test.Kotest import io.spine.dependency.test.Kover -import io.spine.gradle.standardToSpineSdk +import io.spine.gradle.repo.standardToSpineSdk import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.tasks.JavaExec @@ -66,12 +67,18 @@ import org.gradle.plugin.use.PluginDependencySpec private const val ABOUT_DEPENDENCY_EXTENSIONS = "" /** - * Applies [standard][standardToSpineSdk] repositories to this `buildscript`. + * Applies [standard][io.spine.gradle.repo.standardToSpineSdk] repositories to this `buildscript`. */ fun ScriptHandlerScope.standardSpineSdkRepositories() { repositories.standardToSpineSdk() } +/** + * Shortcut to [Protobuf] dependency object for using under `buildScript`. + */ +val ScriptHandlerScope.protobuf: Protobuf + get() = Protobuf + /** * Shortcut to [McJava] dependency object for using under `buildScript`. */ @@ -99,7 +106,7 @@ val ScriptHandlerScope.protoData: ProtoData * This plugin is published at Gradle Plugin Portal. * But when used in a pair with [mcJava], it cannot be applied directly to a project. * It is so, because [mcJava] uses [protoData] as its dependency. - * And buildscript's classpath ends up with both of them. + * And the buildscript's classpath ends up with both of them. */ val PluginDependenciesSpec.protoData: ProtoData get() = ProtoData @@ -111,8 +118,8 @@ val PluginDependenciesSpec.protoData: ProtoData * declared in auto-generated `org.gradle.kotlin.dsl.PluginAccessors.kt` file. * It conflicts with our own declarations. * - * Declaring of top-level shortcuts eliminates the need in applying plugins - * using fully qualified name of dependency objects. + * Declaring of top-level shortcuts eliminates the need to apply plugins + * using a fully qualified name of dependency objects. * * It is still possible to apply a plugin with a custom version, if needed. * Just declare a version again on the returned [PluginDependencySpec]. @@ -150,6 +157,9 @@ val PluginDependenciesSpec.kover: PluginDependencySpec val PluginDependenciesSpec.ksp: PluginDependencySpec get() = id(Ksp.id).version(Ksp.version) +val PluginDependenciesSpec.`plugin-publish`: PluginDependencySpec + get() = id(PluginPublishPlugin.id).version(PluginPublishPlugin.version) + /** * Configures the dependencies between third-party Gradle tasks * and those defined via ProtoData and Spine Model Compiler. @@ -184,18 +194,39 @@ fun Project.configureTaskDependencies() { val launchTestProtoData = "launchTestProtoData" val generateProto = "generateProto" val createVersionFile = "createVersionFile" - "compileKotlin".dependOn(launchProtoData) - "compileTestKotlin".dependOn(launchTestProtoData) + val compileKotlin = "compileKotlin" + compileKotlin.run { + dependOn(generateProto) + dependOn(launchProtoData) + } + val compileTestKotlin = "compileTestKotlin" + compileTestKotlin.dependOn(launchTestProtoData) val sourcesJar = "sourcesJar" - sourcesJar.dependOn(generateProto) - sourcesJar.dependOn(launchProtoData) - sourcesJar.dependOn(createVersionFile) - sourcesJar.dependOn("prepareProtocConfigVersions") + val kspKotlin = "kspKotlin" + sourcesJar.run { + dependOn(generateProto) + dependOn(launchProtoData) + dependOn(kspKotlin) + dependOn(createVersionFile) + dependOn("prepareProtocConfigVersions") + } val dokkaHtml = "dokkaHtml" - dokkaHtml.dependOn(generateProto) - dokkaHtml.dependOn(launchProtoData) - "dokkaJavadoc".dependOn(launchProtoData) + dokkaHtml.run { + dependOn(generateProto) + dependOn(launchProtoData) + dependOn(kspKotlin) + } + val dokkaJavadoc = "dokkaJavadoc" + dokkaJavadoc.run { + dependOn(launchProtoData) + dependOn(kspKotlin) + } "publishPluginJar".dependOn(createVersionFile) + compileKotlin.dependOn(kspKotlin) + compileTestKotlin.dependOn("kspTestKotlin") + "compileTestFixturesKotlin".dependOn("kspTestFixturesKotlin") + "javadocJar".dependOn(dokkaHtml) + "dokkaKotlinJar".dependOn(dokkaJavadoc) } } @@ -205,17 +236,38 @@ fun Project.configureTaskDependencies() { * By convention, such modules are for integration tests and should be treated differently. */ val Project.productionModules: Iterable - get() = rootProject.subprojects.filter { !it.name.contains("-tests") } + get() = rootProject.subprojects.filterNot { subproject -> + subproject.name.run { + contains("-tests") + || contains("test-fixtures") + || contains("integration-tests") + } + } +/** + * Obtains the names of the [productionModules]. + * + * The extension could be useful for excluding modules from standard publishing: + * ```kotlin + * spinePublishing { + * val customModule = "my-custom-module" + * modules = productionModuleNames.toSet().minus(customModule) + * modulesWithCustomPublishing = setOf(customModule) + * //... + * } + * ``` + */ +val Project.productionModuleNames: List + get() = productionModules.map { it.name } /** - * Sets the remote debug option for this task. + * Sets the remote debug option for this [JavaExec] task. * * The port number is [5566][BuildSettings.REMOTE_DEBUG_PORT]. * * @param enabled If `true` the task will be suspended. */ -fun Task.remoteDebug(enabled: Boolean = true) { this as JavaExec +fun JavaExec.remoteDebug(enabled: Boolean = true) { debugOptions { this@debugOptions.enabled.set(enabled) port.set(BuildSettings.REMOTE_DEBUG_PORT) @@ -224,6 +276,26 @@ fun Task.remoteDebug(enabled: Boolean = true) { this as JavaExec } } +/** + * Sets the remote debug option for the task of [JavaExec] type with the given name. + * + * The port number is [5566][BuildSettings.REMOTE_DEBUG_PORT]. + * + * @param enabled If `true` the task will be suspended. + * @throws IllegalStateException if the task with the given name is not found, or, + * if the taks is not of [JavaExec] type. + */ +fun Project.setRemoteDebug(taskName: String, enabled: Boolean = true) { + val task = tasks.findByName(taskName) + check(task != null) { + "Could not find a task named `$taskName` in the project `$name`." + } + check(task is JavaExec) { + "The task `$taskName` is not of type `JavaExec`." + } + task.remoteDebug(enabled) +} + /** * Sets remote debug options for the `launchProtoData` task. * @@ -231,9 +303,8 @@ fun Task.remoteDebug(enabled: Boolean = true) { this as JavaExec * * @see remoteDebug */ -fun Project.protoDataRemoteDebug(enabled: Boolean = true) { - tasks.findByName("launchProtoData")?.remoteDebug(enabled) -} +fun Project.protoDataRemoteDebug(enabled: Boolean = true) = + setRemoteDebug("launchProtoData", enabled) /** * Sets remote debug options for the `launchTestProtoData` task. @@ -242,6 +313,15 @@ fun Project.protoDataRemoteDebug(enabled: Boolean = true) { * * @see remoteDebug */ -fun Project.testProtoDataRemoteDebug(enabled: Boolean = true) { - tasks.findByName("launchTestProtoData")?.remoteDebug(enabled) -} +fun Project.testProtoDataRemoteDebug(enabled: Boolean = true) = + setRemoteDebug("launchTestProtoData", enabled) + +/** + * Sets remote debug options for the `launchTestFixturesProtoData` task. + * + * @param enabled if `true` the task will be suspended. + * + * @see remoteDebug + */ +fun Project.testFixturesProtoDataRemoteDebug(enabled: Boolean = true) = + setRemoteDebug("launchTestFixturesProtoData", enabled) diff --git a/buildSrc/src/main/kotlin/BuildSettings.kt b/buildSrc/src/main/kotlin/BuildSettings.kt index 0e3eb97..be1da27 100644 --- a/buildSrc/src/main/kotlin/BuildSettings.kt +++ b/buildSrc/src/main/kotlin/BuildSettings.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,14 +24,19 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import org.gradle.api.JavaVersion import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget /** * This object provides high-level constants, like the version of JVM, to be used * throughout the project. */ object BuildSettings { - private const val JVM_VERSION = 11 + private const val JVM_VERSION = 17 val javaVersion: JavaLanguageVersion = JavaLanguageVersion.of(JVM_VERSION) + @Suppress("unused") + val javaVersionCompat = JavaVersion.toVersion(JVM_VERSION) + val jvmTarget = JvmTarget.JVM_17 const val REMOTE_DEBUG_PORT = 5566 } diff --git a/buildSrc/src/main/kotlin/DependencyResolution.kt b/buildSrc/src/main/kotlin/DependencyResolution.kt index 25ae2b4..124adb3 100644 --- a/buildSrc/src/main/kotlin/DependencyResolution.kt +++ b/buildSrc/src/main/kotlin/DependencyResolution.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,6 +29,7 @@ import io.spine.dependency.build.CheckerFramework import io.spine.dependency.build.Dokka import io.spine.dependency.build.ErrorProne import io.spine.dependency.build.FindBugs +import io.spine.dependency.build.JSpecify import io.spine.dependency.lib.Asm import io.spine.dependency.lib.AutoCommon import io.spine.dependency.lib.AutoService @@ -39,16 +40,15 @@ import io.spine.dependency.lib.CommonsLogging import io.spine.dependency.lib.Gson import io.spine.dependency.lib.Guava import io.spine.dependency.lib.J2ObjC -import io.spine.dependency.lib.Jackson import io.spine.dependency.lib.JavaDiffUtils import io.spine.dependency.lib.Kotlin import io.spine.dependency.lib.Okio import io.spine.dependency.lib.Plexus import io.spine.dependency.lib.Protobuf import io.spine.dependency.lib.Slf4J +import io.spine.dependency.local.Base import io.spine.dependency.local.Spine import io.spine.dependency.test.Hamcrest -import io.spine.dependency.test.JUnit import io.spine.dependency.test.Kotest import io.spine.dependency.test.OpenTest4J import io.spine.dependency.test.Truth @@ -85,6 +85,9 @@ fun NamedDomainObjectContainer.forceVersions() { private fun ResolutionStrategy.forceProductionDependencies() { @Suppress("DEPRECATION") // Force versions of SLF4J and Kotlin libs. + Protobuf.libs.forEach { + force(it) + } force( AnimalSniffer.lib, AutoCommon.lib, @@ -96,11 +99,7 @@ private fun ResolutionStrategy.forceProductionDependencies() { FindBugs.annotations, Gson.lib, Guava.lib, - Kotlin.reflect, - Kotlin.stdLib, - Kotlin.stdLibCommon, - Kotlin.stdLibJdk7, - Kotlin.stdLibJdk8, + JSpecify.annotations, Protobuf.GradlePlugin.lib, Protobuf.libs, Slf4J.lib @@ -110,11 +109,6 @@ private fun ResolutionStrategy.forceProductionDependencies() { private fun ResolutionStrategy.forceTestDependencies() { force( Guava.testLib, - JUnit.api, - JUnit.bom, - JUnit.Platform.commons, - JUnit.Platform.launcher, - JUnit.legacy, Truth.libs, Kotest.assertions, ) @@ -137,16 +131,6 @@ private fun ResolutionStrategy.forceTransitiveDependencies() { Gson.lib, Hamcrest.core, J2ObjC.annotations, - JUnit.Platform.engine, - JUnit.Platform.suiteApi, - JUnit.runner, - Jackson.annotations, - Jackson.bom, - Jackson.core, - Jackson.databind, - Jackson.dataformatXml, - Jackson.dataformatYaml, - Jackson.moduleKotlin, JavaDiffUtils.lib, Kotlin.jetbrainsAnnotations, Okio.lib, @@ -186,7 +170,7 @@ fun ModuleDependency.excludeSpineBase() { fun Project.forceSpineBase() { configurations.all { resolutionStrategy { - force(Spine.base) + force(Base.lib) } } } @@ -198,9 +182,9 @@ fun Project.forceSpineBase() { @Suppress("unused") fun Project.forceBaseInProtoTasks() { configurations.configureEach { - if (name.lowercased().contains("proto")) { + if (name.lowercase().contains("proto")) { resolutionStrategy { - force(Spine.baseForBuildScript) + force(Base.libForBuildScript) } } } diff --git a/buildSrc/src/main/kotlin/DocumentationSettings.kt b/buildSrc/src/main/kotlin/DocumentationSettings.kt new file mode 100644 index 0000000..b538fae --- /dev/null +++ b/buildSrc/src/main/kotlin/DocumentationSettings.kt @@ -0,0 +1,58 @@ +/* + * 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. + */ + +/** + * The documentation settings specific to this project. + * + * @see + * Dokka source link configuration + */ +@Suppress("ConstPropertyName") +object DocumentationSettings { + + /** + * Settings passed to Dokka for + * [sourceLink][[org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceLinkSpec] + */ + object SourceLink { + + /** + * The URL of the remote source code + * [location][org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceLinkSpec.remoteUrl]. + */ + const val url: String = "https://github.com/SpineEventEngine/base/tree/master/src" + + /** + * The suffix used to append the source code line number to the URL. + * + * The suffix depends on the online code repository. + * + * @see + * remoteLineSuffix + */ + const val lineSuffix: String = "#L" + } +} diff --git a/buildSrc/src/main/kotlin/DokkaExts.kt b/buildSrc/src/main/kotlin/DokkaExts.kt index 89cbee6..03a6e03 100644 --- a/buildSrc/src/main/kotlin/DokkaExts.kt +++ b/buildSrc/src/main/kotlin/DokkaExts.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -36,13 +36,13 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.DependencyHandlerScope -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.DokkaBaseConfiguration -import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask import org.jetbrains.dokka.gradle.AbstractDokkaTask +import org.jetbrains.dokka.gradle.DokkaExtension import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.dokka.gradle.GradleDokkaSourceSetBuilder +import org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceSetSpec +import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier +import org.jetbrains.dokka.gradle.engine.plugins.DokkaHtmlPluginParameters /** * To generate the documentation as seen from Java perspective, the `kotlin-as-java` @@ -69,7 +69,7 @@ fun DependencyHandlerScope.useDokkaWithSpineExtensions() { private fun DependencyHandler.dokkaPlugin(dependencyNotation: Any): Dependency? = add("dokkaPlugin", dependencyNotation) -private fun Project.dokkaOutput(language: String): File { +internal fun Project.dokkaOutput(language: String): File { val lng = language.titleCaseFirstChar() return layout.buildDirectory.dir("docs/dokka$lng").get().asFile } @@ -93,51 +93,106 @@ fun Project.dokkaConfigFile(file: String): File { * @see * Dokka modifying frontend assets */ -fun AbstractDokkaTask.configureStyle() { - pluginConfiguration { - customStyleSheets = listOf(project.dokkaConfigFile("styles/custom-styles.css")) - customAssets = listOf(project.dokkaConfigFile("assets/logo-icon.svg")) - separateInheritedMembers = true - footerMessage = "Copyright ${LocalDate.now().year}, TeamDev" - } +fun DokkaHtmlPluginParameters.configureStyle(project: Project) { + customAssets.from(project.dokkaConfigFile("assets/logo-icon.svg")) + customStyleSheets.from(project.dokkaConfigFile("styles/custom-styles.css")) + footerMessage.set("Copyright ${LocalDate.now().year}, TeamDev") + separateInheritedMembers.set(true) + mergeImplicitExpectActualDeclarations.set(false) } -private fun AbstractDokkaLeafTask.configureFor(language: String) { - dokkaSourceSets.configureEach { - /** - * Configures links to the external Java documentation. - */ - jdkVersion.set(BuildSettings.javaVersion.asInt()) +private fun DokkaExtension.configureFor( + project: Project, + language: String, + sourceLinkRemoteUrl: String +) { + dokkaPublications.named("html").configure { + suppressInheritedMembers.set(true) + failOnWarning.set(true) + } + + val commonMain = "commonMain" + val jvmMain = "jvmMain" - skipEmptyPackages.set(true) + val commonMainDir = project.file("src/$commonMain") + val jvmMainDir = project.file("src/$jvmMain") + val isKmp = commonMainDir.exists() || jvmMainDir.exists() - includeNonPublic.set(true) + if (isKmp) { + if (commonMainDir.exists()) { + dokkaSourceSets.named(commonMain).configure { + configureSourceSet( + SourceSetConfig(commonMainDir, sourceLinkRemoteUrl) + ) + } + } - documentedVisibilities.set( - setOf( - DokkaConfiguration.Visibility.PUBLIC, - DokkaConfiguration.Visibility.PROTECTED + if (jvmMainDir.exists()) { + dokkaSourceSets.named(jvmMain).configure { + configureSourceSet( + SourceSetConfig(jvmMainDir, sourceLinkRemoteUrl, null) + ) + } + } + } else { + dokkaSourceSets.named("main").configure { + configureSourceSet( + SourceSetConfig( + sourceDir = project.file("src/main/${language.lowercase()}"), + sourceLinkRemoteUrl = sourceLinkRemoteUrl, + ) ) - ) + } } - outputDirectory.set(project.dokkaOutput(language)) + pluginsConfiguration.named("html").configure { this as DokkaHtmlPluginParameters + configureStyle(project) + } +} - configureStyle() +private data class SourceSetConfig( + val sourceDir: File, + val sourceLinkRemoteUrl: String, + val moduleDoc: String? = "Module.md" +) + +private fun DokkaSourceSetSpec.configureSourceSet(config: SourceSetConfig) { + config.moduleDoc?.let { doc -> + if (File(doc).exists()) { + this@configureSourceSet.includes.from(doc) + } + } + + sourceLink { + localDirectory.set(config.sourceDir) + remoteUrl(config.sourceLinkRemoteUrl) + remoteLineSuffix.set(DocumentationSettings.SourceLink.lineSuffix) + } + + jdkVersion.set(BuildSettings.javaVersion.asInt()) + skipEmptyPackages.set(true) + + documentedVisibilities.set( + setOf( + VisibilityModifier.Public, + VisibilityModifier.Protected + ) + ) } /** * Configures this [DokkaTask] to accept only Kotlin files. */ -fun AbstractDokkaLeafTask.configureForKotlin() { - configureFor("kotlin") +fun DokkaExtension.configureForKotlin(project: Project, sourceLinkRemoteUrl: String) { + configureFor(project, "kotlin", sourceLinkRemoteUrl) } /** * Configures this [DokkaTask] to accept only Java files. */ -fun AbstractDokkaLeafTask.configureForJava() { - configureFor("java") +@Suppress("unused") +fun DokkaExtension.configureForJava(project: Project, sourceLinkRemoteUrl: String) { + configureFor(project, "java", sourceLinkRemoteUrl) } /** @@ -179,16 +234,17 @@ fun Project.dokkaKotlinJar(): TaskProvider = tasks.getOrCreate("dokkaKotlin } /** - * Tells if this task belongs to the execution graph which contains publishing tasks. + * Tells if this task belongs to the execution graph which contains + * the `publish` and `dokkaGenerate` tasks. * - * The task `"publishToMavenLocal"` is excluded from the check because it is a part of - * the local testing workflow. + * This predicate could be useful for disabling publishing tasks + * when doing, e.g., `publishToMavenLocal` for the purpose of the + * integration tests that (of course) do not test the documentation + * generation proces and its resuults. */ fun AbstractDokkaTask.isInPublishingGraph(): Boolean = project.gradle.taskGraph.allTasks.any { - with(it.name) { - startsWith("publish") && !startsWith("publishToMavenLocal") - } + it.name == "publish" || it.name.contains("dokkaGenerate") } /** @@ -217,7 +273,7 @@ fun Project.dokkaJavaJar(): TaskProvider = tasks.getOrCreate("dokkaJavaJar" fun Project.disableDocumentationTasks() { gradle.taskGraph.whenReady { tasks.forEach { task -> - val lowercaseName = task.name.lowercased() + val lowercaseName = task.name.lowercase() if (lowercaseName.contains("dokka") || lowercaseName.contains("javadoc")) { task.enabled = false } diff --git a/buildSrc/src/main/kotlin/LicenseSettings.kt b/buildSrc/src/main/kotlin/LicenseSettings.kt new file mode 100644 index 0000000..465129f --- /dev/null +++ b/buildSrc/src/main/kotlin/LicenseSettings.kt @@ -0,0 +1,43 @@ +/* +* 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. +*/ + +/** + * The settings of the software license which apply to the code of this project. + * + * The constants defined in this object are used by the + * [PublicationHandler][io.spine.gradle.publish.PublicationHandler] to set up + * corresponding properties of the published `pom.xml` file of an artifact. + * + * So, in order to adapt the license settings to the requirements of a particular project, + * simply change the values of the constants defined in this object. + * + * @see io.spine.gradle.publish.PublicationHandler + */ +@Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names +object LicenseSettings { + const val name = "The Apache License, Version 2.0" + const val url = "https://www.apache.org/licenses/LICENSE-2.0.txt" +} diff --git a/buildSrc/src/main/kotlin/Strings.kt b/buildSrc/src/main/kotlin/Strings.kt index 9589d39..19e0c21 100644 --- a/buildSrc/src/main/kotlin/Strings.kt +++ b/buildSrc/src/main/kotlin/Strings.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,8 +24,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import org.gradle.configurationcache.extensions.capitalized - /** * This file provides extensions to `String` and `CharSequence` that wrap * analogues from standard Kotlin runtime. @@ -43,19 +41,10 @@ private const val ABOUT = "" /** * Makes the first character come in the title case. */ -fun String.titleCaseFirstChar(): String { - // return replaceFirstChar { it.titlecase() } - // OR for earlier Kotlin versions: - // 1. add import of `org.gradle.configurationcache.extensions.capitalized` - // 2. call `capitalized()` instead of `replaceFirstChar` above. - return capitalized() -} +fun String.titleCaseFirstChar(): String = replaceFirstChar { it.titlecase() } /** * Converts this string to lowercase. */ -fun String.lowercased(): String { - // return lowercase() - // OR for earlier Kotlin versions call: - return toLowerCase() -} +@Deprecated(message = "Please use `lowercase()` instead.", replaceWith = ReplaceWith("lowercase")) +fun String.lowercased(): String = lowercase() diff --git a/buildSrc/src/main/kotlin/compile-protobuf.gradle.kts b/buildSrc/src/main/kotlin/compile-protobuf.gradle.kts index cc08bf0..4acbb86 100644 --- a/buildSrc/src/main/kotlin/compile-protobuf.gradle.kts +++ b/buildSrc/src/main/kotlin/compile-protobuf.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -32,13 +32,21 @@ plugins { id("com.google.protobuf") } - // For generating test fixtures. See `src/test/proto`. protobuf { configurations.excludeProtobufLite() protoc { artifact = Protobuf.compiler } + + afterEvaluate { + // Walk the collection of tasks to force the execution + // of the `configureEach` operations earlier. + // This hack allows to avoid `ConcurrentModificationException` on + // creating `kspKotlin` task. + generateProtoTasks.all().size + } + generateProtoTasks.all().configureEach { setup() } diff --git a/buildSrc/src/main/kotlin/config-tester.gradle.kts b/buildSrc/src/main/kotlin/config-tester.gradle.kts index c0cb504..21d31e3 100644 --- a/buildSrc/src/main/kotlin/config-tester.gradle.kts +++ b/buildSrc/src/main/kotlin/config-tester.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts b/buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts index 503114d..7b0ffd1 100644 --- a/buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts +++ b/buildSrc/src/main/kotlin/detekt-code-analysis.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts b/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts index 2c10701..6bab079 100644 --- a/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask +import org.jetbrains.dokka.gradle.DokkaTaskPartial plugins { id("org.jetbrains.dokka") // Cannot use `Dokka` dependency object here yet. @@ -35,9 +35,17 @@ dependencies { useDokkaWithSpineExtensions() } -tasks.withType().configureEach { - configureForJava() +afterEvaluate { + dokka { + configureForKotlin( + project, + DocumentationSettings.SourceLink.url + ) + } +} + +tasks.withType().configureEach { onlyIf { - (it as AbstractDokkaLeafTask).isInPublishingGraph() + isInPublishingGraph() } } diff --git a/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts b/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts index 73da35d..c24135e 100644 --- a/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask +import org.jetbrains.dokka.gradle.DokkaTaskPartial plugins { id("org.jetbrains.dokka") // Cannot use `Dokka` dependency object here yet. @@ -34,9 +34,17 @@ dependencies { useDokkaWithSpineExtensions() } -tasks.withType().configureEach { - configureForKotlin() +afterEvaluate { + dokka { + configureForKotlin( + project, + DocumentationSettings.SourceLink.url + ) + } +} + +tasks.withType().configureEach { onlyIf { - (it as AbstractDokkaLeafTask).isInPublishingGraph() + isInPublishingGraph() } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt b/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt new file mode 100644 index 0000000..87f1174 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt @@ -0,0 +1,116 @@ +/* + * 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.dependency + +import io.spine.gradle.log +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ResolutionStrategy + +/** + * A dependency is a software component we use in a project. + * + * It could be a library, a set of libraries, or a development tool + * that participates in a build. + */ +abstract class Dependency { + + /** + * The version of the dependency in terms of Maven coordinates. + */ + abstract val version: String + + /** + * The group of the dependency in terms of Maven coordinates. + */ + abstract val group: String + + /** + * The modules of the dependency that we use directly or + * transitively in our projects. + */ + abstract val modules: List + + /** + * The [modules] given with the [version]. + */ + final val artifacts: Map by lazy { + modules.associateWith { "$it:$version" } + } + + /** + * Obtains full Maven coordinates for the requested [module]. + */ + fun artifact(module: String): String = artifacts[module] ?: error( + "The dependency `${this::class.simpleName}` does not declare a module `$module`." + ) + + /** + * Forces all artifacts of this dependency using the given resolution strategy. + * + * @param project The project in which the artifacts are forced. Used for logging. + * @param cfg The configuration for which the artifacts are forced. Used for logging. + * @param rs The resolution strategy which forces the artifacts. + */ + fun forceArtifacts(project: Project, cfg: Configuration, rs: ResolutionStrategy) { + artifacts.values.forEach { + rs.forceWithLogging(project, cfg, it) + } + } +} + +/** + * A dependency which declares a Maven Bill of Materials (BOM). + * + * @see + * Maven Bill of Materials + * @see io.spine.dependency.boms.Boms + * @see io.spine.dependency.boms.BomsPlugin + */ +abstract class DependencyWithBom : Dependency() { + + /** + * Maven coordinates of the dependency BOM. + */ + abstract val bom: String +} + +/** + * Returns the suffix of diagnostic messages for this configuration in the given project. + */ +fun Configuration.diagSuffix(project: Project): String = + "the configuration `$name` in the project: `${project.path}`." + + +private fun ResolutionStrategy.forceWithLogging( + project: Project, + configuration: Configuration, + artifact: String +) { + force(artifact) + project.log { "Forced the version of `$artifact` in " + configuration.diagSuffix(project) } +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt b/buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt new file mode 100644 index 0000000..df1751b --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.kt @@ -0,0 +1,66 @@ +/* + * 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.dependency.boms + +import io.spine.dependency.DependencyWithBom +import io.spine.dependency.kotlinx.Coroutines +import io.spine.dependency.lib.Jackson +import io.spine.dependency.lib.Kotlin +import io.spine.dependency.lib.Grpc +import io.spine.dependency.test.JUnit + +/** + * The collection of references to BOMs applied by [BomsPlugin]. + * + * @see + * Maven Bill of Materials + */ +object Boms { + + /** + * The base production BOMs. + */ + val core: List = listOf( + Kotlin, + Coroutines + ) + + /** + * The BOMs for testing dependencies. + */ + val testing: List = listOf( + JUnit + ) + + /** + * Technology-based BOMs. + */ + object Optional { + val jackson = Jackson.bom + val grpc = Grpc.bom + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt new file mode 100644 index 0000000..2724dda --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt @@ -0,0 +1,185 @@ +/* + * 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.dependency.boms + +import io.gitlab.arturbosch.detekt.getSupportedKotlinVersion +import io.spine.dependency.DependencyWithBom +import io.spine.dependency.diagSuffix +import io.spine.dependency.kotlinx.Coroutines +import io.spine.dependency.lib.Kotlin +import io.spine.dependency.test.JUnit +import io.spine.gradle.log +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer + +/** + * The plugin which forces versions of platforms declared in the [Boms] object. + * + * Versions are enforced via the + * [org.gradle.api.artifacts.dsl.DependencyHandler.enforcedPlatform] call + * for configurations of the project to which the plugin is applied. + * + * The configurations are selected by the "kind" of BOM. + * + * [Boms.core] are applied to: + * 1. Production configurations, such as `api` or `implementation`. + * 2. Compilation configurations. + * 3. All `ksp` configurations. + * + * [Boms.testing] are applied to all testing configurations. + * + * In addition to forcing BOM-based dependencies, + * the plugin [forces][org.gradle.api.artifacts.ResolutionStrategy.force] the versions + * of [Kotlin.StdLib.artifacts] for all configurations because even through Kotlin + * artifacts are forced with BOM, the `variants` in the dependencies cannot be + * picked by Gradle. + * + * Run Gradle with the [INFO][org.gradle.api.logging.Logger.isInfoEnabled] logging level + * to see the dependencies forced by this plugin. + */ +class BomsPlugin : Plugin { + + private val productionConfigs = listOf( + "api", + "implementation", + "compileOnly", + "runtimeOnly" + ) + + override fun apply(project: Project) = with(project) { + + configurations.run { + matching { isCompilationConfig(it.name) }.all { + applyBoms(project, Boms.core) + } + matching { isKspConfig(it.name) }.all { + applyBoms(project, Boms.core) + } + matching { it.name in productionConfigs }.all { + applyBoms(project, Boms.core) + } + matching { isTestConfig(it.name) }.all { + applyBoms(project, Boms.core + Boms.testing) + } + + matching { !supportsBom(it.name) }.all { + resolutionStrategy.eachDependency { + if (requested.group == Kotlin.group) { + val kotlinVersion = Kotlin.runtimeVersion + useVersion(kotlinVersion) + val suffix = this@all.diagSuffix(project) + log { "Forced Kotlin version `$kotlinVersion` in $suffix" } + } + } + } + + selectKotlinCompilerForDetekt() + project.forceArtifacts() + } + } +} + +private fun Configuration.applyBoms(project: Project, deps: List) { + deps.forEach { dep -> + withDependencies { + val platform = project.dependencies.platform(dep.bom) + addLater(project.provider { platform }) + project.log { + "Applied BOM: `${dep.bom}` to the configuration: `${this@applyBoms.name}`." + } + } + } +} + +private val Configuration.isDetekt: Boolean + get() = name.contains("detekt", ignoreCase = true) + +@Suppress("UnstableApiUsage") // `io.gitlab.arturbosch.detekt.getSupportedKotlinVersion` +private fun ConfigurationContainer.selectKotlinCompilerForDetekt() = + matching { it.isDetekt } + .configureEach { + resolutionStrategy.eachDependency { + if (requested.group == Kotlin.group) { + val supportedVersion = getSupportedKotlinVersion() + useVersion(supportedVersion) + because("Force Kotlin version $supportedVersion in Detekt configurations.") + } + } + } + +private fun isCompilationConfig(name: String) = + name.contains("compile", ignoreCase = true) && + // `compileProtoPath` or `compileTestProtoPath`. + !name.contains("ProtoPath", ignoreCase = true) + +private fun isKspConfig(name: String) = + name.startsWith("ksp", ignoreCase = true) + +private fun isTestConfig(name: String) = + name.startsWith("test", ignoreCase = true) + +/** + * Tells if the configuration with the given [name] supports forcing + * versions via the BOM mechanism. + * + * Not all configurations support forcing via BOM. E.g., the configurations created + * by Protobuf Gradle Plugin such as `compileProtoPath` or `extractIncludeProto` do + * not pick up versions of dependencies set via `enforcedPlatform(myBom)`. + */ +private fun supportsBom(name: String) = + (isCompilationConfig(name) || isKspConfig(name) || isTestConfig(name)) + +/** + * Forces the versions of the artifacts that are even being correctly selected by BOMs + * are not guaranteed to be handled correctly when Gradle picks up a `variant`. + * + * The function forces the versions for all configurations but [detekt][isDetekt], because + * it requires a compatible version of the Kotlin compiler. + * + * @see Kotlin.artifacts + * @see Kotlin.StdLib.artifacts + * @see Coroutines.artifacts + * @see selectKotlinCompilerForDetekt + */ +private fun Project.forceArtifacts() = + configurations.all { + resolutionStrategy { + if (!isDetekt) { + val rs = this@resolutionStrategy + val project = this@forceArtifacts + val cfg = this@all + Kotlin.forceArtifacts(project, cfg, rs) + Kotlin.StdLib.forceArtifacts(project, cfg, rs) + Coroutines.forceArtifacts(project, cfg, rs) + JUnit.Jupiter.forceArtifacts(project, cfg, rs) /* + for configurations like `testFixturesCompileProtoPath`. + */ + } + } + } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt index 9fe3a89..8258896 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt index ab9a08a..e1e3b28 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt index dd3f1fb..5d2140a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt index 45e73dd..003bf58 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Dokka.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -35,7 +35,7 @@ object Dokka { * When changing the version, also change the version used in the * `buildSrc/build.gradle.kts`. */ - const val version = "1.9.20" + const val version = "2.0.0" object GradlePlugin { const val id = "org.jetbrains.dokka" @@ -78,7 +78,7 @@ object Dokka { object SpineExtensions { private const val group = "io.spine.tools" - const val version = "2.0.0-SNAPSHOT.4" + const val version = "2.0.0-SNAPSHOT.7" const val lib = "$group:spine-dokka-extensions:$version" } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/ErrorProne.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/ErrorProne.kt index 7ce19e0..5b47c1b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/ErrorProne.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/ErrorProne.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -30,18 +30,21 @@ package io.spine.dependency.build @Suppress("unused", "ConstPropertyName") object ErrorProne { // https://github.com/google/error-prone - private const val version = "2.23.0" + private const val version = "2.36.0" + + const val group = "com.google.errorprone" + // https://github.com/tbroyer/gradle-errorprone-plugin/blob/v0.8/build.gradle.kts private const val javacPluginVersion = "9+181-r4173-1" val annotations = listOf( - "com.google.errorprone:error_prone_annotations:$version", - "com.google.errorprone:error_prone_type_annotations:$version" + "$group:error_prone_annotations:$version", + "$group:error_prone_type_annotations:$version" ) - const val core = "com.google.errorprone:error_prone_core:$version" - const val checkApi = "com.google.errorprone:error_prone_check_api:$version" - const val testHelpers = "com.google.errorprone:error_prone_test_helpers:$version" - const val javacPlugin = "com.google.errorprone:javac:$javacPluginVersion" + const val core = "$group:error_prone_core:$version" + const val checkApi = "$group:error_prone_check_api:$version" + const val testHelpers = "$group:error_prone_test_helpers:$version" + const val javacPlugin = "$group:javac:$javacPluginVersion" // https://github.com/tbroyer/gradle-errorprone-plugin/releases object GradlePlugin { @@ -53,7 +56,7 @@ object ErrorProne { * When the plugin is used as a library (e.g., in tools), its version and the library * artifacts are of importance. */ - const val version = "3.1.0" + const val version = "4.1.0" const val lib = "net.ltgt.gradle:gradle-errorprone-plugin:$version" } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/FindBugs.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/FindBugs.kt index ff99a09..98003b9 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/FindBugs.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/FindBugs.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/GradleDoctor.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/GradleDoctor.kt index c1a6bc5..89cfb34 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/GradleDoctor.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/GradleDoctor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt index 9b884f2..662a94e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -26,15 +26,31 @@ package io.spine.dependency.build +import io.spine.dependency.Dependency + /** * Kotlin Symbol Processing API. * * @see KSP GitHub repository */ -object Ksp { - /** - * The latest version compatible with Kotlin v1.8.22, which is bundled with Gradle 7.6.4. - */ - const val version = "1.8.22-1.0.11" +@Suppress("unused") +object Ksp : Dependency() { + override val version = "2.1.21-2.0.2" + override val group = "com.google.devtools.ksp" + const val id = "com.google.devtools.ksp" + + val symbolProcessingApi = "$group:symbol-processing-api" + val symbolProcessing = "$group:symbol-processing" + val symbolProcessingAaEmb = "$group:symbol-processing-aa-embeddable" + val symbolProcessingCommonDeps = "$group:symbol-processing-common-deps" + val gradlePlugin = "$group:symbol-processing-gradle-plugin" + + override val modules = listOf( + symbolProcessingApi, + symbolProcessing, + symbolProcessingAaEmb, + symbolProcessingCommonDeps, + gradlePlugin, + ) } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/LicenseReport.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/LicenseReport.kt index cdbfc04..a4eb754 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/LicenseReport.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/LicenseReport.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/OsDetector.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/OsDetector.kt index 76d2834..5b479bf 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/OsDetector.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/OsDetector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt index 0ec075e..dd71b49 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -31,12 +31,5 @@ package io.spine.dependency.build // https://github.com/pmd/pmd/releases @Suppress("unused", "ConstPropertyName") object Pmd { - /** - * This is the last version in the 6.x series. - * - * There's a major update to 7.x series. - * - * @see GitHub project + */ +object Coroutines : DependencyWithBom() { + override val group = KotlinX.group + override val version = "1.10.2" + + @Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names + const val infix = "kotlinx-coroutines" + + override val bom = "$group:$infix-bom:$version" + + val core = "$group:$infix-core" + val coreJvm = "$group:$infix-core-jvm" + val jdk7 = "$group:$infix-jdk7" + val jdk8 = "$group:$infix-jdk8" + val debug = "$group:$infix-debug" + val test = "$group:$infix-test" + val testJvm = "$group:$infix-test-jvm" + + override val modules = listOf(core, coreJvm, jdk7, jdk8, debug, test, testJvm) +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/KotlinX.kt b/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/KotlinX.kt new file mode 100644 index 0000000..fa34b0f --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/KotlinX.kt @@ -0,0 +1,32 @@ +/* + * 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.dependency.kotlinx + +@Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names +object KotlinX { + const val group = "org.jetbrains.kotlinx" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/Serialization.kt b/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/Serialization.kt new file mode 100644 index 0000000..08e436d --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/kotlinx/Serialization.kt @@ -0,0 +1,73 @@ +/* + * 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.dependency.kotlinx + +/** + * The [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) library. + */ +@Suppress("ConstPropertyName") // https://bit.ly/kotlin-prop-names +object Serialization { + + const val group = KotlinX.group + + /** + * The version of the library. + * + * @see Releases + */ + const val version = "1.8.1" + + private const val infix = "kotlinx-serialization" + const val bom = "$group:$infix-bom:$version" + const val coreJvm = "$group:$infix-core-jvm" + const val json = "$group:$infix-json" + + /** + * The [Gradle plugin](https://github.com/Kotlin/kotlinx.serialization/tree/master?tab=readme-ov-file#gradle) + * for using the serialization library. + * + * Usage: + * ```kotlin + * plugins { + * // ... + * kotlin(Serialization.GradlePlugin.shortId) version Kotlin.version + * } + * ``` + */ + object GradlePlugin { + + /** + * The ID to be used with the `kotlin(shortId)` DSL under the`plugins { }` block. + */ + const val shortId = "plugin.serialization" + + /** + * The full ID of the plugin. + */ + const val id = "org.jetbrains.kotlin.$shortId" + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Aedile.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Aedile.kt index 2c6ee5a..1a623f3 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Aedile.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Aedile.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -27,12 +27,12 @@ package io.spine.dependency.lib /** - * A Kotlin wrapper over [Caffeine]. + * A Kotlin wrapper over [io.spine.dependency.lib.Caffeine]. * * @see Aedile at GitHub */ @Suppress("unused") object Aedile { - private const val version = "1.3.1" + private const val version = "2.1.2" const val lib = "com.sksamuel.aedile:aedile-core:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt index 76e781a..f3eff5e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/AppEngine.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/AppEngine.kt index be4c4f1..b4e7336 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/AppEngine.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/AppEngine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt index 3975683..0a388c9 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -30,13 +30,14 @@ package io.spine.dependency.lib @Suppress("unused", "ConstPropertyName") object Asm { private const val version = "9.6" - const val lib = "org.ow2.asm:asm:$version" + const val group = "org.ow2.asm" + const val lib = "$group:asm:$version" // We use the following artifacts only to force the versions // of the dependencies which are transitive for us. // - const val tree = "org.ow2.asm:asm-tree:$version" - const val analysis = "org.ow2.asm:asm-analysis:$version" - const val util = "org.ow2.asm:asm-util:$version" - const val commons = "org.ow2.asm:asm-commons:$version" + const val tree = "$group:asm-tree:$version" + const val analysis = "$group:asm-analysis:$version" + const val util = "$group:asm-util:$version" + const val commons = "$group:asm-commons:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Auto.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Auto.kt index 526a683..6c222b9 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Auto.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Auto.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -50,11 +50,6 @@ object AutoValue { // https://github.com/ZacSweers/auto-service-ksp object AutoServiceKsp { - /** - * The latest version compatible with Kotlin 1.8.22. - * - * @see Ksp.version - */ - private const val version = "1.1.0" + private const val version = "1.2.0" const val processor = "dev.zacsweers.autoservice:auto-service-ksp:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt index 530b684..5289d0c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt index db490e7..23f44a4 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -30,12 +30,14 @@ package io.spine.dependency.lib * A [high performance](https://github.com/ben-manes/caffeine/wiki/Benchmarks), * [near optimal](https://github.com/ben-manes/caffeine/wiki/Efficiency) caching library. * - * This library is a transitive dependency for us via ErrorProne. + * This library is a transitive dependency for us via + * [io.spine.dependency.lib.Aedile] and + * [io.spine.dependency.build.ErrorProne]. * * @see Caffeine at GitHub */ @Suppress("unused") object Caffeine { - private const val version = "3.0.5" + private const val version = "3.2.0" const val lib = "com.github.ben-manes.caffeine:caffeine:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Clikt.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Clikt.kt index 4773154..3da42ce 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Clikt.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Clikt.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCli.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCli.kt index 42860c2..6063278 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCli.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCli.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt index 736f848..270b704 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsLogging.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsLogging.kt index 844a2f0..9f1139c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsLogging.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsLogging.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Coroutines.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Coroutines.kt index 97e65f9..2073d76 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Coroutines.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Coroutines.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -28,14 +28,25 @@ package io.spine.dependency.lib /** * Kotlin Coroutines. - * + * * @see GitHub projecet */ -@Suppress("unused") +@Suppress("unused", "ConstPropertyName") +@Deprecated( + message = "Please use `Coroutines` from the `io.spine.dependency.kotlinx` package", + replaceWith = ReplaceWith( + expression = "Coroutines", + imports = ["io.spine.dependency.kotlinx.Coroutines"] + ) +) object Coroutines { - const val version = "1.6.4" - const val jdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$version" - const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" - const val bom = "org.jetbrains.kotlinx:kotlinx-coroutines-bom:$version" - const val coreJvm = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$version" + const val group = "org.jetbrains.kotlinx" + val version = io.spine.dependency.kotlinx.Coroutines.version + val bom = "$group:kotlinx-coroutines-bom:$version" + val core = "$group:kotlinx-coroutines-core:$version" + val coreJvm = "$group:kotlinx-coroutines-core-jvm:$version" + val jdk8 = "$group:kotlinx-coroutines-jdk8:$version" + val debug = "$group:kotlinx-coroutines-debug:$version" + val test = "$group:kotlinx-coroutines-test:$version" + val testJvm = "$group:kotlinx-coroutines-test-jvm:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt index 3413aff..1d798ee 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Flogger.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Flogger.kt index 7a4e398..769c905 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Flogger.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Flogger.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt index 07fcf2e..7fd3340 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt index e736f4c..b755168 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt index 9449f12..435d6b4 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -26,25 +26,58 @@ package io.spine.dependency.lib +import io.spine.dependency.DependencyWithBom + // https://github.com/grpc/grpc-java -@Suppress("unused", "ConstPropertyName") -object Grpc { - @Suppress("MemberVisibilityCanBePrivate") - const val version = "1.59.0" - const val api = "io.grpc:grpc-api:$version" - const val auth = "io.grpc:grpc-auth:$version" - const val core = "io.grpc:grpc-core:$version" - const val context = "io.grpc:grpc-context:$version" - const val inProcess = "io.grpc:grpc-inprocess:$version" - const val stub = "io.grpc:grpc-stub:$version" - const val okHttp = "io.grpc:grpc-okhttp:$version" - const val protobuf = "io.grpc:grpc-protobuf:$version" - const val protobufLite = "io.grpc:grpc-protobuf-lite:$version" - const val netty = "io.grpc:grpc-netty:$version" - const val nettyShaded = "io.grpc:grpc-netty-shaded:$version" +@Suppress("unused") +object Grpc : DependencyWithBom() { + + override val version = "1.72.0" + override val group = "io.grpc" + override val bom = "$group:grpc-bom:$version" + + val api = "$group:grpc-api" + val auth = "$group:grpc-auth" + val core = "$group:grpc-core" + val context = "$group:grpc-context" + val inProcess = "$group:grpc-inprocess" + val stub = "$group:grpc-stub" + val okHttp = "$group:grpc-okhttp" + val protobuf = "$group:grpc-protobuf" + val protobufLite = "$group:grpc-protobuf-lite" + val netty = "$group:grpc-netty" + val nettyShaded = "$group:grpc-netty-shaded" + + override val modules = listOf( + api, + auth, + core, + context, + inProcess, + stub, + okHttp, + protobuf, + protobufLite, + netty, + nettyShaded + ) + object ProtocPlugin { const val id = "grpc" - const val artifact = "io.grpc:protoc-gen-grpc-java:$version" + @Deprecated( + message = "Please use `GrpcKotlin.ProtocPlugin.artifact` instead.", + replaceWith = ReplaceWith("GrpcKotlin.ProtocPlugin.artifact") + ) + const val kotlinPluginVersion = GrpcKotlin.version + val artifact = "$group:protoc-gen-grpc-java:$version" + + // https://github.com/grpc/grpc-kotlin + // https://repo.maven.apache.org/maven2/io/grpc/protoc-gen-grpc-kotlin/ + @Deprecated( + message = "Please use `GrpcKotlin.ProtocPlugin.artifact` instead.", + replaceWith = ReplaceWith("GrpcKotlin.ProtocPlugin.artifact") + ) + const val artifactKotlin = GrpcKotlin.ProtocPlugin.artifact } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GrpcKotlin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GrpcKotlin.kt index 1187fe4..05d21dd 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GrpcKotlin.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GrpcKotlin.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -33,11 +33,12 @@ package io.spine.dependency.lib */ @Suppress("unused") object GrpcKotlin { - const val version = "1.3.0" + const val version = "1.4.1" const val stub = "io.grpc:grpc-kotlin-stub:$version" object ProtocPlugin { const val id = "grpckt" + // https://central.sonatype.com/artifact/io.grpc/protoc-gen-grpc-kotlin const val artifact = "io.grpc:protoc-gen-grpc-kotlin:$version:jdk8@jar" } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Gson.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Gson.kt index 2cd1949..da9ae93 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Gson.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Gson.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -34,6 +34,6 @@ package io.spine.dependency.lib */ @Suppress("unused", "ConstPropertyName") object Gson { - private const val version = "2.10.1" + private const val version = "2.13.0" const val lib = "com.google.code.gson:gson:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt index 78dd2a5..9115701 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -37,7 +37,8 @@ package io.spine.dependency.lib */ @Suppress("unused", "ConstPropertyName") object Guava { - private const val version = "32.1.3-jre" - const val lib = "com.google.guava:guava:$version" - const val testLib = "com.google.guava:guava-testlib:$version" + private const val version = "33.4.8-jre" + const val group = "com.google.guava" + const val lib = "$group:guava:$version" + const val testLib = "$group:guava-testlib:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/HttpClient.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/HttpClient.kt index 4e33e51..526f05f 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/HttpClient.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/HttpClient.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt index 4a97db6..7f9232b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/J2ObjC.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/J2ObjC.kt index 3bd4577..3098466 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/J2ObjC.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/J2ObjC.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt index 7ad22a0..b47b0c5 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -26,38 +26,102 @@ package io.spine.dependency.lib +import io.spine.dependency.Dependency +import io.spine.dependency.DependencyWithBom + // https://github.com/FasterXML/jackson/wiki/Jackson-Releases -@Suppress("unused", "ConstPropertyName") -object Jackson { - const val version = "2.15.3" - private const val databindVersion = "2.15.3" +@Suppress("unused") +object Jackson : DependencyWithBom() { + override val group = "com.fasterxml.jackson" + override val version = "2.18.3" + + // https://github.com/FasterXML/jackson-bom + override val bom = "$group:jackson-bom:$version" + + private val groupPrefix = group + private val coreGroup = "$groupPrefix.core" + private val moduleGroup = "$groupPrefix.module" - private const val coreGroup = "com.fasterxml.jackson.core" - private const val dataformatGroup = "com.fasterxml.jackson.dataformat" - private const val moduleGroup = "com.fasterxml.jackson.module" + // Constants coming below without `$version` are covered by the BOM. // https://github.com/FasterXML/jackson-core - const val core = "$coreGroup:jackson-core:$version" + val core = "$coreGroup:jackson-core" + // https://github.com/FasterXML/jackson-databind - const val databind = "$coreGroup:jackson-databind:$databindVersion" - // https://github.com/FasterXML/jackson-annotations - const val annotations = "$coreGroup:jackson-annotations:$version" + val databind = "$coreGroup:jackson-databind" - // https://github.com/FasterXML/jackson-dataformat-xml/releases - const val dataformatXml = "$dataformatGroup:jackson-dataformat-xml:$version" - // https://github.com/FasterXML/jackson-dataformats-text/releases - const val dataformatYaml = "$dataformatGroup:jackson-dataformat-yaml:$version" + // https://github.com/FasterXML/jackson-annotations + val annotations = "$coreGroup:jackson-annotations" // https://github.com/FasterXML/jackson-module-kotlin/releases - const val moduleKotlin = "$moduleGroup:jackson-module-kotlin:$version" + val moduleKotlin = "$moduleGroup:jackson-module-kotlin" - // https://github.com/FasterXML/jackson-bom - const val bom = "com.fasterxml.jackson:jackson-bom:$version" + override val modules = listOf( + core, + databind, + annotations, + moduleKotlin + ) + + object DataFormat : Dependency() { + override val version = Jackson.version + override val group = "$groupPrefix.dataformat" + + private const val infix = "jackson-dataformat" + + // https://github.com/FasterXML/jackson-dataformat-xml/releases + val xml = "$group:$infix-xml" + + // https://github.com/FasterXML/jackson-dataformats-text/releases + val yaml = "$group:$infix-yaml" + + val xmlArtifact = "$xml:$version" + val yamlArtifact = "$yaml:$version" + + override val modules = listOf(xml, yaml) + } + + object DataType : Dependency() { + override val version = Jackson.version + override val group = "$groupPrefix.datatype" + + private const val infix = "jackson-datatype" + + // https://github.com/FasterXML/jackson-modules-java8 + val jdk8 = "$group:$infix-jdk8" + + // https://github.com/FasterXML/jackson-modules-java8/tree/2.19/datetime + val dateTime = "$group:$infix-jsr310" + + // https://github.com/FasterXML/jackson-datatypes-collections/blob/2.19/guava + val guava = "$group:$infix-guava" + + // https://github.com/FasterXML/jackson-dataformats-binary/tree/2.19/protobuf + val protobuf = "$group:$infix-protobuf" + + // https://github.com/FasterXML/jackson-datatypes-misc/tree/2.19/javax-money + val javaXMoney = "$group:$infix-javax-money" + + // https://github.com/FasterXML/jackson-datatypes-misc/tree/2.19/moneta + val moneta = "$group:jackson-datatype-moneta" + + override val modules = listOf( + jdk8, + dateTime, + guava, + protobuf, + javaXMoney, + moneta + ) + } // https://github.com/FasterXML/jackson-jr - object Junior { - const val version = Jackson.version - const val group = "com.fasterxml.jackson.jr" - const val objects = "$group:jackson-jr-objects:$version" + object Junior : Dependency() { + override val version = Jackson.version + override val group = "com.fasterxml.jackson.jr" + + val objects = "$group:jackson-jr-objects" + + override val modules = listOf(objects) } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaDiffUtils.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaDiffUtils.kt index b010d1a..7cc2e59 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaDiffUtils.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaDiffUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaJwt.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaJwt.kt index f5a32b8..4d9b1df 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaJwt.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaJwt.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaPoet.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaPoet.kt index e17a180..b99e503 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaPoet.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaPoet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -30,5 +30,8 @@ package io.spine.dependency.lib @Suppress("unused", "ConstPropertyName") object JavaPoet { private const val version = "1.13.0" - const val lib = "com.squareup:javapoet:$version" + const val group = "com.squareup" + const val artifact = "javapoet" + const val module = "$group:$artifact" + const val lib = "$module:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt index 4db379b..e605a16 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,8 +29,9 @@ package io.spine.dependency.lib @Suppress("unused", "ConstPropertyName") object JavaX { // This artifact, which used to be a part of J2EE, moved under the Eclipse EE4J project. - // https://github.com/eclipse-ee4j/common-annotations-api - const val annotations = "javax.annotation:javax.annotation-api:1.3.2" + // https://github.com/jakartaee/common-annotations-api + const val annotationGroup = "javax.annotation" + const val annotations = "$annotationGroup:javax.annotation-api:1.3.2" const val servletApi = "javax.servlet:javax.servlet-api:3.1.0" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Klaxon.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Klaxon.kt index 3db7e3a..69089c6 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Klaxon.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Klaxon.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt index 5ef694a..48694c5 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -26,16 +26,30 @@ package io.spine.dependency.lib +import io.spine.dependency.Dependency +import io.spine.dependency.DependencyWithBom + // https://github.com/JetBrains/kotlin // https://github.com/Kotlin -@Suppress("unused", "ConstPropertyName") -object Kotlin { +@Suppress("unused") +object Kotlin : DependencyWithBom() { /** - * When changing the version, also change the version used in the `buildSrc/build.gradle.kts`. + * This is the version of Kotlin we use for writing code which does not + * depend on Gradle and the version of embedded Kotlin. */ @Suppress("MemberVisibilityCanBePrivate") // used directly from the outside. - const val version = "1.9.23" + const val runtimeVersion = "2.1.21" + + override val version = runtimeVersion + override val group = "org.jetbrains.kotlin" + override val bom = "$group:kotlin-bom:$runtimeVersion" + + /** + * This is the version of + * [Kotlin embedded into Gradle](https://docs.gradle.org/current/userguide/compatibility.html#kotlin). + */ + const val embeddedVersion = "2.1.21" /** * The version of the JetBrains annotations library, which is a transitive @@ -43,24 +57,64 @@ object Kotlin { * * @see Java Annotations */ - private const val annotationsVersion = "24.0.1" + private const val annotationsVersion = "26.0.2" - private const val group = "org.jetbrains.kotlin" + val scriptRuntime = "$group:kotlin-script-runtime:$runtimeVersion" - const val stdLib = "$group:kotlin-stdlib:$version" - const val stdLibCommon = "$group:kotlin-stdlib-common:$version" + object StdLib : Dependency() { + override val version = runtimeVersion + override val group = Kotlin.group - @Deprecated("Please use `stdLib` instead.") - const val stdLibJdk7 = "$group:kotlin-stdlib-jdk7:$version" + private const val infix = "kotlin-stdlib" + val itself = "$group:$infix" + val common = "$group:$infix-common" + val jdk7 = "$group:$infix-jdk7" + val jdk8 = "$group:$infix-jdk8" - @Deprecated("Please use `stdLib` instead.") - const val stdLibJdk8 = "$group:kotlin-stdlib-jdk8:$version" + override val modules = listOf(itself, common, jdk7, jdk8) + } - const val reflect = "$group:kotlin-reflect:$version" - const val testJUnit5 = "$group:kotlin-test-junit5:$version" + @Deprecated("Please use `StdLib.itself` instead.", ReplaceWith("StdLib.itself")) + val stdLib = StdLib.itself - const val gradlePluginApi = "$group:kotlin-gradle-plugin-api:$version" - const val gradlePluginLib = "$group:kotlin-gradle-plugin:$version" + @Deprecated("Please use `StdLib.common` instead.", ReplaceWith("StdLib.common")) + val stdLibCommon = StdLib.common + + @Deprecated("Please use `StdLib.jdk7` instead.", ReplaceWith("StdLib.jdk7")) + val stdLibJdk7 = StdLib.jdk7 + + @Deprecated("Please use `StdLib.jdk8` instead.") + val stdLibJdk8 = StdLib.jdk8 + + val toolingCore = "$group:kotlin-tooling-core" + val reflect = "$group:kotlin-reflect" + val testJUnit5 = "$group:kotlin-test-junit5" + + /** + * The modules our interest that do not belong to [StdLib]. + */ + override val modules = listOf(reflect, testJUnit5) + + @Deprecated(message = "Please use `GradlePlugin.api` instead.", ReplaceWith("GradlePlugin.api")) + val gradlePluginApi = "$group:kotlin-gradle-plugin-api" + + @Deprecated(message = "Please use `GradlePlugin.lib` instead.", ReplaceWith("GradlePlugin.lib")) + val gradlePluginLib = "$group:kotlin-gradle-plugin" const val jetbrainsAnnotations = "org.jetbrains:annotations:$annotationsVersion" + + object Compiler { + val embeddable = "$group:kotlin-compiler-embeddable:$embeddedVersion" + } + + object GradlePlugin : Dependency() { + override val version = runtimeVersion + override val group = Kotlin.group + + val api = "$group:kotlin-gradle-plugin-api:$version" + val lib = "$group:kotlin-gradle-plugin:$version" + val model = "$group:kotlin-gradle-model:$version" + + override val modules = listOf(api, lib, model) + } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt new file mode 100644 index 0000000..346c5d8 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt @@ -0,0 +1,35 @@ +/* + * 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.dependency.lib + +// https://github.com/square/kotlinpoet +@Suppress("unused", "ConstPropertyName") +object KotlinPoet { + private const val version = "2.0.0" + const val lib = "com.squareup:kotlinpoet:$version" + const val ksp = "com.squareup:kotlinpoet-ksp:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinSemver.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinSemver.kt index ed29f10..92a348d 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinSemver.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinSemver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,6 +29,6 @@ package io.spine.dependency.lib // https://github.com/z4kn4fein/kotlin-semver @Suppress("unused", "ConstPropertyName") object KotlinSemver { - private const val version = "1.4.2" + private const val version = "2.0.0" const val lib = "io.github.z4kn4fein:semver:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinX.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinX.kt index 5b6572c..ee521e3 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinX.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinX.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -27,15 +27,34 @@ package io.spine.dependency.lib @Suppress("unused", "ConstPropertyName") +@Deprecated( + message = "Please use `KotlinX` from `io.spine.dependency.kotlinx` package", + replaceWith = ReplaceWith( + expression = "KotlinX", + imports = ["io.spine.dependency.kotlinx.KotlinX"] + ) +) object KotlinX { const val group = "org.jetbrains.kotlinx" + @Deprecated( + message = "Please use `Coroutines` from the `io.spine.dependency.kotlinx` package", + replaceWith = ReplaceWith( + expression = "Coroutines", + imports = ["io.spine.dependency.kotlinx.Coroutines"] + ) + ) object Coroutines { // https://github.com/Kotlin/kotlinx.coroutines - const val version = "1.7.3" - const val core = "$group:kotlinx-coroutines-core:$version" - const val jdk8 = "$group:kotlinx-coroutines-jdk8:$version" + val version = io.spine.dependency.kotlinx.Coroutines.version + val bom = "$group:kotlinx-coroutines-bom:$version" + val core = "$group:kotlinx-coroutines-core:$version" + val coreJvm = "$group:kotlinx-coroutines-core-jvm:$version" + val jdk8 = "$group:kotlinx-coroutines-jdk8:$version" + val debug = "$group:kotlinx-coroutines-debug:$version" + val test = "$group:kotlinx-coroutines-test:$version" + val testJvm = "$group:kotlinx-coroutines-test-jvm:$version" } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt index 66c45f5..d4f2bd2 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt index f88b098..c317bb3 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt index 522e655..3107d76 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Plexus.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Plexus.kt index 40d4149..e393906 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Plexus.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Plexus.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt index 6794c1a..203059e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -32,8 +32,8 @@ package io.spine.dependency.lib "ConstPropertyName" /* https://bit.ly/kotlin-prop-names */ ) object Protobuf { - private const val group = "com.google.protobuf" - const val version = "3.25.1" + const val group = "com.google.protobuf" + const val version = "4.31.0" /** * The Java library with Protobuf data types. @@ -60,11 +60,11 @@ object Protobuf { object GradlePlugin { /** * The version of this plugin is already specified in `buildSrc/build.gradle.kts` file. - * Thus, when applying the plugin to projects build files, only the [id] should be used. + * Thus, when applying the plugin to project build files, only the [id] should be used. * * When changing the version, also change the version used in the `build.gradle.kts`. */ - const val version = "0.9.4" + const val version = "0.9.5" const val id = "com.google.protobuf" const val lib = "$group:protobuf-gradle-plugin:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Roaster.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Roaster.kt index 2bf9b5c..601be9a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Roaster.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Roaster.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -37,8 +37,9 @@ object Roaster { * [2.29.0.Final](https://github.com/forge/roaster/releases/tag/2.29.0.Final), * Roaster requires Java 17. */ - private const val version = "2.28.0.Final" + private const val version = "2.29.0.Final" - const val api = "org.jboss.forge.roaster:roaster-api:$version" - const val jdt = "org.jboss.forge.roaster:roaster-jdt:$version" + const val group = "org.jboss.forge.roaster" + const val api = "$group:roaster-api:$version" + const val jdt = "$group:roaster-jdt:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt index ecfa86e..bd5b9df 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ArtifactVersion.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ArtifactVersion.kt index f3dc904..77497b2 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ArtifactVersion.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ArtifactVersion.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -37,15 +37,23 @@ object ArtifactVersion { * * @see spine-base */ - const val base = "2.0.0-SNAPSHOT.215" - const val baseForBuildScript = "2.0.0-SNAPSHOT.215" + @Deprecated(message = "Please use `Base.version`.", ReplaceWith("Base.version")) + const val base = Base.version + + @Suppress("unused") + @Deprecated( + message = "Please use `Base.versionForBuildScript`.", + ReplaceWith("Base.versionForBuildScript") + ) + const val baseForBuildScript = Base.versionForBuildScript /** * The version of [Spine.reflect]. * * @see spine-reflect */ - const val reflect = "2.0.0-SNAPSHOT.190" + @Deprecated(message = "Please use `Reflect.version`.", ReplaceWith("Reflect.version")) + const val reflect = Reflect.version /** * The version of [Logging]. @@ -58,7 +66,8 @@ object ArtifactVersion { * * @see spine-testlib */ - const val testlib = "2.0.0-SNAPSHOT.184" + @Deprecated(message = "Please use `TestLib.version`.", ReplaceWith("TestLib.version")) + const val testlib = TestLib.version /** * The version of `core-java`. @@ -71,41 +80,52 @@ object ArtifactVersion { * * @see spine-model-compiler */ - const val mc = "2.0.0-SNAPSHOT.133" + @Suppress("unused") + @Deprecated( + message = "Please use `ModelCompiler.version` instead.", + ReplaceWith("ModelCompiler.version") + ) + const val mc = ModelCompiler.version /** * The version of [Spine.baseTypes]. * * @see spine-base-types */ - const val baseTypes = "2.0.0-SNAPSHOT.126" + @Deprecated(message = "Please use `BaseTypes.version`.", ReplaceWith("BaseTypes.version")) + const val baseTypes = BaseTypes.version /** * The version of [Spine.time]. * * @see spine-time */ - const val time = "2.0.0-SNAPSHOT.135" + @Deprecated(message = "Please use `Time.version`.", ReplaceWith("Time.version")) + const val time = Time.version /** * The version of [Spine.change]. * * @see spine-change */ - const val change = "2.0.0-SNAPSHOT.118" + @Deprecated(message = "Please use `Change.version`.", ReplaceWith("Change.version")) + const val change = Change.version /** * The version of [Spine.text]. * * @see spine-text */ - const val text = "2.0.0-SNAPSHOT.6" + @Deprecated(message = "Please use `Text.version`.", ReplaceWith("Text.version")) + const val text = Text.version /** * The version of [Spine.toolBase]. * * @see spine-tool-base */ + @Suppress("unused") + @Deprecated(message = "Please use `ToolBase.version`.", ReplaceWith("ToolBase.version")) const val toolBase = ToolBase.version /** diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt new file mode 100644 index 0000000..448a11c --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt @@ -0,0 +1,43 @@ +/* + * 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.dependency.local + +/** + * Spine Base module. + * + * @see spine-base + */ +@Suppress("ConstPropertyName") +object Base { + const val version = "2.0.0-SNAPSHOT.322" + const val versionForBuildScript = "2.0.0-SNAPSHOT.317" + const val group = Spine.group + const val artifact = "spine-base" + const val lib = "$group:$artifact:$version" + const val format = "$group:spine-format:$version" + const val libForBuildScript = "$group:$artifact:$versionForBuildScript" +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt similarity index 74% rename from buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt rename to buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt index 9380e7d..b0b534d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/TaskContainerExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,17 +24,17 @@ * 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 +package io.spine.dependency.local /** - * 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. + * Spine Base module. + * + * @see spine-base-types */ -fun TaskContainer.javadocTask() = this.getByName("javadoc") as Javadoc +@Suppress("ConstPropertyName") +object BaseTypes { + const val version = "2.0.0-SNAPSHOT.210" + const val group = Spine.group + const val artifact = "spine-base-types" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt new file mode 100644 index 0000000..2436580 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.local + +/** + * Spine Reflect library. + * + * @see spine-change + */ +@Suppress("ConstPropertyName") +object Change { + const val version = "2.0.0-SNAPSHOT.200" + const val group = Spine.group + const val artifact = "spine-change" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJava.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJava.kt index 0b3682c..27506d4 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJava.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJava.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -34,9 +34,15 @@ package io.spine.dependency.local @Suppress("ConstPropertyName", "unused") object CoreJava { const val group = Spine.group - const val version = "2.0.0-SNAPSHOT.177" - const val core = "$group:spine-core:$version" - const val client = "$group:spine-client:$version" - const val server = "$group:spine-server:$version" + const val version = "2.0.0-SNAPSHOT.316" + + const val coreArtifact = "spine-core" + const val clientArtifact = "spine-client" + const val serverArtifact = "spine-server" + + const val core = "$group:$coreArtifact:$version" + const val client = "$group:$clientArtifact:$version" + const val server = "$group:$serverArtifact:$version" + const val testUtilServer = "${Spine.toolsGroup}:spine-testutil-server:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt index cb600b9..b1a00ab 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -33,9 +33,12 @@ package io.spine.dependency.local */ @Suppress("ConstPropertyName", "unused") object Logging { - const val version = "2.0.0-SNAPSHOT.240" + const val version = "2.0.0-SNAPSHOT.242" const val group = Spine.group - const val lib = "$group:spine-logging:$version" + + const val loggingArtifact = "spine-logging" + + const val lib = "$group:$loggingArtifact:$version" const val libJvm = "$group:spine-logging-jvm:$version" const val log4j2Backend = "$group:spine-logging-log4j2-backend:$version" @@ -43,10 +46,12 @@ object Logging { const val grpcContext = "$group:spine-logging-grpc-context:$version" const val smokeTest = "$group:spine-logging-smoke-test:$version" + const val testLib = "${Spine.toolsGroup}:spine-logging-testlib:$version" + // Transitive dependencies. // Make `public` and use them to force a version in a particular repository, if needed. internal const val julBackend = "$group:spine-logging-jul-backend:$version" - internal const val middleware = "$group:spine-logging-middleware:$version" + const val middleware = "$group:spine-logging-middleware:$version" internal const val platformGenerator = "$group:spine-logging-platform-generator:$version" internal const val jvmDefaultPlatform = "$group:spine-logging-jvm-default-platform:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt index a2f8a59..6ab0a95 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -42,12 +42,12 @@ object McJava { /** * The version used to in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.244" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.320" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.244" + const val version = "2.0.0-SNAPSHOT.320" /** * The ID of the Gradle plugin. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt new file mode 100644 index 0000000..5f9e541 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.local + +/** + * Spine Model Compiler Gradle API. + * + * @see spine-model-compiler + */ +@Suppress("ConstPropertyName") +object ModelCompiler { + const val version = "2.0.0-SNAPSHOT.133" + const val group = Spine.toolsGroup + const val artifact = "spine-model-compiler" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoData.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoData.kt index c01bd27..ae7f535 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoData.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -55,7 +55,6 @@ package io.spine.dependency.local "unused" /* Some subprojects do not use ProtoData directly. */, "ConstPropertyName" /* We use custom convention for artifact properties. */, "MemberVisibilityCanBePrivate" /* The properties are used directly by other subprojects. */, - "KDocUnresolvedReference" /* Referencing private properties in constructor KDoc. */ ) object ProtoData { const val pluginGroup = Spine.group @@ -73,7 +72,7 @@ object ProtoData { * The version of ProtoData dependencies. */ val version: String - private const val fallbackVersion = "0.64.0" + private const val fallbackVersion = "0.96.4" /** * The distinct version of ProtoData used by other build tools. @@ -82,7 +81,7 @@ object ProtoData { * transitional dependencies, this is the version used to build the project itself. */ val dogfoodingVersion: String - private const val fallbackDfVersion = "0.61.8" + private const val fallbackDfVersion = "0.96.4" /** * The artifact for the ProtoData Gradle plugin. @@ -106,6 +105,9 @@ object ProtoData { val backend get() = "$group:protodata-backend:$version" + val params + get() = "$group:protodata-params:$version" + val protocPlugin get() = "$group:protodata-protoc:$version" @@ -115,8 +117,10 @@ object ProtoData { val cliApi get() = "$group:protodata-cli-api:$version" + val javaModule = "$group:protodata-java" + fun java(version: String): String = - "$group:protodata-java:$version" + "$javaModule:$version" val java get() = java(version) diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt index 0795460..6667d1a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -38,7 +38,7 @@ package io.spine.dependency.local ) object ProtoTap { const val group = "io.spine.tools" - const val version = "0.8.7" + const val version = "0.10.0" const val gradlePluginId = "io.spine.prototap" const val api = "$group:prototap-api:$version" const val gradlePlugin = "$group:prototap-gradle-plugin:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt new file mode 100644 index 0000000..fbf37f1 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.local + +/** + * Spine Reflect library. + * + * @see spine-reflect + */ +@Suppress("ConstPropertyName") +object Reflect { + const val version = "2.0.0-SNAPSHOT.191" + const val group = Spine.group + const val artifact = "spine-reflect" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Spine.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Spine.kt index fb69374..d7b6d0c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Spine.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Spine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -35,30 +35,65 @@ object Spine { const val group = "io.spine" const val toolsGroup = "io.spine.tools" - const val base = "$group:spine-base:${ArtifactVersion.base}" - const val baseForBuildScript = "$group:spine-base:${ArtifactVersion.baseForBuildScript}" + @Deprecated(message = "Please use `Base.lib`.", ReplaceWith("Base.lib")) + const val base = Base.lib - const val reflect = "$group:spine-reflect:${ArtifactVersion.reflect}" - const val baseTypes = "$group:spine-base-types:${ArtifactVersion.baseTypes}" - const val time = "$group:spine-time:${ArtifactVersion.time}" - const val change = "$group:spine-change:${ArtifactVersion.change}" - const val text = "$group:spine-text:${ArtifactVersion.text}" + @Deprecated( + message = "Please use `Base.libForBuildScript`.", + ReplaceWith("Base.libForBuildScript") + ) + const val baseForBuildScript = Base.libForBuildScript - const val testlib = "$toolsGroup:spine-testlib:${ArtifactVersion.testlib}" - const val testUtilTime = "$toolsGroup:spine-testutil-time:${ArtifactVersion.time}" + @Deprecated(message = "Please use `Reflect.lib`.", ReplaceWith("Reflect.lib")) + const val reflect = Reflect.lib + + @Deprecated(message = "Please use `BaseTypes.lib`.", ReplaceWith("BaseTypes.lib")) + const val baseTypes = BaseTypes.lib + + @Deprecated(message = "Please use `Time.lib`.", ReplaceWith("Time.lib")) + const val time = Time.lib + + @Deprecated(message = "Please use `Change.lib`.", ReplaceWith("Change.lib")) + const val change = Change.lib + + @Deprecated(message = "Please use `Text.lib`.", ReplaceWith("Text.lib")) + const val text = Text.lib + + @Deprecated(message = "Please use `TestLib.lib`.", ReplaceWith("TestLib.lib")) + const val testlib = TestLib.lib + + @Deprecated(message = "Please use `Time.testLib`.", ReplaceWith("Time.testLib")) + const val testUtilTime = Time.testLib @Deprecated(message = "Please use `ToolBase.psiJava` instead`.") - const val psiJava = "$toolsGroup:spine-psi-java:${ArtifactVersion.toolBase}" - @Deprecated(message = "Please use `ToolBase.psiJava` instead`.") - const val psiJavaBundle = "$toolsGroup:spine-psi-java-bundle:${ArtifactVersion.toolBase}" - @Deprecated(message = "Please use `ToolBase.lib` instead`.") - const val toolBase = "$toolsGroup:spine-tool-base:${ArtifactVersion.toolBase}" - @Deprecated(message = "Please use `ToolBase.pluginBase` instead`.") - const val pluginBase = "$toolsGroup:spine-plugin-base:${ArtifactVersion.toolBase}" - @Deprecated(message = "Please use `ToolBase.pluginTestlib` instead`.") - const val pluginTestlib = "$toolsGroup:spine-plugin-testlib:${ArtifactVersion.toolBase}" - - const val modelCompiler = "$toolsGroup:spine-model-compiler:${ArtifactVersion.mc}" + const val psiJava = "$toolsGroup:spine-psi-java:${ToolBase.version}" + + @Deprecated( + message = "Please use `ToolBase.psiJava` instead`.", + ReplaceWith("ToolBase.psiJava") + ) + const val psiJavaBundle = "$toolsGroup:spine-psi-java-bundle:${ToolBase.version}" + + @Deprecated(message = "Please use `ToolBase.lib` instead`.", ReplaceWith("ToolBase.lib")) + const val toolBase = "$toolsGroup:spine-tool-base:${ToolBase.version}" + + @Deprecated( + message = "Please use `ToolBase.pluginBase` instead`.", + ReplaceWith("ToolBase.pluginBase") + ) + const val pluginBase = "$toolsGroup:spine-plugin-base:${ToolBase.version}" + + @Deprecated( + message = "Please use `ToolBase.pluginTestlib` instead`.", + ReplaceWith("ToolBase.pluginTestlib") + ) + const val pluginTestlib = ToolBase.pluginTestlib + + @Deprecated( + message = "Please use `ModelCompiler.lib` instead.", + ReplaceWith("ModelCompiler.lib") + ) + const val modelCompiler = ModelCompiler.lib @Deprecated( message = "Please use top level `McJava` object instead.", diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt new file mode 100644 index 0000000..1c7a444 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.local + +/** + * Spine TestLib library. + * + * @see spine-testlib + */ +@Suppress("ConstPropertyName") +object TestLib { + const val version = "2.0.0-SNAPSHOT.202" + const val group = Spine.toolsGroup + const val artifact = "spine-testlib" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Text.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Text.kt new file mode 100644 index 0000000..216ec13 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Text.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.local + +/** + * Spine Reflect library. + * + * @see spine-text + */ +@Suppress("ConstPropertyName") +object Text { + const val version = "2.0.0-SNAPSHOT.6" + const val group = Spine.group + const val artifact = "spine-text" + const val lib = "$group:$artifact:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt new file mode 100644 index 0000000..289adee --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt @@ -0,0 +1,42 @@ +/* + * 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.dependency.local + +/** + * Spine Time library. + * + * @see spine-time + */ +@Suppress("ConstPropertyName") +object Time { + const val version = "2.0.0-SNAPSHOT.203" + const val group = Spine.group + const val artifact = "spine-time" + const val lib = "$group:$artifact:$version" + + const val testLib = "${Spine.toolsGroup}:spine-time-testlib:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt index d19a1aa..7d001cf 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,18 +29,27 @@ package io.spine.dependency.local /** * Artifacts of the `tool-base` module. * - * @see spine-tool-base + * @see tool-base */ @Suppress("ConstPropertyName", "unused") object ToolBase { const val group = Spine.toolsGroup - const val version = "2.0.0-SNAPSHOT.233" + const val version = "2.0.0-SNAPSHOT.341" - const val lib = "$group:spine-tool-base:$version" - const val pluginBase = "$group:spine-plugin-base:$version" - const val pluginTestlib = "$group:spine-plugin-testlib:$version" + const val lib = "$group:tool-base:$version" + const val pluginBase = "$group:plugin-base:$version" + const val pluginTestlib = "$group:plugin-testlib:$version" + const val intellijPlatform = "$group:intellij-platform:$version" const val intellijPlatformJava = "$group:intellij-platform-java:$version" - const val psiJava = "$group:spine-psi-java:$version" + const val psi = "$group:psi:$version" + const val psiJava = "$group:psi-java:$version" + + const val gradleRootPlugin = "$group:gradle-root-plugin:$version" + const val gradlePluginApi = "$group:gradle-plugin-api:$version" + const val gradlePluginApiTestFixtures = "$group:gradle-plugin-api-test-fixtures:$version" + + const val jvmTools = "$group:jvm-tools:$version" + const val jvmToolPlugins = "$group:jvm-tool-all-plugins:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt index 7fdcfa4..7326ced 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -31,21 +31,24 @@ package io.spine.dependency.local * * See [`SpineEventEngine/validation`](https://github.com/SpineEventEngine/validation/). */ -@Suppress("unused", "ConstPropertyName") +@Suppress("ConstPropertyName", "unused") object Validation { /** * The version of the Validation library artifacts. */ - const val version = "2.0.0-SNAPSHOT.160" + const val version = "2.0.0-SNAPSHOT.340" const val group = "io.spine.validation" private const val prefix = "spine-validation" - const val runtime = "$group:$prefix-java-runtime:$version" + const val runtimeModule = "$group:$prefix-java-runtime" + const val runtime = "$runtimeModule:$version" const val java = "$group:$prefix-java:$version" + const val javaBundleModule = "$group:$prefix-java-bundle" + /** Obtains the artifact for the `java-bundle` artifact of the given version. */ - fun javaBundle(version: String) = "$group:$prefix-java-bundle:$version" + fun javaBundle(version: String) = "$javaBundleModule:$version" val javaBundle = javaBundle(version) diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/AssertK.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/AssertK.kt index 7e15838..760c116 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/AssertK.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/AssertK.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -34,6 +34,6 @@ package io.spine.dependency.test @Deprecated("Please use Kotest assertions instead.") @Suppress("unused", "ConstPropertyName") object AssertK { - private const val version = "0.26.1" + private const val version = "0.28.1" const val libJvm = "com.willowtreeapps.assertk:assertk-jvm:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Hamcrest.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Hamcrest.kt index 8dde22c..e540c2c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Hamcrest.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Hamcrest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -35,6 +35,6 @@ package io.spine.dependency.test @Suppress("unused", "ConstPropertyName") object Hamcrest { // https://github.com/hamcrest/JavaHamcrest/releases - private const val version = "2.2" + private const val version = "3.0" const val core = "org.hamcrest:hamcrest-core:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt index 2ef5b6a..b07849f 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -26,39 +26,94 @@ package io.spine.dependency.test +import io.spine.dependency.Dependency +import io.spine.dependency.DependencyWithBom + // https://junit.org/junit5/ @Suppress("unused", "ConstPropertyName") -object JUnit { - const val version = "5.10.0" +object JUnit : DependencyWithBom() { + + override val version = "5.13.2" + override val group: String = "org.junit" + + /** + * The BOM of JUnit. + * + * This one should be forced in a project via: + * + * ```kotlin + * dependencies { + * testImplementation(enforcedPlatform(JUnit.bom)) + * } + * ``` + * The version of JUnit is forced automatically by + * the [BomsPlugin][io.spine.dependency.boms.BomsPlugin] + * when it is applied to the project. + */ + override val bom = "$group:junit-bom:$version" + private const val legacyVersion = "4.13.1" // https://github.com/apiguardian-team/apiguardian private const val apiGuardianVersion = "1.1.2" // https://github.com/junit-pioneer/junit-pioneer - private const val pioneerVersion = "2.0.1" + private const val pioneerVersion = "2.3.0" + const val pioneer = "org.junit-pioneer:junit-pioneer:$pioneerVersion" const val legacy = "junit:junit:$legacyVersion" + @Deprecated("Use JUnit.Jupiter.api instead", ReplaceWith("JUnit.Jupiter.api")) val api = listOf( "org.apiguardian:apiguardian-api:$apiGuardianVersion", "org.junit.jupiter:junit-jupiter-api:$version", "org.junit.jupiter:junit-jupiter-params:$version" ) - const val bom = "org.junit:junit-bom:$version" - const val runner = "org.junit.jupiter:junit-jupiter-engine:$version" - const val params = "org.junit.jupiter:junit-jupiter-params:$version" + @Deprecated("Use JUnit.Jupiter.engine instead", ReplaceWith("JUnit.Jupiter.engine")) + val runner = "org.junit.jupiter:junit-jupiter-engine:$version" - const val pioneer = "org.junit-pioneer:junit-pioneer:$pioneerVersion" + @Deprecated("Use JUnit.Jupiter.params instead", ReplaceWith("JUnit.Jupiter.params")) + val params = "org.junit.jupiter:junit-jupiter-params:$version" + + object Jupiter : Dependency() { + override val version = JUnit.version + override val group = "org.junit.jupiter" + private const val infix = "junit-jupiter" + + // We do not use versions because they are forced via BOM. + val api = "$group:$infix-api" + val params = "$group:$infix-params" + val engine = "$group:$infix-engine" + + @Deprecated("Please use `[Jupiter.run { artifacts[api] }` instead.") + val apiArtifact = "$api:$version" + + override val modules = listOf(api, params, engine) + } + + /** + * The same as [Jupiter.artifacts]. + */ + override val modules = Jupiter.modules + + object Platform : Dependency() { + + /** + * The version of the platform is defined by JUnit BOM. + * + * So when we use JUnit as a platform, this property should be picked up + * for the dependencies automatically. + */ + override val version: String = "1.13.2" + override val group = "org.junit.platform" + + private const val infix = "junit-platform" + val commons = "$group:$infix-commons" + val launcher = "$group:$infix-launcher" + val engine = "$group:$infix-engine" + val suiteApi = "$group:$infix-suite-api" - object Platform { - // https://junit.org/junit5/ - const val version = "1.10.0" - internal const val group = "org.junit.platform" - const val commons = "$group:junit-platform-commons:$version" - const val launcher = "$group:junit-platform-launcher:$version" - const val engine = "$group:junit-platform-engine:$version" - const val suiteApi = "$group:junit-platform-suite-api:$version" + override val modules = listOf(commons, launcher, engine, suiteApi) } } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Jacoco.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Jacoco.kt index 9b807d5..5f007ec 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Jacoco.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Jacoco.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -33,5 +33,5 @@ package io.spine.dependency.test */ @Suppress("ConstPropertyName") object Jacoco { - const val version = "0.8.12" + const val version = "0.8.13" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt index 60a0390..8de10ff 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -35,7 +35,7 @@ package io.spine.dependency.test */ @Suppress("unused", "ConstPropertyName") object Kotest { - const val version = "5.8.0" + const val version = "5.9.1" const val group = "io.kotest" const val assertions = "$group:kotest-assertions-core:$version" const val runnerJUnit5 = "$group:kotest-runner-junit5:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt new file mode 100644 index 0000000..bde365a --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.test + +/** + * A library for in-process compilation of Kotlin and Java code compilation. + * + * @see GitHub repo + */ +@Suppress("unused", "ConstPropertyName") +object KotlinCompileTesting { + private const val version = "0.7.1" + private const val group = "dev.zacsweers.kctfork" + const val libCore = "$group:core:$version" + const val libKsp = "$group:ksp:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt index 744ee77..61897cc 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,7 +29,7 @@ package io.spine.dependency.test // https://github.com/Kotlin/kotlinx-kover @Suppress("unused", "ConstPropertyName") object Kover { - const val version = "0.7.6" + const val version = "0.9.1" const val id = "org.jetbrains.kotlinx.kover" const val classpath = "org.jetbrains.kotlinx:kover-gradle-plugin:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/OpenTest4J.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/OpenTest4J.kt index e0f01d8..1e22fb3 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/OpenTest4J.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/OpenTest4J.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/SystemLambda.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/SystemLambda.kt index f454a0b..5f0eef8 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/SystemLambda.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/SystemLambda.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt index 486c625..bfec211 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Truth.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Truth.kt index 2f5ca06..cbfb4ad 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Truth.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Truth.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,7 +29,7 @@ package io.spine.dependency.test // https://github.com/google/truth @Suppress("unused", "ConstPropertyName") object Truth { - private const val version = "1.1.5" + private const val version = "1.4.4" val libs = listOf( "com.google.truth:truth:$version", "com.google.truth.extensions:truth-java8-extension:$version", diff --git a/buildSrc/src/main/kotlin/io/spine/docs/MarkdownDocument.kt b/buildSrc/src/main/kotlin/io/spine/docs/MarkdownDocument.kt index e5db94a..23937bd 100644 --- a/buildSrc/src/main/kotlin/io/spine/docs/MarkdownDocument.kt +++ b/buildSrc/src/main/kotlin/io/spine/docs/MarkdownDocument.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/Build.kt b/buildSrc/src/main/kotlin/io/spine/gradle/Build.kt index 82adf54..327031e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/Build.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/Build.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/Clean.kt b/buildSrc/src/main/kotlin/io/spine/gradle/Clean.kt index 53d42ee..380e835 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/Clean.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/Clean.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt b/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt index b97c6eb..3de99c4 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt index e3ce8d3..deda203 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -40,6 +40,15 @@ import org.gradle.kotlin.dsl.getByType * This file contains extension methods and properties for the Gradle `Project`. */ +/** + * Logs the result of the function using the project logger at `INFO` level. + */ +fun Project.log(message: () -> String) { + if (logger.isInfoEnabled) { + logger.info(message.invoke()) + } +} + /** * Obtains the Java plugin extension of the project. */ @@ -68,7 +77,7 @@ fun Project.applyPlugin(cls: Class>) { * the generic parameter `T`. */ @Suppress("UNCHECKED_CAST") /* See the method docs. */ -fun Project.findTask(name: String): T { +fun Project.getTask(name: String): T { val task = this.tasks.findByName(name) ?: error("Unable to find a task named `$name` in the project `${this.name}`.") return task as T diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/gradle/Repositories.kt deleted file mode 100644 index 585f782..0000000 --- a/buildSrc/src/main/kotlin/io/spine/gradle/Repositories.kt +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2024, 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 - -import io.spine.gradle.publish.CloudRepo -import io.spine.gradle.publish.PublishingRepos -import io.spine.gradle.publish.PublishingRepos.gitHub -import java.io.File -import java.net.URI -import java.util.* -import org.gradle.api.Project -import org.gradle.api.artifacts.dsl.RepositoryHandler -import org.gradle.api.artifacts.repositories.MavenArtifactRepository -import org.gradle.kotlin.dsl.ScriptHandlerScope -import org.gradle.kotlin.dsl.maven - -/** - * Applies [standard][doApplyStandard] repositories to this [ScriptHandlerScope] - * optionally adding [gitHub] repositories for Spine-only components, if - * names of such repositories are given. - * - * @param buildscript - * a [ScriptHandlerScope] to work with. Pass `this` under `buildscript { }`. - * @param rootProject - * a root project where the `buildscript` is declared. - * @param gitHubRepo - * a list of short repository names, or empty list if only - * [standard repositories][doApplyStandard] are required. - */ -@Suppress("unused") -@Deprecated( - message = "Please use `standardSpineSdkRepositories()`.", - replaceWith = ReplaceWith("standardSpineSdkRepositories()") -) -fun applyWithStandard( - buildscript: ScriptHandlerScope, - rootProject: Project, - vararg gitHubRepo: String -) { - val repositories = buildscript.repositories - gitHubRepo.iterator().forEachRemaining { repo -> - repositories.applyGitHubPackages(repo, rootProject) - } - repositories.standardToSpineSdk() -} - -/** - * Registers the selected GitHub Packages repos as Maven repositories. - * - * To be used in `buildscript` clauses when a fully-qualified call must be made. - * - * @param repositories - * the handler to accept registration of the GitHub Packages repository - * @param shortRepositoryName - * the short name of the GitHub repository (e.g. "core-java") - * @param project - * the project which is going to consume artifacts from the repository - * @see applyGitHubPackages - */ -@Suppress("unused") -@Deprecated( - message = "Please use `standardSpineSdkRepositories()`.", - replaceWith = ReplaceWith("standardSpineSdkRepositories()") -) -fun doApplyGitHubPackages( - repositories: RepositoryHandler, - shortRepositoryName: String, - project: Project -) = repositories.applyGitHubPackages(shortRepositoryName, project) - -/** - * 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() - -/** - * Applies the repository hosted at GitHub Packages, to which Spine artifacts were published. - * - * This method should be used by those wishing to have Spine artifacts published - * to GitHub Packages as dependencies. - * - * @param shortRepositoryName - * short names of the GitHub repository (e.g. "base", "core-java", "model-tools") - * @param project - * the project which is going to consume artifacts from repositories - */ -fun RepositoryHandler.applyGitHubPackages(shortRepositoryName: String, project: Project) { - val repository = gitHub(shortRepositoryName) - val credentials = repository.credentials(project) - - credentials?.let { - spineMavenRepo(it, repository.releases) - spineMavenRepo(it, repository.snapshots) - } -} - -/** - * Applies the repositories hosted at GitHub Packages, to which Spine artifacts were published. - * - * This method should be used by those wishing to have Spine artifacts published - * to GitHub Packages as dependencies. - * - * @param shortRepositoryName - * the short name of the GitHub repository (e.g. "core-java") - * @param project - * the project which is going to consume or publish artifacts from - * the registered repository - */ -fun RepositoryHandler.applyGitHubPackages(project: Project, vararg shortRepositoryName: String) { - for (name in shortRepositoryName) { - applyGitHubPackages(name, project) - } -} - -/** - * Applies [standard][applyStandard] repositories to this [RepositoryHandler] - * optionally adding [applyGitHubPackages] repositories for Spine-only components, if - * names of such repositories are given. - * - * @param project - * a project to which we add dependencies - * @param gitHubRepo - * a list of short repository names, or empty list if only - * [standard repositories][applyStandard] are required. - */ -@Suppress("unused") -@Deprecated( - message = "Please use `standardToSpineSdk()`.", - replaceWith = ReplaceWith("standardToSpineSdk()") -) -fun RepositoryHandler.applyStandardWithGitHub(project: Project, vararg gitHubRepo: String) { - gitHubRepo.iterator().forEachRemaining { repo -> - applyGitHubPackages(repo, project) - } - 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 - */ -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() - - 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() - -/** - * A Maven repository. - */ -data class Repository( - val releases: String, - val snapshots: String, - private val credentialsFile: String? = null, - private val credentialValues: ((Project) -> Credentials?)? = null, - val name: String = "Maven repository `$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 toString(): String { - return name - } -} - -/** - * Password credentials for a Maven repository. - */ -data class Credentials( - val username: String?, - val password: String? -) - -/** - * Defines names of additional repositories commonly used in the Spine SDK projects. - * - * @see [applyStandard] - */ -private object Repos { - val spine = CloudRepo.published.releases - val spineSnapshots = CloudRepo.published.snapshots - val artifactRegistry = PublishingRepos.cloudArtifactRegistry.releases - val artifactRegistrySnapshots = PublishingRepos.cloudArtifactRegistry.snapshots - - @Suppress("unused") - @Deprecated( - message = "Sonatype release repository redirects to the Maven Central", - replaceWith = ReplaceWith("sonatypeSnapshots"), - level = DeprecationLevel.ERROR - ) - const val sonatypeReleases = "https://oss.sonatype.org/content/repositories/snapshots" - const val sonatypeSnapshots = "https://oss.sonatype.org/content/repositories/snapshots" -} - -/** - * Registers the Maven repository with the passed [repoCredentials] for authorization. - * - * Only includes the Spine-related artifact groups. - */ -private fun RepositoryHandler.spineMavenRepo( - repoCredentials: Credentials, - repoUrl: String -) { - maven { - url = URI(repoUrl) - includeSpineOnly() - credentials { - username = repoCredentials.username - password = repoCredentials.password - } - } -} - -/** - * 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/RunBuild.kt b/buildSrc/src/main/kotlin/io/spine/gradle/RunBuild.kt index c187e8f..aa2759d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/RunBuild.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/RunBuild.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -32,6 +32,6 @@ package io.spine.gradle open class RunBuild : RunGradle() { init { - task("build") + task("clean", "build") } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/RunGradle.kt b/buildSrc/src/main/kotlin/io/spine/gradle/RunGradle.kt index e3084ca..8942630 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/RunGradle.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/RunGradle.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -100,7 +100,7 @@ open class RunGradle : DefaultTask() { } @TaskAction - private fun execute() { + public fun execute() { // Ensure build error output log. // Since we're executing this task in another process, we redirect error output to // the file under the `_out` directory. Using the `build` directory for this purpose diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/Runtime.kt b/buildSrc/src/main/kotlin/io/spine/gradle/Runtime.kt index facaf78..fb4efaf 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/Runtime.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/Runtime.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,6 +24,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +@file:Suppress("unused") + package io.spine.gradle import java.io.File @@ -48,13 +50,14 @@ class Cli(private val workingFolder: File) { /** * Executes the given terminal command and retrieves the command output. * - *

{@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/StringExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/StringExtensions.kt index 0863dd3..a185892 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/StringExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/StringExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt b/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt index 05ab252..7c7abd1 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/VersionWriter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/VersionWriter.kt index b24c202..d3d4323 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/VersionWriter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/VersionWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/base/Tasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/base/Tasks.kt index f60b7c5..c77b795 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/base/Tasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/base/Tasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 4274418..122a604 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -28,7 +28,6 @@ package io.spine.gradle.checkstyle import io.spine.dependency.build.CheckStyle import org.gradle.api.Project -import org.gradle.api.plugins.quality.Checkstyle import org.gradle.api.plugins.quality.CheckstyleExtension import org.gradle.api.plugins.quality.CheckstylePlugin import org.gradle.kotlin.dsl.the @@ -65,9 +64,13 @@ object CheckStyleConfig { } project.afterEvaluate { - // Disables checking the test sources. - val checkstyleTest = project.tasks.findByName("checkstyleTest") as Checkstyle - checkstyleTest.enabled = false + // Disables checking the test sources and test fixtures. + arrayOf( + "checkstyleTest", + "checkstyleTestFixtures" + ).forEach { + task -> tasks.findByName(task)?.enabled = false + } } } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartContext.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartContext.kt index 6c192e5..c32c10f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartContext.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartContext.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartEnvironment.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartEnvironment.kt index 48883e1..4e4ae83 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartEnvironment.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartEnvironment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartExtension.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartExtension.kt index e0913b5..57a2bcf 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartExtension.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/DartExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/DartPlugins.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/DartPlugins.kt index 3d88bec..9d76a77 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/DartPlugins.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/DartPlugins.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/Protobuf.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/Protobuf.kt index 54cd9b0..bfd503f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/Protobuf.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/plugin/Protobuf.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Build.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Build.kt index 97cdf51..163747e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Build.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Build.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/DartTasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/DartTasks.kt index 656177b..bc5e1e9 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/DartTasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/DartTasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/IntegrationTest.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/IntegrationTest.kt index 65ce4bb..19f1f14 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/IntegrationTest.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/IntegrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Publish.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Publish.kt index d52799f..2f8df6b 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Publish.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dart/task/Publish.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dokka/DokkaExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dokka/DokkaExtensions.kt index 52ec7f3..727a079 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dokka/DokkaExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dokka/DokkaExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/dokka/TaskContainerExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/dokka/TaskContainerExtensions.kt index 39d9dfb..02deead 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/dokka/TaskContainerExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/dokka/TaskContainerExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/fs/LazyTempPath.kt b/buildSrc/src/main/kotlin/io/spine/gradle/fs/LazyTempPath.kt index 3b33318..f6e1777 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/fs/LazyTempPath.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/fs/LazyTempPath.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/git/Branch.kt b/buildSrc/src/main/kotlin/io/spine/gradle/git/Branch.kt index 1883677..a10a65f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/git/Branch.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/git/Branch.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt b/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt index 41a12b7..55ce67f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/git/UserInfo.kt b/buildSrc/src/main/kotlin/io/spine/gradle/git/UserInfo.kt index 43d8ab1..bc9f08d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/git/UserInfo.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/git/UserInfo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt index 0a11eb1..f0d839f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 19021ae..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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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/github/pages/SshKey.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/SshKey.kt index 329e811..186c474 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/SshKey.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/SshKey.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt index 77d4bc9..f5e3bfc 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt index cfcec70..0f9a0f5 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt index bd1a1b2..e46a565 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt index 6ead318..90eebc2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/java/Tasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/java/Tasks.kt index e08e64a..12d4d56 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/java/Tasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/java/Tasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javac/ErrorProne.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javac/ErrorProne.kt index 511791c..84c6bcb 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javac/ErrorProne.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javac/ErrorProne.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -51,6 +51,7 @@ import org.gradle.process.CommandLineArgumentProvider */ @Suppress("unused") fun JavaCompile.configureErrorProne() { + options.compilerArgs.add("--should-stop=ifError=FLOW") options.errorprone .errorproneArgumentProviders .add(ErrorProneConfig.ARGUMENTS) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javac/Javac.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javac/Javac.kt index 933f5da..44223cb 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javac/Javac.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javac/Javac.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/Encoding.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/Encoding.kt index 8ae089b..052a1aa 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/Encoding.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/Encoding.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt index 48da884..541504c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 8f1a7f2..9616e99 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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/javadoc/JavadocTag.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocTag.kt index ec81567..a6224a5 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocTag.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocTag.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsContext.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsContext.kt index 06a681b..ec5d872 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsContext.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsContext.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -28,6 +28,8 @@ package io.spine.gradle.javascript import java.io.File import org.gradle.api.Project +import org.gradle.kotlin.dsl.support.serviceOf +import org.gradle.process.ExecOperations /** * Provides access to the current [JsEnvironment] and shortcuts for running `npm` tool. @@ -47,8 +49,7 @@ open class JsContext(jsEnv: JsEnvironment, internal val project: Project) * * This [File] is used as a working directory. */ - fun File.npm(vararg args: String) = project.exec { - + fun File.npm(vararg args: String) = project.serviceOf().exec { workingDir(this@npm) commandLine(npmExecutable) args(*args) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsEnvironment.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsEnvironment.kt index 38cc649..c2dc68e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsEnvironment.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsEnvironment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsExtension.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsExtension.kt index d593799..69c3e26 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsExtension.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/JsExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Idea.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Idea.kt index 06a112a..087dd25 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Idea.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Idea.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/JsPlugins.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/JsPlugins.kt index 6e007df..b478f64 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/JsPlugins.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/JsPlugins.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/McJs.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/McJs.kt index dbc5c42..2efa4a4 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/McJs.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/McJs.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Protobuf.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Protobuf.kt index 178db24..c1c2d64 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Protobuf.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/plugin/Protobuf.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -50,8 +50,6 @@ fun JsPlugins.protobuf() { val protobufExt = project.extensions.getByType(ProtobufExtension::class.java) protobufExt.apply { - generatedFilesBaseDir = projectDir.path - protoc { artifact = Protobuf.compiler } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Assemble.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Assemble.kt index b5ac3f0..4b57a4e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Assemble.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Assemble.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Check.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Check.kt index 7a87f78..d25c3c2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Check.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Check.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Clean.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Clean.kt index 727553e..c5ff835 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Clean.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Clean.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/IntegrationTest.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/IntegrationTest.kt index a8a6111..227e33e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/IntegrationTest.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/IntegrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/JsTasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/JsTasks.kt index 7d7f709..3cf6335 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/JsTasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/JsTasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/LicenseReport.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/LicenseReport.kt index a06ff01..c3b3a6a 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/LicenseReport.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/LicenseReport.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Publish.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Publish.kt index 9f9d570..7d1baea 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Publish.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Publish.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Webpack.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Webpack.kt index 54b69eb..7c82ad7 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Webpack.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javascript/task/Webpack.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 b27b901..7793277 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -27,12 +27,12 @@ package io.spine.gradle.kotlin import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile /** * Sets [Java toolchain](https://kotlinlang.org/docs/gradle.html#gradle-java-toolchains-support) - * to the specified version (e.g. 11 or 8). + * to the specified version (e.g., 11 or 8). */ fun KotlinJvmProjectExtension.applyJvmToolchain(version: Int) { jvmToolchain { @@ -51,24 +51,21 @@ fun KotlinJvmProjectExtension.applyJvmToolchain(version: String) = /** * Opts-in to experimental features that we use in our codebase. */ -@Suppress("unused", "MagicNumber" /* Kotlin Compiler version. */) -fun KotlinCompile.setFreeCompilerArgs() { - // Avoid the "unsupported flag warning" for Kotlin compilers pre 1.9.20. - // See: https://youtrack.jetbrains.com/issue/KT-61573 - val expectActualClasses = - if (KotlinVersion.CURRENT.isAtLeast(1, 9, 20)) "-Xexpect-actual-classes" else "" - kotlinOptions { - freeCompilerArgs = listOf( +@Suppress("unused") +fun KotlinJvmCompilerOptions.setFreeCompilerArgs() { + freeCompilerArgs.addAll( + listOf( "-Xskip-prerelease-check", "-Xjvm-default=all", "-Xinline-classes", - expectActualClasses, + "-Xexpect-actual-classes", + "-Xcontext-parameters", "-opt-in=" + "kotlin.contracts.ExperimentalContracts," + "kotlin.io.path.ExperimentalPathApi," + "kotlin.ExperimentalUnsignedTypes," + "kotlin.ExperimentalStdlibApi," + "kotlin.experimental.ExperimentalTypeInference", - ).filter { it.isNotBlank() } - } + ) + ) } 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 d992880..4e2e24a 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/protobuf/ProtoTaskExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,28 +24,36 @@ * 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 +import com.google.protobuf.gradle.ProtobufExtension import io.spine.gradle.sourceSets import java.io.File 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 import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.getByType import org.gradle.plugins.ide.idea.GenerateIdeaModule import org.gradle.plugins.ide.idea.model.IdeaModel import org.gradle.plugins.ide.idea.model.IdeaModule -import org.jetbrains.kotlin.gradle.dsl.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask import titleCaseFirstChar /** - * Obtains the name of the `generated` directory under the project root directory. + * Obtains the path of the `generated` directory under the project root directory. */ -private val Project.generatedDir: String - get() = "${projectDir}/generated" +private val Project.generatedDir: Path + get() = projectDir.resolve("generated").toPath() /** * Obtains the `generated` directory for the source set of the task. @@ -58,14 +66,14 @@ private fun GenerateProtoTask.generatedDir(language: String = ""): File { } /** - * Configures protobuf code generation task for the code which cannot use Spine Model Compiler - * (e.g. the `base` project). + * Configures a [GenerateProtoTask] for the code which cannot use Spine Model Compiler + * (e.g., Spine Base or Spine Validation modules). * * The task configuration consists of the following steps: * * 1. Adding `"kotlin"` to the list of involved `protoc` builtins. * - * 2. Generation of descriptor set file is turned on for each source set. + * 2. Turning on the generation of a descriptor set file for each source set. * These files are placed under the `build/descriptors` directory. * * 3. Removing source code generated for `com.google` package for both Java and Kotlin. @@ -78,10 +86,8 @@ private fun GenerateProtoTask.generatedDir(language: String = ""): File { * The usage of this extension in a module build file would be: * ``` * protobuf { - * generateProtoTasks { - * for (task in all()) { - * task.setup() - * } + * generateProtoTasks.all().configureEach { + * setup() * } * } * ``` @@ -103,35 +109,38 @@ private fun GenerateProtoTask.generatedDir(language: String = ""): File { fun GenerateProtoTask.setup() { builtins.maybeCreate("kotlin") setupDescriptorSetFileCreation() + doFirst { + excludeProtocOutput() + } doLast { copyGeneratedFiles() } - excludeProtocOutput() setupKotlinCompile() dependOnProcessResourcesTask() - configureIdeaDirs() + makeDirsForIdeaModule() } /** * Tell `protoc` to generate descriptor set files under the project build dir. * * The name of the descriptor set file to be generated - * is made to be unique per project's Maven coordinates. + * is made to be unique via the project's Maven coordinates. * * As the last step of this task, writes a `desc.ref` file * for the contextual source set, pointing to the generated descriptor set file. - * This is needed in order to allow other Spine libraries - * to locate and load the generated descriptor set files properly. + * This is needed to allow other Spine libraries to locate and load the generated + * descriptor set files properly. * - * Such a job is usually performed by Spine McJava plugin, + * Such a job is usually performed by Spine McJava plugin; * however, it is not possible to use this plugin (or its code) * in this repository due to cyclic dependencies. */ @Suppress( - "TooGenericExceptionCaught" /* Handling all file-writing failures in the same way.*/) -private fun GenerateProtoTask.setupDescriptorSetFileCreation() { - // Tell `protoc` generate descriptor set file. - // The name of the generated file reflects project's Maven coordinates. + "TooGenericExceptionCaught" /* Handling all file-writing failures in the same way.*/ +) +fun GenerateProtoTask.setupDescriptorSetFileCreation() { + // Tell `protoc` generate a descriptor set file. + // The name of the generated file reflects the Maven coordinates of the project. val ssn = sourceSet.name generateDescriptorSet = true val buildDir = project.layout.buildDirectory.asFile.get().path @@ -143,7 +152,7 @@ private fun GenerateProtoTask.setupDescriptorSetFileCreation() { includeSourceInfo = true } - // Make the descriptor set file included into the resources. + // Add the descriptor set file into the resources. project.sourceSets.named(ssn) { resources.srcDirs(descriptorsDir) } @@ -167,7 +176,7 @@ private fun GenerateProtoTask.setupDescriptorSetFileCreation() { * reflecting the Maven coordinates of Gradle artifact, and the source set * for which the descriptor set name is to be generated. * - * The returned value is just a file name, and does not contain a file path. + * The returned value is just a file name and does not contain a file path. */ private fun Project.descriptorSetName(sourceSet: SourceSet) = arrayOf( @@ -220,7 +229,7 @@ private fun GenerateProtoTask.deleteComGoogle(language: String) { * Exclude [GenerateProtoTask.outputBaseDir] from Java source set directories to avoid * duplicated source code files. */ -private fun GenerateProtoTask.excludeProtocOutput() { +fun GenerateProtoTask.excludeProtocOutput() { val protocOutputDir = File(outputBaseDir).parentFile val java: SourceDirectorySet = sourceSet.java @@ -228,6 +237,8 @@ private fun GenerateProtoTask.excludeProtocOutput() { 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) @@ -239,8 +250,8 @@ private fun GenerateProtoTask.excludeProtocOutput() { /** * Make sure Kotlin compilation explicitly depends on this `GenerateProtoTask` to avoid racing. */ -private fun GenerateProtoTask.setupKotlinCompile() { - val kotlinCompile = project.kotlinCompileFor(sourceSet) +fun GenerateProtoTask.setupKotlinCompile() { + val kotlinCompile = project.kotlinCompilationTaskFor(sourceSet) kotlinCompile?.dependsOn(this) } @@ -253,7 +264,7 @@ private fun GenerateProtoTask.setupKotlinCompile() { * by Gradle during the build because Protobuf Gradle Plugin does not set * dependencies between `generateProto` and `processResources` tasks. */ -private fun GenerateProtoTask.dependOnProcessResourcesTask() { +fun GenerateProtoTask.dependOnProcessResourcesTask() { val processResources = processResourceTaskName(sourceSet.name) project.tasks[processResources].dependsOn(this) } @@ -268,78 +279,167 @@ private fun processResourceTaskName(sourceSetName: String): String { return "process${infix}Resources" } -/** - * Attempts to obtain the Kotlin compilation Gradle task for the given source set. - * - * Typically, the task is named by a pattern: `compileKotlin`, or just - * `compileKotlin` if the source set name is `"main"`. If the task does not fit this described - * pattern, this method will not find it. - */ -private fun Project.kotlinCompileFor(sourceSet: SourceSet): KotlinCompile<*>? { +private fun Project.kotlinCompilationTaskFor(sourceSet: SourceSet): KotlinCompilationTask<*>? { val taskName = sourceSet.getCompileTaskName("Kotlin") - return tasks.findByName(taskName) as KotlinCompile<*>? + return tasks.named(taskName, KotlinCompilationTask::class.java).orNull } private fun File.residesIn(directory: File): Boolean = canonicalFile.startsWith(directory.absolutePath) -private fun GenerateProtoTask.configureIdeaDirs() = project.plugins.withId("idea") { - val module = project.extensions.findByType(IdeaModel::class.java)!!.module - - // Make IDEA forget about sources under `outputBaseDir`. - val protocOutputDir = File(outputBaseDir).parentFile - module.generatedSourceDirs.removeIf { dir -> - dir.residesIn(protocOutputDir) +/** + * Ensures that generated directories for Java and Kotlin are created before [GenerateIdeaModule]. + * + * This works as advised by `Utils.groovy` from Protobuf Gradle plugin: + * ``` + * This is required because the IntelliJ IDEA plugin does not allow adding source directories + * that do not exist. The IntelliJ IDEA config files should be valid from the start even if + * a user runs './gradlew idea' before running './gradlew generateProto'. + * ``` + */ +fun GenerateProtoTask.makeDirsForIdeaModule() { + project.plugins.withId("idea") { + val javaDir = generatedDir("java") + val kotlinDir = generatedDir("kotlin") + project.tasks.withType(GenerateIdeaModule::class.java).forEach { + it.doFirst { + javaDir.mkdirs() + kotlinDir.mkdirs() + } + } } +} - module.sourceDirs.removeIf { dir -> - dir.residesIn(protocOutputDir) - } +/** + * Prints diagnostic output of `sourceDirs` and `generatedSourceDirs` of an [IdeaModule]. + * + * To get a handle on [IdeaModule] please use the following code: + * + * ```kotlin + * val module = project.extensions.findByType(IdeaModel::class.java)!!.module + * ``` + */ +@Suppress("unused") // To be used when debugging build scripts. +fun IdeaModule.printSourceDirectories() { + println("**** [IDEA] Source directories:") + sourceDirs.forEach { println(it) } + println() + println("**** [IDEA] Generated source directories:") + generatedSourceDirs.forEach { println(it) } + println() + println("**** [IDEA] Excluded directories:") + 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) - val javaDir = generatedDir("java") - val kotlinDir = generatedDir("kotlin") - - // As advised by `Utils.groovy` from Protobuf Gradle plugin: - // This is required because the IntelliJ IDEA plugin does not allow adding source directories - // that do not exist. The IntelliJ IDEA config files should be valid from the start even if - // a user runs './gradlew idea' before running './gradlew generateProto'. - project.tasks.withType(GenerateIdeaModule::class.java).forEach { - it.doFirst { - javaDir.mkdirs() - kotlinDir.mkdirs() +/** + * Obtains the directory where the Protobuf Gradle Plugin should place the generated code. + * + * 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` (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() { + 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 } - if (isTest) { - module.testSources.run { - from(javaDir) - from(kotlinDir) - } - } else { - module.sourceDirs.run { - add(javaDir) - add(kotlinDir) +/** + * Ensures that the sources generated by Protobuf Gradle Plugin + * are not included in the IDEA project. + * + * IDEA should only see the sources generated by ProtoData as + * we define in [GenerateProtoTask.excludeProtocOutput]. + */ +@Suppress("unused") +fun Project.configureIdea() { + + fun filterSources(sources: Set, excludeDir: File): Set = + sources.filter { !it.residesIn(excludeDir) }.toSet() + + pluginManager.withPlugin("idea") { + val idea = extensions.getByType() + with(idea.module) { + val protocOutput = file(generatedSourceProtoDir) + val protocTargets = protocTargets() + excludeWithNested(protocOutput.toPath(), protocTargets) + sourceDirs = filterSources(sourceDirs, protocOutput) + testSources.filter { !it.residesIn(protocOutput) } + generatedSourceDirs = generatedDir.resolve(protocTargets) + .map { it.toFile() } + .toSet() } } +} - module.generatedSourceDirs.run { - add(javaDir) - add(kotlinDir) +/** + * Lists target directories for Protobuf code generation. + * + * The directory names are in the following format: + * + * `/` + */ +private fun Project.protocTargets(): List { + val protobufTasks = tasks.withType(GenerateProtoTask::class.java) + val codegenTargets = sequence { + protobufTasks.forEach { task -> + val sourceSet = task.sourceSet.name + val builtins = task.builtins.map { builtin -> builtin.name } + val plugins = task.plugins.map { plugin -> plugin.name } + val combined = builtins + plugins + combined.forEach { subdir -> + yield(Paths.get(sourceSet, subdir)) + } + } } + return codegenTargets.toList() } +private fun Path.resolve(subdirs: Iterable): List = + subdirs.map { + resolve(it) + } + /** - * Prints diagnostic output of `sourceDirs` and `generatedSourceDirs` of an [IdeaModule]. + * Excludes the given directory and its subdirectories from + * being seen as ones with the source code. * - * The warning `"unused"` is suppressed because this function is not used in - * the production mode. + * The primary use of this extension is to exclude `build/generated/source/proto` and its + * subdirectories to avoid duplication of types in the generated code with those in + * produced by ProtoData under the `$projectDir/generated/` directory. */ -@Suppress("unused") -private fun IdeaModule.printSourceDirectories() { - println("**** [IDEA] Source directories:") - sourceDirs.forEach { println(it) } - println() - println("**** [IDEA] Generated source directories:") - generatedSourceDirs.forEach { println(it) } - println() +private fun IdeaModule.excludeWithNested(directory: Path, subdirs: Iterable) { + excludeDirs.add(directory.toFile()) + directory.resolve(subdirs).forEach { + excludeDirs.add(it.toFile()) + } +} + +@Suppress("unused") // To be used when debugging build scripts. +private fun printExcluded(dir: Any) { + println(" [IDEA] Excluding directory: $dir") } 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 13dc772..56245ff 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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) } } @@ -70,8 +71,9 @@ open class CheckVersionIncrement : DefaultTask() { val versions = metadata?.versioning?.versions val versionExists = versions?.contains(version) ?: false if (versionExists) { - throw GradleException(""" - Version `$version` is already published to maven repository `$repoUrl`. + throw GradleException( + """ + The version `$version` is already published to the Maven repository `$repoUrl`. Try incrementing the library version. All available versions are: ${versions?.joinToString(separator = ", ")}. @@ -88,13 +90,28 @@ open class CheckVersionIncrement : DefaultTask() { private fun Project.artifactPath(): String { val group = this.group as String - val name = "spine-${this.name}" + val name = "${artifactPrefix()}${this.name}" val pathElements = ArrayList(group.split('.')) pathElements.add(name) val path = pathElements.joinToString(separator = "/") return path } + + /** + * Returns the artifact prefix used for the publishing of this project. + * + * All current Spine modules should be using `SpinePublishing`. + * Therefore, the corresponding extension should be present in the root project. + * However, just in case, we define the "standard" prefix here as well. + * + * This value MUST be the same as defined by the defaults in `SpinePublishing`. + */ + private fun Project.artifactPrefix(): String { + val ext = rootProject.extensions.findByType(SpinePublishing::class.java) + val result = ext?.artifactPrefix ?: SpinePublishing.DEFAULT_PREFIX + return result + } } private data class MavenMetadata(var versioning: Versioning = Versioning()) { @@ -119,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 37f6f23..67716d8 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudArtifactRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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 e918ca5..624f5cb 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CloudRepo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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 - * public repository, and publishing via 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 new file mode 100644 index 0000000..152455d --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CustomPublicationHandler.kt @@ -0,0 +1,70 @@ +/* + * 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 io.spine.gradle.repo.Repository +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPublication + +/** + * A handler for custom publications, which are declared under the [publications] + * section of a module. + * + * 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]. + * + * A module which declares custom publications must be specified in + * the [SpinePublishing.modulesWithCustomPublishing] property. + * + * If a module with [publications] declared locally is not specified as one with custom publishing, + * it may cause a name clash between an artifact produced by + * 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 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 3250bc2..df326b8 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/GitHubPackages.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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/IncrementGuard.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt index dbb69a7..b6683fa 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/JarDsl.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/JarDsl.kt index 8fe6071..1371184 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/JarDsl.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/JarDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt index 5b50fab..874b7f9 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt new file mode 100644 index 0000000..75c5413 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt @@ -0,0 +1,250 @@ +/* + * 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 LicenseSettings +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. + */ +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, + protected var destinations: Set +) { + /** + * Remembers if the [apply] function was called by this handler. + */ + private var applied: Boolean = false + + /** + * 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 + } + + /** + * 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 associated [project] + * or creates new ones. + */ + abstract fun handlePublications() + + /** + * Goes through the [destinations] and registers each as a repository for publishing + * in the given Gradle project. + */ + private fun registerDestinations() { + val repositories = project.publishingExtension.repositories + destinations.forEach { destination -> + repositories.register(project, destination) + } + } + + /** + * Copies the attributes of Gradle [Project] to this [MavenPublication]. + * + * The following project attributes are copied: + * * [group][Project.getGroup]; + * * [version][Project.getVersion]; + * * [description][Project.getDescription]. + * + * Also, this function adds the [artifactPrefix][SpinePublishing.artifactPrefix] to + * the [artifactId][MavenPublication.setArtifactId] of this publication, + * if the prefix is not added yet. + * + * Finally, the Apache Software License 2.0 is set as the only license + * under which the published artifact is distributed. + */ + protected fun MavenPublication.copyProjectAttributes() { + groupId = project.group.toString() + val prefix = project.spinePublishing.artifactPrefix + if (!artifactId.startsWith(prefix)) { + artifactId = prefix + artifactId + } + version = project.version.toString() + pom.description.set(project.description) + + pom.licenses { + license { + name.set(LicenseSettings.name) + url.set(LicenseSettings.url) + } + } + } + + /** + * 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 + } +} + +/** + * Adds a Maven repository to the project specifying credentials, if they are + * [available][Repository.credentials] from the root project. + */ +private fun RepositoryHandler.register(project: Project, repository: Repository) { + val isSnapshot = project.version.toString().isSnapshot() + val credentials = repository.credentials(project.rootProject) + maven { + 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/Publications.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/Publications.kt deleted file mode 100644 index 1ebe17d..0000000 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/Publications.kt +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2024, 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 io.spine.gradle.Repository -import io.spine.gradle.isSnapshot -import org.gradle.api.Project -import org.gradle.api.artifacts.dsl.RepositoryHandler -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.TaskProvider -import org.gradle.api.tasks.bundling.Jar -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.create - -/** - * The name of the Maven Publishing Gradle plugin. - */ -private const val MAVEN_PUBLISH = "maven-publish" - -/** - * Abstract base for handlers of publications in a project - * with [spinePublishing] settings declared. - */ -internal sealed class PublicationHandler( - protected val project: Project, - private val destinations: Set -) { - - fun apply() = with(project) { - if (!hasCustomPublishing) { - apply(plugin = MAVEN_PUBLISH) - } - - pluginManager.withPlugin(MAVEN_PUBLISH) { - handlePublications() - registerDestinations() - configurePublishTask(destinations) - } - } - - /** - * Either handles publications already declared in the given project, - * or creates new ones. - */ - abstract fun handlePublications() - - /** - * Goes through the [destinations] and registers each as a repository for publishing - * in the given Gradle project. - */ - private fun registerDestinations() { - val repositories = project.publishingExtension.repositories - destinations.forEach { destination -> - repositories.register(project, destination) - } - } - - /** - * Copies the attributes of Gradle [Project] to this [MavenPublication]. - * - * The following project attributes are copied: - * * [group][Project.getGroup]; - * * [version][Project.getVersion]; - * * [description][Project.getDescription]. - * - * Also, this function adds the [artifactPrefix][SpinePublishing.artifactPrefix] to - * the [artifactId][MavenPublication.setArtifactId] of this publication, - * if the prefix is not added yet. - * - * Finally, the Apache Software License 2.0 is set as the only license - * under which the published artifact is distributed. - */ - protected fun MavenPublication.copyProjectAttributes() { - groupId = project.group.toString() - val prefix = project.spinePublishing.artifactPrefix - if (!artifactId.startsWith(prefix)) { - artifactId = prefix + artifactId - } - version = project.version.toString() - pom.description.set(project.description) - - pom.licenses { - license { - name.set("The Apache Software License, Version 2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - } -} - -/** - * Adds a Maven repository to the project specifying credentials, if they are - * [available][Repository.credentials] from the root project. - */ -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) - credentials { - username = credentials?.username - password = credentials?.password - } - } -} - -/** - * A publication for a typical Java project. - * - * In Gradle, to publish something, one should create a publication. - * 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 [MavenPublication] named "mavenJava". 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. - * - * @param jarFlags - * flags for additional JARs published along with the compilation output. - * @param destinations - * Maven repositories to which the produced artifacts will be sent. - * @see - * Maven Publish Plugin | Publications - */ -internal class StandardJavaPublicationHandler( - project: Project, - private val jarFlags: JarFlags, - destinations: Set, -) : PublicationHandler(project, destinations) { - - /** - * Creates a new "mavenJava" [MavenPublication] in the given project. - */ - override fun handlePublications() { - val jars = project.artifacts(jarFlags) - val publications = project.publications - publications.create("mavenJava") { - copyProjectAttributes() - specifyArtifacts(jars) - } - } - - /** - * Specifies which artifacts this [MavenPublication] will contain. - * - * A typical Maven publication contains: - * - * 1. Jar archives. For example, compilation output, sources, javadoc, etc. - * 2. Maven metadata file that has the ".pom" extension. - * 3. Gradle's metadata file that has the ".module" extension. - * - * Metadata files contain information about a publication itself, its artifacts, and their - * dependencies. Presence of ".pom" file is mandatory for publication to be consumed by - * `mvn` build tool itself or other build tools that understand Maven notation (Gradle, Ivy). - * The presence of ".module" is optional, but useful when a publication is consumed by Gradle. - * - * @see Maven – POM Reference - * @see - * Understanding Gradle Module Metadata - */ - private fun MavenPublication.specifyArtifacts(jars: 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. - */ - val javaComponent = project.components.findByName("java") - javaComponent?.let { - 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). - */ - jars.forEach { - artifact(it) - } - } -} - -/** - * A handler for custom publications, which are declared under the [publications] - * section of a module. - * - * 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]. - * - * A module which declares custom publications must be specified in - * the [SpinePublishing.modulesWithCustomPublishing] property. - * - * If a module with [publications] declared locally is not specified as one with custom publishing, - * it may cause a name clash between an artifact produced by the [standard][MavenPublication] - * publication, and custom ones. To have both standard and custom publications, - * please specify custom artifact IDs or classifiers for each custom publication. - */ -internal class CustomPublicationHandler(project: Project, destinations: Set) : - PublicationHandler(project, destinations) { - - override fun handlePublications() { - project.publications.forEach { - (it as MavenPublication).copyProjectAttributes() - } - } -} 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 d85885f..0a24b56 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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) @@ -140,14 +155,45 @@ private fun TaskContainer.getOrCreatePublishTask(): TaskProvider = register(PUBLISH_TASK) } +@Suppress( + /* Several types of exceptions may be thrown, + and Kotlin does not have a multi-catch support yet. */ + "TooGenericExceptionCaught" +) private fun TaskContainer.registerCheckCredentialsTask( - destinations: Set -): TaskProvider = - register("checkCredentials") { - doLast { - destinations.forEach { it.ensureCredentials(project) } + destinations: Set, +): TaskProvider { + val checkCredentials = "checkCredentials" + try { + // The result of this call is ignored intentionally. + // + // We expect this line to fail with the exception + // in case the task with this name is NOT registered. + // + // Otherwise, we need to replace the existing task + // to avoid checking the credentials + // for some previously asked `destinations`. + named(checkCredentials) + val toConfigure = replace(checkCredentials) + toConfigure.doLastCredentialsCheck(destinations) + return named(checkCredentials) + } catch (_: Exception) { + return register(checkCredentials) { doLastCredentialsCheck(destinations) } + } +} + +private fun Task.doLastCredentialsCheck(destinations: Set) { + doLast { + 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) } } +} private fun Repository.ensureCredentials(project: Project) { val credentials = credentials(project) @@ -175,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 @@ -220,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") @@ -275,4 +321,3 @@ internal fun Project.artifacts(jarFlags: JarFlags): Set> { return tasks } - 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 41cc101..eea6dc1 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingRepos.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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 f3218ef..9dc7d7c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -28,9 +28,7 @@ package io.spine.gradle.publish -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 @@ -44,75 +42,112 @@ import org.gradle.kotlin.dsl.findByType * * The extension can be configured for single- and multi-module projects. * + * ## Using in a multi-module project + * * When used with a multi-module project, the extension should be opened in a root project's * build file. The published modules are specified explicitly by their names: * - * ``` + * ```kotlin * spinePublishing { * modules = setOf( * "subprojectA", * "subprojectB", * ) - * destinations = setOf( - * PublishingRepos.cloudRepo, - * PublishingRepos.cloudArtifactRegistry, + * destinations = PublishingRepos.run { setOf( + * cloudArtifactRegistry, + * gitHub("") // The name of the GitHub repository of the project. + * )} + * } + * ``` + * + * ### Filtering out test-only modules + * + * Sometimes a functional or an integration test requires a significant amount of + * configuration code which is better understood when isolated into a separate module. + * Conventionally, we use the `-tests` suffix for naming such modules. + * + * In order to avoid publishing of such a test-only module, we use the following extensions + * for the Gradle [Project] class: [productionModules], [productionModuleNames]. + * So the above code for specifying the modules to publish could be rewritten as follows: + * + * ```kotlin + * spinePublishing { + * modules = productionModuleNames.toSet() + * } + * ``` + * This code works for most of the projects. + * + * ### Arranging custom publishing for a module + * ```kotlin + * + * 1. Modify the list of standardly published modules in the root project like this: + * + * ```kotlin + * spinePublishing { + * modules = productionModuleNames + * .minus("my-custom-module") + * .toSet() + * + * modulesWithCustomPublishing = setOf( + * "my-custom-module" * ) + * + * // ... * } * ``` + * 2. Arrange the custom publishing in the `my-custom-module` project. + * + * ## Using in a single-module project * * When used with a single-module project, the extension should be opened in a project's build file. * Only destinations should be specified: * - * ``` + * ```kotlin * spinePublishing { - * destinations = setOf( - * PublishingRepos.cloudRepo, - * PublishingRepos.cloudArtifactRegistry, - * ) + * destinations = PublishingRepos.run { setOf( + * cloudArtifactRegistry, + * gitHub("") + * )} * } * ``` * - * It is worth to mention, that publishing of a module can be configured only from a single place. + * ## Publishing modules + * + * It is worth mentioning that publishing of a module can be configured only from a single place. * For example, declaring `subprojectA` as published in a root project and opening * `spinePublishing` extension within `subprojectA` itself would lead to an exception. * - * In Gradle, in order to publish something somewhere one should create a publication. In each + * In Gradle, in order to publish something somewhere, one should create a publication. In each * of published modules, the extension will create a [publication][StandardJavaPublicationHandler] - * named "mavenJava". All artifacts, published by this extension belong to this publication. + * named "mavenJava". All artifacts published by this extension belong to this publication. + * + * ## Published artifacts * - * By default, along with the compilation output of "main" source set, the extension publishes + * By default, along with the compilation output of the `main` source set, the extension publishes * the following artifacts: * - * 1. [sourcesJar] – sources from "main" source set. Includes "hand-made" Java, - * Kotlin and Proto files. In order to include the generated code into this artifact, a module - * should specify those files as a part of "main" source set. - * - * Here's an example of how to do that: - * - * ``` - * sourceSets { - * val generatedDir by extra("$projectDir/generated") - * val generatedSpineDir by extra("$generatedDir/main/java") - * main { - * java.srcDir(generatedSpineDir) - * } - * } - * ``` - * 2. [protoJar] – only Proto sources from "main" source set. It's published only if + * 1. [sourcesJar] β€” sources from the `main` source set. Includes handcrafted and generated + * code in Java, Kotlin, and `.proto` files. + * + * 2. [protoJar] – only `.proto` sources from the `main` source set. It's published only if * Proto files are actually present in the source set. Publication of this artifact is optional * and can be disabled via [SpinePublishing.protoJar]. - * 3. [javadocJar] - 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. - * 4. [dokkaKotlinJar] - documentation generated by Dokka for Kotlin and Java sources + * + * 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 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. - * 5. [dokkaJavaJar] - documentation generated by Dokka for Kotlin and Java sources - * * using the Java API mode. + * + * 5. [dokkaJavaJar] β€” documentation generated by Dokka for Kotlin and Java sources + * using the Java API mode. * * Additionally, [testJar] artifact can be published. This artifact contains compilation output - * of "test" source set. Use [SpinePublishing.testJar] to enable its publishing. + * of the `test` source set. Use [SpinePublishing.testJar] to enable its publishing. * * @see [artifacts] + * @see SpinePublishing */ fun Project.spinePublishing(block: SpinePublishing.() -> Unit) { apply() @@ -127,16 +162,32 @@ 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. * - * @param project - * a 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. + * 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. * * @see spinePublishing */ open class SpinePublishing(private val project: Project) { + companion object { + + /** + * The default prefix added before a module name when publishing artifacts. + */ + const val DEFAULT_PREFIX = "spine-" + } + private val protoJar = ProtoJar() private val testJar = TestJar() private val dokkaJar = DokkaJar() @@ -155,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 @@ -183,24 +243,22 @@ open class SpinePublishing(private val project: Project) { * Usually, Spine-related projects are published to one or more repositories, * declared in [PublishingRepos]: * - * ``` - * destinations = setOf( - * PublishingRepos.cloudRepo, - * PublishingRepos.cloudArtifactRegistry, - * PublishingRepos.gitHub("base"), - * ) + * ```kotlin + * destinations = PublishingRepos.run { setOf( + * cloudArtifactRegistry, + * gitHub("") // The name of the GitHub repository of the 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. - * - * The default value is "spine-". */ - var artifactPrefix: String = "spine-" + var artifactPrefix: String = DEFAULT_PREFIX /** * Allows disabling publishing of [protoJar] artifact, containing all Proto sources @@ -208,7 +266,7 @@ open class SpinePublishing(private val project: Project) { * * Here's an example of how to disable it for some of the published modules: * - * ``` + * ```kotlin * spinePublishing { * modules = setOf( * "subprojectA", @@ -232,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: * * ``` @@ -271,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") @@ -304,7 +362,7 @@ open class SpinePublishing(private val project: Project) { * } * ``` * - * The resulting artifact is available under "dokka" classifier. + * The resulting artifact is available under the "dokka" classifier. */ fun dokkaJar(block: DokkaJar.() -> Unit) = dokkaJar.run(block) @@ -361,27 +419,47 @@ open class SpinePublishing(private val project: Project) { * * We selected to use [Project.afterEvaluate] so that we can configure publishing of multiple * modules from a root project. When we do this, we configure publishing for a module, - * build file of which has not been even evaluated yet. + * a build file of which has not been even evaluated yet. * * The simplest example here is specifying of `version` and `group` for Maven coordinates. - * 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 a 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. + * 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 + * 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. * @@ -400,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." + ) } } @@ -425,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 new file mode 100644 index 0000000..06d78c1 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt @@ -0,0 +1,133 @@ +/* + * 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 io.spine.gradle.repo.Repository +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.bundling.Jar +import org.gradle.kotlin.dsl.create + +/** + * A publication for a typical Java project. + * + * In Gradle, to publish something, one should create a publication. + * 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 + * [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. + * + * @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 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"` [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(PUBLICATION_NAME) { + copyProjectAttributes() + specifyArtifacts(jars) + } + } + + /** + * Specifies which artifacts this [MavenPublication] will contain. + * + * A typical Maven publication contains: + * + * 1. Jar archives. For example, compilation output, sources, javadoc, etc. + * 2. Maven metadata file that has the ".pom" extension. + * 3. Gradle's metadata file that has the ".module" extension. + * + * Metadata files contain information about a publication itself, its artifacts, and their + * dependencies. Presence of ".pom" file is mandatory for publication to be consumed by + * `mvn` build tool itself or other build tools that understand Maven notation (Gradle, Ivy). + * The presence of ".module" is optional, but useful when a publication is consumed by Gradle. + * + * @see Maven – POM Reference + * @see + * Understanding Gradle Module Metadata + */ + private fun MavenPublication.specifyArtifacts(jars: 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. + */ + val javaComponent = project.components.findByName("java") + javaComponent?.let { + from(it) + } + + /* + 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/repo/Credentials.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt new file mode 100644 index 0000000..1624b38 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Credentials.kt @@ -0,0 +1,35 @@ +/* + * 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 + +/** + * Password credentials for a Maven repository. + */ +data class Credentials( + val username: String?, + val password: String? +) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt new file mode 100644 index 0000000..461c690 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/RepoSlug.kt @@ -0,0 +1,67 @@ +/* + * 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 org.gradle.api.GradleException + +/** + * A name of a repository. + */ +@Suppress("unused") +class RepoSlug(val value: String) { + + companion object { + + /** + * The name of the environment variable containing the repository slug, for which + * the Gradle build is performed. + */ + private const val environmentVariable = "REPO_SLUG" + + /** + * Reads `REPO_SLUG` environment variable and returns its value. + * + * In case it is not set, a [org.gradle.api.GradleException] is thrown. + */ + fun fromVar(): RepoSlug { + val envValue = System.getenv(environmentVariable) + if (envValue.isNullOrEmpty()) { + throw GradleException("`REPO_SLUG` environment variable is not set.") + } + return RepoSlug(envValue) + } + } + + override fun toString(): String = value + + /** + * Returns the GitHub URL to the project repository. + */ + fun gitHost(): String { + return "git@github.com-publish:${value}.git" + } +} 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/coverage/CodebaseFilter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/CodebaseFilter.kt index 59459d3..efdf605 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/CodebaseFilter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/CodebaseFilter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,7 +29,6 @@ package io.spine.gradle.report.coverage import com.google.errorprone.annotations.CanIgnoreReturnValue import io.spine.gradle.report.coverage.FileFilter.generatedOnly import java.io.File -import kotlin.streams.toList import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileTree import org.gradle.api.file.FileTree diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtension.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtension.kt index ddd6e9f..ae4734c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtension.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtension.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtensions.kt index 88d3f40..89c8789 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileFilter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileFilter.kt index aa30c8a..5b26cc7 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileFilter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/FileFilter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt index 826de01..5114add 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/JacocoConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -28,7 +28,7 @@ package io.spine.gradle.report.coverage import io.spine.dependency.test.Jacoco import io.spine.gradle.applyPlugin -import io.spine.gradle.findTask +import io.spine.gradle.getTask import io.spine.gradle.report.coverage.TaskName.check import io.spine.gradle.report.coverage.TaskName.copyReports import io.spine.gradle.report.coverage.TaskName.jacocoRootReport @@ -181,7 +181,7 @@ class JacocoConfig( private fun registerCopy(tasks: TaskContainer): TaskProvider { val everyExecData = mutableListOf() projects.forEach { project -> - val jacocoTestReport = project.findTask(jacocoTestReport.name) + val jacocoTestReport = project.getTask(jacocoTestReport.name) val executionData = jacocoTestReport.executionData everyExecData.add(executionData) } @@ -194,7 +194,7 @@ class JacocoConfig( rename { "${UUID.randomUUID()}.exec" } - dependsOn(projects.map { it.findTask(jacocoTestReport.name) }) + dependsOn(projects.map { it.getTask(jacocoTestReport.name) }) } return copyReports } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/PathMarker.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/PathMarker.kt index 25d81fe..26bb135 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/PathMarker.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/PathMarker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/TaskName.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/TaskName.kt index 26ae433..7c0e386 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/TaskName.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/TaskName.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Configuration.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Configuration.kt index dbc8f43..f6e06fd 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Configuration.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Configuration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt index ed7fd96..ec86eb5 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -30,7 +30,7 @@ import com.github.jk1.license.LicenseReportExtension import com.github.jk1.license.LicenseReportExtension.ALL import com.github.jk1.license.LicenseReportPlugin import io.spine.gradle.applyPlugin -import io.spine.gradle.findTask +import io.spine.gradle.getTask import java.io.File import org.gradle.api.Project import org.gradle.api.Task @@ -98,7 +98,7 @@ object LicenseReporter { } /** - * Tells to merge all per-project reports which were previously [generated][generateReportIn] + * Tells to merge all per-project reports that were previously [generated][generateReportIn] * for each of the subprojects of the root Gradle project. * * The merge result is placed according to [Paths]. @@ -109,10 +109,10 @@ object LicenseReporter { val rootProject = project.rootProject val mergeTask = rootProject.tasks.register(mergeTaskName) { val consolidationTask = this - val assembleTask = project.findTask("assemble") + val assembleTask = project.getTask("assemble") val sourceProjects: Iterable = sourceProjects(rootProject) sourceProjects.forEach { - val perProjectTask = it.findTask(projectTaskName) + val perProjectTask = it.getTask(projectTaskName) consolidationTask.dependsOn(perProjectTask) perProjectTask.dependsOn(assembleTask) } @@ -121,7 +121,7 @@ object LicenseReporter { } dependsOn(assembleTask) } - project.findTask("build") + project.getTask("build") .finalizedBy(mergeTask) } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/MarkdownReportRenderer.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/MarkdownReportRenderer.kt index 5eecfe6..b158210 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/MarkdownReportRenderer.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/MarkdownReportRenderer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 fc735a8..0aca30f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Paths.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Paths.kt index 4a4fa22..975a73b 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Paths.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Paths.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ProjectDependencies.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ProjectDependencies.kt index c1c18c2..d9e569c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ProjectDependencies.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/ProjectDependencies.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Tasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Tasks.kt index f88a58c..05df91a 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Tasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/Tasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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 e758bee..adda37b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyScope.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyScope.kt index cbe860e..1b4f478 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyScope.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyScope.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt index a758ecf..eda2493 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -182,8 +182,8 @@ private fun Project.deduplicate(dependencies: Set): List - group.value.maxByOrNull { dep -> dep.version }!! - } + group.value.maxByOrNull { dep -> dep.version ?: "" } + }.filterNotNull() return filtered } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/InceptionYear.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/InceptionYear.kt index 2e68942..cb25b3d 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/InceptionYear.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/InceptionYear.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/MarkupExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/MarkupExtensions.kt index 750452d..0612f2e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/MarkupExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/MarkupExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ModuleDependency.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ModuleDependency.kt index 4750db5..3d72de1 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ModuleDependency.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ModuleDependency.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -40,7 +40,7 @@ internal class ModuleDependency( val project: Project, val configuration: Configuration, private val dependency: Dependency, - private val factualVersion: String = dependency.version!! + private val factualVersion: String? = dependency.version ) : Dependency by dependency, Comparable { @@ -52,7 +52,7 @@ internal class ModuleDependency( .thenBy { it.factualVersion } } - override fun getVersion(): String = factualVersion + override fun getVersion(): String? = factualVersion /** * A project dependency with its [scope][DependencyScope]. 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 ddd284c..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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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 ff70b82..9a40725 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -68,27 +68,31 @@ object PomGenerator { fun applyTo(project: Project) { /** - * In some cases, the `base` plugin, which is by default is added by e.g. `java`, - * is not yet added. `base` plugin defines the `build` task. This generator needs it. + * 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. + * This generator needs it. */ project.apply { plugin(BasePlugin::class.java) } - val task = project.tasks.create("generatePom") - task.doLast { - val pomFile = project.projectDir.resolve("pom.xml") - project.delete(pomFile) + val task = project.tasks.register("generatePom") { + doLast { + val pomFile = project.projectDir.resolve("pom.xml") + project.delete(pomFile) - val projectData = project.metadata() - val writer = PomXmlWriter(projectData) - writer.writeTo(pomFile) + val projectData = project.metadata() + val writer = PomXmlWriter(projectData) + writer.writeTo(pomFile) + } + + val assembleTask = project.tasks.findByName("assemble")!! + dependsOn(assembleTask) } val buildTask = project.tasks.findByName("build")!! buildTask.finalizedBy(task) - - val assembleTask = project.tasks.findByName("assemble")!! - task.dependsOn(assembleTask) } } 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 ccb5f31..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 @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -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/io/spine/gradle/report/pom/ProjectMetadata.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ProjectMetadata.kt index b1e4e8a..ffb89a2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ProjectMetadata.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ProjectMetadata.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ScopedDependency.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ScopedDependency.kt index 59a95cd..7c67a32 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ScopedDependency.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ScopedDependency.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/SpineLicense.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/SpineLicense.kt index 05addfb..114395e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/SpineLicense.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/SpineLicense.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Logging.kt b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Logging.kt index 1defd9e..6fb7eab 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Logging.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Logging.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Multiproject.kt b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Multiproject.kt index 32a19dd..1f5ed49 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Multiproject.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Multiproject.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Tasks.kt b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Tasks.kt index 7991450..30ac810 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/testing/Tasks.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/testing/Tasks.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -29,6 +29,7 @@ package io.spine.gradle.testing import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType /** * Registers [slowTest][SlowTest] and [fastTest][FastTest] tasks in this [TaskContainer]. @@ -45,10 +46,10 @@ import org.gradle.kotlin.dsl.register */ @Suppress("unused") fun TaskContainer.registerTestTasks() { - withType(Test::class.java).configureEach { + withType().configureEach { filter { - // There could be cases with no matching tests. E.g. tests could be based on Kotest, - // which has custom task types and names. + // There could be cases with no matching tests. + // E.g., tests could be based on Kotest, which has custom task types and names. isFailOnNoMatchingTests = false includeTestsMatching("*Test") includeTestsMatching("*Spec") diff --git a/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts b/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts new file mode 100644 index 0000000..de7f1bf --- /dev/null +++ b/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts @@ -0,0 +1,72 @@ +/* + * 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 java.io.File +import org.gradle.kotlin.dsl.getValue +import org.gradle.kotlin.dsl.getting +import org.gradle.kotlin.dsl.jacoco +import org.gradle.testing.jacoco.tasks.JacocoReport + +plugins { + jacoco +} + +/** + * Configures [JacocoReport] task to run in a Kotlin KMM project for `commonMain` and `jvmMain` + * source sets. + * + * This script plugin must be applied using the following construct at the end of + * a `build.gradle.kts` file of a module: + * + * ```kotlin + * apply(plugin="jacoco-kmm-jvm") + * ``` + * Please do not apply this script plugin in the `plugins {}` block because `jacocoTestReport` + * task is not yet available at this stage. + */ +@Suppress("unused") +private val about = "" + +/** + * Configure the Jacoco task with custom input a KMM project + * to which this convention plugin is applied. + */ +@Suppress("unused") +val jacocoTestReport: JacocoReport by tasks.getting(JacocoReport::class) { + val buildDir = project.layout.buildDirectory.get().asFile.absolutePath + val classFiles = File("${buildDir}/classes/kotlin/jvm/") + .walkBottomUp() + .toSet() + classDirectories.setFrom(classFiles) + + val coverageSourceDirs = arrayOf( + "src/commonMain", + "src/jvmMain" + ) + sourceDirectories.setFrom(files(coverageSourceDirs)) + + executionData.setFrom(files("${buildDir}/jacoco/jvmTest.exec")) +} diff --git a/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts index 3538024..48fb126 100644 --- a/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. diff --git a/buildSrc/src/main/kotlin/jvm-module.gradle.kts b/buildSrc/src/main/kotlin/jvm-module.gradle.kts index 76a70de..b3d6ef0 100644 --- a/buildSrc/src/main/kotlin/jvm-module.gradle.kts +++ b/buildSrc/src/main/kotlin/jvm-module.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -24,66 +24,59 @@ * 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.lib.Guava -import io.spine.dependency.lib.JavaX import io.spine.dependency.lib.Protobuf -import io.spine.dependency.local.Logging -import io.spine.dependency.local.Spine -import io.spine.dependency.test.JUnit +import io.spine.dependency.local.Reflect import io.spine.dependency.test.Jacoco -import io.spine.dependency.test.Kotest import io.spine.gradle.checkstyle.CheckStyleConfig 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 -import io.spine.gradle.testing.configureLogging -import io.spine.gradle.testing.registerTestTasks -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { `java-library` - idea id("net.ltgt.errorprone") id("pmd-settings") id("project-report") id("dokka-for-java") kotlin("jvm") id("io.kotest") - id("org.jetbrains.kotlinx.kover") 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() val generatedDir = "$projectDir/generated" setTaskDependencies(generatedDir) - setupTests() configureGitHubPages() } typealias Module = Project -fun Module.configureJava(javaVersion: JavaLanguageVersion) { +fun Module.configureJava() { java { - toolchain.languageVersion.set(javaVersion) + sourceCompatibility = BuildSettings.javaVersionCompat + targetCompatibility = BuildSettings.javaVersionCompat } tasks { @@ -94,27 +87,22 @@ fun Module.configureJava(javaVersion: JavaLanguageVersion) { } } -fun Module.configureKotlin(javaVersion: JavaLanguageVersion) { +fun Module.configureKotlin() { kotlin { - applyJvmToolchain(javaVersion.asInt()) explicitApi() - } - - tasks { - withType().configureEach { - kotlinOptions.jvmTarget = javaVersion.toString() + compilerOptions { + jvmTarget.set(BuildSettings.jvmTarget) setFreeCompilerArgs() } } kover { useJacoco(version = Jacoco.version) - } - - koverReport { - defaults { - xml { - onCheck = true + reports { + total { + xml { + onCheck = true + } } } } @@ -134,21 +122,8 @@ fun Module.addDependencies() = dependencies { api(Guava.lib) compileOnlyApi(CheckerFramework.annotations) - compileOnlyApi(JavaX.annotations) + api(JSpecify.annotations) ErrorProne.annotations.forEach { compileOnlyApi(it) } - - implementation(Logging.lib) - - testImplementation(Guava.testLib) - testImplementation(JUnit.runner) - testImplementation(JUnit.pioneer) - JUnit.api.forEach { testImplementation(it) } - - testImplementation(Spine.testlib) - testImplementation(Kotest.frameworkEngine) - testImplementation(Kotest.datatest) - testImplementation(Kotest.runnerJUnit5Jvm) - testImplementation(JUnit.runner) } fun Module.forceConfigurations() { @@ -158,28 +133,14 @@ fun Module.forceConfigurations() { all { resolutionStrategy { force( - JUnit.bom, - JUnit.runner, Dokka.BasePlugin.lib, - Spine.reflect + Reflect.lib, ) } } } } -fun Module.setupTests() { - tasks { - registerTestTasks() - test.configure { - useJUnitPlatform { - includeEngines("junit-jupiter") - } - configureLogging() - } - } -} - fun Module.setTaskDependencies(generatedDir: String) { tasks { val cleanGenerated by registering(Delete::class) { @@ -194,7 +155,9 @@ fun Module.setTaskDependencies(generatedDir: String) { publish?.dependsOn("${project.path}:updateGitHubPages") } } - configureTaskDependencies() + afterEvaluate { + configureTaskDependencies() + } } fun Module.configureGitHubPages() { 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..8868f5b --- /dev/null +++ b/buildSrc/src/main/kotlin/kmp-module.gradle.kts @@ -0,0 +1,187 @@ +/* + * 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 +import io.spine.gradle.testing.configureLogging +import io.spine.gradle.testing.registerTestTasks + +/** + * 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() + + // Enables and configures JVM target. + jvm { + compilerOptions { + jvmTarget.set(BuildSettings.jvmTarget) + setFreeCompilerArgs() + } + } + + // Dependencies are specified per-target. + // Please note, common sources are implicitly available in all targets. + 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 new file mode 100644 index 0000000..715852c --- /dev/null +++ b/buildSrc/src/main/kotlin/module-testing.gradle.kts @@ -0,0 +1,121 @@ +/* + * 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.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` +} + +project.run { + setupTests() + forceTestDependencies() +} + +dependencies { + forceJunitPlatform() + + testImplementation(Jupiter.api) + testImplementation(Jupiter.params) + testImplementation(JUnit.pioneer) + + testImplementation(Guava.testLib) + + testImplementation(TestLib.lib) + testImplementation(Kotest.assertions) + testImplementation(Kotest.datatest) + + testRuntimeOnly(Jupiter.engine) +} + +/** + * Forces the version of [JUnit] platform and its dependencies via [JUnit.bom]. + */ +private fun DependencyHandlerScope.forceJunitPlatform() { + testImplementation(enforcedPlatform(JUnit.bom)) +} + +typealias Module = Project + +/** + * Configure this module to run JUnit-based tests. + */ +fun Module.setupTests() { + tasks { + registerTestTasks() + test.configure { + useJUnitPlatform { + includeEngines("junit-jupiter") + } + configureLogging() + } + } +} + +/** + * Forces the versions of task dependencies that are used _in addition_ to + * the forced JUnit platform. + */ +@Suppress( + /* We're OK with incubating API for configurations. It does not seem to change recently. */ + "UnstableApiUsage" +) +fun Module.forceTestDependencies() { + configurations { + all { + resolutionStrategy { + forceTestDependencies() + } + } + } +} + +private fun ResolutionStrategy.forceTestDependencies() { + force( + Guava.testLib, + Truth.libs, + Kotest.assertions, + ) +} diff --git a/buildSrc/src/main/kotlin/pmd-settings.gradle.kts b/buildSrc/src/main/kotlin/pmd-settings.gradle.kts index a3b0890..0373ee0 100644 --- a/buildSrc/src/main/kotlin/pmd-settings.gradle.kts +++ b/buildSrc/src/main/kotlin/pmd-settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. 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/kotlin/write-manifest.gradle.kts b/buildSrc/src/main/kotlin/write-manifest.gradle.kts index 7bfcdb6..b63d327 100644 --- a/buildSrc/src/main/kotlin/write-manifest.gradle.kts +++ b/buildSrc/src/main/kotlin/write-manifest.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024, 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. @@ -103,7 +103,7 @@ val manifestAttributes = mapOf( * when running tests. We cannot depend on the `Jar` from `resources` because it would * form a circular dependency. */ -val exposeManifestForTests by tasks.creating { +val exposeManifestForTests by tasks.registering { val outputFile = layout.buildDirectory.file("resources/main/META-INF/MANIFEST.MF") outputs.file(outputFile).withPropertyName("manifestFile") 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 44789e2..9da4c9d 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 44789e2047551235c130f784442684bb02c5663d +Subproject commit 9da4c9de0aa7f95ae4ade7f23b6ec8ebf254a58b diff --git a/gradle.properties b/gradle.properties index 437f19a..93bcb69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,27 +1,25 @@ -# -# Copyright 2024, 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. -# +# Allow Gradle to auto-detect installed JDKs. +org.gradle.java.installations.auto-detect=true -org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC +# Optional: Allow Gradle to download JDKs if needed. +org.gradle.java.installations.auto-download=true + +# Use parallel builds for better performance. +org.gradle.parallel=true +#org.gradle.caching=true + +# Dokka plugin eats more memory than usual. Therefore, all builds should have enough. +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC + +# suppress inspection "UnusedProperty" +# The below property enables generation of XML reports for tests. +# If this flag is false, it causes `KotlinTestReport` task to replace these reports with one +# consolidated HTML report. +# See: https://github.com/JetBrains/kotlin/blob/9fd05632f0d7f074b6544527e73eb0fbb2fb1ef2/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/testing/internal/KotlinTestReport.kt#L20 +# See: https://youtrack.jetbrains.com/issue/KT-32608 +kotlin.tests.individualTaskReports=true + +# Enables the Dokka migration mode from v1 to v2. +# For details please see: +# https://kotlinlang.org/docs/dokka-migration.html#enable-migration-helpers +org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index afba109285af78dbd2a1d187e33ac4f87c76e392..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch literal 43764 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�Zy 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5c40527..ff23a68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,16 +200,20 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/io/spine/reflect/package-info.java b/src/main/java/io/spine/reflect/package-info.java index 3268f9a..c41e240 100644 --- a/src/main/java/io/spine/reflect/package-info.java +++ b/src/main/java/io/spine/reflect/package-info.java @@ -28,9 +28,9 @@ * This package provides utilities for working with Java Reflection. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package io.spine.reflect; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 944ea12..7dc3192 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -44,7 +44,7 @@ internal class StackWalkerStackGetter : StackGetter { init { // Due to b/241269335, we check in constructor whether this implementation // crashes in runtime, and `CallerFinder` should catch any `Throwable` caused. - @Suppress("unused") + @Suppress("UNUSED_VARIABLE", "unused") val unused = callerOf(StackWalkerStackGetter::class.java, 0) } diff --git a/src/test/java/given/reflect/annotation/package-info.java b/src/test/java/given/reflect/annotation/package-info.java index 71e4de5..3058af0 100644 --- a/src/test/java/given/reflect/annotation/package-info.java +++ b/src/test/java/given/reflect/annotation/package-info.java @@ -28,9 +28,9 @@ * Provides a test environment annotation for {@link io.spine.reflect.PackageInfoTest}. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package given.reflect.annotation; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; diff --git a/src/test/java/io/spine/reflect/given/package-info.java b/src/test/java/io/spine/reflect/given/package-info.java index 2589e9d..c24d980 100644 --- a/src/test/java/io/spine/reflect/given/package-info.java +++ b/src/test/java/io/spine/reflect/given/package-info.java @@ -28,9 +28,9 @@ * The test environment classes for Spine reflection utils. */ @CheckReturnValue -@ParametersAreNonnullByDefault +@NullMarked package io.spine.reflect.given; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.ParametersAreNonnullByDefault; +import org.jspecify.annotations.NullMarked; From a71ebd6df141df99edba62c33f8d1ce4b165dedf Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:29:56 +0100 Subject: [PATCH 31/51] Update dependency reports --- dependencies.md | 534 +++++++++++++++++++++++------------------------- pom.xml | 118 ++++++----- 2 files changed, 328 insertions(+), 324 deletions(-) diff --git a/dependencies.md b/dependencies.md index 5ad2185..0efea16 100644 --- a/dependencies.md +++ b/dependencies.md @@ -7,19 +7,19 @@ * **Project URL:** [http://findbugs.sourceforge.net/](http://findbugs.sourceforge.net/) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.10.1. - * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.13.0. + * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.1. +1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.3. * **Project URL:** [https://github.com/google/guava/](https://github.com/google/guava/) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava. **Version** : 32.1.3-jre. +1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.4.8-jre. * **Project URL:** [https://github.com/google/guava](https://github.com/google/guava) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -30,101 +30,82 @@ * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.31.0. * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.31.0. * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.31.0. + * **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** : org.checkerframework. **Name** : checker-qual. **Version** : 3.40.0. - * **Project URL:** [https://checkerframework.org/](https://checkerframework.org/) - * **License:** [The MIT License](http://opensource.org/licenses/MIT) - -1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 24.0.1. +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.0.2. * **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** : 1.9.23. +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** : 1.9.23. +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** : 1.9.23. +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** : 1.9.23. - * **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** : kotlinx-coroutines-bom. **Version** : 1.10.2. + * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.6. - * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) - * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) - * **License:** [The Apache Software License, Version 2.0](http://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) ## Compile, tests, and tooling -1. **Group** : aopalliance. **Name** : aopalliance. **Version** : 1.0. - * **Project URL:** [http://aopalliance.sourceforge.net](http://aopalliance.sourceforge.net) - * **License:** Public Domain - -1. **Group** : com.beust. **Name** : jcommander. **Version** : 1.48. - * **Project URL:** [http://beust.com/jcommander](http://beust.com/jcommander) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : com.beust. **Name** : jcommander. **Version** : 1.82. - * **Project URL:** [https://jcommander.org](https://jcommander.org) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.15.3. +1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.12.7. * **Project URL:** [https://github.com/FasterXML/jackson-bom](https://github.com/FasterXML/jackson-bom) * **License:** [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** : com.fasterxml.jackson.core. **Name** : jackson-annotations. **Version** : 2.15.3. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-annotations. **Version** : 2.12.7. + * **Project URL:** [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.15.3. +1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.12.7. * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://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** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.15.3. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) +1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.12.7.1. + * **Project URL:** [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://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** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-xml. **Version** : 2.15.3. +1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-xml. **Version** : 2.12.7. * **Project URL:** [https://github.com/FasterXML/jackson-dataformat-xml](https://github.com/FasterXML/jackson-dataformat-xml) * **License:** [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** : com.fasterxml.jackson.module. **Name** : jackson-module-kotlin. **Version** : 2.15.3. +1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-jaxb-annotations. **Version** : 2.12.7. + * **Project URL:** [https://github.com/FasterXML/jackson-modules-base](https://github.com/FasterXML/jackson-modules-base) + * **License:** [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** : com.fasterxml.jackson.module. **Name** : jackson-module-kotlin. **Version** : 2.12.7. * **Project URL:** [https://github.com/FasterXML/jackson-module-kotlin](https://github.com/FasterXML/jackson-module-kotlin) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://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** : com.fasterxml.woodstox. **Name** : woodstox-core. **Version** : 6.5.1. +1. **Group** : com.fasterxml.woodstox. **Name** : woodstox-core. **Version** : 6.2.4. * **Project URL:** [https://github.com/FasterXML/woodstox](https://github.com/FasterXML/woodstox) * **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** : com.github.ajalt. **Name** : colormath. **Version** : 1.2.0. - * **Project URL:** [https://github.com/ajalt/colormath](https://github.com/ajalt/colormath) - * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : com.github.ajalt. **Name** : mordant. **Version** : 1.2.1. - * **Project URL:** [https://github.com/ajalt/mordant](https://github.com/ajalt/mordant) - * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.github.ben-manes.caffeine. **Name** : caffeine. **Version** : 3.0.5. * **Project URL:** [https://github.com/ben-manes/caffeine](https://github.com/ben-manes/caffeine) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -133,6 +114,10 @@ * **Project URL:** [https://www.github.com/KevinStern/software-and-algorithms](https://www.github.com/KevinStern/software-and-algorithms) * **License:** [MIT License](http://www.opensource.org/licenses/mit-license.php) +1. **Group** : com.github.oowekyala.ooxml. **Name** : nice-xml-messages. **Version** : 3.1. + * **Project URL:** [https://github.com/oowekyala/nice-xml-messages](https://github.com/oowekyala/nice-xml-messages) + * **License:** [MIT License](https://github.com/oowekyala/nice-xml-messages/tree/master/LICENSE) + 1. **Group** : com.google.auto. **Name** : auto-common. **Version** : 1.2.2. * **Project URL:** [https://github.com/google/auto/tree/main/common](https://github.com/google/auto/tree/main/common) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -149,27 +134,27 @@ * **Project URL:** [http://findbugs.sourceforge.net/](http://findbugs.sourceforge.net/) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.10.1. - * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.13.0. + * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_annotation. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_annotation. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotation](https://errorprone.info/error_prone_annotation) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_check_api](https://errorprone.info/error_prone_check_api) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_core. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_core. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_core](https://errorprone.info/error_prone_core) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.errorprone. **Name** : error_prone_type_annotations. **Version** : 2.23.0. +1. **Group** : com.google.errorprone. **Name** : error_prone_type_annotations. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_type_annotations](https://errorprone.info/error_prone_type_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -185,49 +170,49 @@ * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.1. - * **Project URL:** [https://github.com/google/guava/](https://github.com/google/guava/) +1. **Group** : com.google.googlejavaformat. **Name** : google-java-format. **Version** : 1.19.1. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava. **Version** : 32.1.3-jre. +1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.3. + * **Project URL:** [https://github.com/google/guava/](https://github.com/google/guava/) + * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.4.8-jre. * **Project URL:** [https://github.com/google/guava](https://github.com/google/guava) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava-testlib. **Version** : 32.1.3-jre. +1. **Group** : com.google.guava. **Name** : guava-testlib. **Version** : 33.4.8-jre. * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : com.google.guava. **Name** : listenablefuture. **Version** : 9999.0-empty-to-avoid-conflict-with-guava. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.inject. **Name** : guice. **Version** : 5.1.0. - * **Project URL:** [https://github.com/google/guice](https://github.com/google/guice) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.google.j2objc. **Name** : j2objc-annotations. **Version** : 2.8. * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.31.0. * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.31.0. * **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** : 3.25.1. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.31.0. + * **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.truth. **Name** : truth. **Version** : 1.1.5. +1. **Group** : com.google.truth. **Name** : truth. **Version** : 1.4.4. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.truth.extensions. **Name** : truth-java8-extension. **Version** : 1.1.5. +1. **Group** : com.google.truth.extensions. **Name** : truth-java8-extension. **Version** : 1.4.4. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.truth.extensions. **Name** : truth-liteproto-extension. **Version** : 1.1.5. +1. **Group** : com.google.truth.extensions. **Name** : truth-liteproto-extension. **Version** : 1.4.4. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. +1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.4.4. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.12.1. @@ -246,271 +231,225 @@ * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : dev.drewhamilton.poko. **Name** : poko-annotations. **Version** : 0.17.1. + * **Project URL:** [https://github.com/drewhamilton/Poko](https://github.com/drewhamilton/Poko) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : dev.drewhamilton.poko. **Name** : poko-annotations-jvm. **Version** : 0.17.1. + * **Project URL:** [https://github.com/drewhamilton/Poko](https://github.com/drewhamilton/Poko) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.7.4. * **Project URL:** [https://picocli.info](https://picocli.info) * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.github.classgraph. **Name** : classgraph. **Version** : 4.8.162. - * **Project URL:** [https://github.com/classgraph/classgraph](https://github.com/classgraph/classgraph) - * **License:** [The MIT License (MIT)](http://opensource.org/licenses/MIT) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.github.detekt.sarif4k. **Name** : sarif4k. **Version** : 0.4.0. +1. **Group** : io.github.detekt.sarif4k. **Name** : sarif4k. **Version** : 0.6.0. * **Project URL:** [https://detekt.github.io/detekt](https://detekt.github.io/detekt) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.github.detekt.sarif4k. **Name** : sarif4k-jvm. **Version** : 0.4.0. +1. **Group** : io.github.detekt.sarif4k. **Name** : sarif4k-jvm. **Version** : 0.6.0. * **Project URL:** [https://detekt.github.io/detekt](https://detekt.github.io/detekt) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.github.eisop. **Name** : dataflow-errorprone. **Version** : 3.34.0-eisop1. +1. **Group** : io.github.eisop. **Name** : dataflow-errorprone. **Version** : 3.41.0-eisop1. * **Project URL:** [https://eisop.github.io/](https://eisop.github.io/) * **License:** [GNU General Public License, version 2 (GPL2), with the classpath exception](http://www.gnu.org/software/classpath/license.html) 1. **Group** : io.github.java-diff-utils. **Name** : java-diff-utils. **Version** : 4.12. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-api. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-api. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-cli. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-cli. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-core. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-core. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-metrics. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-metrics. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-parser. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-parser. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-psi-utils. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-psi-utils. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-html. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-html. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-md. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-md. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-sarif. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-sarif. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-txt. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-txt. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-xml. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-report-xml. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-complexity. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-complexity. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-coroutines. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-coroutines. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-documentation. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-documentation. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-empty. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-empty. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-errorprone. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-errorprone. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-exceptions. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-exceptions. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-naming. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-naming. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-performance. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-performance. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-style. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-rules-style. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-tooling. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-tooling. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-utils. **Version** : 1.23.0. +1. **Group** : io.gitlab.arturbosch.detekt. **Name** : detekt-utils. **Version** : 1.23.8. * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.kotest. **Name** : kotest-assertions-api. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-assertions-api-jvm. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-assertions-core. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-assertions-core-jvm. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-assertions-shared. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-assertions-shared-jvm. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-common. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-common-jvm. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-extensions. **Version** : 5.8.0. - * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) - * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) - -1. **Group** : io.kotest. **Name** : kotest-extensions-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-api. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-api. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-api-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-api-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-core. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-concurrency. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-core-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-concurrency-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-shared. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-datatest. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-assertions-shared-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-datatest-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-common. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-discovery. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-common-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-discovery-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-framework-api. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-engine. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-framework-api-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-framework-engine-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-framework-datatest. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-runner-junit5-jvm. **Version** : 5.8.0. +1. **Group** : io.kotest. **Name** : kotest-framework-datatest-jvm. **Version** : 5.9.1. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : it.unimi.dsi. **Name** : fastutil-core. **Version** : 8.5.12. - * **Project URL:** [http://fastutil.di.unimi.it/](http://fastutil.di.unimi.it/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) +1. **Group** : jakarta.activation. **Name** : jakarta.activation-api. **Version** : 1.2.1. + * **Project URL:** [https://www.eclipse.org](https://www.eclipse.org) + * **License:** [EDL 1.0](http://www.eclipse.org/org/documents/edl-v10.php) + * **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** : jakarta.xml.bind. **Name** : jakarta.xml.bind-api. **Version** : 2.3.2. + * **Project URL:** [https://www.eclipse.org](https://www.eclipse.org) + * **License:** [Eclipse Distribution License - v 1.0](http://www.eclipse.org/org/documents/edl-v10.php) + * **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.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) -1. **Group** : junit. **Name** : junit. **Version** : 4.13.1. +1. **Group** : junit. **Name** : junit. **Version** : 4.13.2. * **Project URL:** [http://junit.org](http://junit.org) * **License:** [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) -1. **Group** : net.bytebuddy. **Name** : byte-buddy. **Version** : 1.10.9. - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : net.bytebuddy. **Name** : byte-buddy-agent. **Version** : 1.10.9. - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : net.java.dev.jna. **Name** : jna. **Version** : 5.6.0. - * **Project URL:** [https://github.com/java-native-access/jna](https://github.com/java-native-access/jna) - * **License:** [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) - -1. **Group** : net.java.dev.jna. **Name** : jna. **Version** : 5.9.0. - * **Project URL:** [https://github.com/java-native-access/jna](https://github.com/java-native-access/jna) - * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [LGPL-2.1-or-later](https://www.gnu.org/licenses/old-licenses/lgpl-2.1) - -1. **Group** : net.java.dev.jna. **Name** : jna-platform. **Version** : 5.9.0. - * **Project URL:** [https://github.com/java-native-access/jna](https://github.com/java-native-access/jna) - * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [LGPL-2.1-or-later](https://www.gnu.org/licenses/old-licenses/lgpl-2.1) - 1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 12.2. * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) -1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. +1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 12.5. + * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) + * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) + +1. **Group** : net.sourceforge.pmd. **Name** : pmd-ant. **Version** : 7.12.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) -1. **Group** : net.sourceforge.pmd. **Name** : pmd-java. **Version** : 6.55.0. +1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 7.12.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) -1. **Group** : net.sourceforge.saxon. **Name** : saxon. **Version** : 9.1.0.8. - * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) - * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) +1. **Group** : net.sourceforge.pmd. **Name** : pmd-java. **Version** : 7.12.0. + * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. +1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.9.3. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) -1. **Group** : org.apache.commons. **Name** : commons-lang3. **Version** : 3.8.1. - * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.apache.commons. **Name** : commons-lang3. **Version** : 3.17.0. + * **Project URL:** [https://commons.apache.org/proper/commons-lang/](https://commons.apache.org/proper/commons-lang/) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -543,24 +482,21 @@ * **Project URL:** [https://freemarker.apache.org/](https://freemarker.apache.org/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.hamcrest. **Name** : hamcrest. **Version** : 2.2. +1. **Group** : org.hamcrest. **Name** : hamcrest. **Version** : 3.0. * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) - * **License:** [BSD License 3](http://opensource.org/licenses/BSD-3-Clause) + * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.hamcrest. **Name** : hamcrest-core. **Version** : 2.2. +1. **Group** : org.hamcrest. **Name** : hamcrest-core. **Version** : 3.0. * **Project URL:** [http://hamcrest.org/JavaHamcrest/](http://hamcrest.org/JavaHamcrest/) - * **License:** [BSD License 3](http://opensource.org/licenses/BSD-3-Clause) + * **License:** [BSD-3-Clause](https://raw.githubusercontent.com/hamcrest/JavaHamcrest/master/LICENSE) -1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.12. +1. **Group** : org.jacoco. **Name** : org.jacoco.agent. **Version** : 0.8.13. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.ant. **Version** : 0.8.12. +1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.13. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.jacoco. **Name** : org.jacoco.core. **Version** : 0.8.12. - * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) - -1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.12. +1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.13. * **License:** [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) 1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. @@ -569,35 +505,43 @@ * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) -1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 24.0.1. +1. **Group** : org.jcommander. **Name** : jcommander. **Version** : 1.85. + * **Project URL:** [https://jcommander.org](https://jcommander.org) + * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.0.2. * **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. **Name** : markdown. **Version** : 0.5.2. +1. **Group** : org.jetbrains. **Name** : markdown. **Version** : 0.7.3. * **Project URL:** [https://github.com/JetBrains/markdown](https://github.com/JetBrains/markdown) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains. **Name** : markdown-jvm. **Version** : 0.5.2. +1. **Group** : org.jetbrains. **Name** : markdown-jvm. **Version** : 0.7.3. * **Project URL:** [https://github.com/JetBrains/markdown](https://github.com/JetBrains/markdown) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.dokka. **Name** : analysis-kotlin-descriptors. **Version** : 1.9.20. +1. **Group** : org.jetbrains.dokka. **Name** : analysis-kotlin-descriptors. **Version** : 2.0.0. + * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.dokka. **Name** : analysis-markdown. **Version** : 2.0.0. * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.dokka. **Name** : analysis-markdown. **Version** : 1.9.20. +1. **Group** : org.jetbrains.dokka. **Name** : dokka-base. **Version** : 2.0.0. * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.dokka. **Name** : dokka-base. **Version** : 1.9.20. +1. **Group** : org.jetbrains.dokka. **Name** : dokka-core. **Version** : 2.0.0. * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.dokka. **Name** : dokka-core. **Version** : 1.9.20. +1. **Group** : org.jetbrains.dokka. **Name** : kotlin-as-java-plugin. **Version** : 2.0.0. * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.dokka. **Name** : kotlin-as-java-plugin. **Version** : 1.9.20. +1. **Group** : org.jetbrains.dokka. **Name** : templating-plugin. **Version** : 2.0.0. * **Project URL:** [https://github.com/Kotlin/dokka](https://github.com/Kotlin/dokka) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -605,113 +549,141 @@ * **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-compiler-embeddable. **Version** : 1.8.21. +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-compiler-embeddable. **Version** : 1.8.22. +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) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **Version** : 1.8.21. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-embeddable. **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-daemon-embeddable. **Version** : 1.8.22. +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-klib-commonizer-embeddable. **Version** : 1.8.22. +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-reflect. **Version** : 1.9.23. +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) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 1.8.21. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **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-script-runtime. **Version** : 1.8.22. +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-scripting-common. **Version** : 1.8.22. +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-scripting-compiler-embeddable. **Version** : 1.8.22. +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-scripting-compiler-impl-embeddable. **Version** : 1.8.22. +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-scripting-jvm. **Version** : 1.8.22. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **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. **Version** : 1.9.23. +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-stdlib-common. **Version** : 1.9.23. +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-stdlib-jdk7. **Version** : 1.9.23. +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-stdlib-jdk8. **Version** : 1.9.23. +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.kotlinx. **Name** : atomicfu. **Version** : 0.20.2. - * **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.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.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.7.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.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.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.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.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.7.0. - * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) +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-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-jdk7. **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.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.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.kotlinx. **Name** : atomicfu. **Version** : 0.23.1. + * **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-core. **Version** : 1.7.3. +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-jvm. **Version** : 1.7.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.7.3. +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-debug. **Version** : 1.7.0. +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-jdk8. **Version** : 1.7.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-coroutines-test. **Version** : 1.7.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-test-jvm. **Version** : 1.7.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-html-jvm. **Version** : 0.8.1. * **Project URL:** [https://github.com/Kotlin/kotlinx.html](https://github.com/Kotlin/kotlinx.html) @@ -741,40 +713,40 @@ * **Project URL:** [https://jsoup.org/](https://jsoup.org/) * **License:** [The MIT License](https://jsoup.org/license) -1. **Group** : org.junit. **Name** : junit-bom. **Version** : 5.10.0. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) - * **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html) +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-pioneer. **Name** : junit-pioneer. **Version** : 2.0.1. - * **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/) +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.10.0. - * **Project URL:** [https://junit.org/junit5/](https://junit.org/junit5/) +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-engine. **Version** : 5.10.0. - * **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-params. **Version** : 5.10.0. - * **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.platform. **Name** : junit-platform-commons. **Version** : 1.10.0. - * **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-engine. **Version** : 1.10.0. - * **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-launcher. **Version** : 1.10.0. - * **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-suite-api. **Version** : 1.10.0. - * **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. @@ -796,7 +768,11 @@ * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.pcollections. **Name** : pcollections. **Version** : 3.1.4. +1. **Group** : org.pcollections. **Name** : pcollections. **Version** : 4.0.1. + * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) + * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) + +1. **Group** : org.pcollections. **Name** : pcollections. **Version** : 4.0.2. * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) @@ -805,7 +781,11 @@ * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [WTFPL](http://www.wtfpl.net/) -1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. +1. **Group** : org.slf4j. **Name** : jul-to-slf4j. **Version** : 1.7.36. + * **Project URL:** [http://www.slf4j.org](http://www.slf4j.org) + * **License:** [MIT License](http://www.opensource.org/licenses/mit-license.php) + +1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.7. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -813,7 +793,11 @@ * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 5.2.2. + * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) + * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) + The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jul 24 23:26:39 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 **Fri Jul 25 10:26:33 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 1018411..aa2f0e8 100644 --- a/pom.xml +++ b/pom.xml @@ -26,120 +26,126 @@ all modules and does not describe the project structure per-subproject. com.google.guava guava - 32.1.3-jre + 33.4.8-jre compile com.google.protobuf protobuf-java - 3.25.1 + 4.31.0 compile com.google.protobuf protobuf-java-util - 3.25.1 + 4.31.0 compile com.google.protobuf protobuf-kotlin - 3.25.1 + 4.31.0 compile - io.spine - spine-logging - 2.0.0-SNAPSHOT.240 + org.jetbrains.kotlin + kotlin-bom + 2.1.21 compile org.jetbrains.kotlin kotlin-reflect - 1.9.23 + 2.1.21 compile org.jetbrains.kotlin - kotlin-stdlib-jdk8 - 1.9.23 + kotlin-stdlib + 2.1.21 compile - com.google.guava - guava-testlib - 32.1.3-jre - test + org.jetbrains.kotlinx + kotlinx-coroutines-bom + 1.10.2 + compile - io.kotest - kotest-framework-datatest - 5.8.0 + org.jspecify + jspecify + 1.0.0 + compile + + + com.google.guava + guava-testlib + 33.4.8-jre test io.kotest - kotest-framework-engine - 5.8.0 + kotest-assertions-core + 5.9.1 test io.kotest - kotest-runner-junit5-jvm - 5.8.0 + kotest-framework-datatest + 5.9.1 test io.spine.tools spine-testlib - 2.0.0-SNAPSHOT.184 + 2.0.0-SNAPSHOT.202 test - org.apiguardian - apiguardian-api - 1.1.2 + org.junit + junit-bom + 5.13.2 test org.junit-pioneer junit-pioneer - 2.0.1 + 2.3.0 test org.junit.jupiter junit-jupiter-api - 5.10.0 + 5.13.2 test org.junit.jupiter junit-jupiter-engine - 5.10.0 + 5.13.2 test org.junit.jupiter junit-jupiter-params - 5.10.0 + 5.13.2 test com.google.errorprone error_prone_annotations - 2.23.0 + 2.36.0 provided com.google.errorprone error_prone_core - 2.23.0 + 2.36.0 com.google.errorprone error_prone_type_annotations - 2.23.0 + 2.36.0 provided @@ -155,23 +161,22 @@ all modules and does not describe the project structure per-subproject. io.gitlab.arturbosch.detekt detekt-cli - 1.23.0 + 1.23.8 io.spine.tools spine-dokka-extensions - 2.0.0-SNAPSHOT.4 + 2.0.0-SNAPSHOT.7 - javax.annotation - javax.annotation-api - 1.3.2 - provided + net.sourceforge.pmd + pmd-ant + 7.12.0 net.sourceforge.pmd pmd-java - 6.55.0 + 7.12.0 org.checkerframework @@ -182,47 +187,62 @@ all modules and does not describe the project structure per-subproject. org.jacoco org.jacoco.agent - 0.8.12 + 0.8.13 org.jacoco - org.jacoco.ant - 0.8.12 + org.jacoco.report + 0.8.13 + + + org.jetbrains.dokka + all-modules-page-plugin + 2.0.0 org.jetbrains.dokka analysis-kotlin-descriptors - 1.9.20 + 2.0.0 org.jetbrains.dokka dokka-base - 1.9.20 + 2.0.0 org.jetbrains.dokka dokka-core - 1.9.20 + 2.0.0 org.jetbrains.dokka kotlin-as-java-plugin - 1.9.20 + 2.0.0 + + + org.jetbrains.dokka + templating-plugin + 2.0.0 + + + org.jetbrains.kotlin + kotlin-build-tools-impl + 2.1.21 org.jetbrains.kotlin kotlin-compiler-embeddable - 1.8.22 + 2.1.21 org.jetbrains.kotlin kotlin-klib-commonizer-embeddable - 1.8.22 + 2.1.21 org.jetbrains.kotlin kotlin-scripting-compiler-embeddable - 1.8.22 + 2.1.21 From 558759e75beb8563dfdb5384e38f7177a58335d3 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:36:27 +0100 Subject: [PATCH 32/51] Add missing dependency objects --- .../io/spine/dependency/build/JSpecify.kt | 40 ++++++++++++++++++ .../dependency/build/PluginPublishPlugin.kt | 42 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/build/JSpecify.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/JSpecify.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/JSpecify.kt new file mode 100644 index 0000000..d884ffb --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/JSpecify.kt @@ -0,0 +1,40 @@ +/* + * 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.dependency.build + +/** + * An artifact of well-specified annotations to power static analysis + * checks and JVM language interop. Developed by consensus of the partner + * organizations listed at [the project site](https://jspecify.org). + * + * @see JSpecify at GitHub + */ +@Suppress("ConstPropertyName") +object JSpecify { + const val version = "1.0.0" + const val annotations = "org.jspecify:jspecify:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt new file mode 100644 index 0000000..12c28f8 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt @@ -0,0 +1,42 @@ +/* + * 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("unused") + +package io.spine.dependency.build + +/** + * The Gradle plugin for publishing Gradle plugins to the Gradle Plugin Portal. + * + * @see + * The plugin page at the Portal + * @see Publishing Rules + */ +@Suppress("ConstPropertyName") +object PluginPublishPlugin { + const val version = "1.3.1" + const val id = "com.gradle.plugin-publish" +} From cee97ef2fbad1fb313a2ef8bdc238821466bbe37 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:36:34 +0100 Subject: [PATCH 33/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 0efea16..7f66544 100644 --- a/dependencies.md +++ b/dependencies.md @@ -800,4 +800,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jul 25 10:26:33 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 **Fri Jul 25 10:33:42 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 From c66f1613473f76ee571f64063a99f8a2f2bd3d97 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:38:19 +0100 Subject: [PATCH 34/51] Bring latest `.gitignore` from ToolBase --- .gitignore | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index dfdba85..87cd90a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ # -# Copyright 2022, 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 @@ -31,8 +31,15 @@ # # Therefore, instructions below are superset of instructions required for all the projects. +# `jenv` local configuration. .java-version +# Internal tool directories. +.fleet/ + +# Kotlin temp directories. +**/.kotlin/ + # IntelliJ IDEA modules and interim config files. *.iml .idea/*.xml @@ -44,20 +51,35 @@ # Do not ignore the following IDEA settings !.idea/misc.xml -!.idea/kotlinc.xml !.idea/codeStyleSettings.xml !.idea/codeStyles/ !.idea/copyright/ +# Ignore IDEA config files under `tests` +/tests/.idea/** + # Gradle interim configs **/.gradle/** +# Temp directory for Gradle TestKit runners +**/.gradle-test-kit/** + +# Integration test log files +/tests/_out/** + # Generated source code **/generated/** +**/*.pb.dart +**/*.pbenum.dart +**/*.pbserver.dart +**/*.pbjson.dart + +# Generated source code with custom path under `tests` +/tests/**/proto-gen/** # Gradle build files **/build/** -!**/src/**/build/ +!**/src/**/build/** # Build files produced by the IDE **/out/** @@ -77,9 +99,6 @@ gradle-app.setting # Spine internal directory for storing intermediate artifacts **/.spine/** -# Spine model compiler auto-generated resources -/tools/gradle-plugins/model-compiler/src/main/resources/spine-protoc.gradle - # Login details to Maven repository. # Each workstation should have developer's login defined in this file. credentials.tar @@ -106,3 +125,5 @@ pubspec.lock # Ignore the `tmp` directory used for building dependant repositories. /tmp + +.gradle-test-kit/ From 7a7864eb8f1c3a2c9142fb140c41d179e1f24473 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:45:00 +0100 Subject: [PATCH 35/51] Bump version -> `2.0.0-SNAPSHOT.200` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 30024af..b0dc733 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.192") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.200") From 0d387aaaa8294b8684dc03f8bc0faa3f25506b95 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:45:10 +0100 Subject: [PATCH 36/51] Update dependency reports --- dependencies.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencies.md b/dependencies.md index 7f66544..103f883 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.192` +# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.200` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -800,4 +800,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jul 25 10:33:42 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 **Fri Jul 25 10:44:48 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 aa2f0e8..9a53719 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine reflect -2.0.0-SNAPSHOT.192 +2.0.0-SNAPSHOT.200 2015 From a88dbb6eff5a99785b007da5a7a57126f524271f Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:52:39 +0100 Subject: [PATCH 37/51] Improve parameter description Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../io/spine/reflect/StackWalkerStackGetter.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 7dc3192..30800b6 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -100,9 +100,16 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { /** * Determines whether the given class name matches the target class name or its companion object. * - * @param className the name of the class to check. Can be `null`. - * @param targetClassName the name of the target class to match against. - * @return `true` if the class name matches the target class name or its companion object, `false` otherwise. + * This function checks if the provided `className` matches the `targetClassName` directly or + * corresponds to the companion object of the target class. In Kotlin, companion objects are + * represented with a `$Companion` suffix in their class name. + * + * @param className the name of the class to check. Can be `null`. Typically, this is the name + * of a class obtained from a stack frame or reflection. + * @param targetClassName the name of the target class to match against. This is the fully + * qualified name of the class being searched for. + * @return `true` if the `className` matches the `targetClassName` or its companion object, + * `false` otherwise. */ internal fun isTargetClass(className: String?, targetClassName: String): Boolean = (className == targetClassName || className == "$targetClassName\$Companion") From a3caf6ca82bed494659db558ac77de137e0b1789 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:53:23 +0100 Subject: [PATCH 38/51] Add the constant for `companion object` class name Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 30800b6..2ac0088 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -112,4 +112,4 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { * `false` otherwise. */ internal fun isTargetClass(className: String?, targetClassName: String): Boolean = - (className == targetClassName || className == "$targetClassName\$Companion") + (className == targetClassName || className == "$targetClassName$COMPANION_SUFFIX") From 207099679454d2896a93b258865d77d1d2849b11 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:56:45 +0100 Subject: [PATCH 39/51] Add constant Also: * Improve the layout of parameter docs. --- .../io/spine/reflect/StackWalkerStackGetter.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 2ac0088..158fa96 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -73,7 +73,6 @@ internal class StackWalkerStackGetter : StackGetter { } companion object { - private val STACK_WALKER: StackWalker = StackWalker.getInstance(SHOW_REFLECT_FRAMES) private fun filterStackTraceAfterTarget( @@ -96,6 +95,10 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { isTargetClass(it.className, target.name) } +/** + * A suffix used to identify companion objects in Kotlin in a fully-qualified class name. + */ +private const val COMPANION_SUFFIX = "\$Companion" /** * Determines whether the given class name matches the target class name or its companion object. @@ -104,10 +107,10 @@ private fun isTargetClass(target: Class<*>): (StackFrame) -> Boolean = { * corresponds to the companion object of the target class. In Kotlin, companion objects are * represented with a `$Companion` suffix in their class name. * - * @param className the name of the class to check. Can be `null`. Typically, this is the name - * of a class obtained from a stack frame or reflection. - * @param targetClassName the name of the target class to match against. This is the fully - * qualified name of the class being searched for. + * @param className The name of the class to check. Can be `null`. + * Typically, this is the name of a class obtained from a stack frame or reflection. + * @param targetClassName The name of the target class to match against. + * This is the fully qualified name of the class being searched for. * @return `true` if the `className` matches the `targetClassName` or its companion object, * `false` otherwise. */ From add41b466d8e64e288cedcefbd0307bf475e814c Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:57:01 +0100 Subject: [PATCH 40/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 103f883..a2fccf4 100644 --- a/dependencies.md +++ b/dependencies.md @@ -800,4 +800,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jul 25 10:44:48 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 **Fri Jul 25 10:56:53 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 From 4fb3d49381275d262b41405c21b9658f2b2c8cea Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:57:34 +0100 Subject: [PATCH 41/51] Optimise imports --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 158fa96..923f3f3 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -30,8 +30,6 @@ import java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES import java.lang.StackWalker.StackFrame import java.util.stream.Stream import kotlin.Long.Companion.MAX_VALUE -import kotlin.collections.toTypedArray -import kotlin.streams.toList /** * StackWalker based implementation of the [StackGetter] interface. From 30b5976ede83d7d70c118db91ecaef111a5b324a Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:58:05 +0100 Subject: [PATCH 42/51] Improve `@see` reference text --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 923f3f3..0450e74 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -35,7 +35,7 @@ import kotlin.Long.Companion.MAX_VALUE * StackWalker based implementation of the [StackGetter] interface. * * @see - * Original Java code of Google Flogger + * Original Java code of Google Flogger for historical reference. */ internal class StackWalkerStackGetter : StackGetter { From 8075b78baecf0ff6313df8d8240db19c4ad1a538 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 10:58:19 +0100 Subject: [PATCH 43/51] Improve doc grammar --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 0450e74..c3476fa 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -40,7 +40,7 @@ import kotlin.Long.Companion.MAX_VALUE internal class StackWalkerStackGetter : StackGetter { init { - // Due to b/241269335, we check in constructor whether this implementation + // Due to b/241269335, we check in the constructor whether this implementation // crashes in runtime, and `CallerFinder` should catch any `Throwable` caused. @Suppress("UNUSED_VARIABLE", "unused") val unused = callerOf(StackWalkerStackGetter::class.java, 0) From eaee90ba65dfc5d992aa2346710bbdcb5755e58e Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 11:52:56 +0100 Subject: [PATCH 44/51] Use `AtomicReference` Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt index f75da72..8feb76d 100644 --- a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt +++ b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt @@ -39,11 +39,10 @@ internal class ClassWithCompanion { } companion object { - var caller: StackTraceElement? = null - private set + private val caller = AtomicReference() fun findCaller(stackGetter: StackGetter) { - caller = stackGetter.callerOf(ClassWithCompanion::class.java, 0) + caller.set(stackGetter.callerOf(ClassWithCompanion::class.java, 0)) } } } From ed6b6b70db912a8ea74b7a8e497356e5fc65ece6 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 11:58:13 +0100 Subject: [PATCH 45/51] Use `AtomicReference` for shared state --- .../kotlin/io/spine/reflect/AbstractStackGetterSpec.kt | 6 ++++-- .../kotlin/io/spine/reflect/given/ClassWithCompanion.kt | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index 087e31a..a26a17d 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -32,6 +32,7 @@ import io.spine.reflect.given.ClassWithCompanion import io.spine.reflect.given.CallingTheClassWithCompanion import io.spine.reflect.given.LoggerCode import io.spine.reflect.given.UserCode +import kotlin.concurrent.atomics.ExperimentalAtomicApi import org.junit.jupiter.api.Test /** @@ -73,6 +74,7 @@ internal abstract class AbstractStackGetterSpec( * Tests that [StackGetter.callerOf] can find the caller of a companion object method. */ @Test + @OptIn(ExperimentalAtomicApi::class) fun `find caller of companion object`() { val companionLibrary = ClassWithCompanion.Companion val userCode = CallingTheClassWithCompanion(companionLibrary, stackGetter) @@ -80,8 +82,8 @@ internal abstract class AbstractStackGetterSpec( userCode.invokeCompanionFun() companionLibrary.run { - caller shouldNotBe null - caller!!.run { + caller.load() shouldNotBe null + caller.load()!!.run { className shouldBe CallingTheClassWithCompanion::class.java.name methodName shouldBe "invokeCompanionFun" } diff --git a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt index 8feb76d..37d7239 100644 --- a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt +++ b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt @@ -27,6 +27,8 @@ package io.spine.reflect.given import io.spine.reflect.StackGetter +import kotlin.concurrent.atomics.AtomicReference +import kotlin.concurrent.atomics.ExperimentalAtomicApi /** * A test library class with a companion object that uses [StackGetter] functionality. @@ -38,11 +40,14 @@ internal class ClassWithCompanion { return stackGetter.callerOf(ClassWithCompanion::class.java, 0) } + @OptIn(ExperimentalAtomicApi::class) companion object { - private val caller = AtomicReference() + + @OptIn(ExperimentalAtomicApi::class) + val caller = AtomicReference(null) fun findCaller(stackGetter: StackGetter) { - caller.set(stackGetter.callerOf(ClassWithCompanion::class.java, 0)) + caller.store(stackGetter.callerOf(ClassWithCompanion::class.java, 0)) } } } From a5fffd78daf855faeb866b8c82f1007c84957bbc Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 11:58:23 +0100 Subject: [PATCH 46/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index a2fccf4..ba67c06 100644 --- a/dependencies.md +++ b/dependencies.md @@ -800,4 +800,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jul 25 10:56:53 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 **Fri Jul 25 11:57:54 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 From 3f4761081ccda13ff33964e71c57f18cd74004b6 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 12:16:00 +0100 Subject: [PATCH 47/51] Clarify null handling Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index c3476fa..42b5691 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -112,5 +112,9 @@ private const val COMPANION_SUFFIX = "\$Companion" * @return `true` if the `className` matches the `targetClassName` or its companion object, * `false` otherwise. */ -internal fun isTargetClass(className: String?, targetClassName: String): Boolean = - (className == targetClassName || className == "$targetClassName$COMPANION_SUFFIX") +internal fun isTargetClass(className: String?, targetClassName: String): Boolean { + if (className == null) { + return false + } + return (className == targetClassName || className == "$targetClassName$COMPANION_SUFFIX") +} From ec22eda496279010e6935d2e6c0f9d55a477ec9a Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 12:17:48 +0100 Subject: [PATCH 48/51] Improve `null` handling --- .../kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 42b5691..6e5d437 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -112,9 +112,9 @@ private const val COMPANION_SUFFIX = "\$Companion" * @return `true` if the `className` matches the `targetClassName` or its companion object, * `false` otherwise. */ -internal fun isTargetClass(className: String?, targetClassName: String): Boolean { +internal fun isTargetClass(className: String?, targetClassName: String): Boolean = if (className == null) { - return false + false + } else { + (className == targetClassName || className == "$targetClassName$COMPANION_SUFFIX") } - return (className == targetClassName || className == "$targetClassName$COMPANION_SUFFIX") -} From f736cc8fa8a95a841a0548931d09991cde4f1a46 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 12:25:03 +0100 Subject: [PATCH 49/51] Improve parameter docs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 6e5d437..d0babf1 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -107,6 +107,8 @@ private const val COMPANION_SUFFIX = "\$Companion" * * @param className The name of the class to check. Can be `null`. * Typically, this is the name of a class obtained from a stack frame or reflection. + * It may be `null` if the stack frame does not contain a valid class name or if reflection + * fails to retrieve the class name due to certain runtime conditions. * @param targetClassName The name of the target class to match against. * This is the fully qualified name of the class being searched for. * @return `true` if the `className` matches the `targetClassName` or its companion object, From 1923411f37ffef60c5755a53a0392de0157c8e26 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 12:25:23 +0100 Subject: [PATCH 50/51] Remove duplicated annotation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt index 37d7239..6c7a406 100644 --- a/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt +++ b/src/test/kotlin/io/spine/reflect/given/ClassWithCompanion.kt @@ -43,7 +43,6 @@ internal class ClassWithCompanion { @OptIn(ExperimentalAtomicApi::class) companion object { - @OptIn(ExperimentalAtomicApi::class) val caller = AtomicReference(null) fun findCaller(stackGetter: StackGetter) { From 1eccb26ce09b54c48525bb51ecf6c5568007ff78 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Fri, 25 Jul 2025 12:25:58 +0100 Subject: [PATCH 51/51] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index ba67c06..bc855ae 100644 --- a/dependencies.md +++ b/dependencies.md @@ -800,4 +800,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jul 25 11:57:54 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 **Fri Jul 25 12:25:48 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