Jenkins任务调度源码简要分析


转载请注明

一、历史

Jenkins是由Java语言编写的一款开源的持续集成的工具,前身为Sun公司的Hudson,众所周知,sun2009年被Oracle收购,2010年末其主要贡献者与Oracle之间发生了内部争执,导致Hudson2011年年初,通过社区投票的方式,将项目名称由Hudson变更为Jenkins,但与此同时Oracle表示也会继续开发原来的Hudson,导致往后的一段时间,双方均认为对方是自己的复刻版本,到了第二年也就是2012的一月,Eclipse基金会将Hudson纳入,代表OracleHudson项目已经失去了兴趣,截止201311月,Jenkins已经拥有了远超Hudson的项目成员以及公共库

二、相关组件解析

Jenkins作为一款老牌CI/CD开源产品,历史悠久,也带来的代码量巨大的问题,这里尝试分析Jenkins源码中的几个核心组件

  • 平台基石:jenkins.model.Jenkins,它是Jenkins 的主要执行对象,所有的调度,运算,任务都是绑定在它上面。

  • 任务执行:在Jenkins中,所有的动作都包装为一个一个可执行的任务作业Job,底层采用线程来实现,其间分的层级也比较多,有:Item->Job->Run->Executor->Thread

image-20211028105458975

  • 资源设备:提供CPU,内存等资源的设备,相关组件有LabelNodeComputer
  • 调度策略:如何分配执行任务与资源设备的关系,相关的组件有QueueNodeProvisionerMultiStageTimeSeriesTimeSeries

1、平台基石

①、Jenkins

系统的根对象,传承于Hudson,维持了某一个JobProjectExecutorUserBuildable Item等等的数据和状态,可以对Jenkins整个生命周期进行管理

image-20211028105210269

2、任务执行

①、Item&ItemGroup

它是Jenkins中基本的配置单元,每一个Item都会被托管在一个ItemGroup中,而每一个Item本身也可以成为一个ItemGroup,这便形成了一个树状结构,而这个树状结构的根节点便是Jenkins

  • Item提供获取父ItemItemGroup的方法
