接上一篇 17. Gradle编译其他应用代码流程(五) - 设置Task过程,这篇帖子讲task的执行过程。

以gradle pmd为例


一. 入口

文件路径:

subprojects\core\src\main\java\org\gradle\initialization\DefaultGradleLauncher.java

// Execute build
buildOperationExecutor.run("Run tasks", new Runnable() {
     @Override
     public void run() {
         buildExecuter.execute(gradle);
     }
});


subprojects\core\src\main\java\org\gradle\execution\DefaultBuildExecuter.java

public class DefaultBuildExecuter implements BuildExecuter {
    ...
    
    public void execute(GradleInternal gradle) {
        execute(gradle, 0);
    }

    private void execute(final GradleInternal gradle, final int index) {
        if (index >= executionActions.size()) {
            return;
        }
        BuildExecutionAction action = executionActions.get(index);
        System.out.println("DefaultBuildExecuter. action: " + action + " index: " + index);
        action.execute(new BuildExecutionContext() {
            public GradleInternal getGradle() {
                return gradle;
            }

            public void proceed() {
                execute(gradle, index + 1);
            }

        });
    }
}

DefaultBuildExecuter采用的方法和上一篇帖子一样,使用递归的方法处理executionActions这个集合(估计是同一个人写 ^_^)。


executionActions包含两个action

org.gradle.execution.DryRunBuildExecutionAction@1e4d93f7 index: 0
org.gradle.execution.SelectedTaskExecutionAction@76673ed index: 1

其实这两个action也是继承自同一接口

public class DryRunBuildExecutionAction implements BuildExecutionAction {}
public class SelectedTaskExecutionAction implements BuildExecutionAction {}


下面来看每个action的执行流程:

  1.  DryRunBuildExecutionAction跳过所有的task

文件路径:

subprojects\core\src\main\java\org\gradle\execution\DryRunBuildExecutionAction.java  

/**
 * A {@link org.gradle.execution.BuildExecutionAction} that disables all selected tasks before they are executed.
 */
public class DryRunBuildExecutionAction implements BuildExecutionAction {
    public void execute(BuildExecutionContext context) {
        GradleInternal gradle = context.getGradle();
        if (gradle.getStartParameter().isDryRun()) {
            for (Task task : gradle.getTaskGraph().getAllTasks()) {
                task.setEnabled(false);
            }
        }
        context.proceed();
    }
}

从代码可以看到,它把所有的task都变成了enable=false;那这个是什么意思? 

就是不执行任何task!


大家可以加上 --dry-run参数试试,比如gradle assemble --dry-run 这样就会跳过task执行过程,其他流程,比如配置,plugin等等都会执行。

这种情况对于一些不需要执行task的场景,可以加快执行速度。

:pushsdk:transformNative_libsWithSyncJniLibsForDebug SKIPPED
:pushsdk:bundleDebug SKIPPED
:pushsdk:compileDebugSources SKIPPED
:pushsdk:assembleDebug SKIPPED
:pushsdk:compileReleaseSources SKIPPED
:pushsdk:assembleRelease SKIPPED
:pushsdk:assemble SKIPPED



2. SelectedTaskExecutionAction

文件路径:

subprojects\core\src\main\java\org\gradle\execution\SelectedTaskExecutionAction.java

public class SelectedTaskExecutionAction implements BuildExecutionAction {
    public void execute(BuildExecutionContext context) {
        ...

        taskGraph.addTaskExecutionGraphListener(new BindAllReferencesOfProjectsToExecuteListener());
        taskGraph.execute();
    }

    ...
}


文件路径:

subprojects\core\src\main\java\org\gradle\execution\taskgraph\DefaultTaskGraphExecuter.java

public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
    ...
    public void execute() {
        ...
        taskPlanExecutor.process(taskExecutionPlan, new EventFiringTaskWorker(taskExecuter.create(), buildOperationExecutor.getCurrentOperationId()));
        ...
    }
    
}


文件路径:

subprojects\core\src\main\java\org\gradle\execution\taskgraph\DefaultTaskPlanExecutor.java

class DefaultTaskPlanExecutor extends AbstractTaskPlanExecutor {
    ...

