• gradle-4-构建有向无环图


    Gradle-4 Build-TaskGraph

    1. 入口


    在这里插入图片描述
    在这里插入图片描述
    configure阶段完毕后,接下来就是任务执行,但执行有个先后依赖顺序,所以gradle需要在执行任务前,构建有向无环图,为后续执行任务做铺垫,接下来我们看看gradle是如何实现的?

    2. 调用链路


    taskExecutionPreparer对象为BuildOperationFiringTaskExecutionPreparer,它也是在GradleScopeServices类中创建的,具体如何调用,此处不在赘述(可以参见之前gradle篇)
    在这里插入图片描述

    BuildOperationFiringTaskExecutionPreparer.prepareForTaskExecution
    CalculateTaskGraph.run
    populateTaskGraph
    DefaultTaskExecutionPreparer.prepareForTaskExecution
    DefaultBuildConfigurationActionExecuter.select
    DefaultTaskExecutionGraph.populate

    前三个步骤都是层层转发任务,最终来到DefaultTaskExecutionPreparer类

     public class DefaultTaskExecutionPreparer implements TaskExecutionPreparer {
        ...
        @Override
        public void prepareForTaskExecution(GradleInternal gradle) {
          	// 1. select
            buildConfigurationActionExecuter.select(gradle);
    				
          	// 2. populate taskGraph 
            TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
            taskGraph.populate();
    		
    		// 默认情况下buildControllers.values()为空,所以这里相当不执行,可以忽略
            includedBuildControllers.populateTaskGraphs();
    		
    		// 3. gradle的生命周期projectsEvaluated方法回调
            if (gradle.getStartParameter().isConfigureOnDemand()) {
                new ProjectsEvaluatedNotifier(buildOperationExecutor).notify(gradle);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. select
      确定执行任务,并对任务依赖进行解析等处理
    2. 生成有向无环图
    3. gradle.projectsEvaluated生命周期通知回掉

    3. select

    在这里插入图片描述
    configure方法是个典型的拦截器模式,不断消费procesingConfigurationActions,所以我们只要关注下processingBuildActions内部那几个action就好了;断点调试可以发现,有三个buildAction

    1. ExcludedTaskFilteringBuildConfigurationAction
    2. DefaultTasksBuildExecutionAction
    3. TaskNameResolvingBuildConfigurationAction

    1. ExcludedTaskFilteringBuildConfigurationAction

    /**
     * A {@link BuildConfigurationAction} which filters excluded tasks.
     */
    public class ExcludedTaskFilteringBuildConfigurationAction implements BuildConfigurationAction {
        ...
        @Override
        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));
                }
              	
              	// 2. 有向任务图对象设置任务过滤器
                gradle.getTaskGraph().useFilter(Specs.intersect(filters));
            }
    
            context.proceed();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    从名字就可以看出,这个action是用来剔除指定任务(从启动参数中读取),eg

    gradle dist --exclude-task test
    
    • 1

    从启动参数中读取需要剔去的任务名后,构建相关的过滤器,并设置到gradle的taskGraph对象中,这样后面将任务添加到taskGraph时都会经过这些过滤器

    2. DefaultTasksBuildExecutionAction

    因为终端输入

    ./gradlew assembleWapaDebug
    
    • 1

    在这里插入图片描述

    /**
     * A {@link BuildConfigurationAction} that selects the default tasks for a project, or if none are defined, the 'help' task.
     */
    public class DefaultTasksBuildExecutionAction implements BuildConfigurationAction {
        private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTasksBuildExecutionAction.class);
        private final ProjectConfigurer projectConfigurer;
    
        public DefaultTasksBuildExecutionAction(ProjectConfigurer projectConfigurer) {
            this.projectConfigurer = projectConfigurer;
        }
    
        @Override
        public void configure(BuildExecutionContext context) {
            StartParameter startParameter = context.getGradle().getStartParameter();
    
            for (TaskExecutionRequest request : startParameter.getTaskRequests()) {
                if (!request.getArgs().isEmpty()) {
                    context.proceed();
                    return;
                }
            }
    
            // Gather the default tasks from this first group project
            ProjectInternal project = context.getGradle().getDefaultProject();
    
            //so that we don't miss out default tasks
            projectConfigurer.configure(project);
    
            List<String> defaultTasks = project.getDefaultTasks();
            if (defaultTasks.size() == 0) {
                defaultTasks = Collections.singletonList(ProjectInternal.HELP_TASK);
                LOGGER.info("No tasks specified. Using default task {}", GUtil.toString(defaultTasks));
            } else {
                LOGGER.info("No tasks specified. Using project default tasks {}", GUtil.toString(defaultTasks));
            }
    
            startParameter.setTaskNames(defaultTasks);
            context.proceed();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    从上述代码可以分析出:

    1. 如果启动命令中包含任务,则执行下个消费单元(TaskNameResolvingBuildConfigurationAction)
    2. 如果没有则,使用defaultProject中的defaultTasks,如果返回空使用help任务占位

    3 TaskNameResolvingBuildConfigurationAction

    这个类的职责就是通过任务名找出所有项目中的任务

    /**
     * A {@link BuildConfigurationAction} which selects tasks which match the provided names. For each name, selects all tasks in all
     * projects whose name is the given name.
     */
    public class TaskNameResolvingBuildConfigurationAction implements BuildConfigurationAction {
      	...
        @Override
        public void configure(BuildExecutionContext context) {
            GradleInternal gradle = context.getGradle();
            TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
    
            List<TaskExecutionRequest> taskParameters = gradle.getStartParameter().getTaskRequests();
            for (TaskExecutionRequest taskParameter : taskParameters) {
              	
              	// 1. taskName解析成多个taskSections
                List<TaskSelection> taskSelections = commandLineTaskParser.parseTasks(taskParameter);
                for (TaskSelection taskSelection : taskSelections) {
                    LOGGER.info("Selected primary task '{}' from project {}", taskSelection.getTaskName(), taskSelection.getProjectPath());
                  	// 2. 将taskSection中的任务依次添加到任务图中
                    taskGraph.addEntryTasks(taskSelection.getTasks());
                }
            }
    
            context.proceed();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    因为gradle执行任务支持执行多个,所以taskParameters是个列表我们好理解;看下图

    ./gradlew assembleWapaDebug
    
    • 1

    assembleWapaDebug任务是怎样被gradle正确解析成多个task?被解析成二个任务了(本次构建中项目有2个一个app 一个module_base)
    在这里插入图片描述
    我们知道task是gradle中的执行基本单元,他是隶属project;要明确task必须只要它属于哪个project;大胆猜测下,gradle是根据task名称去在project遍历寻找的,如果这个taskName没显式所在工程,则遍历所有工程,如果显式声明所在工程则无须遍历project
    在这里插入图片描述
    TaskNameResolver类就是我们刚才猜测任务名是如何被gradle寻找解析的,看下图
    在这里插入图片描述
    方法第三个参数依赖于taskName前是否添加了前缀,如果存在说明是指定project中的task,如果没有gradle会从所有工程中寻找(本例就是属于后者),找到后会把任务搜集起来最终生成TaskSelection对象,交给TaskNameResolvingBuildConfigurationAction处理,它会把任务添加到taskGraph中

    最终调用DefaultTaskExecutionGraph.addEntryTasks方法,将任务添加到结点列表中,这个图每个节点都有对应的依赖任务,见下图

    :app:assembleWapaDebug 依赖三个任务
    在这里插入图片描述
    在这里插入图片描述
    task执行的顺序由谁来控制呢?其实就是任务的依赖处理,doAddNodes方法主要就是针对依赖
    在这里插入图片描述
    节点类型分为LocalTaskNodeResolved,以LocalTaskNode为例

    // LocalTaskNode.java
    @Override
    public void resolveDependencies(TaskDependencyResolver dependencyResolver, Action<Node> processHardSuccessor) {
        for (Node targetNode : getDependencies(dependencyResolver)) {
            addDependencySuccessor(targetNode);
            processHardSuccessor.execute(targetNode);
        }
        for (Node targetNode : getFinalizedBy(dependencyResolver)) {
            if (!(targetNode instanceof TaskNode)) {
                throw new IllegalStateException("Only tasks can be finalizers: " + targetNode);
            }
            addFinalizerNode((TaskNode) targetNode);
            processHardSuccessor.execute(targetNode);
        }
        for (Node targetNode : getMustRunAfter(dependencyResolver)) {
            addMustSuccessor((TaskNode) targetNode);
        }
        for (Node targetNode : getShouldRunAfter(dependencyResolver)) {
            addShouldSuccessor(targetNode);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    该方法会对该任务进行任务依赖解析处理,包括finalizedBymustRunAftershouldRunAfter等都在这儿解析处理的,只有这样才能实现gradle的有向无环图才能顺利生成

    4. populate(有向任务图生成)

    DefaultTaskExecutionGraph.populate

    @Override
        public void populate() {
          	// 1. 生成任务有向任务图
            ensurePopulated();
            if (!hasFiredWhenReady) {
              	// 2. 有向无环图graphPopulated时间通知
                fireWhenReady();
                hasFiredWhenReady = true;
            } else if (!graphListeners.isEmpty()) {
                LOGGER.info("Ignoring listeners of task graph ready event, as this build (" + gradleInternal.getIdentityPath() + ") has already executed work.");
            }
        }
    
    private void ensurePopulated() {
            switch (graphState) {
                case EMPTY:
                    throw new IllegalStateException(
                        "Task information is not available, as this task execution graph has not been populated.");
                case DIRTY:
                    executionPlan.determineExecutionPlan();
                    allTasks = null;
                    graphState = GraphState.POPULATED;
                    return;
                case POPULATED:
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    调用链路:

    step1: DefaultExecutionPlan.determineExecutionPlan
    在这里插入图片描述

    step2: fireWhenReady() ==》NotifyTaskGraphWhenReady.run
    graphPopulated事件通知而已

    //DefaultTaskExecutionGraph.java
    private void fireWhenReady() {
            // We know that we're running single-threaded here, so we can use coarse grained project locks
            projectStateRegistry.withMutableStateOfAllProjects(
                () -> buildOperationExecutor.run(
                    new NotifyTaskGraphWhenReady(DefaultTaskExecutionGraph.this, graphListeners.getSource(), gradleInternal)
                )
            );
        }
    
    @Override
    public void run(BuildOperationContext context) {
      graphListener.graphPopulated(taskExecutionGraph);
      context.setResult(NotifyTaskGraphWhenReadyBuildOperationType.RESULT);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    AST 初探深浅,代码还能这样玩?
    Vega Prime入门教程11:软件界面
    Spring循环依赖-spring源码详解(四)
    Pytoch随笔(光速入门篇)
    隐藏层节点数对网络分类行为的影响
    Ansible 多机自动化工具 初学笔记
    Topaz DeNoise AI 3.7 人工智能降噪
    [网络安全]实操DVWS靶场复现CSRF漏洞
    迅为i.MX8M开发板yocto系统使用Gstarwmr视频转换
    Cesium 空间量算——面积量算
  • 原文地址:https://blog.csdn.net/dbs1215/article/details/126095085