diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9b1e4c5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @JosephSanjaya @kcw-grunt @josikie \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e6c85a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 5 diff --git a/config/detekt-rule.yml b/config/detekt-rule.yml new file mode 100644 index 0000000..d758164 --- /dev/null +++ b/config/detekt-rule.yml @@ -0,0 +1,149 @@ +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'FileBasedFindingsReport' + +output-reports: + active: true + exclude: + - 'TxtOutputReport' + - 'XmlOutputReport' + +comments: + active: false + +complexity: + active: false + +coroutines: + active: false + +empty-blocks: + active: false + +exceptions: + active: false + +formatting: + active: true + android: true + ImportOrdering: + active: false + autoCorrect: false + MaximumLineLength: + active: true + maxLineLength: 120 + excludes: + - "**/test/**" + - "**/androidTest/**" + - "**/testFixtures/**" + - "**/roboletricTest/**" + Filename: + active: false + PackageName: + active: false + +naming: + active: false + +performance: + active: false + +potential-bugs: + active: false + +style: + active: true + UnusedParameter: + active: false + UnusedPrivateProperty: + active: false + ForbiddenImport: + active: false + imports: [ ] + forbiddenPatterns: '(ktor.*|koin.*|kotlinx.datetime.*)' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + allowedPatterns: '' + MaxLineLength: + active: true + maxLineLength: 120 + excludeCommentStatements: true + excludes: + - "**/test/**" + - "**/androidTest/**" + - "**/testFixtures/**" + - "**/roboletricTest/**" + UnnecessaryAbstractClass: + active: true + ignoreAnnotated: + - Module + MagicNumber: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + - '0.2' + - '1024' + UnusedPrivateMember: + active: true + ignoreAnnotated: + - 'Preview' + ReturnCount: + active: false + +TwitterCompose: + CompositionLocalAllowlist: + active: false + # You can optionally define a list of CompositionLocals that are allowed here + # allowedCompositionLocals: LocalSomething,LocalSomethingElse + CompositionLocalNaming: + active: true + ContentEmitterReturningValues: + active: true + # You can optionally add your own composables here + # contentEmitters: MyComposable,MyOtherComposable + ModifierComposable: + active: true + ModifierMissing: + active: true + ModifierReused: + active: true + ModifierWithoutDefault: + active: true + MultipleEmitters: + active: true + # You can optionally add your own composables here + # contentEmitters: MyComposable,MyOtherComposable + MutableParams: + active: true + ComposableNaming: + active: true + # You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters) + # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter + ComposableParamOrder: + active: true + PreviewNaming: + active: true + PreviewPublic: + active: true + # You can optionally disable that only previews with @PreviewParameter are flagged + # previewPublicOnlyIfParams: false + RememberMissing: + active: true + UnstableCollections: + active: true + ViewModelForwarding: + active: false + ViewModelInjection: + active: true diff --git a/convention/build.gradle.kts b/convention/build.gradle.kts new file mode 100644 index 0000000..7903697 --- /dev/null +++ b/convention/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + `java-gradle-plugin` + `kotlin-dsl` + alias(grunt.plugins.ksp) + alias(grunt.plugins.jacoco) +} + +dependencies { + implementation(platform(grunt.koin.bom)) + implementation(grunt.bundles.koin.nonandroid) + implementation(grunt.koin.annotation) + ksp(grunt.koin.ksp) + compileOnly(grunt.plugin.agp) + compileOnly(grunt.plugin.kgp) + compileOnly(grunt.plugin.ksp) + compileOnly(grunt.plugin.detekt) + testImplementation(grunt.junit.jupiter) + testImplementation(grunt.junit.jupiter.api) + testImplementation(grunt.mockk) + testRuntimeOnly(grunt.junit.jupiter.engine) + testImplementation(kotlin("test")) + testImplementation(gradleTestKit()) +} + +extensions.configure { + toolVersion = grunt.versions.jacoco.get() +} + +tasks.test { + useJUnitPlatform() +} + +tasks.withType().configureEach { + if (name.startsWith("test") && name.endsWith("UnitTest")) { + extensions.configure(JacocoTaskExtension::class) { + isIncludeNoLocationClasses = true + excludes = listOf("jdk.internal.*") + } + } +} + +afterEvaluate { + tasks.withType { + group = "verification" + val testTask = tasks.named("test") + dependsOn(testTask) + classDirectories.setFrom( + fileTree(layout.buildDirectory.dir("classes/kotlin/main/com")) + ) + val sources = listOf( + layout.projectDirectory.file("src/main/kotlin") + ) + sourceDirectories.setFrom(sources) + executionData.setFrom( + layout.buildDirectory.dir("jacoco").get() + .asFileTree.matching { include("**/test.exec") } + ) + reports { + xml.required.set(true) + html.required.set(true) + } + } +} + +gradlePlugin { + plugins { + register("detekt") { + id = "com.gruntsoftware.buildlogic.detekt" + implementationClass = "com.gruntsoftware.buildlogic.common.plugins.DetektConventionPlugin" + } + register("androidTest") { + id = "com.gruntsoftware.buildlogic.test" + implementationClass = "com.gruntsoftware.buildlogic.android.plugins.TestConventionPlugin" + } + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/AndroidDependenciesApplicator.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/AndroidDependenciesApplicator.kt new file mode 100644 index 0000000..7ffe720 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/AndroidDependenciesApplicator.kt @@ -0,0 +1,172 @@ +package com.gruntsoftware.buildlogic.android.components.dependency + +import com.gruntsoftware.buildlogic.common.components.BuildLogicLogger +import com.gruntsoftware.buildlogic.common.components.DependenciesFinder +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam + +@Factory +class AndroidDependenciesApplicator( + @InjectedParam private val project: Project, + private val logger: BuildLogicLogger, + private val dependenciesFinder: DependenciesFinder = ComponentProvider.provide(project) +) : DependenciesApplicator { + + override fun implementation(notation: Provider) { + project.dependencies.add("implementation", notation) + notation.orNull?.toString()?.let { + logger.i(TAG, "----> $it") + } + } + + override fun implementations(vararg alias: String) { + logger.i(TAG, "Adding library implementations: ") + alias.forEach { + implementation(it) + } + } + + override fun implementation(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + implementation(dependency) + } + + override fun kapt(notation: Provider) { + project.dependencies.add("kapt", notation) + logger.i(TAG, "Adding library kapt: ${notation.orNull?.name}") + } + + override fun kapt(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + kapt(dependency) + } + + override fun ksp(notation: Provider) { + project.dependencies.add("ksp", notation) + logger.i(TAG, "Adding ksp implementation: ${notation.orNull?.name}") + } + + override fun ksp(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + ksp(dependency) + } + + override fun implementationPlatform(notation: Provider) { + project.dependencies.add("implementation", project.dependencies.platform(notation)) + logger.i(TAG, "Adding platform library implementation: ${notation.orNull?.name}") + } + + override fun implementationPlatform(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + implementationPlatform(dependency) + } + + override fun apiPlatform(notation: Provider) { + project.dependencies.add("api", project.dependencies.platform(notation)) + logger.i(TAG, "Adding platform library implementation: ${notation.orNull?.name}") + } + + override fun apiPlatform(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + apiPlatform(dependency) + } + + override fun testImplementationPlatform(notation: Provider) { + project.dependencies.add("testImplementation", project.dependencies.platform(notation)) + logger.i(TAG, "Adding platform test implementation: ${notation.orNull?.name}") + } + + override fun testImplementationPlatform(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + testImplementationPlatform(dependency) + } + + override fun androidTestImplementationPlatform(notation: Provider) { + project.dependencies.add( + "androidTestImplementation", + project.dependencies.platform(notation) + ) + logger.i( + TAG, + "Adding android test platform library implementation: ${notation.orNull?.name}" + ) + } + + override fun androidTestImplementationPlatform(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + androidTestImplementationPlatform(dependency) + } + + override fun androidTestImplementation(notation: Provider) { + project.dependencies.add("androidTestImplementation", notation) + logger.i(TAG, "Adding android test library implementation: ${notation.orNull?.name}") + } + + override fun androidTestImplementation(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + androidTestImplementation(dependency) + } + + override fun debugImplementation(notation: Provider) { + project.dependencies.add("debugImplementation", notation) + notation.orNull?.toString()?.let { + logger.i(TAG, "----> $it") + } + } + + override fun debugImplementation(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + debugImplementation(dependency) + } + + override fun debugImplementations(vararg alias: String) { + logger.i(TAG, "Adding library debug implementations: ") + alias.forEach { debugImplementation(it) } + } + + override fun testImplementation(notation: Provider) { + project.dependencies.add("testImplementation", notation) + logger.i(TAG, "Adding test library implementation: ${notation.orNull?.name}") + } + + override fun testImplementation(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + testImplementation(dependency) + } + + override fun detektPlugin(notation: Provider) { + project.dependencies.add("detektPlugins", notation) + } + + override fun detektPlugin(alias: String) { + val dependency = dependenciesFinder.findLibrary(alias) + detektPlugin(dependency) + } + + override fun detektPlugins(vararg aliases: String) { + logger.i(TAG, "Adding detekt plugins: ") + aliases.forEach { detektPlugin(it) } + } + + override fun testImplementations(vararg alias: String) { + logger.i(TAG, "Adding library test implementations: ") + alias.forEach { + testImplementation(it) + } + } + + override fun androidTestImplementations(vararg alias: String) { + logger.i(TAG, "Adding library android test implementations: ") + alias.forEach { + androidTestImplementation(it) + } + } + + companion object { + private const val TAG = "AndroidDependenciesApplicator" + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/DependenciesApplicator.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/DependenciesApplicator.kt new file mode 100644 index 0000000..c2e1c9f --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/dependency/DependenciesApplicator.kt @@ -0,0 +1,35 @@ +package com.gruntsoftware.buildlogic.android.components.dependency + +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider +import org.koin.core.component.KoinComponent + +interface DependenciesApplicator : KoinComponent { + fun implementation(notation: Provider) + fun implementation(alias: String) + fun implementations(vararg alias: String) + fun kapt(notation: Provider) + fun kapt(alias: String) + fun ksp(notation: Provider) + fun ksp(alias: String) + fun implementationPlatform(notation: Provider) + fun implementationPlatform(alias: String) + fun apiPlatform(notation: Provider) + fun apiPlatform(alias: String) + fun testImplementationPlatform(notation: Provider) + fun testImplementationPlatform(alias: String) + fun androidTestImplementationPlatform(notation: Provider) + fun androidTestImplementationPlatform(alias: String) + fun androidTestImplementation(notation: Provider) + fun androidTestImplementation(alias: String) + fun debugImplementation(notation: Provider) + fun debugImplementation(alias: String) + fun debugImplementations(vararg alias: String) + fun testImplementation(notation: Provider) + fun testImplementation(alias: String) + fun detektPlugin(notation: Provider) + fun detektPlugin(alias: String) + fun detektPlugins(vararg aliases: String) + fun testImplementations(vararg alias: String) + fun androidTestImplementations(vararg alias: String) +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/setup/TestSetup.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/setup/TestSetup.kt new file mode 100644 index 0000000..372f44a --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/components/setup/TestSetup.kt @@ -0,0 +1,201 @@ +package com.gruntsoftware.buildlogic.android.components.setup + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.internal.core.InternalBaseVariant +import com.gruntsoftware.buildlogic.android.components.dependency.AndroidDependenciesApplicator +import com.gruntsoftware.buildlogic.android.utils.AndroidProjectTypeChecker +import com.gruntsoftware.buildlogic.common.components.BuildLogicLogger +import com.gruntsoftware.buildlogic.common.components.VersionFinder +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.the +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 org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent + +@Factory +class TestSetup( + @InjectedParam private val project: Project, + private val logger: BuildLogicLogger, + private val dependenciesApplicator: AndroidDependenciesApplicator = ComponentProvider.provide( + project + ), + private val projectTypeChecker: AndroidProjectTypeChecker = ComponentProvider.provide(project), + private val versionFinder: VersionFinder = ComponentProvider.provide(project), +) : KoinComponent { + + fun setup() { + logger.title(TAG, "Setting up test framework for project: ${project.name}") + applyTestDependencies() + configureAndroidTestOptions() + configureJacoco() + setupJacocoReports() + } + + private fun applyTestDependencies() { + val testDependencies = arrayOf( + "junit", + "junit-vintage-engine", + "junit-jupiter", + "junit-jupiter-api", + "junit-jupiter-engine", + "kotlin-test", + "mockk", + "mockk-android", + "mockk-agent", + "turbine", + "coroutines-test" + ) + dependenciesApplicator.testImplementations(*testDependencies) + dependenciesApplicator.androidTestImplementations(*testDependencies) + } + + private fun configureAndroidTestOptions() { + if (!projectTypeChecker.isAppOrLib()) return + + val extensionType = when { + projectTypeChecker.isApp() -> AppExtension::class + projectTypeChecker.isLib() -> LibraryExtension::class + else -> return + } + + project.the(extensionType).apply { + testOptions { + unitTests.all { it.useJUnitPlatform() } + } + } + } + + private fun configureJacoco() { + if (!projectTypeChecker.isAppOrLib()) return + + project.pluginManager.apply("jacoco") + project.extensions.configure { + toolVersion = versionFinder.find("jacoco").toString() + } + project.tasks.withType().configureEach { + if (name.startsWith("test") && name.endsWith("UnitTest")) { + extensions.configure(JacocoTaskExtension::class) { + isIncludeNoLocationClasses = true + excludes = listOf("jdk.internal.*") + } + } + } + } + + private fun setupJacocoReports() { + project.afterEvaluate { + val androidVariants = projectTypeChecker.let { checker -> + when { + checker.isAppOrLib() -> { + val extension = project.extensions.getByType(BaseExtension::class.java) + if (checker.isApp()) (extension as AppExtension).applicationVariants + else (extension as LibraryExtension).libraryVariants + } + + else -> null + } + } + androidVariants?.filter { it.buildType.name == "debug" } + ?.forEach { variant -> + val variantName = variant.name.replaceFirstChar { + if (it.isLowerCase()) it.titlecase() else it.toString() + } + val testTask = project.tasks.named("test${variantName}UnitTest") + registerJacocoReportTask(variantName, testTask, variant) + } + } + } + + private fun registerJacocoReportTask( + variantName: String, + testTask: TaskProvider, + variant: InternalBaseVariant + ) { + project.tasks.register("jacoco${variantName}Report") { + group = "verification" + dependsOn(testTask) + classDirectories.setFrom( + project.layout.buildDirectory.dir("tmp/kotlin-classes/${variant.name}") + .get().asFileTree.matching { exclude(JACOCO_FILE_FILTER) }, + project.layout.buildDirectory.dir("intermediates/javac/${variant.name}/classes") + .get().asFileTree.matching { exclude(JACOCO_FILE_FILTER) } + ) + val sources = listOf( + project.layout.projectDirectory.file("src/main/java"), + project.layout.projectDirectory.file("src/main/kotlin") + ) + sourceDirectories.setFrom(sources) + executionData.setFrom( + project.layout.buildDirectory.dir("jacoco").get() + .asFileTree.matching { include("**/test${variantName}UnitTest.exec") } + ) + reports { + xml.required.set(true) + html.required.set(true) + } + } + } + + private companion object { + const val TAG = "TestSetup" + private val JACOCO_FILE_FILTER = listOf( + // Dagger / Hilt generated + "**/*_MembersInjector.class", + "**/*_Factory.class", + "**/*Module.*", + "**/*_HiltModules_*", + "**/*Hilt_*", + "**/hilt_aggregated_deps/**", + "**/dagger/**", + "**/di/**", + + // AssistedInject / Anvil + "**/*_AssistedFactory.class", + "**/*_AssistedInject*.class", + "**/*_GeneratedInjector.class", + + // Android generated + "**/R.class", + "**/R$*.class", + "**/BuildConfig.*", + "**/Manifest*.*", + + // AndroidX / Lifecycle + "**/*_ViewBinding*", + "**/*_ViewModel*", + "**/*_ViewModelFactory*", + "**/*_Impl*", + + // Data binding + "**/databinding/**/*", + "**/BR.class", + + // Compose + "**/*ComposableSingletons*", + "**/*PreviewParameter*", + + // Other generated (kapt, etc.) + "**/*MapperImpl*", + "**/*ModuleDeps*", + "**/*KtLambda*", + + // Koin generated + "**/*Definition*", + "**/*Koin*", + "**/*Module*", + "**/_KSP_**", + ) + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/plugins/TestConventionPlugin.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/plugins/TestConventionPlugin.kt new file mode 100644 index 0000000..435b5ae --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/plugins/TestConventionPlugin.kt @@ -0,0 +1,14 @@ +package com.gruntsoftware.buildlogic.android.plugins + +import com.gruntsoftware.buildlogic.android.components.setup.TestSetup +import com.gruntsoftware.buildlogic.common.plugins.BasePlugin +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project + +class TestConventionPlugin : BasePlugin() { + override fun apply(target: Project) { + super.apply(target) + val testConventionPlugin: TestSetup = ComponentProvider.provide(target) + testConventionPlugin.setup() + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeChecker.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeChecker.kt new file mode 100644 index 0000000..7098b77 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeChecker.kt @@ -0,0 +1,24 @@ +package com.gruntsoftware.buildlogic.android.utils + +import org.gradle.api.Project +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam + +@Factory +class AndroidProjectTypeChecker( + @InjectedParam private val project: Project +) { + fun isApp(): Boolean { + return project.pluginManager.hasPlugin("com.android.application") + } + + fun isLib(): Boolean { + return project.pluginManager.hasPlugin("com.android.library") + } + + fun isAppOrLib(): Boolean { + val isApp = isApp() + val isLib = isLib() + return isApp || isLib + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/BuildLogicLogger.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/BuildLogicLogger.kt new file mode 100644 index 0000000..d437898 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/BuildLogicLogger.kt @@ -0,0 +1,30 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.common.utils.C +import org.koin.core.annotation.Single + +@Single +class BuildLogicLogger { + + fun i(message: String) { + printMessage(message) + } + + fun title(tag: String, message: String) { + i("=======================================================================================") + i(tag, message) + i("=======================================================================================") + } + + fun i(tag: String, message: String) { + printMessage(message, tag) + } + + private fun printMessage( + msg: String, + tag: String? = null, + ) { + val assignee = if (!tag.isNullOrBlank()) "[$tag]" else "" + println("${C.GLOBAL_TAG}$assignee: $msg") + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinder.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinder.kt new file mode 100644 index 0000000..7dcd0be --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinder.kt @@ -0,0 +1,46 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.gradle.api.artifacts.ExternalModuleDependencyBundle +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.provider.Provider +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent +import kotlin.jvm.optionals.getOrNull + +@Factory +class DependenciesFinder( + @InjectedParam private val project: Project, + private val buildLogicLogger: BuildLogicLogger, + private val versionCatalogProvider: VersionCatalogProvider = ComponentProvider.provide(project) +) : KoinComponent { + + fun findLibrary(alias: String): Provider { + var versionCatalog = "" + val library = versionCatalogProvider.getAll().firstOrNull { + it.findLibrary(alias).getOrNull() != null + }?.also { + versionCatalog = it.name + }?.findLibrary(alias)?.getOrNull()?.also { + buildLogicLogger.i(TAG, "----> Found $alias on version catalog: $versionCatalog") + } + return requireNotNull(library) { + "[$TAG]: Cannot find plugin with alias: $alias. Please check your version catalog." + } + } + + fun findBundle(alias: String): Provider { + val bundle = versionCatalogProvider.getAll().firstOrNull { + it.findBundle(alias).getOrNull() != null + }?.findBundle(alias)?.getOrNull() + return requireNotNull(bundle) { + "[$TAG]: Cannot find plugin with alias: $alias. Please check your version catalog." + } + } + + companion object { + private const val TAG = "DependenciesFinder" + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DetektSetup.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DetektSetup.kt new file mode 100644 index 0000000..317414e --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/DetektSetup.kt @@ -0,0 +1,164 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.android.components.dependency.AndroidDependenciesApplicator +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import io.gitlab.arturbosch.detekt.Detekt +import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask +import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import org.gradle.api.Project +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent + +/** + * Sets up Detekt for the given project. + * + * Detekt is a static code analysis tool for Kotlin. This class configures Detekt + * with sensible defaults, including: + * - Applying the Detekt plugin + * - Configuring the Detekt extension + * - Configuring the Detekt tasks + * - Adding Detekt dependencies + * - Attaching the Detekt task to the build process + * + * @property project The Gradle project to set up Detekt for. + * @property buildLogicLogger The logger to use for logging. + */ +@Factory +class DetektSetup( + @InjectedParam private val project: Project, + private val buildLogicLogger: BuildLogicLogger, + private val versionFinder: VersionFinder = ComponentProvider.provide(project), + private val dependenciesApplicator: AndroidDependenciesApplicator = ComponentProvider.provide( + project + ), + private val pluginApplicator: PluginApplicator = ComponentProvider.provide(project) +) : KoinComponent { + + fun setup() { + buildLogicLogger.title(TAG, "Setting up Detekt for project: ${project.name}") + project.applyDetekt() + } + + private fun Project.applyDetekt() { + runCatching { + applyDetektPlugin() + }.onSuccess { + configureDetekt() + configureDetektTasks() + addDetektDependencies() + attachDetektTask() + }.onFailure { + buildLogicLogger.i(TAG, "Failed to apply detekt plugin: ${it.message}") + } + } + + private fun applyDetektPlugin() { + pluginApplicator.applyPluginsByAliases("detekt") + .also { + buildLogicLogger.i( + TAG, + "Success applying detekt plugin, starting configuration.." + ) + } + } + + private fun Project.configureDetekt() { + the().apply { + this@configureDetekt.configureDetektExtension(this) + } + } + + private fun Project.configureDetektExtension(extension: DetektExtension) { + extension.apply { + buildUponDefaultConfig = true + allRules = false + autoCorrect = true + config.setFrom(determineDetektConfig()) + baseline = file("../config/detekt-${project.name}-baseline.xml") + parallel = true + } + } + + private fun Project.determineDetektConfig() = + if (rootProject.file("config/detekt-rule.yml").exists()) { + rootProject.file("config/detekt-rule.yml") + .also { buildLogicLogger.i(TAG, "Using external detekt config file: $it") } + } else { + rootProject.file("gruntsoftware-build-logic/config/detekt-rule.yml") + .also { buildLogicLogger.i(TAG, "Using default detekt config file: $it") } + } + + private fun Project.configureDetektTasks() { + val jvmTarget = versionFinder.find("jvm-target").toString() + tasks.withType().configureEach { + configureDetektTask(this, jvmTarget) + } + tasks.withType().configureEach { + this.jvmTarget = jvmTarget + } + } + + private fun Project.configureDetektTask(task: Detekt, jvmTarget: String) { + task.apply { + this.jvmTarget = jvmTarget + setSource( + files( + "src/main/java", + "src/main/kotlin", + "src/test/java", + "src/test/kotlin", + "src/androidTest/java", + "src/androidTest/kotlin", + "src/androidDeviceTest/java", + "src/androidDeviceTest/kotlin", + "src/androidHostTest/java", + "src/androidHostTest/kotlin", + "src/commonMain/java", + "src/commonMain/kotlin", + "src/commonTest/java", + "src/commonTest/kotlin", + "src/androidMain/java", + "src/androidMain/kotlin", + "src/iosMain/java", + "src/iosMain/kotlin", + "src/jvmMain/java", + "src/jvmMain/kotlin" + ) + ) + exclude("**/build/**") + reports { + html.required.set(true) + xml.required.set(true) + txt.required.set(true) + sarif.required.set(true) + md.required.set(true) + } + } + } + + private fun addDetektDependencies() { + dependenciesApplicator.detektPlugins( + "detekt-formatting", + "detekt-twitter" + ) + } + + private fun Project.attachDetektTask() { + runCatching { + tasks.whenTaskAdded { + if (name.startsWith("assemble") || name.startsWith("compile") || name == "run") { + dependsOn(tasks.getByName("detekt")) + } + } + }.onFailure { + buildLogicLogger.i(TAG, "Failed to attach detekt task: ${it.message}") + } + } + + companion object { + const val TAG = "DetektSetup" + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginApplicator.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginApplicator.kt new file mode 100644 index 0000000..5d9dd5f --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginApplicator.kt @@ -0,0 +1,40 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent + +@Factory +class PluginApplicator( + @InjectedParam private val project: Project, + private val logger: BuildLogicLogger, + private val pluginFinder: PluginFinder = ComponentProvider.provide(project) +) : KoinComponent { + + fun applyPluginsByIds(vararg ids: String) { + logger.i(TAG, "Applying plugin by ids: ") + ids.forEach { applyPluginById(it) } + } + + fun applyPluginsByAliases(vararg aliases: String) { + logger.i(TAG, "Applying plugin by aliases: ") + aliases.forEach { applyPluginByAlias(it) } + } + + private fun applyPluginById(id: String) { + project.pluginManager.apply(id) + logger.i(TAG, "----> $id") + } + + private fun applyPluginByAlias(alias: String) { + val pluginId = pluginFinder.find(alias).pluginId + project.pluginManager.apply(pluginId) + logger.i(TAG, "----> $alias") + } + + companion object { + private const val TAG = "PluginApplicator" + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginFinder.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginFinder.kt new file mode 100644 index 0000000..a7cfef3 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/PluginFinder.kt @@ -0,0 +1,35 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.gradle.plugin.use.PluginDependency +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent +import kotlin.jvm.optionals.getOrNull + +@Factory +class PluginFinder( + @InjectedParam private val project: Project, + private val buildLogicLogger: BuildLogicLogger, + private val versionCatalogProvider: VersionCatalogProvider = ComponentProvider.provide(project) +) : KoinComponent { + + fun find(alias: String): PluginDependency { + var versionCatalog = "" + val plugin = versionCatalogProvider.getAll().firstOrNull { + it.findPlugin(alias).getOrNull() != null + }?.also { + versionCatalog = it.name + }?.findPlugin(alias)?.getOrNull()?.orNull?.also { + buildLogicLogger.i(TAG, "----> Found $alias on version catalog: $versionCatalog") + } + return requireNotNull(plugin) { + "[$TAG]: Cannot find plugin with alias: $alias. Please check your version catalog." + } + } + + private companion object { + const val TAG = "PluginFinder" + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionCatalogProvider.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionCatalogProvider.kt new file mode 100644 index 0000000..8e20e2c --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionCatalogProvider.kt @@ -0,0 +1,49 @@ +package com.gruntsoftware.buildlogic.common.components + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam + +@Factory +class VersionCatalogProvider( + @InjectedParam private val project: Project, + private val logger: BuildLogicLogger +) { + + val libs + get(): VersionCatalog = project.getVersionCatalogByName(LIBS_NAME) + + val grunt + get(): VersionCatalog = project.getVersionCatalogByName(GRUNT_NAME) + + fun getAll(): List { + return listOf( + libs, + grunt + ).sortedBy { + it.name == "libs" + } + } + + private fun Project.getVersionCatalogByName(name: String): VersionCatalog { + return runCatching { + extensions.getByType().named(name) + }.getOrElse { + this@VersionCatalogProvider.logger.i(TAG, "Cannot find version catalog named: $name") + this@VersionCatalogProvider.logger.i( + TAG, + "please add version catalog on your setting.gradle.kts first" + ) + throw it + } + } + + companion object { + private const val TAG = "VersionCatalogProvider" + private const val LIBS_NAME = "libs" + private const val GRUNT_NAME = "grunt" + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionFinder.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionFinder.kt new file mode 100644 index 0000000..be00888 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/components/VersionFinder.kt @@ -0,0 +1,35 @@ +package com.gruntsoftware.buildlogic.common.components + +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionConstraint +import org.koin.core.annotation.Factory +import org.koin.core.annotation.InjectedParam +import org.koin.core.component.KoinComponent +import kotlin.jvm.optionals.getOrNull + +@Factory +class VersionFinder( + @InjectedParam private val project: Project, + private val buildLogicLogger: BuildLogicLogger, + private val versionCatalogProvider: VersionCatalogProvider = ComponentProvider.provide(project) +) : KoinComponent { + + fun find(alias: String): VersionConstraint { + var versionCatalog = "" + val version = versionCatalogProvider.getAll().firstOrNull { + it.findVersion(alias).getOrNull() != null + }?.also { + versionCatalog = it.name + }?.findVersion(alias)?.getOrNull()?.also { + buildLogicLogger.i(TAG, "----> Found $alias on version catalog: $versionCatalog") + } + return requireNotNull(version) { + "[$TAG]: Cannot find version with alias: $alias. Please check your version catalog." + } + } + + private companion object { + const val TAG = "VersionFinder" + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/BasePlugin.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/BasePlugin.kt new file mode 100644 index 0000000..f382207 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/BasePlugin.kt @@ -0,0 +1,21 @@ +package com.gruntsoftware.buildlogic.common.plugins + +import com.gruntsoftware.buildlogic.di.BuildLogicModule +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.koin.core.component.KoinComponent +import org.koin.core.context.startKoin +import org.koin.ksp.generated.module + +open class BasePlugin : Plugin, KoinComponent { + override fun apply(target: Project) { + runCatching { + startKoin { + printLogger() + modules( + listOf(BuildLogicModule.module) + ) + } + } + } +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/DetektConventionPlugin.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/DetektConventionPlugin.kt new file mode 100644 index 0000000..aadd5ec --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/plugins/DetektConventionPlugin.kt @@ -0,0 +1,13 @@ +package com.gruntsoftware.buildlogic.common.plugins + +import com.gruntsoftware.buildlogic.common.components.DetektSetup +import com.gruntsoftware.buildlogic.common.utils.ComponentProvider +import org.gradle.api.Project + +class DetektConventionPlugin : BasePlugin() { + override fun apply(target: Project) { + super.apply(target) + val detektSetup: DetektSetup = ComponentProvider.provide(target) + detektSetup.setup() + } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/C.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/C.kt new file mode 100644 index 0000000..e2769e9 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/C.kt @@ -0,0 +1,5 @@ +package com.gruntsoftware.buildlogic.common.utils + +object C { + const val GLOBAL_TAG = "[Build Logic]" +} \ No newline at end of file diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/ComponentProvider.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/ComponentProvider.kt new file mode 100644 index 0000000..8819f63 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/common/utils/ComponentProvider.kt @@ -0,0 +1,14 @@ +package com.gruntsoftware.buildlogic.common.utils + +import org.gradle.api.Project +import org.koin.core.parameter.ParametersHolder +import org.koin.core.parameter.parametersOf +import org.koin.java.KoinJavaComponent + +object ComponentProvider { + inline fun provide( + noinline parameterHolder: () -> ParametersHolder = { parametersOf() } + ): T = KoinJavaComponent.inject(T::class.java, parameters = parameterHolder).value + + inline fun provide(project: Project) = provide { parametersOf(project) } +} diff --git a/convention/src/main/kotlin/com/gruntsoftware/buildlogic/di/BuildLogicModule.kt b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/di/BuildLogicModule.kt new file mode 100644 index 0000000..bf75cd0 --- /dev/null +++ b/convention/src/main/kotlin/com/gruntsoftware/buildlogic/di/BuildLogicModule.kt @@ -0,0 +1,10 @@ +package com.gruntsoftware.buildlogic.di + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan( + "com.gruntsoftware.buildlogic" +) +object BuildLogicModule diff --git a/convention/src/test/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeCheckerTest.kt b/convention/src/test/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeCheckerTest.kt new file mode 100644 index 0000000..8376613 --- /dev/null +++ b/convention/src/test/kotlin/com/gruntsoftware/buildlogic/android/utils/AndroidProjectTypeCheckerTest.kt @@ -0,0 +1,71 @@ +package com.gruntsoftware.buildlogic.android.utils + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import org.gradle.api.Project +import org.gradle.api.plugins.PluginManager +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class AndroidProjectTypeCheckerTest { + + private lateinit var project: Project + private lateinit var pluginManager: PluginManager + private lateinit var projectTypeChecker: AndroidProjectTypeChecker + + @BeforeEach + fun setUp() { + project = mockk(relaxed = true) + pluginManager = mockk(relaxed = true) + + every { project.pluginManager } returns pluginManager + + projectTypeChecker = AndroidProjectTypeChecker(project) + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `isApp returns true when com_android_application plugin is applied`() { + every { pluginManager.hasPlugin("com.android.application") } returns true + + assertTrue(projectTypeChecker.isApp()) + assertFalse(projectTypeChecker.isLib()) + assertTrue(projectTypeChecker.isAppOrLib()) + } + + @Test + fun `isLib returns true when com_android_library plugin is applied`() { + every { pluginManager.hasPlugin("com.android.library") } returns true + + assertFalse(projectTypeChecker.isApp()) + assertTrue(projectTypeChecker.isLib()) + assertTrue(projectTypeChecker.isAppOrLib()) + } + + @Test + fun `isAppOrLib returns false when no Android plugin is applied`() { + every { pluginManager.hasPlugin(any()) } returns false + + assertFalse(projectTypeChecker.isApp()) + assertFalse(projectTypeChecker.isLib()) + assertFalse(projectTypeChecker.isAppOrLib()) + } + + @Test + fun `isAppOrLib returns true when both app and lib plugins are applied`() { + every { pluginManager.hasPlugin("com.android.application") } returns true + every { pluginManager.hasPlugin("com.android.library") } returns true + + assertTrue(projectTypeChecker.isApp()) + assertTrue(projectTypeChecker.isLib()) + assertTrue(projectTypeChecker.isAppOrLib()) + } +} diff --git a/convention/src/test/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinderTest.kt b/convention/src/test/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinderTest.kt new file mode 100644 index 0000000..12cfe00 --- /dev/null +++ b/convention/src/test/kotlin/com/gruntsoftware/buildlogic/common/components/DependenciesFinderTest.kt @@ -0,0 +1,111 @@ +package com.gruntsoftware.buildlogic.common.components + +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import io.mockk.verify +import org.gradle.api.Project +import org.gradle.api.artifacts.ExternalModuleDependencyBundle +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.internal.provider.Providers +import org.gradle.api.provider.Provider +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.jvm.optionals.getOrNull +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class DependenciesFinderTest { + + lateinit var project: Project + lateinit var buildLogicLogger: BuildLogicLogger + lateinit var versionCatalogProvider: VersionCatalogProvider + lateinit var dependenciesFinder: DependenciesFinder + lateinit var catalog: VersionCatalog + + @BeforeEach + fun setup() { + project = mockk(relaxed = true) + buildLogicLogger = mockk(relaxed = true) + versionCatalogProvider = mockk(relaxed = true) + catalog = mockk(relaxed = true) + + every { versionCatalogProvider.getAll() } returns listOf(catalog) + + dependenciesFinder = DependenciesFinder( + project, + buildLogicLogger, + versionCatalogProvider + ) + } + + @AfterEach + fun tearDown() { + unmockkAll() + } + + @Test + fun `findLibrary should return dependency when alias exists`() { + val alias = "koin-core" + val dependency: MinimalExternalModuleDependency = mockk() + val provider: Provider = Providers.of(dependency) + + every { catalog.name } returns "libs" + every { catalog.findLibrary(alias).getOrNull() } returns provider + + val result = dependenciesFinder.findLibrary(alias) + + assertEquals(dependency, result.get()) + verify { + buildLogicLogger.i( + "DependenciesFinder", + "----> Found $alias on version catalog: libs" + ) + } + } + + @Test + fun `findLibrary should throw when alias does not exist`() { + val alias = "nonexistent" + + every { catalog.findLibrary(alias).getOrNull() } returns null + + val ex = assertFailsWith { + dependenciesFinder.findLibrary(alias) + } + assertEquals( + "[DependenciesFinder]: Cannot find plugin with alias: $alias. Please check your version catalog.", + ex.message + ) + } + + @Test + fun `findBundle should return bundle when alias exists`() { + val alias = "koin-bundle" + val bundle: ExternalModuleDependencyBundle = mockk() + val provider: Provider = Providers.of(bundle) + + every { catalog.findBundle(alias).getOrNull() } returns provider + + val result = dependenciesFinder.findBundle(alias) + + assertEquals(bundle, result.get()) + } + + @Test + fun `findBundle should throw when alias does not exist`() { + val alias = "missing-bundle" + + every { catalog.findBundle(alias).getOrNull() } returns null + + val ex = assertFailsWith { + dependenciesFinder.findBundle(alias) + } + assertEquals( + "[DependenciesFinder]: Cannot find plugin with alias: $alias. Please check your version catalog.", + ex.message + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3b3be9a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,67 @@ +[versions] +agp = "8.13.0" +jvm-target = "17" +kotlin-core = "2.2.10" +ksp = "2.2.20-2.0.2" +detekt = "1.23.8" +junit5 = "5.13.4" +mockk = "1.14.5" +turbine = "1.2.1" +jacoco = "0.8.13" + +# Concurrency +coroutines = "1.10.2" + +# Dependency Injection +koin = "4.1.1" +koin-annotation = "2.1.0" + +[libraries] +# Concurrency +coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } + +# Dependency Injection - Koin Core +koin-bom = { group = "io.insert-koin", name = "koin-bom", version.ref = "koin" } +koin-core = { group = "io.insert-koin", name = "koin-core" } +koin-core-coroutines = { group = "io.insert-koin", name = "koin-core-coroutines" } +koin-annotation = { group = "io.insert-koin", name = "koin-annotations", version.ref = "koin-annotation" } +koin-ksp = { group = "io.insert-koin", name = "koin-ksp-compiler", version.ref = "koin-annotation" } +koin-ktor = { group = "io.insert-koin", name = "koin-ktor" } +koin-compose = { group = "io.insert-koin", name = "koin-compose" } +koin-compose-viewmodel = { group = "io.insert-koin", name = "koin-compose-viewmodel" } + +plugin-agp = { module = "com.android.tools.build:gradle", version.ref = "agp" } +plugin-kgp = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-core" } +plugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } +plugin-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } + +detekt-twitter = { module = "com.twitter.compose.rules:detekt", version = "0.0.26" } +detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } + +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } +junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit5" } +junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" } +junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-core" } + +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } +mockk-agent = { module = "io.mockk:mockk-agent", version.ref = "mockk" } + +turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } + +[bundles] +koin-nonandroid = [ + "koin-core", + "koin-core-coroutines", + "koin-ktor", + "koin-compose", + "koin-compose-viewmodel", +] + +[plugins] +jacoco = { id = "jacoco" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +buildlogic-detekt = { id = "com.gruntsoftware.buildlogic.detekt" } +buildlogic-test = { id = "com.gruntsoftware.buildlogic.test" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..905706b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jun 15 02:01:05 WIB 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..7ba042c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,22 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("grunt") { + from(files("gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "gruntsoftware-build-logic" +include(":convention") \ No newline at end of file