如何将我Gradle插件中的任务执行与另一个插件的输出文件关联?

huangapple go评论70阅读模式
英文:

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(&quot;distZip&quot;).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&lt;Project&gt; {
    @Override
    public void apply(Project project) {
        project.getExtensions().create(&quot;ycf&quot;, YcfPluginExtension.class);
        project.getPluginManager().apply(&quot;java-library-distribution&quot;);

        project.getTasks().register(&quot;ycfCreateVersion&quot;, YcfTaskCreateVersion.class);
    }
}
abstract class YcfTask extends DefaultTask {
    public static final String TASK_GROUP = &quot;Some Description&quot;;
    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(&quot;Some description&quot;);
        this.dependsOn(this.getProject().getTasks().getByName(&quot;distZip&quot;));

    }

    @InputFile
    public File getFi() {
        return this.getProject().getTasks().getByName(&quot;distZip&quot;).getOutputs().getFiles().getSingleFile();
    }
    @TaskAction
    public void run() {
        byte[] bytes = Files.readAllBytes(getFi().toPath());
        //..do something with file content
    }
}

答案1

得分: 1

在你目前的方法中存在一些误解:

  1. @InputFile 注解应该用在一个 getter 方法上。getter 方法不仅仅是以前缀 get 开头的方法。通常它是一个返回(私有)字段的值的方法。在你的示例中有一个叫做 fi 的字段,所以相应的 getter 方法 getFi 应该只是返回这个字段的值。顺便说一下,在你目前的代码中,字段 fi 并没有被使用。

  2. 你知道应该使用哪个值作为任务的输入(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());
        }
    }
    
  3. 不幸的是,上面的代码不会起作用,因为在创建任务时(应用插件时),任务 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"));
    
  4. 让我们查看一下Gradle 关于任务结果的文档。其中有一个关于 UP-TO-DATE 的部分:

    > 任务的输出没有改变。
    > - 任务具有输出和输入,它们没有改变。请参阅增量构建
    > - 任务具有操作,但任务告诉 Gradle 它没有改变其输出。
    > - 任务没有操作,具有一些依赖项,但所有依赖项都是最新的、已跳过的或来自缓存。另请参阅生命周期任务
    > - 任务没有操作,也没有依赖项。

    正如你所看到的,你的任务需要定义输出以被视为最新的。你可以以与输入文件相同的方式定义输出文件(字段、带有 @OutputFile 的 getter 和 setter)。它们应该是可配置的,并默认为 build 目录中的文件。或者你可以使用 onlyIf 来实现自定义检查任务是否应该运行。如果 onlyIf 内的条件返回 false,任务将被标记为 SKIPPED

英文:

There are some misconceptions in your current approach:

  1. The @InputFile annotation should be used on a getter method. A getter method is not just a method that starts with the prefix get. Usually it is a method that returns the value of a (private) field. In your example there is a field called fi, so the respective getter method getFi should just return the value of this field. By the way, in your current code, the field fi is not used at all.

  2. It is good that you know what value should be used as an input of your task (getProject().getTasks().getByName(&quot;distZip&quot;).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&lt;Project&gt; {
        @Override
        public void apply(Project project) {
            // ...
    
            YcfTaskCreateVersion createVersionTask = project.getTasks()
                .register(&quot;ycfCreateVersion&quot;, YcfTaskCreateVersion.class);
            createVersionTask.setFi(project.getTasks().getByName(&quot;distZip&quot;)
                .getOutputs().getFiles().getSingleFile());
        }
    }
    
  3. 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 to Object, so that not only objects of type File 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 use dependsOn anymore:

    createVersionTask.setFi(project.getTasks().getByName(&quot;distZip&quot;));
    
  4. 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 the build directory. Alternatively you may use onlyIf to implement a custom check whether the task should be run. If the predicate inside onlyIf returns false, the task will be SKIPPED.

huangapple
  • 本文由 发表于 2020年10月8日 16:33:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/64258735.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定