Skip to content

Commit

Permalink
Implement option to wait only until the downstream build is started (#…
Browse files Browse the repository at this point in the history
…105)

* Implement option to wait only until the downstream build is started

* Remove old Trigger constructor

* Update readResolve to call new Trigger constructor
  • Loading branch information
stuartrowe committed Mar 4, 2023
1 parent bb555eb commit d08f550
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Trigger> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class BuildTriggerStep extends Step {
private final String job;
private List<ParameterValue> parameters;
private boolean wait = true;
private boolean waitForStart = false;
private boolean propagate = true;
private Integer quietPeriod;

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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});
}

Expand All @@ -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});
}

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ THE SOFTWARE.
<f:entry field="wait">
<f:checkbox default="true" title="Wait for completion"/>
</f:entry>
<f:entry field="waitForStart">
<f:checkbox default="false" title="Wait until the build starts"/>
</f:entry>
<f:entry field="propagate">
<f:checkbox default="true" title="Propagate errors"/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
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 <code>wait</code>.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit d08f550

Please sign in to comment.