英文:
How to link the task execution in my Gradle plugin to the output file of another plugin?
问题
我正在尝试为Gradle编写一个插件,该插件将对执行另一个任务(在另一个插件中定义 - java-library-distribution
)的结果文件执行一些操作。我可以通过在我的任务类中使用this.getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile()
轻松获取所需的文件。
但是通过这种方法,我的任务将每次都会被执行,而不管高层任务中的文件是否已更改。
我已将获取方法注解为@InputFile
。但不幸的是,Gradle仍然不会将问题标记为UP-TO-DATE
。
public class YcfPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().create("ycf", YcfPluginExtension.class);
project.getPluginManager().apply("java-library-distribution");
project.getTasks().register("ycfCreateVersion", YcfTaskCreateVersion.class);
}
}
abstract class YcfTask extends DefaultTask {
public static final String TASK_GROUP = "Some Description";
YcfPluginExtension ycfExtension = this.getProject().getExtensions().getByType(YcfPluginExtension.class);
Logger logger = this.getProject().getLogger();
public YcfTask() {
this.setGroup(TASK_GROUP);
}
}
public class YcfTaskCreateVersion extends YcfTask {
private File fi;
public YcfTaskCreateVersion() {
this.setDescription("Some description");
this.dependsOn(this.getProject().getTasks().getByName("distZip"));
}
@InputFile
public File getFi() {
return this.getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile();
}
@TaskAction
public void run() {
byte[] bytes = Files.readAllBytes(getFi().toPath());
//..对文件内容进行处理
}
}
英文:
I'm trying to write a plugin for Gradle that would perform some actions with the file result of performing another task (defined in another plugin - java-library-distribution
). I can easily get the file I need via this.getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile()
in my task class.
But with this approach, my task will be executed every time, regardless of whether the file has changed in the higher-level task.
I annotated the getter method as an @InputFile
. But unfortunately Gradle still doesn't mark the issue as UP-TO-DATE
.
public class YcfPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().create("ycf", YcfPluginExtension.class);
project.getPluginManager().apply("java-library-distribution");
project.getTasks().register("ycfCreateVersion", YcfTaskCreateVersion.class);
}
}
abstract class YcfTask extends DefaultTask {
public static final String TASK_GROUP = "Some Description";
YcfPluginExtension ycfExtension = this.getProject().getExtensions().getByType(YcfPluginExtension.class);
Logger logger = this.getProject().getLogger();
public YcfTask() {
this.setGroup(TASK_GROUP);
}
}
public class YcfTaskCreateVersion extends YcfTask {
private File fi;
public YcfTaskCreateVersion() {
this.setDescription("Some description");
this.dependsOn(this.getProject().getTasks().getByName("distZip"));
}
@InputFile
public File getFi() {
return this.getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile();
}
@TaskAction
public void run() {
byte[] bytes = Files.readAllBytes(getFi().toPath());
//..do something with file content
}
}
答案1
得分: 1
在你目前的方法中存在一些误解:
-
@InputFile
注解应该用在一个 getter 方法上。getter 方法不仅仅是以前缀get
开头的方法。通常它是一个返回(私有)字段的值的方法。在你的示例中有一个叫做fi
的字段,所以相应的 getter 方法getFi
应该只是返回这个字段的值。顺便说一下,在你目前的代码中,字段fi
并没有被使用。 -
你知道应该使用哪个值作为任务的输入(
getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile()
),这是很好的,但这不应该是任务类型实现的一部分。相反,任务类型应该尽可能地可配置化。然后你可以在插件代码中创建和配置这个任务类型的一个实例:public class YcfTaskCreateVersion extends YcfTask { private File fi; @InputFile public File getFi() { return fi; } public void setFi(File fi) { this.fi = fi; } @TaskAction public void run() { // ... } } public class YcfPlugin implements Plugin<Project> { @Override public void apply(Project project) { // ... YcfTaskCreateVersion createVersionTask = project.getTasks() .register("ycfCreateVersion", YcfTaskCreateVersion.class); createVersionTask.setFi(project.getTasks().getByName("distZip") .getOutputs().getFiles().getSingleFile()); } }
-
不幸的是,上面的代码不会起作用,因为在创建任务时(应用插件时),任务
distZip
的输出(甚至可能是任务本身)在这个时候不会可用。但这并不是一个大问题,因为 Gradle 支持这种用例。你可以将字段的类型更改为Object
,这样不仅可以传递File
类型的对象。是否传递了文件(或者可以转换为文件的内容)将在任务执行时进行检查。public class YcfTaskCreateVersion extends YcfTask { private Object fi; @InputFile public Object getFi() { return fi; } public void setFi(Object fi) { this.fi = fi; } @TaskAction public void run() { File file = getProject().files(fi).getSingleFile(); // 对文件进行操作 } }
这种设置有一个很酷的地方:你可以直接传递任务
distZip
,Gradle 将自动提取文件输出。它甚至会检测到你正在使用一个任务的输出作为另一个任务的输入,因此 Gradle 将自动在这两个任务之间设置任务依赖关系,你就不需要再使用dependsOn
了:createVersionTask.setFi(project.getTasks().getByName("distZip"));
-
让我们查看一下Gradle 关于任务结果的文档。其中有一个关于
UP-TO-DATE
的部分:> 任务的输出没有改变。
> - 任务具有输出和输入,它们没有改变。请参阅增量构建。
> - 任务具有操作,但任务告诉 Gradle 它没有改变其输出。
> - 任务没有操作,具有一些依赖项,但所有依赖项都是最新的、已跳过的或来自缓存。另请参阅生命周期任务。
> - 任务没有操作,也没有依赖项。正如你所看到的,你的任务需要定义输出以被视为最新的。你可以以与输入文件相同的方式定义输出文件(字段、带有
@OutputFile
的 getter 和 setter)。它们应该是可配置的,并默认为build
目录中的文件。或者你可以使用onlyIf
来实现自定义检查任务是否应该运行。如果onlyIf
内的条件返回false
,任务将被标记为SKIPPED
。
英文:
There are some misconceptions in your current approach:
-
The
@InputFile
annotation should be used on a getter method. A getter method is not just a method that starts with the prefixget
. Usually it is a method that returns the value of a (private) field. In your example there is a field calledfi
, so the respective getter methodgetFi
should just return the value of this field. By the way, in your current code, the fieldfi
is not used at all. -
It is good that you know what value should be used as an input of your task (
getProject().getTasks().getByName("distZip").getOutputs().getFiles().getSingleFile()
), but this should not be a part of the task type implementation. Instead, task types should be as configurable as possible. You may then create and configure one instance of this task type in your plugin code:public class YcfTaskCreateVersion extends YcfTask { private File fi; @InputFile public File getFi() { return fi; } public void setFi(File fi) { this.fi = fi; } @TaskAction public void run() { // ... } } public class YcfPlugin implements Plugin<Project> { @Override public void apply(Project project) { // ... YcfTaskCreateVersion createVersionTask = project.getTasks() .register("ycfCreateVersion", YcfTaskCreateVersion.class); createVersionTask.setFi(project.getTasks().getByName("distZip") .getOutputs().getFiles().getSingleFile()); } }
-
Sadly, the code above won't work, because at the time the task is created (when the plugin is applied), the outputs of the task
distZip
(and maybe even the task) won't be available. But that is not a big problem, because Gradle supports this use case. You can change the type of your field toObject
, so that not only objects of typeFile
may be passed in. Whether a file (or something that may be converted to a file) was passed, will be checked when the task executes.public class YcfTaskCreateVersion extends YcfTask { private Object fi; @InputFile public Object getFi() { return fi; } public void setFi(Object fi) { this.fi = fi; } @TaskAction public void run() { File file = getProject().files(fi).getSingleFile(); // do something with file } }
There is a cool thing about this setup: You may pass the task
distZip
directly and Gradle will automatically extract the file outputs. It will even detect that you are using the outputs of a task as inputs of another task, so Gradle will automatically setup a task dependency between the two tasks and you don't have to usedependsOn
anymore:createVersionTask.setFi(project.getTasks().getByName("distZip"));
-
Lets check the Gradle documentation on task outcomes. There is a section on
UP-TO-DATE
:> Task’s outputs did not change.
> - Task has outputs and inputs and they have not changed. See Incremental Builds.
> - Task has actions, but the task tells Gradle it did not change its outputs.
> - Task has no actions and some dependencies, but all of the dependencies are up-to-date, skipped or from cache. See also Lifecycle Tasks.
> - Task has no actions and no dependencies.As you can see, your task needs to define outputs to be considered up-to-date. You can define output files in the same way as input files (field, getter with
@OutputFile
and setter). They should be configurable and default to a file inside thebuild
directory. Alternatively you may useonlyIf
to implement a custom check whether the task should be run. If the predicate insideonlyIf
returnsfalse
, the task will beSKIPPED
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论