From ea0ce04d6aaec6978201327b237d243cd06b639b Mon Sep 17 00:00:00 2001 From: "sungyong.an" Date: Mon, 28 Sep 2026 21:00:36 +0900 Subject: [PATCH 1/2] Add module detector settings plugin Move the inline ModuleDetector out of settings.gradle.kts into the moop.module.detector precompiled settings plugin in build-logic. It skips hidden and build directories, so the configuration cache is no longer invalidated by .gradle/ changing on every build. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Upxj1FvQeDpycupKdPQHUZ --- build-logic/module-detector/build.gradle.kts | 3 + .../moop.module.detector.settings.gradle.kts | 13 +++++ build-logic/settings.gradle.kts | 2 +- settings.gradle.kts | 57 ++----------------- 4 files changed, 22 insertions(+), 53 deletions(-) create mode 100644 build-logic/module-detector/build.gradle.kts create mode 100644 build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts diff --git a/build-logic/module-detector/build.gradle.kts b/build-logic/module-detector/build.gradle.kts new file mode 100644 index 000000000..bc0172f0f --- /dev/null +++ b/build-logic/module-detector/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `kotlin-dsl` +} diff --git a/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts b/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts new file mode 100644 index 000000000..265c1c15f --- /dev/null +++ b/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts @@ -0,0 +1,13 @@ +// Includes every directory that has a build.gradle(.kts) file as a module. +// Hidden and build directories are skipped, and so are nested builds with their own settings file. + +fun File.subDirs(): List = + listFiles().orEmpty().filter { !it.name.startsWith(".") && it.name != "build" && it.isDirectory } + +fun findModules(dir: File, path: String): List = when { + listOf("settings.gradle", "settings.gradle.kts").any { dir.resolve(it).isFile } -> emptyList() + listOf("build.gradle", "build.gradle.kts").any { dir.resolve(it).isFile } -> listOf(path) + else -> dir.subDirs().flatMap { findModules(it, "$path:${it.name}") } +} + +include(rootDir.subDirs().flatMap { findModules(it, ":${it.name}") }.sorted()) diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 8d8850917..8079dcfe5 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -10,4 +10,4 @@ dependencyResolutionManagement { } } -include(":convention") +include(":convention", ":module-detector") diff --git a/settings.gradle.kts b/settings.gradle.kts index efabaf16d..e863c5f8f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,11 @@ pluginManagement { } } +plugins { + // Automatically includes every directory that has a build.gradle(.kts) file. + id("moop.module.detector") +} + dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { @@ -23,55 +28,3 @@ dependencyResolutionManagement { } enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") - -// Automatically detect modules. -ModuleDetector.modules(rootDir).forEach { module -> - include(module) -} - -private object ModuleDetector { - - fun modules(rootDir: File) : List { - return rootDir.listDirs().flatMap { dir -> - findModules(parent = "", dir = dir) - } - } - - private fun findModules(parent: String, dir: File): List { - if (dir.isDirectory.not() || dir.isProject()) { - return emptyList() - } - - val current: String = parent + ":" + dir.name - return if (dir.isModule()) { - println("include '$current'") - listOf(current) - } else { - dir.listDirs().flatMap { subDir -> - findModules(parent = current, dir = subDir) - } - } - } - - private fun File.isProject(): Boolean { - if (isDirectory.not()) { - return false - } - return listFiles().orEmpty().any { - it.isFile && (it.name == "settings.gradle" || it.name == "settings.gradle.kts") - } - } - - private fun File.isModule(): Boolean { - if (isDirectory.not()) { - return false - } - return listFiles().orEmpty().any { - it.isFile && (it.name == "build.gradle" || it.name == "build.gradle.kts") - } - } - - private fun File.listDirs(): List { - return listFiles().orEmpty().filter { it.isDirectory } - } -} From ec72488148abbb660b6c401f3c9f060a3eaa45ae Mon Sep 17 00:00:00 2001 From: "sungyong.an" Date: Mon, 28 Sep 2026 21:24:06 +0900 Subject: [PATCH 2/2] Use a Kotlin plugin class with unit tests for the module detector Replace the precompiled settings script with ModuleDetectorPlugin, registered in gradlePlugin {} like the convention plugins, and cover findModules with JUnit 4 unit tests using the existing test-junit entry. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Upxj1FvQeDpycupKdPQHUZ --- build-logic/module-detector/build.gradle.kts | 13 ++++++ .../src/main/kotlin/ModuleDetectorPlugin.kt | 31 +++++++++++++ .../moop.module.detector.settings.gradle.kts | 13 ------ .../test/kotlin/ModuleDetectorPluginTest.kt | 45 +++++++++++++++++++ build-logic/settings.gradle.kts | 3 +- 5 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 build-logic/module-detector/src/main/kotlin/ModuleDetectorPlugin.kt delete mode 100644 build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts create mode 100644 build-logic/module-detector/src/test/kotlin/ModuleDetectorPluginTest.kt diff --git a/build-logic/module-detector/build.gradle.kts b/build-logic/module-detector/build.gradle.kts index bc0172f0f..f8293c04d 100644 --- a/build-logic/module-detector/build.gradle.kts +++ b/build-logic/module-detector/build.gradle.kts @@ -1,3 +1,16 @@ plugins { `kotlin-dsl` } + +dependencies { + testImplementation(libs.test.junit) +} + +gradlePlugin { + plugins { + register("moduleDetector") { + id = "moop.module.detector" + implementationClass = "ModuleDetectorPlugin" + } + } +} diff --git a/build-logic/module-detector/src/main/kotlin/ModuleDetectorPlugin.kt b/build-logic/module-detector/src/main/kotlin/ModuleDetectorPlugin.kt new file mode 100644 index 000000000..56cc5be12 --- /dev/null +++ b/build-logic/module-detector/src/main/kotlin/ModuleDetectorPlugin.kt @@ -0,0 +1,31 @@ +import org.gradle.api.Plugin +import org.gradle.api.initialization.Settings +import java.io.File + +/** + * Includes every directory that has a build.gradle(.kts) file as a module, + * so settings.gradle.kts needs no include list. + */ +class ModuleDetectorPlugin : Plugin { + override fun apply(settings: Settings) { + settings.include(findModules(settings.rootDir)) + } +} + +/** + * Returns the sorted Gradle paths of the modules below [rootDir]. + * Hidden and build directories are skipped, and so are nested builds with their own settings file. + */ +internal fun findModules(rootDir: File): List = + rootDir.subDirs().flatMap { it.modules(":${it.name}") }.sorted() + +private fun File.modules(path: String): List = when { + hasFile("settings.gradle", "settings.gradle.kts") -> emptyList() + hasFile("build.gradle", "build.gradle.kts") -> listOf(path) + else -> subDirs().flatMap { it.modules("$path:${it.name}") } +} + +private fun File.hasFile(vararg names: String): Boolean = names.any { resolve(it).isFile } + +private fun File.subDirs(): List = + listFiles().orEmpty().filter { !it.name.startsWith(".") && it.name != "build" && it.isDirectory } diff --git a/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts b/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts deleted file mode 100644 index 265c1c15f..000000000 --- a/build-logic/module-detector/src/main/kotlin/moop.module.detector.settings.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -// Includes every directory that has a build.gradle(.kts) file as a module. -// Hidden and build directories are skipped, and so are nested builds with their own settings file. - -fun File.subDirs(): List = - listFiles().orEmpty().filter { !it.name.startsWith(".") && it.name != "build" && it.isDirectory } - -fun findModules(dir: File, path: String): List = when { - listOf("settings.gradle", "settings.gradle.kts").any { dir.resolve(it).isFile } -> emptyList() - listOf("build.gradle", "build.gradle.kts").any { dir.resolve(it).isFile } -> listOf(path) - else -> dir.subDirs().flatMap { findModules(it, "$path:${it.name}") } -} - -include(rootDir.subDirs().flatMap { findModules(it, ":${it.name}") }.sorted()) diff --git a/build-logic/module-detector/src/test/kotlin/ModuleDetectorPluginTest.kt b/build-logic/module-detector/src/test/kotlin/ModuleDetectorPluginTest.kt new file mode 100644 index 000000000..563852bb1 --- /dev/null +++ b/build-logic/module-detector/src/test/kotlin/ModuleDetectorPluginTest.kt @@ -0,0 +1,45 @@ +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class ModuleDetectorPluginTest { + + @get:Rule + val tempFolder = TemporaryFolder() + + @Test + fun findModules_includesDirectoriesWithBuildFile() { + createFile("build.gradle.kts") + createFile("app/build.gradle") + createFile("feature/home/api/build.gradle.kts") + createFile("feature/home/impl/build.gradle") + createFile("feature/home/impl/src/build.gradle") + tempFolder.newFolder("docs") + + assertEquals(listOf(":app", ":feature:home:api", ":feature:home:impl"), findModules(tempFolder.root)) + } + + @Test + fun findModules_skipsHiddenAndBuildDirectories() { + createFile(".gradle/cache/build.gradle") + createFile("build/generated/build.gradle") + createFile("feature/build/tmp/build.gradle") + createFile("app/build.gradle") + + assertEquals(listOf(":app"), findModules(tempFolder.root)) + } + + @Test + fun findModules_skipsNestedBuilds() { + createFile("build-logic/settings.gradle.kts") + createFile("build-logic/convention/build.gradle.kts") + createFile("app/build.gradle") + + assertEquals(listOf(":app"), findModules(tempFolder.root)) + } + + private fun createFile(path: String) { + tempFolder.root.resolve(path).apply { parentFile.mkdirs() }.createNewFile() + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 8079dcfe5..e37e14e72 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -10,4 +10,5 @@ dependencyResolutionManagement { } } -include(":convention", ":module-detector") +include(":convention") +include(":module-detector")