Gradle Chapter--Gradle Source Code Analysis

Preparatory knowledge

  1. Understanding the basic development of gradle
  2. Understanding the use and development of gradle task and plugin
  3. Understanding the use of android gradle plugin

How far can I get after reading this article?

  1. Understanding the implementation principle of gradle

Pre-reading preparation

  1. clone EasyGradle project
  2. download Gradle source code For reference

Posture of reading code

  1. Call links for easy comparison when reading code
  2. Focus on the overall framework, and leave some details untouched

Catalog

This article mainly carries on the analysis from the following several parts

  1. Starting Gradle
  2. loadSettings
  3. configureBuild
  4. constructTaskGraph
  5. runTasks
  6. finishBuild
  7. How to compile and execute gradle scripts
  8. Plug-in call process

I. Starting of Gradle

1.1 Overall Implementation Diagram

1.2 Specific Analysis

When we perform a build task, we always execute commands like. / gradlew assemble Debug, where the gradlew script is the entrance to the entire gradlebuild. Let's start with that.
The previous code basically judges the environment, sets variables, and looks directly at the last line:

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

The final order is basically as follows:

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

Basically, you can see that the org.gradle.wrapper.GradleWrapperMain in gradle/wrapper/gradle-wrapper.jar is executed, so that we know that the entry class of gradle is org.gradle.wrapper.GradleWrapperMain, and we know where to start looking at the code.
First look at 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());
}

Two important classes are org.gradle.wrapper.WrapperExecutor and org.gradle.wrapper.BootstrapMainStarter. Let's follow up on Wrapper Executor.execute to see:

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

Here are two things to do:

  1. Download the dependencies and source code required by gradle wrapper. The gradle wrapper version is the distribution Url that we configure in gradle/wrapper/gradle-wrapper.properties. The download locations are the distribution Path and zipStorePath that we configure in gradle-wrapper.properties. ZipStorePath is the location of the downloaded compressed package, distribution Path is the location of the decompressed package, and the default location is HOME/.gradle/wrapper/dists/. Here you can find the content of gradle wrapper.

If you create more than one project, we can see different versions of gradle wrapper in HOME/.gradle/wrapper/dists/. This also explains why we first said that we should use gradle wrapper instead of installing gradle directly on the computer, because gradle wrapper will be based on different projects. Containing different versions of content, projects do not affect each other.

  1. Execute the gradle build process. This is followed by BootstrapMainStarter.start(), the middle process is not looked at, more tortuous, to understand the overall process is not much help. Eventually it runs to DefaultGradleLauncher.executeTasks(), and then the process down is very clear.
// DefaultGradleLauncher
public GradleInternal executeTasks() {
    doBuildStages(Stage.Build);
    return gradle;
}

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

Basically, the construction process is divided into five steps. Let's look at these five processes separately.

Load Settings

2.1 Overall Implementation Diagram

2.2 Specific analysis

Load Settings is mainly to load settings.gradle file, and then create the corresponding project.

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

        buildOperationExecutor.run(new LoadBuild());

        stage = Stage.Load;
    }
}

Overall construction process:

2.2.1 Calls the BuildListener.buildStarted() callback interface

Notify the start of the build. This is where we were before. Gradle Basic Use Lifecycle callback.

2.2.2 Execute init script

Call Link

LoadBuild.run -> InitScriptHandler.executeScripts

Before Gradle Basic Use The function of init.gradle is mentioned in this article. It will be called before each project build s. It is here that some initialization operations are done.

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

Realization analysis
In getLayoutFor, find the settings.gradle file logic as follows:

  1. If the location of the settings.gradle file is specified in the parameter by - c xxx.gradle, then the specified settings.gradle file is used directly.
  2. If no settings file is specified, look in the current directory
  3. If the current directory does not exist, it will always look up to the higher directory and maseter / directory of the same level.
  4. If it is not found, it is still in the current directory by default.
// 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 Compile the contents under the buildSrc folder. BuilSrc can be seen as a plug-in with similar functions.

Call Link

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

After the settings.gradle file is found in the previous step, the buildSrc directory will be found and compiled in the same directory as settings.gradle, so that the contents of the buildSrc directory can be referenced when building settings.gradle.

2.2.5 Analytical gradle.properites

