gradle

Gradle —— Task 的增量构建

2019-12-01  本文已影响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 任务 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

上一篇 下一篇

猜你喜欢

热点阅读