执行clean后Gradle无法基于ANTLR源码集构建Fat Jar,否则可成功
看起来你遇到的问题核心在于ANTLR代码生成任务的触发时机和生成文件的目录管理,结合你的配置细节,我来帮你拆解原因并给出解决方案:
问题根源分析
生成目录违反Gradle约定
你将ANTLR生成的源码输出到了src/antlr/java/——这个目录属于手动编写的源码目录,Gradle会默认认为这里的文件是开发者手动维护的,不会自动触发generateGrammarSource任务去重新生成它们。当执行clean后(如果该目录下的生成文件被移除),compileAntlrJava找不到编译输入,进而导致compileJava失败,最终让依赖subprojects.compileJava的fatJar任务崩溃。任务依赖链存在隐性断裂
父项目的fatJar仅依赖subprojects.compileJava,但对于使用ANTLR的子项目来说,compileJava需要依赖compileAntlrJava,而compileAntlrJava又依赖generateGrammarSource。由于生成目录不在Gradle默认的管理范围内,这条依赖链无法被Gradle自动识别和触发。
解决方案
方案1:使用Gradle ANTLR插件的默认生成目录(推荐)
遵循Gradle的「约定优于配置」原则,让插件自动管理生成文件的生命周期和任务依赖:
修改子项目的ANTLR配置为:
apply plugin: 'antlr' generateGrammarSource { arguments += ["-visitor", "-package", "it.unibo.alchemist.biochemistrydsl"] // 移除自定义outputDirectory,使用插件默认的build/generated-sources/antlr/main } // 以下两行可以完全省略,插件已自动配置好依赖关系: // compileAntlrJava.dependsOn('generateGrammarSource') // compileJava.dependsOn('compileAntlrJava')
这样做的好处:
- 生成的源码会放在
build目录下,执行clean时会被自动删除 - Gradle会自动将生成目录加入
antlrsourceSet,确保compileAntlrJava能找到源码 - 任务依赖链完全由插件维护,无需手动配置
方案2:修复自定义生成目录的依赖配置(不推荐)
如果你坚持要将生成源码放在src目录下,需要手动补全依赖链并配置sourceSet:
apply plugin: 'antlr' sourceSets { antlr { java.srcDirs = ['src/antlr/java'] // 将生成目录加入antlr源码集合 } } generateGrammarSource { arguments += ["-visitor", "-package", "it.unibo.alchemist.biochemistrydsl"] def target = new File('src/antlr/java/') if ((target.exists() && target.isDirectory()) || target.mkdirs()) { outputDirectory = target } else { throw new IllegalStateException("$target is not an existing directory and could not be created as such.") } } // 强制任务依赖顺序 compileAntlrJava.dependsOn(generateGrammarSource) compileJava.dependsOn(compileAntlrJava) // 让父项目的fatJar任务明确依赖生成任务 rootProject.tasks.fatJar.dependsOn(tasks.generateGrammarSource)
⚠️ 注意:这种方式不推荐,生成代码混入手动源码目录会导致版本控制混乱,且需要手动维护依赖链,容易出错。
方案3:调整父项目FatJar的依赖目标
修改父项目的fatJar任务,使其依赖子项目的jar任务而非直接依赖compileJava——jar任务会自动触发所有前置编译任务:
task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'Alchemist', 'Implementation-Version': rootProject.version, 'Main-Class': 'it.unibo.alchemist.Alchemist' } baseName = "${rootProject.name}-redist" // 依赖子项目的jar任务,会自动触发compileJava及所有前置任务 dependsOn subprojects.jar from(subprojects.configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }) { exclude "META-INF/*.SF" exclude "META-INF/*.DSA" exclude "META-INF/*.RSA" exclude 'build' exclude '.gradle' exclude 'build.gradle' exclude 'gradle' exclude 'gradlew' exclude 'gradlew.bat' } with jar }
这种方式可以确保所有子项目的编译前置任务(包括ANTLR生成)都被正确触发,无论子项目使用了什么特殊插件。
内容的提问来源于stack exchange,提问作者Danilo Pianini

