預備知識

  1. 理解 gradle 的基本開發
  2. 了解 gradle task 和 plugin 使用及開發
  3. 了解 android gradle plugin 的使用

看完本文可以達到什麼程度

  1. 了解 gradle 的實現原理

閱讀前準備工作

  1. clone EasyGradle 項目
  2. 下載 Gradle 源碼 作為參考

讀代碼的姿勢

  1. 調用鏈路,方便讀代碼時對照
  2. 集中於整體框架,一些細節不做追究

目錄

本文主要從下面幾個部分進行分析

  1. Gradle 的啟動
  2. loadSettings
  3. configureBuild
  4. constructTaskGraph
  5. runTasks
  6. finishBuild
  7. gradle 腳本如何編譯和執
  8. 插件調用流程

一、Gradle 的啟動

1.1 整體實現圖

1.2 具體分析

我們執行一個構建任務的時候,都是執行 ./gradlew assembleDebug 這樣的命令,其中的 gradlew 腳本就是整個 gradle 構建的入口,我們先從這裡看起。

前面的代碼基本上就是判斷環境,設置變數的,直接看最後一行:

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

最後執行的命令基本上如下:

exec $JAVA_HOME/bin/java -classpath $APP_HOME/gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain

基本上可以看到,就是執行了 gradle/wrapper/gradle-wrapper.jar 里的 org.gradle.wrapper.GradleWrapperMain,這樣我們就知道了,gradle 的入口類是 org.gradle.wrapper.GradleWrapperMain,也就知道代碼該從何開始看了。

先看 GradleWrapperMain 的 main 函數:

// GradleWrapperMain
public static void main(String[] args) throws Exception {
// ...
WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
wrapperExecutor.execute(
args,
new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
new BootstrapMainStarter());
}

重要的類有兩個 org.gradle.wrapper.WrapperExecutor 和 org.gradle.wrapper.BootstrapMainStarter。我們繼續跟進 WrapperExecutor.execute 里看一下:

// WrapperExecutor.execute
public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
File gradleHome = install.createDist(config);
bootstrapMainStarter.start(args, gradleHome);
}

這裡就做了兩件事:

  1. 下載 gradle wrapper 需要的依賴以及源碼。其中的 gradle wrapper 版本就是我們在 gradle/wrapper/gradle-wrapper.properties 里配置的 distributionUrl,下載位置就是在 gradle-wrapper.properties 里配置的 distributionPath 和 zipStorePath。zipStorePath 是下載的壓縮包位置,distributionPath 是解壓後的位置,一般默認的位置就是 HOME/.gradle/wrapper/dists/,在這裡就可以找到 gradle wrapper 的內容了。如果創建過多個項目的話,我們在 HOME/.gradle/wrapper/dists/ 里可以看到不同版本的 gradle wrapper,這也說明了我們之前最開始說的,為什麼要使用 gradle wrapper 而不是直接在電腦里安裝 gradle,就是因為 gradle wrapper 會根據不同的項目下載不同版本的內容,項目彼此之間互不影響。
  2. 執行 gradle 構建流程。這裡就是順著 BootstrapMainStarter.start() 往下執行了,中間過程就不看了,比較曲折,對理解整體流程也沒什麼太大的幫助。最終會運行到 DefaultGradleLauncher.executeTasks(),然後再往下的流程就非常清晰了。

// DefaultGradleLauncher
public GradleInternal executeTasks() {
doBuildStages(Stage.Build);
return gradle;
}

private void doBuildStages(Stage upTo) {
// ...
loadSettings();
configureBuild();
constructTaskGraph();
runTasks();
finishBuild();
}

基本上構建過程就是分五步走,下面分別看這五個流程。

二、loadSettings

2.1 整體實現圖

2.2 具體分析

loadSettings 主要是載入 settings.gradle 文件,然後創建對應的 project。

// DefaultGradleLauncher.loadSettings
private void loadSettings() {
if (stage == null) {
buildListener.buildStarted(gradle);

buildOperationExecutor.run(new LoadBuild());

stage = Stage.Load;
}
}

整體構建流程:

2.2.1 調用 BuildListener.buildStarted() 回調介面

通知構建開始。這個就是我們之前在 Gradle 基本使用 里說的生命周期回調。

2.2.2 執行 init 腳本

調用鏈路

LoadBuild.run -> InitScriptHandler.executeScripts

之前在 Gradle 基本使用 里說過 init.gradle 的作用,會在每個項目 build 之前被調用,做一些初始化的操作,就是在這裡被調用的。

2.2.3 查找 settings.gradle 位置

調用鏈路

LoadBuild.run -> NotifyingSettingsLoader.findAndLoadSettings -> CompositeBuildSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> DefaultSettingsLoader.findSettings -> DefaultSettingsFinder.find -> BuildLayoutFactory.getLayoutFor

實現分析

在 getLayoutFor 里,查找 settings.gradle 文件邏輯如下:
  1. 如果參數里通過 -c xxx.gradle 指定了 settings.gradle 文件的位置,那麼直接使用指定的 settings.gradle 文件
  2. 如果沒有指定 settings 文件,就在當前目錄下查找
  3. 如果當前目錄沒有,會一直往上級目錄查找,以及同級的 maseter/ 目錄下查找
  4. 如果都沒有找到,還是默認在當前目錄下

