提交 5d78f3c9 编写于 作者: K Kohsuke Kawaguchi

Merge branch 'pull-449'

......@@ -55,7 +55,9 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=>
<li class=rfe>
Added a new hook to enable matrix project axes to change its values per build.
(<a href="https://github.com/jenkinsci/jenkins/pull/449">pull 449</a>)
</ul>
</div><!--=TRUNK-END=-->
......
......@@ -27,6 +27,7 @@ import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.model.AbstractDescribableImpl;
import jenkins.model.Jenkins;
import hudson.util.QuotedStringTokenizer;
......@@ -149,6 +150,37 @@ public class Axis extends AbstractDescribableImpl<Axis> implements Comparable<Ax
return Collections.unmodifiableList(values);
}
/**
* Called right at the beginning of {@link MatrixBuild} execution to allow {@link Axis} to update {@link #values}
* based on the current build.
*
* <p>
* Historically, axes values are considered static. They were assumed to reflect what the user has typed in,
* and their values are changed only when the project is reconfigured. So abstractions are built around this
* notion, and so for example {@link MatrixProject} has the current axes and their values, which it uses
* to render its UI.
*
* <p>
* So when the need was identified to change the values of axes per build, we decided that this be represented
* as a kind of project configuration update (where a project gets reconfigured every time a build runs), and
* this call back was added to allow {@link Axis} to update the next return value from the {@link #getValues()}
* (which is typically done by updating {@link #values}.)
*
* <p>
* While it is not strictly required, because of these historical reasons, UI will look better if
* Future calls to {@link Axis#getValues()} return the same values as what this method returns (until
* the next rebuild call).
*
* @param context
* The ongoing build. Never null.
* @return
* Never null. Returns the updated set of values.
* @since 1.471
*/
public List<String> rebuild(MatrixBuildExecution context) {
return getValues();
}
@Override
public AxisDescriptor getDescriptor() {
return (AxisDescriptor)super.getDescriptor();
......
......@@ -23,15 +23,16 @@
*/
package hudson.matrix;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.thoughtworks.xstream.XStream;
import hudson.Util;
import hudson.util.RobustCollectionConverter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Arrays;
import javax.annotation.Nullable;
import java.util.*;
/**
* List of {@link Axis}.
......@@ -74,43 +75,16 @@ public class AxisList extends ArrayList<Axis> {
* List up all the possible combinations of this list.
*/
public Iterable<Combination> list() {
final int[] base = new int[size()];
if (base.length==0) return Collections.<Combination>emptyList();
int b = 1;
for( int i=size()-1; i>=0; i-- ) {
base[i] = b;
b *= get(i).size();
}
final int total = b; // number of total combinations
return new Iterable<Combination>() {
public Iterator<Combination> iterator() {
return new Iterator<Combination>() {
private int counter = 0;
public boolean hasNext() {
return counter<total;
}
public Combination next() {
String[] data = new String[size()];
int x = counter++;
for( int i=0; i<data.length; i++) {
data[i] = get(i).value(x/base[i]);
x %= base[i];
}
assert x==0;
return new Combination(AxisList.this,data);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
List<Set<String>> axesList = Lists.newArrayList();
for (Axis axis : this)
axesList.add(new LinkedHashSet<String>(axis.getValues()));
return Iterables.transform(Sets.cartesianProduct(axesList), new Function<List<String>, Combination>() {
public Combination apply(@Nullable List<String> strings) {
assert strings != null;
return new Combination(AxisList.this, (String[]) strings.toArray(new String[0]));
}
};
});
}
/**
......
......@@ -222,7 +222,7 @@ public final class Combination extends TreeMap<String,String> implements Compara
}
/**
* Creates compact string representataion suitable for display purpose.
* Creates compact string representation suitable for display purpose.
*
* <p>
* The string is made compact by looking for {@link Axis} whose values
......
......@@ -3,6 +3,7 @@ package hudson.matrix;
import hudson.AbortException;
import hudson.Extension;
import hudson.console.ModelHyperlinkNote;
import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.matrix.listeners.MatrixBuildListener;
import hudson.model.BuildListener;
import hudson.model.Cause.UpstreamCause;
......@@ -109,23 +110,25 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
this.sorter = sorter;
}
public Result run(MatrixBuild build, List<MatrixAggregator> aggregators, BuildListener listener) throws InterruptedException, IOException {
MatrixProject p = build.getProject();
PrintStream logger = listener.getLogger();
@Override
public Result run(MatrixBuildExecution execution) throws InterruptedException, IOException {
MatrixBuild build = execution.getBuild();
MatrixProject p = execution.getProject();
PrintStream logger = execution.getListener().getLogger();
Collection<MatrixConfiguration> touchStoneConfigurations = new HashSet<MatrixConfiguration>();
Collection<MatrixConfiguration> delayedConfigurations = new HashSet<MatrixConfiguration>();
for (MatrixConfiguration c: p.getActiveConfigurations()) {
for (MatrixConfiguration c: execution.getActiveConfigurations()) {
if (!MatrixBuildListener.buildConfiguration(build, c))
continue; // skip rebuild
if (touchStoneCombinationFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), p.getTouchStoneCombinationFilter())) {
if (touchStoneCombinationFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), getTouchStoneCombinationFilter())) {
touchStoneConfigurations.add(c);
} else {
delayedConfigurations.add(c);
}
}
if (notifyStartBuild(aggregators)) return Result.FAILURE;
if (notifyStartBuild(execution.getAggregators())) return Result.FAILURE;
if (sorter != null) {
touchStoneConfigurations = createTreeSet(touchStoneConfigurations, sorter);
......@@ -134,14 +137,14 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
if(!runSequentially)
for(MatrixConfiguration c : touchStoneConfigurations)
scheduleConfigurationBuild(build, listener, c);
scheduleConfigurationBuild(execution, c);
Result r = Result.SUCCESS;
for (MatrixConfiguration c : touchStoneConfigurations) {
if(runSequentially)
scheduleConfigurationBuild(build, listener, c);
MatrixRun run = waitForCompletion(build, listener, c);
notifyEndBuild(run,aggregators);
scheduleConfigurationBuild(execution, c);
MatrixRun run = waitForCompletion(execution, c);
notifyEndBuild(run,execution.getAggregators());
r = r.combine(getResult(run));
}
......@@ -150,15 +153,15 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
return r;
}
if(!p.isRunSequentially())
if(!runSequentially)
for(MatrixConfiguration c : delayedConfigurations)
scheduleConfigurationBuild(build, listener, c);
scheduleConfigurationBuild(execution, c);
for (MatrixConfiguration c : delayedConfigurations) {
if(p.isRunSequentially())
scheduleConfigurationBuild(build, listener, c);
MatrixRun run = waitForCompletion(build, listener, c);
notifyEndBuild(run,aggregators);
if(runSequentially)
scheduleConfigurationBuild(execution, c);
MatrixRun run = waitForCompletion(execution, c);
notifyEndBuild(run,execution.getAggregators());
logger.println(Messages.MatrixBuild_Completed(ModelHyperlinkNote.encodeTo(c), getResult(run)));
r = r.combine(getResult(run));
}
......@@ -191,19 +194,21 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
return r;
}
private void scheduleConfigurationBuild(MatrixBuild build, BuildListener listener, MatrixConfiguration c) {
listener.getLogger().println(Messages.MatrixBuild_Triggering(ModelHyperlinkNote.encodeTo(c)));
private void scheduleConfigurationBuild(MatrixBuildExecution exec, MatrixConfiguration c) {
MatrixBuild build = exec.getBuild();
exec.getListener().getLogger().println(Messages.MatrixBuild_Triggering(ModelHyperlinkNote.encodeTo(c)));
c.scheduleBuild(build.getAction(ParametersAction.class), new UpstreamCause((Run)build));
}
private MatrixRun waitForCompletion(MatrixBuild build, BuildListener listener, MatrixConfiguration c) throws InterruptedException, IOException {
private MatrixRun waitForCompletion(MatrixBuildExecution exec, MatrixConfiguration c) throws InterruptedException, IOException {
BuildListener listener = exec.getListener();
String whyInQueue = "";
long startTime = System.currentTimeMillis();
// wait for the completion
int appearsCancelledCount = 0;
while(true) {
MatrixRun b = c.getBuildByNumber(build.getNumber());
MatrixRun b = c.getBuildByNumber(exec.getBuild().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.
......
......@@ -50,6 +50,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletException;
......@@ -294,22 +295,47 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
return rs;
}
private class MatrixBuildExecution extends AbstractBuildExecution {
/**
* Object that lives from the start of {@link MatrixBuild} execution to its end.
*
* Used to keep track of things that are needed only during the build.
*/
public class MatrixBuildExecution extends AbstractBuildExecution {
private final List<MatrixAggregator> aggregators = new ArrayList<MatrixAggregator>();
private Set<MatrixConfiguration> activeConfigurations;
/**
* Snapshot of {@link MatrixProject#getActiveConfigurations()} to ensure
* that the build will use a consistent view of it.
*/
public Set<MatrixConfiguration> getActiveConfigurations() {
return activeConfigurations;
}
/**
* Aggregators attached to this build execution, that are notified
* of every start/end of {@link MatrixRun}.
*/
public List<MatrixAggregator> getAggregators() {
return aggregators;
}
protected Result doRun(BuildListener listener) throws Exception {
MatrixProject p = getProject();
PrintStream logger = listener.getLogger();
// give axes a chance to rebuild themselves
activeConfigurations = p.rebuildConfigurations(this);
// list up aggregators
listUpAggregators(listener, p.getPublishers().values());
listUpAggregators(listener, p.getProperties().values());
listUpAggregators(listener, p.getBuildWrappers().values());
listUpAggregators(p.getPublishers().values());
listUpAggregators(p.getProperties().values());
listUpAggregators(p.getBuildWrappers().values());
axes = p.getAxes();
try {
return p.getExecutionStrategy().run(MatrixBuild.this, aggregators, listener);
return p.getExecutionStrategy().run(this);
} catch( InterruptedException e ) {
logger.println("Aborted");
Executor x = Executor.currentExecutor();
......@@ -323,7 +349,7 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
Queue q = Jenkins.getInstance().getQueue();
synchronized(q) {// avoid micro-locking in q.cancel.
final int n = getNumber();
for (MatrixConfiguration c : p.getActiveConfigurations()) {
for (MatrixConfiguration c : activeConfigurations) {
if(q.cancel(c))
logger.println(Messages.MatrixBuild_Cancelled(ModelHyperlinkNote.encodeTo(c)));
MatrixRun b = c.getBuildByNumber(n);
......@@ -339,7 +365,7 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
}
}
private void listUpAggregators(BuildListener listener, Collection<?> values) {
private void listUpAggregators(Collection<?> values) {
for (Object v : values) {
if (v instanceof MatrixAggregatable) {
MatrixAggregatable ma = (MatrixAggregatable) v;
......
......@@ -24,6 +24,7 @@
package hudson.matrix;
import hudson.ExtensionPoint;
import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.model.AbstractDescribableImpl;
import hudson.model.BuildListener;
import hudson.model.Result;
......@@ -40,7 +41,17 @@ import java.util.List;
* @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;
public Result run(MatrixBuildExecution execution) throws InterruptedException, IOException {
return run(execution.getBuild(), execution.getAggregators(), execution.getListener());
}
/**
* @deprecated
* Override {@link #run(MatrixBuildExecution)}
*/
public Result run(MatrixBuild build, List<MatrixAggregator> aggregators, BuildListener listener) throws InterruptedException, IOException {
throw new UnsupportedOperationException(getClass()+" needs to override run(MatrixBuildExecution)");
}
@Override
public MatrixExecutionStrategyDescriptor getDescriptor() {
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Jorg Heymans, Red Hat, Inc., id:cactusman
*
*
* 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
......@@ -24,10 +24,15 @@
*/
package hudson.matrix;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import hudson.CopyOnWrite;
import hudson.Extension;
import hudson.Util;
import hudson.XmlFile;
import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildableItemWithBuildWrappers;
......@@ -66,6 +71,7 @@ import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.TokenList;
import org.kohsuke.stapler.export.Exported;
import javax.annotation.Nullable;
import javax.servlet.ServletException;
import java.io.File;
import java.io.FileFilter;
......@@ -94,7 +100,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
* Configuration axes.
*/
private volatile AxisList axes = new AxisList();
/**
* The filter that is applied to combinations. It is a Groovy if condition.
* This can be null, which means "true".
......@@ -137,14 +143,14 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
* 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 transient String touchStoneCombinationFilter;
/**
* Required result on the touchstone combinations, in order to
* continue with the rest
......@@ -163,11 +169,11 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
/**
* Custom workspace location for {@link MatrixConfiguration}s.
*
*
* <p>
* (Historically, we used {@link AbstractProject#customWorkspace} + some unique suffix (see {@link MatrixConfiguration#useShortWorkspaceName})
* for custom workspace, but we now separated that so that the user has more control.
*
*
* <p>
* If null, the historical semantics is assumed.
*
......@@ -185,9 +191,9 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
/**
* Gets the workspace location that {@link MatrixConfiguration} uses.
*
*
* @see MatrixRun.MatrixRunExecution#decideWorkspace(Node, WorkspaceList)
*
*
* @return never null
* even when {@link MatrixProject} uses no custom workspace, this method still
* returns something like "${PARENT_WORKSPACE}/${COMBINATION}" that controls
......@@ -257,7 +263,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
save();
}
}
public AxisList getAxes() {
return axes;
}
......@@ -267,7 +273,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
*/
public void setAxes(AxisList axes) throws IOException {
this.axes = new AxisList(axes);
rebuildConfigurations();
rebuildConfigurations(null);
save();
}
......@@ -318,7 +324,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
*/
public void setCombinationFilter(String combinationFilter) throws IOException {
this.combinationFilter = combinationFilter;
rebuildConfigurations();
rebuildConfigurations(null);
save();
}
......@@ -331,7 +337,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
*
* <p>
* Namely, this expression is evaluated for each axis value combination, and only when it evaluates to true,
* a corresponding {@link MatrixConfiguration} will be created and built.
* a corresponding {@link MatrixConfiguration} will be created and built.
*
* @return can be null.
* @since 1.279
......@@ -457,14 +463,14 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
if (executionStrategy ==null)
executionStrategy = new DefaultMatrixExecutionStrategyImpl(runSequentially,touchStoneCombinationFilter,touchStoneResultCondition,sorter);
rebuildConfigurations();
rebuildConfigurations(null);
}
@Override
public void logRotate() throws IOException, InterruptedException {
super.logRotate();
// perform the log rotation of inactive configurations to make sure
// their logs get eventually discarded
// their logs get eventually discarded
for (MatrixConfiguration config : configurations.values()) {
if(!config.isActiveConfiguration())
config.logRotate();
......@@ -536,8 +542,13 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
/**
* Rebuilds the {@link #configurations} list and {@link #activeConfigurations}.
*
* @param context
* We rebuild configurations right before a build, to allow configurations to be adjusted for the build.
* (think of it as reconfiguring a project right before a build.) And when that happens, this value is the
* build in progress. Otherwise this value is null (for example, when Jenkins is booting up.)
*/
private void rebuildConfigurations() throws IOException {
/*package*/ Set<MatrixConfiguration> rebuildConfigurations(MatrixBuildExecution context) throws IOException {
{
// backward compatibility check to see if there's any data in the old structure
// if so, bring them to the newer structure.
......@@ -564,9 +575,25 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
loadConfigurations(getConfigurationsDir(),configurations,Collections.<String,String>emptyMap());
this.configurations = configurations;
Iterable<Combination> activeCombinations;
if (context!=null) {
List<Set<String>> axesList = Lists.newArrayList();
for (Axis axis : axes)
axesList.add(new LinkedHashSet<String>(axis.getValues()));
activeCombinations = Iterables.transform(Sets.cartesianProduct(axesList), new Function<List<String>, Combination>() {
public Combination apply(@Nullable List<String> strings) {
assert strings != null;
return new Combination(axes, (String[]) strings.toArray(new String[0]));
}
});
} else {
activeCombinations = axes.list();
}
// find all active configurations
Set<MatrixConfiguration> active = new LinkedHashSet<MatrixConfiguration>();
for (Combination c : axes.list()) {
for (Combination c : activeCombinations) {
if(c.evalGroovyExpression(axes,combinationFilter)) {
LOGGER.fine("Adding configuration: " + c);
MatrixConfiguration config = configurations.get(c);
......@@ -579,6 +606,8 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
}
}
this.activeConfigurations = active;
return active;
}
private File getConfigurationsDir() {
......@@ -587,9 +616,15 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
/**
* Gets all active configurations.
*
* <p>
* In contract, inactive configurations are those that are left for archival purpose
* and no longer built when a new {@link MatrixBuild} is executed.
*
* <p>
* During a build, {@link MatrixBuildExecution#getActiveConfigurations()} should be used
* to make sure that a build is using the consistent set of active configurations from
* the start to the end.
*/
@Exported
public Collection<MatrixConfiguration> getActiveConfigurations() {
......@@ -769,12 +804,12 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
newAxes.rebuildHetero(req, json, Axis.all(),"axis");
checkAxisNames(newAxes);
this.axes = new AxisList(newAxes.toList());
buildWrappers.rebuild(req, json, BuildWrappers.getFor(this));
builders.rebuildHetero(req, json, Builder.all(), "builder");
publishers.rebuildHetero(req, json, Publisher.all(), "publisher");
rebuildConfigurations();
rebuildConfigurations(null);
}
/**
......
......@@ -1408,9 +1408,13 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
*/
public abstract void cleanUp(BuildListener listener) throws Exception;
protected final RunT getBuild() {
public final RunT getBuild() {
return _this();
}
public final JobT getProject() {
return _this().getParent();
}
}
/**
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册