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

hudson.model.Build Maven / Gradle / Ivy

package hudson.model;

import hudson.Launcher;
import hudson.Proc;
import hudson.Util;
import static hudson.model.Hudson.isWindows;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Fingerprint.BuildPtr;
import hudson.scm.CVSChangeLogParser;
import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.SCM;
import hudson.scm.ChangeLogSet.Entry;
import hudson.tasks.BuildStep;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapper.Environment;
import hudson.tasks.Fingerprinter.FingerprintAction;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.triggers.SCMTrigger;
import org.xml.sax.SAXException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Calendar;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

/**
 * @author Kohsuke Kawaguchi
 */
public final class Build extends Run implements Runnable {

    /**
     * Name of the slave this project was built on.
     * Null if built by the master.
     */
    private String builtOn;

    /**
     * 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;

    /**
     * Creates a new build.
     */
    Build(Project project) throws IOException {
        super(project);
    }

    public Project getProject() {
        return getParent();
    }

    /**
     * Loads a build from a log file.
     */
    Build(Project project, File buildDir) throws IOException {
        super(project,buildDir);
    }

    @Override
    public String getWhyKeepLog() {
        // if any of the downstream project is configured with 'keep dependency component',
        // we need to keep this log
        for (Map.Entry e : getDownstreamBuilds().entrySet()) {
            Project p = e.getKey();
            if(!p.isKeepDependencies())     continue;

            // is there any active build that depends on us?
            for (Build build : p.getBuilds()) {
                if(e.getValue().includes(build.getNumber()))
                    return "kept because of "+build;
            }
        }

        return super.getWhyKeepLog();
    }

    /**
     * Gets the changes incorporated into this build.
     *
     * @return never null.
     */
    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.EMPTY;

        try {
            return scm.parse(this,changelogFile);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        return ChangeLogSet.EMPTY;
    }

    public Calendar due() {
        return timestamp;
    }

    /**
     * Returns a {@link Slave} on which this build was done.
     */
    public Node getBuiltOn() {
        if(builtOn==null)
            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.
     */
    public String getBuiltOnStr() {
        return builtOn;
    }

    /**
     * Gets {@link AbstractTestResultAction} associated with this build if any.
     */
    public AbstractTestResultAction getTestResultAction() {
        return getAction(AbstractTestResultAction.class);
    }

    /**
     * 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.
     */
    public RangeSet getDownstreamRelationship(Project 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;
    }

    /**
     * Gets the downstream builds of this build, which are the builds of the
     * downstream project sthat 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 (Project p : getParent().getDownstreamProjects()) {
            if(p.isFingerprintConfigured())
                r.put(p,getDownstreamRelationship(p));
        }
        return r;
    }

    /**
     * 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 avilable.
     */
    public int getUpstreamRelationship(Project 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.is(that))
                n = Math.max(n,o.getNumber());
        }