// BuildLayoutFactory
public BuildLayout getLayoutFor(BuildLayoutConfiguration configuration) {
if (configuration.isUseEmptySettings()) {
return new BuildLayout(configuration.getCurrentDir(), configuration.getCurrentDir(), null);
}
File explicitSettingsFile = configuration.getSettingsFile();
if (explicitSettingsFile != null) {
if (!explicitSettingsFile.isFile()) {
throw new MissingResourceException(explicitSettingsFile.toURI(), String.format("Could not read settings file %s as it does not exist.", explicitSettingsFile.getAbsolutePath()));
}
return new BuildLayout(configuration.getCurrentDir(), configuration.getCurrentDir(), explicitSettingsFile);
}

File currentDir = configuration.getCurrentDir();
boolean searchUpwards = configuration.isSearchUpwards();
return getLayoutFor(currentDir, searchUpwards ? null : currentDir.getParentFile());
}

2.2.4 編譯 buildSrc 文件夾下的內容,buildSrc 可以看作插件類似的功能

調用鏈路

LoadBuild.run -> NotifyingSettingsLoader.findAndLoadSettings -> CompositeBuildSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> BuildSourceBuilder.buildAndCreateClassLoader

在上一步找到 settings.gradle 文件以後,會以 settings.gradle 所在的同級目錄下,查找 buildSrc 目錄,並進行編譯,這樣可以保證在構建 settings.gradle 的時候可以引用到 buildSrc 目錄里的內容。

2.2.5 解析 gradle.properites

調用鏈路

LoadBuild.run -> NotifyingSettingsLoader.findAndLoadSettings -> CompositeBuildSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> NotifyingSettingsProcessor.process -> PropertiesLoadingSettingsProcessor.process -> DefaultGradlePropertiesLoader.loadProperties

實現分析

這一步會讀取 gradle.properties 文件里的配置,系統配置,環境變數,以及命令行傳入的配置並存儲。

// DefaultGradlePropertiesLoader
void loadProperties(File settingsDir, StartParameter startParameter, Map<String, String> systemProperties, Map<String, String> envProperties) {
defaultProperties.clear();
overrideProperties.clear();
addGradleProperties(defaultProperties, new File(settingsDir, Project.GRADLE_PROPERTIES));
addGradleProperties(overrideProperties, new File(startParameter.getGradleUserHomeDir(), Project.GRADLE_PROPERTIES));
setSystemProperties(startParameter.getSystemPropertiesArgs());
overrideProperties.putAll(getEnvProjectProperties(envProperties));
overrideProperties.putAll(getSystemProjectProperties(systemProperties));
overrideProperties.putAll(startParameter.getProjectProperties());
}

2.2.6 解析 settings.gradle

調用鏈路

LoadBuild.run -> NotifyingSettingsLoader.findAndLoadSettings -> CompositeBuildSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> NotifyingSettingsProcessor.process -> PropertiesLoadingSettingsProcessor.process -> ScriptEvaluatingSettingsProcessor.process -> ScriptEvaluatingSettingsProcessor.applySettingsScript -> BuildOperationScriptPlugin.apply

實現分析

在 ScriptEvaluatingSettingsProcessor 里,先創建了 SettingsInternal 實例,以及 ScriptSource 實例,代表 settings.gradle 文件在內存中的映射,之後就調用 BuildOperationScriptPlugin.apply 去執行 settings.gradle 文件了。關於 BuildOperationScriptPlugin.apply,我們後面細說,因為在解析 build.gradle 文件的時候也會用到這個方法。下面是對應的代碼:

// ScriptEvaluatingSettingsProcessor
public SettingsInternal process(GradleInternal gradle,
SettingsLocation settingsLocation,
ClassLoaderScope buildRootClassLoaderScope,
StartParameter startParameter) {
Timer settingsProcessingClock = Timers.startTimer();
Map<String, String> properties = propertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
SettingsInternal settings = settingsFactory.createSettings(gradle, settingsLocation.getSettingsDir(),
settingsLocation.getSettingsScriptSource(), properties, startParameter, buildRootClassLoaderScope);
applySettingsScript(settingsLocation, settings);
LOGGER.debug("Timing: Processing settings took: {}", settingsProcessingClock.getElapsed());
return settings;
}

private void applySettingsScript(SettingsLocation settingsLocation, final SettingsInternal settings) {
ScriptSource settingsScriptSource = settingsLocation.getSettingsScriptSource();
ClassLoaderScope settingsClassLoaderScope = settings.getClassLoaderScope();
ScriptHandler scriptHandler = scriptHandlerFactory.create(settingsScriptSource, settingsClassLoaderScope);
ScriptPlugin configurer = configurerFactory.create(settingsScriptSource, scriptHandler, settingsClassLoaderScope, settings.getRootClassLoaderScope(), true);
configurer.apply(settings);
}

2.2.7 創建 project 以及 subproject

調用鏈路

LoadBuild.run -> NotifyingSettingsLoader.findAndLoadSettings -> CompositeBuildSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findAndLoadSettings -> DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> NotifyingSettingsProcessor.process -> ProjectPropertySettingBuildLoader.load -> InstantiatingBuildLoader.load