    @Override
    public void process(TaskExecutionPlan taskExecutionPlan, Action<? super TaskInternal> taskWorker) {
    	System.out.println("DefaultTaskPlanExecutor current thread: " + Thread.currentThread());
        taskWorker(taskExecutionPlan, taskWorker, buildOperationWorkerRegistry).run();
        taskExecutionPlan.awaitCompletion();
    }
}


文件路径:

subprojects\core\src\main\java\org\gradle\execution\taskgraph\AbstractTaskPlanExecutor.java

private static class TaskExecutorWorker implements Runnable {
        ...

        public void run() {
            ...
            while ((task = taskExecutionPlan.getTaskToExecute()) != null) {
                BuildOperationWorkerRegistry.Completion completion = buildOperationWorkerRegistry.operationStart();
                try {
                    ...
                    processTask(task);
                    ...
                } finally {
                    completion.operationFinish();
                }
            }
            ...
        }

        protected void processTask(TaskInfo taskInfo) {
            ...
            taskWorker.execute(taskInfo.getTask());
            ...
        }
    }


文件路径:

subprojects\core\src\main\java\org\gradle\execution\taskgraph\DefaultTaskGraphExecuter.java

/**
     * This action will set the start and end times on the internal task state, and will make sure
     * that when a task is started, the public listeners are executed after the internal listeners
     * are executed and when a task is finished, the public listeners are executed before the internal
     * listeners are executed. Basically the internal listeners embrace the public listeners.
     */
    private class EventFiringTaskWorker implements Action<TaskInternal> {
        ...

        @Override
        public void execute(TaskInternal task) {
            ...
            try {
                taskListeners.getSource().beforeExecute(task);
                System.out.println("EventFiringTaskWorker taskExecuter: " + taskExecuter
                		+" task.getState(): " + task.getState());
                taskExecuter.execute(task, task.getState(), new DefaultTaskExecutionContext());
                taskListeners.getSource().afterExecute(task, state);
            } finally {
                long endTime = timeProvider.getCurrentTime();
                internalTaskListeners.getSource().afterExecute(taskOperation, new OperationResult(startTime, endTime, task.getState().getFailure()));
            }
        }
    }
}

上面几段代码都是一直调用下来,并没有影响到主流程。

下面来看taskExecuter.execute(task, task.getState(), new DefaultTaskExecutionContext());

这个taskExecuter使用了装饰者模式,装饰者模式在gradle中使用的太多了(这句话好像不是第一次说了 ^_^)。

下面是包装的代码。

文件路径:

subprojects\core\src\main\java\org\gradle\internal\service\scopes\TaskExecutionServices.java

