diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c064dc80..f50bb2cb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -540,6 +540,39 @@ jobs: FLAMINGOCK_JRELEASER_GPG_SECRET_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_SECRET_KEY }} FLAMINGOCK_JRELEASER_GPG_PASSPHRASE: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PASSPHRASE }} + flamingock-gradle-plugin: + needs: [ build ] + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v4 + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ~/.gradle/build-cache + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Publish to Gradle Plugin Portal + run: ./gradlew :flamingock-gradle-plugin:publishPlugins + env: + GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} + GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} + github-release: needs: [ flamingock-core, @@ -580,7 +613,8 @@ jobs: mongock-importer-couchbase, flamingock-test-support, flamingock-springboot-test-support, - flamingock-sql-auditstore + flamingock-sql-auditstore, + flamingock-gradle-plugin ] uses: ./.github/workflows/github-release.yml secrets: diff --git a/buildSrc/src/main/kotlin/flamingock.project-structure.gradle.kts b/buildSrc/src/main/kotlin/flamingock.project-structure.gradle.kts index 008d2a098..c7d1a8598 100644 --- a/buildSrc/src/main/kotlin/flamingock.project-structure.gradle.kts +++ b/buildSrc/src/main/kotlin/flamingock.project-structure.gradle.kts @@ -73,6 +73,10 @@ val testKitsProjects = setOf( "couchbase-test-kit" ) +val gradlePluginProjects = setOf( + "flamingock-gradle-plugin" +) + val allProjects = coreProjects + cloudProjects + communityProjects + pluginProjects + targetSystemProjects + externalSystemProjects + utilProjects + legacyProjects + testKitsProjects // Project classification utilities @@ -118,11 +122,16 @@ extra["externalSystemProjects"] = externalSystemProjects extra["utilProjects"] = utilProjects extra["legacyProjects"] = legacyProjects extra["testKitsProjects"] = testKitsProjects +extra["gradlePluginProjects"] = gradlePluginProjects extra["allProjects"] = allProjects // Apply appropriate plugins based on project type when { project == rootProject -> { /* Do not publish root project */ } + name in gradlePluginProjects -> { + // Gradle plugin handles its own publishing via com.gradle.plugin-publish + apply(plugin = "flamingock.license") + } isBomModule() -> { apply(plugin = "java-platform") apply(plugin = "flamingock.license") diff --git a/flamingock-gradle-plugin/build.gradle.kts b/flamingock-gradle-plugin/build.gradle.kts new file mode 100644 index 000000000..7033e8b8b --- /dev/null +++ b/flamingock-gradle-plugin/build.gradle.kts @@ -0,0 +1,63 @@ +plugins { + `java-gradle-plugin` + `kotlin-dsl` + id("com.gradle.plugin-publish") version "1.2.1" +} + +dependencies { + implementation(gradleApi()) + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") +} + +gradlePlugin { + website.set("https://www.flamingock.io/") + vcsUrl.set("https://github.com/flamingock/flamingock-java") + + plugins { + create("flamingock") { + id = "io.flamingock" + implementationClass = "io.flamingock.gradle.FlamingockPlugin" + displayName = "Flamingock Gradle Plugin" + description = "Gradle plugin to configure Flamingock with zero boilerplate" + tags.set(listOf( + "flamingock", "change-as-code", "external-systems", + "application-evolution", "gradle-plugin" + )) + } + } +} + +tasks.processResources { + filesMatching("flamingock-plugin.properties") { + expand("pluginVersion" to project.version) + } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } +} + +tasks.test { + useJUnitPlatform() +} + +publishing { + publications { + withType { + pom { + name.set("Flamingock Gradle Plugin") + description.set("Gradle plugin to configure Flamingock with zero boilerplate") + url.set("https://www.flamingock.io/") + licenses { + license { + name.set("Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + } + } + } +} diff --git a/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockExtension.kt b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockExtension.kt new file mode 100644 index 000000000..91bd85334 --- /dev/null +++ b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockExtension.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2024 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.gradle + +/** + * Extension for configuring Flamingock in a Gradle project. + * + * Usage: + * ``` + * flamingock { + * community() + * sql() + * mongodb() + * dynamodb() + * couchbase() + * mongock() + * springboot() + * graalvm() + * } + * ``` + */ +open class FlamingockExtension { + + internal var isCloudEnabled: Boolean = false + private set + + internal var isCommunityEnabled: Boolean = false + private set + + internal var isMongockEnabled: Boolean = false + private set + + internal var isSpringbootEnabled: Boolean = false + private set + + internal var isGraalvmEnabled: Boolean = false + private set + + internal var isSqlEnabled: Boolean = false + private set + + internal var isMongodbEnabled: Boolean = false + private set + + internal var isDynamodbEnabled: Boolean = false + private set + + internal var isCouchbaseEnabled: Boolean = false + private set + + /** + * Enables the Cloud edition of Flamingock. + * + * This is the default edition. If neither [cloud] nor [community] is called, + * cloud is activated automatically. + * + * Adds: + * - `implementation("io.flamingock:flamingock-cloud")` + */ + fun cloud() { + isCloudEnabled = true + } + + /** + * Enables the Community edition of Flamingock. + * + * Adds: + * - `implementation("io.flamingock:flamingock-community")` + */ + fun community() { + isCommunityEnabled = true + } + + /** + * Enables Mongock compatibility for migrating from Mongock to Flamingock. + * + * Adds: + * - `implementation("io.flamingock:mongock-support")` + * - `annotationProcessor("io.flamingock:mongock-support")` + */ + fun mongock() { + isMongockEnabled = true + } + + /** + * Enables Spring Boot integration. + * + * Adds: + * - `implementation("io.flamingock:flamingock-springboot-integration")` + * + * Also switches test support from `flamingock-test-support` to + * `flamingock-springboot-test-support` (which transitively includes the basic one). + */ + fun springboot() { + isSpringbootEnabled = true + } + + /** + * Enables GraalVM native image support. + * + * Adds: + * - `implementation("io.flamingock:flamingock-graalvm")` + */ + fun graalvm() { + isGraalvmEnabled = true + } + + /** + * Enables SQL support. + * + * Adds: + * - `implementation("io.flamingock:flamingock-sql-template")` + * - `implementation("io.flamingock:flamingock-sql-targetsystem")` + */ + fun sql() { + isSqlEnabled = true + } + + /** + * Enables MongoDB support. + * + * Adds: + * - `implementation("io.flamingock:flamingock-mongodb-sync-template")` + * - `implementation("io.flamingock:flamingock-mongodb-sync-targetsystem")` + * + * If [springboot] is also enabled, additionally adds: + * - `implementation("io.flamingock:flamingock-mongodb-springdata-targetsystem")` + */ + fun mongodb() { + isMongodbEnabled = true + } + + /** + * Enables DynamoDB support. + * + * Adds: + * - `implementation("io.flamingock:flamingock-dynamodb-targetsystem")` + */ + fun dynamodb() { + isDynamodbEnabled = true + } + + /** + * Enables Couchbase support. + * + * Adds: + * - `implementation("io.flamingock:flamingock-couchbase-targetsystem")` + */ + fun couchbase() { + isCouchbaseEnabled = true + } +} diff --git a/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockPlugin.kt b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockPlugin.kt new file mode 100644 index 000000000..09fe85cd7 --- /dev/null +++ b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/FlamingockPlugin.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.gradle + +import io.flamingock.gradle.internal.DependencyConfigurator +import io.flamingock.gradle.internal.FlamingockConstants +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Flamingock Gradle Plugin. + * + * Simplifies Flamingock setup by automatically configuring dependencies + * and annotation processors. + * + * Usage: + * ``` + * plugins { + * id("io.flamingock") version "1.0.0" + * } + * + * flamingock { + * community() + * mongock() // optional + * springboot() // optional + * graalvm() // optional + * } + * ``` + */ +class FlamingockPlugin : Plugin { + + override fun apply(project: Project) { + // Apply java plugin if not already applied + if (!project.plugins.hasPlugin("java")) { + project.plugins.apply("java") + } + + // Create and register the extension + val extension = project.extensions.create( + FlamingockConstants.EXTENSION_NAME, + FlamingockExtension::class.java + ) + + // Configure dependencies after project evaluation + project.afterEvaluate { + validateConfiguration(extension) + DependencyConfigurator.configure(project, extension, FlamingockConstants.FLAMINGOCK_VERSION) + } + } + + private fun validateConfiguration(extension: FlamingockExtension) { + if (extension.isCommunityEnabled && extension.isCloudEnabled) { + throw GradleException( + """ + | + |FLAMINGOCK CONFIGURATION ERROR + | + |Both community() and cloud() editions are selected. + | + |Please choose only one: + | + |flamingock { + | community() // OR cloud() (default) + |} + | + """.trimMargin() + ) + } + } +} diff --git a/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/DependencyConfigurator.kt b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/DependencyConfigurator.kt new file mode 100644 index 000000000..f5e9a3307 --- /dev/null +++ b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/DependencyConfigurator.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2024 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.gradle.internal + +import io.flamingock.gradle.FlamingockExtension +import org.gradle.api.Project + +/** + * Configures Flamingock dependencies based on the extension settings. + */ +internal object DependencyConfigurator { + + fun configure(project: Project, extension: FlamingockExtension, version: String) { + val group = FlamingockConstants.GROUP + val dependencies = project.dependencies + + // Always add the annotation processor + dependencies.add( + "annotationProcessor", + "$group:flamingock-processor:$version" + ) + + // Always add BOM for version management + dependencies.add( + "implementation", + dependencies.platform("$group:flamingock-bom:$version") + ) + + // Edition dependencies (cloud is the default) + if (extension.isCommunityEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-community" + ) + } else { + dependencies.add( + "implementation", + "$group:flamingock-cloud" + ) + } + + // Mongock support + if (extension.isMongockEnabled) { + dependencies.add( + "implementation", + "$group:mongock-support" + ) + dependencies.add( + "annotationProcessor", + "$group:mongock-support:$version" + ) + } + + // Spring Boot integration + if (extension.isSpringbootEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-springboot-integration" + ) + } + + // GraalVM support + if (extension.isGraalvmEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-graalvm" + ) + } + + // SQL support + if (extension.isSqlEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-sql-template" + ) + dependencies.add( + "implementation", + "$group:flamingock-sql-targetsystem" + ) + } + + // MongoDB support + if (extension.isMongodbEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-mongodb-sync-template" + ) + dependencies.add( + "implementation", + "$group:flamingock-mongodb-sync-targetsystem" + ) + if (extension.isSpringbootEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-mongodb-springdata-targetsystem" + ) + } + } + + // DynamoDB support + if (extension.isDynamodbEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-dynamodb-targetsystem" + ) + } + + // Couchbase support + if (extension.isCouchbaseEnabled) { + dependencies.add( + "implementation", + "$group:flamingock-couchbase-targetsystem" + ) + } + + // Test support - springboot variant includes basic test-support transitively + if (extension.isSpringbootEnabled) { + dependencies.add("testImplementation", "$group:flamingock-springboot-test-support") + } else { + dependencies.add("testImplementation", "$group:flamingock-test-support") + } + } +} diff --git a/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/FlamingockConstants.kt b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/FlamingockConstants.kt new file mode 100644 index 000000000..5e1f546f6 --- /dev/null +++ b/flamingock-gradle-plugin/src/main/kotlin/io/flamingock/gradle/internal/FlamingockConstants.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.gradle.internal + +/** + * Central location for all Flamingock plugin constants. + */ +internal object FlamingockConstants { + const val GROUP = "io.flamingock" + const val EXTENSION_NAME = "flamingock" + + val FLAMINGOCK_VERSION: String by lazy { + FlamingockConstants::class.java.classLoader + .getResourceAsStream("flamingock-plugin.properties") + ?.bufferedReader() + ?.use { reader -> + java.util.Properties().apply { load(reader) }.getProperty("version") + } + ?: throw IllegalStateException("Could not read flamingock-plugin.properties from classpath") + } +} \ No newline at end of file diff --git a/flamingock-gradle-plugin/src/main/resources/flamingock-plugin.properties b/flamingock-gradle-plugin/src/main/resources/flamingock-plugin.properties new file mode 100644 index 000000000..33b3fc263 --- /dev/null +++ b/flamingock-gradle-plugin/src/main/resources/flamingock-plugin.properties @@ -0,0 +1 @@ +version=${pluginVersion} diff --git a/settings.gradle.kts b/settings.gradle.kts index c12ee0135..43dfc69c8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -167,6 +167,13 @@ project(":legacy:mongock-importer-couchbase").name = "mongock-importer-couchbase project(":legacy:mongock-importer-couchbase").projectDir = file("legacy/mongock-importer-couchbase") +////////////////////////////////////// +// GRADLE PLUGIN +////////////////////////////////////// +include("flamingock-gradle-plugin") +project(":flamingock-gradle-plugin").projectDir = file("flamingock-gradle-plugin") + + ////////////////////////////////////// // E2E TESTS //////////////////////////////////////