From e76bd8154f18adc2ffbf1f1628ccb2ab58eea8af Mon Sep 17 00:00:00 2001 From: kohsuke Date: Sat, 23 Jun 2007 18:36:07 +0000 Subject: [PATCH] initial commit for the multi-config matrix project type, which is useful for running tests and builds on a large number of semi-identical (but different) configurations. git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@3525 71c3de6d-444a-0410-be80-ed276b4c234a --- core/src/main/java/hudson/matrix/Axis.java | 81 +++++++ .../src/main/java/hudson/matrix/AxisList.java | 69 ++++++ .../main/java/hudson/matrix/Combination.java | 78 +++++++ .../main/java/hudson/matrix/MatrixBuild.java | 42 ++++ .../hudson/matrix/MatrixConfiguration.java | 86 ++++++++ .../java/hudson/matrix/MatrixProject.java | 200 ++++++++++++++++++ .../main/java/hudson/matrix/MatrixRun.java | 31 +++ .../java/hudson/maven/MavenModuleSet.java | 30 +-- .../src/main/java/hudson/model/ItemGroup.java | 1 + .../java/hudson/model/ItemGroupMixIn.java | 53 +++++ core/src/main/java/hudson/model/Items.java | 7 +- core/src/main/java/hudson/util/Function1.java | 10 + .../MatrixProject/configure-entries.jelly | 61 ++++++ .../hudson/matrix/MatrixProject/index.jelly | 10 + .../matrix/MatrixProject/newJobDetail.jelly | 4 + war/resources/help/matrix/jdk.html | 9 + 16 files changed, 752 insertions(+), 20 deletions(-) create mode 100644 core/src/main/java/hudson/matrix/Axis.java create mode 100644 core/src/main/java/hudson/matrix/AxisList.java create mode 100644 core/src/main/java/hudson/matrix/Combination.java create mode 100644 core/src/main/java/hudson/matrix/MatrixBuild.java create mode 100644 core/src/main/java/hudson/matrix/MatrixConfiguration.java create mode 100644 core/src/main/java/hudson/matrix/MatrixProject.java create mode 100644 core/src/main/java/hudson/matrix/MatrixRun.java create mode 100644 core/src/main/java/hudson/model/ItemGroupMixIn.java create mode 100644 core/src/main/java/hudson/util/Function1.java create mode 100644 core/src/main/resources/hudson/matrix/MatrixProject/configure-entries.jelly create mode 100644 core/src/main/resources/hudson/matrix/MatrixProject/index.jelly create mode 100644 core/src/main/resources/hudson/matrix/MatrixProject/newJobDetail.jelly create mode 100644 war/resources/help/matrix/jdk.html diff --git a/core/src/main/java/hudson/matrix/Axis.java b/core/src/main/java/hudson/matrix/Axis.java new file mode 100644 index 0000000000..02992b884c --- /dev/null +++ b/core/src/main/java/hudson/matrix/Axis.java @@ -0,0 +1,81 @@ +package hudson.matrix; + +import hudson.Util; + +import java.util.List; +import java.util.Collections; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.Enumeration; + +import org.kohsuke.stapler.StaplerRequest; + +/** + * Configuration axis. + * + *

+ * This class represents a single dimension of the configuration matrix. + * For example, the JAX-WS RI test configuration might include + * one axis "container={glassfish,tomcat,jetty}" and another axis + * "stax={sjsxp,woodstox}", and so on. + * + * @author Kohsuke Kawaguchi + */ +public final class Axis implements Comparable, Iterable { + /** + * Name of this axis. + * Used as a variable name. + */ + public final String name; + + /** + * Possible values for this axis. + */ + public final List values; + + public Axis(String name, List values) { + this.name = name; + this.values = Collections.unmodifiableList(values); + } + + public Iterator iterator() { + return values.iterator(); + } + + public int size() { + return values.size(); + } + + public String value(int index) { + return values.get(index); + } + + /** + * Axis is fully ordered so that we can convert between a list of axis + * and a string unambiguously. + */ + public int compareTo(Axis that) { + return this.name.compareTo(that.name); + } + + public String toString() { + return new StringBuilder().append(name).append("={").append(Util.join(values,",")).append('}').toString(); + } + + /** + * Parses the submitted form (where possible values are + * presented as a list of checkboxes) and creates an axis + */ + public static Axis parsePrefixed(StaplerRequest req, String name) { + List values = new ArrayList(); + String prefix = name+'.'; + + Enumeration e = req.getParameterNames(); + while (e.hasMoreElements()) { + String paramName = (String) e.nextElement(); + if(paramName.startsWith(prefix)) + values.add(paramName.substring(prefix.length())); + } + return new Axis(name,values); + } +} diff --git a/core/src/main/java/hudson/matrix/AxisList.java b/core/src/main/java/hudson/matrix/AxisList.java new file mode 100644 index 0000000000..7e6008bb26 --- /dev/null +++ b/core/src/main/java/hudson/matrix/AxisList.java @@ -0,0 +1,69 @@ +package hudson.matrix; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * List of {@link Axis}. + * + * @author Kohsuke Kawaguchi + */ +public class AxisList extends ArrayList { + public AxisList() { + } + + public AxisList(Collection c) { + super(c); + } + + public Axis find(String name) { + for (Axis a : this) { + if(a.name.equals(name)) + return a; + } + return null; + } + + /** + * List up all the possible combinations of this list. + */ + public Iterable list() { + final int[] base = new int[size()]; + + 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() { + public Iterator iterator() { + return new Iterator() { + private int counter = 0; + + public boolean hasNext() { + return counter { + + public Combination(AxisList axisList, List values) { + for(int i=0; i keyValuePairs) { + super(keyValuePairs); + } + + /** + * Converts to the ID string representation: + * axisName=value,axisName=value,... + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + for (Map.Entry e : entrySet()) { + if(buf.length()>0) buf.append(','); + buf.append(e.getKey()).append('=').append(e.getValue()); + } + return buf.toString(); + } + + /** + * Reverse operation of {@link #toString()}. + */ + public static Combination fromString(String id) { + Map m = new HashMap(); + StringTokenizer tokens = new StringTokenizer(id, ","); + while(tokens.hasMoreTokens()) { + String token = tokens.nextToken(); + int idx = token.indexOf('='); + if(idx<0) + throw new IllegalArgumentException("Can't parse "+id); + m.put(token.substring(0,idx),token.substring(idx+1)); + } + return new Combination(m); + } + + // read-only + public void clear() { + throw new UnsupportedOperationException(); + } + + public void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + public String put(String key, String value) { + throw new UnsupportedOperationException(); + } + + public String remove(Object key) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/hudson/matrix/MatrixBuild.java b/core/src/main/java/hudson/matrix/MatrixBuild.java new file mode 100644 index 0000000000..47fd0d017c --- /dev/null +++ b/core/src/main/java/hudson/matrix/MatrixBuild.java @@ -0,0 +1,42 @@ +package hudson.matrix; + +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import hudson.model.Result; + +import java.io.IOException; +import java.io.PrintStream; + +/** + * @author Kohsuke Kawaguchi + */ +public class MatrixBuild extends AbstractBuild { + public MatrixBuild(MatrixProject job) throws IOException { + super(job); + } + + @Override + public void run() { + run(new RunnerImpl()); + } + + private class RunnerImpl extends AbstractRunner { + protected Result doRun(BuildListener listener) throws Exception { + MatrixProject p = getProject(); + PrintStream logger = listener.getLogger(); + + for(MatrixConfiguration c : p.getActiveConfigurations()) { + logger.println("Triggering "+c.getName()); + c.scheduleBuild(); + } + + // TODO: wait for completion + + return Result.SUCCESS; + } + + public void post(BuildListener listener) { + // TODO: run aggregators + } + } +} diff --git a/core/src/main/java/hudson/matrix/MatrixConfiguration.java b/core/src/main/java/hudson/matrix/MatrixConfiguration.java new file mode 100644 index 0000000000..04a010dc43 --- /dev/null +++ b/core/src/main/java/hudson/matrix/MatrixConfiguration.java @@ -0,0 +1,86 @@ +package hudson.matrix; + +import hudson.FilePath; +import hudson.model.AbstractProject; +import hudson.model.DependencyGraph; +import hudson.model.Hudson; +import hudson.model.Node; +import hudson.model.SCMedItem; +import hudson.model.JDK; +import hudson.model.Item; +import hudson.model.ItemGroup; +import hudson.model.Label; + +import java.io.IOException; + +/** + * One configuration of {@link MatrixProject}. + * + * @author Kohsuke Kawaguchi + */ +public class MatrixConfiguration extends AbstractProject implements SCMedItem { + /** + * The actual value combination. + */ + private transient /*final*/ Combination combination; + + public MatrixConfiguration(MatrixProject parent, Combination c) { + super(parent,c.toString()); + this.combination = c; + } + + public void onLoad(ItemGroup parent, String name) throws IOException { + super.onLoad(parent, name); + combination = Combination.fromString(name); + } + + public MatrixProject getParent() { + return (MatrixProject)super.getParent(); + } + + @Override + public FilePath getWorkspace() { + Node node = getLastBuiltOn(); + if(node==null) node = Hudson.getInstance(); + return node.getWorkspaceFor(getParent()).child(getName()); + } + + @Override + protected Class getBuildClass() { + return MatrixRun.class; + } + + @Override + public boolean isFingerprintConfigured() { + // TODO + return false; + } + + @Override + protected void buildDependencyGraph(DependencyGraph graph) { + } + + public MatrixConfiguration asProject() { + return this; + } + + @Override + public Label getAssignedLabel() { + return Hudson.getInstance().getLabel(combination.get("label")); + } + + @Override + public JDK getJDK() { + return Hudson.getInstance().getJDK(combination.get("jdk")); + } + + /** + * JDK cannot be set on {@link MatrixConfiguration} because + * it's controlled by {@link MatrixProject}. + * @deprecated + * Not supported. + */ + public void setJDK(JDK jdk) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/hudson/matrix/MatrixProject.java b/core/src/main/java/hudson/matrix/MatrixProject.java new file mode 100644 index 0000000000..e75f9ebd9b --- /dev/null +++ b/core/src/main/java/hudson/matrix/MatrixProject.java @@ -0,0 +1,200 @@ +package hudson.matrix; + +import hudson.FilePath; +import hudson.model.AbstractProject; +import hudson.model.DependencyGraph; +import hudson.model.Descriptor.FormException; +import hudson.model.Hudson; +import hudson.model.Item; +import hudson.model.ItemGroup; +import hudson.model.ItemGroupMixIn; +import static hudson.model.ItemGroupMixIn.KEYED_BY_NAME; +import hudson.model.JDK; +import hudson.model.Label; +import hudson.model.Node; +import hudson.model.SCMedItem; +import hudson.model.TopLevelItem; +import hudson.model.TopLevelItemDescriptor; +import hudson.util.CopyOnWriteMap; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; + +import javax.servlet.ServletException; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author Kohsuke Kawaguchi + */ +public class MatrixProject extends AbstractProject implements TopLevelItem, SCMedItem, ItemGroup { + /** + * Other configuration axes. + * + * This also includes special axis "label" and "jdk" if they are configured. + */ + private volatile AxisList axes = new AxisList(); + + /** + * All {@link MatrixConfiguration}s, keyed by their {@link MatrixConfiguration#getName() names}. + */ + private transient /*final*/ Map configurations = new CopyOnWriteMap.Tree(); + + /** + * @see #getActiveConfigurations() + */ + private transient /*final*/ Set activeConfigurations = new LinkedHashSet(); + + public MatrixProject(String name) { + super(Hudson.getInstance(), name); + } + + public void onLoad(ItemGroup parent, String name) throws IOException { + super.onLoad(parent,name); + Collections.sort(axes); // perhaps the file was edited on disk and the sort order might have been broken + + configurations = ItemGroupMixIn.loadChildren(this,getConfigurationsDir(), KEYED_BY_NAME); + + // find all active configurations + Set active = new LinkedHashSet(); + for (Combination c : axes.list()) { + MatrixConfiguration config = configurations.get(c.toString()); + if(config==null) { + config = new MatrixConfiguration(this,c); + configurations.put(config.getName(), config); + } + active.add(config); + } + this.activeConfigurations = active; + } + + private File getConfigurationsDir() { + return new File(getRootDir(),"configurations"); + } + + /** + * Gets all active configurations. + *

+ * In contract, inactive configurations are those that are left for archival purpose + * and no longer built when a new {@link MatrixBuild} is executed. + */ + public Collection getActiveConfigurations() { + return activeConfigurations; + } + + public Collection getItems() { + return configurations.values(); + } + + public String getUrlChildPrefix() { + return "."; + } + + public MatrixConfiguration getItem(String name) { + return configurations.get(name); + } + + public File getRootDirFor(MatrixConfiguration child) { + return new File(getConfigurationsDir(),child.getName()); + } + + public Hudson getParent() { + return Hudson.getInstance(); + } + + /** + * @see #getJDKs() + */ + @Override @Deprecated + public JDK getJDK() { + return super.getJDK(); + } + + /** + * Gets the {@link JDK}s where the builds will be run. + * @return never null but can be empty + */ + public Set getJDKs() { + Axis a = axes.find("jdk"); + if(a==null) return Collections.emptySet(); + Set r = new HashSet(); + for (String j : a) { + JDK jdk = Hudson.getInstance().getJDK(j); + if(jdk!=null) + r.add(jdk); + } + return r; + } + + /** + * Gets the {@link Label}s where the builds will be run. + * @return never null + */ + public Set

+ + + + + + + + + diff --git a/core/src/main/resources/hudson/matrix/MatrixProject/index.jelly b/core/src/main/resources/hudson/matrix/MatrixProject/index.jelly new file mode 100644 index 0000000000..e843d958a9 --- /dev/null +++ b/core/src/main/resources/hudson/matrix/MatrixProject/index.jelly @@ -0,0 +1,10 @@ + + + + +

Project ${it.name}

+ + +
+
+
\ No newline at end of file diff --git a/core/src/main/resources/hudson/matrix/MatrixProject/newJobDetail.jelly b/core/src/main/resources/hudson/matrix/MatrixProject/newJobDetail.jelly new file mode 100644 index 0000000000..6c421e2b32 --- /dev/null +++ b/core/src/main/resources/hudson/matrix/MatrixProject/newJobDetail.jelly @@ -0,0 +1,4 @@ +
+ Suitable for projects that need large number of different configurations, + such as testing on multiple environments, platform-specific builds, etc. +
\ No newline at end of file diff --git a/war/resources/help/matrix/jdk.html b/war/resources/help/matrix/jdk.html new file mode 100644 index 0000000000..002836270b --- /dev/null +++ b/war/resources/help/matrix/jdk.html @@ -0,0 +1,9 @@ +
+ Specify the JDK(s) with which builds are performed. If none is selected, + the default JDK is used (no explicit JAVA_HOME, and java command + is assumed to be in PATH.) If multiple JDKs are selected, + the build matrix will includes all the configurations. +
+ Selecting multiple values is typically useful when this job is running tests, + and you need to run tests on multiple different versions of JDKs. +
\ No newline at end of file -- GitLab