Call Link

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

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

// 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 Analytical settings.gradle

Call Link

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

Realization analysis
In ScriptEvaluating Settings Processor, Settings International instance and ScriptSource instance are created to represent the mapping of settings.gradle file in memory, and then BuildOperationScriptPlugin. application is called to execute settings.gradle file.
As for BuildOperationScriptPlugin.apply, let's go into detail later, because this method is also used when parsing build.gradle files.
The following 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);
}
2.2.7 Create projects and subproject s

Call Link

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

Realization analysis
After parsing the settings.gradle file, you can know which projects are in the project, and you can create project instances.

// InstantiatingBuildLoader
// The parameters passed in here correspond to: rootProject Descriptor: Settings Internal. getRootProject () default Project: Settings Internal. getDefaultProject () buildRootClassLoaderScope: Settings Internal. 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 master project instance
    // Project Internal inherits from Project, and the ultimate root Project returned is the DefaultProject type
    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) {
    // Create subproject instances
    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) {
    // Get build.gradle for project 
    File buildFile = projectDescriptor.getBuildFile();
    ScriptSource source = UriScriptSource.file("build file", buildFile);
    // Create project instances
    DefaultProject project = instantiator.newInstance(DefaultProject.class,
            projectDescriptor.getName(),
            parent,
            projectDescriptor.getProjectDir(),
            source,
            gradle,
            gradle.getServiceRegistryFactory(),
            selfClassLoaderScope,
            baseClassLoaderScope
    );

    // Setting up hierarchical relationships for project
    if (parent != null) {
        parent.addChildProject(project);
    }
    // Registration project
    projectRegistry.addProject(project);

    return project;
}

Here, according to settings.gradle configuration, create a project instance. When creating a child project, if the parent project is not empty, set yourself as the child project of the parent project, so that you can get the child project of the project through project.getChildProjects.
When we write gradle scripts, we often use the project attribute, which is created at this time.

So far, the settings.gradle file is parsed and a project instance is created.

3. CongureBuild

3.1 Overall Implementation Diagram

3.2 Specific Analysis

As we mentioned earlier, the gradle construction process is divided into configuration phase and run phase. Configuration phase is mainly the content of executing scripts, while run phase is the content of executing task. Here is the process of configuration phase. It should be noted that the configuration and operation stages mentioned above are two stages from a holistic point of view. Understanding from the source code is the several stages introduced in this article, which should be more detailed.
The content of the configuration phase is relatively simple, which is to compile the gradle script into a class file, and then run (gradle is written in groovy language, groovy is a jvm language, so it must be compiled into class to run).

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

        stage = Stage.Configure;
    }
}

When configuring a project, if specified configure-on-demand parameter It configures only the main project and the project needed to execute task. By default, it does not specify. It configures all the projects. See the default only.

3.2.1 Configure the main links of the main project and its sub-projects

Call Link

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

Realization analysis

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

Finally, it executes to Lifecycle Project Evaluator. doConfigure

3.2.2 Callback to BuildListener.beforeEvaluate interface

Callback the beforeEvaluate interface here to inform you that configuration is about to begin. We also know the implementation phase of this callback.

3.2.3 Set the default task and plug-in

Call Link

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

Realization analysis
In Plugins Project ConfigureActions, you add two tasks to the project: init and wrapper, and then add the help plug-in: org.gradle.help-tasks.

3.2.4 Compile scripts and execute them

Call Link

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

Realization analysis
Build OperationScript Plugin. apply is also called here to compile and execute the build.gradle script, which is the same as the previous parsing settings.gradle. Here we first know that this is compiling build.gradle as class.
File and execute, then look at the process, and then elaborate on how the script is compiled and executed.

3.2.5 Callback to BuildListener.afterEvaluate
3.2.6 Callback to BuildListener.projects Evaluated

IV. constructTaskGraph

4.1 Overall Implementation Diagram

4.2 Specific analysis

This step is to build the task dependency graph

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

        stage = Stage.TaskGraph;
    }
}
4.2.1 Processing task s that need to be excluded

Call Link

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

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

This step is used to deal with tasks that need to be excluded, that is, tasks specified on the command line through - x or --exclude-task. Here, a filter is set up for TaskGraph to exclude the corresponding tasks when calculating dependencies later.

