Gradle 执行流程

前言

本文主要梳理下 Gradle 的执行顺序及实现原理,加深对 Gradle 的理解。

开始

从常用命令着手,一般命令行构建打包 App 都会执行以下命令:

./gradlew app:assembleDebug

那么具体是怎么执行的呢?查看下 gradlew 的源码,发现其中包含以下代码。

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

其实就是执行 gradle-wrapper.jar 中的 GradleWrapperMain#main。

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

首先执行 install.createDist 方法读取 gradle/wrapper/gradle-wrapper.properties 中相关配置,检查本地是否已经存在对应版本 Gradle 的 zip 包,如不存在则下载,然后进行解压;

# gradle-wrapper.properties
# 解压后根目录
distributionBase=GRADLE_USER_HOME
# 下载地址
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
# 解压后子目录
distributionPath=wrapper/dists
# 下载 zip 包存放子目录
zipStorePath=wrapper/dists
# 下载 zip 包存放根目录
zipStoreBase=GRADLE_USER_HOME

注:根目录如果是 GRADLE_USER_HOME 那么实际使用的为 ~/.gradle ,因此下载的 Gradle 一般保存在 ~/.gradle/wrapper/dists/ 下。截屏2021-02-11 下午2.37.10

当确认本地存在 Gradle 以后就可以调用 bootstrapMainStarter.start 执行 Gradle 构建流程。

