diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..62e75fc --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,59 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: [ + "config:base" + ], + enabled: true, + enabledManagers: [ + "npm", + "gradle", + "gradle-wrapper", + "github-actions", + ], + + // Will auto-merge directly, without a PR, if tests pass - else, makes a PR. + // Must add Renovate to 'Allow specified actors to bypass required pull requests' + // in branch protection rule + automergeType: "branch", + platformAutomerge: true, + ignoreTests: false, + packageRules: [ + { + description: "auto-merge all but major releases", + matchUpdateTypes: [ + "minor", + "patch", + "pin", + "digest", + ], + automerge: true, + } + ], + + timezone: "Etc/UTC", + // loosely limit to Europe work hours, so we don't get pinged in the middle of the night + schedule: [ + "after 10am and before 6pm", + ], + automergeSchedule: [ + "after 10am and before 6pm", + ], + + stabilityDays: 14, + // suppressNotifications: [ + // "artifactErrors", + // "branchAutomergeFailure", + // "configErrorIssue", + // "deprecationWarningIssues", + // "lockFileErrors", + // "onboardingClose", + // "prEditedNotification", + // "prIgnoreNotification", + // ], + prCreation: "status-success", + semanticCommits: "disabled", + + ignorePaths: [ + "**/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/**", + ] +} diff --git a/.github/workflows/gradle_task.yml b/.github/workflows/gradle_task.yml deleted file mode 100644 index ea4fc1b..0000000 --- a/.github/workflows/gradle_task.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Gradle Task -run-name: "Gradle Task ${{ inputs.gradle-task }} @ ${{ inputs.runs-on }}" - -# Reusable Workflow for running a Gradle task - -on: - workflow_dispatch: - - workflow_call: - inputs: - gradle-task: - description: "The Gradle task to run, including any flags" - required: true - type: string - runs-on: - description: "OSes to run the task on" - required: true - type: string - - -concurrency: - # note: the Workflow inputs are also included in the concurrency group - group: "${{ github.workflow }} ${{ join(inputs.*) }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" - cancel-in-progress: true - - -permissions: - contents: read - checks: write # required by mikepenz/action-junit-report - -jobs: - - run-task: - runs-on: ${{ inputs.runs-on }} - name: "./gradlew ${{ inputs.gradle-task}} @ ${{ inputs.runs-on }}" - timeout-minutes: 60 - steps: - - ### Gradle task ### - - - name: Checkout the repo - uses: actions/checkout@v3 - - - name: Setup JDK - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 11 - - - name: Cache Gradle TestKit - id: cache-gradle-testkit - uses: actions/cache@v3 - with: - path: | - ${{ runner.temp }}/.gradle-test-kit/caches - key: gradle-testkit-${{ runner.os }} - - - uses: gradle/gradle-build-action@v2 - env: - GRADLE_TESTKIT_DIR: ${{ runner.temp }}/.gradle-test-kit/caches - with: - gradle-home-cache-cleanup: true - arguments: ${{ inputs.gradle-task }} --scan - - - name: Upload build reports - if: failure() - uses: actions/upload-artifact@v3 - with: - name: build-report-${{ runner.os }}${{ github.action }} - path: | - **/build/reports/ - **/*.hprof - **/*.log - if-no-files-found: ignore - - - name: Publish Test Reports - uses: mikepenz/action-junit-report@v3 - if: always() - with: - report_paths: | - **/build/test-results/**/TEST-*.xml - require_tests: false diff --git a/.github/workflows/run_gradle_task.yml b/.github/workflows/run_gradle_task.yml new file mode 100644 index 0000000..62ca879 --- /dev/null +++ b/.github/workflows/run_gradle_task.yml @@ -0,0 +1,111 @@ +name: Gradle Task +run-name: "Gradle Task ${{ inputs.gradle-task }} @ ${{ inputs.runs-on }}" + +# Reusable Workflow for running a Gradle task + +on: + workflow_dispatch: + inputs: + gradle-task: + description: "The Gradle task to run, including any flags" + required: true + type: string + runs-on: + description: "OS to run the task on" + required: true + type: string + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + workflow_call: + inputs: + gradle-task: + description: "The Gradle task to run, including any flags" + required: true + type: string + runs-on: + description: "OS to run the task on" + required: true + type: string + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + + +concurrency: + # note: the Workflow inputs are also included in the concurrency group + group: "Gradle Task: ${{ github.workflow }} ${{ join(inputs.*) }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + + +permissions: + contents: read + checks: write # required by mikepenz/action-junit-report + + +jobs: + + run-task: + runs-on: ${{ inputs.runs-on }} + name: "./gradlew ${{ inputs.gradle-task}} @ ${{ inputs.runs-on }}" + timeout-minutes: 60 + steps: + + ### Gradle task ### + + - name: Checkout the repo + uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkout-ref || github.ref }} + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Setup JDK 11 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 11 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Setup Android SDK # required for integration tests + uses: android-actions/setup-android@v3 + + - uses: gradle/gradle-build-action@v3 + with: + gradle-home-cache-cleanup: true + + - name: Run the tests + run: ./gradlew ${{ inputs.gradle-task }} + env: + MAVEN_SONATYPE_USERNAME: ${{ secrets.MAVEN_SONATYPE_USERNAME }} + MAVEN_SONATYPE_PASSWORD: ${{ secrets.MAVEN_SONATYPE_PASSWORD }} + MAVEN_SONATYPE_SIGNING_KEY_ID: ${{ secrets.MAVEN_SONATYPE_SIGNING_KEY_ID }} + MAVEN_SONATYPE_SIGNING_KEY: ${{ secrets.MAVEN_SONATYPE_SIGNING_KEY }} + MAVEN_SONATYPE_SIGNING_PASSWORD: ${{ secrets.MAVEN_SONATYPE_SIGNING_PASSWORD }} + + - name: Upload build reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: build-report-${{ runner.os }}${{ github.action }} + path: | + **/build/reports/ + **/*.hprof + **/*.log + if-no-files-found: ignore + + - name: Publish Test Reports + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: | + **/build/test-results/**/TEST-*.xml + require_tests: false diff --git a/.github/workflows/run_publish_maven.yml b/.github/workflows/run_publish_maven.yml new file mode 100644 index 0000000..3d0cf4a --- /dev/null +++ b/.github/workflows/run_publish_maven.yml @@ -0,0 +1,44 @@ +name: Publish Maven + + +on: + workflow_dispatch: + inputs: + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + workflow_call: + inputs: + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + + +concurrency: + group: "Publish Maven: ${{ github.workflow }}" + cancel-in-progress: false + + +permissions: + contents: write + packages: write + checks: write + + +jobs: + + sonatype-release: + if: github.ref == 'refs/heads/main' + permissions: + contents: read + packages: write + checks: write + uses: ./.github/workflows/run_gradle_task.yml + secrets: inherit + with: + runs-on: macos-latest # only macOS supports building all Kotlin targets + gradle-task: >- + publishAllPublicationsToSonatypeReleaseRepository --stacktrace --no-configuration-cache --no-parallel + checkout-ref: ${{ inputs.checkout-ref }} diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..273a43c --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,47 @@ +name: Tests + + +on: + workflow_dispatch: + inputs: + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + workflow_call: + inputs: + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + + +concurrency: + group: "Tests: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + + +permissions: + contents: read + checks: write # required by mikepenz/action-junit-report + + +jobs: + + gradle-check: + strategy: + matrix: + include: + - os: macos-latest + task: "check --continue" + - os: ubuntu-latest + task: "check --continue" + - os: windows-latest + task: "check --continue" + fail-fast: false + uses: ./.github/workflows/run_gradle_task.yml + with: + runs-on: ${{ matrix.os }} + gradle-task: >- + ${{ matrix.task }} --stacktrace + checkout-ref: ${{ inputs.checkout-ref }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 2e10ef8..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Tests - -on: - pull_request: - workflow_dispatch: - workflow_call: - push: - branches: - - main - - -concurrency: - group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" - cancel-in-progress: true - - -permissions: - contents: read - checks: write # required by mikepenz/action-junit-report - - -jobs: - - gradle-check: - strategy: - matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] - task: - - check --continue - fail-fast: false - uses: ./.github/workflows/gradle_task.yml - with: - runs-on: ${{ matrix.os }} - gradle-task: >- - ${{ matrix.task }} --stacktrace diff --git a/.github/workflows/workflow_pull_request.yml b/.github/workflows/workflow_pull_request.yml new file mode 100644 index 0000000..281189d --- /dev/null +++ b/.github/workflows/workflow_pull_request.yml @@ -0,0 +1,24 @@ +name: Pull Requests + + +on: + workflow_dispatch: + pull_request: + merge_group: + push: + branches: + - "renovate/**" + + +concurrency: + group: "Pull Requests: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + + +jobs: + + tests: + uses: ./.github/workflows/run_tests.yml + permissions: + contents: read + checks: write diff --git a/.github/workflows/workflow_release.yml b/.github/workflows/workflow_release.yml new file mode 100644 index 0000000..5a26f9d --- /dev/null +++ b/.github/workflows/workflow_release.yml @@ -0,0 +1,43 @@ +name: Releases + + +on: + workflow_dispatch: + inputs: + checkout-ref: + description: "The branch, tag or SHA to checkout. See actions/checkout 'ref'." + required: false + type: string + push: + branches: [ main ] + release: + types: [ created ] + + +concurrency: + group: "Releases: ${{ github.workflow }} @ ${{ inputs.checkout-ref }} ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + + +jobs: + + tests: + uses: ./.github/workflows/run_tests.yml + permissions: + checks: write + contents: read + with: + checkout-ref: ${{ inputs.checkout-ref }} + + publish-maven: + needs: tests + # only publish when manually triggered, or it's the main branch, or it's for a release + if: inputs.checkout-ref || github.ref == 'refs/heads/main' || (github.event_name == 'release' && github.event.action == 'created') + uses: ./.github/workflows/run_publish_maven.yml + secrets: inherit + permissions: + checks: write + contents: write + packages: write + with: + checkout-ref: ${{ inputs.checkout-ref }} diff --git a/README.md b/README.md index d0dc75d..434be9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![GitHub license](https://img.shields.io/github/license/adamko-dev/kotlin-binary-compatibility-validator-mu?style=for-the-badge)](https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu/blob/main/LICENSE) [![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/dev.adamko.kotlin.binary-compatibility-validator?style=for-the-badge)](https://plugins.gradle.org/plugin/dev.adamko.kotlin.binary-compatibility-validator) +[![Maven Central](https://img.shields.io/maven-central/v/dev.adamko.kotlin.binary-compatibility-validator/bcv-gradle-plugin?style=for-the-badge&logo=apache-maven&color=6545e7&link=https%3A%2F%2Fsearch.maven.org%2Fsearch%3Fq%3Dg%3Adev.adamko.kotlin.binary-compatibility-validator)](https://search.maven.org/search?q=g:dev.adamko.kotlin.binary-compatibility-validator) +[![Maven Central Snapshots](https://img.shields.io/maven-metadata/v?label=MAVEN%20SNAPSHOT&metadataUrl=https%3A%2F%2Fs01.oss.sonatype.org%2Fcontent%2Frepositories%2Fsnapshots%2Fdev%2Fadamko%2Fkotlin%2Fbinary-compatibility-validator%2Fbcv-gradle-plugin%2Fmaven-metadata.xml&style=for-the-badge&logo=apache-maven)](https://s01.oss.sonatype.org/content/repositories/snapshots/dev/adamko/kotlin/binary-compatibility-validator/bcv-gradle-plugin/) # Kotlin Binary Compatibility Validator (Mirror Universe) @@ -32,7 +34,7 @@ or (**experimentally**) [as a Settings plugin](#settings-plugin) in `settings.gr The minimal supported Gradle version is 7.6. -By default, BCV-MU uses BCV version `0.13.1`, which can be overridden, but may introduce runtime +By default, BCV-MU uses BCV version `0.13.2`, which can be overridden, but may introduce runtime errors. ### Build plugin @@ -98,7 +100,7 @@ binaryCompatibilityValidator { bcvEnabled.set(true) // Override the default BCV version - kotlinxBinaryCompatibilityValidatorVersion.set("0.13.1") + kotlinxBinaryCompatibilityValidatorVersion.set("0.13.2") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index bb0209d..6e678c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,20 +1,24 @@ -import buildsrc.utils.excludeGeneratedGradleDsl +import buildsrc.utils.generatedKotlinDslAccessorDirs plugins { buildsrc.conventions.base idea } -group = "dev.adamko.kotlin.binary_compatibility_validator" -version = "0.1.0" +group = "dev.adamko.kotlin.binary-compatibility-validator" +project.version = object { + private val gitVersion = project.gitVersion + override fun toString(): String = gitVersion.get() +} idea { module { - excludeGeneratedGradleDsl(layout) - excludeDirs = excludeDirs + layout.files( - ".idea", - "gradle/wrapper", - ) + excludeDirs = excludeDirs + + layout.generatedKotlinDslAccessorDirs() + + layout.files( + ".idea", + "gradle/wrapper", + ) } } @@ -42,3 +46,14 @@ val readmeCheck by tasks.registering { tasks.check { dependsOn(readmeCheck) } + +val projectVersion by tasks.registering { + description = "prints the project version" + group = "help" + val version = providers.provider { project.version } + inputs.property("version", version) + outputs.cacheIf("logging task, it should always run") { false } + doLast { + logger.quiet("${version.orNull}") + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1d243d8..9f4caf6 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation(libs.gradlePlugin.bcvMu) implementation(libs.gradlePlugin.pluginPublishing) implementation(libs.gradlePlugin.shadow) + implementation(libs.gradlePlugin.devPublish) } java { diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index a1038fe..12943d9 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -15,6 +15,10 @@ dependencyResolutionManagement { repositories { mavenCentral() gradlePluginPortal() + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") { + name = "MavenCentralSnapshots" + mavenContent { snapshotsOnly() } + } } versionCatalogs { diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/github-maven-publish.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/github-maven-publish.gradle.kts deleted file mode 100644 index ae28076..0000000 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/github-maven-publish.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -package buildsrc.conventions - -plugins { - `maven-publish` -} - -val githubPublishDir: Provider = - providers.environmentVariable("GITHUB_PUBLISH_DIR").map { file(it) } - -publishing { - repositories { - maven(githubPublishDir) { - name = "GitHubPublish" - } - } -} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts index 2382654..f37fea2 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/kotlin-gradle-plugin.gradle.kts @@ -5,87 +5,45 @@ plugins { id("buildsrc.conventions.java-base") id("org.gradle.kotlin.kotlin-dsl") id("com.gradle.plugin-publish") - `maven-publish` } tasks.validatePlugins { enableStricterValidation = true } -sourceSets { - configureEach { - java.setSrcDirs(emptyList()) +val createJavadocJarReadme by tasks.registering(Sync::class) { + description = "generate a readme.txt for the Javadoc JAR" + val projectCoords = provider { project.run { "$group:$name:$version" } } + inputs.property("projectGAV", projectCoords) + val projectCoordsToken = "%{projectGAV}" + from( + resources.text.fromString( + """ + |This Javadoc JAR for $projectCoordsToken is intentionally empty. + | + |For documentation, see the sources JAR or https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu/ + | + """.trimMargin() + ) + ) { + rename { "readme.txt" } + } + into(temporaryDir) + doLast { + temporaryDir.walk() + .filter { it.isFile } + .forEach { file -> + file.writeText( + file.readText().replace(projectCoordsToken, projectCoords.get()) + ) + } } } -// -///** These dependencies will be provided by Gradle, and we should prevent version conflict */ -//fun Configuration.excludeGradleCommonDependencies() { -// dependencies -// .withType() -// .configureEach { -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib") -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7") -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8") -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-common") -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-reflect") -// exclude(group = "org.jetbrains.kotlin", module = "kotlin-script-runtime") -// } -//} -// -//// Exclude Gradle runtime from given SourceSet configurations -////configurations[sourceSet.implementationConfigurationName].excludeGradleCommonDependencies() -////configurations[sourceSet.apiConfigurationName].excludeGradleCommonDependencies() -////configurations[sourceSet.runtimeOnlyConfigurationName].excludeGradleCommonDependencies() -// -//dependencies { -// constraints { -// -// } -//} -// -//abstract class AsmCapability : ComponentMetadataRule { -//} -// -//@CacheableRule -//abstract class GradleKotlinLibsCapability () : ComponentMetadataRule { -// -// private val kotlinGroup = "org.jetbrains.kotlin" -// private val kotlinLibs = setOf( -// "kotlin-stdlib", -// "kotlin-stdlib-jdk7", -// "kotlin-stdlib-jdk8", -// "kotlin-stdlib-common", -// "kotlin-reflect", -// "kotlin-script-runtime", -// ) -// -// override fun execute(context: ComponentMetadataContext) = context.details.run { -// if (id.group == kotlinGroup && id.name in kotlinLibs) { -// allVariants { -// withCapabilities { -// addCapability(kotlinGroup, "gradle-kotlin-embedded", id.version) -// } -// } -// } -// } -// override fun execute(context: ComponentMetadataContext) { context.details.withVariant("compile") { -// attributes { -// attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion) -// attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API)) -// } -// } -// } -//} -//dependencies { -// components { -// withModule("commons-io:commons-io") { -// params(7) -// } -// withModule("commons-collections:commons-collections") { -// params(8) -// } -// } -// implementation("commons-io:commons-io:2.6") -// implementation("commons-collections:commons-collections:3.2.2") -//} + +// The Gradle Publish Plugin enables the Javadoc JAR in afterEvaluate, so find it lazily +tasks.withType() + .matching { it.name == "javadocJar" } + .configureEach { + from(createJavadocJarReadme) + } diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts deleted file mode 100644 index e6d5af7..0000000 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publish-test.gradle.kts +++ /dev/null @@ -1,104 +0,0 @@ -package buildsrc.conventions - -import buildsrc.utils.asConsumer -import buildsrc.utils.asProvider -import buildsrc.utils.titlecaseFirstChar - -/** - * Utility for publishing a project to a local Maven directory for use in integration tests. - */ -plugins { - base -} - -abstract class MavenPublishTest( - val testMavenRepo: Provider -) { - companion object { - val attribute = Attribute.of("maven-publish-test", String::class.java) - } -} - -val Gradle.rootGradle: Gradle get() = generateSequence(gradle) { it.parent }.last() - -val mavenPublishTestExtension = extensions.create( - "mavenPublishTest", - gradle.rootGradle.rootProject.layout.buildDirectory.dir("test-maven-repo"), -) - - -val publishToTestMavenRepo by tasks.registering { - group = PublishingPlugin.PUBLISH_TASK_GROUP - description = "Publishes all Maven publications to the test Maven repository." -} - - -plugins.withType().all { - extensions - .getByType() - .publications - .withType().all publication@{ - val publicationName = this@publication.name - val installTaskName = - "publish${publicationName.titlecaseFirstChar()}PublicationToTestMavenRepo" - - // Register a publication task for each publication. - // Use PublishToMavenLocal, because the PublishToMavenRepository task will *always* - // append a timestamp to the JAR for SNAPSHOT versions, even if no code has changed. - // PublishToMavenLocal does not append a timestamp, so the target directory is smaller, and - // up-to-date checks work. - val installTask = tasks.register(installTaskName) { - description = "Publishes Maven publication '$publicationName' to the test Maven repository." - group = PublishingPlugin.PUBLISH_TASK_GROUP - outputs.cacheIf { true } - publication = this@publication - val destinationDir = mavenPublishTestExtension.testMavenRepo.get().asFile - inputs.property("testMavenRepoTempDir", destinationDir.invariantSeparatorsPath) - doFirst { - /** - * `maven.repo.local` will set the destination directory for this [PublishToMavenLocal] task. - * - * @see org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator.getLocalMavenRepository - */ - System.setProperty("maven.repo.local", destinationDir.absolutePath) - } - } - - publishToTestMavenRepo.configure { - dependsOn(installTask) - } - - tasks.check { - mustRunAfter(installTask) - } - } -} - - -val testMavenPublication by configurations.registering { - asConsumer() - isVisible = false - attributes { - attribute(MavenPublishTest.attribute, "testMavenRepo") - } -} - -val testMavenPublicationElements by configurations.registering { - asProvider() - isVisible = true - extendsFrom(testMavenPublication.get()) - attributes { - attribute(MavenPublishTest.attribute, "testMavenRepo") - } - outgoing { - artifact(mavenPublishTestExtension.testMavenRepo) { - builtBy(publishToTestMavenRepo) - } - } -} - -dependencies { - attributesSchema { - attribute(MavenPublishTest.attribute) - } -} diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts new file mode 100644 index 0000000..4dcac80 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts @@ -0,0 +1,126 @@ +package buildsrc.conventions + +import buildsrc.settings.MavenPublishingSettings + +plugins { + `maven-publish` + signing +} + +val mavenPublishing = + extensions.create(MavenPublishingSettings.EXTENSION_NAME, project) + + +//region POM convention +publishing { + publications.withType().configureEach { + pom { + name.convention("Binary Compatibility Validator MU") + description.convention("BCV-MU is a Gradle Plugin that validates the public JVM binary API of libraries, to make sure that breaking changes are tracked.") + url.convention("https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu") + + scm { + connection.convention("scm:git:https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu") + developerConnection.convention("scm:git:https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu") + url.convention("https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu") + } + + licenses { + license { + name.convention("Apache-2.0") + url.convention("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + + developers { + developer { + email.set("adam@adamko.dev") + } + } + } + } +} +//endregion + + +//region Maven Central publishing/signing +publishing { + repositories { + val mavenCentralUsername = mavenPublishing.mavenCentralUsername.orNull + val mavenCentralPassword = mavenPublishing.mavenCentralPassword.orNull + if (!mavenCentralUsername.isNullOrBlank() && !mavenCentralPassword.isNullOrBlank()) { + maven(mavenPublishing.sonatypeReleaseUrl) { + name = "SonatypeRelease" + credentials { + username = mavenCentralUsername + password = mavenCentralPassword + } + } + } + } + + // com.gradle.plugin-publish automatically adds a Javadoc jar +} + +signing { + logger.info("maven-publishing.gradle.kts enabled signing for ${project.path}") + + val keyId = mavenPublishing.signingKeyId.orNull + val key = mavenPublishing.signingKey.orNull + val password = mavenPublishing.signingPassword.orNull + + if (!keyId.isNullOrBlank() && !key.isNullOrBlank() && !password.isNullOrBlank()) { + useInMemoryPgpKeys(keyId, key, password) + } + + setRequired({ + gradle.taskGraph.allTasks.filterIsInstance().any { + it.repository.name == "SonatypeRelease" + } + }) +} + +//afterEvaluate { +// com.gradle.plugin-publish automatically signs tasks in a weird way, that stops this from working: +// signing { +// sign(publishing.publications) +// } +//} +//endregion + + +//region Fix Gradle warning about signing tasks using publishing task outputs without explicit dependencies +// https://youtrack.jetbrains.com/issue/KT-46466 https://github.com/gradle/gradle/issues/26091 +tasks.withType().configureEach { + val signingTasks = tasks.withType() + mustRunAfter(signingTasks) +} +//endregion + + +//region publishing logging +tasks.withType().configureEach { + val publicationGAV = provider { publication?.run { "$group:$artifactId:$version" } } + doLast("log publication GAV") { + if (publicationGAV.isPresent) { + logger.info("[task: ${path}] ${publicationGAV.get()}") + } + } +} +//endregion + + +//region IJ workarounds +// manually define the Kotlin DSL accessors because IntelliJ _still_ doesn't load them properly +fun Project.publishing(configure: PublishingExtension.() -> Unit): Unit = + extensions.configure(configure) + +val Project.publishing: PublishingExtension + get() = extensions.getByType() + +fun Project.signing(configure: SigningExtension.() -> Unit): Unit = + extensions.configure(configure) + +val Project.signing: SigningExtension + get() = extensions.getByType() +//endregion diff --git a/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt b/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt new file mode 100644 index 0000000..73030de --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/settings/MavenPublishingSettings.kt @@ -0,0 +1,68 @@ +package buildsrc.settings + +import java.io.File +import javax.inject.Inject +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.kotlin.dsl.* + + +/** + * Settings for the [buildsrc.conventions.Maven_publish_test_gradle] convention plugin. + */ +abstract class MavenPublishingSettings @Inject constructor( + private val project: Project, + private val providers: ProviderFactory, +) { + + private val isReleaseVersion: Provider = + providers.provider { !project.version.toString().endsWith("-SNAPSHOT") } + + val sonatypeReleaseUrl: Provider = + isReleaseVersion.map { isRelease -> + if (isRelease) { + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + } else { + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + } + } + + val mavenCentralUsername: Provider = + bcvProp("mavenCentralUsername") + .orElse(providers.environmentVariable("MAVEN_SONATYPE_USERNAME")) + val mavenCentralPassword: Provider = + bcvProp("mavenCentralPassword") + .orElse(providers.environmentVariable("MAVEN_SONATYPE_PASSWORD")) + + val signingKeyId: Provider = + bcvProp("signing.keyId") + .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_KEY_ID")) + val signingKey: Provider = + bcvProp("signing.key") + .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_KEY")) + val signingPassword: Provider = + bcvProp("signing.password") + .orElse(providers.environmentVariable("MAVEN_SONATYPE_SIGNING_PASSWORD")) + + val githubPublishDir: Provider = + providers.environmentVariable("GITHUB_PUBLISH_DIR").map { File(it) } + + private fun bcvProp(name: String): Provider = + providers.gradleProperty("dev.adamko.bcv-mu.$name") + + private fun bcvProp(name: String, convert: (String) -> T): Provider = + bcvProp(name).map(convert) + + companion object { + const val EXTENSION_NAME = "mavenPublishing" + + /** Retrieve the [KayrayBuildProperties] extension. */ + internal val Project.mavenPublishing: MavenPublishingSettings + get() = extensions.getByType() + + /** Configure the [KayrayBuildProperties] extension. */ + internal fun Project.mavenPublishing(configure: MavenPublishingSettings.() -> Unit) = + extensions.configure(configure) + } +} diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt b/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt index 480f64c..f18860f 100644 --- a/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt +++ b/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt @@ -1,11 +1,47 @@ package buildsrc.utils +import java.io.File import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.component.AdhocComponentWithVariants +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RelativePath import org.gradle.api.tasks.SourceSet -import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.* +import org.gradle.util.GradleVersion + + +/** The current Gradle version */ +internal val CurrentGradleVersion: GradleVersion + get() = GradleVersion.current() + + +/** @see GradleVersion.compareTo */ +internal operator fun GradleVersion.compareTo(version: String): Int = + compareTo(GradleVersion.version(version)) + + +/** + * Mark this [Configuration] as one that should be used to declare dependencies in + * [Project.dependencies] block. + * + * Declarable Configurations should be extended by [resolvable] and [consumable] Configurations. + * + * ``` + * isCanBeResolved = false + * isCanBeConsumed = false + * isCanBeDeclared = true + * ``` + */ +internal fun Configuration.declarable( + visible: Boolean = false, +) { + isCanBeResolved = false + isCanBeConsumed = false + canBeDeclared(true) + isVisible = visible +} + /** * Mark this [Configuration] as one that will be consumed by other subprojects. @@ -13,24 +49,49 @@ import org.gradle.kotlin.dsl.get * ``` * isCanBeResolved = false * isCanBeConsumed = true + * isCanBeDeclared = false * ``` */ -fun Configuration.asProvider() { +internal fun Configuration.consumable( + visible: Boolean = false, +) { isCanBeResolved = false isCanBeConsumed = true + canBeDeclared(false) + isVisible = visible } + /** * Mark this [Configuration] as one that will consume artifacts from other subprojects (also known as 'resolving') * * ``` * isCanBeResolved = true * isCanBeConsumed = false + * isCanBeDeclared = false * ``` - * */ -fun Configuration.asConsumer() { + */ +internal fun Configuration.resolvable( + visible: Boolean = false, +) { isCanBeResolved = true isCanBeConsumed = false + canBeDeclared(false) + isVisible = visible +} + + +/** + * Enable/disable [Configuration.isCanBeDeclared] only if it is supported by the + * [CurrentGradleVersion] + * + * This function should be removed when the minimal supported Gradle version is 8.2. + */ +@Suppress("UnstableApiUsage") +private fun Configuration.canBeDeclared(value: Boolean) { + if (CurrentGradleVersion >= "8.2") { + isCanBeDeclared = value + } } @@ -81,3 +142,22 @@ fun SourceSet.configurationNames() = javadocElementsConfigurationName, sourcesElementsConfigurationName, ) + +/** exclude generated Gradle code, so it doesn't clog up search results */ +fun ProjectLayout.generatedKotlinDslAccessorDirs(): Set { + + val generatedSrcDirs = listOf( + "kotlin-dsl-accessors", + "kotlin-dsl-external-plugin-spec-builders", + "kotlin-dsl-plugins", + ) + + return projectDirectory.dir("buildSrc/build/generated-sources") + .asFile + .walk() + .filter { it.isDirectory && it.parentFile.name in generatedSrcDirs } + .flatMap { file -> + file.walk().maxDepth(1).filter { it.isDirectory }.toList() + } + .toSet() +} diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/gradlePluginVariants.kt b/buildSrc/src/main/kotlin/buildsrc/utils/gradlePluginVariants.kt deleted file mode 100644 index d5c8f5b..0000000 --- a/buildSrc/src/main/kotlin/buildsrc/utils/gradlePluginVariants.kt +++ /dev/null @@ -1,562 +0,0 @@ -//package buildsrc.utils -// -//import java.util.* -//import org.gradle.api.Action -//import org.gradle.api.Project -//import org.gradle.api.artifacts.Configuration -//import org.gradle.api.artifacts.type.ArtifactTypeDefinition -//import org.gradle.api.attributes.* -//import org.gradle.api.attributes.java.TargetJvmEnvironment -//import org.gradle.api.attributes.java.TargetJvmVersion -//import org.gradle.api.attributes.plugin.GradlePluginApiVersion -//import org.gradle.api.component.AdhocComponentWithVariants -//import org.gradle.api.file.FileCollection -//import org.gradle.api.plugins.JavaLibraryPlugin -//import org.gradle.api.plugins.JavaPlugin -//import org.gradle.api.plugins.JavaPluginExtension -//import org.gradle.api.tasks.Copy -//import org.gradle.api.tasks.SourceSet -//import org.gradle.api.tasks.SourceSetContainer -//import org.gradle.api.tasks.compile.JavaCompile -//import org.gradle.jvm.tasks.Jar -//import org.gradle.kotlin.dsl.* -//import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin -//import org.jetbrains.kotlin.gradle.dsl.KotlinSingleJavaTargetExtension -//import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -//import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -// -///** -// * Gradle plugins common variants. -// */ -//enum class GradlePluginVariant( -// val sourceSetName: String, -// val gradleVersion: String, -// val gradleApiJavadocUrl: String = "https://docs.gradle.org/${gradleVersion}/javadoc/" -//) { -// GRADLE_7("gradle7", "7.6"), -// GRADLE_8("gradle8", "8.1"), -// ; -// -// companion object { -// val GRADLE_MIN = GRADLE_7 -// } -//} -// -//val commonSourceSetName = "common" -// -/////** -//// * Configures common pom configuration parameters -//// */ -////fun Project.configureCommonPublicationSettingsForGradle( -//// signingRequired: Boolean -////) { -//// plugins.withId("maven-publish") { -////// configureDefaultPublishing(signingRequired) -//// -//// extensions.configure { -//// publications -//// .withType() -//// .configureEach { -//// configureKotlinPomAttributes(project) -//// } -//// } -//// } -////} -// -// -///** -// * Common sources for all variants. -// * Should contain classes that are independent of Gradle API version or using minimal supported Gradle api. -// */ -//fun Project.createGradleCommonSourceSet(): SourceSet { -// val commonSourceSet = sourceSets.create(commonSourceSetName) { -// excludeGradleCommonDependencies(this) -// -// // Adding Gradle API to separate configuration, so version will not leak into variants -// val commonGradleApiConfiguration = configurations.create("commonGradleApiCompileOnly") { -// isVisible = false -// isCanBeConsumed = false -// isCanBeResolved = true -// } -// configurations[compileClasspathConfigurationName].extendsFrom(commonGradleApiConfiguration) -// -// dependencies { -// compileOnlyConfigurationName(kotlinStdlib()) -// "commonGradleApiCompileOnly"("dev.gradleplugins:gradle-api:7.6") -// if (this@createGradleCommonSourceSet.name != "kotlin-gradle-plugin-api" && -// this@createGradleCommonSourceSet.name != "android-test-fixes" && -// this@createGradleCommonSourceSet.name != "gradle-warnings-detector" -// ) { -// compileOnlyConfigurationName(project(":kotlin-gradle-plugin-api")) { -// capabilities { -// requireCapability("org.jetbrains.kotlin:kotlin-gradle-plugin-api-common") -// } -// } -// } -// } -// } -// -// plugins.withType().configureEach { -// this@createGradleCommonSourceSet.extensions.configure { -// registerFeature(commonSourceSet.name) { -// usingSourceSet(commonSourceSet) -// disablePublication() -// } -// } -// } -// -// // Common outputs will also produce '${project.name}.kotlin_module' file, so we need to avoid -// // files clash -// tasks.named("compile${commonSourceSet.name.replaceFirstChar { it.uppercase() }}Kotlin") { -// @Suppress("DEPRECATION") -// kotlinOptions { -// moduleName = "${this@createGradleCommonSourceSet.name}_${commonSourceSet.name}" -// } -// } -// -// return commonSourceSet -//} -// -///** -// * Fixes wired SourceSet does not expose compiled common classes and common resources as secondary variant -// * which is used in the Kotlin Project compilation. -// */ -//private fun Project.fixWiredSourceSetSecondaryVariants( -// wireSourceSet: SourceSet, -// commonSourceSet: SourceSet -//) { -// configurations -// .matching { -// it.name == wireSourceSet.apiElementsConfigurationName || -// it.name == wireSourceSet.runtimeElementsConfigurationName -// } -// .configureEach { -// outgoing { -// variants.maybeCreate("classes").apply { -// attributes { -// attribute( -// LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, -// objects.named(LibraryElements.CLASSES) -// ) -// } -// (commonSourceSet.output.classesDirs.files + wireSourceSet.output.classesDirs.files) -// .toSet() -// .forEach { -// if (!artifacts.files.contains(it)) { -// artifact(it) { -// type = ArtifactTypeDefinition.JVM_CLASS_DIRECTORY -// } -// } -// } -// } -// } -// } -// -// configurations -// .matching { it.name == wireSourceSet.runtimeElementsConfigurationName } -// .configureEach { -// outgoing { -// val resourcesDirectories = listOfNotNull( -// commonSourceSet.output.resourcesDir, -// wireSourceSet.output.resourcesDir -// ) -// -// if (resourcesDirectories.isNotEmpty()) { -// variants.maybeCreate("resources").apply { -// attributes { -// attribute( -// LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, -// objects.named(LibraryElements.RESOURCES) -// ) -// } -// resourcesDirectories.forEach { -// if (!artifacts.files.contains(it)) { -// artifact(it) { -// type = ArtifactTypeDefinition.JVM_RESOURCES_DIRECTORY -// } -// } -// } -// } -// } -// } -// } -//} -// -///** -// * Make [wireSourceSet] to extend [commonSourceSet]. -// */ -//fun Project.wireGradleVariantToCommonGradleVariant( -// wireSourceSet: SourceSet, -// commonSourceSet: SourceSet -//) { -// wireSourceSet.compileClasspath += commonSourceSet.output -// wireSourceSet.runtimeClasspath += commonSourceSet.output -// -// // Allowing to use 'internal' classes/methods from common source code -// (extensions.getByName("kotlin") as KotlinSingleJavaTargetExtension).target.compilations.run { -// getByName(wireSourceSet.name).associateWith(getByName(commonSourceSet.name)) -// } -// -// configurations[wireSourceSet.apiConfigurationName].extendsFrom( -// configurations[commonSourceSet.apiConfigurationName] -// ) -// configurations[wireSourceSet.implementationConfigurationName].extendsFrom( -// configurations[commonSourceSet.implementationConfigurationName] -// ) -// configurations[wireSourceSet.runtimeOnlyConfigurationName].extendsFrom( -// configurations[commonSourceSet.runtimeOnlyConfigurationName] -// ) -// configurations[wireSourceSet.compileOnlyConfigurationName].extendsFrom( -// configurations[commonSourceSet.compileOnlyConfigurationName] -// ) -// -// fixWiredSourceSetSecondaryVariants(wireSourceSet, commonSourceSet) -// -// tasks.withType().configureEach { -// if (name == wireSourceSet.jarTaskName) { -// from(wireSourceSet.output, commonSourceSet.output) -// setupPublicJar(archiveBaseName.get()) -// addEmbeddedRuntime() -// } else if (name == wireSourceSet.sourcesJarTaskName) { -// from(wireSourceSet.allSource, commonSourceSet.allSource) -// } -// } -//} -// -//private const val FIXED_CONFIGURATION_SUFFIX = "WithFixedAttribute" -// -///** -// * 'main' sources are used for minimal supported Gradle versions (6.7) up to Gradle 7.0. -// */ -//fun Project.reconfigureMainSourcesSetForGradlePlugin( -// commonSourceSet: SourceSet -//) { -// sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME) { -// plugins.withType().configureEach { -// // Removing Gradle api default dependency added by 'java-gradle-plugin' -// configurations[apiConfigurationName].dependencies.remove(dependencies.gradleApi()) -// } -// -// dependencies { -// "compileOnly"(kotlinStdlib()) -// // Decoupling gradle-api artifact from current project Gradle version. Later would be useful for -// // gradle plugin variants -// "compileOnly"("dev.gradleplugins:gradle-api:${GradlePluginVariant.GRADLE_MIN.gradleApiVersion}") -// if (this@reconfigureMainSourcesSetForGradlePlugin.name != "kotlin-gradle-plugin-api" && -// this@reconfigureMainSourcesSetForGradlePlugin.name != "android-test-fixes" && -// this@reconfigureMainSourcesSetForGradlePlugin.name != "gradle-warnings-detector" -// ) { -// "api"(project(":kotlin-gradle-plugin-api")) -// } -// } -// -// excludeGradleCommonDependencies(this) -// wireGradleVariantToCommonGradleVariant(this, commonSourceSet) -// -// // https://youtrack.jetbrains.com/issue/KT-51913 -// // Remove workaround after bootstrap update -// if (configurations["default"].attributes.contains(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE)) { -// configurations["default"].attributes.attribute( -// TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, -// objects.named(TargetJvmEnvironment::class, "no-op") -// ) -// } -// -// plugins.withType().configureEach { -// this@reconfigureMainSourcesSetForGradlePlugin -// .extensions -// .configure { -// withSourcesJar() -// if (kotlinBuildProperties.publishGradlePluginsJavadoc) { -// withJavadocJar() -// } -// } -// } -// -// // Workaround for https://youtrack.jetbrains.com/issue/KT-52987 -// val javaComponent = project.components["java"] as AdhocComponentWithVariants -// listOf( -// runtimeElementsConfigurationName, -// apiElementsConfigurationName -// ) -// .map { configurations[it] } -// .forEach { originalConfiguration -> -// configurations.create("${originalConfiguration.name}$FIXED_CONFIGURATION_SUFFIX") { -// isCanBeResolved = originalConfiguration.isCanBeResolved -// isCanBeConsumed = originalConfiguration.isCanBeConsumed -// isVisible = originalConfiguration.isVisible -// setExtendsFrom(originalConfiguration.extendsFrom) -// -// artifacts { -// originalConfiguration.artifacts.forEach { -// add(name, it) -// } -// } -// -// // Removing 'org.jetbrains.kotlin.platform.type' attribute -// // as it brings issues with Gradle variant resolve on Gradle 7.6+ versions -// attributes { -// originalConfiguration.attributes.keySet() -// .filter { it.name != KotlinPlatformType.attribute.name } -// .forEach { originalAttribute -> -// @Suppress("UNCHECKED_CAST") -// attribute( -// originalAttribute as Attribute, -// originalConfiguration.attributes.getAttribute(originalAttribute)!! -// ) -// } -// -// plugins.withType { -// tasks.named(compileJavaTaskName).get().apply { -// attribute( -// TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, -// targetCompatibility.toInt() -// ) -// } -// } -// } -// -// val expectedAttributes = setOf( -// Category.CATEGORY_ATTRIBUTE, -// Bundling.BUNDLING_ATTRIBUTE, -// Usage.USAGE_ATTRIBUTE, -// LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, -// TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, -// TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE -// ) -// if (attributes.keySet() != expectedAttributes) { -// error("Wrong set of attributes:\n" + -// " Expected: ${expectedAttributes.joinToString(", ")}\n" + -// " Actual: ${ -// attributes.keySet() -// .joinToString(", ") { "${it.name}=${attributes.getAttribute(it)}" } -// }" -// ) -// } -// -// javaComponent.addVariantsFromConfiguration(this) { -// mapToMavenScope( -// when (originalConfiguration.name) { -// runtimeElementsConfigurationName -> "runtime" -// apiElementsConfigurationName -> "compile" -// else -> error("Unsupported configuration name") -// } -// ) -// } -// -// // Make original configuration unpublishable and not visible -// originalConfiguration.isCanBeConsumed = false -// originalConfiguration.isVisible = false -// javaComponent.withVariantsFromConfiguration(originalConfiguration) { -// skip() -// } -// } -// } -// } -// -// // Fix common sources visibility for tests -// sourceSets.named(SourceSet.TEST_SOURCE_SET_NAME) { -// compileClasspath += commonSourceSet.output -// runtimeClasspath += commonSourceSet.output -// } -// -// // Allowing to use 'internal' classes/methods from common source code -// (extensions.getByName("kotlin") as KotlinSingleJavaTargetExtension).target.compilations.run { -// getByName(SourceSet.TEST_SOURCE_SET_NAME).associateWith(getByName(commonSourceSet.name)) -// } -//} -// -///** -// * Adding plugin variants: https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#plugin-with-variants -// */ -//fun Project.createGradlePluginVariant( -// variant: GradlePluginVariant, -// commonSourceSet: SourceSet, -// isGradlePlugin: Boolean = true -//): SourceSet { -// val variantSourceSet = sourceSets.create(variant.sourceSetName) { -// excludeGradleCommonDependencies(this) -// wireGradleVariantToCommonGradleVariant(this, commonSourceSet) -// } -// -// plugins.withType().configureEach { -// extensions.configure { -// registerFeature(variantSourceSet.name) { -// usingSourceSet(variantSourceSet) -// if (isGradlePlugin) { -// capability(project.group.toString(), project.name, project.version.toString()) -// } -// -// if (kotlinBuildProperties.publishGradlePluginsJavadoc) { -// withJavadocJar() -// } -// withSourcesJar() -// } -// -// configurations.named(variantSourceSet.apiElementsConfigurationName, commonVariantAttributes()) -// configurations.named( -// variantSourceSet.runtimeElementsConfigurationName, -// commonVariantAttributes() -// ) -// } -// -// tasks.named(variantSourceSet.sourcesJarTaskName) { -// addEmbeddedSources() -// } -// } -// -// plugins.withId("java-gradle-plugin") { -// tasks.named(variantSourceSet.processResourcesTaskName) { -// val copyPluginDescriptors = rootSpec.addChild() -// copyPluginDescriptors.into("META-INF/gradle-plugins") -// copyPluginDescriptors.from(tasks.named("pluginDescriptors")) -// } -// } -// -// configurations.configureEach { -// if (isCanBeConsumed && this@configureEach.name.startsWith(variantSourceSet.name)) { -// attributes { -// attribute( -// GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, -// objects.named(variant.minimalSupportedGradleVersion) -// ) -// } -// } -// } -// -// // KT-52138: Make module name the same for all variants, so KSP could access internal methods/properties -// tasks.named("compile${variantSourceSet.name.replaceFirstChar { it.uppercase() }}Kotlin") { -// @Suppress("DEPRECATION") -// kotlinOptions { -// moduleName = this@createGradlePluginVariant.name -// } -// } -// -// dependencies { -// variantSourceSet.compileOnlyConfigurationName(kotlinStdlib()) -// variantSourceSet.compileOnlyConfigurationName("dev.gradleplugins:gradle-api:${variant.gradleApiVersion}") -// if (this@createGradlePluginVariant.name != "kotlin-gradle-plugin-api" && -// this@createGradlePluginVariant.name != "android-test-fixes" && -// this@createGradlePluginVariant.name != "gradle-warnings-detector" -// ) { -// variantSourceSet.apiConfigurationName(project(":kotlin-gradle-plugin-api")) { -// capabilities { -// requireCapability("org.jetbrains.kotlin:kotlin-gradle-plugin-api-${variant.sourceSetName}") -// } -// } -// } -// } -// -// return variantSourceSet -//} -// -///** -// * All additional configuration attributes in plugin variant should be the same as in the 'main' variant. -// * Otherwise, Gradle <7.0 will fail to select plugin variant. -// */ -//private fun Project.commonVariantAttributes(): Action = Action { -// attributes { -// attribute( -// TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, -// objects.named(TargetJvmEnvironment.STANDARD_JVM) -// ) -// } -//} -// -// -//// Will allow combining outputs of multiple SourceSets -//fun Project.publishShadowedJar( -// sourceSet: SourceSet, -// commonSourceSet: SourceSet -//) { -// val jarTask = tasks.named(sourceSet.jarTaskName) -// -// val shadowJarTask = embeddableCompilerDummyForDependenciesRewriting( -// taskName = "$EMBEDDABLE_COMPILER_TASK_NAME${sourceSet.jarTaskName.replaceFirstChar { it.uppercase() }}" -// ) { -// setupPublicJar( -// jarTask.flatMap { it.archiveBaseName }, -// jarTask.flatMap { it.archiveClassifier } -// ) -// addEmbeddedRuntime() -// from(sourceSet.output) -// from(commonSourceSet.output) -// -// // When Gradle traverses the inputs, reject the shaded compiler JAR, -// // which leads to the content of that JAR being excluded as well: -// val compilerDummyJarConfiguration: FileCollection = -// project.configurations.getByName("compilerDummyJar") -// exclude { it.file == compilerDummyJarConfiguration.singleFile } -// } -// -// // Removing artifact produced by Jar task -// if (sourceSet.name == SourceSet.MAIN_SOURCE_SET_NAME) { -// configurations["${sourceSet.runtimeElementsConfigurationName}$FIXED_CONFIGURATION_SUFFIX"] -// .artifacts.removeAll { true } -// configurations["${sourceSet.apiElementsConfigurationName}$FIXED_CONFIGURATION_SUFFIX"] -// .artifacts.removeAll { true } -// } else { -// configurations[sourceSet.runtimeElementsConfigurationName] -// .artifacts.removeAll { true } -// configurations[sourceSet.apiElementsConfigurationName] -// .artifacts.removeAll { true } -// } -// -// // Adding instead artifact from shadow jar task -// configurations { -// artifacts { -// if (sourceSet.name == SourceSet.MAIN_SOURCE_SET_NAME) { -// add( -// "${sourceSet.runtimeElementsConfigurationName}$FIXED_CONFIGURATION_SUFFIX", -// shadowJarTask -// ) -// add("${sourceSet.apiElementsConfigurationName}$FIXED_CONFIGURATION_SUFFIX", shadowJarTask) -// } else { -// add(sourceSet.apiElementsConfigurationName, shadowJarTask) -// add(sourceSet.runtimeElementsConfigurationName, shadowJarTask) -// } -// } -// } -//} -// -////fun Project.addBomCheckTask() { -//// val checkBomTask = tasks.register("checkGradlePluginsBom") { -//// group = "Validation" -//// description = "Check if project is added into Kotlin Gradle Plugins bom" -//// -//// val bomBuildFile = project(":kotlin-gradle-plugins-bom").projectDir.resolve("build.gradle.kts") -//// val exceptions = listOf( -//// project(":gradle:android-test-fixes").path, -//// project(":gradle:gradle-warnings-detector").path, -//// project(":kotlin-gradle-build-metrics").path, -//// project(":kotlin-gradle-statistics").path, -//// ) -//// val projectPath = this@addBomCheckTask.path -//// -//// doLast { -//// if (projectPath in exceptions) return@doLast -//// -//// val constraintsLines = bomBuildFile.readText() -//// .substringAfter("constraints {") -//// .substringBefore("}") -//// .split("\n") -//// .map { it.trim() } -//// -//// val isContainingThisProject = constraintsLines.contains( -//// "api(project(\"$projectPath\"))" -//// ) -//// -//// if (!isContainingThisProject) { -//// throw GradleException(":kotlin-gradle-plugins-bom does not contain $projectPath project constraint!") -//// } -//// } -//// } -//// -//// tasks.named("check") { -//// dependsOn(checkBomTask) -//// } -////} -// -// -//private val Project.sourceSets: SourceSetContainer -// get() = extensions.getByType() diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt b/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt deleted file mode 100644 index 4446475..0000000 --- a/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt +++ /dev/null @@ -1,23 +0,0 @@ -package buildsrc.utils - -import org.gradle.api.file.ProjectLayout -import org.gradle.plugins.ide.idea.model.IdeaModule - - -/** exclude generated Gradle code, so it doesn't clog up search results */ -fun IdeaModule.excludeGeneratedGradleDsl(layout: ProjectLayout) { - - val generatedSrcDirs = listOf( - "kotlin-dsl-accessors", - "kotlin-dsl-external-plugin-spec-builders", - "kotlin-dsl-plugins", - ) - - excludeDirs.addAll( - layout.projectDirectory.asFile.walk() - .filter { it.isDirectory && it.parentFile.name in generatedSrcDirs } - .flatMap { file -> - file.walk().maxDepth(1).filter { it.isDirectory }.toList() - } - ) -} diff --git a/gradle.properties b/gradle.properties index 92c473a..5023d3a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,5 +8,5 @@ org.gradle.unsafe.configuration-cache-problems=warn org.gradle.parallel=true org.gradle.welcome=never -# https://github.com/gradle/gradle/issues/9268#issuecomment-1397116301 -systemProp.org.gradle.unsafe.kotlin.assignment=true +# will be enabled by default in Gradle 9.0 +org.gradle.kotlin.dsl.skipMetadataVersionCheck=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 651c148..1f93161 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,16 @@ [versions] -kotlinGradle = "1.6.0" +kotlinGradle = "1.9.25" -javaDiffUtils = "4.12" -junit = "5.9.3" -kotest = "5.6.1" -kotlinx-bcv = "0.13.1" +javaDiffUtils = "4.16" +junit = "5.13.4" +kotest = "5.9.1" +kotlinx-bcv = "0.13.2" -gradlePluginPublishPlugin = "1.2.0" -shadowPlugin = "8.1.0" -bcvMu = "0.0.3" +gradlePluginPublishPlugin = "1.3.1" +shadowPlugin = "8.1.1" +devPublish = "0.4.2" +bcvMu = "main-SNAPSHOT" supportedGradleVersion = "7.6" # the minimal supported Gradle plugin version, used in functional tests @@ -35,4 +36,5 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun gradlePlugin-bcvMu = { module = "dev.adamko.kotlin.binary_compatibility_validator:bcv-gradle-plugin", version.ref = "bcvMu" } gradlePlugin-pluginPublishing = { module = "com.gradle.publish:plugin-publish-plugin", version.ref = "gradlePluginPublishPlugin" } gradlePlugin-shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadowPlugin" } +gradlePlugin-devPublish = { module = "dev.adamko.gradle:dev-publish-plugin", version.ref = "devPublish" } ## endregion diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7..d64cd49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..e18bc25 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.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d4..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,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 +145,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 +153,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,11 +198,15 @@ 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, 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" \ diff --git a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts index a83f050..432191e 100644 --- a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts @@ -1,6 +1,6 @@ plugins { buildsrc.conventions.`kotlin-gradle-plugin-tests` - buildsrc.conventions.`maven-publish-test` + id("dev.adamko.dev-publish") `java-test-fixtures` `jvm-test-suite` } @@ -8,7 +8,7 @@ plugins { description = "Functional tests for bcv-gradle-plugin" dependencies { - testMavenPublication(projects.modules.bcvGradlePlugin) + devPublication(projects.modules.bcvGradlePlugin) testFixturesApi(gradleTestKit()) @@ -28,7 +28,7 @@ testing.suites { withType().configureEach { targets.configureEach { testTask.configure { - val projectTestTempDirPath = "$buildDir/test-temp-dir" + val projectTestTempDirPath = layout.buildDirectory.dir("test-temp-dir").get().asFile inputs.property("projectTestTempDir", projectTestTempDirPath) systemProperty("projectTestTempDir", projectTestTempDirPath) systemProperty("integrationTestProjectsDir", "$projectDir/projects") @@ -52,10 +52,11 @@ testing.suites { targets.configureEach { testTask.configure { shouldRunAfter(test) - dependsOn(project.configurations.testMavenPublication) - inputs.files(project.configurations.testMavenPublication) - - systemProperty("testMavenRepoDir", file(mavenPublishTest.testMavenRepo).canonicalPath) + dependsOn(tasks.updateDevRepo) + systemProperty( + "devMavenRepoDir", + devPublish.devMavenRepo.asFile.get().invariantSeparatorsPath, + ) } } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaTestFixturesTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaTestFixturesTest.kt index fb9cdb3..2f8f78b 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaTestFixturesTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaTestFixturesTest.kt @@ -125,7 +125,7 @@ private fun FunSpec.createTestFixturesProject( buildGradleKts = """ |plugins { | kotlin("jvm") version "1.7.10" - | id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + | id("dev.adamko.kotlin.binary-compatibility-validator") version "+" | `java-test-fixtures` |} | @@ -141,20 +141,20 @@ private fun FunSpec.createTestFixturesProject( createKotlinFile( "Hello.kt", """ - |class Hello(private val response: String) { - | fun greeting() = response - |} - | - """.trimMargin() + |class Hello(private val response: String) { + | fun greeting() = response + |} + | + """.trimMargin() ) } dir("src/testFixtures/kotlin") { createKotlinFile( "HelloHelper.kt", """ - |fun standardHello() = Hello("standard") - | - """.trimMargin() + |fun standardHello() = Hello("standard") + | + """.trimMargin() ) } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt index 95b6883..bdc6029 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt @@ -152,9 +152,8 @@ buildscript { } } - plugins { - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } include( diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidJavaLibrary.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidJavaLibrary.gradle.kts index 3f9c498..9146c1d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidJavaLibrary.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidJavaLibrary.gradle.kts @@ -1,6 +1,6 @@ plugins { id("com.android.library") - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } android { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidKotlinLibrary.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidKotlinLibrary.gradle.kts index d84c09d..ed1f3b6 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidKotlinLibrary.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidKotlinLibrary.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.library") id("kotlin-android") - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } android { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts index 9114dd9..dca231e 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts @@ -2,7 +2,7 @@ plugins { id("com.android.application").version("7.2.2").apply(false) id("com.android.library").version("7.2.2").apply(false) id("org.jetbrains.kotlin.android").version("1.7.10").apply(false) - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" apply false + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" apply false } tasks.register("clean", Delete::class) { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts index 640c00e..895de7b 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("multiplatform") version "1.5.20" - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } kotlin { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts index 2527abc..8fcf6bf 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("multiplatform") version "1.7.20" - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } kotlin { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin-noKotlinVersion.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin-noKotlinVersion.gradle.kts index 7997e4a..0065e8d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin-noKotlinVersion.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin-noKotlinVersion.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("jvm") - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } dependencies { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts index b4ce1fa..07272f7 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts @@ -1,4 +1,4 @@ plugins { kotlin("jvm") version "1.7.20" - id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest2/kotlin/DefaultConfigTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest2/kotlin/DefaultConfigTests.kt index 18f8e96..07f413a 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest2/kotlin/DefaultConfigTests.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest2/kotlin/DefaultConfigTests.kt @@ -23,7 +23,7 @@ internal class DefaultConfigTests : FunSpec({ buildGradleKts += """ |plugins { | kotlin("jvm") version "1.7.20" - | id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.6-SNAPSHOT" + | id("dev.adamko.kotlin.binary-compatibility-validator") version "+" |} | """.trimMargin() diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt index 471e1cf..1085709 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -2,7 +2,7 @@ package dev.adamko.kotlin.binary_compatibility_validator.test.utils -import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProjectTest.Companion.testMavenRepoPathString +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProjectTest.Companion.devMavenRepoPathString import java.io.File import java.nio.file.Path import java.nio.file.Paths @@ -43,9 +43,9 @@ class GradleProjectTest( val gradleTestKitDir: Path? by optionalSystemProperty(Paths::get) /** file-based Maven Repo that contains the published plugin */ - private val testMavenRepoDir: Path by systemProperty(Paths::get) - val testMavenRepoPathString - get() = testMavenRepoDir + private val devMavenRepoDir: Path by systemProperty(Paths::get) + val devMavenRepoPathString + get() = devMavenRepoDir .toFile() .canonicalFile .absoluteFile @@ -101,20 +101,20 @@ fun gradleKtsProjectTest( settingsGradleKts = """ |rootProject.name = "$projectName" | - |@Suppress("UnstableApiUsage") - |dependencyResolutionManagement { - | repositories { - | mavenCentral() - | maven(file("$testMavenRepoPathString")) - | } + |pluginManagement { + | repositories { + |${devMavenRepoKotlinDsl().prependIndent(" ")} + | mavenCentral() + | gradlePluginPortal() + | } |} | - |pluginManagement { - | repositories { - | gradlePluginPortal() - | mavenCentral() - | maven(file("$testMavenRepoPathString")) - | } + |@Suppress("UnstableApiUsage") + |dependencyResolutionManagement { + | repositories { + |${devMavenRepoKotlinDsl().prependIndent(" ")} + | mavenCentral() + | } |} | """.trimMargin() @@ -122,9 +122,9 @@ fun gradleKtsProjectTest( buildGradleKts = """""" gradleProperties = """ - |kotlin.mpp.stability.nowarn=true - | - """.trimMargin() + |kotlin.mpp.stability.nowarn=true + | + """.trimMargin() build() } @@ -144,34 +144,71 @@ fun gradleGroovyProjectTest( return GradleProjectTest(baseDir = baseDir, testProjectName = testProjectName).apply { settingsGradle = """ - |rootProject.name = "test" - | - |dependencyResolutionManagement { - | repositories { - | mavenCentral() - | maven { url = file("$testMavenRepoPathString") } - | } - |} - | - |pluginManagement { - | repositories { - | gradlePluginPortal() - | mavenCentral() - | maven { url = file("$testMavenRepoPathString") } - | } - |} - | - """.trimMargin() + |rootProject.name = "test" + | + |dependencyResolutionManagement { + | repositories { + | mavenCentral() + |${devMavenRepoGroovyDsl().prependIndent(" ")} + | } + |} + | + |pluginManagement { + | repositories { + |${devMavenRepoGroovyDsl().prependIndent(" ")} + | } + |} + | + """.trimMargin() gradleProperties = """ - |kotlin.mpp.stability.nowarn=true - |org.gradle.cache=true - """.trimMargin() + |kotlin.mpp.stability.nowarn=true + |org.gradle.cache=true + """.trimMargin() build() } } +@Language("kts") +internal fun devMavenRepoKotlinDsl(): String { + return """ + |exclusiveContent { + | forRepository { + | maven(file("$devMavenRepoPathString")) { + | name = "Dev Maven Repo" + | } + | } + | filter { + | includeGroup("dev.adamko.kotlin.binary_compatibility_validator") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.project") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.settings") + | } + |} + """.trimMargin() +} + +@Language("groovy") +private fun devMavenRepoGroovyDsl(): String { + return """ + |exclusiveContent { + | forRepository { + | maven { + | url = file("$devMavenRepoPathString") + | name = "Dev Maven Repo" + | } + | } + | filter { + | includeGroup("dev.adamko.kotlin.binary_compatibility_validator") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.project") + | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.settings") + | } + |} + """.trimMargin() +} + fun GradleProjectTest.projectFile( @Language("TEXT") diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt index 79f3653..d7a5aff 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt @@ -2,7 +2,7 @@ package dev.adamko.kotlin.binary_compatibility_validator.test.utils.api import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProjectTest import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProjectTest.Companion.minimumGradleTestVersion -import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProjectTest.Companion.testMavenRepoPathString +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.devMavenRepoKotlinDsl import dev.adamko.kotlin.binary_compatibility_validator.test.utils.invariantNewlines import java.io.File import org.gradle.testkit.runner.GradleRunner @@ -15,33 +15,19 @@ fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner { baseKotlinScope.settingsGradleKts { addText(/*language=kts*/ """ - |@Suppress("UnstableApiUsage") - |dependencyResolutionManagement { + |pluginManagement { | repositories { + |${devMavenRepoKotlinDsl().prependIndent(" ")} + | gradlePluginPortal() | mavenCentral() - | maven(file("$testMavenRepoPathString")) { - | mavenContent { - | includeGroup("dev.adamko.kotlin.binary_compatibility_validator") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.project") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.settings") - | } - | } | } |} | - |pluginManagement { + |@Suppress("UnstableApiUsage") + |dependencyResolutionManagement { | repositories { - | gradlePluginPortal() + |${devMavenRepoKotlinDsl().prependIndent(" ")} | mavenCentral() - | maven(file("$testMavenRepoPathString")) { - | mavenContent { - | includeGroup("dev.adamko.kotlin.binary_compatibility_validator") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.project") - | includeGroup("dev.adamko.kotlin.binary-compatibility-validator.settings") - | } - | } | } |} | diff --git a/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api b/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api index fce1d78..8b660f6 100644 --- a/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api +++ b/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api @@ -1,8 +1,12 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVPlugin : org/gradle/api/Plugin { + public static final field API_CHECK_TASK_NAME Ljava/lang/String; public static final field API_DIR Ljava/lang/String; + public static final field API_DUMP_TASK_NAME Ljava/lang/String; + public static final field API_GENERATE_TASK_NAME Ljava/lang/String; public static final field Companion Ldev/adamko/kotlin/binary_compatibility_validator/BCVPlugin$Companion; public static final field EXTENSION_NAME Ljava/lang/String; public static final field RUNTIME_CLASSPATH_CONFIGURATION_NAME Ljava/lang/String; + public static final field RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME Ljava/lang/String; public static final field TASK_GROUP Ljava/lang/String; public synthetic fun apply (Ljava/lang/Object;)V public fun apply (Lorg/gradle/api/plugins/PluginAware;)V @@ -27,10 +31,14 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjec } public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjectPlugin : org/gradle/api/Plugin { + public static final field Companion Ldev/adamko/kotlin/binary_compatibility_validator/BCVProjectPlugin$Companion; public synthetic fun apply (Ljava/lang/Object;)V public fun apply (Lorg/gradle/api/Project;)V } +public final class dev/adamko/kotlin/binary_compatibility_validator/BCVProjectPlugin$Companion { +} + public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVSettingsPlugin : org/gradle/api/Plugin { public synthetic fun apply (Ljava/lang/Object;)V public fun apply (Lorg/gradle/api/initialization/Settings;)V diff --git a/modules/bcv-gradle-plugin/build.gradle.kts b/modules/bcv-gradle-plugin/build.gradle.kts index 55b530f..557ff59 100644 --- a/modules/bcv-gradle-plugin/build.gradle.kts +++ b/modules/bcv-gradle-plugin/build.gradle.kts @@ -5,7 +5,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask plugins { buildsrc.conventions.`kotlin-gradle-plugin` - buildsrc.conventions.`maven-publish-test` + buildsrc.conventions.`maven-publishing` + id("dev.adamko.dev-publish") `java-test-fixtures` //com.github.johnrengelman.shadow //buildsrc.conventions.`gradle-plugin-variants` @@ -102,3 +103,30 @@ tasks.withType>().configureEach { binaryCompatibilityValidator { ignoredMarkers.add("dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi") } + + +publishing { + publications { + register("relocation") { + pom { + val relocationMessage = + "Relocated artifact. Replaced underscores with dashes in the Group ID, to match BCV-MU's Gradle Plugin ID." + name = "Binary Compatibility Validator MU [RELOCATION MARKER]" + description = relocationMessage + + // Old artifact coordinates + groupId = "dev.adamko.kotlin.binary_compatibility_validator" + artifactId = "bcv-gradle-plugin" + + distributionManagement { + relocation { + // New artifact coordinates + groupId = project.group.toString() + artifactId = project.name + message = relocationMessage + } + } + } + } + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt index 8eeac8e..3163de2 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt @@ -22,7 +22,12 @@ constructor() : Plugin { companion object { const val API_DIR = "api" const val EXTENSION_NAME = "binaryCompatibilityValidator" - const val TASK_GROUP = "bcv mu" const val RUNTIME_CLASSPATH_CONFIGURATION_NAME = "bcvMuRuntime" + const val RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME = "bcvMuRuntimeResolver" + + const val TASK_GROUP = "bcv mu" + const val API_CHECK_TASK_NAME = "apiCheck" + const val API_DUMP_TASK_NAME = "apiDump" + const val API_GENERATE_TASK_NAME = "apiGenerate" } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt index 79cceee..080fd12 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt @@ -1,23 +1,33 @@ package dev.adamko.kotlin.binary_compatibility_validator +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_CHECK_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_DIR +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_DUMP_TASK_NAME +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_GENERATE_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.EXTENSION_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.RUNTIME_CLASSPATH_CONFIGURATION_NAME +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.declarable +import dev.adamko.kotlin.binary_compatibility_validator.internal.resolvable import dev.adamko.kotlin.binary_compatibility_validator.internal.sourceSets import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiCheckTask import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiDumpTask import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiGenerateTask import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVDefaultTask import javax.inject.Inject +import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration import org.gradle.api.file.ProjectLayout +import org.gradle.api.logging.Logging import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.SourceSet import org.gradle.internal.component.external.model.TestFixturesSupport.TEST_FIXTURE_SOURCESET_NAME import org.gradle.kotlin.dsl.* import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainer @@ -37,21 +47,7 @@ constructor( val extension = createExtension(project) - val bcvGenerateClasspath = - project.configurations.register(RUNTIME_CLASSPATH_CONFIGURATION_NAME) { - isCanBeConsumed = false - isCanBeResolved = true - isVisible = false - defaultDependencies { - addLater( - extension.kotlinxBinaryCompatibilityValidatorVersion.map { version -> - project.dependencies.create( - "org.jetbrains.kotlinx:binary-compatibility-validator:$version" - ) - } - ) - } - } + val bcvGenerateClasspath = createBcvMuClasspath(project, extension) project.tasks.withType().configureEach { bcvEnabled.convention(extension.enabled) @@ -59,7 +55,7 @@ constructor( } project.tasks.withType().configureEach { - runtimeClasspath.from(bcvGenerateClasspath.map { it.incoming.files }) + runtimeClasspath.from(bcvGenerateClasspath) targets.addAllLater(providers.provider { extension.targets }) onlyIf("Must have at least one target") { targets.isNotEmpty() } outputApiBuildDir.convention(layout.buildDirectory.dir("bcv-api")) @@ -76,17 +72,17 @@ constructor( apiDirectory.convention(extension.outputApiDir) } - val apiGenerateTask = project.tasks.register("apiGenerate", BCVApiGenerateTask::class) + val apiGenerateTask = project.tasks.register(API_GENERATE_TASK_NAME, BCVApiGenerateTask::class) - project.tasks.register("apiDump", BCVApiDumpTask::class) { + project.tasks.register(API_DUMP_TASK_NAME, BCVApiDumpTask::class) { apiDumpFiles.from(apiGenerateTask.map { it.outputApiBuildDir }) } - val apiCheckTask = project.tasks.register("apiCheck", BCVApiCheckTask::class) { + val apiCheckTask = project.tasks.register(API_CHECK_TASK_NAME, BCVApiCheckTask::class) { apiBuildDir.convention(apiGenerateTask.flatMap { it.outputApiBuildDir }) } - project.tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME).configure { + project.tasks.named(CHECK_TASK_NAME).configure { dependsOn(apiCheckTask) } @@ -122,6 +118,34 @@ constructor( return extension } + private fun createBcvMuClasspath( + project: Project, + extension: BCVProjectExtension, + ): NamedDomainObjectProvider { + + val bcvGenerateClasspath = + project.configurations.register(RUNTIME_CLASSPATH_CONFIGURATION_NAME) { + description = "Runtime classpath for running binary-compatibility-validator." + declarable() + defaultDependencies { + addLater( + extension.kotlinxBinaryCompatibilityValidatorVersion.map { version -> + project.dependencies.create( + "org.jetbrains.kotlinx:binary-compatibility-validator:$version" + ) + } + ) + } + } + + return project.configurations.register(RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME) { + description = "Resolve the runtime classpath for running binary-compatibility-validator." + resolvable() + isVisible = false + extendsFrom(bcvGenerateClasspath.get()) + } + } + private fun createKotlinJvmTargets( project: Project, extension: BCVProjectExtension, @@ -199,4 +223,8 @@ constructor( } } } + + companion object { + private val logger = Logging.getLogger(BCVProjectPlugin::class.java) + } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVInternalApi.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVInternalApi.kt index 0d95251..3789ed0 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVInternalApi.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVInternalApi.kt @@ -1,17 +1,20 @@ package dev.adamko.kotlin.binary_compatibility_validator.internal +import kotlin.RequiresOptIn.Level.WARNING +import kotlin.annotation.AnnotationRetention.BINARY import kotlin.annotation.AnnotationTarget.* @RequiresOptIn( "Internal API - may change at any time without notice", - level = RequiresOptIn.Level.WARNING + level = WARNING ) -@Retention(AnnotationRetention.BINARY) +@Retention(BINARY) @Target( CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, ) +@MustBeDocumented internal annotation class BCVInternalApi diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt index 11c1b97..fe36551 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt @@ -2,9 +2,96 @@ package dev.adamko.kotlin.binary_compatibility_validator.internal import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectFactory +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.ExtensionContainer import org.gradle.kotlin.dsl.* +import org.gradle.util.GradleVersion + + +/** The current Gradle version */ +internal val CurrentGradleVersion: GradleVersion + get() = GradleVersion.current() + + +/** @see GradleVersion.compareTo */ +internal operator fun GradleVersion.compareTo(version: String): Int = + compareTo(GradleVersion.version(version)) + + +/** + * Mark this [Configuration] as one that should be used to declare dependencies in + * [Project.dependencies] block. + * + * Declarable Configurations should be extended by [resolvable] and [consumable] Configurations. + * + * ``` + * isCanBeResolved = false + * isCanBeConsumed = false + * isCanBeDeclared = true + * ``` + */ +internal fun Configuration.declarable( + visible: Boolean = false, +) { + isCanBeResolved = false + isCanBeConsumed = false + canBeDeclared(true) + isVisible = visible +} + + +/** + * Mark this [Configuration] as one that will be consumed by other subprojects. + * + * ``` + * isCanBeResolved = false + * isCanBeConsumed = true + * isCanBeDeclared = false + * ``` + */ +internal fun Configuration.consumable( + visible: Boolean = true, +) { + isCanBeResolved = false + isCanBeConsumed = true + canBeDeclared(false) + isVisible = visible +} + + +/** + * Mark this [Configuration] as one that will consume artifacts from other subprojects (also known as 'resolving') + * + * ``` + * isCanBeResolved = true + * isCanBeConsumed = false + * isCanBeDeclared = false + * ``` + */ +internal fun Configuration.resolvable( + visible: Boolean = false, +) { + isCanBeResolved = true + isCanBeConsumed = false + canBeDeclared(false) + isVisible = visible +} + + +/** + * Enable/disable [Configuration.isCanBeDeclared] only if it is supported by the + * [CurrentGradleVersion] + * + * This function should be removed when the minimal supported Gradle version is 8.2. + */ +@Suppress("UnstableApiUsage") +private fun Configuration.canBeDeclared(value: Boolean) { + if (CurrentGradleVersion >= "8.2") { + isCanBeDeclared = value + } +} /** diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt index 8d80773..539595f 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt @@ -2,19 +2,22 @@ package dev.adamko.kotlin.binary_compatibility_validator.tasks import com.github.difflib.DiffUtils import com.github.difflib.UnifiedDiffUtils +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_DUMP_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi import dev.adamko.kotlin.binary_compatibility_validator.internal.GradlePath import dev.adamko.kotlin.binary_compatibility_validator.internal.fullPath -import java.io.* +import java.io.File import java.util.TreeMap import javax.inject.Inject -import org.gradle.api.* -import org.gradle.api.file.* +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileVisitDetails +import org.gradle.api.file.RelativePath import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.* +import org.gradle.api.tasks.PathSensitivity.RELATIVE @CacheableTask abstract class BCVApiCheckTask @@ -27,7 +30,7 @@ constructor( @get:InputDirectory @get:Optional - @get:PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(RELATIVE) val projectApiDir: Provider // workaround for https://github.com/gradle/gradle/issues/2016 get() = expectedApiDirPath.flatMap { providers.provider { File(it).takeIf(File::exists) } } @@ -37,7 +40,7 @@ constructor( abstract val expectedApiDirPath: Property @get:InputDirectory - @get:PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(RELATIVE) abstract val apiBuildDir: DirectoryProperty @get:Input @@ -45,7 +48,7 @@ constructor( // Project and tasks paths are used for creating better error messages private val projectFullPath = project.fullPath - private val apiDumpTaskPath = GradlePath(project.path).child("apiDump") + private val apiDumpTaskPath = GradlePath(project.path).child(API_DUMP_TASK_NAME) private val rootDir = project.rootProject.rootDir @@ -99,17 +102,16 @@ constructor( ) } - val diff = compareFiles( + val diffText = compareFiles( checkFile = checkApiDeclaration, builtFile = builtApiDeclaration, - ) - val diffSet = mutableSetOf() - if (diff != null) diffSet.add(diff) - if (diffSet.isNotEmpty()) { - val diffText = diffSet.joinToString("\n\n") + )?.trim() + + if (!diffText.isNullOrBlank()) { error( """ |API check failed for project $projectFullPath. + | |$diffText | |You can run '$apiDumpTaskPath' task to overwrite API declarations @@ -154,12 +156,11 @@ constructor( } /* - * We use case-insensitive comparison to workaround issues with case-insensitive OSes - * and Gradle behaving slightly different on different platforms. - * We neither know original sensitivity of existing .api files, not - * build ones, because projectName that is part of the path can have any sensitivity. - * To work around that, we replace paths we are looking for the same paths that - * actually exist on FS. + * We use case-insensitive comparison to workaround issues with case-insensitive OSes and Gradle + * behaving slightly different on different platforms. We neither know original sensitivity of + * existing .api files, not build ones, because projectName that is part of the path can have any + * sensitivity. To work around that, we replace paths we are looking for the same paths that + * actually exist on the FS. */ private class RelativePaths( private val map: TreeMap = caseInsensitiveMap() diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt index 57fe878..b77f0cb 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt @@ -31,8 +31,9 @@ constructor( val targets: NamedDomainObjectContainer = extensions.adding("targets") { objects.domainObjectContainer() } - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) + @get:Internal + @Deprecated("inputDependencies was unused and can be removed without impact") + @Suppress("unused") abstract val inputDependencies: ConfigurableFileCollection @get:Classpath diff --git a/settings.gradle.kts b/settings.gradle.kts index a0161b5..7da05be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + rootProject.name = "kotlin-binary-compatibility-validator-mu" pluginManagement { @@ -7,7 +9,6 @@ pluginManagement { } } -@Suppress("UnstableApiUsage") dependencyResolutionManagement { repositoriesMode = RepositoriesMode.PREFER_SETTINGS @@ -22,5 +23,87 @@ include( ":modules:bcv-gradle-plugin-functional-tests", ) + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") enableFeaturePreview("STABLE_CONFIGURATION_CACHE") + + +//region git versioning +val gitDescribe: Provider = + providers + .exec { + workingDir(rootDir) + commandLine( + "git", + "describe", + "--always", + "--tags", + "--dirty=-DIRTY", + "--broken=-BROKEN", + "--match=v[0-9]*\\.[0-9]*\\.[0-9]*", + ) + isIgnoreExitValue = true + }.standardOutput.asText.map { it.trim() } + +val currentBranchName: Provider = + providers + .exec { + workingDir(rootDir) + commandLine( + "git", + "branch", + "--show-current", + ) + isIgnoreExitValue = true + }.standardOutput.asText.map { it.trim() } + +val currentCommitHash: Provider = + providers.exec { + workingDir(rootDir) + commandLine( + "git", + "rev-parse", + "--short", + "HEAD", + ) + isIgnoreExitValue = true + }.standardOutput.asText.map { it.trim() } + +/** + * The standard Gradle way of setting the version, which can be set on the CLI with + * + * ```shell + * ./gradlew -Pversion=1.2.3 + * ``` + * + * This can be used to override [gitVersion]. + */ +val standardVersion: Provider = providers.gradleProperty("version") + +/** Match simple SemVer tags. The first group is the `major.minor.patch` digits. */ +val semverRegex = Regex("""v((?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))""") + +val gitVersion: Provider = + gitDescribe.zip(currentBranchName) { described, branch -> + val detached = branch.isNullOrBlank() + + if (!detached) { + "$branch-SNAPSHOT" + // control chars and slashes aren't allowed in Maven Versions + .map { c -> if (c.isISOControl() || c == '/' || c == '\\') "_" else c } + .joinToString("") + } else { + val descriptions = described.split("-") + val head = descriptions.singleOrNull() ?: "" + // drop the leading `v`, try to find the `major.minor.patch` digits group + val headVersion = semverRegex.matchEntire(head)?.groupValues?.last() + headVersion + ?: currentCommitHash.orNull // fall back to using the git commit hash + ?: "unknown" // just in case there's no git repo, e.g. someone downloaded a zip archive + } + } + +gradle.allprojects { + extensions.add>("gitVersion", standardVersion.orElse(gitVersion)) +} +//endregion