4.2.2 Add default task

Call Link

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

Realization analysis
This checks to see if the Task name is passed in from the command line, and if a task is specified to be executed, nothing is done.
If it is not specified, it depends on whether the project has a default task, which can be specified by defaultTasks in build.gradle.
If the default task does not exist, then set the task to be specified to help task, which is to output the help content of gradle.

4.2.3 Computing task dependency graph

Call Link

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

Realization analysis

  1. Filter tasks according to taskname on the command line. If our task specifies a project, that is, app:assembleDebug, then task is selected directly. If no specific project is specified, then tasks that match taskname under all projects are screened out.
CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure -> CommandLineTaskParser.parseTasks
  1. Adding task to taskGraph handles the dependencies of task, including dependson finalizedby mustrunafter shouldrunafter, and then saves the information in org.gradle.execution.taskgraph.TaskInfo.
CalculateTaskGraph.run -> DefaultBuildConfigurationActionExecuter.select -> TaskNameResolvingBuildConfigurationAction.configure -> DefaultTaskGraphExecuter.addTasks
4.2.4 Generate task graph

Call Link

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

Realization analysis
Based on the task and its dependencies calculated in the previous step, a task graph is generated.

Run Tasks

5.1 Overall Implementation Diagram

5.2 Specific analysis

After the task graph is generated, the task is executed

5.2.1 Processing dry run

Call Link

DefaultBuildExecuter.execute -> DryRunBuildExecutionAction.execute

Realization analysis
If -- dry-run is specified on the command line, the execution of task is intercepted here, and the name of task and the execution sequence are output directly.

5.2.2 Create threads and execute task s

Call Link

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

Realization analysis
Create TaskExecutor Worker to execute task, defaulting to 8 threads.

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

Call Link

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

Realization analysis
This is where we formally begin the task execution process. There are several steps:

  1. Callback to TaskExecutionListener.beforeExecute
  2. Chain execution of some columns of Task processing, the specific treatment is as follows:
CatchExceptionTaskExecuter.execute // Attempt catch is added to prevent exceptions during execution   
ExecuteAtMostOnceTaskExecuter.execute  // Determine whether task has been executed   
SkipOnlyIfTaskExecuter.execute  // Judging whether the onlyif condition of task satisfies execution   
SkipTaskWithNoActionsExecuter.execute  // Skip task without action, no action indicates that task does not need to be executed   
ResolveTaskArtifactStateTaskExecuter.execute  // Setting artifact status    
SkipEmptySourceFilesTaskExecuter.execute  // Skip the task where the source file is set but the source file is empty, and the source file is empty, indicating that the task has no resources to deal with.   
ValidatingTaskExecuter.execute()  // Verify that task is executable   
ResolveTaskOutputCachingStateExecuter.execute // Processing task output cache   
SkipUpToDateTaskExecuter.execute  // Skip update-to-date task    
ExecuteActionsTaskExecuter.execute // True 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

Realization analysis
After the previous series of processing, here we begin to really execute task.

  1. Callback to TaskActionListener.beforeActions
  2. Callback OutputsGeneration Listener. beforeTaskOutputsGenerated
  3. Remove all Actions from task and execute them
// 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;
}

As you can see here, the essence of Task is actually to execute the Actions in it. For example, when we customize Task, we often use the following words:

task {
    doLast {
        // task Specific Tasks
    }
}

doLast here is equivalent to adding an Action to Task.
Take a look at AbstractTask's 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);
}

As you can see, the closure we passed in is eventually wrapped as TaskAction Wrapper and added to the actions of task.

  1. Callback to TaskActionListener.afterActions
  2. Callback to TaskExecutionListener.afterExecute

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

There is not much logic here. The BuildListener. BuilFinished interface is called back.

Through the above steps, we have basically seen the gradle execution process. In short, the steps are as follows:

  1. Parse settings.gradle and execute to generate Project instances
  2. Parse build.gradle and execute
  3. Generating task dependency graph
  4. Executing task s

7. How to compile and execute gradle scripts

In the previous introduction of the loadSettings and configureBuild phases, we mentioned the method of Build OperationScript Plugin. application, which is simply skipped. It is used to compile and execute gradle scripts. Here we will analyze it in detail.

