diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerAction.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerAction.java index 2ecd6f35..dbb405eb 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerAction.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerAction.java @@ -29,29 +29,31 @@ static class Trigger { final StepContext context; final boolean propagate; + final boolean waitForStart; /** Record of cancellation cause passed to {@link BuildTriggerStepExecution#stop}, if any. */ @CheckForNull Throwable interruption; - Trigger(StepContext context, boolean propagate) { + Trigger(StepContext context, boolean propagate, boolean waitForStart) { this.context = context; this.propagate = propagate; + this.waitForStart = waitForStart; } } private /* final */ List triggers; - BuildTriggerAction(StepContext context, boolean propagate) { + BuildTriggerAction(StepContext context, boolean propagate, boolean waitForStart) { triggers = new ArrayList<>(); - triggers.add(new Trigger(context, propagate)); + triggers.add(new Trigger(context, propagate, waitForStart)); } private Object readResolve() { if (triggers == null) { triggers = new ArrayList<>(); - triggers.add(new Trigger(context, propagate != null ? propagate : /* old serialized record */ true)); + triggers.add(new Trigger(context, propagate != null ? propagate : /* old serialized record */ true, false)); context = null; propagate = null; } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerListener.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerListener.java index 1bb8c078..0982132a 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerListener.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerListener.java @@ -31,6 +31,9 @@ public void onStarted(Run run, TaskListener listener) { TaskListener taskListener = stepContext.get(TaskListener.class); // encodeTo(Run) calls getDisplayName, which does not include the project name. taskListener.getLogger().println("Starting building: " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName())); + if (trigger.waitForStart) { + stepContext.onSuccess(new RunWrapper(run, false)); + } } catch (Exception e) { LOGGER.log(Level.WARNING, null, e); } @@ -43,30 +46,32 @@ public void onStarted(Run run, TaskListener listener) { @Override public void onCompleted(Run run, @NonNull TaskListener listener) { for (BuildTriggerAction.Trigger trigger : BuildTriggerAction.triggersFor(run)) { - StepContext stepContext = trigger.context; - LOGGER.log(Level.FINE, "completing {0} for {1}", new Object[] {run, stepContext}); - Result result = run.getResult(); - if (result == null) { /* probably impossible */ - result = Result.FAILURE; - } + if (!trigger.waitForStart) { + StepContext stepContext = trigger.context; + LOGGER.log(Level.FINE, "completing {0} for {1}", new Object[] {run, stepContext}); + Result result = run.getResult(); + if (result == null) { /* probably impossible */ + result = Result.FAILURE; + } - try { - stepContext.get(TaskListener.class).getLogger().println("Build " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName()) + " completed: " + result.toString()); - if (result != Result.SUCCESS) { - stepContext.get(FlowNode.class).addOrReplaceAction(new WarningAction(result)); + try { + stepContext.get(TaskListener.class).getLogger().println("Build " + ModelHyperlinkNote.encodeTo("/" + run.getUrl(), run.getFullDisplayName()) + " completed: " + result.toString()); + if (result != Result.SUCCESS) { + stepContext.get(FlowNode.class).addOrReplaceAction(new WarningAction(result)); + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, null, e); } - } catch (Exception e) { - LOGGER.log(Level.WARNING, null, e); - } - if (!trigger.propagate || result == Result.SUCCESS) { - if (trigger.interruption == null) { - stepContext.onSuccess(new RunWrapper(run, false)); + if (!trigger.propagate || result == Result.SUCCESS) { + if (trigger.interruption == null) { + stepContext.onSuccess(new RunWrapper(run, false)); + } else { + stepContext.onFailure(trigger.interruption); + } } else { - stepContext.onFailure(trigger.interruption); + stepContext.onFailure(new FlowInterruptedException(result, false, new DownstreamFailureCause(run))); } - } else { - stepContext.onFailure(new FlowInterruptedException(result, false, new DownstreamFailureCause(run))); } } run.removeActions(BuildTriggerAction.class); diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep.java index 55c17b81..63a6f88e 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep.java @@ -57,6 +57,7 @@ public class BuildTriggerStep extends Step { private final String job; private List parameters; private boolean wait = true; + private boolean waitForStart = false; private boolean propagate = true; private Integer quietPeriod; @@ -85,6 +86,14 @@ public boolean getWait() { this.wait = wait; } + public boolean getWaitForStart() { + return waitForStart; + } + + @DataBoundSetter public void setWaitForStart(boolean waitForStart) { + this.waitForStart = waitForStart; + } + public Integer getQuietPeriod() { return quietPeriod; } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java index 5ea86353..96ed7106 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepExecution.java @@ -71,7 +71,7 @@ public boolean start() throws Exception { throw new AbortException("No item named " + job + " found"); } item.checkPermission(Item.BUILD); - if (step.getWait() && !(item instanceof Job)) { + if ((step.getWait() || step.getWaitForStart()) && !(item instanceof Job)) { // TODO find some way of allowing ComputedFolders to hook into the listener code throw new AbortException("Waiting for non-job items is not supported"); } @@ -86,9 +86,9 @@ public boolean start() throws Exception { getContext().get(FlowNode.class).addAction(new LabelAction(Messages.BuildTriggerStepExecution_building_(project.getFullDisplayName()))); - if (step.getWait()) { + if (step.getWait() || step.getWaitForStart()) { StepContext context = getContext(); - actions.add(new BuildTriggerAction(context, step.isPropagate())); + actions.add(new BuildTriggerAction(context, step.isPropagate(), step.getWaitForStart())); LOGGER.log(Level.FINER, "scheduling a build of {0} from {1}", new Object[]{project, context}); } @@ -111,9 +111,9 @@ public boolean start() throws Exception { Queue.Task task = (Queue.Task) item; getContext().get(TaskListener.class).getLogger().println("Scheduling item: " + ModelHyperlinkNote.encodeTo(item)); getContext().get(FlowNode.class).addAction(new LabelAction(Messages.BuildTriggerStepExecution_building_(task.getFullDisplayName()))); - if (step.getWait()) { + if (step.getWait() || step.getWaitForStart()) { StepContext context = getContext(); - actions.add(new BuildTriggerAction(context, step.isPropagate())); + actions.add(new BuildTriggerAction(context, step.isPropagate(), step.getWaitForStart())); LOGGER.log(Level.FINER, "scheduling a build of {0} from {1}", new Object[]{task, context}); } @@ -144,7 +144,7 @@ public boolean start() throws Exception { : item.getClass().getName()) + " which is not something that can be built"); } - if (step.getWait()) { + if (step.getWait() || step.getWaitForStart()) { return false; } else { getContext().onSuccess(null); diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/config.jelly b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/config.jelly index 7f8f82e4..173137b3 100644 --- a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/config.jelly @@ -32,6 +32,9 @@ THE SOFTWARE. + + + diff --git a/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/help-waitForStart.html b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/help-waitForStart.html new file mode 100644 index 00000000..4c4c841a --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStep/help-waitForStart.html @@ -0,0 +1,4 @@ +
+ If true, the pipeline will wait for the downstream build to start before jumping to the next step. Defaults to false. + Overrides the value of wait. +
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java index d9d5d3d4..e914dbdf 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/build/BuildTriggerStepTest.java @@ -453,6 +453,13 @@ public void cancelBuildQueue() throws Exception { j.buildAndAssertSuccess(us); } + @Test public void waitForStart() throws Exception { + j.createFreeStyleProject("ds").getBuildersList().add(new FailureBuilder()); + WorkflowJob us = j.jenkins.createProject(WorkflowJob.class, "us"); + us.setDefinition(new CpsFlowDefinition("build job: 'ds', waitForStart: true", true)); + j.assertLogContains("Starting building:", j.buildAndAssertSuccess(us)); + } + @Test public void rejectedStart() throws Exception { j.createFreeStyleProject("ds"); WorkflowJob us = j.jenkins.createProject(WorkflowJob.class, "us");