18. Gradle編譯其他應用代碼流程(六) - 執行Task過程

接上一篇 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的守護進程編譯。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章