實現分析

在解析了 settings.gradle 文件以後,就可以知道項目里有哪些 project,就可以創建 project 實例了。

// InstantiatingBuildLoader
// 這裡傳入的參數對應的是:rootProjectDescriptor: SettingsInternal.getRootProject() defaultProject: SettingsInternal.getDefaultProject() buildRootClassLoaderScope:SettingsInternal.getRootClassLoaderScope()
public void load(ProjectDescriptor rootProjectDescriptor, ProjectDescriptor defaultProject, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
createProjects(rootProjectDescriptor, gradle, buildRootClassLoaderScope);
attachDefaultProject(defaultProject, gradle);
}

private void attachDefaultProject(ProjectDescriptor defaultProject, GradleInternal gradle) {
gradle.setDefaultProject(gradle.getRootProject().getProjectRegistry().getProject(defaultProject.getPath()));
}

private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
// 創建主項目實例
// ProjectInternal 繼承自 Project,最終返回的 rootProject 是 DefaultProject 類型
ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle, buildRootClassLoaderScope.createChild("root-project"), buildRootClassLoaderScope);
gradle.setRootProject(rootProject);
addProjects(rootProject, rootProjectDescriptor, gradle, buildRootClassLoaderScope);
}

private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
// 創建子項目實例
for (ProjectDescriptor childProjectDescriptor : parentProjectDescriptor.getChildren()) {
ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle, parent.getClassLoaderScope().createChild("project-" + childProjectDescriptor.getName()), buildRootClassLoaderScope);
addProjects(childProject, childProjectDescriptor, gradle, buildRootClassLoaderScope);
}
}

// ProjectFactory
public DefaultProject createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle, ClassLoaderScope selfClassLoaderScope, ClassLoaderScope baseClassLoaderScope) {
// 獲取 project 對應的 build.gradle
File buildFile = projectDescriptor.getBuildFile();
ScriptSource source = UriScriptSource.file("build file", buildFile);
// 創建 project 實例
DefaultProject project = instantiator.newInstance(DefaultProject.class,
projectDescriptor.getName(),
parent,
projectDescriptor.getProjectDir(),
source,
gradle,
gradle.getServiceRegistryFactory(),
selfClassLoaderScope,
baseClassLoaderScope
);

// 設置 project 的層級關係
if (parent != null) {
parent.addChildProject(project);
}
// 註冊 project
projectRegistry.addProject(project);

return project;
}

這裡根據 settings.gradle 的配置,創建項目實例。創建子項目的時候,如果父項目不為空,就將自己設置成父項目的子項目,這樣就可以通過 project.getChildProjects 獲取項目的子項目了。

我們在寫 gradle 腳本的時候,經常會用到的 project 屬性,就是在這個時候創建出來了。

到此為止,就解析了 settings.gradle 文件然後創建了項目實例。

三、configureBuild

3.1 整體實現圖

3.2 具體分析

我們之前有說到,gradle 構建過程分為配置階段和運行階段,配置階段主要是執行腳本的內容,運行階段是執行 task 的內容,這裡就是配置階段的流程。要注意,之前說的配置和運行階段,是從整體來看的兩個階段,從源碼來理解,就是這篇文章介紹的幾個階段,要更細化一點。

配置階段執行的內容比較簡單,就是把 gradle 腳本編譯成 class 文件,然後運行(gradle 是採用 groovy 語言編寫的,groovy 是一門 jvm 語言,所以必須要編譯成 class 才能運行)。

// DefaultGradleLauncher
private void configureBuild() {
if (stage == Stage.Load) {
buildOperationExecutor.run(new ConfigureBuild());

stage = Stage.Configure;
}
}

在配置項目的時候,如果指定了 configure-on-demand 參數,只會配置主項目以及執行 task 需要的項目,默認沒有指定,會配置所有的項目,這裡只看默認情況。

3.2.1 配置主項目及其子項目的主要鏈路

調用鏈路

ConfigureBuild.run -> DefaultBuildConfigurer.configure -> TaskPathProjectEvaluator.configureHierarchy -> TaskPathProjectEvaluator.configure -> DefaultProject.evaluate -> LifecycleProjectEvaluator.evaluate -> LifecycleProjectEvaluator.doConfigure -> ConfigureActionsProjectEvaluator.evaluate

實現分析

// TaskPathProjectEvaluator
public void configureHierarchy(ProjectInternal project) {
configure(project);
for (Project sub : project.getSubprojects()) {
configure((ProjectInternal) sub);
}
}

最終執行到了 LifecycleProjectEvaluator.doConfigure

3.2.2 回調 BuildListener.beforeEvaluate 介面

在這裡回調 beforeEvaluate 介面,通知配置將要開始。我們也就知道了這個回調執行的階段。

3.2.3 設置默認的 task 和 插件

調用鏈路

ConfigureBuild.run -> DefaultBuildConfigurer.configure -> TaskPathProjectEvaluator.configureHierarchy -> TaskPathProjectEvaluator.configure -> DefaultProject.evaluate -> LifecycleProjectEvaluator.evaluate -> LifecycleProjectEvaluator.doConfigure -> ConfigureActionsProjectEvaluator.evaluate -> PluginsProjectConfigureActions.execute

