Preliminary knowledge

  1. Understand the basic development of Gradle
  2. Understand gradle Task and Plugin usage and development
  3. Understand the use of android Gradle Plugin

How far can I go after reading this article

  1. Understand how Gradle works

Prepare before reading

  1. Clone EasyGradle project
  2. Download Gradle source code for your reference

Reading code posture

  1. Call link, easy to read code control
  2. Focus on the overall framework and leave out the details

directory

This paper is mainly analyzed from the following parts

  1. Gradle startup
  2. loadSettings
  3. configureBuild
  4. constructTaskGraph
  5. runTasks
  6. finishBuild
  7. How do Gradle scripts compile and execute
  8. Plug-in call flow

Start Gradle

1.1 Overall realization diagram

1.2 Specific Analysis

Gradlew assembleDebug./gradlew assembleDebug. Gradlew script is the entry point to a gradle build. The previous code is basically to determine the environment, set variables, directly look at the last line:

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

The final command is basically as follows:

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

As you can see, basically is to perform the gradle/wrapper/gradle – wrapper. Org in the jar. The gradle. Wrapper. GradleWrapperMain, so that we know, Gradle entry class is org. Gradle. Wrapper. GradleWrapperMain, also just know the code to where to start. The main function of GradleWrapperMain:

// 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());
}
Copy the code

Important class has two org. Gradle. Wrapper. WrapperExecutor and 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);
}
Copy the code

Here are two things:

  1. Download the dependencies and source code needed for gradle Wrapper. The gradle wrapper version is the distributionUrl we configured in gradle/wrapper/gradle-wrapper.properties. The download location is the distributionPath and zipStorePath configured in gradle-wrapper.properties. ZipStorePath download package, distributionPath is unzipped position, generally the default position is the HOME /. Gradle/wrapper/dists /, here you can find gradle wrapper. If you create a number of projects, we in the HOME /. Gradle/wrapper/dists/can see different versions of gradle wrapper, it also illustrates the we initially said before, Gradle Wrapper is used instead of installing Gradle wrapper directly on your computer because gradle Wrapper downloads different versions of content for different projects that do not affect each other.

  2. Execute the Gradle build process. Here follow the BootstrapMainStarter. Start () to perform, just don’t see the intermediate process, more twists and turns, help to understand the overall process also no too big. Will eventually run to DefaultGradleLauncher. ExecuteTasks (), then down the process is very clear.

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

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

Basically, the build process is a five-step process. Here are five steps.

Second, the loadSettings

2.1 Overall realization diagram

2.2 Specific Analysis

LoadSettings basically loads settings.gradle file and creates the corresponding project.

// DefaultGradleLauncher.loadSettings private void loadSettings() { if (stage == null) { buildListener.buildStarted(gradle); buildOperationExecutor.run(new LoadBuild()); stage = Stage.Load; }}Copy the code

Overall construction process:

2.2.1 call BuildListener. BuildStarted () callback interface

Notify the build to begin. This is the life cycle callback we talked about in the basic use of Gradle.

2.2.2 Executing the Init Script

Call link

LoadBuild.run -> InitScriptHandler.executeScripts
Copy the code

Gradle is called before each project build to do some initialization. This is where it is called.

2.2.3 Find settings.gradle location

Call link

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

In getLayoutFor, look for settings.gradle as follows:

  1. Gradle file if you specify the location of settings.gradle file with -c xxx.gradle, use the specified settings.gradle file

  2. If the Settings file is not specified, look it up in the current directory

  3. If the current directory does not exist, the system searches the upper-level directory and the maseter/ directory of the same level

  4. If none is found, the default directory is still in the current directory

    // 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());
    Copy the code

    }

2.2.4 Compile content in the buildSrc folder, which can be seen as plugin-like functionality

Call link

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

After finding settings.gradle in the previous step, we will find the buildSrc directory in settings.gradle’s parent directory and compile it. This ensures that you can reference the buildSrc directory when building settings.gradle.

2.2.5 parsing gradle. Properites

Call link

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

The implementation analysis step reads and stores the configuration, system configuration, environment variables, and the configuration passed in from the command line from the gradle.properties file.

// 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());
}
Copy the code

2.2.6 resolution Settings. Gradle

Call link

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

