Gradle自动生成include列表失效,手动列表正常,求原因
我有大量子项目,想自动加入构建。手动编写include的项目路径列表时一切正常,但用递归函数生成的内容完全一致(仅顺序不同)的列表时,运行engine implementation模块的测试会报错,提示该项目目录不在settings.gradle.kts定义的构建中。为什么自动生成的列表会失效?
settings.gradle.kts 相关代码
rootProject.name = "MyApplication" // 递归遍历所有包含build.gradle.kts的目录并加入构建 fun includeModules(set: Set<String>, prefix: String, dir: File): Set<String> { val gradlePath = if (dir.name == ".") "" else "$prefix:${dir.name}" val intermediateSet = set + if (dir.listFiles()?.firstOrNull { it.name == "build.gradle.kts" } != null && gradlePath != "") { println("including $gradlePath") setOf(gradlePath) } else { emptySet() } return dir.listFiles()?.filter { it.name != "build" && it.isDirectory } ?.fold(intermediateSet) { acc: Set<String>, file: File? -> acc + includeModules( intermediateSet, gradlePath, file ?: File(".") ) } ?: emptySet() } val output = includeModules(emptySet(), "", File(".")).toList() println("set $output") val list = mutableListOf<String>() list.add(":androidApp") list.add(":shared") list.add(":desktopApp") list.add(":client-network:header") list.add(":client-network:ktorImpl") list.add(":irc_entities") list.add(":irc_parser:header") list.add(":irc_parser:header_test") list.add(":irc_parser:implementation") list.add(":TestUtils") list.add(":client_state:header") list.add(":client_irc_engine:header") list.add(":client_irc_engine:implementation") list.add(":Utility_Functions") list.add(":bot_prototype:core") list.add(":bot_prototype:dep_inject") println("manual $list") include(output)
Gradle 配置阶段输出
including :client_irc_engine:implementation including :client_irc_engine:header including :irc_parser:implementation including :irc_parser:header_test including :irc_parser:header including :irc_entities including :Utility_Functions including :shared including :client-network:ktorImpl including :client-network:header including :desktopApp including :bot_prototype:core including :bot_prototype:dep_inject including :androidApp including :client_state:header including :TestUtils set [:client_irc_engine:implementation, :client_irc_engine:header, :irc_parser:implementation, :irc_parser:header_test, :irc_parser:header, :irc_entities, :Utility_Functions, :shared, :client-network:ktorImpl, :client-network:header, :desktopApp, :bot_prototype:core, :bot_prototype:dep_inject, :androidApp, :client_state:header, :TestUtils] manual [:androidApp, :shared, :desktopApp, :client-network:header, :client-network:ktorImpl, :irc_entities, :irc_parser:header, :irc_parser:header_test, :irc_parser:implementation, :TestUtils, :client_state:header, :client_irc_engine:header, :client_irc_engine:implementation, :Utility_Functions, :bot_prototype:core, :bot_prototype:dep_inject] > Configure project :shared w: The Default Kotlin Hierarchy Template was not applied to 'project ':shared'': Explicit .dependsOn() edges were configured for the following source sets: [iosArm64Main, iosMain, iosSimulatorArm64Main, iosX64Main] Consider removing dependsOn-calls or disabling the default template by adding 'kotlin.mpp.applyDefaultHierarchyTemplate=false' to your gradle.properties Learn more about hierarchy templates: https://kotl.in/hierarchy-template > Task :commonizeNativeDistribution UP-TO-DATE > Task :commonize UP-TO-DATE > Task :prepareKotlinIdeaImport UP-TO-DATE > Task :androidApp:commonizeCInterop UP-TO-DATE > Task :androidApp:copyCommonizeCInteropForIde UP-TO-DATE > Task :androidApp:commonize UP-TO-DATE > Task :androidApp:prepareKotlinIdeaImport UP-TO-DATE > Task :desktopApp:commonizeCInterop UP-TO-DATE > Task :desktopApp:copyCommonizeCInteropForIde UP-TO-DATE > Task :desktopApp:commonize UP-TO-DATE > Task :desktopApp:prepareKotlinIdeaImport UP-TO-DATE > Task :irc_entities:commonizeCInterop UP-TO-DATE > Task :irc_entities:copyCommonizeCInteropForIde UP-TO-DATE > Task :irc_entities:commonize UP-TO-DATE > Task :irc_entities:prepareKotlinIdeaImport UP-TO-DATE > Task :shared:commonizeCInterop UP-TO-DATE > Task :shared:copyCommonizeCInteropForIde UP-TO-DATE > Task :shared:commonize UP-TO-DATE > Task :shared:transformIosMainCInteropDependenciesMetadataForIde UP-TO-DATE > Task :shared:prepareKotlinIdeaImport UP-TO-DATE > Task :TestUtils:commonizeCInterop UP-TO-DATE > Task :TestUtils:copyCommonizeCInteropForIde UP-TO-DATE > Task :TestUtils:commonize UP-TO-DATE > Task :TestUtils:prepareKotlinIdeaImport UP-TO-DATE > Task :Utility_Functions:commonizeCInterop UP-TO-DATE > Task :Utility_Functions:copyCommonizeCInteropForIde UP-TO-DATE > Task :Utility_Functions:commonize UP-TO-DATE > Task :Utility_Functions:prepareKotlinIdeaImport UP-TO-DATE > Task :client-network:header:commonizeCInterop UP-TO-DATE > Task :client-network:header:copyCommonizeCInteropForIde UP-TO-DATE > Task :client-network:header:commonize UP-TO-DATE > Task :client-network:header:prepareKotlinIdeaImport UP-TO-DATE > Task :client-network:ktorImpl:commonizeCInterop UP-TO-DATE > Task :client-network:ktorImpl:copyCommonizeCInteropForIde UP-TO-DATE > Task :client-network:ktorImpl:commonize UP-TO-DATE > Task :client-network:ktorImpl:prepareKotlinIdeaImport UP-TO-DATE > Task :client_irc_engine:header:commonizeCInterop UP-TO-DATE > Task :client_irc_engine:header:copyCommonizeCInteropForIde UP-TO-DATE > Task :client_irc_engine:header:commonize UP-TO-DATE > Task :client_irc_engine:header:prepareKotlinIdeaImport UP-TO-DATE > Task :client_irc_engine:implementation:commonizeCInterop UP-TO-DATE > Task :client_irc_engine:implementation:copyCommonizeCInteropForIde UP-TO-DATE > Task :client_irc_engine:implementation:commonize UP-TO-DATE > Task :client_irc_engine:implementation:prepareKotlinIdeaImport UP-TO-DATE > Task :client_state:header:commonizeCInterop UP-TO-DATE > Task :client_state:header:copyCommonizeCInteropForIde UP-TO-DATE > Task :client_state:header:commonize UP-TO-DATE > Task :client_state:header:prepareKotlinIdeaImport UP-TO-DATE > Task :irc_parser:header:commonizeCInterop UP-TO-DATE > Task :irc_parser:header:copyCommonizeCInteropForIde UP-TO-DATE > Task :irc_parser:header:commonize UP-TO-DATE > Task :irc_parser:header:prepareKotlinIdeaImport UP-TO-DATE > Task :irc_parser:header_test:commonizeCInterop UP-TO-DATE > Task :irc_parser:header_test:copyCommonizeCInteropForIde UP-TO-DATE > Task :irc_parser:header_test:commonize UP-TO-DATE > Task :irc_parser:header_test:prepareKotlinIdeaImport UP-TO-DATE > Task :irc_parser:implementation:commonizeCInterop UP-TO-DATE > Task :irc_parser:implementation:copyCommonizeCInteropForIde UP-TO-DATE > Task :irc_parser:implementation:commonize UP-TO-DATE > Task :irc_parser:implementation:prepareKotlinIdeaImport UP-TO-DATE > Task :prepareKotlinBuildScriptModel UP-TO-DATE Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. For more on this, please refer to https://docs.gradle.org/8.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. BUILD SUCCESSFUL in 2s 30 actionable tasks: 30 up-to-date
测试阶段报错信息
Project directory '/Users/kylemcburnett/IdeaProjects/kmirc/client_irc_engine/implementation' is not part of the build defined by settings file '/Users/kylemcburnett/IdeaProjects/kmirc/settings.gradle.kts'. If this is an unrelated build, it must have its own settings file. * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. > Get more help at https://help.gradle.org. BUILD FAILED in 17ms
核心问题出在递归函数的fold逻辑里:你每次递归调用includeModules时,传入的是intermediateSet而非累加后的acc。这会导致父模块(比如:client_irc_engine)的路径没有被正确加入到集合中。
Gradle的规则是必须先包含父模块,才能包含其子模块。如果直接先尝试包含子模块:client_irc_engine:implementation,而父模块:client_irc_engine还没被include,Gradle无法识别这个子模块的归属关系,就会抛出“项目目录不在构建中”的错误。
手动列表之所以正常,是因为它先包含了:client_irc_engine:header,此时Gradle会自动隐式创建父模块:client_irc_engine,后续再包含:client_irc_engine:implementation时就能正常识别。但递归生成的列表先包含了:client_irc_engine:implementation,此时父模块还未被处理,就触发了报错。
修改递归函数中fold lambda里的参数传递,把递归调用includeModules的第一个参数从intermediateSet改成acc,确保每次递归都传递已经累加的模块集合,让父模块路径被正确包含:
rootProject.name = "MyApplication" // 递归遍历所有包含build.gradle.kts的目录并加入构建 fun includeModules(set: Set<String>, prefix: String, dir: File): Set<String> { val gradlePath = if (dir.name == ".") "" else "$prefix:${dir.name}" val intermediateSet = set + if (dir.listFiles()?.firstOrNull { it.name == "build.gradle.kts" } != null && gradlePath != "") { println("including $gradlePath") setOf(gradlePath) } else { emptySet() } return dir.listFiles()?.filter { it.name != "build" && it.isDirectory } ?.fold(intermediateSet) { acc: Set<String>, file: File? -> acc + includeModules( acc, // 修复:传递累加后的集合而非当前目录的临时集合 gradlePath, file ?: File(".") ) } ?: emptySet() } val output = includeModules(emptySet(), "", File(".")).toList() println("set $output") // 手动列表可保留用于对比,实际使用时可注释 val list = mutableListOf<String>() list.add(":androidApp") list.add(":shared") list.add(":desktopApp") list.add(":client-network:header") list.add(":client-network:ktorImpl") list.add(":irc_entities") list.add(":irc_parser:header") list.add(":irc_parser:header_test") list.add(":irc_parser:implementation") list.add(":TestUtils") list.add(":client_state:header") list.add(":client_irc_engine:header") list.add(":client_irc_engine:implementation") list.add(":Utility_Functions") list.add(":bot_prototype:core") list.add(":bot_prototype:dep_inject") println("manual $list") include(output)
修改后,递归函数会正确传递累加的模块集合,确保父模块被提前包含,Gradle就能正常识别所有子模块的归属,测试也能正常运行。
内容的提问来源于stack exchange,提问作者Kyle M