public interface Item extends PersistenceRoot, SearchableModelObject, AccessControlled, OnMaster {
    /**
     * Gets the parent that contains this item.
     */
    ItemGroup<? extends Item> getParent();
    (...)
  • 一个ItemGroup也提供获取所有子Item的方法
public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject {
    /**
     * Gets all the items in this collection in a read-only view
     * that matches supplied Predicate
     * @since 2.221
     */
     default Collection<T> getItems(Predicate<T> pred) {
         return getItemsStream(pred)
                          .collect(Collectors.toList());
     }
    (...)

有点像文件系统中的文件夹的感觉,但是不同的是,在文件系统中,文件可以从一个目录移动到另一个目录,Item本身只能属于单独的ItemGroup,这个关系不能改变。

  • Windows设备管理器中,一个硬盘总是显示在磁盘驱动器下方,它永远不可能移动到处理器,显示器等等其他位置,同样的,ItemGroup也不是一个通用的容器,ItemGroup的每一个子类通常只能承载某种有限类型的Item,比如老熟人FreeStyleProject便是其子类

image-20211028112317028

每一个Item都有一个唯一的名称用来区分其他Item,名称可以用“/”组合起来,从而形成一个完整的项目名称,并在Jenkins中来唯一标识。

②、Job

Job便是Jenkins下面可追踪的可运行实体,它是静态的概念,包含一次构建的所有配置信息,比如构建的脚本,被构建的源代码,构建所需时间,源代码仓库等等等信息

image-20211028113956593

如果需要自定义一个Job类型,需要继承TopLevelItemDescriptor顶级Item描述器的对象,并为其加上Extension注解

③、Run

因为Job是一个可运行的实体,是一个静态的概念,那么在其运行起来后,对于这个动态的过程便是由Run来管理,比如在pre build stage任务执行构建前常做的拉取源代码,post build stage执行构建完成后执行归档产出物等,这些熟悉的操作都是由Run来管理,特别地,Run应该便是对应于Jenkins API中的Build

image-20211028115006994

同样它也支持自定义,常与自定义Job来配合使用,毕竟自定义的Job理所应当需要特定的Run来执行才行

④、Executor&Executable

Executor是执行构建的线程,它可以按需执行,继承自Thread类,Executable为真正代替Executor执行计算的对象

  • Thread类使用run开启线程的时候,实际上调用的也是Runnable接口的run方法,所以既然Executor继承自Thread类,那么就应该还有一个类继承Runnable,这个类就是Executable
@Override
public void run() {
    //agent是否在线
    if (!owner.isOnline()) {
		(...)
    }
    //node节点是否为空
    if (owner.getNode() == null) {
		(...)
    }
    final WorkUnit workUnit;
    //将当前Executor写锁锁住
    lock.writeLock().lock();
    try {
        //记录执行开始时间
        startTime = System.currentTimeMillis();
        workUnit = this.workUnit;
    } finally {
        //释放当前Executor写锁
        lock.writeLock().unlock();
    }

    try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) {
         //这个类是一个任务的一部分,代表了一个Executor的一次计算,一个Task包含多个SubTask
        SubTask task;
        task = Queue.withLock(new Callable<SubTask>() {
            @Override
            public SubTask call() throws Exception {
                if (!owner.isOnline()) {
					(...)
                }
                if (owner.getNode() == null) {
                    (...)
                }
                workUnit.setExecutor(Executor.this);
                queue.onStartExecuting(Executor.this);
                (...)
                lock.writeLock().lock();
                try {
                    Executor.this.executable = executable;
                } finally {
                    lock.writeLock().unlock();
                }
                workUnit.setExecutable(executable);
                return task;
            }
        });
        Executable executable;
        //将当前Executor读锁锁住
        lock.readLock().lock();
        try {
            if (this.workUnit == null) {
                return;
            }
            executable = this.executable;
        } finally {
            //读锁解除
            lock.readLock().unlock();
        }
        if (LOGGER.isLoggable(FINE))
            LOGGER.log(FINE, getName()+" is going to execute "+executable);

        Throwable problems = null;
        try {
            //同步开始
            workUnit.context.synchronizeStart();

            if (executable == null) {
                return;
            }

            //估计时间
            executableEstimatedDuration = executable.getEstimatedDuration();

            (...)
            
            try (ACLContext context = ACL.as2(auth)) {
                //真正开始执行
                queue.execute(executable, task);
            }
        } catch (AsynchronousExecution x) {
           (...)
        } catch (Throwable e) {
            problems = e;
        } finally {
			(...)
        }
    } catch (InterruptedException e) {
        LOGGER.log(FINE, getName()+" interrupted",e);
        // die peacefully
    } catch(Exception | Error e) {
        LOGGER.log(SEVERE, getName()+": Unexpected executor death", e);
    } finally {
        if (asynchronousExecution == null) {
            finish2();
        }
    }
}
  • Thread类的start方法,自然也被Executor所继承下来,但是前者调用的是start是没有参数的,后者却并不支持直接调用,必须传入一个WorkUnit对象,否则会抛出UnsupportedOperationException
/**
 * Can't start executor like you normally start a thread.
 *
 * @see #start(WorkUnit)
 */
@Override
public void start() {
    throw new UnsupportedOperationException();
}

/*protected*/ void start(WorkUnit task) {
    lock.writeLock().lock();
    try {
        this.workUnit = task;
        super.start();
        started = true;
    } finally {
        lock.writeLock().unlock();
    }
}

⑤、WorkUnit

表示从队列Queue中拿到Task任务交给给Executor的基本单位,类结构较为简单,有用于执行的任务SubTask,有执行线程Executor和其对应的Executable,最后还有一个工作单元的共享上下文

image-20211028193754365

⑥、AbstractProject和AbstractBuild

前者表示软件构建的基础抽象类,其主要实现类有熟悉的ProjectMavenModule,后者代表软件运行的基础抽象类,其主要的实现类有熟悉的BuildMavenBuild

image-20211028195724262

⑦、Action

盲猜应该是构建步骤,可以在任务配置中定义

3、资源设备

①、Node

Jenkins agent的基本类型,在实际操作中,也可以继承它来扩展定义新的agent类型,主要负责的是最基础的配置,比如ExecutorNumNodeNameDescription

②、Label

负责一组Node,因为在某些场景中,一个Job可能由多种语言实现,那么可以用Label来管理一组Node,如果遇到算力不足的情况,可以利用其内部属性NodeProvisioner来初始化新的Node

③、Computer

它和Node多少有点关系,但是也有一些显著的差异,Computer作为一系列Executor的拥有者,如果一个Node中没有配置Executor(可能是暂时的),那么也将不会拥有Computer对象,如果一个Node本身是存在的,且其已经执行过若干构建,当把它移除后,相应的Computer也需要一定时间被删除,在更改节点配置的时候,没有被影响的Computer依然会保存完整直到所有的Node都被删除

4、调度策略

①、Queue

这个对象实现了核心调度逻辑,其中的内部接口Task表示放置在此队列中的可执行任务,在队列中,Task可以被包装进另一个内部类Item中,所以我们可以跟踪额外的用于决定何时执行的其他数据,队列中的任务将经过如下几个阶段