Implementation analysis in ScriptEvaluatingSettingsProcessor, created the first SettingsInternal instance, and ScriptSource instance, for Settings. Gradle file in memory mapping, Then call BuildOperationScriptPlugin. Apply to perform Settings. Gradle file. About BuildOperationScriptPlugin. Apply, behind us in detail, because in the parsing build. Gradle file is also using this method. Here is the corresponding code:

// 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);
}
Copy the code

2.2.7 Creating a Project and subProject

Call link

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

After you parse settings.gradle, you know what projects are in your project and can create project instances.

/ / InstantiatingBuildLoader / / incoming parameters corresponding here is: 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) {/ / / / create a main Project instance ProjectInternal inherited from the Project, Eventually return rootProject is DefaultProject type ProjectInternal rootProject = projectFactory. The 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) {/ / create instances for components (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) {// Get the build.gradle File buildFile = for project projectDescriptor.getBuildFile(); ScriptSource source = UriScriptSource.file("build file", buildFile); / / create a project instance DefaultProject project = instantiator. NewInstance (DefaultProject. Class, projectDescriptor getName (), parent, projectDescriptor.getProjectDir(), source, gradle, gradle.getServiceRegistryFactory(), selfClassLoaderScope, baseClassLoaderScope ); // Set the hierarchy of project if (parent! = null) { parent.addChildProject(project); } / / registered project projectRegistry. AddProject (project); return project; }Copy the code

Here, the project instance is created based on Settings. Gradle configuration. When you create a child project, if the parent project is not empty, set yourself as a child project of the parent project, so that you can get the child projects of the project through project.getChildProjects. This is where the Project property, which is often used when writing Gradle scripts, is created.

At this point, the settings.gradle file is parsed and the project instance is created.

Third, configureBuild

3.1 Overall realization diagram

3.2 Specific Analysis

As mentioned earlier, the Gradle build process is divided into the configuration stage and the run stage. The configuration stage is mainly about executing scripts, and the run stage is about executing tasks. Here is the configuration stage. Note that the previous configuration and operation phase, from the overall view of the two stages, from the source code to understand, is this article introduced several stages, to a little more detailed. The configuration phase is simple: compile gradle scripts into class files and run them. (Gradle is written in Groovy, which is a JVM language, so groovy must be compiled into class to run.)

// DefaultGradleLauncher private void configureBuild() { if (stage == Stage.Load) { buildOperationExecutor.run(new ConfigureBuild()); stage = Stage.Configure; }}Copy the code

If configure-on-demand is specified during project configuration, only the main project and projects required by task execution will be configured. If this parameter is not specified by default, all projects will be configured.

3.2.1 Configure Main Links for the Main Project and its subprojects

Call link

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

Implementation analysis

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

Eventually executed by LifecycleProjectEvaluator doConfigure

3.2.2 callback BuildListener beforeEvaluate interface

This is where the beforeEvaluate interface is called back to inform you that configuration is about to begin. We now know the stage of the callback execution.

3.2.3 Setting Default Tasks and plug-ins

Call link

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

In PluginsProjectConfigureActions implementation analysis, two will be added to the project task: init and wrapper, then add help plug-in: org. Gradle. Help – the tasks.

3.2.4 Compiling the Script and Executing it

Call link

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

Realize the analysis here call or BuildOperationScriptPlugin. Apply to compile and execute the build. Gradle script, and front resolution Settings. Gradle is same, Build. Gradle to class. File and execute, and then move on to the process before detailing how the script is compiled and executed.

3.2.5 callback BuildListener afterEvaluate

3.2.6 callback BuildListener projectsEvaluated

Four, constructTaskGraph

4.1 Overall realization diagram

4.2 Specific Analysis

This step is to build the Task dependency diagram

// DefaultGradleLauncher private void constructTaskGraph() { if (stage == Stage.Configure) { buildOperationExecutor.run(new CalculateTaskGraph()); stage = Stage.TaskGraph; }}Copy the code

4.2.1 Handling Tasks to be removed

Call link

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> ExcludedTaskFilteringBuildConfigurationAction.configure
Copy the code

Implementation analysis

// 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(); }Copy the code

This step is used to process tasks that need to be excluded, that is, tasks specified by -x or –exclude-task on the command line.

4.2.2 Adding a Default Task

Call link

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> DefaultTasksBuildExecutionAction.configure
Copy the code

Implementation analysis checks the command line to see if a Task name is passed in, and does nothing if a Task is specified. If not specified, check whether the project has a default task. DefaultTasks can be specified in build.gradle using defaultTasks. If the default task also does not exist, set the task to help task and print gradle’s help content.

4.2.3 Calculating the Task dependency graph

Call link

CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure
Copy the code

Implementation analysis

  1. Filter tasks based on taskname on the command line. If our task specifies project, like app:assembleDebug, then the task is directly selected. If no specific project is specified, All tasks under project that match the taskname will be filtered.

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

  2. Dependson Finalizedby MustrunAfter shouldrunAfter add a task to the taskGraph. And then the information is stored in the org. Gradle. Execution. Taskgraph. TaskInfo.

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

4.2.4 Generating Task Graph

Call link

CalculateTaskGraph.run -> TaskGraphExecuter.populate -> DefaultTaskExecutionPlan.determineExecutionPlan
Copy the code

Implementation analysis Generates a Task graph based on the tasks calculated in the previous step and their dependencies

Fifth, runTasks

5.1 Overall realization diagram

5.2 Specific Analysis

Once the task graph is generated, the task is executed

5.2.1 Handling dry Run

Call link

DefaultBuildExecuter.execute -> DryRunBuildExecutionAction.execute
Copy the code

Implementation analysis If –dry-run is specified on the command line, the execution of the task is intercepted, and the task name and execution sequence are printed directly.

5.2.2 Creating a Thread and Executing tasks

Call link

DefaultBuildExecuter.execute -> SelectedTaskExecutionAction.execute -> DefaultTaskPlanExecutor.process
Copy the code

Implementation analysis creates a TaskExecutorWorker to execute tasks, with eight threads by default.

// 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(); }}Copy the code

5.2.3 Task Processing before execution

Call link

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

This is where the implementation analysis officially begins the task execution process. There are several steps:

  1. The callback TaskExecutionListener beforeExecute

  2. Perform a series of tasks as follows:

    CatchExceptionTaskExecuter. Execute / / added try catch, To prevent abnormal ExecuteAtMostOnceTaskExecuter. During the implementation of the execute / / whether the task execution SkipOnlyIfTaskExecuter. Execute / / onlyif judgment task Whether conditions satisfy SkipTaskWithNoActionsExecuter. Execute / / skip without action task, No action that does not need to be performed the task ResolveTaskArtifactStateTaskExecuter. Execute / / set the state of an artifact SkipEmptySourceFilesTaskExecuter. Execute / / skip set the source file but the source file is empty task, The source file is empty ValidatingTaskExecuter task there is no need to deal with resources. The execute () / / confirm whether the task can be performed ResolveTaskOutputCachingStateExecuter. Execute / / processing task output caching SkipUpToDateTaskExecuter. Execute / / skip the update – to – date The task of ExecuteActionsTaskExecuter. Execute / / real task execution

5.2.4 task execution

Call link

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

Implementation analysis after the previous steps, it’s time to actually execute the task.

  1. The callback TaskActionListener beforeActions

  2. The callback OutputsGenerationListener beforeTaskOutputsGenerated

  3. Take the Actions from the task and execute them all

    // ExecuteActionsTaskExecuter private GradleException executeActions(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) { final List actions = new ArrayList(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; }

As you can see, the essence of a Task is to execute its Actions. For example, when we are customizing tasks, we often use the following:

Task {doLast {// task}}Copy the code

DoLast is the equivalent of adding an Action to a Task. Take a look at the AbstractTask doLast method

// 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); }Copy the code

As you can see, the closure we passed in ends up being wrapped as TaskActionWrapper and added to the Task’s actions.

  1. The callback TaskActionListener afterActions
  2. The callback TaskExecutionListener afterExecute

Six, finishBuild

6.1 Overall implementation diagram

6.2 Specific Analysis

private void finishBuild(BuildResult result) { if (stage == Stage.Finished) { return; } buildListener.buildFinished(result); if (! isNestedBuild()) { gradle.getServices().get(IncludedBuildControllers.class).stopTaskExecution(); } stage = Stage.Finished; }Copy the code

The logic here is not much, the callback BuildListener. BuildFinished interface

Gradle: Gradle: gradle: Gradle

  1. Parse settings.gradle and execute to generate a Project instance
  2. Parse build.gradle and execute
  3. Generate task dependency diagrams
  4. To perform a task

How do Gradle scripts compile and execute

Introduce loadsettings and configureBuild phase in front, we mentioned BuildOperationScriptPlugin. Apply this method, simply, is used to compile gradle script and execute, So let’s do a little analysis here.

7.1 Compiling scripts

Call link

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

Implementation analysis Here the compilation process is divided into two parts, first compiling the buildScript {} part of the script, ignoring the rest, and then compiling and executing the rest of the script. So the stuff in buildScript {} is executed before anything else.

  1. If there is a cache, use it directly. If there is no cache, compile it again
  2. Will call to CompileToCrossBuildCacheAction. Execute pileToDir – > – > DefaultScriptCompilationHandler.com DefaultScriptCompilationHandler.com to perform real compile operation pileScript script cache path: / Users/zy /. Gradle/caches / 4.1 / scripts – remapped/build_a3v29m9cbrge95ug6eejz9wuw / 31 f5shvfkfunwn5ullupyy7xt/cp_proj4dada642 4967 ba8dfea75e81c8880f7f/classes directory class is as follows:

3. Detailed compilation method is through the RemappingScriptSource. GetResource (). The getText () get to the content of the script, and then through GroovyClassLoader. ParseClass compilation. Let’s use app/build.gradle as an example to see what the resulting script looks like.

Build. gradle script content

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. The constraint, the constraint - layout: 1.1.3' implementation project (' : module1 ')} Gradle.addbuildlistener (new BuildListener() {@override void buildStarted(gradle gradle) {// println(' buildStarted ')} @override void settingsEvaluated(Settings Settings) {// println(' Settings file parsed ')} @override void ProjectsLoaded (Gradle Gradle) {// println(' Project loaded complete ')} @override void projectsEvaluated(Gradle Gradle) {// Override void buildFinished(BuildResult result) {// println(' BuildResult result ')}} gradle.addProjectEvaluationListener(new ProjectEvaluationListener() { @Override void beforeEvaluate(Project project) { // println("${project.name} before the project is configured ")} @override void afterEvaluate(project project, ProjectState state) {/ / println (" ${project. The name} project configuration called after ")}}) gradle. TaskGraph. WhenReady {/ / println (" task graph to build complete ") } gradle. TaskGraph. BeforeTask {/ / println (" task completes ")} gradle. TaskGraph. AfterTask {/ / println (" task completes ")} task task1 { doLast { println('task2') } } task task2 { doLast { println('task2') } } task1.finalizedBy(task2)Copy the code

Compile the class content

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[]{"", ""})));
    }
}
Copy the code

