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

无效使用可缓存注解

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

解决方案是移除该注解。

对于任务,应使用的注解是 @CacheableTask。对于工件转换,应使用的注解是 @CacheableTransform

缺少标准化注解

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

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

所需值未设置

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

要解决此问题,您必须:

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

  • 或通过使用 @Optional 注解使属性变为可选

工件转换中绝对路径敏感度的无效使用

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

要解决此问题,您必须使用以下方法之一更改标准化策略:

工件转换中输出属性的无效使用

此错误表明您已使用输出注解标注了工件转换的属性,这不是注册工件转换输出的正确方法。

要解决此问题,您必须删除该属性,并改用 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 忽略这些注解,因此它们通常不会用于最新检查或缓存。

要解决此问题,您必须删除该注解,为您要使用的属性创建一个 getter 方法,并转而注解该 getter 方法。

属性或字段上的无效注解

此错误表示一个 intended for a non-property method 的注解被意外地放置在字段或属性方法上。只有像 @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 方法,或将其中一个 Getter 方法标记为 @Internal

私有 Getter 上的注解

此错误表示您已使用输入或输出注解标注了 private Getter。Gradle 不会将私有 Getter 视为最新检查的输入,这意味着您的注解实际上被忽略了。修复这一点很重要,因为您可能会认为自己已声明了一个输入,但事实并非如此。

要解决此问题,请将 Getter 设为公共,或者注解现有的 Getter,或者创建一个新的带注解的 Getter。

私有方法上的注解

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

要解决此问题,请将方法设为公共,或者注解另一个新的或现有的公共方法。

被忽略属性上的注解

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

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

要解决此问题,您必须:

  • 从属性中删除输入注解,或

  • 从属性中删除忽略注解。

冲突的注解

此错误表示属性被冲突的注解标注,即具有不同、不可调和语义的注解。

例如,属性不能同时被 @InputFile@OutputFile 标注。

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

注解在特定上下文中无效

此错误表示属性被一个在特定上下文中无效的注解所标注。例如,通常可以将 DirectoryProperty 标注为 @OutputDirectory,但在工件转换的上下文中这是无效的,因为工件转换提供了自己的工作空间。

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

没有注解的属性

此错误表示属性没有用输入或输出注解。因此,Gradle 不知道此属性是表示输入、输出,还是应被忽略。结果,最新检查和缓存将不起作用。

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

或者,如果该属性是内部的,也就是说它不应参与最新检查(它既不是输入也不是输出),那么您需要使用 @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 注解的属性上不受支持,因为对于此类型,最新检查可能会不一致,导致构建结果不正确。这是由于 Java 中的一个已知问题引起的,其中 java.net.URL 的序列化不正确。有关更多详细信息,请参阅 OpenJDK 错误报告。为了解决此问题,我们建议改用 java.net.URI

不支持的嵌套 Map 的键类型

此错误表示嵌套映射声明了一个不支持类型的键。Gradle 使用键为(子)属性生成名称。仅允许某些类型的键可确保这些名称是唯一的且格式良好。这优于依赖 toString() 来生成此类名称。

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

不支持的嵌套类型

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

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