  (进入) --> 等待任务队列 --+--> 阻塞任务队列
                         |        ^
                         |        |
                         |        v
                         +--> 可执行的任务 ---> 挂起 ---> 离开
                                  ^            |
                                  |            |
                                  +--极少数情况--+

通常,一个任务的执行只能向出队方向移动,但是出现下面这种情况,任务的执行流程也可以回退

  • 被分配执行任务的Jenkins节点在执行器线程Executable启动前(或者实例化前)消失了,也就是这一个Jenkins节点被移除了,那么这个任务就可以回退到一个可执行的状态

换句话说,当执行器线程已经实例化的情况下,唯一能让让执行器线程Executable停止或销毁的情况是在让其执行的Jenkins节点不存在

另外,在任何阶段,队列中的节点都可以被移除出去,比如在构建的时候点击取消

②、NodeProvisioner

用于负载统计和决定何时需要增加新的NodeJenkins需要合理分配Executor和运算资源,保证运算充足的情况下,不启动冗余的Node,即使运算需求陡然升高,也会有条不紊的去开启Node,而NodeProvisioner便是通过调用Queue来管理下面的Node资源,而如何管理资源调度则是交给其内部类StandardStrategyImpl

三、流程调试

Jenkins所使用的Web框架,名为Stapler,这款框架国内很少见,网上的文档也极其稀少,所以这里仅仅做一个大致介绍

  • Stapler可以将程序对象Model绑定到一个URL上面,它能够自动帮我们的对象Model分配一个专属URL,创建一种非常直观的URL层级结构,这里的Model我们可以简单理解为对应于一个SpringMVC中的Controller

1、创建一个FreeStyleProject

Jenkins的创建Web页面为http://localhost:8080/jenkins/view/all/newJob,这个方法绑定到了View这个Model上面,但是View是一个抽象类,具体执行交由其实现类,对应的newJob.jelly页面查询到这个新建请求会发送给View.createItem方法

image-20211101110447355

@RequirePOST
@Override
public Item doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
    ItemGroup<? extends TopLevelItem> ig = getOwner().getItemGroup();
    if (ig instanceof ModifiableItemGroup)
        return ((ModifiableItemGroup<? extends TopLevelItem>)ig).doCreateItem(req, rsp);
    return null;
}

方法会继续走到Jenkins对象的doCreateItem方法,然后继续走到ItemGroupMixIncreateTopLevelItem方法,这个方法会对这个工程进行若干合法性校验,校验通过会走到createProject方法

public synchronized TopLevelItem createTopLevelItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
	(...)
    if(name==null)
        throw new Failure("Query parameter 'name' is required");

    {// check if the name looks good
		(...)
    }

    if(mode!=null && mode.equals("copy")) {
       (...)
    } else {
        if(isXmlSubmission) {
           (...)
        } else {
           (...)

            // 这一步真正开始创建
            result = createProject(descriptor, name, true);
        }
    }

    rsp.sendRedirect2(redirectAfterCreateItem(req, result));
    return result;
}

createProject方法会对创建的工程进一步做校验,如果合法,则调用newInstance进行创建

public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify )
        throws IOException {
	(...)

    TopLevelItem item = type.newInstance(parent, name);
    item.onCreatedFromScratch();
    item.save();
    add(item);
    Jenkins.get().rebuildDependencyGraphAsync();

    if (notify)
        ItemListener.fireOnCreated(item);

    return item;
}

创建工程完毕后,会进入http://localhost:8080/jenkins/job/Test/configure对工程进行若干配置,这一步对应的页面为configure.jelly页面,可以看到,当配置完成点击保存后,会把请求发送到configSubmit方法

image-20211101110654553

根据Stapler的特性,这个请求肯定会发送到Job这个对象中,实际上,断点进入的方法为AbstractProject类,重写自JobdoConfigSubmit方法

