Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
78
build-plugin/README.md
Normal file
78
build-plugin/README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Build plugins
|
||||
|
||||
The `build-plugin` folder defines Gradle build plugins, used as single source of truth for the project configuration.
|
||||
This helps to avoid duplicated build script setups and provides a central location for all common build logic.
|
||||
|
||||
## Background
|
||||
|
||||
We use Gradle's
|
||||
[sharing build logic in a multi-repo setup](https://docs.gradle.org/current/samples/sample_publishing_convention_plugins.html)
|
||||
to create common configuration. It allows usage of `xyz.gradle.kts` files, that are then automatically converted to
|
||||
Gradle Plugins.
|
||||
|
||||
The `build-plugin` is used as included build in the root `settings.gradle.kts` and provides all
|
||||
included `xyz.gradle.kts` as plugins under their `xyz` name to the whole project.
|
||||
|
||||
The plugins should try to accomplish single responsibility and leave one-off configuration to the
|
||||
module's `build.gradle.kts`.
|
||||
|
||||
## Convention plugins
|
||||
|
||||
- `thunderbird.app.android` - Configures common options for Android apps
|
||||
- `thunderbird.app.android.compose` - Configures common options for Jetpack Compose, based
|
||||
on `thunderbird.app.android`
|
||||
- `thunderbird.library.android` - Configures common options for Android libraries
|
||||
- `thunderbird.library.android.compose` - Configures common options for Jetpack Compose, based
|
||||
on `thunderbird.library.android`
|
||||
- `thunderbird.library.jvm` - Configures common options for JVM libraries
|
||||
|
||||
## Supportive plugins
|
||||
|
||||
- `thunderbird.dependency.check` - [Gradle Versions: Gradle plugin to discover dependency updates](https://github.com/ben-manes/gradle-versions-plugin)
|
||||
- Use `./gradlew dependencyUpdates` to generate a dependency update report
|
||||
- `thunderbird.quality.detekt` - [Detekt - Static code analysis for Kotlin](https://detekt.dev/)
|
||||
- Use `./gradlew detekt` to check for any issue and `./gradlew detektBaseline` in case you can't fix the reported
|
||||
issue.
|
||||
- `thunderbird.quality.spotless` - [Spotless - Code formatter](https://github.com/diffplug/spotless)
|
||||
with [Ktlint - Kotlin linter and formatter](https://pinterest.github.io/ktlint/)
|
||||
- Use `./gradlew spotlessCheck` to check for any issue and `./gradlew spotlessApply` to format your code
|
||||
- `thunderbird.quality.badging` - [Android Badging Check Plugin](https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt)
|
||||
- Use `./gradlew generate{VariantName}Badging` to generate badging file
|
||||
- Use `./gradlew check{VariantName}Badging` to validate allowed badging
|
||||
- Use `./gradlew update{VariantName}Badging` to update allowed badging
|
||||
|
||||
## Add new build plugin
|
||||
|
||||
Create a `thunderbird.xyz.gradle.kts` file, while `xyz` describes the new plugin.
|
||||
|
||||
If you need to access dependencies that are not yet defined in `build-plugin/build.gradle.kts` you have to:
|
||||
|
||||
1. Add the dependency to the version catalog `gradle/libs.versions.toml`
|
||||
2. Then add it to `build-plugin/build.gradle.kts`.
|
||||
1. In case of a plugin dependency use `implementation(plugin(libs.plugins.YOUR_PLUGIN_DEPENDENCY))`.
|
||||
2. Otherwise `implementation(libs.YOUR_DEPENDENCY))`.
|
||||
|
||||
When done, add the plugin to `build-plugin/src/main/kotlin/ThunderbirdPlugins.kt`
|
||||
|
||||
Then apply the plugin to any subproject it should be used with:
|
||||
|
||||
```
|
||||
plugins {
|
||||
id(ThunderbirdPlugins.xyz)
|
||||
}
|
||||
```
|
||||
|
||||
If the plugin is meant for the root `build.gradle.kts`, you can't use `ThunderbirdPlugins`, as it's not available to
|
||||
the `plugins` block. Instead use:
|
||||
|
||||
```
|
||||
plugins {
|
||||
id("thunderbird.xyz")
|
||||
}
|
||||
```
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [Herding Elephants | Square Corner Blog](https://developer.squareup.com/blog/herding-elephants/)
|
||||
- [Idiomatic Gradle: How do I idiomatically structure a large build with Gradle](https://github.com/jjohannes/idiomatic-gradle#idiomatic-build-logic-structure)
|
||||
|
||||
35
build-plugin/build.gradle.kts
Normal file
35
build-plugin/build.gradle.kts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||
|
||||
implementation(plugin(libs.plugins.kotlin.android))
|
||||
implementation(plugin(libs.plugins.kotlin.jvm))
|
||||
implementation(plugin(libs.plugins.kotlin.multiplatform))
|
||||
implementation(plugin(libs.plugins.kotlin.parcelize))
|
||||
implementation(plugin(libs.plugins.kotlin.serialization))
|
||||
|
||||
implementation(plugin(libs.plugins.android.application))
|
||||
implementation(plugin(libs.plugins.android.library))
|
||||
|
||||
implementation(plugin(libs.plugins.compose))
|
||||
|
||||
implementation(plugin(libs.plugins.jetbrains.compose))
|
||||
|
||||
implementation(plugin(libs.plugins.dependency.check))
|
||||
implementation(plugin(libs.plugins.detekt))
|
||||
implementation(plugin(libs.plugins.spotless))
|
||||
|
||||
implementation(libs.diff.utils)
|
||||
compileOnly(libs.android.tools.common)
|
||||
|
||||
// This defines the used Kotlin version for all Plugin dependencies
|
||||
// and ensures that transitive dependencies are aligned on one version.
|
||||
implementation(platform(libs.kotlin.gradle.bom))
|
||||
}
|
||||
|
||||
fun plugin(provider: Provider<PluginDependency>) = with(provider.get()) {
|
||||
"$pluginId:$pluginId.gradle.plugin:$version"
|
||||
}
|
||||
21
build-plugin/settings.gradle.kts
Normal file
21
build-plugin/settings.gradle.kts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
versionCatalogs.create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-plugin"
|
||||
72
build-plugin/src/main/kotlin/AndroidExtension.kt
Normal file
72
build-plugin/src/main/kotlin/AndroidExtension.kt
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import com.android.build.api.dsl.CommonExtension
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
|
||||
internal fun CommonExtension<*, *, *, *, *, *>.configureSharedConfig(project: Project) {
|
||||
compileSdk = ThunderbirdProjectConfig.Android.sdkCompile
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = ThunderbirdProjectConfig.Android.sdkCompile
|
||||
minSdk = ThunderbirdProjectConfig.Android.sdkMin
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
targetCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
}
|
||||
|
||||
lint {
|
||||
warningsAsErrors = false
|
||||
abortOnError = true
|
||||
checkDependencies = true
|
||||
lintConfig = project.file("${project.rootProject.projectDir}/config/lint/lint.xml")
|
||||
baseline = project.file("${project.rootProject.projectDir}/config/lint/android-lint-baseline.xml")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += listOf(
|
||||
"/META-INF/{AL2.0,LGPL2.1}",
|
||||
"/META-INF/DEPENDENCIES",
|
||||
"/META-INF/LICENSE",
|
||||
"/META-INF/LICENSE.txt",
|
||||
"/META-INF/NOTICE",
|
||||
"/META-INF/NOTICE.txt",
|
||||
"/META-INF/README",
|
||||
"/META-INF/README.md",
|
||||
"/META-INF/CHANGES",
|
||||
"/LICENSE.txt",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun CommonExtension<*, *, *, *, *, *>.configureSharedComposeConfig(libs: LibrariesForLibs) {
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun DependencyHandler.configureSharedComposeDependencies(libs: LibrariesForLibs) {
|
||||
val composeBom = platform(libs.androidx.compose.bom)
|
||||
implementation(composeBom)
|
||||
androidTestImplementation(composeBom)
|
||||
|
||||
implementation(libs.bundles.shared.jvm.android.compose)
|
||||
|
||||
debugImplementation(libs.bundles.shared.jvm.android.compose.debug)
|
||||
|
||||
testImplementation(libs.bundles.shared.jvm.test.compose)
|
||||
|
||||
androidTestImplementation(libs.bundles.shared.jvm.androidtest.compose)
|
||||
}
|
||||
14
build-plugin/src/main/kotlin/DependencyHandlerExtension.kt
Normal file
14
build-plugin/src/main/kotlin/DependencyHandlerExtension.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
|
||||
internal fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? =
|
||||
add("implementation", dependencyNotation)
|
||||
|
||||
internal fun DependencyHandler.debugImplementation(dependencyNotation: Any): Dependency? =
|
||||
add("debugImplementation", dependencyNotation)
|
||||
|
||||
internal fun DependencyHandler.testImplementation(dependencyNotation: Any): Dependency? =
|
||||
add("testImplementation", dependencyNotation)
|
||||
|
||||
internal fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? =
|
||||
add("androidTestImplementation", dependencyNotation)
|
||||
11
build-plugin/src/main/kotlin/KotlinExtension.kt
Normal file
11
build-plugin/src/main/kotlin/KotlinExtension.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
fun Project.configureKotlinJavaCompatibility() {
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
jvmTarget.set(ThunderbirdProjectConfig.Compiler.jvmTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
build-plugin/src/main/kotlin/ProjectExtension.kt
Normal file
10
build-plugin/src/main/kotlin/ProjectExtension.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
||||
import org.gradle.kotlin.dsl.getByName
|
||||
|
||||
internal val Project.libs: LibrariesForLibs
|
||||
get() = extensions.getByName<LibrariesForLibs>("libs")
|
||||
|
||||
internal fun Project.dependencies(configuration: DependencyHandlerScope.() -> Unit) =
|
||||
DependencyHandlerScope.of(dependencies).configuration()
|
||||
91
build-plugin/src/main/kotlin/SigningExtensions.kt
Normal file
91
build-plugin/src/main/kotlin/SigningExtensions.kt
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import com.android.build.api.dsl.ApkSigningConfig
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
import org.gradle.api.NamedDomainObjectContainer
|
||||
import org.gradle.api.Project
|
||||
|
||||
private const val SIGNING_FOLDER = ".signing"
|
||||
private const val SIGNING_FILE_ENDING = ".signing.properties"
|
||||
private const val UPLOAD_FILE_ENDING = ".upload.properties"
|
||||
|
||||
private const val PROPERTY_STORE_FILE = "storeFile"
|
||||
private const val PROPERTY_STORE_PASSWORD = "storePassword"
|
||||
private const val PROPERTY_KEY_ALIAS = "keyAlias"
|
||||
private const val PROPERTY_KEY_PASSWORD = "keyPassword"
|
||||
|
||||
/**
|
||||
* Creates an [ApkSigningConfig] for the given signing type.
|
||||
*
|
||||
* The signing properties are read from a file in the `.signing` folder in the project root directory.
|
||||
* File names are expected to be in the format `$app.$type.signing.properties` or `$app.$type.upload.properties`.
|
||||
*
|
||||
* The file should contain the following properties:
|
||||
* - `$app.$type.storeFile`
|
||||
* - `$app.$type.storePassword`
|
||||
* - `$app.$type.keyAlias`
|
||||
* - `$app.$type.keyPassword`
|
||||
*
|
||||
* @param project the project to create the signing config for
|
||||
* @param signingType the signing type to create the signing config for
|
||||
* @param isUpload whether the upload or signing config is used
|
||||
*/
|
||||
fun NamedDomainObjectContainer<out ApkSigningConfig>.createSigningConfig(
|
||||
project: Project,
|
||||
signingType: SigningType,
|
||||
isUpload: Boolean = true,
|
||||
) {
|
||||
val properties = project.readSigningProperties(signingType, isUpload)
|
||||
|
||||
if (properties.hasSigningConfig(signingType)) {
|
||||
create(signingType.type) {
|
||||
storeFile = project.file(properties.getSigningProperty(signingType, PROPERTY_STORE_FILE))
|
||||
storePassword = properties.getSigningProperty(signingType, PROPERTY_STORE_PASSWORD)
|
||||
keyAlias = properties.getSigningProperty(signingType, PROPERTY_KEY_ALIAS)
|
||||
keyPassword = properties.getSigningProperty(signingType, PROPERTY_KEY_PASSWORD)
|
||||
}
|
||||
} else {
|
||||
project.logger.warn("Signing config not created for ${signingType.type}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [ApkSigningConfig] for the given signing type.
|
||||
*
|
||||
* @param signingType the signing type to get the signing config for
|
||||
*/
|
||||
fun NamedDomainObjectContainer<out ApkSigningConfig>.getByType(signingType: SigningType): ApkSigningConfig? {
|
||||
return findByName(signingType.type)
|
||||
}
|
||||
|
||||
private fun Project.readSigningProperties(signingType: SigningType, isUpload: Boolean) = Properties().apply {
|
||||
val signingPropertiesFile = if (isUpload) {
|
||||
rootProject.file("$SIGNING_FOLDER/${signingType.id}$UPLOAD_FILE_ENDING")
|
||||
} else {
|
||||
rootProject.file("$SIGNING_FOLDER/${signingType.id}$SIGNING_FILE_ENDING")
|
||||
}
|
||||
|
||||
if (signingPropertiesFile.exists()) {
|
||||
FileInputStream(signingPropertiesFile).use { inputStream ->
|
||||
load(inputStream)
|
||||
}
|
||||
} else {
|
||||
logger.warn("Signing properties file not found: $signingPropertiesFile")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Properties.hasSigningConfig(signingType: SigningType): Boolean {
|
||||
return isNotEmpty() &&
|
||||
containsKey(signingType, PROPERTY_STORE_FILE) &&
|
||||
containsKey(signingType, PROPERTY_STORE_PASSWORD) &&
|
||||
containsKey(signingType, PROPERTY_KEY_ALIAS) &&
|
||||
containsKey(signingType, PROPERTY_KEY_PASSWORD)
|
||||
}
|
||||
|
||||
private fun Properties.containsKey(signingType: SigningType, key: String): Boolean {
|
||||
return containsKey("${signingType.id}.$key")
|
||||
}
|
||||
|
||||
private fun Properties.getSigningProperty(signingType: SigningType, key: String): String {
|
||||
return getProperty("${signingType.id}.$key")
|
||||
?: throw IllegalArgumentException("Missing property: ${signingType.type}.$key")
|
||||
}
|
||||
10
build-plugin/src/main/kotlin/SigningType.kt
Normal file
10
build-plugin/src/main/kotlin/SigningType.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
enum class SigningType(
|
||||
val app: String,
|
||||
val type: String,
|
||||
val id: String = "$app.$type",
|
||||
) {
|
||||
K9_RELEASE(app = "k9", type = "release"),
|
||||
TB_RELEASE(app = "tb", type = "release"),
|
||||
TB_BETA(app = "tb", type = "beta"),
|
||||
TB_DAILY(app = "tb", type = "daily"),
|
||||
}
|
||||
11
build-plugin/src/main/kotlin/SpotlessExtension.kt
Normal file
11
build-plugin/src/main/kotlin/SpotlessExtension.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
val kotlinEditorConfigOverride = mapOf(
|
||||
"ktlint_code_style" to "intellij_idea",
|
||||
"ktlint_ignore_back_ticked_identifier" to "true",
|
||||
"ktlint_function_naming_ignore_when_annotated_with" to "Composable",
|
||||
"ktlint_standard_class-signature" to "disabled",
|
||||
"ktlint_standard_function-expression-body" to "disabled",
|
||||
"ktlint_standard_function-signature" to "disabled",
|
||||
"ktlint_standard_parameter-list-spacing" to "disabled",
|
||||
"ktlint_standard_property-naming" to "disabled",
|
||||
)
|
||||
20
build-plugin/src/main/kotlin/ThunderbirdPlugins.kt
Normal file
20
build-plugin/src/main/kotlin/ThunderbirdPlugins.kt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
object ThunderbirdPlugins {
|
||||
|
||||
object App {
|
||||
const val android = "thunderbird.app.android"
|
||||
const val androidCompose = "thunderbird.app.android.compose"
|
||||
|
||||
const val jvm = "thunderbird.app.jvm"
|
||||
|
||||
const val kmpAndroidCompose = "thunderbird.app.kmp.android.compose"
|
||||
}
|
||||
object Library {
|
||||
const val android = "thunderbird.library.android"
|
||||
const val androidCompose = "thunderbird.library.android.compose"
|
||||
|
||||
const val jvm = "thunderbird.library.jvm"
|
||||
|
||||
const val kmp = "thunderbird.library.kmp"
|
||||
const val kmpCompose = "thunderbird.library.kmp.compose"
|
||||
}
|
||||
}
|
||||
19
build-plugin/src/main/kotlin/ThunderbirdProjectConfig.kt
Normal file
19
build-plugin/src/main/kotlin/ThunderbirdProjectConfig.kt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import org.gradle.api.JavaVersion
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
object ThunderbirdProjectConfig {
|
||||
|
||||
object Android {
|
||||
const val sdkMin = 21
|
||||
|
||||
// Only needed for application
|
||||
const val sdkTarget = 35
|
||||
const val sdkCompile = 35
|
||||
}
|
||||
|
||||
object Compiler {
|
||||
val javaCompatibility = JavaVersion.VERSION_11
|
||||
val jvmTarget = JvmTarget.JVM_11
|
||||
val javaVersion = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
plugins {
|
||||
id("thunderbird.app.android")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
android {
|
||||
configureSharedComposeConfig(libs)
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
configureSharedComposeDependencies(libs)
|
||||
|
||||
implementation(libs.androidx.activity.compose)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
android {
|
||||
configureSharedConfig(project)
|
||||
|
||||
defaultConfig {
|
||||
targetSdk = ThunderbirdProjectConfig.Android.sdkTarget
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString()
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring(libs.android.desugar.nio)
|
||||
|
||||
implementation(platform(libs.kotlin.bom))
|
||||
implementation(platform(libs.koin.bom))
|
||||
|
||||
implementation(libs.bundles.shared.jvm.android.app)
|
||||
|
||||
testImplementation(libs.bundles.shared.jvm.test)
|
||||
}
|
||||
21
build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts
Normal file
21
build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
plugins {
|
||||
id("application")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
targetCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
}
|
||||
|
||||
configureKotlinJavaCompatibility()
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.kotlin.bom))
|
||||
implementation(platform(libs.koin.bom))
|
||||
|
||||
implementation(libs.bundles.shared.jvm.main)
|
||||
testImplementation(libs.bundles.shared.jvm.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.xpath.XPathConstants
|
||||
import javax.xml.xpath.XPathFactory
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
val variantName = variant.name.capitalized()
|
||||
val printVersionInfoTaskName = "printVersionInfo$variantName"
|
||||
tasks.register<PrintVersionInfo>(printVersionInfoTaskName) {
|
||||
applicationId.set(variant.applicationId)
|
||||
applicationLabel.set(getApplicationLabel(variant))
|
||||
versionCode.set(getVersionCode(variant))
|
||||
versionName.set(getVersionName(variant))
|
||||
versionNameSuffix.set(getVersionNameSuffix(variant))
|
||||
|
||||
// Set outputFile only if provided via -PoutputFile=...
|
||||
project.findProperty("outputFile")?.toString()?.let { path ->
|
||||
outputFile.set(File(path))
|
||||
}
|
||||
|
||||
// Set the `strings.xml` file for the variant to track changes
|
||||
findStringsXmlForVariant(variant)?.let { stringsFile ->
|
||||
stringsXmlFile.set(project.layout.projectDirectory.file(stringsFile.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.capitalized() = replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase() else it.toString()
|
||||
}
|
||||
|
||||
abstract class PrintVersionInfo : DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
abstract val applicationId: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val applicationLabel: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val versionCode: Property<Int>
|
||||
|
||||
@get:Input
|
||||
abstract val versionName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val versionNameSuffix: Property<String>
|
||||
|
||||
@get:OutputFile
|
||||
@get:Optional
|
||||
abstract val outputFile: RegularFileProperty
|
||||
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val stringsXmlFile: RegularFileProperty
|
||||
|
||||
init {
|
||||
outputs.upToDateWhen { false } // This forces Gradle to always re-run the task
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun printVersionInfo() {
|
||||
val output = """
|
||||
APPLICATION_ID=${applicationId.get()}
|
||||
APPLICATION_LABEL=${applicationLabel.get()}
|
||||
VERSION_CODE=${versionCode.get()}
|
||||
VERSION_NAME=${versionName.get()}
|
||||
VERSION_NAME_SUFFIX=${versionNameSuffix.get()}
|
||||
FULL_VERSION_NAME=${versionName.get()}${versionNameSuffix.get()}
|
||||
""".trimIndent()
|
||||
|
||||
println(output)
|
||||
|
||||
if (outputFile.isPresent) {
|
||||
outputFile.get().asFile.writeText(output + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the correct `strings.xml` for the given variant.
|
||||
*/
|
||||
private fun findStringsXmlForVariant(variant: com.android.build.api.variant.Variant): File? {
|
||||
val targetBuildType = variant.buildType ?: return null
|
||||
val sourceSets = android.sourceSets
|
||||
|
||||
// Try to find the strings.xml for the specific build type
|
||||
val buildTypeSource = sourceSets.findByName(targetBuildType)?.res?.srcDirs?.firstOrNull()
|
||||
val stringsXmlFile = buildTypeSource?.resolve("values/strings.xml")
|
||||
|
||||
if (stringsXmlFile?.exists() == true) {
|
||||
return stringsXmlFile
|
||||
}
|
||||
|
||||
// Fallback to the `main` source set
|
||||
val mainSourceSet = sourceSets.findByName("main")?.res?.srcDirs?.firstOrNull()
|
||||
return mainSourceSet?.resolve("values/strings.xml")?.takeIf { it.exists() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts `APPLICATION_LABEL` from `strings.xml`
|
||||
*/
|
||||
private fun getApplicationLabel(variant: com.android.build.api.variant.Variant): Provider<String> {
|
||||
return project.provider {
|
||||
findStringsXmlForVariant(variant)?.let {
|
||||
extractAppName(it)
|
||||
} ?: "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `strings.xml` to extract `<string name="app_name">...</string>`
|
||||
*/
|
||||
private fun extractAppName(stringsXmlFile: File): String {
|
||||
val xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stringsXmlFile)
|
||||
val xPath = XPathFactory.newInstance().newXPath()
|
||||
val expression = "/resources/string[@name='app_name']/text()"
|
||||
return xPath.evaluate(expression, xmlDocument, XPathConstants.STRING) as String
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the `VERSION_CODE` from product flavors
|
||||
*/
|
||||
private fun getVersionCode(variant: com.android.build.api.variant.Variant): Int {
|
||||
val flavorNames = variant.productFlavors.map { it.second }
|
||||
|
||||
val androidExtension =
|
||||
project.extensions.findByType(com.android.build.gradle.internal.dsl.BaseAppModuleExtension::class.java)
|
||||
val flavor = androidExtension?.productFlavors?.find { it.name in flavorNames }
|
||||
|
||||
return flavor?.versionCode ?: androidExtension?.defaultConfig?.versionCode ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the `VERSION_NAME` from product flavors
|
||||
*/
|
||||
private fun getVersionName(variant: com.android.build.api.variant.Variant): String {
|
||||
val flavorNames = variant.productFlavors.map { it.second }
|
||||
|
||||
val androidExtension = project.extensions.findByType(
|
||||
com.android.build.gradle.internal.dsl.BaseAppModuleExtension::class.java,
|
||||
)
|
||||
val flavor = androidExtension?.productFlavors?.find { it.name in flavorNames }
|
||||
|
||||
return flavor?.versionName ?: androidExtension?.defaultConfig?.versionName ?: "unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the `VERSION_NAME_SUFFIX` from build types
|
||||
*/
|
||||
private fun getVersionNameSuffix(variant: com.android.build.api.variant.Variant): String {
|
||||
val buildTypeName = variant.buildType ?: return ""
|
||||
val androidExtension =
|
||||
project.extensions.findByType(com.android.build.gradle.internal.dsl.BaseAppModuleExtension::class.java)
|
||||
val buildType = androidExtension?.buildTypes?.find { it.name == buildTypeName }
|
||||
return buildType?.versionNameSuffix ?: ""
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
|
||||
|
||||
plugins {
|
||||
id("com.github.ben-manes.versions")
|
||||
}
|
||||
|
||||
tasks.withType<DependencyUpdatesTask> {
|
||||
rejectVersionIf {
|
||||
isNonStable(candidate.version) && !isNonStable(currentVersion)
|
||||
}
|
||||
}
|
||||
|
||||
fun isNonStable(version: String): Boolean {
|
||||
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) }
|
||||
val regex = "^[\\d,.v-]+(-r)?$".toRegex()
|
||||
val isStable = stableKeyword || regex.matches(version)
|
||||
return isStable.not()
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
id("thunderbird.library.android")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
android {
|
||||
configureSharedComposeConfig(libs)
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants(selector().withBuildType("release")) { variantBuilder ->
|
||||
variantBuilder.enableUnitTest = false
|
||||
variantBuilder.enableAndroidTest = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
configureSharedComposeDependencies(libs)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
android {
|
||||
configureSharedConfig(project)
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = false
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString()
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets.all {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xwhen-guards")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.kotlin.bom))
|
||||
implementation(platform(libs.koin.bom))
|
||||
|
||||
implementation(libs.bundles.shared.jvm.main)
|
||||
implementation(libs.bundles.shared.jvm.android)
|
||||
|
||||
testImplementation(libs.bundles.shared.jvm.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import org.gradle.jvm.tasks.Jar
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
targetCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
// We want to avoid ending up with multiple JARs having the same name, e.g. "common.jar".
|
||||
// To do this, we use the modified project path as base name, e.g. ":core:common" -> "core.common".
|
||||
val projectDotPath = project.path.split(":").filter { it.isNotEmpty() }.joinToString(separator = ".")
|
||||
archiveBaseName.set(projectDotPath)
|
||||
}
|
||||
|
||||
configureKotlinJavaCompatibility()
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.kotlin.bom))
|
||||
implementation(platform(libs.koin.bom))
|
||||
|
||||
implementation(libs.bundles.shared.jvm.main)
|
||||
testImplementation(libs.bundles.shared.jvm.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.compose")
|
||||
id("org.jetbrains.kotlin.multiplatform")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(ThunderbirdProjectConfig.Compiler.jvmTarget)
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
compilerOptions {
|
||||
jvmTarget.set(ThunderbirdProjectConfig.Compiler.jvmTarget)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project.dependencies.platform(libs.kotlin.bom))
|
||||
implementation(project.dependencies.platform(libs.koin.bom))
|
||||
implementation(libs.bundles.shared.kmp.common)
|
||||
implementation(libs.bundles.shared.kmp.compose)
|
||||
|
||||
implementation(compose.runtime)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.ui)
|
||||
implementation(compose.components.resources)
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.bundles.shared.kmp.common.test)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.bundles.shared.kmp.android)
|
||||
implementation(libs.bundles.shared.kmp.compose.android)
|
||||
implementation(compose.preview)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = ThunderbirdProjectConfig.Android.sdkCompile
|
||||
|
||||
defaultConfig {
|
||||
minSdk = ThunderbirdProjectConfig.Android.sdkMin
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
targetCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
}
|
||||
|
||||
configureSharedComposeConfig(libs)
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.multiplatform")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("thunderbird.quality.detekt.typed")
|
||||
id("thunderbird.quality.spotless")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidTarget {
|
||||
compilerOptions {
|
||||
jvmTarget.set(ThunderbirdProjectConfig.Compiler.jvmTarget)
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
compilerOptions {
|
||||
jvmTarget.set(ThunderbirdProjectConfig.Compiler.jvmTarget)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(project.dependencies.platform(libs.kotlin.bom))
|
||||
implementation(project.dependencies.platform(libs.koin.bom))
|
||||
implementation(libs.bundles.shared.kmp.common)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.bundles.shared.kmp.common.test)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.bundles.shared.kmp.android)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = ThunderbirdProjectConfig.Android.sdkCompile
|
||||
|
||||
defaultConfig {
|
||||
minSdk = ThunderbirdProjectConfig.Android.sdkMin
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
targetCompatibility = ThunderbirdProjectConfig.Compiler.javaCompatibility
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
import com.android.SdkConstants
|
||||
import com.android.build.api.artifact.SingleArtifact
|
||||
import com.github.difflib.text.DiffRow
|
||||
import com.github.difflib.text.DiffRowGenerator
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* This is a Gradle plugin that adds a task to generate the badging of the APKs and a task to check that the
|
||||
* generated badging is the same as the golden badging.
|
||||
*
|
||||
* This is taken from [nowinandroid](https://github.com/android/nowinandroid) and follows recommendations from
|
||||
* [Prevent regressions with CI and badging](https://android-developers.googleblog.com/2023/12/increase-your-apps-availability-across-device-types.html).
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
val variantsToCheck = listOf("release", "beta", "daily")
|
||||
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
if (variantsToCheck.any { variant.name.contains(it, ignoreCase = true) }) {
|
||||
val capitalizedVariantName = variant.name.capitalized()
|
||||
val generateBadgingTaskName = "generate${capitalizedVariantName}Badging"
|
||||
val generateBadging = tasks.register<GenerateBadgingTask>(generateBadgingTaskName) {
|
||||
apk.set(variant.artifacts.get(SingleArtifact.APK_FROM_BUNDLE))
|
||||
aapt2Executable.set(
|
||||
File(
|
||||
android.sdkDirectory,
|
||||
"${SdkConstants.FD_BUILD_TOOLS}/" +
|
||||
"${android.buildToolsVersion}/" +
|
||||
SdkConstants.FN_AAPT2,
|
||||
),
|
||||
)
|
||||
badging.set(
|
||||
project.layout.buildDirectory.file(
|
||||
"outputs/badging/${variant.name}/${variant.name}-badging.txt",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val updateBadgingTaskName = "update${capitalizedVariantName}Badging"
|
||||
tasks.register<Copy>(updateBadgingTaskName) {
|
||||
from(generateBadging.get().badging)
|
||||
into(project.layout.projectDirectory.dir("badging"))
|
||||
}
|
||||
|
||||
val checkBadgingTaskName = "check${capitalizedVariantName}Badging"
|
||||
val goldenBadgingPath = project.layout.projectDirectory.file("badging/${variant.name}-badging.txt")
|
||||
tasks.register<CheckBadgingTask>(checkBadgingTaskName) {
|
||||
if (goldenBadgingPath.asFile.exists()) {
|
||||
goldenBadging.set(goldenBadgingPath)
|
||||
}
|
||||
generatedBadging.set(
|
||||
generateBadging.get().badging,
|
||||
)
|
||||
this.updateBadgingTaskName.set(updateBadgingTaskName)
|
||||
|
||||
output.set(
|
||||
project.layout.buildDirectory.dir("intermediates/$checkBadgingTaskName"),
|
||||
)
|
||||
}
|
||||
|
||||
tasks.named("build") {
|
||||
dependsOn(checkBadgingTaskName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.capitalized() = replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase() else it.toString()
|
||||
}
|
||||
|
||||
@CacheableTask
|
||||
abstract class GenerateBadgingTask : DefaultTask() {
|
||||
|
||||
@get:OutputFile
|
||||
abstract val badging: RegularFileProperty
|
||||
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
@get:InputFile
|
||||
abstract val apk: RegularFileProperty
|
||||
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
@get:InputFile
|
||||
abstract val aapt2Executable: RegularFileProperty
|
||||
|
||||
@get:Inject
|
||||
abstract val execOperations: ExecOperations
|
||||
|
||||
@TaskAction
|
||||
fun taskAction() {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
execOperations.exec {
|
||||
commandLine(
|
||||
aapt2Executable.get().asFile.absolutePath,
|
||||
"dump",
|
||||
"badging",
|
||||
apk.get().asFile.absolutePath,
|
||||
)
|
||||
standardOutput = outputStream
|
||||
}
|
||||
|
||||
badging.asFile.get().writeText(cleanBadgingContent(outputStream) + "\n")
|
||||
}
|
||||
|
||||
private fun cleanBadgingContent(outputStream: ByteArrayOutputStream): String {
|
||||
return ByteArrayInputStream(outputStream.toByteArray()).bufferedReader().use { reader ->
|
||||
reader.lineSequence().map { line ->
|
||||
line.cleanBadgingLine()
|
||||
}.sorted().joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.cleanBadgingLine(): String {
|
||||
return if (startsWith("package:")) {
|
||||
replace(Regex("versionName='[^']*'"), "")
|
||||
.replace(Regex("versionCode='[^']*'"), "")
|
||||
.replace(Regex("\\s+"), " ")
|
||||
.trim()
|
||||
} else if (trim().startsWith("uses-feature-not-required:")) {
|
||||
trim()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CacheableTask
|
||||
abstract class CheckBadgingTask : DefaultTask() {
|
||||
|
||||
// In order for the task to be up-to-date when the inputs have not changed,
|
||||
// the task must declare an output, even if it's not used. Tasks with no
|
||||
// output are always run regardless of whether the inputs changed
|
||||
@get:OutputDirectory
|
||||
abstract val output: DirectoryProperty
|
||||
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
@get:Optional
|
||||
@get:InputFile
|
||||
abstract val goldenBadging: RegularFileProperty
|
||||
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
@get:InputFile
|
||||
abstract val generatedBadging: RegularFileProperty
|
||||
|
||||
@get:Input
|
||||
abstract val updateBadgingTaskName: Property<String>
|
||||
|
||||
override fun getGroup(): String = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
|
||||
@TaskAction
|
||||
fun taskAction() {
|
||||
if (goldenBadging.isPresent.not()) {
|
||||
printlnColor(
|
||||
ANSI_YELLOW,
|
||||
"Golden badging file does not exist!" +
|
||||
" If this is the first time running this task," +
|
||||
" run ./gradlew ${updateBadgingTaskName.get()}",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val goldenBadgingContent = goldenBadging.get().asFile.readText()
|
||||
val generatedBadgingContent = generatedBadging.get().asFile.readText()
|
||||
if (goldenBadgingContent == generatedBadgingContent) {
|
||||
printlnColor(ANSI_YELLOW, "Generated badging is the same as golden badging!")
|
||||
return
|
||||
}
|
||||
|
||||
val diff = performDiff(goldenBadgingContent, generatedBadgingContent)
|
||||
printDiff(diff)
|
||||
|
||||
throw GradleException(
|
||||
"""
|
||||
Generated badging is different from golden badging!
|
||||
|
||||
If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun performDiff(goldenBadgingContent: String, generatedBadgingContent: String): String {
|
||||
val generator: DiffRowGenerator = DiffRowGenerator.create()
|
||||
.showInlineDiffs(true)
|
||||
.mergeOriginalRevised(true)
|
||||
.inlineDiffByWord(true)
|
||||
.oldTag { _ -> "" }
|
||||
.newTag { _ -> "" }
|
||||
.build()
|
||||
|
||||
return generator.generateDiffRows(
|
||||
goldenBadgingContent.lines(),
|
||||
generatedBadgingContent.lines(),
|
||||
).filter { row -> row.tag != DiffRow.Tag.EQUAL }
|
||||
.joinToString("\n") { row ->
|
||||
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
||||
when (row.tag) {
|
||||
DiffRow.Tag.INSERT -> {
|
||||
"+ ${row.newLine}"
|
||||
}
|
||||
|
||||
DiffRow.Tag.DELETE -> {
|
||||
"- ${row.oldLine}"
|
||||
}
|
||||
|
||||
DiffRow.Tag.CHANGE -> {
|
||||
"+ ${row.newLine}"
|
||||
"- ${row.oldLine}"
|
||||
}
|
||||
|
||||
DiffRow.Tag.EQUAL -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printDiff(diff: String) {
|
||||
printlnColor("", null)
|
||||
printlnColor(ANSI_YELLOW, "Badging diff:")
|
||||
|
||||
diff.lines().forEach { line ->
|
||||
val ansiColor = if (line.startsWith("+")) {
|
||||
ANSI_GREEN
|
||||
} else if (line.startsWith("-")) {
|
||||
ANSI_RED
|
||||
} else {
|
||||
null
|
||||
}
|
||||
printlnColor(line, ansiColor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun printlnColor(text: String, ansiColor: String?) {
|
||||
println(
|
||||
if (ansiColor != null) {
|
||||
ansiColor + text + ANSI_RESET
|
||||
} else {
|
||||
text
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ANSI_RESET = "\u001B[0m"
|
||||
const val ANSI_RED = "\u001B[31m"
|
||||
const val ANSI_GREEN = "\u001B[32m"
|
||||
const val ANSI_YELLOW = "\u001B[33m"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import io.gitlab.arturbosch.detekt.Detekt
|
||||
import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
|
||||
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
plugins {
|
||||
id("io.gitlab.arturbosch.detekt")
|
||||
}
|
||||
|
||||
configure<DetektExtension> {
|
||||
config.setFrom(project.rootProject.files("config/detekt/detekt.yml"))
|
||||
val name = project.path.replace(":", "-").replace("/", "-")
|
||||
baseline = project.rootProject.file("config/detekt/detekt-baseline$name.xml")
|
||||
|
||||
ignoredBuildTypes = listOf("release")
|
||||
}
|
||||
|
||||
tasks.withType<Detekt>().configureEach {
|
||||
jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString()
|
||||
|
||||
exclude(defaultExcludes)
|
||||
|
||||
reports {
|
||||
html.required.set(true)
|
||||
sarif.required.set(true)
|
||||
xml.required.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<DetektCreateBaselineTask>().configureEach {
|
||||
jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString()
|
||||
|
||||
exclude(defaultExcludes)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
detektPlugins(libs.detekt.plugin.compose)
|
||||
}
|
||||
|
||||
val defaultExcludes = listOf(
|
||||
"**/.gradle/**",
|
||||
"**/.idea/**",
|
||||
"**/build/**",
|
||||
".github/**",
|
||||
"gradle/**",
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import com.diffplug.gradle.spotless.SpotlessExtension
|
||||
|
||||
plugins {
|
||||
id("com.diffplug.spotless")
|
||||
}
|
||||
|
||||
configure<SpotlessExtension> {
|
||||
kotlin {
|
||||
target(
|
||||
"src/*/java/*.kt",
|
||||
"src/*/kotlin/*.kt",
|
||||
"src/*/java/**/*.kt",
|
||||
"src/*/kotlin/**/*.kt",
|
||||
)
|
||||
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig")
|
||||
.editorConfigOverride(kotlinEditorConfigOverride)
|
||||
}
|
||||
|
||||
kotlinGradle {
|
||||
target(
|
||||
"*.gradle.kts",
|
||||
)
|
||||
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig")
|
||||
.editorConfigOverride(
|
||||
mapOf(
|
||||
"ktlint_code_style" to "intellij_idea",
|
||||
"ktlint_standard_function-expression-body" to "disabled",
|
||||
"ktlint_standard_function-signature" to "disabled",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
flexmark {
|
||||
target(
|
||||
"*.md",
|
||||
)
|
||||
flexmark()
|
||||
}
|
||||
|
||||
format("misc") {
|
||||
target(".gitignore")
|
||||
trimTrailingWhitespace()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import com.diffplug.gradle.spotless.SpotlessExtension
|
||||
|
||||
plugins {
|
||||
id("com.diffplug.spotless")
|
||||
}
|
||||
|
||||
configure<SpotlessExtension> {
|
||||
kotlin {
|
||||
target(
|
||||
"build-plugin/src/*/kotlin/*.kt",
|
||||
"build-plugin/src/*/kotlin/**/*.kt",
|
||||
)
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig")
|
||||
.editorConfigOverride(kotlinEditorConfigOverride)
|
||||
}
|
||||
|
||||
kotlinGradle {
|
||||
target(
|
||||
"*.gradle.kts",
|
||||
"build-plugin/*.gradle.kts",
|
||||
"build-plugin/src/*/kotlin/*.kts",
|
||||
"build-plugin/src/*/kotlin/**/*.kts",
|
||||
)
|
||||
|
||||
ktlint(libs.versions.ktlint.get())
|
||||
.setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig")
|
||||
.editorConfigOverride(
|
||||
mapOf(
|
||||
"ktlint_code_style" to "intellij_idea",
|
||||
"ktlint_standard_function-expression-body" to "disabled",
|
||||
"ktlint_standard_function-signature" to "disabled",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
flexmark {
|
||||
target(
|
||||
"*.md",
|
||||
"docs/*.md",
|
||||
"docs/**/*.md",
|
||||
)
|
||||
flexmark()
|
||||
}
|
||||
|
||||
format("misc") {
|
||||
target(".gitignore")
|
||||
trimTrailingWhitespace()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue