此页面总结了 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 进行注释。

可变类型带有 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。

私有 getter 上的注解

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

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

被忽略属性上的注解

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

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

要解决此问题,您必须:

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

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

冲突的注解

此错误表明一个属性被注解了冲突的注解,也就是说注解具有不同的、不可调和的语义。

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

要解决这个问题,您需要了解不同注解的语义,并只选择其中一个。

注解在特定上下文中无效

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

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

没有注解的属性

此错误表示属性没有使用输入或输出注解进行标注。因此,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

使用依赖项解析结果作为任务输入

java.net.URL

此类型在使用 @Input 注释的属性上不受支持,因为最新检查可能对此类型不一致,从而导致构建结果不正确。这是由 Java 中的一个已知问题引起的,即 java.net.URL 的序列化不正确。有关更多详细信息,请参阅 OpenJDK 错误报告。要解决此问题,我们建议使用 java.net.URI 代替。

嵌套映射不支持的键类型

此错误表明嵌套映射声明了一个不支持类型的键。Gradle 使用键为(子)属性生成名称。仅允许 EnumIntegerString 类型的键可以保证这些名称是唯一的且格式良好的,并且比依赖 toString() 生成此类名称更可取。

要解决此问题,请将键的类型更改为 EnumIntegerString

不支持的嵌套类型

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

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