7.1 Compile script

Call Link

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

Realization analysis
Here, the compilation process is divided into two parts. First, the build script {} part of the script is compiled, ignoring the other parts, and then the other parts of the script are compiled and executed. So the content in buildscript {} is executed before other content.

  1. The cache is checked first, and if there is a cache, it is used directly, and no cache is compiled.
  2. Eventually, you call CompileToCrossBuildCacheAction. execute - > DefaultScriptCompilation Handler. compileToDir - > DefaultScriptCompilation Handler. compileScript to perform the real compilation operation

The script cache path:
/Users/zy/.gradle/caches/4.1/scripts-remapped/build_a3v29m9cbrge95ug6eejz9wuw/31f5shvfkfunwn5ullupyy7xt/cp_proj4dada6424967ba8dfea75e81c8880f7f/classes
The class es in the catalog are as follows:

  1. The specific compilation method is to obtain the script content through RemappingScriptSource.getResource().getText(), and then compile through GroovyClassLoader.parseClass.

We use app/build.gradle For example, take a look at 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.constraint:constraint-layout:1.1.3'
    implementation project(':module1')
}

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        // println('build start')
    }

    @Override
    void settingsEvaluated(Settings settings) {
        // println('settings file parsing completed')
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        // println('project load completed')
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        // println('project parsing completed')
    }

    @Override
    void buildFinished(BuildResult result) {
        // println('build completed')
    }
})

gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    @Override
    void beforeEvaluate(Project project) {
        // println("${project.name} called before project configuration")
    }

    @Override
    void afterEvaluate(Project project, ProjectState state) {
        // println("${project.name} called after project configuration")
    }
})

gradle.taskGraph.whenReady {
    // println("task graph construction completed")
}
gradle.taskGraph.beforeTask {
    // println("task completion")
}
gradle.taskGraph.afterTask {
    // println("task completion")
}

task task1 {
    doLast {
        println('task2')
    }
}

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

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

As you can see, the script class inherits from ProjectScript and implements the run method.
What has been done in the run method? Look at the first line.

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

Get the callsiteArray, which is the assignment in the createCallSiteArray_1() method. As you can see, the callsiteArray here is the dsl in the script, which is actually the method name of the call.
Once you get the callsiteArray, execute a similar method of $getCallSiteArray[0].callCurrent(), which is calling the method. The script code corresponding to the method invoked is commented below.

public Object run() {
    CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
    // apply plugin "com.android.application" dependency plug-in
    $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));
}

As you can see above, task1 corresponds to the class _run_closure 6. Let's 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()));
    }
}

After omitting some content, you can see that the closure class inherits Closure, and then implements the doCall method. In the doCall method, the doLast method is called and an instance of _closure 17 is passed in. This is the corresponding implementation of task {doLast {}} in the script.
Let's look at the implementation of _closure 17.

/* 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");
    }
}

It also inherits Closure and calls println in the doCall method, which is exactly what we do in task, the actions of task mentioned earlier.

Let's straighten out here that each build.gradle script corresponds to a class that inherits ProjectScript, each closure, and a class that inherits Closure.

7.2 Call the script run method

Next is the run method to execute the script class, which we analyzed above.
One of the important points is that task.doCall is only created in the run method, which is why the task task task is not executed in the configuration phase, but the content in the task closure is executed.

task task1 {
    // The configuration phase executes
    println('configure')
    doLast {
        // Running Phase Execution
        println('run')
    }
}

8. Plug-in Call Process

Before Basic Use of Gradle The custom plug-in is used by applying plugin'xxx'. The specific call 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

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

Summary

Overall structure diagram

  1. gradle operation process
loadSettings
configureBuild
constructTaskGraph
runTasks
finishBuild
  1. The essence of Task is a series of Actions.
  2. Script compilation process

Get script content - > compile into class file, inherit from ProjectScript - > execute ProjectScript.run method

  1. The build script of the script is executed before the rest of the script

The Android Manual is updated weekly
Welcome to the following account for updates:
Wechat Search Public Number: ZYLAB
Github
Know about
Nuggets

Tags: Android Gradle Java Attribute

Posted on Wed, 28 Aug 2019 06:16:42 -0700 by blkrt10