提交 95c5ea27 编写于 作者: K Kohsuke Kawaguchi

Matrix project execution order is made pluggable.

上级 5301b262
......@@ -69,6 +69,8 @@ Upcoming changes</a>
<li class=rfe>
Performance improvement in JavaScript on modern browsers
(<a href="https://github.com/jenkinsci/jenkins/pull/276">pull 276</a>)
<li class=rfe>
Matrix project execution order is made pluggable.
</ul>
</div><!--=TRUNK-END=-->
......
package hudson.matrix;
import hudson.AbortException;
import hudson.Extension;
import hudson.console.HyperlinkNote;
import hudson.matrix.listeners.MatrixBuildListener;
import hudson.model.BuildListener;
import hudson.model.Cause.UpstreamCause;
import hudson.model.ParametersAction;
import hudson.model.Queue;
import hudson.model.ResourceController;
import hudson.model.Result;
import hudson.model.Run;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
/**
* {@link MatrixExecutionStrategy} that captures historical behavior.
*
* <p>
* This class is somewhat complex because historically this wasn't an extension point and so
* people tried to put various logics that cover different use cases into one place.
* Going forward, people are encouraged to create subtypes to implement a custom logic that suits their needs.
*
* @author Kohsuke Kawaguchi
* @since 1.456
*/
public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy {
private volatile boolean runSequentially;
/**
* Filter to select a number of combinations to build first
*/
private volatile String touchStoneCombinationFilter;
/**
* Required result on the touchstone combinations, in order to
* continue with the rest
*/
private volatile Result touchStoneResultCondition;
private volatile MatrixConfigurationSorter sorter;
@DataBoundConstructor
public DefaultMatrixExecutionStrategyImpl(Boolean runSequentially, boolean hasTouchStoneCombinationFilter, String touchStoneCombinationFilter, Result touchStoneResultCondition, MatrixConfigurationSorter sorter) {
this(runSequentially!=null ? runSequentially : false,
hasTouchStoneCombinationFilter ? touchStoneCombinationFilter : null,
hasTouchStoneCombinationFilter ? touchStoneResultCondition : null,
sorter);
}
public DefaultMatrixExecutionStrategyImpl(boolean runSequentially, String touchStoneCombinationFilter, Result touchStoneResultCondition, MatrixConfigurationSorter sorter) {
this.runSequentially = runSequentially;
this.touchStoneCombinationFilter = touchStoneCombinationFilter;
this.touchStoneResultCondition = touchStoneResultCondition;
this.sorter = sorter;
}
public DefaultMatrixExecutionStrategyImpl() {
this(false,false,null,null,null);
}
/**
* If true, {@link MatrixRun}s are run sequentially, instead of running in parallel.
*
* TODO: this should be subsumed by {@link ResourceController}.
*/
public boolean isRunSequentially() {
return runSequentially;
}
public void setRunSequentially(boolean runSequentially) {
this.runSequentially = runSequentially;
}
public String getTouchStoneCombinationFilter() {
return touchStoneCombinationFilter;
}
public void setTouchStoneCombinationFilter(String touchStoneCombinationFilter) {
this.touchStoneCombinationFilter = touchStoneCombinationFilter;
}
public Result getTouchStoneResultCondition() {
return touchStoneResultCondition;
}
public void setTouchStoneResultCondition(Result touchStoneResultCondition) {
this.touchStoneResultCondition = touchStoneResultCondition;
}
public MatrixConfigurationSorter getSorter() {
return sorter;
}
public void setSorter(MatrixConfigurationSorter sorter) {
this.sorter = sorter;
}
public Result run(MatrixBuild build, List<MatrixAggregator> aggregators, BuildListener listener) throws InterruptedException, IOException {
MatrixProject p = build.getProject();
PrintStream logger = listener.getLogger();
Collection<MatrixConfiguration> touchStoneConfigurations = new HashSet<MatrixConfiguration>();
Collection<MatrixConfiguration> delayedConfigurations = new HashSet<MatrixConfiguration>();
for (MatrixConfiguration c: p.getActiveConfigurations()) {
if (!MatrixBuildListener.buildConfiguration(build, c))
continue; // skip rebuild
if (touchStoneCombinationFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), p.getTouchStoneCombinationFilter())) {
touchStoneConfigurations.add(c);
} else {
delayedConfigurations.add(c);
}
}
if (notifyStartBuild(aggregators)) return Result.FAILURE;
if (sorter != null) {
touchStoneConfigurations = createTreeSet(touchStoneConfigurations, sorter);
delayedConfigurations = createTreeSet(delayedConfigurations,sorter);
}
if(!runSequentially)
for(MatrixConfiguration c : touchStoneConfigurations)
scheduleConfigurationBuild(build, listener, c);
Result r = Result.SUCCESS;
for (MatrixConfiguration c : touchStoneConfigurations) {
if(runSequentially)
scheduleConfigurationBuild(build, listener, c);
MatrixRun run = waitForCompletion(build, listener, c);
notifyEndBuild(run,aggregators);
r = r.combine(getResult(run));
}
if (touchStoneResultCondition != null && r.isWorseThan(touchStoneResultCondition)) {
logger.printf("Touchstone configurations resulted in %s, so aborting...%n", r);
return r;
}
if(!p.isRunSequentially())
for(MatrixConfiguration c : delayedConfigurations)
scheduleConfigurationBuild(build, listener, c);
for (MatrixConfiguration c : delayedConfigurations) {
if(p.isRunSequentially())
scheduleConfigurationBuild(build, listener, c);
MatrixRun run = waitForCompletion(build, listener, c);
notifyEndBuild(run,aggregators);
logger.println(Messages.MatrixBuild_Completed(HyperlinkNote.encodeTo('/' + c.getUrl(), c.getDisplayName()), getResult(run)));
r = r.combine(getResult(run));
}
return r;
}
private Result getResult(@Nullable MatrixRun run) {
// null indicates that the run was cancelled before it even gets going
return run!=null ? run.getResult() : Result.ABORTED;
}
private boolean notifyStartBuild(List<MatrixAggregator> aggregators) throws InterruptedException, IOException {
for (MatrixAggregator a : aggregators)
if(!a.startBuild())
return true;
return false;
}
private void notifyEndBuild(MatrixRun b, List<MatrixAggregator> aggregators) throws InterruptedException, IOException {
if (b==null) return; // can happen if the configuration run gets cancelled before it gets started.
for (MatrixAggregator a : aggregators)
if(!a.endRun(b))
throw new AbortException();
}
private <T> TreeSet<T> createTreeSet(Collection<T> items, Comparator<T> sorter) {
TreeSet<T> r = new TreeSet<T>(sorter);
r.addAll(items);
return r;
}
private void scheduleConfigurationBuild(MatrixBuild build, BuildListener listener, MatrixConfiguration c) {
listener.getLogger().println(Messages.MatrixBuild_Triggering(HyperlinkNote.encodeTo('/' + c.getUrl(), c.getDisplayName())));
c.scheduleBuild(build.getAction(ParametersAction.class), new UpstreamCause((Run)build));
}
private MatrixRun waitForCompletion(MatrixBuild build, BuildListener listener, MatrixConfiguration c) throws InterruptedException, IOException {
String whyInQueue = "";
long startTime = System.currentTimeMillis();
// wait for the completion
int appearsCancelledCount = 0;
while(true) {
MatrixRun b = c.getBuildByNumber(build.getNumber());
// two ways to get beyond this. one is that the build starts and gets done,
// or the build gets cancelled before it even started.
if(b!=null && !b.isBuilding()) {
Result buildResult = b.getResult();
if(buildResult!=null)
return b;
}
Queue.Item qi = c.getQueueItem();
if(b==null && qi==null)
appearsCancelledCount++;
else
appearsCancelledCount = 0;
if(appearsCancelledCount>=5) {
// there's conceivably a race condition in computating b and qi, as their computation
// are not synchronized. There are indeed several reports of Hudson incorrectly assuming
// builds being cancelled. See
// http://www.nabble.com/Master-slave-problem-tt14710987.html and also
// http://www.nabble.com/Anyone-using-AccuRev-plugin--tt21634577.html#a21671389
// because of this, we really make sure that the build is cancelled by doing this 5
// times over 5 seconds
listener.getLogger().println(Messages.MatrixBuild_AppearsCancelled(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())));
return null;
}
if(qi!=null) {
// if the build seems to be stuck in the queue, display why
String why = qi.getWhy();
if(!why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
listener.getLogger().println(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())+" is still in the queue: "+why);
whyInQueue = why;
}
}
Thread.sleep(1000);
}
}
@Extension
public static class DescriptorImpl extends MatrixExecutionStrategyDescriptor {
@Override
public String getDisplayName() {
return "Classic";
}
}
}
......@@ -24,16 +24,14 @@
*/
package hudson.matrix;
import hudson.AbortException;
import hudson.Util;
import hudson.console.HyperlinkNote;
import hudson.matrix.listeners.MatrixBuildListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Cause.UpstreamCause;
import hudson.model.Executor;
import hudson.model.Fingerprint;
import hudson.model.ParametersAction;
import hudson.model.Queue;
import hudson.model.Result;
import jenkins.model.Jenkins;
......@@ -48,10 +46,7 @@ import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
import javax.servlet.ServletException;
......@@ -274,76 +269,23 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
listUpAggregators(listener, p.getBuildWrappers().values());
axes = p.getAxes();
Collection<MatrixConfiguration> activeConfigurations = p.getActiveConfigurations();
final int n = getNumber();
String touchStoneFilter = p.getTouchStoneCombinationFilter();
Collection<MatrixConfiguration> touchStoneConfigurations = new HashSet<MatrixConfiguration>();
Collection<MatrixConfiguration> delayedConfigurations = new HashSet<MatrixConfiguration>();
for (MatrixConfiguration c: activeConfigurations) {
if (!MatrixBuildListener.buildConfiguration(MatrixBuild.this, c))
continue; // skip rebuild
if (touchStoneFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), p.getTouchStoneCombinationFilter())) {
touchStoneConfigurations.add(c);
} else {
delayedConfigurations.add(c);
}
}
for (MatrixAggregator a : aggregators)
if(!a.startBuild())
return Result.FAILURE;
MatrixConfigurationSorter sorter = p.getSorter();
if (sorter != null) {
touchStoneConfigurations = createTreeSet(touchStoneConfigurations, sorter);
delayedConfigurations = createTreeSet(delayedConfigurations,sorter);
}
try {
if(!p.isRunSequentially())
for(MatrixConfiguration c : touchStoneConfigurations)
scheduleConfigurationBuild(logger, c);
Result r = Result.SUCCESS;
for (MatrixConfiguration c : touchStoneConfigurations) {
if(p.isRunSequentially())
scheduleConfigurationBuild(logger, c);
Result buildResult = waitForCompletion(listener, c);
r = r.combine(buildResult);
}
if (p.getTouchStoneResultCondition() != null && r.isWorseThan(p.getTouchStoneResultCondition())) {
logger.printf("Touchstone configurations resulted in %s, so aborting...%n", r);
return r;
}
if(!p.isRunSequentially())
for(MatrixConfiguration c : delayedConfigurations)
scheduleConfigurationBuild(logger, c);
for (MatrixConfiguration c : delayedConfigurations) {
if(p.isRunSequentially())
scheduleConfigurationBuild(logger, c);
Result buildResult = waitForCompletion(listener, c);
logger.println(Messages.MatrixBuild_Completed(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName()), buildResult));
r = r.combine(buildResult);
}
return r;
return p.getExecutionStrategy().run(MatrixBuild.this, aggregators, listener);
} catch( InterruptedException e ) {
logger.println("Aborted");
Executor x = Executor.currentExecutor();
x.recordCauseOfInterruption(MatrixBuild.this, listener);
return x.abortResult();
} catch (AggregatorFailureException e) {
} catch (AbortException e) {
logger.println(e.getMessage());
return Result.FAILURE;
}
finally {
} finally {
// if the build was aborted in the middle. Cancel all the configuration builds.
Queue q = Jenkins.getInstance().getQueue();
synchronized(q) {// avoid micro-locking in q.cancel.
for (MatrixConfiguration c : activeConfigurations) {
final int n = getNumber();
for (MatrixConfiguration c : p.getActiveConfigurations()) {
if(q.cancel(c))
logger.println(Messages.MatrixBuild_Cancelled(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())));
MatrixRun b = c.getBuildByNumber(n);
......@@ -370,78 +312,9 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
}
}
private Result waitForCompletion(BuildListener listener, MatrixConfiguration c) throws InterruptedException, IOException, AggregatorFailureException {
String whyInQueue = "";
long startTime = System.currentTimeMillis();
// wait for the completion
int appearsCancelledCount = 0;
while(true) {
MatrixRun b = c.getBuildByNumber(getNumber());
// two ways to get beyond this. one is that the build starts and gets done,
// or the build gets cancelled before it even started.
Result buildResult = null;
if(b!=null && !b.isBuilding())
buildResult = b.getResult();
Queue.Item qi = c.getQueueItem();
if(b==null && qi==null)
appearsCancelledCount++;
else
appearsCancelledCount = 0;
if(appearsCancelledCount>=5) {
// there's conceivably a race condition in computating b and qi, as their computation
// are not synchronized. There are indeed several reports of Hudson incorrectly assuming
// builds being cancelled. See
// http://www.nabble.com/Master-slave-problem-tt14710987.html and also
// http://www.nabble.com/Anyone-using-AccuRev-plugin--tt21634577.html#a21671389
// because of this, we really make sure that the build is cancelled by doing this 5
// times over 5 seconds
listener.getLogger().println(Messages.MatrixBuild_AppearsCancelled(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())));
buildResult = Result.ABORTED;
}
if(buildResult!=null) {
for (MatrixAggregator a : aggregators)
if(!a.endRun(b))
throw new AggregatorFailureException();
return buildResult;
}
if(qi!=null) {
// if the build seems to be stuck in the queue, display why
String why = qi.getWhy();
if(!why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
listener.getLogger().println(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())+" is still in the queue: "+why);
whyInQueue = why;
}
}
Thread.sleep(1000);
}
}
private void scheduleConfigurationBuild(PrintStream logger, MatrixConfiguration c) {
logger.println(Messages.MatrixBuild_Triggering(HyperlinkNote.encodeTo('/'+ c.getUrl(),c.getDisplayName())));
c.scheduleBuild(getAction(ParametersAction.class), new UpstreamCause(MatrixBuild.this));
}
public void post2(BuildListener listener) throws Exception {
for (MatrixAggregator a : aggregators)
a.endBuild();
}
}
private <T> TreeSet<T> createTreeSet(Collection<T> items, Comparator<T> sorter) {
TreeSet<T> r = new TreeSet<T>(sorter);
r.addAll(items);
return r;
}
/**
* A private exception to help maintain the correct control flow after extracting the 'waitForCompletion' method
*/
private static class AggregatorFailureException extends Exception {}
}
......@@ -19,7 +19,7 @@ public abstract class MatrixConfigurationSorterDescriptor extends Descriptor<Mat
}
/**
* Returns all the registered {@link AxisDescriptor}s.
* Returns all the registered {@link MatrixConfigurationSorterDescriptor}s.
*/
public static DescriptorExtensionList<MatrixConfigurationSorter,MatrixConfigurationSorterDescriptor> all() {
return Jenkins.getInstance().<MatrixConfigurationSorter,MatrixConfigurationSorterDescriptor>getDescriptorList(MatrixConfigurationSorter.class);
......
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.matrix;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import hudson.model.BuildListener;
import hudson.model.Result;
import java.io.IOException;
import java.util.List;
/**
* Controls the execution sequence of {@link MatrixConfiguration} when {@link MatrixProject} builds,
* including what degree it gets serialized/parallelled, how the whole build is abandoned when
* some fails, etc.
*
* @author Kohsuke Kawaguchi
* @sicne 1.456
*/
public abstract class MatrixExecutionStrategy extends AbstractDescribableImpl<MatrixExecutionStrategy> implements ExtensionPoint {
public abstract Result run(MatrixBuild build, List<MatrixAggregator> aggregators, BuildListener listener) throws InterruptedException, IOException;
@Override
public MatrixExecutionStrategyDescriptor getDescriptor() {
return (MatrixExecutionStrategyDescriptor)super.getDescriptor();
}
}
/*
* The MIT License
*
* Copyright (c) 2012, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.matrix;
import hudson.DescriptorExtensionList;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;
/**
* @author Kohsuke Kawaguchi
* @since 1.456
*/
public abstract class MatrixExecutionStrategyDescriptor extends Descriptor<MatrixExecutionStrategy> {
protected MatrixExecutionStrategyDescriptor(Class<? extends MatrixExecutionStrategy> clazz) {
super(clazz);
}
protected MatrixExecutionStrategyDescriptor() {
}
/**
* Returns all the registered {@link MatrixExecutionStrategyDescriptor}s.
*/
public static DescriptorExtensionList<MatrixExecutionStrategy,MatrixExecutionStrategyDescriptor> all() {
return Jenkins.getInstance().<MatrixExecutionStrategy,MatrixExecutionStrategyDescriptor>getDescriptorList(MatrixExecutionStrategy.class);
}
}
......@@ -42,7 +42,6 @@ import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.Queue.FlyweightTask;
import hudson.model.ResourceController;
import hudson.model.Result;
import hudson.model.SCMedItem;
import hudson.model.Saveable;
......@@ -131,20 +130,34 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
@CopyOnWrite
private transient /*final*/ Set<MatrixConfiguration> activeConfigurations = new LinkedHashSet<MatrixConfiguration>();
private boolean runSequentially;
/**
* @deprecated as of 1.456
* Moved to {@link DefaultMatrixExecutionStrategyImpl}
*/
private transient Boolean runSequentially;
/**
* Filter to select a number of combinations to build first
* @deprecated as of 1.456
* Moved to {@link DefaultMatrixExecutionStrategyImpl}
*/
private String touchStoneCombinationFilter;
private transient String touchStoneCombinationFilter;
/**
* Required result on the touchstone combinations, in order to
* continue with the rest
* @deprecated as of 1.456
* Moved to {@link DefaultMatrixExecutionStrategyImpl}
*/
private Result touchStoneResultCondition;
private MatrixConfigurationSorter sorter;
private transient Result touchStoneResultCondition;
/**
* @deprecated as of 1.456
* Moved to {@link DefaultMatrixExecutionStrategyImpl}
*/
private transient MatrixConfigurationSorter sorter;
private MatrixExecutionStrategy executionStrategy;
public MatrixProject(String name) {
this(Jenkins.getInstance(), name);
......@@ -154,12 +167,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
super(parent, name);
}
/**
* @return can be null (to indicate that the configurations should be left to their natural order.)
*/
public MatrixConfigurationSorter getSorter() {
return sorter;
}
/**
* {@link MatrixProject} is relevant with all the labels its configurations are relevant.
......@@ -173,9 +181,35 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return super.getRelevantLabels();
}
/**
* @return can be null (to indicate that the configurations should be left to their natural order.)
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#getSorter()}.
* This method tries to emulate the previous behavior the best it can, but will return null
* if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public MatrixConfigurationSorter getSorter() {
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
return dm.getSorter();
}
return null;
}
/**
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#setSorter(MatrixConfigurationSorter)}.
* This method tries to emulate the previous behavior the best it can, but will fall back
* to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public void setSorter(MatrixConfigurationSorter sorter) throws IOException {
this.sorter = sorter;
save();
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
dm.setSorter(sorter);
save();
}
}
public AxisList getAxes() {
......@@ -191,18 +225,44 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
save();
}
public MatrixExecutionStrategy getExecutionStrategy() {
return executionStrategy;
}
public void setExecutionStrategy(MatrixExecutionStrategy executionStrategy) throws IOException {
if (executionStrategy ==null) throw new IllegalArgumentException();
this.executionStrategy = executionStrategy;
save();
}
/**
* If true, {@link MatrixRun}s are run sequentially, instead of running in parallel.
*
* TODO: this should be subsumed by {@link ResourceController}.
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#isRunSequentially()}.
* This method tries to emulate the previous behavior the best it can, but will return false
* if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public boolean isRunSequentially() {
return runSequentially;
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
return dm.isRunSequentially();
}
return false;
}
/**
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#setRunSequentially(boolean)}.
* This method tries to emulate the previous behavior the best it can, but will fall back
* to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public void setRunSequentially(boolean runSequentially) throws IOException {
this.runSequentially = runSequentially;
save();
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
dm.setRunSequentially(runSequentially);
save();
}
}
/**
......@@ -234,21 +294,66 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return combinationFilter;
}
/**
* @return can be null (to indicate that the configurations should be left to their natural order.)
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#getTouchStoneCombinationFilter()}.
* This method tries to emulate the previous behavior the best it can, but will return null
* if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public String getTouchStoneCombinationFilter() {
return touchStoneCombinationFilter;
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
return dm.getTouchStoneCombinationFilter();
}
return null;
}
public void setTouchStoneCombinationFilter(
String touchStoneCombinationFilter) {
this.touchStoneCombinationFilter = touchStoneCombinationFilter;
/**
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#setTouchStoneCombinationFilter(String)}.
* This method tries to emulate the previous behavior the best it can, but will fall back
* to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public void setTouchStoneCombinationFilter(String touchStoneCombinationFilter) throws IOException {
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
dm.setTouchStoneCombinationFilter(touchStoneCombinationFilter);
save();
}
}
/**
* @return can be null (to indicate that the configurations should be left to their natural order.)
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#getTouchStoneResultCondition()}.
* This method tries to emulate the previous behavior the best it can, but will return null
* if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public Result getTouchStoneResultCondition() {
return touchStoneResultCondition;
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
return dm.getTouchStoneResultCondition();
}
return null;
}
public void setTouchStoneResultCondition(Result touchStoneResultCondition) {
this.touchStoneResultCondition = touchStoneResultCondition;
/**
* @deprecated as of 1.456
* Use {@link DefaultMatrixExecutionStrategyImpl#setTouchStoneResultCondition(Result)}.
* This method tries to emulate the previous behavior the best it can, but will fall back
* to no-op if the current {@link MatrixExecutionStrategy} is not the default one.
*/
public void setTouchStoneResultCondition(Result touchStoneResultCondition) throws IOException {
MatrixExecutionStrategy e = executionStrategy;
if (e instanceof DefaultMatrixExecutionStrategyImpl) {
DefaultMatrixExecutionStrategyImpl dm = (DefaultMatrixExecutionStrategyImpl) e;
dm.setTouchStoneResultCondition(touchStoneResultCondition);
save();
}
}
@Override
......@@ -261,7 +366,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
r.addAll(step.getProjectActions(this));
for (BuildWrapper step : buildWrappers)
r.addAll(step.getProjectActions(this));
for (Trigger trigger : triggers)
for (Trigger<?> trigger : triggers)
r.addAll(trigger.getProjectActions());
return r;
......@@ -289,6 +394,12 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
};
}
@Override
public void onCreatedFromScratch() {
executionStrategy = new DefaultMatrixExecutionStrategyImpl();
super.onCreatedFromScratch();
}
@Override
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
super.onLoad(parent,name);
......@@ -296,6 +407,10 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
builders.setOwner(this);
publishers.setOwner(this);
buildWrappers.setOwner(this);
if (executionStrategy ==null)
executionStrategy = new DefaultMatrixExecutionStrategyImpl(runSequentially,touchStoneCombinationFilter,touchStoneResultCondition,sorter);
rebuildConfigurations();
}
......@@ -586,14 +701,12 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
} else {
this.combinationFilter = null;
}
if (req.getParameter("hasTouchStoneCombinationFilter")!=null) {
this.touchStoneCombinationFilter = Util.nullify(req.getParameter("touchStoneCombinationFilter"));
String touchStoneResultCondition = req.getParameter("touchStoneResultCondition");
this.touchStoneResultCondition = Result.fromString(touchStoneResultCondition);
} else {
this.touchStoneCombinationFilter = null;
}
List<MatrixExecutionStrategyDescriptor> esd = getDescriptor().getExecutionStrategyDescriptors();
if (esd.size()>1)
executionStrategy = req.bindJSON(MatrixExecutionStrategy.class,json.getJSONObject("executionStrategy"));
else
executionStrategy = req.bindJSON(esd.get(0).clazz,json.getJSONObject("executionStrategy"));
// parse system axes
DescribableList<Axis,AxisDescriptor> newAxes = new DescribableList<Axis,AxisDescriptor>(this);
......@@ -601,19 +714,6 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
checkAxisNames(newAxes);
this.axes = new AxisList(newAxes.toList());
runSequentially = json.optBoolean("runSequentially");
// set sorter if any sorter is chosen
if (runSequentially) {
MatrixConfigurationSorter s = req.bindJSON(MatrixConfigurationSorter.class,json.optJSONObject("sorter"));
if (s!=null) s.validate(this);
if (s instanceof NoopMatrixConfigurationSorter) s=null;
setSorter(s);
} else {
setSorter(null);
}
buildWrappers.rebuild(req, json, BuildWrappers.getFor(this));
builders.rebuildHetero(req, json, Builder.all(), "builder");
publishers.rebuild(req, json, BuildStepDescriptor.filter(Publisher.all(),this.getClass()));
......@@ -677,9 +777,17 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return r;
}
/**
* @deprecated as of 1.456
* This was only exposed for Jelly.
*/
public List<MatrixConfigurationSorterDescriptor> getSorterDescriptors() {
return MatrixConfigurationSorterDescriptor.all();
}
public List<MatrixExecutionStrategyDescriptor> getExecutionStrategyDescriptors() {
return MatrixExecutionStrategyDescriptor.all();
}
}
private static final Logger LOGGER = Logger.getLogger(MatrixProject.class.getName());
......
......@@ -27,10 +27,13 @@ import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
import hudson.cli.declarative.OptionHandlerExtension;
import hudson.util.EditDistance;
import hudson.util.EnumConverter;
import org.apache.commons.beanutils.Converter;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.*;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.export.CustomExportedBean;
import java.io.Serializable;
......@@ -183,4 +186,12 @@ public final class Result implements Serializable, CustomExportedBean {
return "STATUS";
}
}
static {
Stapler.CONVERT_UTILS.register(new Converter() {
public Object convert(Class type, Object value) {
return Result.fromString(value.toString());
}
}, Result.class);
}
}
package hudson.matrix.DefaultMatrixExecutionStrategyImpl;
import hudson.matrix.MatrixConfigurationSorterDescriptor
import hudson.model.Result;
def f = namespace(lib.FormTagLib)
f.optionalBlock (field:"runSequentially", title:_("Run each configuration sequentially"), inline:true) {
if (MatrixConfigurationSorterDescriptor.all().size()>1) {
f.dropdownDescriptorSelector(title:_("Execution order of builds"), field:"sorter")
}
}
f.optionalBlock (field:"hasTouchStoneCombinationFilter", title:_("Execute touchstone builds first"),
inline:true, checked:my.touchStoneCombinationFilter!=null) {
// TODO: help="/help/matrix/touchstone.html">
// TODO: move l10n from MatrixProject/configEntries.jelly
f.entry(title:_("Filter"), field:"touchStoneCombinationFilter") {
f.textbox()
}
f.entry(title:_("Required result"), field:"touchStoneResultCondition", description:_("required.result.description")) {
select(name:"touchStoneResultCondition") {
f.option(value:"SUCCESS", selected:my.touchStoneResultCondition==Result.SUCCESS, _("Stable"))
f.option(value:"UNSTABLE", selected:my.touchStoneResultCondition==Result.UNSTABLE, _("Unstable"))
}
}
}
......@@ -63,12 +63,6 @@ THE SOFTWARE.
addCaption="${%Add axis}"/>
</f:block>
<f:optionalBlock field="runSequentially" title="${%Run each configuration sequentially}" inline="true">
<j:if test="${descriptor.sorterDescriptors.size() gt 1}">
<f:dropdownDescriptorSelector title="${%Execution order of builds}" field="sorter"/>
</j:if>
</f:optionalBlock>
<f:optionalBlock name="hasCombinationFilter" title="${%Combination Filter}" checked="${!empty(it.combinationFilter)}"
help="/help/matrix/combinationfilter.html">
<f:entry title="${%Filter}">
......@@ -76,19 +70,15 @@ THE SOFTWARE.
</f:entry>
</f:optionalBlock>
<f:optionalBlock name="hasTouchStoneCombinationFilter" title="${%Execute touchstone builds first}" checked="${!empty(it.touchStoneCombinationFilter)}"
help="/help/matrix/touchstone.html">
<f:entry title="${%Filter}">
<f:textbox name="touchStoneCombinationFilter" value="${it.touchStoneCombinationFilter}" />
</f:entry>
<f:entry title="${%Required result}" description="${%required.result.description}">
<select name="touchStoneResultCondition">
<f:option value="SUCCESS" selected='${it.touchStoneResultCondition.toExportedObject()=="SUCCESS"}'>${%Stable}</f:option>
<f:option value="UNSTABLE" selected='${it.touchStoneResultCondition.toExportedObject()=="UNSTABLE"}'>${%Unstable}</f:option>
</select>
</f:entry>
</f:optionalBlock>
<j:choose>
<j:when test="${descriptor.executionStrategyDescriptors.size() gt 1}">
<f:dropdownDescriptorSelector title="${%Execution Strategy}" field="executionStrategy"/>
</j:when>
<j:otherwise>
<!-- for the default case when there's only one execution strategy, render it inline to simplify the UI -->
<f:property field="executionStrategy" propertyDescriptor="${descriptor.executionStrategyDescriptors.get(0)}"/>
</j:otherwise>
</j:choose>
</f:section>
<p:config-buildWrappers />
......
package jenkins.model.GlobalProjectNamingStrategyConfiguration
package jenkins.model.ProjectNamingStrategy.PatternProjectNamingStrategy;
def f=namespace(lib.FormTagLib)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册