diff --git a/.github/actions/minor-version-bump/action.yml b/.github/actions/minor-version-bump/action.yml new file mode 100644 index 00000000..f6e96a44 --- /dev/null +++ b/.github/actions/minor-version-bump/action.yml @@ -0,0 +1,14 @@ +name: Minor version bump +description: Verifies branch is ready to merge before minor version bump +runs: + using: composite + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure Gradle + uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main + + - name: Verify minor version bump + shell: bash + run: ./gradlew verifyMinorVersionBump diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/CodeStyle.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/CodeStyle.kt index fc927792..732f2655 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/CodeStyle.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/CodeStyle.kt @@ -40,7 +40,7 @@ fun Project.configureLinting(lintPaths: List) { attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.SHADOWED)) } } - ktlint("aws.sdk.kotlin.gradle:ktlint-rules:$repoToolsVersion") + ktlint("aws.sdk.kotlin.gradle:style-rules:$repoToolsVersion") } // add the buildscript classpath which should pick up our custom ktlint-rules (via runtimeOnly dep on this plugin) diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/MinorVersionStrategy.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/MinorVersionStrategy.kt new file mode 100644 index 00000000..0ac80269 --- /dev/null +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/MinorVersionStrategy.kt @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.sdk.kotlin.gradle.dsl + +import aws.sdk.kotlin.gradle.util.verifyRootProject +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.api.attributes.Bundling +import org.gradle.api.tasks.JavaExec +import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.getByType +import org.gradle.language.base.plugins.LifecycleBasePlugin + +/** + * Configures Gradle task for minor-version-bump-specific Ktlint rules. + */ +fun Project.configureMinorVersionStrategyRules(lintPaths: List) { + verifyRootProject { "Task configuration is expected to be configured on the root project" } + + val ktlintVersion = object {} // Can't use Project.javaClass because that's using the Gradle classloader + .javaClass + .getResource("ktlint-version.txt") + ?.readText() + ?: error("Missing ktlint-version.txt") + + val repoToolsVersion = extensions + .getByType() + .named("libs") + .findVersion("aws-kotlin-repo-tools-version") + .get() + .requiredVersion + + val minorVersionBumpKtlint by configurations.creating + + dependencies { + minorVersionBumpKtlint("com.pinterest.ktlint:ktlint-cli:$ktlintVersion") { + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.SHADOWED)) + } + } + minorVersionBumpKtlint("aws.sdk.kotlin.gradle:minor-version-rules:$repoToolsVersion") + } + + tasks.register("verifyMinorVersionBump") { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Check minor version bump rules" + classpath = minorVersionBumpKtlint + mainClass.set("com.pinterest.ktlint.Main") + args = lintPaths + } +} diff --git a/build.gradle.kts b/build.gradle.kts index c2810059..a8c4a664 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,7 +59,7 @@ dependencies { } } - ktlint(project(":ktlint-rules")) + ktlint(project(":ktlint:style-rules")) } val lintPaths = listOf( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1732d286..a1cef139 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,6 @@ aws-sdk-cloudwatch = { module = "aws.sdk.kotlin:cloudwatch", version.ref = "aws- aws-sdk-s3 = { module = "aws.sdk.kotlin:s3", version.ref = "aws-sdk-version" } ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } ktlint-cli-ruleset-core = { module = "com.pinterest.ktlint:ktlint-cli-ruleset-core", version.ref = "ktlint" } -ktlint-test = {module = "com.pinterest.ktlint:ktlint-test", version.ref = "ktlint" } nexus-publish-plugin = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus-plugin-version" } jreleaser-plugin = { module = "org.jreleaser:jreleaser-gradle-plugin", version.ref = "jreleaser-plugin-version" } smithy-model = { module = "software.amazon.smithy:smithy-model", version.ref = "smithy-version" } diff --git a/ktlint/minor-version-rules/build.gradle.kts b/ktlint/minor-version-rules/build.gradle.kts new file mode 100644 index 00000000..324bafcd --- /dev/null +++ b/ktlint/minor-version-rules/build.gradle.kts @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +description = "Lint rules for minor version bumps" + +plugins { + `maven-publish` + kotlin("jvm") +} + +kotlin { + sourceSets { + main { + dependencies { + implementation(libs.ktlint.cli.ruleset.core) + } + } + + test { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +tasks.test { + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + showStackTraces = true + showExceptions = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } +} + +tasks.withType { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) + freeCompilerArgs.add("-Xjdk-release=1.8") + } +} + +tasks.withType { + sourceCompatibility = JavaVersion.VERSION_1_8.toString() + targetCompatibility = JavaVersion.VERSION_1_8.toString() +} + +publishing { + publications { + create("ktlintRules") { + from(components["kotlin"]) + } + } +} diff --git a/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/DeprecatedApiRule.kt b/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/DeprecatedApiRule.kt new file mode 100644 index 00000000..adbe9797 --- /dev/null +++ b/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/DeprecatedApiRule.kt @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.aws.ktlint.rules + +import com.pinterest.ktlint.rule.engine.core.api.ElementType +import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.RuleId +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import java.io.File +import java.util.Properties + +/** + * Matches @DeprecatedUntilVersion with either named args (major=x, minor=y) or positional args (x, y) + */ +internal fun deprecatedUntilVersionRegex(major: Int, minor: Int): Regex = + Regex( + """@DeprecatedUntilVersion\s*\(\s*(?:major\s*=\s*$major\s*,\s*minor\s*=\s*$minor\s*|\s*$major\s*,\s*$minor\s*)\s*\)""", + ) + +/** + * Creates a ktlint rule that detects APIs annotated with @DeprecatedUntilVersion for the upcoming minor version. + */ +class DeprecatedApiRule : Rule(RuleId("minor-version-strategy-rules:deprecated-apis"), About()) { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.elementType == ElementType.ANNOTATION_ENTRY) { + val gradleProperties = Properties().apply { + load(File("gradle.properties").inputStream()) + } + + val sdkVersion = gradleProperties.getProperty("sdkVersion").split(".") + val majorVersion = sdkVersion[0].toInt() + val minorVersion = sdkVersion[1].toInt() + + val regex = deprecatedUntilVersionRegex(majorVersion, minorVersion + 1) + if (regex.containsMatchIn(node.text)) { + emit( + node.startOffset, + "The deprecated API is scheduled for removal, please remove it before releasing the next minor version.", + true, + ) + } + } + } +} diff --git a/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/MinorVersionRuleSetProvider.kt b/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/MinorVersionRuleSetProvider.kt new file mode 100644 index 00000000..5bfc3a97 --- /dev/null +++ b/ktlint/minor-version-rules/src/main/kotlin/software/aws/ktlint/rules/MinorVersionRuleSetProvider.kt @@ -0,0 +1,15 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.aws.ktlint.rules + +import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 +import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.rule.engine.core.api.RuleSetId + +class MinorVersionRuleSetProvider : RuleSetProviderV3(RuleSetId("minor-version-strategy-rules")) { + override fun getRuleProviders() = setOf( + RuleProvider { DeprecatedApiRule() }, + ) +} diff --git a/ktlint/minor-version-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 b/ktlint/minor-version-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 new file mode 100644 index 00000000..45e4adb9 --- /dev/null +++ b/ktlint/minor-version-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 @@ -0,0 +1,4 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +software.aws.ktlint.rules.MinorVersionRuleSetProvider diff --git a/ktlint/minor-version-rules/src/test/kotlin/software/aws/ktlint/rules/DeprecatedApiRuleTest.kt b/ktlint/minor-version-rules/src/test/kotlin/software/aws/ktlint/rules/DeprecatedApiRuleTest.kt new file mode 100644 index 00000000..8bf3e3b2 --- /dev/null +++ b/ktlint/minor-version-rules/src/test/kotlin/software/aws/ktlint/rules/DeprecatedApiRuleTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.aws.ktlint.rules + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class DeprecatedApiRuleTest { + fun runRegexTestCases(minor: Int, major: Int) { + val regex = deprecatedUntilVersionRegex(major, minor) + + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion($major,$minor)")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion($major,$minor )")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion($major, $minor)")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion($major ,$minor)")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion( $major,$minor)")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion( $major , $minor )")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion(major=$major,minor=$minor)")) + assertTrue(regex.containsMatchIn("@DeprecatedUntilVersion( major= $major , minor= $minor )")) + + assertFalse(regex.containsMatchIn("@DeprecatedUntilVersion")) + assertFalse(regex.containsMatchIn("@DeprecatedUntilVersion()")) + assertFalse(regex.containsMatchIn("@DeprecatedUntilVersion($minor,$minor)")) + assertFalse(regex.containsMatchIn("@DeprecatedUntilVersion($major,$major)")) + assertFalse(regex.containsMatchIn("@DeprecatedUntilVersion($minor,$major)")) + } + + @Test + fun testRegex() { + runRegexTestCases(0, 1) + runRegexTestCases(1, 70) + runRegexTestCases(100, 1_000_000) + } +} diff --git a/ktlint-rules/build.gradle.kts b/ktlint/style-rules/build.gradle.kts similarity index 88% rename from ktlint-rules/build.gradle.kts rename to ktlint/style-rules/build.gradle.kts index 65ed219d..0b8b741b 100644 --- a/ktlint-rules/build.gradle.kts +++ b/ktlint/style-rules/build.gradle.kts @@ -19,12 +19,6 @@ kotlin { implementation(libs.ktlint.cli.ruleset.core) } } - - test { - dependencies { - implementation(libs.ktlint.test) - } - } } } diff --git a/ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/CopyrightHeaderRule.kt b/ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/CopyrightHeaderRule.kt similarity index 100% rename from ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/CopyrightHeaderRule.kt rename to ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/CopyrightHeaderRule.kt diff --git a/ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/ExpressionBodyRule.kt b/ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/ExpressionBodyRule.kt similarity index 100% rename from ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/ExpressionBodyRule.kt rename to ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/ExpressionBodyRule.kt diff --git a/ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/MultilineIfElseBlockRule.kt b/ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/MultilineIfElseBlockRule.kt similarity index 100% rename from ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/MultilineIfElseBlockRule.kt rename to ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/MultilineIfElseBlockRule.kt diff --git a/ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/CustomRuleSetProvider.kt b/ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/StyleRuleSetProvider.kt similarity index 85% rename from ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/CustomRuleSetProvider.kt rename to ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/StyleRuleSetProvider.kt index f2fe298d..38446fa6 100644 --- a/ktlint-rules/src/main/kotlin/software/aws/ktlint/rules/CustomRuleSetProvider.kt +++ b/ktlint/style-rules/src/main/kotlin/software/aws/ktlint/rules/StyleRuleSetProvider.kt @@ -8,7 +8,7 @@ import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.RuleSetId -class CustomRuleSetProvider : RuleSetProviderV3(RuleSetId("aws-kotlin-repo-tools-rules")) { +class StyleRuleSetProvider : RuleSetProviderV3(RuleSetId("aws-kotlin-repo-tools-rules")) { override fun getRuleProviders() = setOf( RuleProvider { CopyrightHeaderRule() }, RuleProvider { ExpressionBodyRule() }, diff --git a/ktlint-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 b/ktlint/style-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 similarity index 69% rename from ktlint-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 rename to ktlint/style-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 index 77bc8cdb..b4ad9480 100644 --- a/ktlint-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 +++ b/ktlint/style-rules/src/main/resources/META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 @@ -1,4 +1,4 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -software.aws.ktlint.rules.CustomRuleSetProvider +software.aws.ktlint.rules.StyleRuleSetProvider diff --git a/settings.gradle.kts b/settings.gradle.kts index f031a970..a5697dad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,4 +20,5 @@ dependencyResolutionManagement { include(":build-plugins:build-support") include(":build-plugins:kmp-conventions") include(":build-plugins:smithy-build") -include(":ktlint-rules") +include(":ktlint:style-rules") +include(":ktlint:minor-version-rules")