diff --git a/Advanced.md b/Advanced.md new file mode 100644 index 00000000..cdf40c87 --- /dev/null +++ b/Advanced.md @@ -0,0 +1,48 @@ +## Advanced usage + +### Global configuration +Additional default configuration can be applied by adding to the **root project**'s `build.gradle`. +All submodules will use this config as default + +``` groovy +plugins { + id("com.starter.config") version("x.y.z) +} + +commonConfig { + javaVersion JavaVersion.VERSION_1_8 + javaFilesAllowed = true + androidPlugin { + compileSdkVersion 29 + minSdkVersion 23 + targetSdkVersion 29 + } + qualityPlugin { + formatOnCompile = false + } + versioningPlugin { + enabled = true + } +} +``` + +- `javaVersion` - defines which java version source code is compatible with +- `javaFilesAllowed` - defines if the project can contain java files, fails the build otherwise. +- `androidPlugin`: + - contains values passed to _Android Gradle Plugin_ +- `qualityPlugin`: + - `formatOnCompile` - defines if ktlint should format source code on every compilation +- `versioningPlugin`: + - `enabled` - enables/disables [Versioning Plugin](..#versioning-plugin) + +### Generating baselines +It is possible to generate baseline for every quality tool available in the project. +- `Android Lint` + Type `rm **/lint-*.xml ; ./gradlew projectLint -PrefreshBaseline --continue` into console +- `Detekt` + Create baseline using [provided configuration](https://github.com/arturbosch/detekt/blob/master/docs/pages/baseline.md) +- `Checkstyle` + Execute `./gradlew generateCheckstyleBaseline` task. +- `ktlint` + Unfortunately it is not possible to generate `ktlint` baseline. + Proper code style may be achieved by using `./gradlew formatKotlin` task. diff --git a/Readme.md b/Readme.md index b7c79f04..2b3bc682 100644 --- a/Readme.md +++ b/Readme.md @@ -11,7 +11,7 @@ ___ ## Motivation Maintaining multiple multi-module Android project often requires **copying project configuration across different projects**. -Even when project reaches more advanced stage it is still required to put non minimal effort to maintain its configuration. +Even when project reaches more advanced stage it is still required to put non-minimal effort to maintain its configuration. Starting a new project, from the scratch, **takes more than a day** to configure every tool you usually want to use. Sometimes people create template project or another way of keeping your project configuration in a good shape is using `buildSrc` plugins. Less code written, ease of sharing between projects but still some part of the code needed to be copied. @@ -23,16 +23,12 @@ This project goes further and addresses that issue by **exposing set of plugins* Repository consists of several plugins that makes initial project configuration effortless and easily extensible. Each module consists of configuration code most commonly used in Android project configuration. -### Getting started - -#### Add buildscript dependency - -### Plugins Configuration +### Module plugins #### Kotlin Library Plugin -Plugin configures automated [code style tasks](#quality-plugin), hooks for [common tasks](#day-to-day-use), +Plugin configures [code style tasks](#quality-plugin), hooks for [common tasks](#day-to-day-use), sets coverage reports generation or manages [versioning](#versioning-plugin) of the artifact -Apply plugin to project level `build.gradle` +Apply plugin to **project** level `build.gradle` ``` groovy plugins { @@ -47,12 +43,21 @@ projectConfig { - `javaFilesAllowed` - defines if the project can contain java files, fails the build otherwise +#### Multiplatform Library Plugin +For kotlin multiplatform libraries apply plugin to **project** level `build.gradle` + +``` groovy +plugins { + id("com.starter.library.multiplatform") version("x.y.z") +} +``` + #### Android Application/Library Plugin In addition to customizations made to [Kotlin Library Plugin](#kotlin-library-plugin) Android plugins -tweak default Android Gradle Plugin setup by disabling _BuildConfig_ file generation +tweaks default Android Gradle Plugin setup by disabling _BuildConfig_ file generation or recognizing `src/main/kotlin` (and similar) path as a valid source set. -Android Library plugin requires adding to project level `build.gradle`: +Android Library plugin requires adding to **project** level `build.gradle`: ``` groovy plugins { @@ -85,97 +90,52 @@ for example setting `fullDebug` as default variant would make `testFullDebugUnit `["freeRelease", "fullRelease"]` would make add `testFreeReleaseUnitTest` to `projectTest` and `testFreeReleaseLint` to `projectLint`. - `coverageExclusions` - defines jacoco coverage exclusions for specific module -#### Quality Plugin +##### Day-to-day use +After applying _Library_/_Application_ plugin following tasks become available: +- `./gradlew projectTest` + Runs tests for all modules using either predefined tasks (i.e. `test` for kotlin modules or `testDebugUnitTest` for android libraries) or use customized values. +- `./gradlew projectLint` + Runs Android lint checks against all modules (if custom lint checks are applied then for Kotlin modules too) +- `./gradlew projectCodeStyle` + Verifies if code style matches modern standards using tools such as [`ktlint`](https://github.com/pinterest/ktlint), [`Detekt`](https://github.com/arturbosch/detekt) or [`Checkstyle`](https://checkstyle.org/) with predefined config. +- `./gradlew projectCoverage` + Automatically generates test coverage reports for all modules using [`Jacoco`](https://github.com/jacoco/jacoco) -Quality plugin is applied automatically by a _Module plugin_, but there is a possibility to use it as a standalone plugin. -Apply plugin to project level `build.gradle` +Those tasks allows you to run tests efficiently for all modules by typing just a single task. + +### Standalone plugins +#### Quality Plugin +To only configure codestyle tools apply plugin to **project** level `build.gradle` ``` plugins { id("com.starter.quality") version("x.y.z") } ``` which applies and configures code style tasks for the project automatically. -To execute run: `./gradlew projectCodeStyle` to check codestyl or `./gradlew issueLinksReport` to find and check state of all issuetracker links linked in code comments. - -##### Generating baselines - -When integrating code style checks into large projects it is almost forbidden to introduce large sets of changes. -It is possible to generate baseline for every quality tool available in the project. -- `Android Lint` - Type `rm **/lint-*.xml ; ./gradlew projectLint -PrefreshBaseline --continue` into console -- `Detekt` - Create baseline using [provided configuration](https://github.com/arturbosch/detekt/blob/master/docs/pages/baseline.md) -- `Checkstyle` - Execute `./gradlew generateCheckstyleBaseline` task. -- `ktlint` - Unfortunately it is not possible to generate `ktlint` baseline. - Proper code style may be achieved by using `./gradlew formatKotlin` task. + +Tasks available: +- `./gradlew projectCodeStyle` - checks codestyle using all tools +- `./gradlew issueLinksReport` - finds and check state of all issuetracker links linked in code comments + +Quality Plugin gets applied automatically when using any of module _Application_/_Library_ plugins above. #### Versioning Plugin -Applied automatically. Uses tag-based versioning backed by the [allegro/axion-release-plugin](https://github.com/allegro/axion-release-plugin) (view a [full documentation](https://github.com/allegro/axion-release-plugin)) To enable it as a standalone plugin, apply plugin to root project `build.gradle` ``` apply plugin: 'com.starter.versioning' ``` -Can be disabled using [Global Configuration](#global-configuration) +Versioning plugin gets applied automatically when using any of module _Application_/_Library_ plugins above and can be disabled using [Global Configuration](Advanced.md#global-configuration) Regular flow relies on calling - `./gradlew cV` or `./gradlew currentVersion` - `./gradlew markNextVersion -Prelease.version=1.0.0` - `./gradlew release` (which pushes proper tags to remote server) -#### Global configuration - -Additional default configuration can be applied by adding to **root project** `build.gradle`. -All submodules will use this config as default - -``` groovy -plugins { - id("com.starter.config") version("x.y.z) -} - -commonConfig { - javaVersion JavaVersion.VERSION_1_8 - javaFilesAllowed = true - androidPlugin { - compileSdkVersion 29 - minSdkVersion 23 - targetSdkVersion 29 - } - qualityPlugin { - formatOnCompile = false - } - versioningPlugin { - enabled = true - } -} -``` - -- `javaVersion` - defines which java version source code is compatible with -- `javaFilesAllowed` - defines if the project can contain java files, fails the build otherwise. -- `androidPlugin`: - - contains values passed to _Android Gradle Plugin_ -- `qualityPlugin`: - - `formatOnCompile` - defines if ktlint should format source code on every compilation -- `versioningPlugin`: - - `enabled` - enables/disables [Versioning Plugin](#versioning-plugin) - -### Day-to-day use -After applying library/application plugin there are appropriate tasks added: -- `./gradlew projectTest` -Runs tests for all modules using either predefined tasks (i.e. `test` for kotlin modules or `testDebugUnitTest` for android libraries) or use customized values. -- `./gradlew projectLint` -Runs Android lint checks against all modules (if custom lint checks are applied then for Kotlin modules too) -- `./gradlew projectCodeStyle` -Verifies if code style matches modern standards using tools such as [`ktlint`](https://github.com/pinterest/ktlint), [`Detekt`](https://github.com/arturbosch/detekt) or [`Checkstyle`](https://checkstyle.org/) with predefined config. -- `./gradlew projectCoverage` -Automatically generates test coverage reports for all modules using [`Jacoco`](https://github.com/jacoco/jacoco) - -Those tasks allows you to run tests efficiently for all modules by typing just a single task. -That solves an issue when for example `test` task unnecessarily executes tests for all build variants and more strict `testDebug` skips executing tests in kotlin only modules. +### Advanced usage +See [Advanced usage](Advanced.md) ## Sample project Sample [Github Browser](https://github.com/mateuszkwiecinski/github_browser) project - a customized, `buildSrc` based plugin application. diff --git a/android/src/main/kotlin/com/project/starter/modules/internal/AndroidPluginUtils.kt b/android/src/main/kotlin/com/project/starter/modules/internal/AndroidPluginUtils.kt index a3ee87c0..4da7d321 100644 --- a/android/src/main/kotlin/com/project/starter/modules/internal/AndroidPluginUtils.kt +++ b/android/src/main/kotlin/com/project/starter/modules/internal/AndroidPluginUtils.kt @@ -1,6 +1,5 @@ package com.project.starter.modules.internal -import com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION import com.android.build.gradle.BaseExtension import com.android.build.gradle.TestedExtension import com.android.build.gradle.api.BaseVariant @@ -16,8 +15,6 @@ import com.project.starter.quality.internal.configureAndroidCoverage import org.gradle.api.DomainObjectSet import org.gradle.api.Project -private const val LAST_STABLE_AGP_VERSION = 4 - internal fun BaseExtension.configureAndroidPlugin(rootConfig: RootConfigExtension) { defaultConfig.apply { compileSdkVersion(rootConfig.android.compileSdkVersion) @@ -26,11 +23,6 @@ internal fun BaseExtension.configureAndroidPlugin(rootConfig: RootConfigExtensio setTestInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner") } - if (ANDROID_GRADLE_PLUGIN_VERSION.split(".").firstOrNull()?.toIntOrNull() ?: 0 <= LAST_STABLE_AGP_VERSION) { - @Suppress("deprecation") - dexOptions.preDexLibraries = true - } - addKotlinSourceSets() compileOptions.apply { @@ -42,13 +34,10 @@ internal fun BaseExtension.configureAndroidPlugin(rootConfig: RootConfigExtensio internal fun Project.configureAndroidProject(variants: DomainObjectSet, projectConfig: AndroidExtension) { configureAndroidCoverage(variants, projectConfig.coverageExclusions) val findBuildVariants = { - if (projectConfig.defaultVariants.isEmpty()) { - val default = variants.firstOrNull { it.buildType.name == "debug" } - ?: variants.first() + projectConfig.defaultVariants.ifEmpty { + val default = variants.firstOrNull { it.buildType.name == "debug" } ?: variants.first() listOf(default.name.capitalize()) - } else { - projectConfig.defaultVariants } } registerProjectLintTask { projectLint -> diff --git a/android/src/main/kotlin/com/project/starter/quality/internal/AndroidCoverage.kt b/android/src/main/kotlin/com/project/starter/quality/internal/AndroidCoverage.kt index 9fcb21ab..5cfbf959 100644 --- a/android/src/main/kotlin/com/project/starter/quality/internal/AndroidCoverage.kt +++ b/android/src/main/kotlin/com/project/starter/quality/internal/AndroidCoverage.kt @@ -35,12 +35,12 @@ internal fun Project.configureAndroidCoverage(variants: DomainObjectSet("checkstyle") { - it.toolVersion = "8.41.1" + it.toolVersion = "8.44" } } @@ -124,7 +124,7 @@ private fun Project.configureTask(task: Checkstyle) { config?.let { task.configFile = it } ?: logger.warn("Missing Checkstyle configuration file") task.reports { report -> - report.html.isEnabled = false - report.xml.isEnabled = true + report.html.required.set(false) + report.xml.required.set(true) } } diff --git a/quality/src/test/kotlin/com/project/starter/quality/QualityPluginTest.kt b/quality/src/test/kotlin/com/project/starter/quality/QualityPluginTest.kt index e48a2e15..2ee2287c 100644 --- a/quality/src/test/kotlin/com/project/starter/quality/QualityPluginTest.kt +++ b/quality/src/test/kotlin/com/project/starter/quality/QualityPluginTest.kt @@ -5,7 +5,6 @@ import com.project.starter.javaClass import com.project.starter.kotlinClass import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome -import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -62,7 +61,7 @@ internal class QualityPluginTest : WithGradleProjectTest() { @Test fun `formatOnCompile option enables failing builds if code style errors found`() { val enableFormatOnCompile = { - @Language("groovy") + // language=groovy val buildscript = """ plugins { @@ -97,7 +96,7 @@ internal class QualityPluginTest : WithGradleProjectTest() { @Test fun `projectCodeStyle fails if Checkstyle violation found`() { rootDirectory.resolve("src/test/java/JavaFileWithCheckstyleIssues.java") { - @Language("java") + // language=java val javaClass = """ import java.io.IOException; @@ -128,7 +127,7 @@ internal class QualityPluginTest : WithGradleProjectTest() { @Test fun `projectCodeStyle is not present if java files are not allowed`() { - @Language("groovy") + // language=groovy val buildscript = """ plugins { @@ -152,7 +151,7 @@ internal class QualityPluginTest : WithGradleProjectTest() { @Test fun `detekt fails on magic number`() { rootDirectory.resolve("src/main/kotlin/MagicNumber.kt") { - @Language("kotlin") + // language=kotlin val kotlinClass = """ class MagicNumber { diff --git a/quality/src/test/kotlin/com/project/starter/quality/tasks/GenerateCheckstyleBaselineTaskTest.kt b/quality/src/test/kotlin/com/project/starter/quality/tasks/GenerateCheckstyleBaselineTaskTest.kt index 7d0b45d5..70545dd1 100644 --- a/quality/src/test/kotlin/com/project/starter/quality/tasks/GenerateCheckstyleBaselineTaskTest.kt +++ b/quality/src/test/kotlin/com/project/starter/quality/tasks/GenerateCheckstyleBaselineTaskTest.kt @@ -4,7 +4,6 @@ import com.project.starter.WithGradleProjectTest import com.project.starter.javaClass import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome -import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import java.io.File @@ -20,7 +19,7 @@ internal class GenerateCheckstyleBaselineTaskTest : WithGradleProjectTest() { resolve("build.gradle").writeText("") moduleRoot = resolve("javaModule") { - @Language("groovy") + // language=groovy val script = """ plugins { @@ -45,7 +44,7 @@ internal class GenerateCheckstyleBaselineTaskTest : WithGradleProjectTest() { @Test fun `generating baseline makes build to pass on old code, but fail on new one`() { moduleRoot.resolve("src/test/java/OldCode.java") { - @Language("java") + // language=java val javaClass = """ public class OldCode { @@ -65,7 +64,7 @@ internal class GenerateCheckstyleBaselineTaskTest : WithGradleProjectTest() { assertThat(checkStyleOldCode.task(":javaModule:checkstyle")?.outcome).isEqualTo(TaskOutcome.SUCCESS) moduleRoot.resolve("src/test/java/NewCode.java") { - @Language("java") + // language=java val javaClass = """ public class NewCode { diff --git a/quality/src/test/kotlin/com/project/starter/quality/tasks/IssueLinksCheckerTaskTest.kt b/quality/src/test/kotlin/com/project/starter/quality/tasks/IssueLinksCheckerTaskTest.kt index 7fcb0e20..33347aee 100644 --- a/quality/src/test/kotlin/com/project/starter/quality/tasks/IssueLinksCheckerTaskTest.kt +++ b/quality/src/test/kotlin/com/project/starter/quality/tasks/IssueLinksCheckerTaskTest.kt @@ -4,7 +4,6 @@ import com.project.starter.WithGradleProjectTest import com.project.starter.javaClass import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome -import org.intellij.lang.annotations.Language import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -14,7 +13,7 @@ internal class IssueLinksCheckerTaskTest : WithGradleProjectTest() { @BeforeEach fun setUp() { rootDirectory.apply { - @Language("groovy") + // language=groovy val script = """ plugins { @@ -39,7 +38,7 @@ internal class IssueLinksCheckerTaskTest : WithGradleProjectTest() { @Test fun `does not warn on regular project`() { rootDirectory.resolve("src/main/kotlin/ValidKotlin.kt") { - @Language("kotlin") + // language=kotlin val randomLinks = """ /** @@ -61,7 +60,7 @@ internal class IssueLinksCheckerTaskTest : WithGradleProjectTest() { @Disabled("Google Issue tracker is not supported yet") fun `reports issuetracker issues`() { rootDirectory.resolve("src/main/kotlin/ValidKotlin.kt") { - @Language("kotlin") + // language=kotlin val randomLinks = """ /** @@ -82,7 +81,7 @@ internal class IssueLinksCheckerTaskTest : WithGradleProjectTest() { @Test fun `reports youtrack issues`() { - @Language("kotlin") + // language=kotlin val randomLinks = """ /** @@ -115,7 +114,7 @@ internal class IssueLinksCheckerTaskTest : WithGradleProjectTest() { @Test fun `reports github issues`() { rootDirectory.resolve("src/main/kotlin/ValidKotlin.kt") { - @Language("kotlin") + // language=kotlin val randomLinks = """ /** diff --git a/testing/src/main/kotlin/com/project/starter/Factories.kt b/testing/src/main/kotlin/com/project/starter/Factories.kt index 939fe6a3..9a882840 100644 --- a/testing/src/main/kotlin/com/project/starter/Factories.kt +++ b/testing/src/main/kotlin/com/project/starter/Factories.kt @@ -1,9 +1,7 @@ package com.project.starter -import org.intellij.lang.annotations.Language - -@Language("java") fun javaClass(className: String) = + // language=java """ public class $className { @@ -11,15 +9,15 @@ fun javaClass(className: String) = """.trimIndent() -@Language("kotlin") fun kotlinClass(className: String) = + // language=kotlin """ object $className """.trimIndent() -@Language("kotlin") fun kotlinTestClass(className: String) = + // language=kotlin """ class $className {