本页总结了 Gradle 报告的不同任务(或一般工作)验证问题,并提供了解决这些问题的指导。

无效地使用 cacheable 注解

此错误表示您在使用 @CacheableTransform 注解了非制品转换的类型,或在使用 @CacheableTask 注解了非 Task 的类型。

解决方案是移除该注解。

对于任务,应使用 @CacheableTask 注解。对于制品转换,应使用 @CacheableTransform 注解。

缺少 normalization 注解

当任务或制品转换是可缓存的,并且文件或文件集合输入属性未声明应如何进行 normalization 时,会发生此错误。Normalization 告诉 Gradle 输入文件的绝对路径是否重要,或者是否只有内容相关。如果不声明 normalization 策略,则任务的输出无法在不同机器之间或同一机器的不同位置之间重复使用。简而言之,没有 normalization,缓存效率会很低。

要解决此问题,您需要通过应用以下注解之一来声明 normalization 策略

必需的值未设置

此错误表示属性期望一个值但未提供任何值。默认情况下,Gradle 属性是必需的,也就是说,如果输入或输出属性未配置,无论是通过约定值还是通过构建脚本明确指定,Gradle 都将失败,因为它不知道在执行任务时应使用什么值。

要解决此问题,您必须:

  • 明确地为此属性提供一个值(例如,通过在构建脚本中配置任务)

  • 或者通过使用 @Optional 注解将其设为可选属性

制品转换无效地使用绝对路径敏感性

此错误表示您将可缓存制品转换的输入注解为对绝对路径敏感。然而,制品转换是在使用其自己的工作区中隔离执行的,例如,该工作区对 clean 构建具有弹性。即使制品转换结果无法通过构建缓存共享,使用绝对路径敏感性也没有意义。

要解决此问题,您必须使用以下策略之一更改 normalization 策略:

在制品转换上无效地使用 output 属性

此错误表示您在使用 output 注解注解了制品转换的属性,这不是为制品转换注册输出的正确方法。

要解决此问题,您必须移除该属性并改用 TransformOutputs 参数。

有关详细信息,请参阅 TransformAction#transform(TransformOutputs)

无效地在字段上使用注解

此错误表示您有一个字段已添加注解,但此字段没有相应的 getter 方法。Gradle 仅在存在相应 getter 方法时才会识别字段上的注解。如果缺少此 getter 方法,则注解将完全无效。

要修复此问题,您需要为此字段创建一个 getter。我们还建议注解 getter 方法而非字段本身。

如果您正在使用 Groovy,您可能无意中在属性声明中添加了 private 修饰符

@InputFile
RegularFileProperty inputFile // this is a public property

@InputFile
private RegularFileProperty inputFile // this is a field, remove the `private` modifier

方法上无效的注解

此错误表示注解被放置在意外的方法上。通常,像 @InputFiles@OutputDirectory 这样的注解需要放置在“属性”方法上。属性通过拥有 getter 方法来定义。

这会导致 Gradle 忽略这些注解,因此它们通常不会用于 up-to-date 检查或缓存。

要解决此问题,您必须移除注解,为您想要使用的属性创建一个 getter 方法,并改用注解此 getter 方法。

属性或字段上无效的注解

此错误表示原本用于非属性方法的注解被意外地放置在字段或“属性”方法上。只有输入和输出注解,例如 @InputFiles@OutputDirectory,才应放置在字段或“属性”方法上。属性通过拥有 getter 方法来定义。

这会导致 Gradle 忽略这些注解,因此在 Gradle 检查类以查找重要注解时,通常不会识别它们。

要解决此问题,您必须移除注解,或更改方法以符合注解的要求。

带有 setter 的可变类型

此错误表示“可变”类型的属性也提供了 setter 方法。Gradle 中的可变类型包括 PropertyConfigurableFileCollection

例如,您写了

class MyTask extends DefaultTask {
    private Property<Integer> x

    @Input
    Property<Integer> getX() { this.x }

    void setX(Property<Integer> x) { this.x = x }
}

然而,像 Property 这样的可变类型旨在跟踪提供给它们的值的依赖项和来源。因此,将它们设置为可覆盖是错误的,因为这样 Gradle 将无法确定属性值的来源。

要解决此问题,您应该将属性设置为 final 并移除 setter

class MyTask extends DefaultTask {
    @Input
    final Property<Integer> getX() = ...

您也可以依赖 Gradle 内置的能力来注入 final 属性

abstract class MyTask {
    abstract Property<Integer> getX()
}

然后需要通过变异方法配置属性的值

myTask {
    x.set(123)
}

多余的 getter

此错误表示 boolean 类型的属性同时拥有 getis 两种 getter 方法。这是一个问题,因为这两个 getter 可以用不同的方式注解,而 Gradle 无法知道应该使用哪一个。

解决此问题的方法是去掉其中一个 getter,或者使用 @Internal 注解标记其中一个 getter。

private getter 上的注解

此错误表示您使用输入或输出注解注解了 private 的 getter。Gradle 不会将 private getter 视为 up-to-date 检查的输入,这意味着您的注解实际上被忽略了。修复此问题很重要,因为您可能认为自己声明了输入,但实际并非如此。

要修复此问题,您可以将 getter 设为 public,或者改用注解现有的 getter,或者创建一个新的带注解的 getter。

private 方法上的注解

此错误表示您使用 Gradle 期望查询的注解注解了 private 方法。Gradle 将无法调用 private 方法,这意味着您的注解实际上被忽略了。修复此问题很重要,因为您可能认为自己声明了一个带注解的方法,但它无法使用。

要修复此问题,您可以将方法设为 public,或者改用注解另一个新的或现有的 public 方法。

ignored 属性上的注解

此错误表示您有一个属性,它既带有告诉 Gradle 忽略它的注解(例如 @ReplacedBy),又带有输入注解(例如 @InputFile)。

这是个错误,因为 Gradle 无法确定该属性是否应实际用于 up-to-date 检查,也就是说,它是否真的是一个输入。

要修复此问题,您必须:

  • 移除属性上的输入注解,或者

  • 移除属性上的忽略注解。

冲突的注解

此错误表示属性被使用了冲突的注解,也就是说,这些注解具有不同且不可调和的语义。

例如,一个属性不能同时被 @InputFile@OutputFile 注解。

要解决此问题,您需要理解不同注解的语义,并只选择一个。

注解在特定上下文中无效

此错误表示属性被使用了在特定上下文中无效的注解。例如,通常可以在 DirectoryProperty 上使用 @OutputDirectory 注解,但在制品转换的上下文中这是无效的,因为制品转换提供了自己的工作区。

要解决此问题,您必须移除该属性。

没有注解的属性

此错误表示属性没有使用输入或输出注解进行注解。因此,Gradle 不知道该属性是表示输入、输出,还是应简单地忽略。因此,up-to-date 检查和缓存将无法工作。

要解决此问题,您需要使用适当的注解注解该属性,例如,表示输入目录的属性使用 @InputDirectory,表示输出目录的属性使用 @OutputDirectory

或者,如果该属性是内部属性,也就是说它不应参与 up-to-date 检查(它不是输入或输出),则需要使用 @Internal 注解它。

注解与属性类型不兼容

此错误表示对于特定类型的属性,修饰注解没有意义。例如,如果在输出属性上使用 @SkipWhenEmpty 就是这种情况。由于此组合没有关联的语义,Gradle 无法推断您的意图。

要修复此问题,您最可能需要移除冲突的修饰注解,或者检查实际属性类型是否是您想要的。

@Input 注解的使用不正确

此错误表示属性被 @Input 注解,但实际上应该使用 @InputFile@InputDirectory 进行注解。

如果您在基于文件的属性上使用 @Input 注解,Gradle 不会像您期望的那样将文件内容或目录内容视为输入。

要解决此问题,您需要告诉 Gradle 该文件属性是表示输入文件,如果是,则应使用 @InputFile 注解它;如果表示目录,则应使用 @InputDirectory 注解它。如果您真正想表达的是实际文件路径是一个输入,那么您应该返回一个对应于文件绝对路径的 String

@ServiceReference 注解的属性不是 BuildService

此错误表示用 @ServiceReference 注解的属性的类型未实现 BuildService 接口。

@ServiceReference 注解的属性旨在保存对共享构建服务的引用。

任务之间的隐式依赖项

此错误表示您有一个任务依赖于另一个任务,但在两个任务之间没有声明显式或隐式依赖关系。因此,构建结果取决于任务的执行顺序,这通常被称为“任务之间的意外依赖”。通常,这是因为您直接引用另一个任务的输出文件,而不是直接将该任务用作输入。

例如,假设您有一个任务接受 ConfigurableFileCollection 作为输入,并且您使用以下方式声明了对 jar 任务的依赖:

someTask {
    inputFile.from(jar.archivePath)
}

jar.archivePath 属性是 File 类型,它不携带任何任务依赖关系。这意味着如果您在调用 jar 之后调用 someTask,任务会成功;但如果 jar 文件被删除,例如,任务就会失败。

要修复此问题,您可以改用声明 Property 作为输入:

someTask {
    inputFile.from(jar.archiveFile)
}

jar.archiveFile 属性是 Provider<RegularFile> 类型,它正确地携带了任务依赖关系:Gradle 将能够知道该文件是由 jar 任务生成的。

实际上,将隐式依赖关系添加到任务本身甚至更容易:

someTask {
    inputFile.from(jar)
}

在某些情况下,对于不使用配置避免 API 的生产者任务,您可以转而声明对该任务的*显式依赖*:

someTask {
    dependsOn(producer)
    inputFile.from(producer.someFile)
}

在某些情况下,不希望添加对生产者任务的依赖,例如当消费者为可能多个任务生成报告时。在这种情况下,您可以通过使用 Task.mustRunAfter() 在两个任务之间引入顺序

输入文件不存在

当文件(或目录)被声明为任务的输入,但在任务执行时,该文件(或目录)不存在时,会发生此错误。

通常,这暗示着缺少任务依赖:文件应该在任务执行之前存在,这意味着依赖任务没有被执行。

症状类似于任务之间的隐式依赖项,不同之处在于本例中创建文件的任务尚未执行。

请参阅任务之间的隐式依赖项部分以获取可能的解决方案。如果文件不是由另一个任务产生的,您可能需要确保在调用任务之前它已存在。如果您想声明的是文件在任务执行时是否存在无关紧要,可以使用 @InputFiles,它不会因为不存在的输入而失败。

build.gradle.kts
abstract class GreetingFileTask : DefaultTask() {

    @get:InputFiles
    abstract val source: RegularFileProperty

    @get:OutputFile
    abstract val destination: RegularFileProperty

    @TaskAction
    fun greet() {
        val file = destination.get().asFile
        if (source.get().asFile.exists()) {
            file.writeText("Hello ${source.get().asFile.readText()}")
        } else {
            file.writeText("Hello missing file!")
        }
    }
}
build.gradle
abstract class GreetingFileTask extends DefaultTask {

    @InputFiles
    abstract RegularFileProperty getSource()

    @OutputFile
    abstract RegularFileProperty getDestination()

    @TaskAction
    def greet() {
        def file = getDestination().get().asFile
        if (source.get().asFile.exists()) {
            file.write("Hello ${source.get().asFile.text}!")
        } else {
            file.write 'Hello missing file!'
        }
    }
}

意外的输入文件或目录

此错误表示属性期望将普通文件作为输入,但提供的是目录(反之亦然)。

例如,如果属性使用 @InputFile 注解:

@InputFile
File getInputFile()

那么 Gradle 期望输入文件是一个普通文件。如果输入是一个目录,则验证失败。

要解决此问题,您有两种选择:

  • 要么您犯了错误,提供的是目录而不是文件,在这种情况下您只需修正输入

  • 或者任务实际上应该使用目录作为输入,在这种情况下您需要将属性的类型更改为 @InputDirectory

无法写入输出文件或目录

此错误表示:

  • 无法写入输出目录,因为已配置的目录属性实际上引用的是一个普通文件(或不是实际目录的其他内容)。

  • 或者无法写入输出文件,因为已配置的文件属性实际上引用的是一个目录。

  • 或者输出位置的父目录存在且是一个文件。

例如,您将输出目录设置为 /some/path/file.txt 而不是 /some/path。也可能是您将输出目录配置为 /some/path,但其祖先目录 /some 是一个普通文件。

要解决此问题,请确保配置的输出是目录(对于期望目录的属性)或文件(对于期望文件的任务)。

无法写入保留位置

此错误表示您正在尝试将文件写入仅由 Gradle 管理的位置。通常,当您尝试将文件直接写入制品转换的输出目录时,会发生这种情况。如果您是故意这样做的,这是一个错误,因为这些目录绝不应直接写入:所有制品转换写入都应在制品转换代码本身内部执行。

如果您无意写入此目录,只需设置您的任务写入不同的位置即可。

文件输入中不受支持的表示法

此错误表示文件、目录、文件集合或嵌套文件集合属性引用了 Gradle 无法转换为文件的元素。

要修复此问题,请查看错误消息中列出的支持的文件表示法列表,并确保选择其中一种。

在基本类型上无效地使用 @Optional 注解

此错误表示基本类型的属性同时也被 @Optional 注解。这类似于 Java 中 null 不能赋值给基本类型。

要解决此问题,您有两种选择:

  • 移除 @Optional 注解

  • 或者如果您想使属性可为空,则使用包装类型(例如 Integer 而不是 int)

无法使用实现未知的输入

此错误表示任务将类用作输入,但 Gradle 无法跟踪该类的实现。Gradle 将以下类的实现视为任务的输入:

  • 任务类,

  • 任务操作的类,

  • 以及任务嵌套输入的类,即用 @Nested 注解的输入。

Gradle 无法跟踪类实现的原因有两个:

  • 使用了不可序列化的 Java lambda 来实现该类,

  • 或者该类是由 Gradle 未知的类加载器加载的。

使用 Java lambda 意味着字节码使用 invoke-dynamic 而不是创建实际的子类。invoke-dynamic 指令的类是在 JVM 运行时生成的,如果目标函数式接口不可序列化,Gradle 无法在不同的 JVM 之间唯一标识该类。对于任务操作,这不是问题,因为 Gradle 会以特殊方式处理它们。另一方面,对于由不可序列化 lambda 实现的嵌套输入类,Gradle 不支持跟踪。作为一种变通方法,您可以将 lambda 转换为匿名内部类或使目标函数式接口可序列化。

如果 Gradle 无法跟踪实现是因为它是由 Gradle 未知的类加载器加载的,您可以使用 Gradle 内置的方式加载该类。

缺少不缓存的原因

此警告表明任务或制品转换操作未标记为可缓存,尽管也没有说明为何不可缓存。任务或制品转换的作者应始终使用 @DisableCachingByDefault 注解提供不可缓存的原因。

要解决此问题,请使用 @CacheableTask/@CacheableTransform@DisableCachingByDefault(because = "…​") 注解工作类型。

不支持的值类型

此消息表示任务声明了一个输入属性,其值类型不受支持。

要解决此问题,请查看消息中指示的可能解决方案列表。

下面列出了不受支持的值类型:

ResolvedArtifactResult

将 ResolvedArtifactResult 映射为任务输入

java.net.URL

用 @Input 注解的属性不支持此类型,因为此类可能会导致 up-to-date 检查不一致,从而产生不正确的构建结果。这是由 Java 中一个已知问题引起的,即 java.net.URL 的序列化不正确。有关详细信息,请参阅 OpenJDK bug 报告。为了解决此问题,我们建议改用 java.net.URI

嵌套 map 中不受支持的键类型

此错误表示嵌套 map 声明了一个不受支持类型的键。Gradle 使用键为(子)属性生成名称。只允许特定类型的键才能保证这些名称是唯一的且格式正确。这比依赖 toString() 来生成此类名称更可取。

要解决此问题,请将键的类型更改为 IntegerString 或枚举。

不支持的嵌套类型

此错误表示不受支持的类型被注解为嵌套类型。嵌套类型应声明一些带注解的属性(这些属性本身也会检查注解)或一些条件行为,其中将类型本身作为输入捕获很重要。不支持 Java SE API 类型、Kotlin stdlib 类型和 Groovy 的 GString,因为它们都不满足这些要求。

要解决此问题,请声明嵌套类型,例如 Provider<T>Iterable<T>MapProperty<K, V>,其中 TV 具有一些带注解的属性或需要将类型作为输入捕获的某些行为。