├── .editorconfig ├── .github ├── actions │ └── setup-gradle-build │ │ └── action.yml └── workflows │ ├── default.yml │ ├── publish_release.yml │ └── publish_snapshots.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── libs.versions.toml ├── plugins │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── PublishingPlugin.kt └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── maven-sympathy ├── api │ └── maven-sympathy.api ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── usefulness │ │ └── mavensympathy │ │ ├── AttachStrategy.kt │ │ ├── BehaviorOnMismatch.kt │ │ ├── MavenSympathyExtension.kt │ │ ├── MavenSympathyPlugin.kt │ │ └── SympathyForMrMavenTask.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── usefulness │ └── mavensympathy │ ├── AllConfigurationsIntegrationTest.kt │ ├── AttachStrategyTest.kt │ ├── MavenPublishIntegrationTest.kt │ ├── PluginsTest.kt │ └── internal │ └── Uitls.kt ├── renovate.json └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | 6 | [*.{kt,kts}] 7 | max_line_length = 140 8 | indent_size = 4 9 | ij_kotlin_allow_trailing_comma = true 10 | ij_kotlin_allow_trailing_comma_on_call_site = true 11 | ktlint_code_style = intellij_idea 12 | ktlint_standard_property-naming = disabled 13 | ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2 14 | ktlint_function_naming_ignore_when_annotated_with = Composable 15 | ktlint_compose_unstable-collections = disabled 16 | 17 | [*.xml] 18 | indent_size = 4 19 | ij_xml_space_inside_empty_tag = true -------------------------------------------------------------------------------- /.github/actions/setup-gradle-build/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Gradle 2 | description: Sets up the environment to run Gradle 3 | 4 | runs: 5 | using: composite 6 | 7 | steps: 8 | - name: Write Gradle build properties to `~/.gradle/gradle.properties` 9 | run: | 10 | mkdir -p ~/.gradle 11 | printf "org.gradle.jvmargs=-Xmx3G -XX:+UseParallelGC\n" >> ~/.gradle/gradle.properties 12 | printf "org.gradle.vfs.watch=false\n" >> ~/.gradle/gradle.properties 13 | shell: bash 14 | 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: 'temurin' 18 | java-version: 21 19 | 20 | - uses: gradle/actions/wrapper-validation@v4 21 | 22 | - uses: gradle/actions/setup-gradle@v4 23 | with: 24 | gradle-home-cache-cleanup: true # https://github.com/gradle/actions/issues/26 25 | -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | name: Build Project 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ ubuntu-latest, windows-latest ] 20 | name: '[${{ matrix.os }}] build plugin' 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - uses: gradle/actions/wrapper-validation@v4 27 | 28 | - uses: ./.github/actions/setup-gradle-build 29 | 30 | - run: ./gradlew assemble 31 | 32 | - run: ./gradlew check 33 | 34 | - run: ./gradlew publishToMavenLocal 35 | 36 | - run: ./gradlew publishPlugins -m 37 | 38 | - run: git diff --exit-code 39 | 40 | - uses: actions/upload-artifact@v4 41 | if: ${{ always() }} 42 | with: 43 | name: test-results-${{ matrix.os }} 44 | path: "${{ github.workspace }}/**/build/reports/tests" 45 | -------------------------------------------------------------------------------- /.github/workflows/publish_release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Project to all Maven repositories 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 17 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 18 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 19 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 20 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - uses: ./.github/actions/setup-gradle-build 27 | 28 | - name: Unwrap GPG key 29 | env: 30 | GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} 31 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} 32 | run: sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'" 33 | 34 | - run: ./gradlew publishAllPublicationsToGithubRepository 35 | 36 | - run: ./gradlew publishAllPublicationsToMavenCentralRepository 37 | 38 | - run: ./gradlew publishPlugins -Pgradle.publish.key=${{ secrets.gradle_publish_key }} -Pgradle.publish.secret=${{ secrets.gradle_publish_secret }} 39 | -------------------------------------------------------------------------------- /.github/workflows/publish_snapshots.yml: -------------------------------------------------------------------------------- 1 | name: Publish Project Snapshot 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | schedule: 8 | - cron: '0 3 * * 1,4' 9 | 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 19 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 20 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 21 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 22 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | 28 | - uses: ./.github/actions/setup-gradle-build 29 | 30 | - run: ./gradlew assemble 31 | 32 | - name: Unwrap GPG key 33 | env: 34 | GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} 35 | SIGNING_SECRET_KEY_RING_FILE: ${{ secrets.SIGNING_SECRET_KEY_RING_FILE }} 36 | run: sudo bash -c "echo '$GPG_KEY_CONTENTS' | base64 -d > '$SIGNING_SECRET_KEY_RING_FILE'" 37 | 38 | - name: Publish to Github Package Registry 39 | run: ./gradlew publishAllPublicationsToGithubRepository 40 | 41 | diffuse: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | fetch-depth: 0 47 | 48 | - uses: ./.github/actions/setup-gradle-build 49 | 50 | - run: ./gradlew assemble -PskipJarVersion 51 | 52 | - name: Upload diffuse base artifact 53 | uses: actions/cache@v4 54 | with: 55 | path: diffuse-base-file 56 | key: diffuse-${{ github.sha }} 57 | 58 | - name: Check size 59 | run: du -h maven-sympathy/build/libs/maven-sympathy.jar 60 | shell: bash 61 | 62 | - name: Copy diffuse base artifact to be picked by cache save 63 | run: cp maven-sympathy/build/libs/maven-sympathy.jar diffuse-base-file 64 | shell: bash 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | *.iws 4 | *.ipr 5 | *.iml 6 | TODO.md 7 | .idea 8 | .gradle 9 | # Gradle files 10 | .gradle/ 11 | build/ 12 | local.properties 13 | .kotlin 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2024 Mateusz Kwieciński 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maven-sympathy 2 | 3 | [![Build Project](https://github.com/usefulness/maven-sympathy/actions/workflows/default.yml/badge.svg?branch=master&event=push)](https://github.com/usefulness/maven-sympathy/actions/workflows/default.yml) 4 | [![Latest Version](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/io/github/usefulness/maven-sympathy/maven-metadata.xml?label=gradle)](https://plugins.gradle.org/plugin/io.github.usefulness.maven-sympathy) 5 | ![Maven Central](https://img.shields.io/maven-central/v/io.github.usefulness/maven-sympathy) 6 | 7 | https://jakewharton.com/nonsensical-maven-is-still-a-gradle-problem/ 8 | 9 | 10 | ### Usage: 11 | ```groovy 12 | plugins { 13 | id("io.github.usefulness.maven-sympathy") version "{{version}}" 14 | } 15 | ``` 16 | 17 |
18 | Version Catalog 19 | 20 | ```toml 21 | usefulness-maven-sympathy = { id = "io.github.usefulness.maven-sympathy", version = "{{version}}" } 22 | ``` 23 |
24 | 25 | From now on, the `sympathyForMrMaven` will run on every `check` task invocation. 26 | 27 | ``` 28 | [compileClasspath] dependency org.jetbrains.kotlin:kotlin-stdlib:1.9.22 version changed: 1.9.22 → 1.9.23 29 | [runtimeClasspath] dependency org.jetbrains.kotlin:kotlin-stdlib:1.9.22 version changed: 1.9.22 → 1.9.23 30 | > Task :sympathyForMrMaven FAILED 31 | 32 | FAILURE: Build failed with an exception. 33 | 34 | * What went wrong: 35 | 36 | Execution failed for task ':sympathyForMrMaven'. 37 | > Declared dependencies were upgraded transitively. See task output above. Please update their versions. 38 | ``` 39 | 40 | #### Advanced configuration 41 | 42 | ###### Customize plugin behavior 43 | 44 | Configurable via `io.github.usefulness.mavensympathy.MavenSympathyExtension` extension. 45 | 46 |
47 | Groovy 48 | 49 | ```groovy 50 | mavenSympathy { 51 | attachStrategy = io.github.usefulness.mavensympathy.AttachStrategy.Default 52 | } 53 | ``` 54 |
55 | 56 |
57 | Kotlin 58 | 59 | ```kotlin 60 | mavenSympathy { 61 | attachStrategy = io.github.usefulness.mavensympathy.AttachStrategy.Default 62 | } 63 | ``` 64 |
65 | 66 | - `attachStrategy` - Defines how the plugin will hook up with the project to listen for version mismatches. Has to be one of: 67 | - `WatchAllResolvableConfigurations` - the plugin will check all resolvable configurations for versions mismatch 68 | - `ExtractFromMavenPublishComponents` - the plugin will only watch configurations attached to SoftwareComponents 69 | The implementation relies on internal gradle APIs and may break in the future Gradle versions. 70 | - `Default` - if `maven-publish` is present, the plugin will behave as `ExtractFromMavenPublishComponents`, if not it will fall back to `WatchAllResolvableConfigurations` behavior. 71 | The behavior is subject to change, but the assumption is it should cover most common setups. 72 | 73 | 74 | ###### Customize task behavior 75 |
76 | Groovy 77 | 78 | ```groovy 79 | tasks.named("sympathyForMrMaven") { 80 | behaviorOnMismatch = BehaviorOnMismatch.Fail 81 | } 82 | ``` 83 |
84 | 85 |
86 | Kotlin 87 | 88 | ```kotlin 89 | tasks.named("sympathyForMrMaven") { 90 | behaviorOnMismatch = BehaviorOnMismatch.Fail 91 | } 92 | ``` 93 |
94 | 95 | `behaviorOnMismatch` - one of `Fail` (prints error logs + fails the build) or `Warn` (only prints error logs) 96 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.starter.config) 3 | alias(libs.plugins.starter.versioning) 4 | alias(libs.plugins.starter.library.kotlin) apply false 5 | } 6 | 7 | commonConfig { 8 | javaVersion JavaVersion.VERSION_1_8 9 | } 10 | 11 | allprojects { 12 | pluginManager.withPlugin("kotlin") { plugin -> 13 | kotlin { 14 | jvmToolchain(libs.versions.java.compilation.get().toInteger()) 15 | } 16 | } 17 | pluginManager.withPlugin("java") { 18 | if (project.hasProperty("skipJarVersion")) { 19 | def projectName = project.name 20 | tasks.named("jar") { 21 | archiveFile.set(layout.buildDirectory.map { it.file("libs/${projectName}.jar") }) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.parallel=true 4 | org.gradle.jvmargs="-XX:+UseParallelGC" -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | java-compilation = "21" 3 | gradle-starter = "0.84.1" 4 | gradle-pluginpublish = "1.3.1" 5 | maven-kotlin = "2.1.21" 6 | maven-junit = "5.13.0" 7 | maven-assertj = "3.27.3" 8 | maven-binarycompatiblity = "0.17.0" 9 | maven-dokka = "2.0.0" 10 | 11 | [libraries] 12 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "maven-junit" } 13 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "maven-junit" } 14 | junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } 15 | assertj-core = { module = "org.assertj:assertj-core", version.ref = "maven-assertj" } 16 | jetbrains-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "maven-dokka" } 17 | 18 | [plugins] 19 | starter-config = { id = "com.starter.config", version.ref = "gradle-starter" } 20 | starter-versioning = { id = "com.starter.versioning", version.ref = "gradle-starter" } 21 | starter-library-kotlin = { id = "com.starter.library.kotlin", version.ref = "gradle-starter" } 22 | gradle-pluginpublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-pluginpublish" } 23 | kotlinx-binarycompatibility = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "maven-binarycompatiblity" } 24 | kotlin-samwithreceiver = { id = "org.jetbrains.kotlin.plugin.sam.with.receiver", version.ref = "maven-kotlin" } 25 | -------------------------------------------------------------------------------- /gradle/plugins/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.dsl.KotlinCompile 3 | 4 | plugins { 5 | id("java-gradle-plugin") 6 | alias(libs.plugins.starter.library.kotlin) 7 | } 8 | 9 | kotlin { 10 | jvmToolchain(libs.versions.java.compilation.get().toInteger()) 11 | } 12 | 13 | def targetJavaVersion = JavaVersion.VERSION_11 14 | tasks.withType(JavaCompile).configureEach { 15 | options.release.set(targetJavaVersion.majorVersion.toInteger()) 16 | } 17 | tasks.withType(KotlinCompile).configureEach { 18 | it.compilerOptions.jvmTarget = JvmTarget.@Companion.fromTarget(targetJavaVersion.toString()) 19 | } 20 | 21 | dependencies { 22 | implementation libs.jetbrains.dokka 23 | } 24 | 25 | gradlePlugin { 26 | plugins { 27 | publishingPlugin { 28 | id = 'com.starter.publishing' 29 | implementationClass = 'PublishingPlugin' 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gradle/plugins/settings.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.api.initialization.resolve.RepositoriesMode 2 | 3 | plugins { 4 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 9 | repositories { 10 | gradlePluginPortal() 11 | } 12 | versionCatalogs { 13 | create("libs") { 14 | from(files("../libs.versions.toml")) 15 | } 16 | } 17 | } 18 | 19 | rootProject.name = "plugins" 20 | -------------------------------------------------------------------------------- /gradle/plugins/src/main/kotlin/PublishingPlugin.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | import org.gradle.api.plugins.ExtensionContainer 4 | import org.gradle.api.plugins.JavaPluginExtension 5 | import org.gradle.api.publish.PublishingExtension 6 | import org.gradle.api.publish.maven.MavenPublication 7 | import org.gradle.jvm.tasks.Jar 8 | import org.gradle.language.jvm.tasks.ProcessResources 9 | import org.gradle.plugin.devel.GradlePluginDevelopmentExtension 10 | import org.gradle.plugins.signing.SigningExtension 11 | import org.jetbrains.dokka.gradle.DokkaTask 12 | 13 | class PublishingPlugin : Plugin { 14 | 15 | override fun apply(target: Project) = with(target) { 16 | pluginManager.apply("maven-publish") 17 | if (findConfig("SIGNING_PASSWORD").isNotEmpty()) { 18 | pluginManager.apply("signing") 19 | } 20 | 21 | extensions.configure { 22 | with(repositories) { 23 | maven { maven -> 24 | maven.name = "github" 25 | maven.setUrl("https://maven.pkg.github.com/usefulness/maven-sympathy") 26 | with(maven.credentials) { 27 | username = "usefulness" 28 | password = findConfig("GITHUB_TOKEN") 29 | } 30 | } 31 | maven { maven -> 32 | maven.name = "mavenCentral" 33 | maven.setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 34 | maven.mavenContent { it.releasesOnly() } 35 | with(maven.credentials) { 36 | username = findConfig("OSSRH_USERNAME") 37 | password = findConfig("OSSRH_PASSWORD") 38 | } 39 | } 40 | } 41 | } 42 | pluginManager.withPlugin("com.gradle.plugin-publish") { 43 | extensions.configure("gradlePlugin") { gradlePlugin -> 44 | gradlePlugin.apply { 45 | website.set("https://github.com/usefulness/maven-sympathy") 46 | vcsUrl.set("https://github.com/usefulness/maven-sympathy") 47 | } 48 | } 49 | } 50 | 51 | pluginManager.withPlugin("signing") { 52 | with(extensions.extraProperties) { 53 | set("signing.keyId", findConfig("SIGNING_KEY_ID")) 54 | set("signing.password", findConfig("SIGNING_PASSWORD")) 55 | set("signing.secretKeyRingFile", findConfig("SIGNING_SECRET_KEY_RING_FILE")) 56 | } 57 | 58 | extensions.configure("signing") { signing -> 59 | signing.sign(extensions.getByType(PublishingExtension::class.java).publications) 60 | } 61 | } 62 | 63 | pluginManager.withPlugin("java") { 64 | extensions.configure { 65 | withSourcesJar() 66 | withJavadocJar() 67 | } 68 | 69 | tasks.named("processResources", ProcessResources::class.java) { processResources -> 70 | processResources.from(rootProject.file("LICENSE")) 71 | } 72 | 73 | pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { 74 | pluginManager.apply("org.jetbrains.dokka") 75 | 76 | tasks.withType(DokkaTask::class.java).configureEach { dokkaTask -> 77 | dokkaTask.notCompatibleWithConfigurationCache("https://github.com/Kotlin/dokka/issues/1217") 78 | } 79 | tasks.named("javadocJar", Jar::class.java) { javadocJar -> 80 | javadocJar.from(tasks.named("dokkaJavadoc")) 81 | } 82 | } 83 | 84 | extensions.configure { 85 | if (!pluginManager.hasPlugin("com.gradle.plugin-publish")) { 86 | publications.register("mavenJava", MavenPublication::class.java) { publication -> 87 | publication.from(components.getByName("java")) 88 | } 89 | } 90 | publications.configureEach { publication -> 91 | (publication as? MavenPublication)?.pom { pom -> 92 | afterEvaluate { 93 | pom.name.set("${project.group}:${project.name}") 94 | pom.description.set(project.description) 95 | } 96 | pom.url.set("https://github.com/usefulness/maven-sympathy") 97 | pom.licenses { licenses -> 98 | licenses.license { license -> 99 | license.name.set("MIT") 100 | license.url.set("https://github.com/usefulness/maven-sympathy/blob/master/LICENSE") 101 | } 102 | } 103 | pom.developers { developers -> 104 | developers.developer { developer -> 105 | developer.id.set("mateuszkwiecinski") 106 | developer.name.set("Mateusz Kwiecinski") 107 | developer.email.set("36954793+mateuszkwiecinski@users.noreply.github.com") 108 | } 109 | } 110 | pom.scm { scm -> 111 | scm.connection.set("scm:git:github.com/usefulness/maven-sympathy.git") 112 | scm.developerConnection.set("scm:git:ssh://github.com/usefulness/maven-sympathy.git") 113 | scm.url.set("https://github.com/usefulness/maven-sympathy/tree/master") 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | private inline fun ExtensionContainer.configure(crossinline receiver: T.() -> Unit) { 122 | configure(T::class.java) { receiver(it) } 123 | } 124 | } 125 | 126 | private fun Project.findConfig(key: String): String = findProperty(key)?.toString() ?: System.getenv(key) ?: "" 127 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usefulness/maven-sympathy/1a4a3154694eee83e51096ff765348848a22b725/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%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" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /maven-sympathy/api/maven-sympathy.api: -------------------------------------------------------------------------------- 1 | public final class io/github/usefulness/mavensympathy/AttachStrategy : java/lang/Enum { 2 | public static final field Default Lio/github/usefulness/mavensympathy/AttachStrategy; 3 | public static final field ExtractFromMavenPublishComponents Lio/github/usefulness/mavensympathy/AttachStrategy; 4 | public static final field WatchAllResolvableConfigurations Lio/github/usefulness/mavensympathy/AttachStrategy; 5 | public static fun getEntries ()Lkotlin/enums/EnumEntries; 6 | public static fun valueOf (Ljava/lang/String;)Lio/github/usefulness/mavensympathy/AttachStrategy; 7 | public static fun values ()[Lio/github/usefulness/mavensympathy/AttachStrategy; 8 | } 9 | 10 | public final class io/github/usefulness/mavensympathy/BehaviorOnMismatch : java/lang/Enum { 11 | public static final field Fail Lio/github/usefulness/mavensympathy/BehaviorOnMismatch; 12 | public static final field Warn Lio/github/usefulness/mavensympathy/BehaviorOnMismatch; 13 | public static fun getEntries ()Lkotlin/enums/EnumEntries; 14 | public static fun valueOf (Ljava/lang/String;)Lio/github/usefulness/mavensympathy/BehaviorOnMismatch; 15 | public static fun values ()[Lio/github/usefulness/mavensympathy/BehaviorOnMismatch; 16 | } 17 | 18 | public class io/github/usefulness/mavensympathy/MavenSympathyExtension { 19 | public fun (Lorg/gradle/api/model/ObjectFactory;)V 20 | public final fun getAttachStrategy ()Lorg/gradle/api/provider/Property; 21 | } 22 | 23 | public final class io/github/usefulness/mavensympathy/MavenSympathyPlugin : org/gradle/api/Plugin { 24 | public fun ()V 25 | public synthetic fun apply (Ljava/lang/Object;)V 26 | public fun apply (Lorg/gradle/api/Project;)V 27 | } 28 | 29 | public class io/github/usefulness/mavensympathy/SympathyForMrMavenTask : org/gradle/api/DefaultTask { 30 | public fun (Lorg/gradle/api/model/ObjectFactory;)V 31 | public final fun behaviorOnMismatch (Ljava/lang/String;)V 32 | public final fun getBehaviorOnMismatch ()Lorg/gradle/api/provider/Property; 33 | public final fun getOutputFile ()Lorg/gradle/api/file/RegularFileProperty; 34 | public final fun run ()V 35 | } 36 | 37 | -------------------------------------------------------------------------------- /maven-sympathy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-gradle-plugin") 3 | alias(libs.plugins.gradle.pluginpublish) 4 | alias(libs.plugins.starter.library.kotlin) 5 | alias(libs.plugins.kotlinx.binarycompatibility) 6 | alias(libs.plugins.kotlin.samwithreceiver) 7 | id("com.starter.publishing") 8 | } 9 | 10 | samWithReceiver { 11 | annotation("org.gradle.api.HasImplicitReceiver") 12 | } 13 | 14 | kotlin { 15 | explicitApi() 16 | } 17 | 18 | tasks.withType(Test).configureEach { 19 | useJUnitPlatform() 20 | } 21 | 22 | description = "Gradle plugin that ensures first-order dependencies select the same version as they request." 23 | 24 | gradlePlugin { 25 | plugins { 26 | register("mavenSympathy") { 27 | id = "io.github.usefulness.maven-sympathy" 28 | displayName = "Let's be sympathetic to Maven users" 29 | description = project.description 30 | tags.addAll(["maven", "sympathy", "dependencies", "correctness"]) 31 | implementationClass = "io.github.usefulness.mavensympathy.MavenSympathyPlugin" 32 | } 33 | } 34 | } 35 | 36 | dependencies { 37 | testRuntimeOnly(libs.junit.platform.launcher) 38 | testRuntimeOnly(libs.junit.jupiter.engine) 39 | 40 | testImplementation(libs.junit.jupiter.api) 41 | testImplementation(libs.assertj.core) 42 | } 43 | -------------------------------------------------------------------------------- /maven-sympathy/src/main/kotlin/io/github/usefulness/mavensympathy/AttachStrategy.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import org.gradle.api.Incubating 4 | 5 | public enum class AttachStrategy { 6 | /** 7 | * If `maven-publish` is present, the plugin will behave as [ExtractFromMavenPublishComponents], 8 | * if not it will fall back to [WatchAllResolvableConfigurations] behavior 9 | * 10 | * The behavior is subject to change, but the assumption is it should cover most common setups. 11 | */ 12 | @Incubating 13 | Default, 14 | 15 | /** 16 | * The plugin will only watch configurations attached to SoftwareComponents 17 | * 18 | * The implementation relies on internal gradle APIs and may break in the future Gradle versions. 19 | */ 20 | @Incubating 21 | ExtractFromMavenPublishComponents, 22 | 23 | /** 24 | * The plugin will check all resolvable configurations for versions mismatch 25 | */ 26 | WatchAllResolvableConfigurations, 27 | } 28 | -------------------------------------------------------------------------------- /maven-sympathy/src/main/kotlin/io/github/usefulness/mavensympathy/BehaviorOnMismatch.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | public enum class BehaviorOnMismatch { 4 | Warn, 5 | Fail, 6 | } 7 | -------------------------------------------------------------------------------- /maven-sympathy/src/main/kotlin/io/github/usefulness/mavensympathy/MavenSympathyExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import org.gradle.api.model.ObjectFactory 4 | import org.gradle.api.provider.Property 5 | import javax.inject.Inject 6 | 7 | public open class MavenSympathyExtension @Inject constructor(objectFactory: ObjectFactory) { 8 | 9 | /** 10 | * Defines how the plugin will hook up with the project to listen for version mismatches 11 | */ 12 | public val attachStrategy: Property = objectFactory.property(AttachStrategy::class.java) 13 | .apply { set(AttachStrategy.Default) } 14 | } 15 | -------------------------------------------------------------------------------- /maven-sympathy/src/main/kotlin/io/github/usefulness/mavensympathy/MavenSympathyPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.api.plugins.ReportingBasePlugin 6 | import org.gradle.api.publish.PublishingExtension 7 | import org.gradle.api.publish.internal.PublicationInternal 8 | import org.gradle.api.publish.internal.component.ResolutionBackedVariant 9 | import org.gradle.api.reporting.ReportingExtension 10 | import org.gradle.api.tasks.TaskProvider 11 | 12 | public class MavenSympathyPlugin : Plugin { 13 | 14 | override fun apply(target: Project): Unit = with(target) { 15 | pluginManager.apply(ReportingBasePlugin::class.java) 16 | val pluginExtension = extensions.create("mavenSympathy", MavenSympathyExtension::class.java) 17 | val reportingExtension = extensions.getByType(ReportingExtension::class.java) 18 | val task = tasks.register("sympathyForMrMaven", SympathyForMrMavenTask::class.java) { 19 | outputFile.set(reportingExtension.baseDirectory.map { it.dir(name).file("output.txt") }) 20 | } 21 | 22 | afterEvaluate { 23 | when (pluginExtension.attachStrategy.orNull) { 24 | AttachStrategy.ExtractFromMavenPublishComponents -> listenForPublishedConfigurations(task) 25 | 26 | AttachStrategy.WatchAllResolvableConfigurations -> listenForAllConfigurations(task) 27 | 28 | AttachStrategy.Default, 29 | null, 30 | -> { 31 | if (pluginManager.hasPlugin("maven-publish")) { 32 | listenForPublishedConfigurations(task) 33 | } else { 34 | listenForAllConfigurations(task) 35 | } 36 | } 37 | } 38 | 39 | pluginExtension.attachStrategy.finalizeValue() 40 | } 41 | 42 | tasks.named("check") { dependsOn("sympathyForMrMaven") } 43 | } 44 | 45 | private fun Project.listenForAllConfigurations(task: TaskProvider) { 46 | logger.info("[maven-sympathy] watching all configurations") 47 | configurations.matching { it.isCanBeResolved }.configureEach { 48 | task.configure { 49 | if (!isCanBeResolved) return@configure 50 | configurationsWithDependencies.put(this@configureEach.name, incoming.resolutionResult.rootComponent) 51 | } 52 | } 53 | } 54 | 55 | private fun Project.listenForPublishedConfigurations(task: TaskProvider) { 56 | pluginManager.withPlugin("maven-publish") { 57 | logger.info("[maven-sympathy] registering hooks for published configurations") 58 | extensions.getByType(PublishingExtension::class.java).publications.configureEach { 59 | if (this !is PublicationInternal<*>) return@configureEach logger.info("[maven-sympathy] Ignoring publication=$name") 60 | 61 | val publicationName = name 62 | task.configure { 63 | val component = component.getOrNull() 64 | ?: return@configure logger.info("[maven-sympathy] Missing component for publication=$publicationName") 65 | val configurationVariants = component.usages.filterIsInstance() 66 | if (configurationVariants.isEmpty()) { 67 | return@configure logger.info("[maven-sympathy] Could not find configurations for publication=$publicationName") 68 | } 69 | 70 | configurationVariants.mapNotNull { it.resolutionConfiguration } 71 | .filter { it.isCanBeResolved } 72 | .forEach { configurationsWithDependencies.put(it.name, it.incoming.resolutionResult.rootComponent) } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /maven-sympathy/src/main/kotlin/io/github/usefulness/mavensympathy/SympathyForMrMavenTask.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.artifacts.component.ModuleComponentSelector 5 | import org.gradle.api.artifacts.result.ResolvedComponentResult 6 | import org.gradle.api.artifacts.result.ResolvedDependencyResult 7 | import org.gradle.api.file.RegularFileProperty 8 | import org.gradle.api.model.ObjectFactory 9 | import org.gradle.api.provider.MapProperty 10 | import org.gradle.api.provider.Property 11 | import org.gradle.api.tasks.Input 12 | import org.gradle.api.tasks.OutputFile 13 | import org.gradle.api.tasks.TaskAction 14 | import javax.inject.Inject 15 | 16 | public open class SympathyForMrMavenTask @Inject constructor(objectFactory: ObjectFactory) : DefaultTask() { 17 | 18 | @get:Input 19 | internal val configurationsWithDependencies: MapProperty = objectFactory.mapProperty( 20 | String::class.java, 21 | ResolvedComponentResult::class.java, 22 | ) 23 | 24 | @OutputFile 25 | public val outputFile: RegularFileProperty = objectFactory.fileProperty() 26 | 27 | @Input 28 | public val behaviorOnMismatch: Property = objectFactory.property(BehaviorOnMismatch::class.java) 29 | .value(BehaviorOnMismatch.Fail) 30 | 31 | public fun behaviorOnMismatch(value: String) { 32 | behaviorOnMismatch.set(BehaviorOnMismatch.entries.first { it.name.equals(value, ignoreCase = true) }) 33 | } 34 | 35 | @TaskAction 36 | public fun run() { 37 | var fail = false 38 | val errorMessages = mutableListOf() 39 | 40 | val configurations = configurationsWithDependencies.get() 41 | if (configurations.isEmpty()) { 42 | logger.warn("[maven-sympathy] No configurations detected!") 43 | } else { 44 | logger.info("[maven-sympathy] Running against: ${configurations.keys.joinToString(separator = ", ")}") 45 | } 46 | 47 | configurations.forEach { (name, root) -> 48 | root.dependencies.filterIsInstance().forEach perDependency@{ rdr -> 49 | val requested = rdr.requested as? ModuleComponentSelector ?: return@perDependency 50 | val selected = rdr.selected 51 | val requestedVersion = requested.version 52 | val selectedVersion = selected.moduleVersion?.version 53 | if (!requestedVersion.isNullOrBlank() && requestedVersion != selectedVersion) { 54 | val errorMessage = "[$name] dependency $requested version changed: $requestedVersion → $selectedVersion" 55 | errorMessages.add(errorMessage) 56 | logger.error(errorMessage) 57 | fail = true 58 | } 59 | } 60 | } 61 | val report = outputFile.get().asFile 62 | if (fail) { 63 | report.writeText(errorMessages.joinToString(separator = "\n")) 64 | val failureMessage = "Declared dependencies were upgraded transitively. See task output above. Please update their versions." 65 | 66 | when (behaviorOnMismatch.get()) { 67 | BehaviorOnMismatch.Warn -> logger.error(failureMessage) 68 | BehaviorOnMismatch.Fail, null -> error(failureMessage) 69 | } 70 | } else { 71 | report.writeText("OK") 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /maven-sympathy/src/test/kotlin/io/github/usefulness/mavensympathy/AllConfigurationsIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import io.github.usefulness.mavensympathy.internal.resolve 4 | import io.github.usefulness.mavensympathy.internal.runGradle 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.gradle.testkit.runner.TaskOutcome 7 | import org.junit.jupiter.api.BeforeEach 8 | import org.junit.jupiter.api.Test 9 | import org.junit.jupiter.api.io.TempDir 10 | import java.io.File 11 | 12 | class AllConfigurationsIntegrationTest { 13 | 14 | @TempDir 15 | lateinit var rootDirectory: File 16 | 17 | @BeforeEach 18 | fun setUp() { 19 | rootDirectory.resolve("settings.gradle") { 20 | // language=groovy 21 | writeText( 22 | """ 23 | pluginManagement { 24 | repositories { 25 | mavenCentral() 26 | gradlePluginPortal() 27 | } 28 | } 29 | dependencyResolutionManagement { 30 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 31 | repositories { 32 | mavenCentral() 33 | } 34 | } 35 | 36 | """.trimIndent(), 37 | ) 38 | } 39 | } 40 | 41 | @Test 42 | fun successfulRun() { 43 | rootDirectory.resolve("build.gradle") { 44 | // language=groovy 45 | writeText( 46 | """ 47 | plugins { 48 | id("java-library") 49 | id("io.github.usefulness.maven-sympathy") 50 | } 51 | 52 | tasks.withType(Test).configureEach { 53 | useJUnitPlatform() 54 | } 55 | 56 | dependencies { 57 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 58 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") 59 | } 60 | """.trimIndent(), 61 | ) 62 | } 63 | val result = runGradle(projectDir = rootDirectory) 64 | assertThat(result.output).doesNotContain("changed to") 65 | assertThat(result.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 66 | 67 | val reRunResult = runGradle(projectDir = rootDirectory) 68 | assertThat(reRunResult.output).doesNotContain("changed to") 69 | assertThat(reRunResult.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 70 | } 71 | 72 | @Test 73 | fun boms() { 74 | rootDirectory.resolve("build.gradle") { 75 | // language=groovy 76 | writeText( 77 | """ 78 | plugins { 79 | id("java-library") 80 | id("io.github.usefulness.maven-sympathy") 81 | } 82 | 83 | dependencies { 84 | implementation(platform("com.squareup.retrofit2:retrofit-bom:2.11.0") ) 85 | implementation("com.squareup.retrofit2:retrofit") 86 | implementation("com.squareup.retrofit2:converter-jackson") 87 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 88 | } 89 | """.trimIndent(), 90 | ) 91 | } 92 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 93 | assertThat(result.output).contains("dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 94 | assertThat(result.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.FAILED) 95 | 96 | rootDirectory.resolve("build.gradle") { 97 | // language=groovy 98 | writeText( 99 | """ 100 | plugins { 101 | id("java-library") 102 | id("io.github.usefulness.maven-sympathy") 103 | } 104 | 105 | dependencies { 106 | implementation(platform("com.squareup.retrofit2:retrofit-bom:2.11.0") ) 107 | implementation("com.squareup.retrofit2:retrofit") 108 | implementation("com.squareup.retrofit2:converter-jackson") 109 | implementation("com.squareup.okhttp3:okhttp:3.14.9") 110 | } 111 | """.trimIndent(), 112 | ) 113 | } 114 | val reRunResult = runGradle(projectDir = rootDirectory) 115 | assertThat(reRunResult.output).doesNotContain("changed to") 116 | assertThat(reRunResult.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 117 | } 118 | 119 | @Test 120 | fun bumpViaOwnDependency() { 121 | rootDirectory.resolve("build.gradle") { 122 | // language=groovy 123 | writeText( 124 | """ 125 | plugins { 126 | id("java-library") 127 | id("io.github.usefulness.maven-sympathy") 128 | } 129 | 130 | dependencies { 131 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 132 | implementation("com.squareup.okhttp3:okhttp:4.12.0") 133 | } 134 | """.trimIndent(), 135 | ) 136 | } 137 | val result = runGradle(projectDir = rootDirectory) 138 | 139 | assertThat(result.output).doesNotContain("changed to") 140 | } 141 | 142 | @Test 143 | fun bumpViaTransitiveDependency() { 144 | rootDirectory.resolve("build.gradle") { 145 | // language=groovy 146 | writeText( 147 | """ 148 | plugins { 149 | id("java-library") 150 | id("io.github.usefulness.maven-sympathy") 151 | } 152 | 153 | dependencies { 154 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 155 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 156 | } 157 | """.trimIndent(), 158 | ) 159 | } 160 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 161 | 162 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 163 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 164 | .isEqualToIgnoringWhitespace( 165 | """ 166 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 167 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 168 | [testCompileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 169 | [testRuntimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 170 | """.trimIndent(), 171 | ) 172 | } 173 | 174 | @Test 175 | fun warningOnly() { 176 | rootDirectory.resolve("build.gradle") { 177 | // language=groovy 178 | writeText( 179 | """ 180 | import io.github.usefulness.mavensympathy.BehaviorOnMismatch 181 | 182 | plugins { 183 | id("java-library") 184 | id("io.github.usefulness.maven-sympathy") 185 | } 186 | 187 | tasks.named("sympathyForMrMaven") { 188 | behaviorOnMismatch "fail" 189 | behaviorOnMismatch = BehaviorOnMismatch.Warn 190 | } 191 | 192 | dependencies { 193 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 194 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 195 | } 196 | """.trimIndent(), 197 | ) 198 | } 199 | val result = runGradle(projectDir = rootDirectory) 200 | 201 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 202 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 203 | .isEqualToIgnoringWhitespace( 204 | """ 205 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 206 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 207 | [testCompileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 208 | [testRuntimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 209 | """.trimIndent(), 210 | ) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /maven-sympathy/src/test/kotlin/io/github/usefulness/mavensympathy/AttachStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import io.github.usefulness.mavensympathy.internal.resolve 4 | import io.github.usefulness.mavensympathy.internal.runGradle 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.junit.jupiter.api.BeforeEach 7 | import org.junit.jupiter.api.Test 8 | import org.junit.jupiter.api.io.TempDir 9 | import java.io.File 10 | 11 | class AttachStrategyTest { 12 | 13 | @TempDir 14 | lateinit var rootDirectory: File 15 | 16 | @BeforeEach 17 | fun setUp() { 18 | rootDirectory.resolve("settings.gradle") { 19 | // language=groovy 20 | writeText( 21 | """ 22 | pluginManagement { 23 | repositories { 24 | mavenCentral() 25 | gradlePluginPortal() 26 | } 27 | } 28 | dependencyResolutionManagement { 29 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 30 | repositories { 31 | mavenCentral() 32 | } 33 | } 34 | 35 | """.trimIndent(), 36 | ) 37 | } 38 | } 39 | 40 | @Test 41 | fun mavenPublishEnforcedButNotApplied() { 42 | rootDirectory.resolve("build.gradle") { 43 | // language=groovy 44 | writeText( 45 | """ 46 | import io.github.usefulness.mavensympathy.AttachStrategy 47 | 48 | plugins { 49 | id("java-library") 50 | id("io.github.usefulness.maven-sympathy") 51 | } 52 | 53 | mavenSympathy { 54 | attachStrategy = AttachStrategy.ExtractFromMavenPublishComponents 55 | } 56 | 57 | dependencies { 58 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 59 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 60 | } 61 | """.trimIndent(), 62 | ) 63 | } 64 | val result = runGradle(projectDir = rootDirectory) 65 | 66 | assertThat(result.output).doesNotContain("changed to") 67 | } 68 | 69 | @Test 70 | fun mavenPublishPresentButAllConfigurationsEnforced() { 71 | rootDirectory.resolve("build.gradle") { 72 | // language=groovy 73 | writeText( 74 | """ 75 | import io.github.usefulness.mavensympathy.AttachStrategy 76 | 77 | plugins { 78 | id("java-library") 79 | id("maven-publish") 80 | id("io.github.usefulness.maven-sympathy") 81 | } 82 | 83 | publishing { 84 | publications { 85 | mavenJava(MavenPublication) { 86 | from components.java 87 | } 88 | } 89 | } 90 | 91 | mavenSympathy { 92 | attachStrategy = AttachStrategy.WatchAllResolvableConfigurations 93 | } 94 | 95 | dependencies { 96 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 97 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 98 | } 99 | """.trimIndent(), 100 | ) 101 | } 102 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 103 | 104 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 105 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 106 | .isEqualToIgnoringWhitespace( 107 | """ 108 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 109 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 110 | [testCompileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 111 | [testRuntimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 112 | """.trimIndent(), 113 | ) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /maven-sympathy/src/test/kotlin/io/github/usefulness/mavensympathy/MavenPublishIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import io.github.usefulness.mavensympathy.internal.resolve 4 | import io.github.usefulness.mavensympathy.internal.runGradle 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.gradle.testkit.runner.TaskOutcome 7 | import org.junit.jupiter.api.BeforeEach 8 | import org.junit.jupiter.api.Test 9 | import org.junit.jupiter.api.io.TempDir 10 | import java.io.File 11 | 12 | class MavenPublishIntegrationTest { 13 | 14 | @TempDir 15 | lateinit var rootDirectory: File 16 | 17 | @BeforeEach 18 | fun setUp() { 19 | rootDirectory.resolve("settings.gradle") { 20 | // language=groovy 21 | writeText( 22 | """ 23 | pluginManagement { 24 | repositories { 25 | mavenCentral() 26 | gradlePluginPortal() 27 | } 28 | } 29 | dependencyResolutionManagement { 30 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 31 | repositories { 32 | mavenCentral() 33 | } 34 | } 35 | 36 | """.trimIndent(), 37 | ) 38 | } 39 | } 40 | 41 | @Test 42 | fun successfulRun() { 43 | rootDirectory.resolve("build.gradle") { 44 | // language=groovy 45 | writeText( 46 | """ 47 | plugins { 48 | id("java-library") 49 | id("maven-publish") 50 | id("io.github.usefulness.maven-sympathy") 51 | } 52 | 53 | publishing { 54 | publications { 55 | mavenJava(MavenPublication) { 56 | from components.java 57 | } 58 | } 59 | } 60 | 61 | tasks.withType(Test).configureEach { 62 | useJUnitPlatform() 63 | } 64 | 65 | dependencies { 66 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 67 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") 68 | } 69 | """.trimIndent(), 70 | ) 71 | } 72 | val result = runGradle(projectDir = rootDirectory) 73 | assertThat(result.output).doesNotContain("changed to") 74 | assertThat(result.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 75 | 76 | val reRunResult = runGradle(projectDir = rootDirectory) 77 | assertThat(reRunResult.output).doesNotContain("changed to") 78 | assertThat(reRunResult.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) 79 | } 80 | 81 | @Test 82 | fun boms() { 83 | rootDirectory.resolve("build.gradle") { 84 | // language=groovy 85 | writeText( 86 | """ 87 | plugins { 88 | id("java-library") 89 | id("maven-publish") 90 | id("io.github.usefulness.maven-sympathy") 91 | } 92 | 93 | publishing { 94 | publications { 95 | mavenJava(MavenPublication) { 96 | from components.java 97 | } 98 | } 99 | } 100 | 101 | dependencies { 102 | implementation(platform("com.squareup.retrofit2:retrofit-bom:2.11.0") ) 103 | implementation("com.squareup.retrofit2:retrofit") 104 | implementation("com.squareup.retrofit2:converter-jackson") 105 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 106 | } 107 | """.trimIndent(), 108 | ) 109 | } 110 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 111 | assertThat(result.output).contains("dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 112 | assertThat(result.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.FAILED) 113 | 114 | rootDirectory.resolve("build.gradle") { 115 | // language=groovy 116 | writeText( 117 | """ 118 | plugins { 119 | id("java-library") 120 | id("maven-publish") 121 | id("io.github.usefulness.maven-sympathy") 122 | } 123 | 124 | publishing { 125 | publications { 126 | mavenJava(MavenPublication) { 127 | from components.java 128 | } 129 | } 130 | } 131 | 132 | dependencies { 133 | implementation(platform("com.squareup.retrofit2:retrofit-bom:2.11.0") ) 134 | implementation("com.squareup.retrofit2:retrofit") 135 | implementation("com.squareup.retrofit2:converter-jackson") 136 | implementation("com.squareup.okhttp3:okhttp:3.14.9") 137 | } 138 | """.trimIndent(), 139 | ) 140 | } 141 | val reRunResult = runGradle(projectDir = rootDirectory) 142 | assertThat(reRunResult.output).doesNotContain("changed to") 143 | assertThat(reRunResult.task(":sympathyForMrMaven")?.outcome).isEqualTo(TaskOutcome.SUCCESS) 144 | } 145 | 146 | @Test 147 | fun bumpViaOwnDependency() { 148 | rootDirectory.resolve("build.gradle") { 149 | // language=groovy 150 | writeText( 151 | """ 152 | plugins { 153 | id("java-library") 154 | id("maven-publish") 155 | id("io.github.usefulness.maven-sympathy") 156 | } 157 | 158 | publishing { 159 | publications { 160 | mavenJava(MavenPublication) { 161 | from components.java 162 | } 163 | } 164 | } 165 | 166 | dependencies { 167 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 168 | implementation("com.squareup.okhttp3:okhttp:4.12.0") 169 | } 170 | """.trimIndent(), 171 | ) 172 | } 173 | val result = runGradle(projectDir = rootDirectory) 174 | 175 | assertThat(result.output).doesNotContain("changed to") 176 | } 177 | 178 | @Test 179 | fun bumpViaTransitiveDependency() { 180 | rootDirectory.resolve("build.gradle") { 181 | // language=groovy 182 | writeText( 183 | """ 184 | plugins { 185 | id("java-library") 186 | id("maven-publish") 187 | id("io.github.usefulness.maven-sympathy") 188 | } 189 | 190 | publishing { 191 | publications { 192 | mavenJava(MavenPublication) { 193 | from components.java 194 | } 195 | } 196 | } 197 | 198 | dependencies { 199 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 200 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 201 | } 202 | """.trimIndent(), 203 | ) 204 | } 205 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 206 | 207 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 208 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 209 | .isEqualToIgnoringWhitespace( 210 | """ 211 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 212 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 213 | """.trimIndent(), 214 | ) 215 | } 216 | 217 | @Test 218 | fun warningOnly() { 219 | rootDirectory.resolve("build.gradle") { 220 | // language=groovy 221 | writeText( 222 | """ 223 | import io.github.usefulness.mavensympathy.BehaviorOnMismatch 224 | 225 | plugins { 226 | id("java-library") 227 | id("maven-publish") 228 | id("io.github.usefulness.maven-sympathy") 229 | } 230 | 231 | publishing { 232 | publications { 233 | mavenJava(MavenPublication) { 234 | from components.java 235 | } 236 | } 237 | } 238 | 239 | tasks.named("sympathyForMrMaven") { 240 | behaviorOnMismatch "fail" 241 | behaviorOnMismatch = BehaviorOnMismatch.Warn 242 | } 243 | 244 | dependencies { 245 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 246 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 247 | } 248 | """.trimIndent(), 249 | ) 250 | } 251 | val result = runGradle(projectDir = rootDirectory) 252 | 253 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 254 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 255 | .isEqualToIgnoringWhitespace( 256 | """ 257 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 258 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 259 | """.trimIndent(), 260 | ) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /maven-sympathy/src/test/kotlin/io/github/usefulness/mavensympathy/PluginsTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy 2 | 3 | import io.github.usefulness.mavensympathy.internal.resolve 4 | import io.github.usefulness.mavensympathy.internal.runGradle 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.junit.jupiter.api.BeforeEach 7 | import org.junit.jupiter.api.Test 8 | import org.junit.jupiter.api.io.TempDir 9 | import java.io.File 10 | 11 | class PluginsTest { 12 | 13 | @TempDir 14 | lateinit var rootDirectory: File 15 | 16 | @BeforeEach 17 | fun setUp() { 18 | rootDirectory.resolve("settings.gradle") { 19 | // language=groovy 20 | writeText( 21 | """ 22 | pluginManagement { 23 | repositories { 24 | mavenCentral() 25 | gradlePluginPortal() 26 | } 27 | } 28 | dependencyResolutionManagement { 29 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 30 | repositories { 31 | mavenCentral() 32 | } 33 | } 34 | 35 | """.trimIndent(), 36 | ) 37 | } 38 | } 39 | 40 | @Test 41 | fun gradlePlugin() { 42 | rootDirectory.resolve("build.gradle") { 43 | // language=groovy 44 | writeText( 45 | """ 46 | plugins { 47 | id("java-gradle-plugin") 48 | id("maven-publish") 49 | id("io.github.usefulness.maven-sympathy") 50 | } 51 | 52 | gradlePlugin { 53 | plugins { 54 | fooPlugin { 55 | id = 'io.github.foo.plugin' 56 | implementationClass = 'FixturImplementationClass' 57 | } 58 | } 59 | } 60 | 61 | dependencies { 62 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 63 | implementation("com.squareup.okhttp3:okhttp:3.14.8") 64 | } 65 | """.trimIndent(), 66 | ) 67 | } 68 | val result = runGradle(projectDir = rootDirectory, shouldFail = true) 69 | 70 | assertThat(result.output).contains("com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9") 71 | assertThat(rootDirectory.resolve("build/reports/sympathyForMrMaven/output.txt")).content() 72 | .isEqualToIgnoringWhitespace( 73 | """ 74 | [compileClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 75 | [runtimeClasspath] dependency com.squareup.okhttp3:okhttp:3.14.8 version changed: 3.14.8 → 3.14.9 76 | """.trimIndent(), 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /maven-sympathy/src/test/kotlin/io/github/usefulness/mavensympathy/internal/Uitls.kt: -------------------------------------------------------------------------------- 1 | package io.github.usefulness.mavensympathy.internal 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.GradleRunner 5 | import java.io.File 6 | import java.io.InputStream 7 | 8 | internal fun runGradle( 9 | projectDir: File, 10 | tasksToRun: List = listOf("check"), 11 | configurationCacheEnabled: Boolean = true, 12 | shouldFail: Boolean = false, 13 | ): BuildResult = GradleRunner.create().apply { 14 | forwardOutput() 15 | withPluginClasspath() 16 | withProjectDir(projectDir) 17 | 18 | withArguments( 19 | buildList { 20 | addAll(tasksToRun) 21 | if (configurationCacheEnabled) { 22 | add("--configuration-cache") 23 | } 24 | add("--stacktrace") 25 | }, 26 | ) 27 | 28 | // https://docs.gradle.org/8.1.1/userguide/configuration_cache.html#config_cache:not_yet_implemented:testkit_build_with_java_agent 29 | if (!configurationCacheEnabled) { 30 | withJaCoCo() 31 | } 32 | }.run { 33 | if (shouldFail) { 34 | buildAndFail() 35 | } else { 36 | build() 37 | } 38 | } 39 | 40 | private fun GradleRunner.withJaCoCo(): GradleRunner { 41 | javaClass.classLoader.getResourceAsStream("testkit-gradle.properties") 42 | ?.toFile(File(projectDir, "gradle.properties")) 43 | return this 44 | } 45 | 46 | private fun InputStream.toFile(file: File) { 47 | use { input -> 48 | file.outputStream().use { input.copyTo(it) } 49 | } 50 | } 51 | 52 | internal fun File.resolve(relative: String, receiver: File.() -> Unit): File = resolve(relative).apply { 53 | parentFile.mkdirs() 54 | receiver() 55 | } 56 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "forkProcessing": "enabled", 4 | "assignees": [ 5 | "mateuszkwiecinski" 6 | ], 7 | "extends": [ 8 | "config:recommended" 9 | ], 10 | "packageRules": [ 11 | { 12 | "matchUpdateTypes": [ 13 | "minor", 14 | "patch", 15 | "pin", 16 | "digest" 17 | ], 18 | "automerge": true 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.api.initialization.resolve.RepositoriesMode 2 | 3 | pluginManagement { 4 | repositories { // https://github.com/gradle/gradle/issues/20866 5 | gradlePluginPortal() 6 | mavenCentral() 7 | } 8 | } 9 | 10 | plugins { 11 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 12 | } 13 | 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.name = "io.github.usefulness" 22 | 23 | include("maven-sympathy") 24 | includeBuild("gradle/plugins") 25 | --------------------------------------------------------------------------------