// BootstrapMainStarter
public void start(String[] args, File gradleHome) throws Exception {
URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{findLauncherJar(gradleHome).toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
Thread.currentThread().setContextClassLoader(contextClassLoader);
contextClassLoader.loadClass("org.gradle.launcher.GradleMain").getMethod("main", new Class[]{String[].class}).invoke((Object) null, new Object[]{args});
if (contextClassLoader instanceof Closeable) {
contextClassLoader.close();
}
}

通过自定义 ClassLoader 加载 gradle-launcher-5.6.4.jar 中的 GradleMain 类,并反射调用其 main 方法。后续就是开启 Deamon 进程,然后通过 Socket 进行连接,并提交构建请求,这块就不展开了,最终会调用 DefaultGradleLauncher 的 executeTasks 方法。

public GradleInternal executeTasks() {
doBuildStages(Stage.RunTasks);
return gradle;
}
private void doBuildStages(Stage upTo) {
doClassicBuildStages(upTo);
}
private void doClassicBuildStages(Stage upTo) {
prepareSettings();
prepareProjects();
prepareTaskExecution();
instantExecution.saveTaskGraph();
runWork();
}

很明显构建项目一共需要进行以下五个步骤

  1. prepareSettings。
  2. prepareProjects。
  3. prepareTaskExecution。
  4. runWork。

下面一个个进行分析

prepareSettings

在这一阶段,主要工作为

  1. 寻找编译执行 init 脚本。
  2. 寻找 settings 脚本。
  3. 编译 buildSrc 模块。
  4. 解析 gradle.properties。
  5. 编译执行 settings.gradle。
private void prepareSettings() {
if (stage == null) {
buildListener.buildStarted(gradle);
settingsPreparer.prepareSettings(gradle);
stage = Stage.LoadSettings;
}
}

buildListener.buildStarted

用于通知外界构建开始,不过这时候外界不可能设置 buildListener,所以外界是无法接收到该事件的。

settingsPreparer.prepareSettings

注:settingPreparer 为 DefaultSettingPreparer 实例。

public void prepareSettings(GradleInternal gradle) {
initScriptHandler.executeScripts(gradle);
SettingsLoader settingsLoader = gradle.getParent() != null ? settingsLoaderFactory.forNestedBuild() : settingsLoaderFactory.forTopLevelBuild();
settingsLoader.findAndLoadSettings(gradle);
}

initScriptHandler.executeScripts

注:initScriptHandler 为 InitScriptHandler 实例。

public void executeScripts(final GradleInternal gradle) {
final List<File> initScripts = gradle.getStartParameter().getAllInitScripts();
if (initScripts.isEmpty()) {
return;
}
buildOperationExecutor.run(new RunnableBuildOperation() {
public void run(BuildOperationContext context) {
BasicTextResourceLoader resourceLoader = new BasicTextResourceLoader();
for (File script : initScripts) {
TextResource resource = resourceLoader.loadFile("initialization script", script);
processor.process(new TextResourceScriptSource(resource), gradle);
}
}
public BuildOperationDescriptor.Builder description() {
return BuildOperationDescriptor.displayName("Run init scripts").progressDisplayName("Running init scripts");
}
});
}

方法内部首先寻找到所有配置的 init 脚本,接着对其进行编译并执行。

init 脚本存放位置为以下几个地方:

  1. ~/.gradle/init.gradle 或者 ~/.gradle/init.gradle.kts 文件。

  2. ~/gradle/init.d/ 目录下所有后缀为 gradle 或者 gradle.kts 的文件。

  3. {gradleHome}/init.d/ 目录下所有后缀为 gradle 或者 gradle.kts 的文件。

  4. 命令行带 –init-script 明确指定。

  5. 调用 gradle.startParameter.addInitScript 进行指定。

注:gradleHome 为 Gradle 执行路径,在我电脑上为

/Users/hefuwei/.gradle/wrapper/dists/gradle-5.6.4-all/ankdp27end7byghfw1q2sw75f/gradle-5.6.4

至于如何编译执行 init 脚本暂时先忽略,我们可以自己编写 init 脚本,在每次构建前做一些初始化操作,可做的内容可以参考这篇文章

settingsLoader.findAndLoadSettings

方法内部主要工作为:

  1. 寻找 settings 脚本。
  2. 构建 buildSrc 模块。
  3. 解析 gradle.properties 文件。
  4. 编译执行 settings 脚本。
寻找 settings 脚本

方法调用链

CompositeBuildSettingsLoader.findAndLoadSettings -> ChildBuildRegisteringSettingsLoader.findAndLoadSettings ->
CommandLineIncludedBuildSettingsLoader.findAndLoadSettings ->
SettingsAttachingSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findSettingsAndLoadIfAppropriate ->
DefaultSettingsFinder.find ->
BuildLayoutFactory.getLayoutFor

最终调用到 BuildLayoutFactory.getLayoutFor 方法。

public BuildLayout getLayoutFor(BuildLayoutConfiguration configuration) {
if (configuration.isUseEmptySettings()) {
return buildLayoutFrom(configuration, 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 buildLayoutFrom(configuration, explicitSettingsFile);
}
File currentDir = configuration.getCurrentDir();
boolean searchUpwards = configuration.isSearchUpwards();
return getLayoutFor(currentDir, searchUpwards ? null : currentDir.getParentFile());
}

寻找 settings 脚本逻辑如下

  • 如果明确指定了 settings 脚本,那么就使用该脚本(命令行带 –settings-file 明确指定,或者调用 gradle.startParameter.setSettingsFile 进行指定)。
  • 在当前目录(项目目录)下查找 settings 脚本,如果存在就使用该脚本。
  • 当前目录不存在,并且允许向上搜索(默认允许),那么在其父目录下搜索,如存在就使用该脚本。
  • 父目录下还是不存在,那么继续向上直到根目录。
  • 如果上述都不存在,那么还是取当前目录下的 settings 脚本(虽然不存在)。

settings 脚本寻找结束后,并没有立即进行编译执行

构建 buildSrc 模块

调用链

CompositeBuildSettingsLoader.findAndLoadSettings -> ChildBuildRegisteringSettingsLoader.findAndLoadSettings ->
CommandLineIncludedBuildSettingsLoader.findAndLoadSettings ->
SettingsAttachingSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findSettingsAndLoadIfAppropriate

在 settings 脚本寻找到后会继续执行 DefaultSettingsLoader.findSettingsAndLoadIfAppropriate 方法

private SettingsInternal findSettingsAndLoadIfAppropriate(GradleInternal gradle,
StartParameter startParameter) {
SettingsLocation settingsLocation = findSettings(startParameter);
// We found the desired settings file, now build the associated buildSrc before loading settings. This allows
// the settings script to reference classes in the buildSrc.
ClassLoaderScope buildSourceClassLoaderScope = buildSourceBuilder.buildAndCreateClassLoader(settingsLocation.getSettingsDir(), startParameter);
return settingsProcessor.process(gradle, settingsLocation, buildSourceClassLoaderScope, startParameter);
}

代码中注释也说了,查找到 setting 脚本后会在其编译执行前先去编译 buildSrc 模块),这么做的目的是为了在 settings 脚本中可以引用 buildSrc 中的内容。

解析 gradle.properties 文件

调用链

CompositeBuildSettingsLoader.findAndLoadSettings -> ChildBuildRegisteringSettingsLoader.findAndLoadSettings ->
CommandLineIncludedBuildSettingsLoader.findAndLoadSettings ->
SettingsAttachingSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> BuildOperationSettingsProcessor.process -> RootBuildCacheControllerSettingsProcessor.process -> PropertiesLoadingSettingsProcessor.process

最终会调用到 PropertiesLoadingSettingsProcessor.process 来解析 gradle.properties。

public SettingsInternal process(GradleInternal gradle,
SettingsLocation settingsLocation,
ClassLoaderScope buildRootClassLoaderScope,
StartParameter startParameter) {
propertiesLoader.loadProperties(settingsLocation.getSettingsDir());
return processor.process(gradle, settingsLocation, buildRootClassLoaderScope, startParameter);
}

方法内部会读取 gradle.properties 文件里的配置,系统配置,环境变量,以及命令行传入的配置并存储。

gradle.properties 存放位置为以下几个地方:

  1. ~/.gradle/gradle.properties。
  2. {settingsDir}/gradle.properties。
  3. {gradleHome}/gradle.properties。

上述三个文件都会进行读取,下面的优先级较高,可以覆盖上面的。

执行 settings.gradle 文件

调用链

CompositeBuildSettingsLoader.findAndLoadSettings -> ChildBuildRegisteringSettingsLoader.findAndLoadSettings ->
CommandLineIncludedBuildSettingsLoader.findAndLoadSettings ->
SettingsAttachingSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findAndLoadSettings ->
DefaultSettingsLoader.findSettingsAndLoadIfAppropriate -> BuildOperationSettingsProcessor.process -> RootBuildCacheControllerSettingsProcessor.process -> PropertiesLoadingSettingsProcessor.process -> ScriptEvaluatingSettingsProcessor.process

最终调用到 ScriptEvaluatingSettingsProcessor.process 来合并参数以及编译执行 settings 脚本。

public SettingsInternal process(GradleInternal gradle,
SettingsLocation settingsLocation,
ClassLoaderScope buildRootClassLoaderScope,
StartParameter startParameter) {
Map<String, String> properties = propertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
TextResourceScriptSource settingsScript = new TextResourceScriptSource(textResourceLoader.loadFile("settings file", settingsLocation.getSettingsFile()));
SettingsInternal settings = settingsFactory.createSettings(gradle, settingsLocation.getSettingsDir(), settingsScript, properties, startParameter, buildRootClassLoaderScope);
applySettingsScript(settingsScript, settings);
return settings;
}

编译执行和 init 脚本一样先忽略。

prepareProjects

在这一阶段,主要工作为:

  1. 创建 project 以及 subProject。
  2. 配置每个 project(编译执行 build 脚本)。
private void prepareProjects() {
if (stage == Stage.LoadSettings) {
projectsPreparer.prepareProjects(gradle);
stage = Stage.Configure;
}
}
public void prepareProjects(GradleInternal gradle) {
buildOperationExecutor.run(new ConfigureBuild(gradle));
}
// DefaultProjectsPreparer(间接会被调用)
public void prepareProjects(GradleInternal gradle) {
buildLoader.load(gradle.getSettings(), gradle);
if (gradle.getParent() == null) {
buildRegistry.beforeConfigureRootBuild();
}
if (gradle.getStartParameter().isConfigureOnDemand()) {
projectConfigurer.configure(gradle.getRootProject());
} else {
projectConfigurer.configureHierarchy(gradle.getRootProject());
new ProjectsEvaluatedNotifier(buildOperationExecutor).notify(gradle);
}
modelConfigurationListener.onConfigure(gradle);
}

buildLoader.load

注:buildLoader 为 NotifyingBuildLoader 实例。

创建所有项目

调用链

DefaultProjectsPreparer.prepareProjects -> NotifyingBuildLoader.load -> ProjectPropertySettingBuildLoader.load -> InstantiatingBuildLoader.load

最终会调用 InstantiatingBuildLoader.load 来创建 project 。

public void load(SettingsInternal settings, GradleInternal gradle) {
load(settings.getRootProject(), settings.getDefaultProject(), gradle, settings.getRootClassLoaderScope());
}
private void load(ProjectDescriptor rootProjectDescriptor, ProjectDescriptor defaultProject, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
createProjects(rootProjectDescriptor, gradle, buildRootClassLoaderScope);
attachDefaultProject(defaultProject, gradle);
}
private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle, buildRootClassLoaderScope.createChild("root-project"), buildRootClassLoaderScope);
gradle.setRootProject(rootProject);
addProjects(rootProject, rootProjectDescriptor, gradle, buildRootClassLoaderScope);
}

方法内部首先创建了 rootProject,然后创建相应的 subProject ,那么 Gradle 是如何知道要创建哪些 project 的呢?这是因为前面已经执行过了 settings 脚本,而在其内部会调用 include xxx 来指定哪些模块要参与构建。因此如果新建一个模块,并且要参与构建,一定要在 settings 脚本中进行指定

projectConfigurer.configureHierarchy

注:projectConfigurer 为 TaskPathProjectEvaluator 实例。

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

方法内部首先配置根项目然后配置所有的子项目,最终会调用到 EvaluateProject.run 方法。

public void run(final BuildOperationContext context) {
project.getMutationState().withMutableState(new Runnable() {
public void run() {
try {
state.toBeforeEvaluate();
buildOperationExecutor.run(new NotifyBeforeEvaluate(project, state)); // 1
if (!state.hasFailure()) {
state.toEvaluate();
try {
delegate.evaluate(project, state); // 2
} catch (Exception e) {
addConfigurationFailure(project, state, e, context);
} finally {
state.toAfterEvaluate();
buildOperationExecutor.run(new NotifyAfterEvaluate(project, state)); // 3
}
}
if (state.hasFailure()) {
state.rethrowFailure();
} else {
context.setResult(ConfigureProjectBuildOperationType.RESULT);
}
} finally {
state.configured();
}
}
});
}

