Preliminary knowledge
- Understand the basic development of Gradle
- Understand gradle Task and Plugin usage and development
- Understand the use of android Gradle Plugin
How far can I go after reading this article
- Understand how Gradle works
Prepare before reading
- Clone EasyGradle project
- Download Gradle source code for your reference
Reading code posture
- Call link, easy to read code control
- Focus on the overall framework and leave out the details
directory
This paper is mainly analyzed from the following parts
- Gradle startup
- loadSettings
- configureBuild
- constructTaskGraph
- runTasks
- finishBuild
- How do Gradle scripts compile and execute
- 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:
- 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.
- 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(a) {
doBuildStages(Stage.Build);
return gradle;
}
private void doBuildStages(Stage upTo) {
// ...
loadSettings();
configureBuild();
constructTaskGraph();
runTasks();
finishBuild();
}
Copy the code
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(a) {
if (stage == null) {
buildListener.buildStarted(gradle);
buildOperationExecutor.run(newLoadBuild()); 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:
- Gradle file if you specify the location of settings.gradle file with -c xxx.gradle, use the specified settings.gradle file
- If the Settings file is not specified, look it up in the current directory
- If the current directory does not exist, the system searches the upper-level directory and the maseter/ directory of the same level
- 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
/ / the 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 the main project instance
// ProjectInternal inherits from Project and returns a rootProject of type DefaultProject
ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle, buildRootClassLoaderScope.createChild("root-project"), buildRootClassLoaderScope);
gradle.setRootProject(rootProject);
addProjects(rootProject, rootProjectDescriptor, gradle, buildRootClassLoaderScope);
}
private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle, ClassLoaderScope buildRootClassLoaderScope) {
// Create a subproject instance
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 the build.gradle for project
File buildFile = projectDescriptor.getBuildFile();
ScriptSource source = UriScriptSource.file("build file", buildFile);
// Create 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);
}
/ / register the 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(a) {
if (stage == Stage.Load) {
buildOperationExecutor.run(newConfigureBuild()); 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(a) {
if (stage == Stage.Configure) {
buildOperationExecutor.run(newCalculateTaskGraph()); 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
- 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
Copy the code
- 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
Copy the code
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();
/ / thread
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:
- The callback TaskExecutionListener beforeExecute
- Perform a series of tasks as follows:
CatchExceptionTaskExecuter.execute // Add a try catch to prevent exceptions during execution
ExecuteAtMostOnceTaskExecuter.execute // Check whether the task has been executed
SkipOnlyIfTaskExecuter.execute // Check whether the onlyif condition of the task can be executed
SkipTaskWithNoActionsExecuter.execute // Skip tasks without action. If there is no action, the task does not need to be executed
ResolveTaskArtifactStateTaskExecuter.execute // Set the artifact state
SkipEmptySourceFilesTaskExecuter.execute // Skip tasks whose source file is set to empty. If the source file is empty, the task has no resources to process
ValidatingTaskExecuter.execute() // Check whether the task can be executed
ResolveTaskOutputCachingStateExecuter.execute // Process the task output cache
SkipUpToDateTaskExecuter.execute // Skip the update-to-date task
ExecuteActionsTaskExecuter.execute // Execute the task
Copy the code
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.
- The callback TaskActionListener beforeActions
- The callback OutputsGenerationListener beforeTaskOutputsGenerated
- Take the Actions from the task and execute them all
// 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;
}
Copy the code
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 Specific 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(a) { 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.
- The callback TaskActionListener afterActions
- 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
- Parse settings.gradle and execute to generate a Project instance
- Parse build.gradle and execute
- Generate task dependency diagrams
- 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.
-
If there is a cache, use it directly. If there is no cache, compile it again
-
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:
-
Detailed compilation method is through 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' implementationproject(':module1')
}
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
// println(' build started ')
}
@Override
void settingsEvaluated(Settings settings) {
// println(' Settings file parsed ')
}
@Override
void projectsLoaded(Gradle gradle) {
// println(' project load completed ')
}
@Override
void projectsEvaluated(Gradle gradle) {
// println(' project parsing done ')
}
@Override
void buildFinished(BuildResult result) {
// println(' Build done ')
}
})
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} is called after the project is configured ")
}
})
gradle.taskGraph.whenReady {
// println(" Task graph completed ")
}
gradle.taskGraph.beforeTask {
// println(" Task completed ")
}
gradle.taskGraph.afterTask {
// println(" Task completed ")
}
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.
(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(a) {
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(a) {
return __signature;
}
public String getOriginalClassName(a) {
return __originalClassName;
}
public Object run(a) {
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(a) {
CallSite[] $getCallSiteArray = build_ak168fqfikdepd6py4yef8tgs.$getCallSiteArray();
// apply plugin "com.android.application" depends on the plugin
$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(a) {
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(a) {
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 {
// The configuration phase is executed
println('configure')
doLast {
// Run phase execution
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
- Gradle runs the process
loadSettings
configureBuild
constructTaskGraph
runTasks
finishBuild
Copy the code
- The essence of a Task is a series of Actions
- The script compilation process gets the script content -> compiles it into a class file inherited from ProjectScript -> executes the projectscript.run method
- The script’s buildScript is executed before the rest of the script