【Android 修煉手冊】Gradle 篇 -- Gradle 源碼分析

預備知識

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

看完本文能夠達到什麼程度

  1. 瞭解 gradle 的實現原理

閱讀前準備工做

  1. clone EasyGradle 項目
  2. 下載 Gradle 源碼 做爲參考

讀代碼的姿式

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

目錄

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

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

1、Gradle 的啓動

1.1 總體實現圖

start

1.2 具體分析

咱們執行一個構建任務的時候,都是執行 ./gradlew assembleDebug 這樣的命令,其中的 gradlew 腳本就是整個 gradle 構建的入口,咱們先從這裏看起。
前面的代碼基本上就是判斷環境,設置變量的,直接看最後一行:java

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

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

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 函數:git

// 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 裏看一下:github

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

這裏就作了兩件事:shell

  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();
}
複製代碼

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

2、loadSettings

2.1 總體實現圖

loadSettings

2.2 具體分析

loadSettings 主要是加載 settings.gradle 文件,而後建立對應的 project。api

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

        buildOperationExecutor.run(new LoadBuild());

        stage = Stage.Load;
    }
}
複製代碼

總體構建流程:緩存

2.2.1 調用 BuildListener.buildStarted() 回調接口

通知構建開始。這個就是咱們以前在 Gradle 基本使用 裏說的生命週期回調。bash

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 文件而後建立了項目實例。

3、configureBuild

3.1 總體實現圖

configureBuild

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

4、constructTaskGraph

4.1 總體實現圖

constructTaskGraph

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 圖

5、runTasks

5.1 總體實現圖

runTask

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

6、finishBuild

6.1 總體實現圖

finishBuild

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

7、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 以下:

    script-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')
    }
}
複製代碼

8、插件調用流程

以前在 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() 函數了

9、總結

總體結構圖

gradle-source-all

  1. gradle 運行流程
loadSettings
configureBuild
constructTaskGraph
runTasks
finishBuild
複製代碼
  1. Task 的本質,就是一系列的 Actions
  2. 腳本編譯流程 獲取腳本內容 -> 編譯成 class 文件,繼承自 ProjectScript -> 執行 ProjectScript.run 方法
  3. 腳本的 buildscript 在腳本其餘內容前執行

關於我

about
相關文章
相關標籤/搜索