projectEvaluationListener.beforeEvaluate

通知外界当前项目配置开始,对应注释一。

配置当前项目

开始配置当前项目,对应注释二。

配置流程为:

  1. 应用 org.gradle.help-tasks 插件。
  2. 应用 org.gradle.build-init 插件。
  3. 应用 org.gradle.wrapper 插件。
  4. 编译执行 build 脚本。

projectEvaluationListener.afterEvaluate

通知外界当前项目配置结束,对应注释三。

prepareTaskExecution

在这一阶段,主要工作为寻找要执行的 Task 以及处理依赖最终填充 TaskGraph。

private void prepareTaskExecution() {
if (stage == Stage.Configure) {
taskExecutionPreparer.prepareForTaskExecution(gradle);
stage = Stage.TaskGraph;
}
}
public void prepareForTaskExecution(GradleInternal gradle) {
buildOperationExecutor.run(new CalculateTaskGraph(gradle));
}
public void run(BuildOperationContext buildOperationContext) {
final TaskExecutionGraphInternal taskGraph = populateTaskGraph(); // 1
...
}

在注释一处调用了 populateTaskGraph 用于填充 TaskGraph。

TaskExecutionGraphInternal populateTaskGraph() {          
BuildOperatingFiringTaskExecutionPreparer.this
.delegate.prepareForTaskExecution(this.gradle);
return this.gradle.getTaskGraph();
}
// DefaultTaskExecutionPreparer
public void prepareForTaskExecution(GradleInternal gradle) {
this.buildConfigurationActionExecuter.select(gradle); // 1
TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
taskGraph.populate(); // 2
this.includedBuildControllers.populateTaskGraphs();
if (gradle.getStartParameter().isConfigureOnDemand()) {
(new ProjectsEvaluatedNotifier(this.buildOperationExecutor)).notify(gradle);
}
}

注释一处会顺序执行

  1. ExcludedTaskFilteringBuildConfigurationAction.configure 方法。

    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();
    }

    方法内部先获取了被排除的 Tasks 名称,并依次把它们放入 filters 中,接着调用 useFilter 通知 TaskGraph 过滤掉这些 Task。在日常开发过程中,我们也可以根据需求适当的过滤掉一些 Task,从而加快构建速度。比如不想执行 lint ,那么就可以在 settings.gradle 中添加以下代码。

    gradle.startParameter.excludedTaskNames.add("lint")
  2. DefaultTasksBuildExecutionAction.configure 方法。

    public void configure(BuildExecutionContext context) {
    StartParameter startParameter = context.getGradle().getStartParameter();
    for (TaskExecutionRequest request : startParameter.getTaskRequests()) {
    if (!request.getArgs().isEmpty()) {
    context.proceed();
    return;
    }
    }
    ProjectInternal project = context.getGradle().getDefaultProject();
    projectConfigurer.configure(project);
    List<String> defaultTasks = project.getDefaultTasks();
    startParameter.setTaskNames(defaultTasks);
    context.proceed();
    }

    方法内部先判断了是否明确指定了将要执行的 Task,如果明确指定了那么啥也不干,如果没有指定那么添加默认 Task。

  3. TaskNameResolvingBuildConfigurationAction.configure 方法。

    public void configure(BuildExecutionContext context) {
    GradleInternal gradle = context.getGradle();
    TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
    List<TaskExecutionRequest> taskParameters = gradle.getStartParameter().getTaskRequests();
    for (TaskExecutionRequest taskParameter : taskParameters) {
    List<TaskSelector.TaskSelection> taskSelections = commandLineTaskParser.parseTasks(taskParameter);
    for (TaskSelector.TaskSelection taskSelection : taskSelections) {
    taskGraph.addEntryTasks(taskSelection.getTasks());
    }
    }
    context.proceed();
    }

    方法内部会判断是否明确指定了执行哪个项目的 Task,如 :app:assembleDebug ,如果指定了那么就会直接选中该 Task,如果没有指定那么会把所有项目下满足条件的 Task 选出来。然后遍历并添加所有已选择的 Tasks 到 taskGraph 中,内部会处理 dependson finalizedby mustrunafter shouldrunafter 等 Tasks 间的依赖关系,并把信息保存在 TaskInfo 之中。

