dim.red
环境:Gradle 4.4.1

相关

Task 输入输出注解
@Input,@InputFile,@InputFiles,@InputDirectory, @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

1
2
3
4
5
6
7
8
9
this.allTaskChanges = new ErrorHandlingTaskStateChanges(task, new SummaryTaskStateChanges(MAX_OUT_OF_DATE_MESSAGES, 
previousSuccessState,
noHistoryState,
taskTypeState,
inputPropertiesState,
outputFileChanges,
inputFileChanges,
discoveredInputFilesChanges
));

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

  • previousSuccessState:判断之前执行是否成功。
  • noHistoryState:判断是否有执行记录。
  • taskTypeState:比对 Task 和 Action 的实现。具体是比较 ClassLoader 的 Hash 。
  • inputPropertiesState:比对 InputPropert 变更。
  • outputFileChanges:比对 OutputFie 变更。
  • inputFileChanges:比对 InputFie 变更。
  • discoveredInputFilesChanges:比对 Task 中新增的 Input 变更 。

InputPropert

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

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

InputFile

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

OutputFile

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

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

  • 一般的文件:比较文件的的 Hash,Hash 是由文件 Normalized Name 和 文件的内容计算出来的 MD5。
  • Classpath文件: @Classpath 注释。这种类型在计算 jar 的Hash, 会先对 jar 文件里面的 ZipEntry 进行排序再和 Normalized Name 一起算出 MD5. 这样就不会因ZipEntry 排序导致的 MD5 不同。
  • CompileClassPath:@CompileClasspath 注释。 在 Classpath 的基础上, 对 Jar 中的 class 进行 ABI 格式化, 即当 jar 提供的接口不变,则 Jar 的 MD5 不变。 这种类型的加入也是使依赖从 compile 升级到 implementation 的关键。

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

  • ABSOLUTE:文件的绝对位置。
  • RELATIVE:文件的相对位置
  • NAME_ONLY:文件名
  • NONE:忽略
    默认值为 ABSOLUTE
    具体实现查看代码InputPathNormalizationStrategy

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

注:注解可以声明字段或者 get 方法。但是注解要生效一定要有对应的 get 方法。

0x01

Task 通过注解的方式定义输入和输出。
Gradle 中定义 Task 。
image.png
通过接受一个 Class 类型来声明一个 Task 。
Class\ -> Class\<Task_Decorated> -> Task_Decorated

image.png
Class\ 会经过 ClassGenerator , TaskFactory,AnnotationProcessingTaskFactory 生成 Task_Decorated 对象。Task_Decorated 是对 Task 的扩展。

  • ClassGenerator:使用 ASM 对原始的类进行分析,生成 Task 的子类 Task_Decorated,并且实现新的接口,增加新的方法和字段。使之具有扩展的能力。
  • TaskFactory:主要设置 Task_Decorated 合适的实例化方法。(为Task 构造方法注入 Service 对象)
  • AnnotationProcessingTaskFactory:反射获取 Task 的注解信息,通过一系列的 PropertyAnnotationHandler 处理 Task 类解析出对应 Input 和 Output 。

0x02

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
}

这是一个装饰者模式。

  • CatchExceptionTaskExecuter:拦截执行中出现的异常。
  • ExecuteAtMostOnceTaskExecuter:确保 Task 只执行一次。
  • SkipOnlyIfTaskExecuter:支持 Task.OnlyIf . Task.onlyIf 为 false 将跳过该任务的执行。
  • SkipTaskWithNoActionsExecuter:过滤没有 Action 的 Task。(默认第一个 Action 是 Task 中的被 @TaskAction 的方法)
  • ResolveTaskArtifactStateTaskExecuter:从快照中反序列化出上次执行的状态(HistoricalTaskExecution)。
  • CleanupStaleOutputsExecuter:负责清除非 Task 执行中生成的文件
  • SkipEmptySourceFilesTaskExecuter:判断存在 Output 存在时 Source 文件不为空。Source 文件是输入文件中被 @SkipWhenEmpty 注释的属性。
  • ValidatingTaskExecuter:验证 input 和 output 。比如 input 的文件要存在等等。
  • ResolveBuildCacheKeyExecuter:计算当前执行 Task 的 CacheKey,基于 TaskPath,input , output , Action 等信息, 作为后面从缓存中获取数据的 key。
  • ResolveTaskOutputCachingStateExecuter: 设置 Task Output 的缓存状态。
  • SkipUpToDateTaskExecuter:是否能直接跳过执行过程,逻辑主要是有几个, 一,task 的输入输出没有变更, 二 Task 输出 upToDate 为true,三,Gradle 执行命令没有使用 rerun-tasks 参数, 同时执行成功会保存当前 Task 状态(CurrentTaskExecution)的快照, 其中包括 InputPropert ,InputFile,OutputFile。 InputPropert 是将它序列化,而 InputFile,OutputFile 是保存的的文件的 Normalized Name 和 Hash,并不保存文件本身。默认保存在项目中(.gradle/4.4.1/taskHistory/taskHistory.bin)文件下。而这些将成为下一个执行时候从 ResolveTaskArtifactStateTaskExecuter 反序列化出来。
  • SkipCachedTaskExecuter:缓存有效的时候。尝试从根据 CacheKey 把 OutputFile 文件加载进来, 这里的缓存的来源可以是本地的文件也可是是远程的 Http 服务。同时在 Task 执行完成以后,将Task 输出缓存起来,同样可是缓存在本地或者远程。
    默认情况本地存储是开启的,位置在全局的 .gradle/caches/build-cache-1/,
    远程 Http 服务是关闭的。
  • OutputDirectoryCreatingTaskExecuter:Output 文件不存在自动创建
  • VerifyNoInputChangesTaskExecuter:验证输入在执行过程中是否有变更。
  • ExecuteActionsTaskExecuter: 执行被注解 @TaskAction 的方法和添加进来的 Action 。

需要注意的是这里有两种东西,一种是快照由 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 这块的代码相对比较集中。通过本章当中的一些关键节点可以很方便的进行学习和深入了解。