return new ExecuteAtMostOnceTaskExecuter(
            new SkipOnlyIfTaskExecuter(
                new SkipTaskWithNoActionsExecuter(
                    new SkipEmptySourceFilesTaskExecuter(
                        taskInputsListener,
                        new ValidatingTaskExecuter(
                            new SkipUpToDateTaskExecuter(
                                repository,
                                createSkipCachedExecuterIfNecessary(
                                    startParameter,
                                    gradle.getTaskCaching(),
                                    packer,
                                    new PostExecutionAnalysisTaskExecuter(
                                        new ExecuteActionsTaskExecuter(
                                            listenerManager.getBroadcaster(TaskActionListener.class)
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );

最后会调用到ExecuteActionsTaskExecuter里面。

不过我们可以稍微解释下各个封装Executer的作用。

ExecuteAtMostOnceTaskExecuter:检查是否已经执行过

SkipOnlyIfTaskExecuter:检查是否是skip(这个估计是个属性配置,暂时还没有找到在哪里配)

SkipTaskWithNoActionsExecuter:检查是否有action,没有则返回

SkipEmptySourceFilesTaskExecuter:检查是否有source file


这种设计模式的话,可以让每个类专注于自己的功能,然后像一根链条一样把他们串起来,从外到内,依次执行。同时外层还可以处理内层的返回结果。


3. ExecuteActionsTaskExecuter

这个executer从名字上面看就可以知道它的大概意思:执行actions,按照先后顺序,逐个执行action,如果某个action出错,那么将终止执行。


在gradle里面,有几个概念比较重要,从大到小: project,task, actions

一个project可以有多个子project; 一个project可以有多个task,task用来描述具体的操作;同时一个task可以包含多个action。


文件路径:

subprojects\core\src\main\java\org\gradle\api\internal\tasks\execution\ExecuteActionsTaskExecuter.java

/**
 * A {@link org.gradle.api.internal.tasks.TaskExecuter} which executes the actions of a task.
 */
public class ExecuteActionsTaskExecuter implements TaskExecuter {
    ...

    public void execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
    	...
    	GradleException failure = executeActions(task, state, context);
        ...  
            
    }

    private GradleException executeActions(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) {
        ...
        for (ContextAwareTaskAction action : actions) {
            state.setDidWork(true);
            task.getStandardOutputCapture().start();
            executeAction(task, action, context);
            ...
        }
        return null;
    }

    private void executeAction(TaskInternal task, ContextAwareTaskAction action, TaskExecutionContext context) {
        action.contextualise(context);
        try {
            action.execute(task);
        } finally {
            action.contextualise(null);
        }
    }
}



关于task的描述,大家可以看下Task.java的注释:


wKiom1kuPMrAKJyzAAHgdPqnjRU391.png-wh_50


A Task represents a single atomic piece of work for a build, 
such as compiling classes or generating javadoc.
Each task belongs to a Project. 

You can use the various methods on org.gradle.api.tasks.TaskContainer to create and lookup task instances. For example, org.gradle.api.tasks.TaskContainer.create(String) creates an empty task with the given name. You can also use the 
task keyword in your build file:
 task myTask
 task myTask { configure closure }
 task myType << { task action }
 task myTask(type: SomeType)
 task myTask(type: SomeType) { configure closure } 
 
Each task has a name, which can be used to refer to the task within its 
owning project, and a fully qualified path, which is unique across all tasks in 
all projects. The path is the concatenation of the owning project's path and the 
task's name. Path elements are separated using the {@value 
org.gradle.api.Project#PATH_SEPARATOR} character.

Task Actions
A Task is made up of a sequence of Action objects. When the task is executed, each of the actions is executed in turn, by 
calling Action.execute. 
You can add actions to a task by calling doFirst(Action) or doLast(Action).
Groovy closures can also be used to provide a task action. When the action is 
executed, the closure is called with the task as parameter. You can add action 
closures to a task by calling doFirst(groovy.lang.Closure) or doLast(groovy.lang.Closure) or using the left-shift << operator.
There are 2 special exceptions which a task action can throw to abort 
execution and continue without failing the build. A task action can abort 
execution of the action and continue to the next action of the task by throwing 
a org.gradle.api.tasks.StopActionException. 
A task action can abort execution of the task and continue to the next task by 
throwing a org.gradle.api.tasks.StopExecutionException. 
Using these exceptions allows you to have precondition actions which skip 
execution of the task, or part of the task, if not true.


Task Dependencies and Task Ordering
A task may have dependencies on other tasks or might be scheduled to always 
run after another task. Gradle ensures that all task dependencies and ordering 
rules are honored when executing tasks, so that the task is executed after all 
of its dependencies and any "must run after" tasks have been executed.
Dependencies to a task are controlled using dependsOn(Object) or setDependsOn(Iterable), 
and mustRunAfter(Object), 
setMustRunAfter(Iterable), 
shouldRunAfter(Object) and setShouldRunAfter(Iterable) are used to specify ordering between tasks. You can use objects of any of the 
following types to specify dependencies and ordering:
A String, CharSequence or 
groovy.lang.GString task path or name. A relative path is 
interpreted relative to the task's Project. 
This allows you to refer to tasks in other projects.
A Task.
A closure. The closure may take a Task as parameter. It may 
return any of the types listed here. Its return value is recursively converted 
to tasks. A null return value is treated as an empty collection.
A TaskDependency object.
A Buildable object.
A Iterable, Collection, Map or array. 
May contain any of the types listed here. The elements of the 
iterable/collection/map/array are recursively converted to tasks.
A Callable. The call() method may return any of 
the types listed here. Its return value is recursively converted to tasks. A 
null return value is treated as an empty collection.
Using a Task in a Build File


Dynamic Properties
A Task has 4 'scopes' for properties. You can access these 
properties by name from the build file or by calling the property(String) method. You can change the value of these properties by calling the setProperty(String, 
Object) method.
The Task object itself. This includes any property getters and 
setters declared by the Task implementation class. The properties 
of this scope are readable or writable based on the presence of the 
corresponding getter and setter methods.
The extensions added to the task by plugins. Each extension is 
available as a read-only property with the same name as the extension.
The convention properties added to the task by plugins. A plugin 
can add properties and methods to a task through the task's Convention object. The properties of this scope may be readable or writable, depending on 
the convention objects.
The extra properties of the task. Each task object maintains a map 
of additional properties. These are arbitrary name -> value pairs which you 
can use to dynamically add properties to a task object. Once defined, the 
properties of this scope are readable and writable.


Dynamic Methods
A Plugin may add methods to a Task using its Convention object.
Parallel Execution
By default, tasks are not executed in parallel. Parallel execution can be 
enabled by the --parallel flag when the build is initiated. In 
parallel mode, the tasks of different projects (i.e. in a multi project build) 
are able to be executed in parallel. If a task is annotated with org.gradle.api.tasks.ParallelizableTask, 
it may also be executed in parallel with other tasks of the same project. See 
org.gradle.api.tasks.ParallelizableTask for more details on writing parallelizable tasks.


个人理解意思是(仅供参考):


Task


一个task是一个独立的原子任务,比如编译一个类或者生成javadoc。


每个task都属于一个project,你可以使用TaskContainer里面的多个方法去创建或者查找task引用,例如:TaskContainer.create(String) 可以使用你传入的名字创建一个空的task。你也可以在你的build文件里面使用'task'关键字创建task。


task myTask
task myTask { configure closure }
task myType << { task action }
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }


每个task都有一个名字,这个名字可以用来和它所在的project关联,and a fully qualified path, which is unique across all tasks in all projects. The path is the concatenation of the owning project's path and the task's name. Path elements are separated using the {@value org.gradle.api.Project#PATH_SEPARATOR} character.(这段不是很理解,应该是完全限定路径)。


Task Actions


每个task都有一系列有顺序的action,当一个task被执行的时候,它的action会按照顺序被逐一执行。你可以通过doFirst(Action)或者doLast(Action)来添加action。

Groovy闭包当然也可以用来提供action,当action被执行的时候,闭包会被作为参数调用。你可以通过doFirst(groovy.lang.Closure)和doLast(groovy.lang.Closure)或者通过左偏移符号"<<"来添加闭包action。


这里有两个特殊的异常可以允许你去中断execution,但是不会让编译失败。

org.gradle.api.tasks.StopActionException可以中断当前执行的action,继续执行下一个action。

org.gradle.api.tasks.StopExecutionException可以中断当前task,继续执行下一个task。

通过这些异常控制,你可以进行一些预处理检查等等。


其实这两个异常通过代码也可以看出来。

for (ContextAwareTaskAction action : actions) {
            state.setDidWork(true);
            task.getStandardOutputCapture().start();
            try {
                executeAction(task, action, context);
            } catch (StopActionException e) {
                // Ignore
                LOGGER.debug("Action stopped by some action with message: {}", e.getMessage());
            } catch (StopExecutionException e) {
                LOGGER.info("Execution stopped by some action with message: {}", e.getMessage());
                break;
            } catch (Throwable t) {
                return new TaskExecutionException(task, t);
            } finally {
                task.getStandardOutputCapture().stop();
            }
        }



Task Dependencies and Task Ordering


一个task可能会依赖于其他task,或者需要在其他task之后运行,gradle支持了这种功能。

依赖一个task可以使用dependsOn(Object)或者setDependsOn(Iterable)和 mustRunAfter(Object), setMustRunAfter(Iterable), shouldRunAfter(Object) and setShouldRunAfter(Iterable) ,你可以使用上面这些方法指定先后关系。


在Build文件中使用Task


动态属性

task有4种范围的属性,你可以通过property(String)获取这个属性值,或者通过setProperty(String, Object)来设置值。

  1. task本身

  2. extensions

  3. convention

  4. extra



动态方法

plugin可能会通过Convention对象添加方法


并发执行

默认task是顺序执行的,并发执行可以通过--parallel 参数开启。



下一篇会讲讲gradle的守护进程编译。