注释二处调用 TaskGraph.populate 填充 TaskGraph(注释一已经把需要执行的 Task 及相关依赖处理了)。

runWork

这一阶段主要工作为执行 Task。

private void runWork() {
if (stage != Stage.TaskGraph) {
throw new IllegalStateException("Cannot execute tasks: current stage = " + stage);
}
List<Throwable> taskFailures = new ArrayList<Throwable>();
buildExecuter.execute(gradle, taskFailures);
if (!taskFailures.isEmpty()) {
throw new MultipleBuildFailures(taskFailures);
}
stage = Stage.RunTasks;
}

接着调用 buildExecuter.execute 方法 。注:buildExecuter 为 DefaultBuildWorkExecutor 实例。

private void execute(final GradleInternal gradle, final int index, final Collection<? super Throwable> taskFailures) {
if (index >= executionActions.size()) {
return;
}
executionActions.get(index).execute(new BuildExecutionContext() {
public GradleInternal getGradle() {
return gradle;
}
public void proceed() {
execute(gradle, index + 1, taskFailures);
}
}, taskFailures);
}

这里 executionActions 里面有两项会依次执行:

  1. DryRunBuildExecutionAction.execute。

    public void execute(BuildExecutionContext context, Collection<? super Throwable> taskFailures) {
    GradleInternal gradle = context.getGradle();
    if (gradle.getStartParameter().isDryRun()) {
    for (Task task : gradle.getTaskGraph().getAllTasks()) {
    textOutputFactory.create(DryRunBuildExecutionAction.class)
    .append(((TaskInternal) task).getIdentityPath().getPath())
    .append(" ")
    .style(StyledTextOutput.Style.ProgressStatus)
    .append("SKIPPED")
    .println();
    }
    } else {
    context.proceed();
    }
    }

    如果命令行带有 –dry-run 标记,则会跳过所有 Task 执行。如果我们只要看下哪些 Task 会被执行,那么就可以在 settings.gradle 中添加以下代码。

    gradle.startParameter.setIsDryRun(true)
  2. SelectedTaskExecutionAction.execute。

    public void execute(BuildExecutionContext context, Collection<? super Throwable> taskFailures) {
    GradleInternal gradle = context.getGradle();
    TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
    if (gradle.getStartParameter().isContinueOnFailure()) {
    taskGraph.setContinueOnFailure(true);
    }
    taskGraph.addTaskExecutionGraphListener(new BindAllReferencesOfProjectsToExecuteListener());
    taskGraph.execute(taskFailures);
    }

    在该方法内部会去真正执行 Task,不过在执行前还会进行校验。比如会跳过无 action 的 Task 以及 update-to-date 的 Task。本质上执行 Task 就是执行其内部的 Action,这可以通过 doFirst、doLast 进行添加。

总结

Gradle 完整执行流程如下:

  1. 确保本地存在 Gradle ,如无则进行下载。
  2. 寻找 init 脚本,编译并执行。
  3. 寻找 settings 脚本。
  4. 构建 buildSrc 模块。
  5. 解析 gradle.properties 文件。
  6. 编译并执行 settings 脚本。
  7. 创建 project 以及 subProject。
  8. 编译并执行每个项目下的 build 脚本。
  9. 构建填充 task 有向无环图。
  10. 执行 task。

参考:

  1. 【Android 修炼手册】Gradle 篇 – Gradle 源码分析
  2. 深度探索 Gradle 自动化构建技术
0%