gradle实战

Gradle Task UP-TO-DATE

2018-08-24  本文已影响449人  轻微

dim.red
环境:Gradle 4.4.1

相关

Task 输入输出注解
@Input,@InputFile,@InputDirectory,@InputFiles, @OutputFile,@OutputFiles,@OutputDirectory,@OutputDirectories,@Destroys,@LocalState,@Nested,@Inject,@OptionValues
@PathSensitive
@Classpath
@CompileClasspath

0x00

Gradle 为了加快构建速度, 加入了快照缓存的概念。
当你的 Task 输出不需要变更。Gradle 会跳转执行过程,同时 Task 在输出打上 UP-TO-DATE 标识。

0x01

怎样的判断一个 Task 输出不需要变更 ?
其中一个条件是比对当前执行状态和上次执行状态的不同。
HistoricalTaskExecution: 表示上次执行状态, 是从快照中反序列化出来的。TaskExecutionSnapshotSerializer.read()
CurrentTaskExecution:表示当前执行状态,是根据当前 Task 的输入输出生成的。 CacheBackedTaskHistoryRepository.createExecution

比对具体逻辑 TaskUpToDateState

this.allTaskChanges = new ErrorHandlingTaskStateChanges(task, new SummaryTaskStateChanges(MAX_OUT_OF_DATE_MESSAGES, 
previousSuccessState,
noHistoryState, 
taskTypeState, 
inputPropertiesState, 
outputFileChanges, 
inputFileChanges, 
discoveredInputFilesChanges
));

这里经过 7 个校验,全部验证通过说明这次执行相对上次没有变更,可以直接使用上次执行的输出。

InputPropert

Map<String, Object> 类型
来自注解 @Input 或API Task.getInput.propertyTask.getInput.propertys

@Input 可以被序列化和反序列化的类型。支持的类型有 基本类型,枚举,Serializable 和 Name 扩展类型。


image.png

InputFile

文件类型
来自注解 @InputDirectory @InputFile 或 API
Task.getInput.fileTask.getInput.filesTask.getInput.dir

OutputFile

文件类型
来自注解 @OutputDirectory @OutputDirectories @OutputFile @OutputFiles 或 API Task.getInput.dirTask.getInput.dirsTask.getInput.fileTask.getInput.files

文件的比较主要分为3种,

Normalized Name 的策略是注解 @PathSensitive 来确定的。

@Nested 是自定义的类型。 是一组或者多组相关输入输出的集合。内部使用上面的注解来定义输入和输出。

注:注解生效一定要声明对应的 get 方法,而不是字段上面。

0x01

Task 通过注解的方式定义输入和输出。
Gradle 中定义 Task 。


image.png

通过接受一个 Class 类型来声明一个 Task 。
Class<Task> -> Class<Task_Decorated> -> Task_Decorated

image.png

Class<Task> 会经过 ClassGenerator , TaskFactory,AnnotationProcessingTaskFactory 生成 Task_Decorated 对象。Task_Decorated 是对 Task 的扩展。

0x02

Task 的执行由 TaskExecuter 执行的。
TaskExecutionServices.createTaskExecuter()

TaskExecuter createTaskExecuter(TaskArtifactStateRepository repository,
                                    TaskOutputCacheCommandFactory taskOutputCacheCommandFactory,
                                    BuildCacheController buildCacheController,
                                    StartParameter startParameter,
                                    ListenerManager listenerManager,
                                    TaskInputsListener inputsListener,
                                    BuildOperationExecutor buildOperationExecutor,
                                    AsyncWorkTracker asyncWorkTracker,
                                    BuildOutputCleanupRegistry cleanupRegistry,
                                    TaskOutputFilesRepository taskOutputFilesRepository,
                                    BuildScanPluginApplied buildScanPlugin) {

        boolean taskOutputCacheEnabled = startParameter.isBuildCacheEnabled();
        boolean scanPluginApplied = buildScanPlugin.isBuildScanPluginApplied();
        TaskOutputsGenerationListener taskOutputsGenerationListener = listenerManager.getBroadcaster(TaskOutputsGenerationListener.class);

        TaskExecuter executer = new ExecuteActionsTaskExecuter(
            taskOutputsGenerationListener,
            listenerManager.getBroadcaster(TaskActionListener.class),
            buildOperationExecutor,
            asyncWorkTracker
        );
        boolean verifyInputsEnabled = Boolean.getBoolean("org.gradle.tasks.verifyinputs");
        if (verifyInputsEnabled) {
            executer = new VerifyNoInputChangesTaskExecuter(repository, executer);
        }
        executer = new OutputDirectoryCreatingTaskExecuter(executer);
        if (taskOutputCacheEnabled) {
            executer = new SkipCachedTaskExecuter(
                buildCacheController,
                taskOutputsGenerationListener,
                taskOutputCacheCommandFactory,
                executer
            );
        }
        executer = new SkipUpToDateTaskExecuter(executer);
        executer = new ResolveTaskOutputCachingStateExecuter(taskOutputCacheEnabled, executer);
        if (verifyInputsEnabled || taskOutputCacheEnabled || scanPluginApplied) {
            executer = new ResolveBuildCacheKeyExecuter(executer, buildOperationExecutor);
        }
        executer = new ValidatingTaskExecuter(executer);
        executer = new SkipEmptySourceFilesTaskExecuter(inputsListener, cleanupRegistry, taskOutputsGenerationListener, executer);
        executer = new CleanupStaleOutputsExecuter(cleanupRegistry, taskOutputFilesRepository, buildOperationExecutor, executer);
        executer = new ResolveTaskArtifactStateTaskExecuter(repository, executer);
        executer = new SkipTaskWithNoActionsExecuter(executer);
        executer = new SkipOnlyIfTaskExecuter(executer);
        executer = new ExecuteAtMostOnceTaskExecuter(executer);
        executer = new CatchExceptionTaskExecuter(executer);
        return executer;
    }

这是一个装饰者模式。

需要注意的是这里有两种东西,一种是快照由 SkipUpToDateTaskExecuter 存储的是执行的状态,不包括 Output 的实体。另一种缓存是由 SkipCachedTaskExecuter 存储,是 Output 的实体而不是状态。

0x03

当 Task 满足以下 4 个条件其中一个,则 Gradle 跳转执行过程。

  1. 当一个 Task 定义了输出,Task 的 Output.upToDate 为 true,Task Source 为空。Gradle 将跳过该任务的执行。 Output 被标识 NO-SOURCE, Output 为空。
  2. 当一个 Task 定义了输出,Task 的 Output.upToDate 为 true,Task Source 不为空, Task 的输入和输出没有变更。Output 被标识 UP-TO-DATE,Gradle 将跳过该任务的执行。 使用上次的 Output 。
  3. Task 的 OnlyIf 为 false, Gradle 将跳过该任务的执行。Output 被标识 SKIPPED Output 为空。
  4. 支持缓存。缓存存在且有效,Gradle 将跳过该任务的执行。Output 被标识 FROM-CACHE,使用从缓存解压的 Output 。

0x04 尾巴

Gradle 的代码相对比较松散, 而 Task 这块的代码相对比较集中。通过本章当中的一些关键节点可以很方便的进行学习和深入了解。

上一篇下一篇

猜你喜欢

热点阅读