DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
晚安云;香港 CN2 ¥15/月
雨云;给项目一台云服务器

Gradle 9 报 SourceUnit 为 null:检查闭包里的局部变量重名

Groovy 构建脚本在闭包内重复声明外层局部变量,Gradle 9 可能以 SourceUnit 空引用遮住真正的作用域错误。给独立临时值改名并保留两个变量的原含义。

Gradle 9 编译 build.gradle 时,在 semantic analysis 阶段报 SourceUnit.getErrorCollector() 的 source 为 null,日志又没有指出具体脚本行。若此前刚在任务闭包内增加一个局部变量,先找它是否与外层脚本变量重名。

这类输入在 Gradle 8.14.3 会显示正常的“当前作用域已包含该变量”错误;Gradle 9.0.0 和 9.9.0-milestone-2 则出现难读的内部异常。前后都不是有效脚本通过编译:变化在于错误报告遮住了原来的作用域冲突。

从闭包内的重复声明找起

下面是会触发冲突的 Groovy DSL 结构:

def channel = 'stable'
tasks.register('t') {
    doLast {
        def channel = 'unknown'
        println channel
    }
}

内层 def channel 再次声明了外层已经存在的局部变量。Groovy 的作用域检查会沿父作用域查找同名声明,并报告冲突;这里不能按某些其他语言允许的局部遮蔽规则理解。

若两处数据本来就分别代表发布渠道和任务内的临时值,可给临时值使用独立名称,并同步修改它的使用位置:

def channel = 'stable'
tasks.register('t') {
    doLast {
        def taskChannel = 'unknown'
        println taskChannel
    }
}

改名保留了两个变量各自的含义。直接删掉内层的 def 可能变成给外层变量赋值;改成项目扩展属性也会改变可见范围和生命周期,不能只为消除编译错误随意替换。

空引用来自错误报告路径

Gradle 9.8.2 正式源码使用自定义 GradleResolveVisitor,其 startResolving 只设置自身保存的 SourceUnit,没有调用父类的同名初始化方法。该发行包声明使用 Groovy 4.0.33;父类处理类的作用域时使用自己保存的 source 创建 VariableScopeVisitor,遇到重复声明需要记录错误时,空 source 会让正常诊断变成内部异常。

因此,日志里的 null 不一定意味着业务脚本正在对空对象调用方法,也不能从中推导出 Gradle 缓存损坏。这里对应的是脚本编译期的声明冲突;如果原输入没有重复局部声明,还需按完整堆栈检查其他语义错误,不能把所有 semantic analysis 异常都归到这个原因。

修改后从项目原有 Wrapper 入口运行原任务,确认构建脚本完成编译,任务内使用的是预期临时值,外层渠道配置没有被改写。换到能显示准确诊断的构建版本,也仍需修正无效声明;清空缓存或更换 JDK 不会让两个冲突声明自动获得不同含义。