As you can see, the script class inherits from ProjectScript and implements the Run method. So what’s going on in the run method, if I look at the first line,

CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
Copy the code

Get the callsiteArray that was assigned to the createCallSiteArray_1() method. As you can see, the callsiteArray is a DSL in the script, which is actually the name of the called method. After getting the callsiteArray, call a method like $getCallSiteArray[0].callCurrent(). The script code corresponding to the invoked method is commented below.

public Object run() { CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray(); $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)); }Copy the code

Task1 corresponds to the _run_closure6 class. Let’s take a look at the contents of this class.

/* 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())); }}Copy the code

As you can see, the Closure class inherits Closure and then implements the doCall method, which calls the doLast method and passes in an instance of _closure17. This is the implementation of task {doLast {}} in the script. Let’s look at the implementation of _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"); }}Copy the code

It also inherits Closure and calls println in the doCall method, which is the task we are executing in the task, the actions for the task mentioned earlier.

To clarify, each build.gradle script corresponds to a class that inherits from ProjectScript, and each Closure corresponds to a class that inherits from Closure

7.2 Invoking the Script run method

The next step is to execute the script class’s run method, which we analyzed above. One important point is that task creation in the run method only executes task.docall, which is why the configuration phase does not execute the Task, but the contents of the Task closure.

Task task1 {// Execute println('run') doLast {// execute println('run')}}Copy the code

8. Call process of plug-in

Gradle plugins are called by the apply plugin ‘XXX’. The link is as follows:

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

Finally, plugin.apply calls the apply() function implemented in the plug-in

Nine,

Overall structure drawing

  1. Gradle runs the process

    loadSettings configureBuild constructTaskGraph runTasks finishBuild

  2. The essence of a Task is a series of Actions

  3. The script compilation process gets the script content -> compiles it into a class file inherited from ProjectScript -> executes the projectscript.run method

  4. The script’s buildScript is executed before the rest of the script