处理验证问题
- 无效使用可缓存注解
- 缺少标准化注解
- 所需值未设置
- 工件转换中绝对路径敏感度的无效使用
- 工件转换中输出属性的无效使用
- 字段上注解的无效使用
- 方法上的无效注解
- 属性或字段上的无效注解
- 带 Setter 的可变类型
- 冗余的 Getter
- 私有 Getter 上的注解
- 私有方法上的注解
- 被忽略属性上的注解
- 冲突的注解
- 注解在特定上下文中无效
- 没有注解的属性
- 注解与属性类型不兼容
@Input
注解使用不正确- 使用
@ServiceReference
注解的属性不是 BuildService - 任务之间的隐式依赖
- 输入文件不存在
- 意外的输入文件或目录
- 无法写入输出文件或目录
- 无法写入保留位置
- 文件输入中不支持的表示法
- 在原始类型上无效使用 @Optional 注解
- 无法使用未知实现的输入
- 缺少不缓存的原因
- 不支持的值类型
- 不支持的嵌套 Map 的键类型
- 不支持的嵌套类型
本页总结了 Gradle 报告的不同任务(或一般性工作)验证问题,并提供了修复指导。
无效使用可缓存注解
此错误表示您在非工件转换的类型上使用了 @CacheableTransform
注解,或者在非 Task
的类型上使用了 @CacheableTask
注解。
解决方案是移除该注解。
对于任务,应使用的注解是 @CacheableTask
。对于工件转换,应使用的注解是 @CacheableTransform
。
缺少标准化注解
当任务或工件转换是可缓存的,并且文件或文件集合输入属性未声明其应如何标准化时,会发生此错误。标准化告诉 Gradle,例如,输入文件的绝对路径是否重要,或者是否只有内容相关。如果您不声明标准化策略,则任务的输出无法在不同机器之间或同一机器上不同位置之间重用。简而言之,没有标准化,缓存效率极低。
要解决此问题,您需要通过应用以下注解之一来声明标准化策略:
所需值未设置
此错误表示属性需要一个值但未提供。默认情况下,Gradle 属性是必需的,也就是说,如果输入或输出属性未通过常规值或通过构建脚本明确配置,则 Gradle 将会失败,因为它不知道在任务执行时使用什么值。
要解决此问题,您必须:
-
显式地为该属性提供一个值(例如,通过在构建脚本中配置任务)
-
或通过使用
@Optional
注解使属性变为可选
工件转换中绝对路径敏感度的无效使用
此错误表示您已将可缓存工件转换的输入标记为对绝对路径敏感。然而,工件转换是在其自己的工作区中独立执行的,该工作区例如对 clean
构建具有弹性。即使工件转换结果无法通过构建缓存共享,使用绝对路径敏感度也没有意义。
要解决此问题,您必须使用以下方法之一更改标准化策略:
-
PathSensitive
(但不是绝对路径)
工件转换中输出属性的无效使用
此错误表明您已使用输出注解标注了工件转换的属性,这不是注册工件转换输出的正确方法。
要解决此问题,您必须删除该属性,并改用 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 中的可变类型包括 Property 或 ConfigurableFileCollection。
例如,您写了:
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
属性同时具有 get
和 is
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
,它不会因不存在的输入而失败:
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!")
}
}
}
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
。
不支持的嵌套 Map 的键类型
此错误表示嵌套映射声明了一个不支持类型的键。Gradle 使用键为(子)属性生成名称。仅允许某些类型的键可确保这些名称是唯一的且格式良好。这优于依赖 toString()
来生成此类名称。
要解决此问题,请将键的类型更改为 Integer
或 String
或枚举。
不支持的嵌套类型
此错误表示不支持的类型被注解为嵌套类型。嵌套类型应声明一些带注解的属性(这些属性本身会检查注解)或一些条件行为,其中捕获类型本身作为输入很重要。Java SE API 的类型、Kotlin stdlib 的类型以及 Groovy 的 GString 不受支持,因为它们不符合这些要求。
要解决此问题,请声明一个嵌套类型,例如 Provider<T>
、Iterable<T>
或 MapProperty<K, V>
,其中 T
和 V
具有一些带注解的属性或需要将类型作为输入捕获的一些行为。