實現分析

在 PluginsProjectConfigureActions 里,會給 project 添加兩個 task:init 和 wrapper,然後添加幫助插件:org.gradle.help-tasks。

3.2.4 編譯腳本並執行

調用鏈路

ConfigureBuild.run -> DefaultBuildConfigurer.configure -> TaskPathProjectEvaluator.configureHierarchy -> TaskPathProjectEvaluator.configure -> DefaultProject.evaluate -> LifecycleProjectEvaluator.evaluate -> LifecycleProjectEvaluator.doConfigure -> ConfigureActionsProjectEvaluator.evaluate -> BuildScriptProcessor.execute -> BuildOperationScriptPlugin.apply

實現分析

這裡調用的還是 BuildOperationScriptPlugin.apply 去編譯和執行 build.gradle 腳本,和前面解析 settings.gradle 是一樣的,這裡我們先知道這個就是編譯 build.gradle 為 class。文件並且執行,然後先往後看流程,後面再詳細說腳本是如何編譯和執行的。

3.2.5 回調 BuildListener.afterEvaluate

3.2.6 回調 BuildListener.projectsEvaluated

四、constructTaskGraph

4.1 整體實現圖

4.2 具體分析

這一步是構建 task 依賴圖

// DefaultGradleLauncher
private void constructTaskGraph() {
if (stage == Stage.Configure) {
buildOperationExecutor.run(new CalculateTaskGraph());

stage = Stage.TaskGraph;
}
}

4.2.1 處理需要排除的 task

調用鏈路

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> ExcludedTaskFilteringBuildConfigurationAction.configure

實現分析

// ExcludedTaskFilteringBuildConfigurationAction
public void configure(BuildExecutionContext context) {
GradleInternal gradle = context.getGradle();
Set<String> excludedTaskNames = gradle.getStartParameter().getExcludedTaskNames();
if (!excludedTaskNames.isEmpty()) {
final Set<Spec<Task>> filters = new HashSet<Spec<Task>>();
for (String taskName : excludedTaskNames) {
filters.add(taskSelector.getFilter(taskName));
}
gradle.getTaskGraph().useFilter(Specs.intersect(filters));
}

context.proceed();
}

這一步是用來處理需要排除的 task,也就是在命令行通過 -x or --exclude-task 指定的 task,這裡主要是給 TaskGraph 設置了 filter,以便在後面計算依賴的時候排除相應的 task。

4.2.2 添加默認的 task

調用鏈路

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> DefaultTasksBuildExecutionAction.configure

實現分析

這裡會檢查命令行里是否有傳入 Task 名稱進來,如果指定了要執行的 task,那麼什麼都不做。如果沒有指定,就看 project 是否有默認的 task,默認的 task 可以通過 defaultTasks 在 build.gradle 里進行指定。如果也默認 task 也沒有,那麼就把要指定的 task 設置成 help task,也就是輸出 gradle 的幫助內容。

4.2.3 計算 task 依賴圖

調用鏈路

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure

實現分析

  1. 根據命令行的 taskname 篩選 task。如果我們的 task 指定了 project,也就是類似這樣的 :app:assembleDebug,那麼就直接選中了 task,如果沒有指定具體 project,那麼會把所有 project 下符合 taskname 的 task 都篩選出來。

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure -> CommandLineTaskParser.parseTasks

  1. 把 task 添加到 taskGraph 中,這裡會處理 task 的依賴關係,包括 dependson finalizedby mustrunafter shouldrunafter,然後把信息都保存在 org.gradle.execution.taskgraph.TaskInfo 里。

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure -> DefaultTaskGraphExecuter.addTasks

4.2.4 生成 task graph

調用鏈路

CalculateTaskGraph.run -> TaskGraphExecuter.populate -> DefaultTaskExecutionPlan.determineExecutionPlan

實現分析

根據上一步計算的 task 及其依賴,生成 task 圖

五、runTasks

5.1 整體實現圖

5.2 具體分析

task 圖生成以後,就開始執行 task

5.2.1 處理 dry run

調用鏈路

DefaultBuildExecuter.execute -> DryRunBuildExecutionAction.execute

實現分析

如果在命令行里指定了 --dry-run,在這裡就會攔截 task 的執行,直接輸出 task 的名稱以及執行的先後關係。

5.2.2 創建線程,執行 task

調用鏈路

DefaultBuildExecuter.execute -> SelectedTaskExecutionAction.execute -> DefaultTaskPlanExecutor.process

實現分析

創建 TaskExecutorWorker 去執行 task,默認是 8 個線程。

// DefaultTaskPlanExecutor
public void process(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
ManagedExecutor executor = executorFactory.create("Task worker for " + taskExecutionPlan.getDisplayName() + "");
try {
WorkerLease parentWorkerLease = workerLeaseService.getCurrentWorkerLease();
// 開線程
startAdditionalWorkers(taskExecutionPlan, taskWorker, executor, parentWorkerLease);
taskWorker(taskExecutionPlan, taskWorker, parentWorkerLease).run();
taskExecutionPlan.awaitCompletion();
} finally {
executor.stop();
}
}

