From 2bca63b9ff42f242853b99ad393aac492790f6df Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Mon, 4 Mar 2024 11:44:06 +0200 Subject: [PATCH] Improved Dependency Handling with Logging and Safety Checks --- .../kotlin/AndroidHiltConventionPlugin.kt | 14 +++- .../kotlin/AndroidLibraryConventionPlugin.kt | 9 ++- .../kotlin/AndroidRoomConventionPlugin.kt | 18 +++-- .../dev/teogor/ceres/AndroidBuildConfig.kt | 11 --- .../kotlin/dev/teogor/ceres/AndroidCompose.kt | 14 ++-- .../dev/teogor/ceres/GradleManagedDevices.kt | 1 + .../main/kotlin/dev/teogor/ceres/Jacoco.kt | 8 ++- .../kotlin/dev/teogor/ceres/KotlinAndroid.kt | 5 +- .../dev/teogor/ceres/models/DependencyType.kt | 11 +++ .../dev/teogor/ceres/models/LibrarySpec.kt | 54 +++++++++++++++ .../teogor/ceres/utils/DependencyHandler.kt | 67 +++++++++++++++++++ plugin/settings.gradle.kts | 2 + 12 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/DependencyType.kt create mode 100644 plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/LibrarySpec.kt create mode 100644 plugin/library-convention/src/main/kotlin/dev/teogor/ceres/utils/DependencyHandler.kt diff --git a/plugin/library-convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/plugin/library-convention/src/main/kotlin/AndroidHiltConventionPlugin.kt index a091b535..ea77ba9d 100644 --- a/plugin/library-convention/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ b/plugin/library-convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -14,6 +14,9 @@ * limitations under the License. */ +import dev.teogor.ceres.models.hiltAndroid +import dev.teogor.ceres.models.hiltAndroidCompiler +import dev.teogor.ceres.utils.add import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension @@ -30,9 +33,14 @@ class AndroidHiltConventionPlugin : Plugin { val libs = extensions.getByType().named("libs") dependencies { - "implementation"(libs.findLibrary("hilt.android").get()) - "ksp"(libs.findLibrary("hilt.compiler").get()) - "kspAndroidTest"(libs.findLibrary("hilt.compiler").get()) + add( + dependencies = listOf( + hiltAndroid, + hiltAndroidCompiler, + ), + logger = logger, + libs = libs, + ) } } } diff --git a/plugin/library-convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/plugin/library-convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 54ee7a41..e0e1893d 100644 --- a/plugin/library-convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/plugin/library-convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -51,9 +51,12 @@ class AndroidLibraryConventionPlugin : Plugin { val libs = extensions.getByType().named("libs") configurations.configureEach { resolutionStrategy { - force(libs.findLibrary("junit4").get()) - // Temporary workaround for https://issuetracker.google.com/174733673 - force("org.objenesis:objenesis:2.6") + val junit4 = libs.findLibrary("junit4") + if (junit4.isPresent) { + force(libs.findLibrary("junit4").get()) + // Temporary workaround for https://issuetracker.google.com/174733673 + force("org.objenesis:objenesis:2.6") + } } } dependencies { diff --git a/plugin/library-convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/plugin/library-convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index 1f75ca43..47304e2a 100644 --- a/plugin/library-convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/plugin/library-convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -16,6 +16,10 @@ import com.google.devtools.ksp.gradle.KspExtension import dev.teogor.ceres.models.RoomOptionsExtension +import dev.teogor.ceres.models.roomCompiler +import dev.teogor.ceres.models.roomKtx +import dev.teogor.ceres.models.roomRuntime +import dev.teogor.ceres.utils.add import java.io.File import org.gradle.api.Plugin import org.gradle.api.Project @@ -44,7 +48,7 @@ class AndroidRoomConventionPlugin : Plugin { // The schemas directory contains a schema file for each version of the Room database. // This is required to enable Room auto migrations. // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration. - if(roomOptions.enableSchemaProvider) { + if (roomOptions.enableSchemaProvider) { arg(RoomSchemaArgProvider(File(projectDir, roomOptions.schemasPath))) } } @@ -52,9 +56,15 @@ class AndroidRoomConventionPlugin : Plugin { val libs = extensions.getByType().named("libs") dependencies { - add("implementation", libs.findLibrary("room.runtime").get()) - add("implementation", libs.findLibrary("room.ktx").get()) - add("ksp", libs.findLibrary("room.compiler").get()) + add( + dependencies = listOf( + roomRuntime, + roomKtx, + roomCompiler, + ), + logger = logger, + libs = libs, + ) } } } diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidBuildConfig.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidBuildConfig.kt index eda5420f..fff71464 100644 --- a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidBuildConfig.kt +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidBuildConfig.kt @@ -80,17 +80,6 @@ internal fun Project.configureAndroidBuildConfig( val gitHashProvider = providers.of(GitHashValueSource::class) {} - afterEvaluate { - val specificDependency = configurations.findSpecificDependency("dev.teogor.ceres", "bom") - - val version = specificDependency?.version - if (version != null) { - println("[CeresBomVersion] Version of dev.teogor.ceres:bom: $version") - } else { - println("[CeresBomVersion] Dependency dev.teogor.ceres:bom not found or has no version.") - } - } - // Enable BuildConfig generation commonExtension.apply { buildFeatures { diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidCompose.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidCompose.kt index 2e19263f..dd55c48a 100644 --- a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidCompose.kt +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/AndroidCompose.kt @@ -17,13 +17,15 @@ package dev.teogor.ceres import com.android.build.api.dsl.CommonExtension +import dev.teogor.ceres.models.androidxComposeBom +import dev.teogor.ceres.utils.add +import java.io.File import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.File /** * Configure Compose-specific options @@ -43,9 +45,13 @@ internal fun Project.configureAndroidCompose( } dependencies { - val bom = libs.findLibrary("androidx-compose-bom").get() - add("implementation", platform(bom)) - add("androidTestImplementation", platform(bom)) + add( + dependencies = listOf( + androidxComposeBom, + ), + logger = logger, + libs = libs, + ) } } diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/GradleManagedDevices.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/GradleManagedDevices.kt index 12a2847d..75b075b6 100644 --- a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/GradleManagedDevices.kt +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/GradleManagedDevices.kt @@ -24,6 +24,7 @@ import org.gradle.kotlin.dsl.invoke /** * Configure project for Gradle managed devices */ +@Suppress("UnstableApiUsage") internal fun configureGradleManagedDevices( commonExtension: CommonExtension<*, *, *, *, *>, ) { diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/Jacoco.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/Jacoco.kt index a0b94009..820a4286 100644 --- a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/Jacoco.kt +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/Jacoco.kt @@ -17,6 +17,7 @@ package dev.teogor.ceres import com.android.build.api.variant.AndroidComponentsExtension +import java.util.Locale import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.tasks.testing.Test @@ -27,7 +28,6 @@ import org.gradle.kotlin.dsl.withType import org.gradle.testing.jacoco.plugins.JacocoPluginExtension import org.gradle.testing.jacoco.plugins.JacocoTaskExtension import org.gradle.testing.jacoco.tasks.JacocoReport -import java.util.Locale private val coverageExclusions = listOf( // Android @@ -47,7 +47,11 @@ internal fun Project.configureJacoco( val libs = extensions.getByType().named("libs") configure { - toolVersion = libs.findVersion("jacoco").get().toString() + libs.findVersion("jacoco").let { jacoco -> + if (jacoco.isPresent) { + toolVersion = jacoco.get().toString() + } + } } val jacocoTestReport = tasks.create("jacocoTestReport") diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/KotlinAndroid.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/KotlinAndroid.kt index 4fd69ddb..369106de 100644 --- a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/KotlinAndroid.kt +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/KotlinAndroid.kt @@ -87,7 +87,10 @@ internal fun Project.configureKotlinAndroid( defaultValue = true, ) ) { - add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) + val desugarJdkLibs = libs.findLibrary("android.desugarJdkLibs") + if (desugarJdkLibs.isPresent) { + add("coreLibraryDesugaring", desugarJdkLibs.get()) + } } } } diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/DependencyType.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/DependencyType.kt new file mode 100644 index 00000000..1f2f384a --- /dev/null +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/DependencyType.kt @@ -0,0 +1,11 @@ +package dev.teogor.ceres.models + +enum class DependencyType(val gradleNotation: String) { + KSP("ksp"), + KSP_ANDROID_TEST("kspAndroidTest"), + IMPLEMENTATION("implementation"), + API("api"), + ANDROID_TEST_IMPLEMENTATION("androidTestImplementation"), + TEST_IMPLEMENTATION("testImplementation"), + COMPILE_ONLY("compileOnly"), +} diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/LibrarySpec.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/LibrarySpec.kt new file mode 100644 index 00000000..ea0f553e --- /dev/null +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/models/LibrarySpec.kt @@ -0,0 +1,54 @@ +package dev.teogor.ceres.models + +data class LibrarySpec( + val name: String, + val module: String, + val dependencyTypes: List = emptyList(), + val isBom: Boolean = false, +) + +val roomRuntime = LibrarySpec( + name = "room.runtime", + module = "androidx.room:room-runtime", + dependencyTypes = listOf( + DependencyType.IMPLEMENTATION, + ), +) +val roomKtx = LibrarySpec( + name = "room.ktx", + module = "androidx.room:room-ktx", + dependencyTypes = listOf( + DependencyType.IMPLEMENTATION, + ), +) +val roomCompiler = LibrarySpec( + name = "room.compiler", + module = "androidx.room:room-compiler", + dependencyTypes = listOf( + DependencyType.KSP, + ), +) +val hiltAndroid = LibrarySpec( + name = "hilt.android", + module = "com.google.dagger:hilt-android", + dependencyTypes = listOf( + DependencyType.IMPLEMENTATION, + ), +) +val hiltAndroidCompiler = LibrarySpec( + name = "hilt.android.compiler", + module = "com.google.dagger:hilt-android-compiler", + dependencyTypes = listOf( + DependencyType.KSP, + DependencyType.KSP_ANDROID_TEST, + ), +) +val androidxComposeBom = LibrarySpec( + name = "androidx.compose.bom", + module = "androidx.compose:compose-bom", + dependencyTypes = listOf( + DependencyType.IMPLEMENTATION, + DependencyType.ANDROID_TEST_IMPLEMENTATION, + ), + isBom = true, +) diff --git a/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/utils/DependencyHandler.kt b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/utils/DependencyHandler.kt new file mode 100644 index 00000000..a8482027 --- /dev/null +++ b/plugin/library-convention/src/main/kotlin/dev/teogor/ceres/utils/DependencyHandler.kt @@ -0,0 +1,67 @@ +package dev.teogor.ceres.utils + +import dev.teogor.ceres.models.LibrarySpec +import java.util.Optional +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.logging.Logger +import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.DependencyHandlerScope + +fun VersionCatalog.findLibrary( + librarySpec: LibrarySpec, +): Optional> { + return findLibrary(librarySpec.name) +} + +fun DependencyHandlerScope.add( + dependencies: List, + logger: Logger, + libs: VersionCatalog, +) { + dependencies.forEach { libraryDependency -> + val library = libs.findLibrary(libraryDependency) + if (!library.isPresent) { + logger.error("Library alias named \"${libraryDependency.name}\" not found in the version catalog. Please ensure it's properly defined.") + + libs.resolveLibraryDependency(libraryDependency.name)?.let { + logger.warn("Found library with name \"${it.first}\". Using recommended alias \"${libraryDependency.name}\" is encouraged for consistency and clarity.") + add(libraryDependency, it.second) + } + } else { + add(libraryDependency, library.get().get()) + } + } +} + +fun DependencyHandlerScope.add( + librarySpec: LibrarySpec, + moduleProvider: MinimalExternalModuleDependency, +) { + librarySpec.dependencyTypes.forEach { + if (librarySpec.isBom) { + add( + configurationName = it.gradleNotation, + dependencyNotation = platform(moduleProvider), + ) + } else { + add( + configurationName = it.gradleNotation, + dependencyNotation = moduleProvider, + ) + } + } +} + +fun VersionCatalog.resolveLibraryDependency(module: String): Pair? { + val regex = "[._-]".toRegex() + return libraryAliases + .map { libraryAlias -> + Pair(libraryAlias, findLibrary(libraryAlias).get().getOrNull()) + } + .filter { it.second != null } + .map { it.first to it.second!! } + .firstOrNull { + regex.replace(it.second.module.name, ".") == module + } +} diff --git a/plugin/settings.gradle.kts b/plugin/settings.gradle.kts index 49584d36..db29eced 100644 --- a/plugin/settings.gradle.kts +++ b/plugin/settings.gradle.kts @@ -14,6 +14,7 @@ * limitations under the License. */ +@Suppress("UnstableApiUsage") dependencyResolutionManagement { repositories { google() @@ -27,4 +28,5 @@ dependencyResolutionManagement { } rootProject.name = "plugin" + include(":library-convention")