        return n;
    }

    /**
     * Gets the upstream builds of this build, which are the builds of the
     * upstream projects whose artifacts feed into this build.
     */
    public Map getUpstreamBuilds() {
        Map r = new HashMap();
        for (Project p : getParent().getUpstreamProjects()) {
            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(Build 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()) {
            Project 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 Project 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 Build from;
        /**
         * Version of the dependency project used in this build.
         */
        public final int toId;

        public final Build to;

        public DependencyChange(Project project, int fromId, int toId) {
            this.project = project;
            this.fromId = fromId;
            this.toId = toId;
            this.from = project.getBuildByNumber(fromId);
            this.to = project.getBuildByNumber(toId);
        }
    }

    /**
     * During the build this field remembers {@link Environment}s created by
     * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility.
     */
    private transient List buildEnvironments;

    /**
     * Performs a build.
     */
    public void run() {
        run(new Runner() {

            /**
             * Since configuration can be changed while a build is in progress,
             * stick to one launcher and use it.
             */
            private Launcher launcher;

            public Result run(BuildListener listener) throws IOException {
                Node node = Executor.currentExecutor().getOwner().getNode();
                assert builtOn==null;
                builtOn = node.getNodeName();

                launcher = node.createLauncher(listener);
                if(node instanceof Slave)
                    listener.getLogger().println("Building remotely on "+node.getNodeName());


                if(!project.checkout(Build.this,launcher,listener,new File(getRootDir(),"changelog.xml")))
                    return Result.FAILURE;

                SCM scm = project.getScm();

                Build.this.scm = scm.createChangeLogParser();
                Build.this.changeSet = Build.this.calcChangeSet();

                if(!preBuild(listener,project.getBuilders()))
                    return Result.FAILURE;
                if(!preBuild(listener,project.getPublishers()))
                    return Result.FAILURE;

                buildEnvironments = new ArrayList();
                try {
                    for( BuildWrapper w : project.getBuildWrappers().values() ) {
                        Environment e = w.setUp(Build.this, launcher, listener);
                        if(e==null)
                            return Result.FAILURE;
                        buildEnvironments.add(e);
                    }


                    if(!build(listener,project.getBuilders()))
                        return Result.FAILURE;
                } finally {
                    // tear down in reverse order
                    for( int i=buildEnvironments.size()-1; i>=0; i-- )
                        buildEnvironments.get(i).tearDown(Build.this,listener);
                    buildEnvironments = null;
                }

                if(!isWindows()) {
                    try {
                        // ignore a failure.
                        new Proc(new String[]{"rm","../lastSuccessful"},new String[0],listener.getLogger(),getProject().getBuildDir()).join();

                        int r = new Proc(new String[]{
                            "ln","-s","builds/"+getId()/*ugly*/,"../lastSuccessful"},
                            new String[0],listener.getLogger(),getProject().getBuildDir()).join();
                        if(r!=0)
                            listener.getLogger().println("ln failed: "+r);
                    } catch (IOException e) {
                        PrintStream log = listener.getLogger();
                        log.println("ln failed");
                        Util.displayIOException(e,listener);
                        e.printStackTrace( log );
                    }
                }

                return Result.SUCCESS;
            }

            public void post(BuildListener listener) {
                // run all of them even if one of them failed
                for( Publisher bs : project.getPublishers().values() )
                    bs.perform(Build.this, launcher, listener);
            }

            private boolean build(BuildListener listener, Map steps) {
                for( Builder bs : steps.values() )
                    if(!bs.perform(Build.this, launcher, listener))
                        return false;
                return true;
            }

            private boolean preBuild(BuildListener listener,Map steps) {
                for( BuildStep bs : steps.values() )
                    if(!bs.prebuild(Build.this,listener))
                        return false;
                return true;
            }
        });
    }

    @Override
    protected void onStartBuilding() {
        SCMTrigger t = (SCMTrigger)project.getTriggers().get(SCMTrigger.DESCRIPTOR);
        if(t==null) {
            super.onStartBuilding();
        } else {
            synchronized(t) {
                try {
                    t.abort();
                } catch (InterruptedException e) {
                    // handle the interrupt later
                    Thread.currentThread().interrupt();
                }
                super.onStartBuilding();
            }
        }
    }

    @Override
    protected void onEndBuilding() {
        SCMTrigger t = (SCMTrigger)project.getTriggers().get(SCMTrigger.DESCRIPTOR);
        if(t==null) {
            super.onEndBuilding();
        } else {
            synchronized(t) {
                super.onEndBuilding();
                t.startPolling();
            }
        }
    }

    @Override
    public Map getEnvVars() {
        Map env = super.getEnvVars();

        JDK jdk = project.getJDK();
        if(jdk !=null)
            jdk.buildEnvVars(env);
        project.getScm().buildEnvVars(env);

        if(buildEnvironments!=null) {
            for (Environment e : buildEnvironments)
                e.buildEnvVars(env);
        }

        return env;
    }

//
//
// actions
//
//
    /**
     * 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