5.2.3 task 執行前處理

調用鏈路

DefaultBuildExecuter.execute -> SelectedTaskExecutionAction.execute -> DefaultTaskPlanExecutor.process -> TaskExecutorWorker.run -> DefaultTaskExecutionPlan.executeWithTask -> DefaultTaskExecutionPlan.selectNextTask -> DefaultTaskExecutionPlan.processTask -> EventFiringTaskWorker.execute -> DefaultBuildOperationExecutor.run

實現分析

到這裡就正式開始 task 的執行過程了。有幾個步驟:
  1. 回調 TaskExecutionListener.beforeExecute
  2. 鏈式執行一些列對 Task 的處理,具體的處理如下:

CatchExceptionTaskExecuter.execute // 加了 try catch,防止執行過程中異常
ExecuteAtMostOnceTaskExecuter.execute // 判斷 task 是否執行過
SkipOnlyIfTaskExecuter.execute // 判斷 task 的 onlyif 條件是否滿足執行
SkipTaskWithNoActionsExecuter.execute // 跳過沒有 action 的 task,沒有 action 說明 task 不需要執行
ResolveTaskArtifactStateTaskExecuter.execute // 設置 artifact 狀態
SkipEmptySourceFilesTaskExecuter.execute // 跳過設置了 source file 但是 source file 為空的 task,source file 為空說明 task 沒有需要處理的資源
ValidatingTaskExecuter.execute() // 確認 task 是否可以執行
ResolveTaskOutputCachingStateExecuter.execute // 處理 task output 緩存
SkipUpToDateTaskExecuter.execute // 跳過 update-to-date 的 task
ExecuteActionsTaskExecuter.execute // 真正執行 task

5.2.4 task 執行

調用鏈路

DefaultBuildExecuter.execute -> SelectedTaskExecutionAction.execute -> DefaultTaskPlanExecutor.process -> TaskExecutorWorker.run -> DefaultTaskExecutionPlan.executeWithTask -> DefaultTaskExecutionPlan.selectNextTask -> DefaultTaskExecutionPlan.processTask -> EventFiringTaskWorker.execute -> DefaultBuildOperationExecutor.run -> ExecuteActionsTaskExecuter.execute

實現分析

經過前面一系列處理,這裡開始真正執行 task 了。
  1. 回調 TaskActionListener.beforeActions
  2. 回調 OutputsGenerationListener.beforeTaskOutputsGenerated
  3. 取出 task 中的 Actions 全部執行

// ExecuteActionsTaskExecuter
private GradleException executeActions(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
final List<ContextAwareTaskAction> actions = new ArrayList<ContextAwareTaskAction>(task.getTaskActions());
int actionNumber = 1;
for (ContextAwareTaskAction action : actions) {
// ...
executeAction("Execute task action " + actionNumber + "/" + actions.size() + " for " + task.getPath(), task, action, context);
// ...
actionNumber++;
}
return null;
}

這裡可以看到,Task 的本質,其實就是執行其中的 Actions。舉個例子來說,我們一般自定義 Task 的時候,經常用下面的寫法:

task {
doLast {
// task 具體任務
}
}

這裡的 doLast 就相當於給 Task 添加了一個 Action。

看一下 AbstractTask 的 doLast 方法

// AbstractTask
public Task doLast(final Action<? super Task> action) {
// ...
taskMutator.mutate("Task.doLast(Action)", new Runnable() {
public void run() {
getTaskActions().add(wrap(action));
}
});
return this;
}

private ContextAwareTaskAction wrap(final Action<? super Task> action) {
if (action instanceof ContextAwareTaskAction) {
return (ContextAwareTaskAction) action;
}
return new TaskActionWrapper(action);
}

可以看到,我們傳入的閉包,最終是包裝成 TaskActionWrapper 添加到 task 的 actions 中的。

  1. 回調 TaskActionListener.afterActions
  2. 回調 TaskExecutionListener.afterExecute

六、finishBuild

6.1 整體實現圖

6.2 具體分析

private void finishBuild(BuildResult result) {
if (stage == Stage.Finished) {
return;
}

buildListener.buildFinished(result);
if (!isNestedBuild()) {
gradle.getServices().get(IncludedBuildControllers.class).stopTaskExecution();
}
stage = Stage.Finished;
}

這裡邏輯不多,回調了 BuildListener.buildFinished 介面

通過上面幾個步驟,我們基本上看到了 gradle 的執行流程,簡單來說,步驟如下:

  1. 解析 settings.gradle 並執行,生成 Project 實例
  2. 解析 build.gradle 並執行
  3. 生成 task 依賴圖
  4. 執行 task

七、gradle 腳本如何編譯和執行

在前面介紹 loadsettings 和 configureBuild 階段的時候,我們提到了 BuildOperationScriptPlugin.apply 這個方法,只是簡單帶過,是用來編譯 gradle 腳本並執行的,這裡來具體分析一下。

7.1 編譯腳本

調用鏈路

BuildOperationScriptPlugin.apply -> DefaultScriptPluginFactory.ScriptPluginImpl.apply -> DefaultScriptCompilerFactory.ScriptCompilerImpl.compile -> BuildScopeInMemoryCachingScriptClassCompiler.compile -> CrossBuildInMemoryCachingScriptClassCache.getOrCompile -> FileCacheBackedScriptClassCompiler.compile

實現分析

這裡編譯過程分為兩部分,首先編譯腳本的 buildscript {} 部分,忽略其他部分,然後再編譯腳本的其他部分並執行。所以 buildscript {} 里的內容會先於其他內容執行
  1. 會先檢查緩存,如果有緩存的話,直接使用,沒有緩存再進行編譯
  2. 最終會調用到 CompileToCrossBuildCacheAction.execute -> DefaultScriptCompilationHandler.compileToDir -> DefaultScriptCompilationHandler.compileScript 去執行真正的編譯操作腳本緩存路徑: /Users/zy/.gradle/caches/4.1/scripts-remapped/build_a3v29m9cbrge95ug6eejz9wuw/31f5shvfkfunwn5ullupyy7xt/cp_proj4dada6424967ba8dfea75e81c8880f7f/classes目錄下的 class 如下:

3. 具體編譯方法是通過 RemappingScriptSource.getResource().getText() 獲取到腳本內容,然後通過 GroovyClassLoader.parseClass 編譯的。

我們以 app/build.gradle 為例,看一下最終生成的腳本是什麼樣子的。

build.gradle 腳本內容

apply plugin: com.android.application
apply plugin: myplugin

android {
compileSdkVersion 26
defaultConfig {
applicationId "com.zy.easygradle"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}

flavorDimensions "size", "color"

productFlavors {
big {
dimension "size"
}
small {
dimension "size"
}
blue {
dimension "color"
}
red {
dimension "color"
}
}
}

dependencies {
// implementation gradleApi()
implementation fileTree(dir: libs, include: [*.jar])
implementation com.android.support:appcompat-v7:26.1.0
implementation com.android.support.constraint:constraint-layout:1.1.3
implementation project(:module1)
}

gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
// println(構建開始)
}

@Override
void settingsEvaluated(Settings settings) {
// println(settings 文件解析完成)
}

@Override
void projectsLoaded(Gradle gradle) {
// println(項目載入完成)
}

@Override
void projectsEvaluated(Gradle gradle) {
// println(項目解析完成)
}

@Override
void buildFinished(BuildResult result) {
// println(構建完成)
}
})

gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
@Override
void beforeEvaluate(Project project) {
// println("${project.name} 項目配置之前調用")
}

@Override
void afterEvaluate(Project project, ProjectState state) {
// println("${project.name} 項目配置之後調用")
}
})

gradle.taskGraph.whenReady {
// println("task 圖構建完成")
}
gradle.taskGraph.beforeTask {
// println("task 執行完成")
}
gradle.taskGraph.afterTask {
// println("task 執行完成")
}

task task1 {
doLast {
println(task2)
}
}

task task2 {
doLast {
println(task2)
}
}
task1.finalizedBy(task2)

編譯後 class 內容

package defpackage;

import groovy.lang.MetaClass;
import java.lang.ref.SoftReference;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.callsite.CallSiteArray;
import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling;
import org.gradle.api.internal.project.ProjectScript;
import org.gradle.internal.scripts.ScriptOrigin;

