diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..6a3cff1 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + compileOnly(libs.plugins.kotlinMultiplatform.asDependency()) + compileOnly(libs.plugins.androidApplication.asDependency()) + compileOnly(libs.plugins.composeMultiplatform.asDependency()) + compileOnly(libs.plugins.composeCompiler.asDependency()) + compileOnly(libs.plugins.composeHotReload.asDependency()) + compileOnly(libs.plugins.kotlinJvm.asDependency()) + compileOnly(libs.plugins.ktor.asDependency()) + compileOnly(libs.plugins.spotless.asDependency()) + compileOnly(libs.plugins.flywayPlugin.asDependency()) +} + +fun Provider.asDependency(): Provider = + map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version.requiredVersion}" } diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..011684d --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,14 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/recipe.android.application.gradle.kts b/build-logic/src/main/kotlin/recipe.android.application.gradle.kts new file mode 100644 index 0000000..bce88da --- /dev/null +++ b/build-logic/src/main/kotlin/recipe.android.application.gradle.kts @@ -0,0 +1,35 @@ +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +plugins { + id("com.android.application") +} + +val libs = extensions.getByType().named("libs") + +android { + namespace = "dev.ulfrx.recipe" + compileSdk = libs.findVersion("android-compileSdk").get().toString().toInt() + + defaultConfig { + applicationId = "dev.ulfrx.recipe" + minSdk = libs.findVersion("android-minSdk").get().toString().toInt() + targetSdk = libs.findVersion("android-targetSdk").get().toString().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} diff --git a/build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts b/build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts new file mode 100644 index 0000000..f8dd887 --- /dev/null +++ b/build-logic/src/main/kotlin/recipe.compose.multiplatform.gradle.kts @@ -0,0 +1,27 @@ +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +plugins { + id("recipe.kotlin.multiplatform") + id("org.jetbrains.compose") + id("org.jetbrains.kotlin.plugin.compose") + id("org.jetbrains.compose.hot-reload") +} + +val libs = extensions.getByType().named("libs") + +kotlin { + sourceSets { + commonMain.dependencies { + implementation(libs.findLibrary("compose-runtime").get()) + implementation(libs.findLibrary("compose-foundation").get()) + implementation(libs.findLibrary("compose-material3").get()) + implementation(libs.findLibrary("compose-ui").get()) + implementation(libs.findLibrary("compose-components-resources").get()) + implementation(libs.findLibrary("androidx-lifecycle-viewmodelCompose").get()) + implementation(libs.findLibrary("androidx-lifecycle-runtimeCompose").get()) + implementation(libs.findLibrary("koin-compose").get()) + implementation(libs.findLibrary("koin-composeViewmodel").get()) + } + } +} diff --git a/build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts b/build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts new file mode 100644 index 0000000..bb2aeb5 --- /dev/null +++ b/build-logic/src/main/kotlin/recipe.jvm.server.gradle.kts @@ -0,0 +1,41 @@ +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +plugins { + id("org.jetbrains.kotlin.jvm") + id("io.ktor.plugin") + id("org.flywaydb.flyway") + application +} + +val libs = extensions.getByType().named("libs") + +kotlin { + jvmToolchain(21) + compilerOptions { + allWarningsAsErrors.set(true) + } +} + +dependencies { + "implementation"(libs.findLibrary("ktor-serverCore").get()) + "implementation"(libs.findLibrary("ktor-serverNetty").get()) + "implementation"(libs.findLibrary("ktor-serverContentNegotiation").get()) + "implementation"(libs.findLibrary("ktor-serializationKotlinxJson").get()) + "implementation"(libs.findLibrary("logback").get()) + "implementation"(libs.findLibrary("flyway-core").get()) + "implementation"(libs.findLibrary("flyway-database-postgresql").get()) + "implementation"(libs.findLibrary("postgresql").get()) + "testImplementation"(libs.findLibrary("ktor-serverTestHost").get()) + "testImplementation"(libs.findLibrary("kotlin-testJunit").get()) +} + +flyway { + url = System.getenv("DATABASE_URL") ?: "jdbc:postgresql://localhost:5432/recipe" + user = System.getenv("DATABASE_USER") ?: "recipe" + password = System.getenv("DATABASE_PASSWORD") ?: "recipe" + locations = arrayOf("classpath:db/migration") + cleanDisabled = true + baselineOnMigrate = true + validateOnMigrate = true +} diff --git a/build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts b/build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts new file mode 100644 index 0000000..9cc76d2 --- /dev/null +++ b/build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts @@ -0,0 +1,55 @@ +// build-logic/src/main/kotlin/recipe.kotlin.multiplatform.gradle.kts +// Establishes the D-05 target matrix + JVM toolchain + common deps. +// Android bytecode is JVM 11 (D-08); server + desktop + shared/jvm are JVM 21. + +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("org.jetbrains.kotlin.multiplatform") +} + +val libs = extensions.getByType().named("libs") + +kotlin { + jvmToolchain(21) + + androidTarget { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + jvm { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { browser() } + + compilerOptions { + allWarningsAsErrors.set(true) + } + + sourceSets { + commonMain.dependencies { + implementation(project.dependencies.platform(libs.findLibrary("koin-bom").get())) + implementation(libs.findLibrary("koin-core").get()) + implementation(libs.findLibrary("kermit").get()) + } + commonTest.dependencies { + implementation(libs.findLibrary("kotlin-test").get()) + } + } +} diff --git a/build-logic/src/main/kotlin/recipe.quality.gradle.kts b/build-logic/src/main/kotlin/recipe.quality.gradle.kts new file mode 100644 index 0000000..18fa24d --- /dev/null +++ b/build-logic/src/main/kotlin/recipe.quality.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("com.diffplug.spotless") +} + +spotless { + kotlin { + target("src/**/*.kt") + targetExclude("**/build/**", "**/generated/**") + ktlint() + } + kotlinGradle { + target("*.gradle.kts") + ktlint() + } + format("markdown") { + target("*.md", "docs/**/*.md") + endWithNewline() + trimTrailingWhitespace() + } +} + +// D-11 redundancy guard: if a module applies recipe.quality alongside a Kotlin plugin +// (multiplatform or jvm), ensure allWarningsAsErrors still applies even if the module +// build didn't already configure it. Guarded with plugins.withId so this plugin is +// safely composable even when applied alone (no KotlinCompilationTask type available +// on the classpath until a Kotlin plugin is present). +plugins.withId("org.jetbrains.kotlin.multiplatform") { + tasks.withType>().configureEach { + compilerOptions { + allWarningsAsErrors.set(true) + } + } +} +plugins.withId("org.jetbrains.kotlin.jvm") { + tasks.withType>().configureEach { + compilerOptions { + allWarningsAsErrors.set(true) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 0116bfc..b628fb1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,4 +9,6 @@ plugins { alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.ktor) apply false + alias(libs.plugins.spotless) apply false + alias(libs.plugins.flywayPlugin) apply false } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1be7746..31dbd58 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,7 @@ rootProject.name = "recipe" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { + includeBuild("build-logic") repositories { google { mavenContent {