From 295770002ab1a9bc8723f8d43337e7885a50ec21 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:16:31 +0800 Subject: [PATCH] Extract common build logic to convention plugins --- .github/workflows/ci.yml | 3 + .gitignore | 5 +- app/.gitignore | 2 - app/build.gradle.kts | 56 ++-------------- benchmark/.gitignore | 1 - build-logic/convention/build.gradle.kts | 43 ++++++++++++ .../AndroidApplicationConventionPlugin.kt | 37 +++++++++++ ...iplatformLibraryComposeConventionPlugin.kt | 23 +++++++ .../MultiplatformLibraryConventionPlugin.kt | 66 +++++++++++++++++++ .../src/main/kotlin/com/ehviewer/Kotlin.kt | 36 ++++++++++ .../src/main/kotlin/com/ehviewer/Lint.kt | 9 +++ .../kotlin/com/ehviewer/ProjectExtensions.kt | 8 +++ .../src/main/kotlin/com/ehviewer/Spotless.kt | 22 +++++++ build-logic/gradle.properties | 4 ++ build-logic/settings.gradle.kts | 20 ++++++ build.gradle.kts | 6 +- gradle/libs.versions.toml | 18 ++++- settings.gradle.kts | 5 +- 18 files changed, 302 insertions(+), 62 deletions(-) delete mode 100644 app/.gitignore delete mode 100644 benchmark/.gitignore create mode 100644 build-logic/convention/build.gradle.kts create mode 100644 build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt create mode 100644 build-logic/convention/src/main/kotlin/MultiplatformLibraryComposeConventionPlugin.kt create mode 100644 build-logic/convention/src/main/kotlin/MultiplatformLibraryConventionPlugin.kt create mode 100644 build-logic/convention/src/main/kotlin/com/ehviewer/Kotlin.kt create mode 100644 build-logic/convention/src/main/kotlin/com/ehviewer/Lint.kt create mode 100644 build-logic/convention/src/main/kotlin/com/ehviewer/ProjectExtensions.kt create mode 100644 build-logic/convention/src/main/kotlin/com/ehviewer/Spotless.kt create mode 100644 build-logic/gradle.properties create mode 100644 build-logic/settings.gradle.kts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46a5881bb..915e54720 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,9 @@ jobs: with: cache-read-only: true + - name: Check build-logic + run: ./gradlew :build-logic:convention:check + - name: Spotless Check run: ./gradlew spotlessCheck diff --git a/.gitignore b/.gitignore index bffae2d4b..83324ec6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ .gradle /local.properties .DS_Store -/build -/.kotlin +build +.cxx +.kotlin *.iml .idea .vscode diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 9ff2a3773..000000000 --- a/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/.cxx diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 16e9385a9..9051ac76b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,20 +1,15 @@ import com.mikepenz.aboutlibraries.plugin.DuplicateMode import com.mikepenz.aboutlibraries.plugin.DuplicateRule import java.util.regex.Pattern -import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode val isRelease: Boolean get() = gradle.startParameter.taskNames.any { it.contains("Release") } plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.compose) + alias(libs.plugins.ehviewer.android.application) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) - alias(libs.plugins.spotless) alias(libs.plugins.aboutlibrariesPlugin) - alias(libs.plugins.composeCompilerReportGenerator) alias(libs.plugins.baselineprofile) } @@ -79,9 +74,10 @@ android { flavorDimensions += "api" productFlavors { - create("default") + create("default") { + minSdk = 26 + } create("marshmallow") { - minSdk = 23 applicationIdSuffix = ".m" versionNameSuffix = "-M" } @@ -97,12 +93,6 @@ android { isCoreLibraryDesugaringEnabled = true } - lint { - checkReleaseBuilds = false - disable += setOf("MissingTranslation", "MissingQuantity") - error += setOf("InlinedApi") - } - packaging { dex { useLegacyPackaging = false @@ -254,39 +244,14 @@ dependencies { } kotlin { - jvmToolchain(21) - - // https://kotlinlang.org/docs/gradle-compiler-options.html#all-compiler-options compilerOptions { - jvmDefault = JvmDefaultMode.NO_COMPATIBILITY - progressiveMode = true optIn.addAll( "coil3.annotation.ExperimentalCoilApi", - "androidx.compose.foundation.layout.ExperimentalLayoutApi", - "androidx.compose.material3.ExperimentalMaterial3Api", - "androidx.compose.material3.ExperimentalMaterial3ExpressiveApi", - "androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi", - "androidx.compose.ui.ExperimentalComposeUiApi", - "androidx.compose.foundation.ExperimentalFoundationApi", - "androidx.compose.animation.ExperimentalAnimationApi", - "androidx.compose.animation.ExperimentalSharedTransitionApi", - "androidx.compose.runtime.ExperimentalComposeRuntimeApi", "androidx.paging.ExperimentalPagingApi", - "kotlin.ExperimentalStdlibApi", - "kotlin.concurrent.atomics.ExperimentalAtomicApi", - "kotlin.contracts.ExperimentalContracts", - "kotlin.time.ExperimentalTime", - "kotlinx.coroutines.ExperimentalCoroutinesApi", - "kotlinx.coroutines.FlowPreview", "kotlinx.serialization.ExperimentalSerializationApi", "splitties.experimental.ExperimentalSplittiesApi", "splitties.preferences.DataStorePreferencesPreview", ) - freeCompilerArgs.addAll( - "-Xcontext-parameters", - "-Xwhen-expressions=indy", - "-Xannotation-default-target=param-property", - ) } } @@ -305,16 +270,3 @@ aboutLibraries { duplicationRule = DuplicateRule.GROUP } } - -val ktlintVersion = libs.ktlint.get().version - -spotless { - kotlin { - // https://github.com/diffplug/spotless/issues/111 - target("src/**/*.kt") - ktlint(ktlintVersion) - } - kotlinGradle { - ktlint(ktlintVersion) - } -} diff --git a/benchmark/.gitignore b/benchmark/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/benchmark/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 000000000..6c3471fa8 --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + `kotlin-dsl` + alias(libs.plugins.spotless) +} + +dependencies { + compileOnly(libs.android.gradlePlugin) + compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.spotless.gradlePlugin) +} + +gradlePlugin { + plugins { + register("androidApplication") { + id = libs.plugins.ehviewer.android.application.get().pluginId + implementationClass = "AndroidApplicationConventionPlugin" + } + register("multiplatformLibrary") { + id = libs.plugins.ehviewer.multiplatform.library.asProvider().get().pluginId + implementationClass = "MultiplatformLibraryConventionPlugin" + } + register("multiplatformLibraryCompose") { + id = libs.plugins.ehviewer.multiplatform.library.compose.get().pluginId + implementationClass = "MultiplatformLibraryComposeConventionPlugin" + } + } +} + +kotlin { + jvmToolchain(21) +} + +spotless { + val ktlintVersion = libs.ktlint.get().version + kotlin { + // https://github.com/diffplug/spotless/issues/111 + target("src/**/*.kt") + ktlint(ktlintVersion) + } + kotlinGradle { + ktlint(ktlintVersion) + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt new file mode 100644 index 000000000..2c069a45a --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,37 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.Lint +import com.ehviewer.configureKotlin +import com.ehviewer.configureKotlinCompose +import com.ehviewer.configureLint +import com.ehviewer.configureSpotless +import com.ehviewer.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.assign +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode +import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidExtension + +@Suppress("unused") +class AndroidApplicationConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + apply(plugin = libs.findPlugin("kotlin-android").get().get().pluginId) + apply(plugin = libs.findPlugin("android-application").get().get().pluginId) + apply(plugin = libs.findPlugin("compose-compiler").get().get().pluginId) + apply(plugin = libs.findPlugin("composeCompilerReportGenerator").get().get().pluginId) + + configure { + jvmToolchain(21) + compilerOptions { + configureKotlin() + configureKotlinCompose() + jvmDefault = JvmDefaultMode.NO_COMPATIBILITY + } + } + configure { + lint(Lint::configureLint) + } + configureSpotless() + } +} diff --git a/build-logic/convention/src/main/kotlin/MultiplatformLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/MultiplatformLibraryComposeConventionPlugin.kt new file mode 100644 index 000000000..b07309dcb --- /dev/null +++ b/build-logic/convention/src/main/kotlin/MultiplatformLibraryComposeConventionPlugin.kt @@ -0,0 +1,23 @@ +import com.ehviewer.configureKotlinCompose +import com.ehviewer.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +@Suppress("unused") +class MultiplatformLibraryComposeConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + apply() + apply(plugin = libs.findPlugin("compose-compiler").get().get().pluginId) + apply(plugin = libs.findPlugin("compose-multiplatform").get().get().pluginId) + apply(plugin = libs.findPlugin("composeCompilerReportGenerator").get().get().pluginId) + + configure { + compilerOptions { + configureKotlinCompose() + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/MultiplatformLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/MultiplatformLibraryConventionPlugin.kt new file mode 100644 index 000000000..b649e7c30 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/MultiplatformLibraryConventionPlugin.kt @@ -0,0 +1,66 @@ +import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget +import com.android.build.api.dsl.Lint +import com.android.build.api.dsl.androidLibrary +import com.ehviewer.configureKotlin +import com.ehviewer.configureLint +import com.ehviewer.configureSpotless +import com.ehviewer.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.assign +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +@OptIn(ExperimentalKotlinGradlePluginApi::class) +class MultiplatformLibraryConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + apply(plugin = libs.findPlugin("kotlin-multiplatform").get().get().pluginId) + apply(plugin = libs.findPlugin("android-kotlin-multiplatform-library").get().get().pluginId) + apply(plugin = libs.findPlugin("android-lint").get().get().pluginId) + + configure { + jvmToolchain(21) + compilerOptions { + configureKotlin() + } + + applyHierarchyTemplate { + common { + group("jvm") { + withCompilations { it.target is KotlinMultiplatformAndroidLibraryTarget } + withJvm() + } + } + } + + // jvm("desktop") { + // compilerOptions { + // jvmDefault = JvmDefaultMode.NO_COMPATIBILITY + // } + // } + + androidLibrary { + namespace = "com.ehviewer${path.replace(':', '.')}" + + withHostTestBuilder { + } + + enableCoreLibraryDesugaring = true + + compilerOptions { + jvmDefault = JvmDefaultMode.NO_COMPATIBILITY + } + } + } + configure(Lint::configureLint) + configureSpotless() + + dependencies { + "coreLibraryDesugaring"(libs.findLibrary("desugar").get()) + } + } +} diff --git a/build-logic/convention/src/main/kotlin/com/ehviewer/Kotlin.kt b/build-logic/convention/src/main/kotlin/com/ehviewer/Kotlin.kt new file mode 100644 index 000000000..6ecb4f792 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/ehviewer/Kotlin.kt @@ -0,0 +1,36 @@ +package com.ehviewer + +import org.gradle.kotlin.dsl.assign +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions + +// https://kotlinlang.org/docs/gradle-compiler-options.html#all-compiler-options +internal fun KotlinCommonCompilerOptions.configureKotlin() { + progressiveMode = true + optIn.addAll( + "kotlin.ExperimentalStdlibApi", + "kotlin.concurrent.atomics.ExperimentalAtomicApi", + "kotlin.contracts.ExperimentalContracts", + "kotlin.time.ExperimentalTime", + "kotlinx.coroutines.ExperimentalCoroutinesApi", + "kotlinx.coroutines.FlowPreview", + ) + freeCompilerArgs.addAll( + "-Xcontext-parameters", + "-Xwhen-expressions=indy", + "-Xannotation-default-target=param-property", + ) +} + +internal fun KotlinCommonCompilerOptions.configureKotlinCompose() { + optIn.addAll( + "androidx.compose.animation.ExperimentalAnimationApi", + "androidx.compose.animation.ExperimentalSharedTransitionApi", + "androidx.compose.foundation.ExperimentalFoundationApi", + "androidx.compose.foundation.layout.ExperimentalLayoutApi", + "androidx.compose.material3.ExperimentalMaterial3Api", + "androidx.compose.material3.ExperimentalMaterial3ExpressiveApi", + "androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi", + "androidx.compose.runtime.ExperimentalComposeRuntimeApi", + "androidx.compose.ui.ExperimentalComposeUiApi", + ) +} diff --git a/build-logic/convention/src/main/kotlin/com/ehviewer/Lint.kt b/build-logic/convention/src/main/kotlin/com/ehviewer/Lint.kt new file mode 100644 index 000000000..785c38ae0 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/ehviewer/Lint.kt @@ -0,0 +1,9 @@ +package com.ehviewer + +import com.android.build.api.dsl.Lint + +internal fun Lint.configureLint() { + checkReleaseBuilds = false + disable += setOf("MissingTranslation", "MissingQuantity") + error += setOf("InlinedApi") +} diff --git a/build-logic/convention/src/main/kotlin/com/ehviewer/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/com/ehviewer/ProjectExtensions.kt new file mode 100644 index 000000000..b884b2531 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/ehviewer/ProjectExtensions.kt @@ -0,0 +1,8 @@ +package com.ehviewer + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +internal val Project.libs + get() = extensions.getByType().named("libs") diff --git a/build-logic/convention/src/main/kotlin/com/ehviewer/Spotless.kt b/build-logic/convention/src/main/kotlin/com/ehviewer/Spotless.kt new file mode 100644 index 000000000..c784715c8 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/ehviewer/Spotless.kt @@ -0,0 +1,22 @@ +package com.ehviewer + +import com.diffplug.gradle.spotless.SpotlessExtension +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure + +internal fun Project.configureSpotless() { + apply(plugin = libs.findPlugin("spotless").get().get().pluginId) + + configure { + val ktlintVersion = libs.findLibrary("ktlint").get().get().version + kotlin { + // https://github.com/diffplug/spotless/issues/111 + target("src/**/*.kt") + ktlint(ktlintVersion) + } + kotlinGradle { + ktlint(ktlintVersion) + } + } +} diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties new file mode 100644 index 000000000..0193decb3 --- /dev/null +++ b/build-logic/gradle.properties @@ -0,0 +1,4 @@ +# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534 +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 000000000..2e2f69e1c --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,20 @@ +dependencyResolutionManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") diff --git a/build.gradle.kts b/build.gradle.kts index bb85bc7db..f842cca43 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,11 @@ plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.kotlin.multiplatform.library) apply false + alias(libs.plugins.android.lint) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.spotless) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b91cbc90a..14045c970 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ androidx-work = "2.10.3" arrow = "2.1.2" coil = "3.3.0" compose-bom = "2025.07.01" +compose-multiplatform = "1.8.2" compose-destinations = "2.2.0" compose-preference = "2.1.0" kotlin = "2.2.20-Beta2" @@ -27,6 +28,7 @@ ktor = "3.2.3" okhttp = "5.1.0" okio = "3.16.0" splitties = "3.0.0" +spotless = "7.2.1" telephoto = "0.15.1" xmlutil = "0.91.2" junit = "1.3.0" @@ -121,6 +123,10 @@ androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", versi androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmark" } androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" } +android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } +kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +spotless-gradlePlugin = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } + [bundles] arrow = ["arrow-fx-coroutines", "arrow-functions", "arrow-resilience"] coil = ["coil-compose", "coil-gif", "coil-network"] @@ -131,11 +137,19 @@ splitties = ["splitties-appctx", "splitties-arch-room", "splitties-preferences", [plugins] aboutlibrariesPlugin = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } android-application = { id = "com.android.application", version.ref = "agp" } +android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +android-lint = { id = "com.android.lint", version.ref = "agp" } composeCompilerReportGenerator = { id = "dev.shreyaspatil.compose-compiler-report-generator", version = "1.4.2" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -spotless = { id = "com.diffplug.spotless", version = "7.2.1" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } android-test = { id = "com.android.test", version.ref = "agp" } baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" } + +ehviewer-android-application = { id = "ehviewer.android.application" } +ehviewer-multiplatform-library = { id = "ehviewer.multiplatform.library" } +ehviewer-multiplatform-library-compose = { id = "ehviewer.multiplatform.library.compose" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 43add369d..e692bd408 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { + includeBuild("build-logic") repositories { - gradlePluginPortal() google { content { includeGroupByRegex("com\\.android.*") @@ -9,6 +9,7 @@ pluginManagement { } } mavenCentral() + gradlePluginPortal() } } @@ -32,7 +33,7 @@ plugins { android { compileSdk = 36 - minSdk = 26 + minSdk = 23 targetSdk = 36 ndkVersion = "28.2.13676358" buildToolsVersion = "36.0.0"