/* compiled from: /Users/zy/workspace/note/blog/android-training/gradle/EasyGradle/app/build.gradle */
public class build_ak168fqfikdepd6py4yef8tgs extends ProjectScript implements ScriptOrigin {
private static /* synthetic */ SoftReference $callSiteArray = null;
private static /* synthetic */ ClassInfo $staticClassInfo = null;
public static transient /* synthetic */ boolean __$stMC = false;
private static final /* synthetic */ String __originalClassName = "_BuildScript_";
private static final /* synthetic */ String __signature = "988274f32891a2a3d3b8d16074617c05";

private static /* synthetic */ CallSiteArray $createCallSiteArray() {
String[] strArr = new String[22];
build_ak168fqfikdepd6py4yef8tgs.$createCallSiteArray_1(strArr);
return new CallSiteArray(build_ak168fqfikdepd6py4yef8tgs.class, strArr);
}

private static /* synthetic */ void $createCallSiteArray_1(String[] strArr) {
strArr[0] = "apply";
strArr[1] = "apply";
strArr[2] = "android";
strArr[3] = "dependencies";
strArr[4] = "addBuildListener";
strArr[5] = "gradle";
strArr[6] = "addProjectEvaluationListener";
strArr[7] = "gradle";
strArr[8] = "whenReady";
strArr[9] = "taskGraph";
strArr[10] = "gradle";
strArr[11] = "beforeTask";
strArr[12] = "taskGraph";
strArr[13] = "gradle";
strArr[14] = "afterTask";
strArr[15] = "taskGraph";
strArr[16] = "gradle";
strArr[17] = "task";
strArr[18] = "task";
strArr[19] = "finalizedBy";
strArr[20] = "task1";
strArr[21] = "task2";
}

/* JADX WARNING: inconsistent code. */
/* Code decompiled incorrectly, please refer to instructions dump. */
private static /* synthetic */ org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray() {
/*
r0 = $callSiteArray;
if (r0 == 0) goto L_0x000e;
L_0x0004:
r0 = $callSiteArray;
r0 = r0.get();
r0 = (org.codehaus.groovy.runtime.callsite.CallSiteArray) r0;
if (r0 != 0) goto L_0x0019;
L_0x000e:
r0 = defpackage.build_ak168fqfikdepd6py4yef8tgs.$createCallSiteArray();
r1 = new java.lang.ref.SoftReference;
r1.<init>(r0);
$callSiteArray = r1;
L_0x0019:
r0 = r0.array;
return r0;
*/
throw new UnsupportedOperationException("Method not decompiled: build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray():org.codehaus.groovy.runtime.callsite.CallSite[]");
}

public build_ak168fqfikdepd6py4yef8tgs() {
build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
}

protected /* synthetic */ MetaClass $getStaticMetaClass() {
if (getClass() != build_ak168fqfikdepd6py4yef8tgs.class) {
return ScriptBytecodeAdapter.initMetaClass(this);
}
ClassInfo classInfo = $staticClassInfo;
if (classInfo == null) {
classInfo = ClassInfo.getClassInfo(getClass());
$staticClassInfo = classInfo;
}
return classInfo.getMetaClass();
}

public String getContentHash() {
return __signature;
}

public String getOriginalClassName() {
return __originalClassName;
}

public Object run() {
CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
$getCallSiteArray[0].callCurrent(this, ScriptBytecodeAdapter.createMap(new Object[]{"plugin", "com.android.application"}));
$getCallSiteArray[1].callCurrent(this, ScriptBytecodeAdapter.createMap(new Object[]{"plugin", "myplugin"}));
$getCallSiteArray[2].callCurrent(this, new _run_closure1(this, this));
$getCallSiteArray[3].callCurrent(this, new _run_closure2(this, this));
$getCallSiteArray[4].call($getCallSiteArray[5].callGroovyObjectGetProperty(this), new 1(this));
$getCallSiteArray[6].call($getCallSiteArray[7].callGroovyObjectGetProperty(this), new 2(this));
$getCallSiteArray[8].call($getCallSiteArray[9].callGetProperty($getCallSiteArray[10].callGroovyObjectGetProperty(this)), new _run_closure3(this, this));
$getCallSiteArray[11].call($getCallSiteArray[12].callGetProperty($getCallSiteArray[13].callGroovyObjectGetProperty(this)), new _run_closure4(this, this));
$getCallSiteArray[14].call($getCallSiteArray[15].callGetProperty($getCallSiteArray[16].callGroovyObjectGetProperty(this)), new _run_closure5(this, this));
$getCallSiteArray[17].callCurrent(this, "task1", new _run_closure6(this, this));
$getCallSiteArray[18].callCurrent(this, "task2", new _run_closure7(this, this));
return $getCallSiteArray[19].call($getCallSiteArray[20].callGroovyObjectGetProperty(this), $getCallSiteArray[21].callGroovyObjectGetProperty(this));
}

public /* synthetic */ Object this$dist$get$7(String name) {
build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
return ScriptBytecodeAdapter.getGroovyObjectProperty(build_ak168fqfikdepd6py4yef8tgs.class, this, ShortTypeHandling.castToString(new GStringImpl(new Object[]{name}, new String[]{"", ""})));
}

public /* synthetic */ Object this$dist$invoke$7(String name, Object args) {
build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
return ScriptBytecodeAdapter.invokeMethodOnCurrentN(build_ak168fqfikdepd6py4yef8tgs.class, this, ShortTypeHandling.castToString(new GStringImpl(new Object[]{name}, new String[]{"", ""})), ScriptBytecodeAdapter.despreadList(new Object[0], new Object[]{args}, new int[]{0}));
}

public /* synthetic */ void this$dist$set$7(String name, Object value) {
build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
ScriptBytecodeAdapter.setGroovyObjectProperty(value, build_ak168fqfikdepd6py4yef8tgs.class, this, ShortTypeHandling.castToString(new GStringImpl(new Object[]{name}, new String[]{"", ""})));
}
}

可以看到,腳本類繼承自 ProjectScript,實現了 run 方法。

run 方法里做了些什麼呢,先看第一行,

CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();

獲取到 callsiteArray,這個就是 createCallSiteArray_1() 方法中賦值的,可以看到,此處的 callsiteArray,都是腳本中的 dsl,其實也就是調用的方法名。 獲取到 callsiteArray 以後,執行 $getCallSiteArray[0].callCurrent() 類似的方法,這個就是在調用方法。調用的方法對應的腳本代碼在下面加了注釋。

public Object run() {
CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
// apply plugin "com.android.application" 依賴插件
$getCallSiteArray[0].callCurrent(this, ScriptBytecodeAdapter.createMap(new Object[]{"plugin", "com.android.application"}));
// apply plugin myplugin
$getCallSiteArray[1].callCurrent(this, ScriptBytecodeAdapter.createMap(new Object[]{"plugin", "myplugin"}));
// android {}
$getCallSiteArray[2].callCurrent(this, new _run_closure1(this, this));
// dependencies {}
$getCallSiteArray[3].callCurrent(this, new _run_closure2(this, this));
// task {}
$getCallSiteArray[17].callCurrent(this, "task1", new _run_closure6(this, this));
// ...
return $getCallSiteArray[19].call($getCallSiteArray[20].callGroovyObjectGetProperty(this), $getCallSiteArray[21].callGroovyObjectGetProperty(this));
}

