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

hudson.model.AbstractBuild Maven / Gradle / Ivy

package hudson.model;

import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.matrix.MatrixConfiguration;
import hudson.maven.MavenBuild;
import hudson.model.Fingerprint.BuildPtr;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.listeners.SCMListener;
import hudson.scm.CVSChangeLogParser;
import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.SCM;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Fingerprinter.FingerprintAction;
import hudson.tasks.Publisher;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.util.AdaptedIterator;
import hudson.util.Iterators;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.xml.sax.SAXException;

import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Base implementation of {@link Run}s that build software.
 *
 * For now this is primarily the common part of {@link Build} and {@link MavenBuild}.
 *
 * @author Kohsuke Kawaguchi
 * @see AbstractProject
 */
public abstract class AbstractBuild

,R extends AbstractBuild> extends Run implements Queue.Executable { /** * Name of the slave this project was built on. * Null or "" if built by the master. (null happens when we read old record that didn't have this information.) */ private String builtOn; /** * Version of Hudson that built this. */ private String hudsonVersion; /** * SCM used for this build. * Maybe null, for historical reason, in which case CVS is assumed. */ private ChangeLogParser scm; /** * Changes in this build. */ private volatile transient ChangeLogSet changeSet; /** * Cumulative list of people who contributed to the build problem. * *

* This is a list of {@link User#getId() user ids} who made a change * since the last non-broken build. Can be null (which should be * treated like empty set), because of the compatibility. * *

* This field is semi-final --- once set the value will never be modified. * * @since 1.137 */ private volatile Set culprits; /** * During the build this field remembers {@link BuildWrapper.Environment}s created by * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility. */ protected transient List buildEnvironments; protected AbstractBuild(P job) throws IOException { super(job); } protected AbstractBuild(P job, Calendar timestamp) { super(job, timestamp); } protected AbstractBuild(P project, File buildDir) throws IOException { super(project, buildDir); } public final P getProject() { return getParent(); } /** * Returns a {@link Slave} on which this build was done. */ public Node getBuiltOn() { if(builtOn==null || builtOn.equals("")) return Hudson.getInstance(); else return Hudson.getInstance().getSlave(builtOn); } /** * Returns the name of the slave it was built on, or null if it was the master. */ @Exported(name="builtOn") public String getBuiltOnStr() { return builtOn; } /** * Used to render the side panel "Back to project" link. * *

* In a rare situation where a build can be reached from multiple paths, * returning different URLs from this method based on situations might * be desirable. * *

* If you override this method, you'll most likely also want to override * {@link #getDisplayName()}. */ public String getUpUrl() { return Functions.getNearestAncestorUrl(Stapler.getCurrentRequest(),getParent())+'/'; } /** * List of users who committed a change since the last non-broken build till now. * *

* This list at least always include people who made changes in this build, but * if the previous build was a failure it also includes the culprit list from there. * * @return * can be empty but never null. */ @Exported public Set getCulprits() { if(culprits==null) { Set r = new HashSet(); if(getPreviousBuild()!=null && isBuilding() && getPreviousBuild().getResult().isWorseThan(Result.UNSTABLE)) { // we are still building, so this is just the current latest information, // but we seems to be failing so far, so inherit culprits from the previous build. // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record // this information r.addAll(getPreviousBuild().getCulprits()); } for( Entry e : getChangeSet() ) r.add(e.getAuthor()); return r; } return new AbstractSet() { public Iterator iterator() { return new AdaptedIterator(culprits.iterator()) { protected User adapt(String id) { return User.get(id); } }; } public int size() { return culprits.size(); } }; } /** * Returns true if this user has made a commit to this build. * * @since 1.191 */ public boolean hasParticipant(User user) { for (ChangeLogSet.Entry e : getChangeSet()) if(e.getAuthor()==user) return true; return false; } /** * Gets the version of Hudson that was used to build this job. * * @since 1.246 */ public String getHudsonVersion() { return hudsonVersion; } protected abstract class AbstractRunner implements Runner { /** * Since configuration can be changed while a build is in progress, * stick to one launcher and use it. */ protected Launcher launcher; /** * Returns the current {@link Node} on which we are buildling. */ protected final Node getCurrentNode() { return Executor.currentExecutor().getOwner().getNode(); } public Result run(BuildListener listener) throws Exception { Node node = getCurrentNode(); assert builtOn==null; builtOn = node.getNodeName(); hudsonVersion = Hudson.VERSION; launcher = node.createLauncher(listener); if(node instanceof Slave) listener.getLogger().println(Messages.AbstractBuild_BuildingRemotely(node.getNodeName())); if(checkout(listener)) return Result.FAILURE; if(!preBuild(listener,project.getProperties())) return Result.FAILURE; Result result = doRun(listener); // the two if statements here are a real mess. // the "return null to mean success" convention is not very consistent // so we should probably get rid of that. And in the mean time // this check will allows us to create symlinks if(result!=null && result!=Result.SUCCESS) return result; // abort here if(getResult()==null || getResult()==Result.SUCCESS) createLastSuccessfulLink(listener); return Result.SUCCESS; } private void createLastSuccessfulLink(BuildListener listener) throws InterruptedException { Util.createSymlink(getProject().getBuildDir(),"builds/"+getId(),"../lastSuccessful",listener); } private boolean checkout(BuildListener listener) throws Exception { if(!project.checkout(AbstractBuild.this,launcher,listener,new File(getRootDir(),"changelog.xml"))) return true; SCM scm = project.getScm(); AbstractBuild.this.scm = scm.createChangeLogParser(); AbstractBuild.this.changeSet = AbstractBuild.this.calcChangeSet(); for (SCMListener l : Hudson.getInstance().getSCMListeners()) l.onChangeLogParsed(AbstractBuild.this,listener,changeSet); return false; } /** * The portion of a build that is specific to a subclass of {@link AbstractBuild} * goes here. * * @return * null to continue the build normally (that means the doRun method * itself run successfully) * Return a non-null value to abort the build right there with the specified result code. */ protected abstract Result doRun(BuildListener listener) throws Exception, RunnerAbortedException; /** * @see #post(BuildListener) */ protected abstract void post2(BuildListener listener) throws Exception; public final void post(BuildListener listener) throws Exception { try { post2(listener); } finally { // update the culprit list HashSet r = new HashSet(); for (User u : getCulprits()) r.add(u.getId()); culprits = r; } } public void cleanUp(BuildListener listener) throws Exception { // default is no-op } protected final void performAllBuildStep(BuildListener listener, Map buildSteps, boolean phase) throws InterruptedException, IOException { performAllBuildStep(listener,buildSteps.values(),phase); } /** * Runs all the given build steps, even if one of them fail. * * @param phase * true for the post build processing, and false for the final "run after finished" execution. */ protected final void performAllBuildStep(BuildListener listener, Iterable buildSteps, boolean phase) throws InterruptedException, IOException { for( BuildStep bs : buildSteps ) { if( (bs instanceof Publisher && ((Publisher)bs).needsToRunAfterFinalized()) ^ phase) bs.perform(AbstractBuild.this, launcher, listener); } } protected final boolean preBuild(BuildListener listener,Map steps) { return preBuild(listener,steps.values()); } protected final boolean preBuild(BuildListener listener,Collection steps) { return preBuild(listener,(Iterable)steps); } protected final boolean preBuild(BuildListener listener,Iterable steps) { for( BuildStep bs : steps ) if(!bs.prebuild(AbstractBuild.this,listener)) return false; return true; } } /** * Gets the changes incorporated into this build. * * @return never null. */ @Exported public ChangeLogSet getChangeSet() { if(scm==null) scm = new CVSChangeLogParser(); if(changeSet==null) // cached value changeSet = calcChangeSet(); return changeSet; } /** * Returns true if the changelog is already computed. */ public boolean hasChangeSetComputed() { File changelogFile = new File(getRootDir(), "changelog.xml"); return changelogFile.exists(); } private ChangeLogSet calcChangeSet() { File changelogFile = new File(getRootDir(), "changelog.xml"); if(!changelogFile.exists()) return ChangeLogSet.createEmpty(this); try { return scm.parse(this,changelogFile); } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } return ChangeLogSet.createEmpty(this); } @Override public Map getEnvVars() { Map env = super.getEnvVars(); env.put("WORKSPACE", getProject().getWorkspace().getRemote()); // servlet container may have set CLASSPATH in its launch script, // so don't let that inherit to the new child process. // see http://www.nabble.com/Run-Job-with-JDK-1.4.2-tf4468601.html env.put("CLASSPATH",""); JDK jdk = project.getJDK(); if(jdk !=null) jdk.buildEnvVars(env); project.getScm().buildEnvVars(this,env); ParametersAction parameters = getAction(ParametersAction.class); if (parameters != null) parameters.buildEnvVars(this,env); if(buildEnvironments!=null) for (BuildWrapper.Environment e : buildEnvironments) e.buildEnvVars(env); return env; } public Calendar due() { return getTimestamp(); } /** * Provides additional variables and their values to {@link Builder}s. * *

* This mechanism is used by {@link MatrixConfiguration} to pass * the configuration values to the current build. It is up to * {@link Builder}s to decide whether it wants to recognize the values * or how to use them. * * ugly ugly hack. */ public Map getBuildVariables() { return Collections.emptyMap(); } /** * Gets {@link AbstractTestResultAction} associated with this build if any. */ public AbstractTestResultAction getTestResultAction() { return getAction(AbstractTestResultAction.class); } /** * Invoked by {@link Executor} to performs a build. */ public abstract void run(); // // // fingerprint related stuff // // @Override public String getWhyKeepLog() { // if any of the downstream project is configured with 'keep dependency component', // we need to keep this log OUTER: for (AbstractProject p : getParent().getDownstreamProjects()) { if(!p.isKeepDependencies()) continue; AbstractBuild fb = p.getFirstBuild(); if(fb==null) continue; // no active record // is there any active build that depends on us? for(int i : getDownstreamRelationship(p).listNumbersReverse()) { // TODO: this is essentially a "find intersection between two sparse sequences" // and we should be able to do much better. if(i b = p.getBuildByNumber(i); if(b!=null) return Messages.AbstractBuild_KeptBecause(b); } } return super.getWhyKeepLog(); } /** * Gets the dependency relationship from this build (as the source) * and that project (as the sink.) * * @return * range of build numbers that represent which downstream builds are using this build. * The range will be empty if no build of that project matches this, but it'll never be null. */ public RangeSet getDownstreamRelationship(AbstractProject that) { RangeSet rs = new RangeSet(); FingerprintAction f = getAction(FingerprintAction.class); if(f==null) return rs; // look for fingerprints that point to this build as the source, and merge them all for (Fingerprint e : f.getFingerprints().values()) { BuildPtr o = e.getOriginal(); if(o!=null && o.is(this)) rs.add(e.getRangeSet(that)); } return rs; } /** * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns * the actual build objects, in ascending order. * @since 1.150 */ public Iterable> getDownstreamBuilds(final AbstractProject that) { final Iterable nums = getDownstreamRelationship(that).listNumbers(); return new Iterable>() { public Iterator> iterator() { return new Iterators.FilterIterator>( new AdaptedIterator>(nums) { protected AbstractBuild adapt(Integer item) { return that.getBuildByNumber(item); } }) { protected boolean filter(AbstractBuild build) { return build!=null; } }; } }; } /** * Gets the dependency relationship from this build (as the sink) * and that project (as the source.) * * @return * Build number of the upstream build that feed into this build, * or -1 if no record is available. */ public int getUpstreamRelationship(AbstractProject that) { FingerprintAction f = getAction(FingerprintAction.class); if(f==null) return -1; int n = -1; // look for fingerprints that point to the given project as the source, and merge them all for (Fingerprint e : f.getFingerprints().values()) { BuildPtr o = e.getOriginal(); if(o!=null && o.belongsTo(that)) n = Math.max(n,o.getNumber()); } return n; } /** * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the * actual build object. * * @return * null if no such upstream build was found, or it was found but the * build record is already lost. */ public AbstractBuild getUpstreamRelationshipBuild(AbstractProject that) { int n = getUpstreamRelationship(that); if(n==-1) return null; return that.getBuildByNumber(n); } /** * Gets the downstream builds of this build, which are the builds of the * downstream projects that use artifacts of this build. * * @return * For each project with fingerprinting enabled, returns the range * of builds (which can be empty if no build uses the artifact from this build.) */ public Map getDownstreamBuilds() { Map r = new HashMap(); for (AbstractProject p : getParent().getDownstreamProjects()) { if(p.isFingerprintConfigured()) r.put(p,getDownstreamRelationship(p)); } return r; } /** * Gets the upstream builds of this build, which are the builds of the * upstream projects whose artifacts feed into this build. * * @see #getTransitiveUpstreamBuilds() */ public Map getUpstreamBuilds() { return _getUpstreamBuilds(getParent().getUpstreamProjects()); } /** * Works like {@link #getUpstreamBuilds()} but also includes all the transitive * dependencies as well. */ public Map getTransitiveUpstreamBuilds() { return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects()); } private Map _getUpstreamBuilds(Collection projects) { Map r = new HashMap(); for (AbstractProject p : projects) { int n = getUpstreamRelationship(p); if(n>=0) r.put(p,n); } return r; } /** * Gets the changes in the dependency between the given build and this build. */ public Map getDependencyChanges(AbstractBuild from) { if(from==null) return Collections.emptyMap(); // make it easy to call this from views FingerprintAction n = this.getAction(FingerprintAction.class); FingerprintAction o = from.getAction(FingerprintAction.class); if(n==null || o==null) return Collections.emptyMap(); Map ndep = n.getDependencies(); Map odep = o.getDependencies(); Map r = new HashMap(); for (Map.Entry entry : odep.entrySet()) { AbstractProject p = entry.getKey(); Integer oldNumber = entry.getValue(); Integer newNumber = ndep.get(p); if(newNumber!=null && oldNumber.compareTo(newNumber)<0) { r.put(p,new DependencyChange(p,oldNumber,newNumber)); } } return r; } /** * Represents a change in the dependency. */ public static final class DependencyChange { /** * The dependency project. */ public final AbstractProject project; /** * Version of the dependency project used in the previous build. */ public final int fromId; /** * {@link Build} object for {@link #fromId}. Can be null if the log is gone. */ public final AbstractBuild from; /** * Version of the dependency project used in this build. */ public final int toId; public final AbstractBuild to; public DependencyChange(AbstractProject project, int fromId, int toId) { this.project = project; this.fromId = fromId; this.toId = toId; this.from = project.getBuildByNumber(fromId); this.to = project.getBuildByNumber(toId); } /** * Gets the {@link AbstractBuild} objects (fromId,toId]. *

* This method returns all such available builds in the ascending order * of IDs, but due to log rotations, some builds may be already unavailable. */ public List getBuilds() { List r = new ArrayList(); AbstractBuild b = (AbstractBuild)project.getNearestBuild(fromId); if(b!=null && b.getNumber()==fromId) b = b.getNextBuild(); // fromId exclusive while(b!=null && b.getNumber()<=toId) { r.add(b); b = b.getNextBuild(); } return r; } } // // // web methods // // /** * Stops this build if it's still going. * * If we use this/executor/stop URL, it causes 404 if the build is already killed, * as {@link #getExecutor()} returns null. */ public synchronized void doStop( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { Executor e = getExecutor(); if(e!=null) e.doStop(req,rsp); else // nothing is building rsp.forwardToPreviousPage(req); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy