Skip to content

Commit 8c311a9

Browse files
Add build logic (#55)
* Add static analysis and linting configuration via custom convention plugins * Implemented a `build-logic` system using convention plugins to standardize project configurations across Android, KMP, and JVM modules. * Introduced `MifosDetektConventionPlugin`, `MifosSpotlessConventionPlugin`, and `MifosKtlintConventionPlugin` for automated code quality and formatting. * Added a custom `lint` module containing specialized detectors: * `DesignSystemDetector`: Enforces usage of the internal design system over standard Material components. * `Material2Detector`: Flags usage of deprecated Material 2 APIs in favor of Material 3. * `TestMethodNameDetector`: Validates test naming conventions (prefix removal and format checks). * Configured Spotless with copyright header templates for Kotlin, KTS, and XML files. * Integrated Detekt with a comprehensive rule set in `config/detekt/detekt.yml`, including Compose-specific rules. * Refactored existing modules (`mifos-authenticator-biometrics`, `mifos-authenticator-passcode`, etc.) to use the new convention plugins. * Updated `libs.versions.toml` with new dependencies for linting, static analysis, and Android build tools. * Add static analysis and linting configuration via custom convention plugins * Implemented a `build-logic` system using convention plugins to standardize project configurations across Android, KMP, and JVM modules. * Introduced `MifosDetektConventionPlugin`, `MifosSpotlessConventionPlugin`, and `MifosKtlintConventionPlugin` for automated code quality and formatting. * Added a custom `lint` module containing specialized detectors: * `DesignSystemDetector`: Enforces usage of the internal design system over standard Material components. * `Material2Detector`: Flags usage of deprecated Material 2 APIs in favor of Material 3. * `TestMethodNameDetector`: Validates test naming conventions (prefix removal and format checks). * Configured Spotless with copyright header templates for Kotlin, KTS, and XML files. * Integrated Detekt with a comprehensive rule set in `config/detekt/detekt.yml`, including Compose-specific rules. * Refactored existing modules (`mifos-authenticator-biometrics`, `mifos-authenticator-passcode`, etc.) to use the new convention plugins. * Updated `libs.versions.toml` with new dependencies for linting, static analysis, and Android build tools. * Apply licensing and code style updates across the project. * Added license headers to Kotlin source files, XML resources, and build scripts. * Renamed `ProvidableCompositionLocal` variables in `mifos-authenticator-biometrics` to follow camelCase naming conventions (e.g., `LibraryLocalAndroidActivity` to `libraryLocalAndroidActivity`). * Applied consistent code formatting and trailing commas throughout the codebase using Spotless/Ktlint. * Updated Spotless configuration to ignore function naming rules for `@Composable` annotations. * Cleaned up unused imports in several sample module files. * Update project dependencies and improve code quality (#50) * Added license headers to all source files. * Updated several dependencies in `libs.versions.toml`, including `androidx-lifecycle` to 2.10.0, `composeBom` to 2026.01.00, and static analysis tools like `spotless` and `detekt`. * Configured a GitHub Actions workflow for automated PR checks. * Added a Pull Request template for better contribution tracking. * Refactored `LibraryLocalCompositionProvider` across all platforms to ensure proper provision of authentication-related locals. * Applied consistent formatting and style fixes, including trailing commas, improved spacing, and `@Suppress` annotations where necessary. * Replaced `e.printStackTrace()` with `Logger.e` in `PlatformAuthenticator` for Android. * Simplified `PasscodeHeader` by removing the unused `isPasscodeAlreadySet` parameter. * Improved UI components like `PasscodeKeys` and `PasscodeLengthSwitch` with better alignment and formatting. * Clean up build-logic convention scripts * Removed unused import `org.gradle.kotlin.dsl.assign` in `KotlinAndroid.kt`. * Refactored `disableUnnecessaryAndroidTests` in `AndroidInstrumentedTests.kt` for better formatting and added a trailing newline. * Minor formatting adjustments to trailing braces in `Spotless.kt`, `Detekt.kt`, and `KotlinMultiplatform.kt`. * Removed dependency-guard plugin and updated Kotlin compiler and Webpack configurations. * Removed dependencyGuard plugin and version from `build.gradle.kts` and `libs.versions.toml`. * Updated `KotlinAndroid.kt` to use the `.set()` method for `jvmTarget` and `allWarningsAsErrors` compiler options. * Updated `cmp-sample-web` build configuration to use `ExperimentalWasmDsl` and simplified the `devServer` static file serving logic. * Add Git hooks plugin and update build configurations * Created `GitHooksConventionPlugin` to automate the installation of pre-commit hooks from the `scripts` directory. * Integrated the Git hooks plugin into `AndroidApplicationConventionPlugin`. * Updated `targetSdk` to 36 in `AndroidApplicationConventionPlugin`. * Added `dependency-guard` plugin for dependency tracking and management. * Added `google-oss-licenses-plugin` and `truth` library dependencies. * Updated Kotlin compiler options to use the `kotlin` extension block instead of `KotlinCompile` tasks. * Registered the `mifos.git.hooks` plugin in `build-logic`. * Clean up build configuration and update dependencies * Removed unused imports in `build.gradle.kts` and `KotlinMultiplatform.kt`. * Formatted task list in `GitHooksConventionPlugin.kt` for better readability. * Updated `kotlinSerialization` version to 1.10.0 in `libs.versions.toml`. * Minor linting and whitespace adjustments in `build.gradle.kts`. * Update build configuration and dependencies in cmp-sample-android * Changed `ktlintVersion` to a `const val` in Spotless configuration. * Updated `cmp-sample-android` to use the `kotlin` extension for JVM target configuration instead of `tasks.withType`. * Added `dependencyGuard` configuration for debug and release runtime classpaths. * Removed commented-out Koin dependencies. * Update LICENSE path in file headers Updated the license file reference in the copyright headers across multiple modules from `LICENSE.md` to `LICENSE`.
1 parent 8f93f77 commit 8c311a9

123 files changed

Lines changed: 3313 additions & 843 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Fixes - [Jira-#Issue_Number](https://mifosforge.jira.com/browse/MIFOSAC-)
2+
3+
Didn't create a Jira ticket, click [here](https://mifosforge.jira.com/jira/software/c/projects/MIFOSAC/issues/) to create new.
4+
5+
Please Add Screenshots If there are any UI changes.
6+
7+
| Before | After |
8+
|--------------------------------------------|----------------------------------------|
9+
| | |
10+
11+
Please make sure these boxes are checked before submitting your pull request - thanks!
12+
13+
- [ ] Run the static analysis check `./gradlew check` or `ci-prepush.sh` to make sure you didn't break anything
14+
15+
- [ ] If you have multiple commits please combine them into one commit by squashing them.

.github/workflows/pr-check.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: PR Checks
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
java-version:
7+
description: 'Java version to use'
8+
type: string
9+
required: false
10+
default: '17'
11+
push:
12+
branches:
13+
- main
14+
pull_request:
15+
branches:
16+
- main
17+
18+
19+
concurrency:
20+
group: pr-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
permissions:
24+
contents: write
25+
26+
jobs:
27+
checks:
28+
name: Static Analysis Check
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Checkout Repository
32+
uses: actions/checkout@v4
33+
- name: Static Analysis Check
34+
uses: openMF/mifos-x-actionhub-static-analysis-check@v1.0.1
35+
with:
36+
java-version: ${{ inputs.java-version || '17' }}
37+
gradle-args: --info
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
3+
plugins {
4+
`kotlin-dsl`
5+
}
6+
7+
group = "com.mifos.buildlogic"
8+
9+
// Configure the build-logic plugins to target JDK 17
10+
// This matches the JDK used to build the project, and is not related to what is running on device.
11+
java {
12+
sourceCompatibility = JavaVersion.VERSION_17
13+
targetCompatibility = JavaVersion.VERSION_17
14+
}
15+
16+
kotlin {
17+
compilerOptions {
18+
jvmTarget = JvmTarget.JVM_17
19+
}
20+
}
21+
22+
dependencies {
23+
compileOnly(libs.android.gradlePlugin)
24+
compileOnly(libs.android.tools.common)
25+
compileOnly(libs.kotlin.gradlePlugin)
26+
compileOnly(libs.compose.gradlePlugin)
27+
compileOnly(libs.detekt.gradlePlugin)
28+
compileOnly(libs.spotless.gradlePlugin)
29+
compileOnly(libs.ktlint.gradlePlugin)
30+
implementation(libs.truth)
31+
}
32+
33+
tasks {
34+
validatePlugins {
35+
enableStricterValidation = true
36+
failOnWarning = false
37+
}
38+
}
39+
40+
gradlePlugin {
41+
plugins {
42+
register("androidApplicationCompose") {
43+
id = "mifos.android.application.compose"
44+
implementationClass = "AndroidApplicationComposeConventionPlugin"
45+
}
46+
register("androidApplication") {
47+
id = "mifos.android.application"
48+
implementationClass = "AndroidApplicationConventionPlugin"
49+
}
50+
register("androidLint") {
51+
id = "mifos.android.lint"
52+
implementationClass = "AndroidLintConventionPlugin"
53+
}
54+
register("detekt") {
55+
id = "mifos.detekt.plugin"
56+
implementationClass = "MifosDetektConventionPlugin"
57+
description = "Configures detekt for the project"
58+
}
59+
register("spotless") {
60+
id = "mifos.spotless.plugin"
61+
implementationClass = "MifosSpotlessConventionPlugin"
62+
description = "Configures spotless for the project"
63+
}
64+
register("gitHooks") {
65+
id = "mifos.git.hooks"
66+
implementationClass = "GitHooksConventionPlugin"
67+
description = "Installs git hooks for the project"
68+
}
69+
register("ktlint") {
70+
id = "mifos.ktlint.plugin"
71+
implementationClass = "MifosKtlintConventionPlugin"
72+
description = "Configures kotlinter for the project"
73+
}
74+
register("kmpLibrary") {
75+
id = "mifos.kmp.library"
76+
implementationClass = "KMPLibraryConventionPlugin"
77+
}
78+
register("cmpFeature") {
79+
id = "mifos.cmp.feature"
80+
implementationClass = "CMPFeatureConventionPlugin"
81+
}
82+
83+
}
84+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import com.android.build.api.dsl.ApplicationExtension
2+
import org.gradle.api.Plugin
3+
import org.gradle.api.Project
4+
import org.gradle.kotlin.dsl.apply
5+
import org.gradle.kotlin.dsl.getByType
6+
import org.mifos.configureAndroidCompose
7+
8+
class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
9+
override fun apply(target: Project) {
10+
with(target) {
11+
apply(plugin = "com.android.application")
12+
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
13+
14+
val extension = extensions.getByType<ApplicationExtension>()
15+
configureAndroidCompose(extension)
16+
}
17+
}
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import com.android.build.api.dsl.ApplicationExtension
2+
import org.gradle.api.Plugin
3+
import org.gradle.api.Project
4+
import org.gradle.kotlin.dsl.configure
5+
import org.mifos.configureKotlinAndroid
6+
7+
class AndroidApplicationConventionPlugin : Plugin<Project> {
8+
override fun apply(target: Project) {
9+
with(target) {
10+
with(pluginManager) {
11+
apply("com.android.application")
12+
apply("org.jetbrains.kotlin.android")
13+
apply("mifos.android.lint")
14+
apply("mifos.git.hooks")
15+
apply("mifos.detekt.plugin")
16+
apply("mifos.spotless.plugin")
17+
apply("mifos.ktlint.plugin")
18+
apply("com.dropbox.dependency-guard")
19+
}
20+
21+
extensions.configure<ApplicationExtension> {
22+
configureKotlinAndroid(this)
23+
defaultConfig.targetSdk = 36
24+
@Suppress("UnstableApiUsage")
25+
testOptions.animationsDisabled = true
26+
}
27+
}
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import com.android.build.api.dsl.ApplicationExtension
2+
import com.android.build.api.dsl.LibraryExtension
3+
import com.android.build.api.dsl.Lint
4+
import org.gradle.api.Plugin
5+
import org.gradle.api.Project
6+
import org.gradle.kotlin.dsl.configure
7+
import java.io.File
8+
9+
class AndroidLintConventionPlugin : Plugin<Project> {
10+
override fun apply(target: Project) {
11+
with(target) {
12+
when {
13+
pluginManager.hasPlugin("com.android.application") ->
14+
configure<ApplicationExtension> { lint(Lint::configure) }
15+
16+
pluginManager.hasPlugin("com.android.library") ->
17+
configure<LibraryExtension> { lint(Lint::configure) }
18+
19+
else -> {
20+
pluginManager.apply("com.android.lint")
21+
configure<Lint>(Lint::configure)
22+
}
23+
}
24+
}
25+
}
26+
}
27+
28+
private fun Lint.configure() {
29+
xmlReport = true
30+
checkDependencies = true
31+
abortOnError = false
32+
// Disable this rule until we ship the libraries to some maven.
33+
disable += "ResourceName"
34+
baseline = File("lint-baseline.xml")
35+
explainIssues = true
36+
htmlReport = true
37+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import org.gradle.api.Plugin
2+
import org.gradle.api.Project
3+
import org.gradle.kotlin.dsl.dependencies
4+
import org.mifos.libs
5+
6+
class CMPFeatureConventionPlugin : Plugin<Project> {
7+
8+
override fun apply(target: Project) {
9+
with(target) {
10+
with(pluginManager) {
11+
apply("mifos.kmp.library")
12+
apply("org.jetbrains.kotlin.plugin.compose")
13+
apply("org.jetbrains.compose")
14+
apply("org.jetbrains.kotlin.plugin.serialization")
15+
}
16+
17+
dependencies {
18+
19+
add("commonMainImplementation", libs.findLibrary("jb.composeRuntime").get())
20+
add("commonMainImplementation", libs.findLibrary("jb.lifecycle.compose").get())
21+
add("commonMainImplementation", libs.findLibrary("jb.composeViewmodel").get())
22+
add("commonMainImplementation", libs.findLibrary("jb.lifecycleViewmodel").get())
23+
add("commonMainImplementation", libs.findLibrary("jb.lifecycleViewmodelSavedState").get())
24+
add("commonMainImplementation", libs.findLibrary("jb.savedstate").get())
25+
add("commonMainImplementation", libs.findLibrary("jb.bundle").get())
26+
add("commonMainImplementation", libs.findLibrary("jb.composeNavigation").get())
27+
add("commonMainImplementation", libs.findLibrary("jb.compose.ui").get())
28+
add("commonMainImplementation", libs.findLibrary("jb.ui.tooling.preview").get())
29+
add("commonMainImplementation", libs.findLibrary("kermit.logger").get())
30+
add("commonMainImplementation", libs.findLibrary("material3").get())
31+
add("commonMainImplementation", libs.findLibrary("foundation").get())
32+
add("commonMainImplementation", libs.findLibrary("material3.icons").get())
33+
add("commonMainImplementation", libs.findLibrary("components.resources").get())
34+
add("commonMainImplementation", libs.findLibrary("kotlinx.serialization.json").get())
35+
36+
// add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
37+
// add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
38+
add("androidMainImplementation", libs.findLibrary("androidx.activity.compose").get())
39+
add("androidMainImplementation", libs.findLibrary("androidx.activity.ktx").get())
40+
41+
add("androidMainImplementation", libs.findLibrary("kotlinx.coroutines.android").get())
42+
43+
add("desktopMainImplementation", libs.findLibrary("kotlinx.coroutines.swing").get())
44+
45+
}
46+
}
47+
}
48+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import org.gradle.api.Plugin
2+
import org.gradle.api.Project
3+
import org.gradle.api.tasks.Copy
4+
import org.gradle.api.tasks.Exec
5+
import org.gradle.kotlin.dsl.register
6+
import java.util.Locale
7+
8+
/**
9+
* Plugin that installs the pre-commit git hooks from the scripts directory.
10+
*/
11+
class GitHooksConventionPlugin : Plugin<Project> {
12+
override fun apply(project: Project) {
13+
// Define a function to check if the OS is Linux or MacOS
14+
fun isLinuxOrMacOs(): Boolean {
15+
val osName = System.getProperty("os.name").lowercase(Locale.getDefault())
16+
return osName.contains("linux") || osName.contains("mac os") || osName.contains("macos")
17+
}
18+
19+
// Define the copyGitHooks task
20+
project.tasks.register<Copy>("copyGitHooks") {
21+
description = "Copies the git hooks from /scripts to the .git/hooks folder."
22+
from("${project.rootDir}/scripts/") {
23+
include("**/*.sh")
24+
rename { it.removeSuffix(".sh") }
25+
}
26+
into("${project.rootDir}/.git/hooks")
27+
}
28+
29+
// Define the installGitHooks task
30+
project.tasks.register<Exec>("installGitHooks") {
31+
description = "Installs the pre-commit git hooks from the scripts directory."
32+
group = "git hooks"
33+
workingDir = project.rootDir
34+
35+
if (isLinuxOrMacOs()) {
36+
commandLine("chmod", "-R", "+x", ".git/hooks/")
37+
}else {
38+
commandLine("cmd", "/c", "attrib", "-R", "+X", ".git/hooks/*.*")
39+
}
40+
dependsOn(project.tasks.named("copyGitHooks"))
41+
42+
doLast {
43+
println("Git hooks installed successfully.")
44+
}
45+
}
46+
47+
// Configure task dependencies after evaluation
48+
project.afterEvaluate {
49+
project.tasks.matching {
50+
it.name in listOf(
51+
"preBuild", "build", "assembleDebug", "assembleRelease",
52+
"installDebug", "installRelease", "clean"
53+
)
54+
}.configureEach {
55+
dependsOn(project.tasks.named("installGitHooks"))
56+
}
57+
}
58+
}
59+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import com.android.build.gradle.LibraryExtension
2+
import org.gradle.api.Plugin
3+
import org.gradle.api.Project
4+
import org.gradle.kotlin.dsl.configure
5+
import org.gradle.kotlin.dsl.dependencies
6+
import org.mifos.configureKotlinAndroid
7+
import org.mifos.configureKotlinMultiplatform
8+
import org.mifos.libs
9+
10+
11+
class KMPLibraryConventionPlugin : Plugin<Project> {
12+
13+
override fun apply(target: Project) {
14+
with(target) {
15+
with(pluginManager) {
16+
apply("com.android.library")
17+
apply("org.jetbrains.kotlin.multiplatform")
18+
apply("mifos.detekt.plugin")
19+
apply("mifos.spotless.plugin")
20+
21+
}
22+
23+
configureKotlinMultiplatform()
24+
25+
extensions.configure<LibraryExtension> {
26+
configureKotlinAndroid(this)
27+
defaultConfig.targetSdk = 36
28+
/**
29+
* The resource prefix is derived from the module name,
30+
* so resources inside ":core:module1" must be prefixed with "core_module1_"
31+
*/
32+
resourcePrefix = path
33+
.split("""\W""".toRegex())
34+
.drop(1).distinct()
35+
.joinToString(separator = "_")
36+
.lowercase() + "_"
37+
}
38+
39+
dependencies {
40+
add("commonMainImplementation", libs.findLibrary("kotlinx.coroutines.core").get())
41+
}
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)