上面看到,task1 對應的是 _run_closure6 這個類,我們看看這個類的內容。

/* compiled from: /Users/zy/workspace/note/blog/android-training/gradle/EasyGradle/app/build.gradle */
public class build_ak168fqfikdepd6py4yef8tgs$_run_closure6 extends Closure implements GeneratedClosure, ScriptOrigin {
private static final /* synthetic */ String __originalClassName = "_BuildScript_$_run_closure6";

private static /* synthetic */ CallSiteArray $createCallSiteArray() {
String[] strArr = new String[1];
strArr[0] = "doLast";
return new CallSiteArray(build_ak168fqfikdepd6py4yef8tgs$_run_closure6.class, strArr);
}

public build_ak168fqfikdepd6py4yef8tgs$_run_closure6(Object _outerInstance, Object _thisObject) {
build_ak168fqfikdepd6py4yef8tgs$_run_closure6.$getCallSiteArray();
super(_outerInstance, _thisObject);
}

public Object doCall() {
build_ak168fqfikdepd6py4yef8tgs$_run_closure6.$getCallSiteArray();
return doCall(null);
}

public Object doCall(Object it) {
return build_ak168fqfikdepd6py4yef8tgs$_run_closure6.$getCallSiteArray()[0].callCurrent(this, new _closure17(this, getThisObject()));
}
}

省略了一些內容,可以看到,這個閉包的類繼承了 Closure,然後實現了 doCall 方法,在 doCall 方法里,調用了 doLast 方法,傳入了 _closure17 實例。這個就是腳本中的 task { doLast {} } 對應的實現。

我們再看看 _closure17 的實現。

/* compiled from: /Users/zy/workspace/note/blog/android-training/gradle/EasyGradle/app/build.gradle */
public class build_ak168fqfikdepd6py4yef8tgs$_run_closure6$_closure17 extends Closure implements GeneratedClosure, ScriptOrigin {
private static /* synthetic */ SoftReference $callSiteArray = null;
private static /* synthetic */ ClassInfo $staticClassInfo = null;
public static transient /* synthetic */ boolean __$stMC = false;
private static final /* synthetic */ String __originalClassName = "_BuildScript_$_run_closure6$_closure17";
private static final /* synthetic */ String __signature = "ab46bccc923a8e0a93329f7333d732c8";

private static /* synthetic */ CallSiteArray $createCallSiteArray() {
String[] strArr = new String[1];
strArr[0] = "println";
return new CallSiteArray(build_ak168fqfikdepd6py4yef8tgs$_run_closure6$_closure17.class, strArr);
}
public Object doCall() {
build_ak168fqfikdepd6py4yef8tgs$_run_closure6$_closure17.$getCallSiteArray();
return doCall(null);
}
public Object doCall(Object it) {
return build_ak168fqfikdepd6py4yef8tgs$_run_closure6$_closure17.$getCallSiteArray()[0].callCurrent(this, "task2");
}
}

同樣也是繼承了 Closure,在 doCall 方法里調用了 println,這正是我們在 task 的里執行的任務,也就是前面提到的 task 的 actions。

這裡我們再理順一下,每一個 build.gradle 腳本,對應一個繼承了 ProjectScript 的類,每一個閉包,對應了一個繼承自 Closure 的類

7.2 調用腳本 run 方法

接著就是執行腳本類的 run 方法,也就是我們在上面分析的 run 方法。

其中強調的一點是,run 方法里對 task 的創建,僅僅是執行了 task.doCall,這也就是為什麼配置階段不會執行 task 任務,但會執行 task 閉包里的內容。

task task1 {
// 配置階段會執行
println(configure)
doLast {
// 運行階段執行
println(run)
}
}

八、插件調用流程

之前在 Gradle的基本使用 里講到過自定義插件,使用的時候是通過 apply plugin xxx 來使用的,具體的調用鏈路如下:

apply: "xxx" -> Script.run -> ProjectScript.apply -> DefaultObjectConfigurationAction.run -> DefaultObjectConfigurationAction.applyType(pluginId) -> DefaultPluginManager.apply -> DefaultPluginManager.AddPluginBuildOperation.run -> AddPluginBuildOperation.addPlugin -> RuleBasedPluginTarget.applyImpreative -> ImperativeOnlyPluginTarget.applyImperative -> Plugin.apply

最後的 Plugin.apply 就調用到插件里實現的 apply() 函數了

九、總結

整體結構圖

  1. gradle 運行流程

loadSettings
configureBuild
constructTaskGraph
runTasks
finishBuild

  1. Task 的本質,就是一系列的 Actions
  2. 腳本編譯流程 獲取腳本內容 -> 編譯成 class 文件,繼承自 ProjectScript -> 執行 ProjectScript.run 方法
  3. 腳本的 buildscript 在腳本其他內容前執行

【Android 修鍊手冊】系列內容 每周更新 歡迎關注下面賬號,獲取更新:

微信搜索公眾號: ZYLABGithub知乎ZY5A59 的個人主頁 - 掘金
推薦閱讀:
相关文章