Skip to content

Commit 26fb981

Browse files
Fix tools.forma.includer plugin behavior (#128)
* Fix `tools.forma.includer` plugin behavior - Skip including nested projects - Consider only dirs with `build.gradle(.kts)` files as subprojects - Extend `IncluderPluginFunctionalTest` to cover problematic use cases * Make Includer plugin more flexible and configurable - Allowed to disable arbitrary build file names - Allowed to specify additional folder names to ignore - Do not allow to configure the project if a module with more than one build file is encountered - Covered all use cases of the plugin with tests - Added documentation to the README * Made Includer configurable via extension in settings.gradle.kts - Create IncluderExtension and Dsl function `includer` for it - Add docs for IncluderExtension properties - Disable arbitraryBuildScriptNames by default - Covered all use cases of the plugin with tests - Update documentation in the README * Includer 0.2 --------- Co-authored-by: Stepan Goncharov <[email protected]>
1 parent bc0ca83 commit 26fb981

File tree

8 files changed

+369
-96
lines changed

8 files changed

+369
-96
lines changed

application/settings.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyConstraint.strictly
2-
31
pluginManagement {
42
apply(
53
from =
@@ -19,6 +17,8 @@ plugins {
1917
id("tools.forma.depgen")
2018
}
2119

20+
includer { arbitraryBuildScriptNames = true }
21+
2222
rootProject.name = "application"
2323

2424
val filteredTokens =
@@ -58,7 +58,7 @@ dependencyResolutionManagement {
5858
addPlugin("tools.forma.demo:dependencies", "0.0.1")
5959
addPlugin(
6060
"com.google.devtools.ksp",
61-
"1.8.10-1.0.9",
61+
"$embeddedKotlinVersion-1.0.9",
6262
"androidx.room:room-compiler:$roomVersion"
6363
)
6464
}

includer/Changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.2.0 Minor patch release
2+
3+
- Includer configuration extension added
4+
15
# 0.1.3 Minor patch release
26

37
- Prevent traversing nested projects

includer/Readme.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Includer
22

3-
Simple and powerful plugin which helps you to save time and avoid writing tons of boilerplate code. Uses kotlin coroutines to efficiently traverse even deeply nested project trees.
3+
Simple and powerful plugin which helps you to save time and avoid writing tons of boilerplate code.
4+
Uses kotlin coroutines to efficiently traverse even deeply nested project trees.
45

56
## Installation
67

@@ -11,3 +12,55 @@ plugins {
1112
id("tools.forma.includer") version "0.1.3"
1213
}
1314
```
15+
16+
# How does it work?
17+
18+
Includer traverses the project's file tree and includes as subprojects all directories that have
19+
files with `build.gradle(.kts)` files.
20+
21+
Includer skips directories that have `settings.gradle(.kts)` files, treating them as nested projects.
22+
23+
Example:
24+
25+
```
26+
rootProject
27+
|--app <- will be included as :app
28+
|----build.gradle.kts
29+
|
30+
|--build-logic <- will be ignored
31+
|----settings.gradle.kts
32+
|
33+
|--feature1
34+
|----api <- will be included as :feature1-api
35+
|------build.gradle.kts
36+
```
37+
38+
# Plugin configuration
39+
40+
The plugin is configurable by specifying properties in the `includer` extension.
41+
42+
## Ignored folders
43+
44+
Includer always skips directories with following names: `build`, `src`, `buildSrc`. But you can
45+
specify additional ignored folder names:
46+
47+
```kotlin
48+
// in settings.gradle.kts file after `plugins` block
49+
includer {
50+
excludeFolders(".cmake_cache", "scripts")
51+
}
52+
```
53+
54+
## Arbitrary build file names
55+
56+
If you want the plugin to look for any `*.gradle(.kts)` files, not just `build.gradle(.kts)`:
57+
58+
```kotlin
59+
// in settings.gradle.kts file after `plugins` block
60+
includer {
61+
arbitraryBuildScriptNames = true
62+
}
63+
```
64+
65+
> NOTE: If you use this property, there can only be one `*.gradle(.kts)` file in the root
66+
> of any module.

includer/plugin/build.gradle.kts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
@file:Suppress("UnstableApiUsage")
22

33
plugins {
4-
id("com.gradle.plugin-publish") version "1.1.0"
4+
id("com.gradle.plugin-publish") version "1.2.0"
55
id("org.jetbrains.kotlin.jvm") version embeddedKotlinVersion
66
}
77

8-
version = "0.1.3"
8+
version = "0.2.0"
99
group = "tools.forma"
1010

1111
repositories {
1212
mavenCentral()
1313
}
1414

1515
dependencies {
16-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
17-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.4")
16+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2")
1817
}
1918

2019
val javaLanguageVersion = JavaLanguageVersion.of(11)
@@ -34,19 +33,18 @@ testing {
3433
suites {
3534
// Configure the built-in test suite
3635
val test by getting(JvmTestSuite::class) {
37-
// Use Kotlin Test test framework
36+
// Use Kotlin Test framework
3837
useKotlinTest(embeddedKotlinVersion)
3938
}
4039

4140
// Create a new test suite
4241
val functionalTest by registering(JvmTestSuite::class) {
43-
// Use Kotlin Test test framework
42+
// Use Kotlin Test framework
4443
useKotlinTest(embeddedKotlinVersion)
4544

4645
dependencies {
4746
// functionalTest test suite depends on the production code in tests
4847
implementation(project())
49-
// implementation("org.gradle:gradle-test-kit:${gradle.gradleVersion}")
5048
}
5149

5250
targets {

includer/plugin/src/functionalTest/kotlin/tools/forma/includer/IncluderPluginFunctionalTest.kt

Lines changed: 195 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
*/
44
package tools.forma.includer
55

6-
import java.io.File
7-
import kotlin.test.assertTrue
8-
import kotlin.test.Test
96
import org.gradle.testkit.runner.GradleRunner
7+
import org.junit.jupiter.api.assertThrows
108
import org.junit.jupiter.api.io.TempDir
9+
import java.io.File
10+
import kotlin.test.BeforeTest
11+
import kotlin.test.Test
12+
import kotlin.test.assertFalse
13+
import kotlin.test.assertTrue
1114

1215
/**
1316
* A simple functional test includer plugin.
@@ -17,26 +20,44 @@ class IncluderPluginFunctionalTest {
1720
@field:TempDir
1821
lateinit var projectDir: File
1922

20-
private val rootBuildFile by lazy { projectDir.resolve("build.gradle.kts") }
21-
private val settingsFile by lazy { projectDir.resolve("settings.gradle.kts") }
22-
private val projectFile by lazy {
23-
File(projectDir, "app").mkdir()
24-
projectDir.resolve("app/build.gradle.kts")
25-
}
26-
27-
@Test
28-
fun `include extra projects`() {
29-
// Set up the test build
30-
rootBuildFile.writeText("")
31-
projectFile.writeText("")
32-
settingsFile.writeText(
23+
@BeforeTest
24+
fun `prepare filesystem`() {
25+
projectDir.resolve("build.gradle.kts").createNewFile()
26+
projectDir.resolve("settings.gradle.kts").writeText(
3327
"""
3428
plugins {
3529
id("tools.forma.includer")
3630
}
31+
3732
""".trimIndent()
3833
)
34+
// :app
35+
File(projectDir, "app").mkdir()
36+
projectDir.resolve("app/build.gradle.kts").createNewFile()
37+
38+
// :feature1-api
39+
File(projectDir, "feature1/api").mkdirs()
40+
projectDir.resolve("feature1/api/api.gradle.kts").createNewFile()
3941

42+
// :feature1-impl
43+
File(projectDir, "feature1/impl").mkdirs()
44+
projectDir.resolve("feature1/impl/impl.gradle.kts").createNewFile()
45+
46+
// :util-android
47+
File(projectDir, "util/android").mkdirs()
48+
projectDir.resolve("util/android/build.gradle.kts").createNewFile()
49+
50+
// composite build :build-logic
51+
File(projectDir, "build-logic").mkdir()
52+
projectDir.resolve("build-logic/settings.gradle.kts").createNewFile()
53+
54+
// composite build :build-logic:conventions
55+
File(projectDir, "build-logic/conventions").mkdir()
56+
projectDir.resolve("build-logic/conventions/build.gradle.kts").createNewFile()
57+
}
58+
59+
@Test
60+
fun `include projects with default options`() {
4061
// Run the build
4162
val runner = GradleRunner.create()
4263
runner.forwardOutput()
@@ -46,6 +67,163 @@ class IncluderPluginFunctionalTest {
4667
val result = runner.build()
4768

4869
// Verify the result
49-
assertTrue(result.output.contains("Project ':app'"))
70+
assertTrue("Should include ':app' project") {
71+
result.output.contains("Project ':app'")
72+
}
73+
assertFalse(
74+
"Shouldn't include ':feature1-api' project " +
75+
"because this project has a non-standard build file name"
76+
) {
77+
result.output.contains("Project ':feature1-api'")
78+
}
79+
assertFalse(
80+
"Shouldn't include ':feature1-impl' project " +
81+
"because this project has a non-standard build file name"
82+
) {
83+
result.output.contains("Project ':feature1-impl'")
84+
}
85+
assertTrue("Should include ':util-android' project") {
86+
result.output.contains("Project ':util-android'")
87+
}
88+
assertFalse(
89+
"Shouldn't include ':build-logic' project " +
90+
"because it's a nested project"
91+
) {
92+
result.output.contains("Project ':build-logic'")
93+
}
94+
assertFalse(
95+
"Shouldn't include ':build-logic:conventions' project " +
96+
"because it's a subproject of a nested project"
97+
) {
98+
result.output.contains("Project ':build-logic-conventions'")
99+
}
100+
}
101+
102+
@Test
103+
fun `include projects with 'arbitraryBuildScriptNames=true' option`() {
104+
// Enable option `arbitraryBuildScriptNames`
105+
projectDir.resolve("settings.gradle.kts").appendText("""
106+
includer {
107+
arbitraryBuildScriptNames = true
108+
}
109+
""".trimIndent())
110+
111+
// Run the build
112+
val runner = GradleRunner.create()
113+
runner.forwardOutput()
114+
runner.withPluginClasspath()
115+
runner.withArguments("projects")
116+
runner.withProjectDir(projectDir)
117+
val result = runner.build()
118+
119+
// Verify the result
120+
assertTrue("Should include ':app' project") {
121+
result.output.contains("Project ':app'")
122+
}
123+
assertTrue("Should include ':feature1-api' project" +
124+
"because 'arbitraryBuildScriptNames = true'") {
125+
result.output.contains("Project ':feature1-api'")
126+
}
127+
assertTrue("Should include ':feature1-impl' project" +
128+
"because 'arbitraryBuildScriptNames = true'") {
129+
result.output.contains("Project ':feature1-impl'")
130+
}
131+
assertTrue("Should include ':util-android' project") {
132+
result.output.contains("Project ':util-android'")
133+
}
134+
assertFalse(
135+
"Shouldn't include ':build-logic' project " +
136+
"because it's a nested project"
137+
) {
138+
result.output.contains("Project ':build-logic'")
139+
}
140+
assertFalse(
141+
"Shouldn't include ':build-logic:conventions' project " +
142+
"because it's a subproject of a nested project"
143+
) {
144+
result.output.contains("Project ':build-logic-conventions'")
145+
}
146+
}
147+
148+
149+
@Test
150+
fun `include projects with 'excludeFolders' option`() {
151+
// Exclude additional folders with `excludeFolders(...)`
152+
projectDir.resolve("settings.gradle.kts").appendText("""
153+
includer {
154+
excludeFolders("android", "impl")
155+
}
156+
""".trimIndent())
157+
158+
// Run the build
159+
val runner = GradleRunner.create()
160+
runner.forwardOutput()
161+
runner.withPluginClasspath()
162+
runner.withArguments("projects")
163+
runner.withProjectDir(projectDir)
164+
val result = runner.build()
165+
166+
// Verify the result
167+
assertTrue("Should include ':app' project") {
168+
result.output.contains("Project ':app'")
169+
}
170+
assertFalse(
171+
"Shouldn't include ':feature1-api' project " +
172+
"because this project has a non-standard build file name"
173+
) {
174+
result.output.contains("Project ':feature1-api'")
175+
}
176+
assertFalse(
177+
"Shouldn't include ':feature1-impl' project " +
178+
"because this project has a non-standard build file name"
179+
) {
180+
result.output.contains("Project ':feature1-impl'")
181+
}
182+
assertFalse(
183+
"Shouldn't include ':util-android' project " +
184+
"because its folder name is in ignored folder names"
185+
) {
186+
result.output.contains("Project ':util-android'")
187+
}
188+
assertFalse(
189+
"Shouldn't include ':build-logic' project " +
190+
"because it's a nested project"
191+
) {
192+
result.output.contains("Project ':build-logic'")
193+
}
194+
assertFalse(
195+
"Shouldn't include ':build-logic:conventions' project " +
196+
"because it's a subproject of a nested project"
197+
) {
198+
result.output.contains("Project ':build-logic-conventions'")
199+
}
200+
}
201+
202+
@Test
203+
fun `include projects with multiple build files`() {
204+
// Enable option `arbitraryBuildScriptNames`
205+
projectDir.resolve("settings.gradle.kts").appendText("""
206+
includer {
207+
arbitraryBuildScriptNames = true
208+
}
209+
""".trimIndent())
210+
211+
// Create a second build file in addition to the existing one
212+
projectDir.resolve("app/something.gradle").createNewFile()
213+
214+
// Run the build
215+
val runner = GradleRunner.create()
216+
runner.forwardOutput()
217+
runner.withPluginClasspath()
218+
runner.withArguments("projects")
219+
runner.withProjectDir(projectDir)
220+
221+
// Verify the result
222+
val exception = assertThrows<Exception>("The build should fail") {
223+
runner.build()
224+
}
225+
assertTrue("Should detect more than one build file in :app module") {
226+
exception.message?.contains("app has more than one gradle build file") == true
227+
}
50228
}
51229
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import org.gradle.api.initialization.Settings
2+
import tools.forma.includer.IncluderExtension
3+
4+
fun Settings.includer(action: IncluderExtension.() -> Unit) =
5+
extensions.getByType(IncluderExtension::class.java).action()

0 commit comments

Comments
 (0)