美文网首页gradle
Gradle —— Task 的增量构建

Gradle —— Task 的增量构建

作者: 你可记得叫安可 | 来源:发表于2019-12-01 14:33 被阅读0次

    我们在运行构建时,一定已经看到过任务旁边的 UP-TO-DATE 的提示,这就是增量构建的效果。
    我们可以把一个 Task 的功能看做是处理输入素材的逻辑,它的产物是输出。如果一个 Task 的输入没有变,它的输出也一定不会变,而且这个 Task 之前应执行过一次,那么我们是不是只用检查输入就可以决定下一次是否需要再执行一遍这个 Task

    一个例子

    task combineFileContentNonIncremental {
       def sources = fileTree('srcFile')
       def destination = file('destination.txt')
       doLast {
          destination.withPrintWriter { writer ->
             sources.each {source ->
                writer.println source.text
             }
          }
       }
    }
    

    上面代码逻辑是,将所有 srcFile 目录下的文件的内容都拼接写到 destination.txt 文件中。这个 Task 没有使用增量构建,每次执行都会再执行一次逻辑。


    下面我们将它改为支持增量构建:

    task combineFileContentIncremental {
       def sources = fileTree('srcFile')
       def destination = file('destination.txt')
    
       inputs.dir sources // 支持增量构建
       outputs.file destination // 支持增量构建
    
       doLast {
          destination.withPrintWriter { writer ->
             sources.each {source ->
                writer.println source.text
             }
          }
       }
    }
    

    根据文章开头增量构建的原理,要使 Gradle 支持增量构建,就需要开发者告诉 Gradle 自定义任务的输入和输出。如上面的代码,我们通过给 Taskinputs.diroutputs.file 来告诉 Gradle 任务 combineFileContentIncremental 的输入是目录 srcFile,输出是文件 destination.txt

    自定义 Task 的增量构建

    简单版

    我们定义如下一个生成文件的 Generate Task

    public class Generate extends DefaultTask {
        private int fileCount;
        private String content;
        private File generatedFileDir;
    
        @Input
        public int getFileCount() {
            return fileCount;
        }
    
        public void setFileCount(int fileCount) {
            this.fileCount = fileCount;
        }
    
        @Input
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        @OutputDirectory
        public File getGeneratedFileDir() {
            return generatedFileDir;
        }
    
        public void setGeneratedFileDir(File generatedFileDir) {
            this.generatedFileDir = generatedFileDir;
        }
    
        @TaskAction
        public void perform() throws IOException {
            for (int i = 1; i <= fileCount; i++) {
                writeFile(new File(generatedFileDir, i + ".txt"), content);
            }
        }
    
        private void writeFile(File destination, String content) throws IOException {
            BufferedWriter output = null;
            try {
                output = new BufferedWriter(new FileWriter(destination));
                output.write(content);
            } finally {
                if (output != null) {
                    output.close();
                }
            }
        }
    }
    

    注意,我们使用了 @Input@OutputDirectory 这两个注解,来告诉 Gradle 这个 Task 的输入和输出,这样 Gradle 第二次执行 Generate 时,就可以判断是否可以不用再执行 Generate,而是利用上一次执行的产物。Gradle 以此来支持增量构建。

    复杂版

    我们可以注意到上面的 Generate Task 中有两个 @Input,因此我们在使用时需要这样写:

    tasks.register("generate", Generate) {
        fileCount = 2
        content = 'Hello World!'
        generatedFileDir = file("$buildDir/generated-output")
    }
    

    注意到 fileCountcontent 都是输入的配置信息,如果我们有更多的输入配置信息,那么我们可能会考虑将这些配置信息分类。这就涉及到 @Nested 注解。
    新写一个类 ConfigData 来包装 Generate 中的两个 @Input 属性

    public class ConfigData {
        private int fileCount;
        private String content;
    
        public int getFileCount() {
            return fileCount;
        }
    
        public void setFileCount(int fileCount) {
            this.fileCount = fileCount;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    }
    

    修改 Generate 类,将之前的 fileCountcontent 的相关使用都删除,添加使用 ConfigData

    public class Generate extends DefaultTask {
        private File generatedFileDir;
        // 由于下面的 configData 方法要使用,这里需要先构造出来
        private ConfigData configData = new ConfigData();
    
        // 添加方法接受一个 action,这里就是开发者使用 configData 使最终调用的地方
        public void configData(Action<? super ConfigData> action) {
            action.execute(configData);
        }
    
        @OutputDirectory
        public File getGeneratedFileDir() {
            return generatedFileDir;
        }
    
        public void setGeneratedFileDir(File generatedFileDir) {
            this.generatedFileDir = generatedFileDir;
        }
    
        @TaskAction
        public void perform() throws IOException {
            for (int i = 1; i <= configData.getFileCount(); i++) {
                writeFile(new File(generatedFileDir, i + ".txt"), configData.getContent());
            }
        }
    
        private void writeFile(File destination, String content) throws IOException {
            BufferedWriter output = null;
            try {
                output = new BufferedWriter(new FileWriter(destination));
                output.write(content);
            } finally {
                if (output != null) {
                    output.close();
                }
            }
        }
    }
    

    经过上面的修改,我们在使用时就可以如下使用:

    tasks.register("generate", Generate) {
        configData {
            fileCount = 2
            content = 'Hello World!'
        }
    //    fileCount = 2
    //    content = 'Hello World!'
        generatedFileDir = file("$buildDir/generated-output")
    }
    

    但是这里有个问题,当我们修改 fileCount = 3 后,再执行,发现是 up-to-date。这是因为上面代码我们没有告诉 Gradle 输入是什么(但是却通过 @OutputDirectory 告诉了输出是什么),这时 Gradle 在每次构建时都会认为是 up-to-date

    • 只告诉 Gradle 输入/输出中的一端,那么每次构建都是 up-to-date
    • 输入/输出都没告诉,那么每次构建都会被 Gradle 重新执行
    • 输入/输出都告诉,那么 Gradle 会执行增量构建

    支持增量构建的复杂版

    我们要使其能够增量构建,只需告诉 Gradle 任务 generate 的输入即可。

    1. ConfigDatafileCountcontentgetter 方法都加上 @Input
    public class ConfigData {
        private int fileCount;
        private String content;
    
        @Input
        public int getFileCount() {
            return fileCount;
        }
    
        public void setFileCount(int fileCount) {
            this.fileCount = fileCount;
        }
    
        @Input
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    }
    
    1. Generate 中使用 @Nested 注解,来告诉 Gradle 任务的输入。添加如下语句即可
    // 这里使用 Nested 
    @Nested
    public ConfigData getConfigData() {
        return configData;
    }
    

    更多内容可参考:https://docs.gradle.org/current/userguide/more_about_tasks.html

    相关文章

      网友评论

        本文标题:Gradle —— Task 的增量构建

        本文链接:https://www.haomeiwen.com/subject/szdzwctx.html