All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.matrix.MatrixProject Maven / Gradle / Ivy

package hudson.matrix;

import hudson.CopyOnWrite;
import hudson.FilePath;
import hudson.XmlFile;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.DependencyGraph;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.SCMedItem;
import hudson.model.TopLevelItem;
import hudson.model.TopLevelItemDescriptor;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrappers;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.triggers.Trigger;
import hudson.util.CopyOnWriteMap;
import hudson.util.DescribableList;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * {@link Job} that allows you to run multiple different configurations
 * from a single setting.
 *
 * @author Kohsuke Kawaguchi
 */
public class MatrixProject extends AbstractProject implements TopLevelItem, SCMedItem, ItemGroup, DescribableList.Owner {
    /**
     * Other configuration axes.
     *
     * This also includes special axis "label" and "jdk" if they are configured.
     */
    private volatile AxisList axes = new AxisList();

    /**
     * List of active {@link Builder}s configured for this project.
     */
    private DescribableList> builders =
            new DescribableList>(this);

    /**
     * List of active {@link Publisher}s configured for this project.
     */
    private DescribableList> publishers =
            new DescribableList>(this);

    /**
     * List of active {@link BuildWrapper}s configured for this project.
     */
    private DescribableList> buildWrappers =
            new DescribableList>(this);

    /**
     * All {@link MatrixConfiguration}s, keyed by their {@link MatrixConfiguration#getName() names}.
     */
    private transient /*final*/ Map configurations = new CopyOnWriteMap.Tree();

    /**
     * @see #getActiveConfigurations()
     */
    @CopyOnWrite
    private transient /*final*/ Set activeConfigurations = new LinkedHashSet();

    public MatrixProject(String name) {
        super(Hudson.getInstance(), name);
    }

    public AxisList getAxes() {
        return axes;
    }

    protected void updateTransientActions() {
        synchronized(transientActions) {
            super.updateTransientActions();

            for (BuildStep step : builders) {
                Action a = step.getProjectAction(this);
                if(a!=null)
                    transientActions.add(a);
            }
            for (BuildStep step : publishers) {
                Action a = step.getProjectAction(this);
                if(a!=null)
                    transientActions.add(a);
            }
            for (BuildWrapper step : buildWrappers) {
                Action a = step.getProjectAction(this);
                if(a!=null)
                    transientActions.add(a);
            }
            for (Trigger trigger : triggers) {
                Action a = trigger.getProjectAction();
                if(a!=null)
                    transientActions.add(a);
            }
        }
    }

    /**
     * Gets the subset of {@link AxisList} that are not system axes.
     */
    public List getUserAxes() {
        List r = new ArrayList();
        for (Axis a : axes)
            if(!a.isSystem())
                r.add(a);
        return r;
    }

    public Layouter getLayouter() {
        return new Layouter(axes) {
            protected MatrixConfiguration getT(Combination c) {
                return getItem(c);
            }
        };
    }

    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
        builders.setOwner(this);
        publishers.setOwner(this);
        buildWrappers.setOwner(this);
        rebuildConfigurations();
    }

    public void logRotate() throws IOException {
        super.logRotate();
        // perform the log rotation of inactive configurations to make sure
        // their logs get eventually discarded 
        for (MatrixConfiguration config : configurations.values()) {
            if(!config.isActiveConfiguration())
                config.logRotate();
        }
    }

    /**
     * Recursively search for configuration and put them to the map
     *
     * 

* The directory structure would be axis-a/b/axis-c/d/axis-e/f for * combination [a=b,c=d,e=f]. Note that two combinations [a=b,c=d] and [a=b,c=d,e=f] * can both co-exist (where one is an archived record and the other is live, for example) * so search needs to be thorough. * * @param dir * Directory to be searched. * @param result * Receives the loaded {@link MatrixConfiguration}s. * @param combination * Combination of key/values discovered so far while traversing the directories. * Read-only. */ private void loadConfigurations( File dir, CopyOnWriteMap.Tree result, Map combination ) { File[] axisDirs = dir.listFiles(new FileFilter() { public boolean accept(File child) { return child.isDirectory() && child.getName().startsWith("axis-"); } }); if(axisDirs==null) return; for (File subdir : axisDirs) { String axis = subdir.getName().substring(5); // axis name File[] valuesDir = subdir.listFiles(new FileFilter() { public boolean accept(File child) { return child.isDirectory(); } }); if(valuesDir==null) continue; // no values here for (File v : valuesDir) { Map c = new HashMap(combination); c.put(axis,v.getName()); try { XmlFile config = Items.getConfigFile(v); if(config.exists()) { Combination comb = new Combination(c); // if we already have this in memory, just use it. // otherwise load it MatrixConfiguration item=null; if(this.configurations!=null) item = this.configurations.get(comb); if(item==null) { item = (MatrixConfiguration) config.read(); item.setCombination(comb); item.onLoad(this, v.getName()); } result.put(item.getCombination(), item); } } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to load matrix configuration "+v,e); } loadConfigurations(v,result,c); } } } /** * Rebuilds the {@link #configurations} list and {@link #activeConfigurations}. */ private void rebuildConfigurations() throws IOException { { // backward compatibility check to see if there's any data in the old structure // if so, bring them to the newer structure. File[] oldDirs = getConfigurationsDir().listFiles(new FileFilter() { public boolean accept(File child) { return child.isDirectory() && !child.getName().startsWith("axis-"); } }); if(oldDirs!=null) { // rename the old directory to the new one for (File dir : oldDirs) { try { Combination c = Combination.fromString(dir.getName()); dir.renameTo(getRootDirFor(c)); } catch (IllegalArgumentException e) { // it's not a configuration dir. Just ignore. } } } } CopyOnWriteMap.Tree configurations = new CopyOnWriteMap.Tree(); loadConfigurations(getConfigurationsDir(),configurations,Collections.emptyMap()); this.configurations = configurations; // find all active configurations Set active = new LinkedHashSet(); for (Combination c : axes.list()) { MatrixConfiguration config = configurations.get(c); if(config==null) { config = new MatrixConfiguration(this,c); config.save(); configurations.put(config.getCombination(), 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(); } @Override public Collection getAllJobs() { Set jobs = new HashSet(getItems()); jobs.add(this); return jobs; } public String getUrlChildPrefix() { return "."; } public MatrixConfiguration getItem(String name) { return getItem(Combination.fromString(name)); } public MatrixConfiguration getItem(Combination c) { return configurations.get(c); } public File getRootDirFor(MatrixConfiguration child) { return getRootDirFor(child.getCombination()); } public File getRootDirFor(Combination combination) { File f = getConfigurationsDir(); for (Entry e : combination.entrySet()) f = new File(f,"axis-"+e.getKey()+'/'+e.getValue()); f.getParentFile().mkdirs(); return f; } 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





© 2015 - 2025 Weber Informatics LLC | Privacy Policy