@Override
@POST
public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
    super.doConfigSubmit(req,rsp);

    updateTransientActions();

    // notify the queue as the project might be now tied to different node
    Jenkins.get().getQueue().scheduleMaintenance();

    // this is to reflect the upstream build adjustments done above
    Jenkins.get().rebuildDependencyGraphAsync();
}

给这个任务配置一个构建脚本

echo helloworld >> hello.txt

2、构建调度调试

对这个任务执行构建,断点走到ParameterizedJobMixIn对象的doBuild方法

default void doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
    getParameterizedJobMixIn().doBuild(req, rsp, delay);
}

这个方法会将当前构建任务从Queue中拿出来然后使用schedule2方法将这个构建任务放入一个WaitingItem的等待队列中,随时可以交给Jenkins来执行

public final void doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
    
    (...)
    
    Queue.Item item = Jenkins.get().getQueue().schedule2(asJob(), delay.getTimeInSeconds(), getBuildCause(asJob(), req)).getItem();
    if (item != null) {
        rsp.sendRedirect(SC_CREATED, req.getContextPath() + '/' + item.getUrl());
    } else {
        rsp.sendRedirect(".");
    }
}

schedule2方法嵌套了好几层,不过始终都是在当前Queue对象下,真正提交构建任务的核心代码在scheduleInternal方法上,然后内部调用scheduleMaintenance方法提交当前构建任务

private @NonNull ScheduleResult scheduleInternal(Task p, int quietPeriod, List<Action> actions) {
	(...)
    scheduleMaintenance();   // let an executor know that a new item is in the queue.
	(...)
}
public Future<?> scheduleMaintenance() {
    return maintainerThread.submit();
}

submit方法也还没有结束

public synchronized Future<V> submit() {
    if (pending==null) {
        pending = new CompletableFuture<>();
        maybeRun();
    }
    return pending;
}

还要执行一个maybeRun方法,这里的submit方法会将当前任务正式提交进入等待队列,其中的参数是一个带回调的异步的方法,在这个方法中会继续直接调用task.call()创建一个新的FutureTask来将刚才提交的任务进行构建

private synchronized void maybeRun() {
    if (inprogress==null && pending!=null) {
        base.submit(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                synchronized (AtmostOneTaskExecutor.this) {
                    // everyone who submits after this should form a next batch
                    inprogress = pending;
                    pending = null;
                }

                try {
(断点)              inprogress.complete(task.call());
                } catch (Throwable t) {
                    LOGGER.log(Level.WARNING, null, t);
                    inprogress.completeExceptionally(t);
                } finally {
                    synchronized (AtmostOneTaskExecutor.this) {
                        // if next one is pending, get that scheduled
                        inprogress = null;
                        maybeRun();
                    }
                }
                return null;
            }
        });
    }
}

正常情况下,当前agentexecutor还有空闲的时候,这里是可以直接创建出我们想要的FutureTask来执行这个任务,执行构建的过程下面会聊,这里讲一下调度的特殊情况,当系统资源不足的情况下,即executor没有空闲,这段代码显然并不会立马执行,那么我们将断点打到下面代码的低13行,让程序停在这里,模拟了一下系统延迟执行的情况

  • 在聊特殊情况之前,首先了解一下Jenkins架构中的另一个抽象类SafeTimerTask,这个类是一个全局共享的定时器,他会每隔若干时间执行一个方法,在Queue类中有一个内部类为MaintainTask,它继承了这个抽象类,实现了doRun方法,并且在periodic方法中定义好了循环周期为五秒,也就是说MaintainTask对象每隔五秒就会执行一下doRun方法
private static class MaintainTask extends SafeTimerTask {
    private final WeakReference<Queue> queue;

    MaintainTask(Queue queue) {
        this.queue = new WeakReference<>(queue);
    }

    private void periodic() {
        long interval = 5000;
        Timer.get().scheduleWithFixedDelay(this, interval, interval, TimeUnit.MILLISECONDS);
    }

    @Override
    protected void doRun() {
        Queue q = queue.get();
        if (q != null)
            q.maintain();
        else
            cancel();
    }
}

当程序停下来后,我们就会发现,程序就会执行到这个doRun方法,那么这个方法的核心便是maintain方法,这个方法非常长,在整个Queue类中都应该算是一个很核心的方法,你长任你长,将这个方法简化,我们关注这一段代码:

public void maintain() {
    for (BuildableItem p : new ArrayList<>(
        buildables)) {
        (...)
        WorkUnitContext wuc = new WorkUnitContext(p);
        MappingWorksheet ws = new MappingWorksheet(p, candidates);
        Mapping m = loadBalancer.map(p.task, ws);
        m.execute(wuc);
        (...)
    }
}

Jenkins会将当前等待队列中一个一个拿出可构建的任务出来,然后调用execute方法执行

private void execute(WorkChunk wc, WorkUnitContext wuc) {
    assert capacity() >= wc.size();
    int e = 0;
    for (SubTask s : wc) {
        while (!get(e).isAvailable())
            e++;
        get(e++).set(wuc.createWorkUnit(s));
    }
}

这段代码需要关注这里的set方法,这里就可以看到调用了excutor.start()方法开始构建,还记得介绍Executor的时候有提到,要使用它开启一个线程,需要调用的是start的单参方法,无参的start会报UnsupportedOperationException,所以这里便传入了一个WorkUnit对象来开启线程执行任务

@Override
protected void set(WorkUnit p) {
    assert this.workUnit == null;
    this.workUnit = p;
    assert executor.isParking();
    executor.start(workUnit);
    // LOGGER.info("Starting "+executor.getName());
}

3、构建开始调试

前面讲到的两种方式最终都会调用Executor来执行构建任务,Executor开始执行的时候执行的是run方法,这段代码也非常长,所以还是将其简化,关注这段代码即可

@Override
public void run() {
    (...)
    startTime = System.currentTimeMillis();
    workUnit = this.workUnit;
    SubTask task;
    task = Queue.withLock(new Callable<SubTask>() {
        @Override
        public SubTask call() throws Exception {
            workUnit.setExecutor(Executor.this);
            queue.onStartExecuting(Executor.this);
            SubTask task = workUnit.work;
            Executable executable = task.createExecutable();
            lock.writeLock().lock();
            try {
                Executor.this.executable = executable;
            } finally {
                lock.writeLock().unlock();
            }
            workUnit.setExecutable(executable);
            return task;
        }
    });
    Executable executable;
    executable = this.executable;
    Throwable problems = null;
    executableEstimatedDuration = executable.getEstimatedDuration();
    try (ACLContext context = ACL.as2(auth)) {
        queue.execute(executable, task);
    }
    (...)
}

将当前Executor中的Executable对象拿到,然后就进入execute方法执行构建

public void execute(@NonNull Runnable task, final ResourceActivity activity ) throws InterruptedException {
  	(...)

    try {
        task.run();
    } finally {
     	(...)
    }
}

task是传入的一个executable对象,这个对象实现了Runnable接口,然后调用其run方法,run方法还会执行一个execute方法,如下

protected final void execute(@NonNull RunExecution job) {
    if(result!=null)
        return;     // already built.

    OutputStream logger = null;
    StreamBuildListener listener=null;

    runner = job;
    onStartBuilding();
    try {
        long start = System.currentTimeMillis();

        try {
            try {
                Computer computer = Computer.currentComputer();
                Charset charset = null;
                if (computer != null) {
                    charset = computer.getDefaultCharset();
                    this.charset = charset.name();
                }
                logger = createLogger();
                listener = createBuildListener(job, logger, charset);
                listener.started(getCauses());

                Authentication auth = Jenkins.getAuthentication2();
                if (auth.equals(ACL.SYSTEM2)) {
                    listener.getLogger().println(Messages.Run_running_as_SYSTEM());
                } else {
                    String id = auth.getName();
                    if (!auth.equals(Jenkins.ANONYMOUS2)) {
                        final User usr = User.getById(id, false);
                        if (usr != null) { // Encode user hyperlink for existing users
                            id = ModelHyperlinkNote.encodeTo(usr);
                        }
                    }
                    listener.getLogger().println(Messages.Run_running_as_(id));
                }

                RunListener.fireStarted(this,listener);

                //执行入口
                setResult(job.run(listener));

                (...)
            } catch (ThreadDeath t) {
  				(...)
            }

        } catch (ThreadDeath t) {
            throw t;
        } catch( Throwable e ) {
			(...)
        } finally {
            long end = System.currentTimeMillis();
   			(...)
        }

        try {
            getParent().logRotate();
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Failed to rotate log",e);
        }
    } finally {
       (...)
    }
}

在这个execute方法中,会将当前执行的Computer对象拿到,然后还会创建若干的Listenner,如StreamBuildListenerRunListener等,用于构建期间发生的一些事件的监听,完成这些前置工作后,调用job对象run方法

@Override
public Result run(@NonNull BuildListener listener) throws Exception {
    final Node node = getCurrentNode();
    
    assert builtOn==null;
    builtOn = node.getNodeName();
    hudsonVersion = Jenkins.VERSION;
    this.listener = listener;

    Result result = null;
    buildEnvironments = new ArrayList<>();
    TearDownCheckEnvironment tearDownMarker = new TearDownCheckEnvironment();
    buildEnvironments.add(tearDownMarker);
    try {
        launcher = createLauncher(listener);
        if (!Jenkins.get().getNodes().isEmpty()) {
            if (node instanceof Jenkins) {
                listener.getLogger().print(Messages.AbstractBuild_BuildingOnMaster());
            } else {
                listener.getLogger().print(Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, node.getDisplayName())));
                Set<LabelAtom> assignedLabels = new HashSet<>(node.getAssignedLabels());
                assignedLabels.remove(node.getSelfLabel());
                if (!assignedLabels.isEmpty()) {
                    boolean first = true;
                    for (LabelAtom label : assignedLabels) {
                        if (first) {
                            listener.getLogger().print(" (");
                            first = false;
                        } else {
                            listener.getLogger().print(' ');
                        }
                        listener.getLogger().print(label.getName());
                    }
                    listener.getLogger().print(')');
                }
            }
        } else {
            listener.getLogger().print(Messages.AbstractBuild_Building());
        }
        
        lease = decideWorkspace(node, Computer.currentComputer().getWorkspaceList());

        workspace = lease.path.getRemote();
        listener.getLogger().println(Messages.AbstractBuild_BuildingInWorkspace(workspace));

        for (WorkspaceListener wl : WorkspaceListener.all()) {
            wl.beforeUse(AbstractBuild.this, lease.path, listener);
        }

        getProject().getScmCheckoutStrategy().preCheckout(AbstractBuild.this, launcher, this.listener);
        getProject().getScmCheckoutStrategy().checkout(this);

        if (!preBuild(listener,project.getProperties()))
            return Result.FAILURE;

        result = doRun(listener);
    } finally {
        if (!tearDownMarker.tornDown) {
            result = Result.combine(result, tearDownBuildEnvironments(listener));
        }
    }

    if (node.getChannel() != null) {
        launcher.kill(getCharacteristicEnvVars());
    }

    if (result==null)    result = getResult();
    if (result==null)    result = Result.SUCCESS;

    return result;
}

这段代码会获取到构建的时候所需要的一些环境资源Environment,还会拿到当前工作空间workspace目录,如果配置了相关的构建前检出步骤,还会在这里执行,最后再调用doRun方法,返回值为当前任务是否执行成功,接下来的调用嵌套便很繁琐了,关系是:doRun()->build()->perform()->…->perform()

public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws InterruptedException {
    FilePath ws = build.getWorkspace();
    if (ws == null) {
        Node node = build.getBuiltOn();
        if (node == null) {
            throw new NullPointerException("no such build node: " + build.getBuiltOnStr());
        }
        throw new NullPointerException("no workspace from node " + node + " which is computer " + node.toComputer() + " and has channel " + node.getChannel());
    }
    FilePath script=null;
    int r = -1;
    try {
        try {
            script = createScriptFile(ws);
        } catch (IOException e) {
            Util.displayIOException(e,listener);
            Functions.printStackTrace(e, listener.fatalError(Messages.CommandInterpreter_UnableToProduceScript()));
            return false;
        }

        try {
            EnvVars envVars = build.getEnvironment(listener);
            for(Map.Entry<String,String> e : build.getBuildVariables().entrySet())
                envVars.put(e.getKey(),e.getValue());

            launcher.prepareFilterRules(build, this);

            Launcher.ProcStarter procStarter = launcher.launch();
            procStarter.cmds(buildCommandLine(script))
                    .envs(envVars)
                    .stdout(listener)
                    .pwd(ws);

            try {
                Proc proc = procStarter.start();
                r = join(proc);
            } catch (EnvVarsFilterException se) {
                LOGGER.log(Level.FINE, "Environment variable filtering failed", se);
                return false;
            }

            if(isErrorlevelForUnstableBuild(r)) {
                build.setResult(Result.UNSTABLE);
                r = 0;
            }
        } catch (IOException e) {
			(...)
        }
        return r==0;
    } finally {
		(...)
    }
}

到这一步就已经开始执行我们配置的shell命令了,还可以继续再深入,感兴趣就继续跟下去吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值