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

hudson.tasks.Fingerprinter Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2010 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 *    Kohsuke Kawaguchi
 *
 *
 *******************************************************************************/ 

package hudson.tasks;

import com.google.common.collect.ImmutableMap;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Build;
import hudson.model.BuildListener;
import hudson.model.Fingerprint;
import hudson.model.Fingerprint.BuildPtr;
import hudson.model.FingerprintMap;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.RunAction;
import hudson.remoting.VirtualChannel;
import hudson.util.FormValidation;
import hudson.util.IOException2;
import hudson.util.PackedMap;
import net.sf.json.JSONObject;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Records fingerprints of the specified files.
 *
 * @author Kohsuke Kawaguchi
 */
public class Fingerprinter extends Recorder implements Serializable {

    /**
     * Comma-separated list of files/directories to be fingerprinted.
     */
    private final String targets;
    /**
     * Also record all the finger prints of the build artifacts.
     */
    private final boolean recordBuildArtifacts;

    @DataBoundConstructor
    public Fingerprinter(String targets, boolean recordBuildArtifacts) {
        this.targets = targets;
        this.recordBuildArtifacts = recordBuildArtifacts;
    }

    public String getTargets() {
        return targets;
    }

    public boolean getRecordBuildArtifacts() {
        return recordBuildArtifacts;
    }

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException {
        try {
            listener.getLogger().println(Messages.Fingerprinter_Recording());

            Map record = new HashMap();


            if (targets.length() != 0) {
                record(build, listener, record, targets);
            }

            if (recordBuildArtifacts) {
                ArtifactArchiver aa = build.getProject().getPublishersList().get(ArtifactArchiver.class);
                if (aa == null) {
                    // configuration error
                    listener.error(Messages.Fingerprinter_NoArchiving());
                    build.setResult(Result.FAILURE);
                    return true;
                }
                record(build, listener, record, aa.getArtifacts());
            }

            FingerprintAction.add(build, record);

        } catch (IOException e) {
            e.printStackTrace(listener.error(Messages.Fingerprinter_Failed()));
            build.setResult(Result.FAILURE);
        }

        // failing to record fingerprints is an error but not fatal
        return true;
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }

    private void record(AbstractBuild build, BuildListener listener, Map record, final String targets) throws IOException, InterruptedException {
        final class Record implements Serializable {

            final boolean produced;
            final String relativePath;
            final String fileName;
            final String md5sum;

            public Record(boolean produced, String relativePath, String fileName, String md5sum) {
                this.produced = produced;
                this.relativePath = relativePath;
                this.fileName = fileName;
                this.md5sum = md5sum;
            }

            Fingerprint addRecord(AbstractBuild build) throws IOException {
                FingerprintMap map = Hudson.getInstance().getFingerprintMap();
                return map.getOrCreate(produced ? build : null, fileName, md5sum);
            }
            private static final long serialVersionUID = 1L;
        }

        final long buildTimestamp = build.getTimeInMillis();

        FilePath ws = build.getWorkspace();
        if (ws == null) {
            listener.error(Messages.Fingerprinter_NoWorkspace());
            build.setResult(Result.FAILURE);
            return;
        }

        List records = ws.act(new FileCallable>() {
            public List invoke(File baseDir, VirtualChannel channel) throws IOException {
                List results = new ArrayList();

                FileSet src = Util.createFileSet(baseDir, targets);

                DirectoryScanner ds = src.getDirectoryScanner();
                for (String f : ds.getIncludedFiles()) {
                    File file = new File(baseDir, f);

                    // consider the file to be produced by this build only if the timestamp
                    // is newer than when the build has started.
                    // 2000ms is an error margin since since VFAT only retains timestamp at 2sec precision
                    boolean produced = buildTimestamp <= file.lastModified() + 2000;

                    try {
                        results.add(new Record(produced, f, file.getName(), new FilePath(file).digest()));
                    } catch (IOException e) {
                        throw new IOException2(Messages.Fingerprinter_DigestFailed(file), e);
                    } catch (InterruptedException e) {
                        throw new IOException2(Messages.Fingerprinter_Aborted(), e);
                    }
                }

                return results;
            }
        });

        for (Record r : records) {
            Fingerprint fp = r.addRecord(build);
            if (fp == null) {
                listener.error(Messages.Fingerprinter_FailedFor(r.relativePath));
                continue;
            }
            fp.add(build);
            record.put(r.relativePath, fp.getHashString());
        }
    }

    @Extension
    public static class DescriptorImpl extends BuildStepDescriptor {

        public String getDisplayName() {
            return Messages.Fingerprinter_DisplayName();
        }

        @Override
        public String getHelpFile() {
            return "/help/project-config/fingerprint.html";
        }

        /**
         * Performs on-the-fly validation on the file mask wildcard.
         */
        public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException {
            return FilePath.validateFileMask(project.getSomeWorkspace(), value);
        }

        @Override
        public Publisher newInstance(StaplerRequest req, JSONObject formData) {
            return req.bindJSON(Fingerprinter.class, formData);
        }

        public boolean isApplicable(Class jobType) {
            return true;
        }
    }

    /**
     * Action for displaying fingerprints.
     *
     * To ensure there is only one per build use
     * {@link FingerprintAction#add(AbstractBuild, Map)}. This allows for
     * additional fingerprint contributions outside of the
     * {@link Fingerprinter}.
     */
    public static final class FingerprintAction implements RunAction {

        private final AbstractBuild build;
        /**
         * From file name to the digest.
         */
        private /*almost final*/ Map record;
        private transient WeakReference> ref;

        public FingerprintAction(AbstractBuild build, Map record) {
            this.build = checkNotNull(build);
            this.record = PackedMap.of(checkNotNull(record));
        }

        /**
         * Add fingerprint records to this Action. Assumes the records came from
         * the same build that initially created the {@link FingerprintAction}.
         */
        public void add(Map moreRecords) {
            Map r = new HashMap(record);
            r.putAll(moreRecords);
            record = PackedMap.of(checkNotNull(r));
            ref = null;
        }

        /**
         * Adds the record to a {@link FingerprintAction} corresponding to the
         * build.
         *
         * Safely consolidates multiple sources of records (e.g. from different
         * post build actions) into a single {@link FingerprintAction}.
         *
         * @param build to add the FingerprintAction and records to
         * @param record to add
         *
         * @since 2.1.0
         */
        public static void add(final AbstractBuild build, final Map record) {
            checkNotNull(build);
            checkNotNull(record);

            FingerprintAction action = build.getAction(FingerprintAction.class);
            if (action != null) {
                action.add(record);
            } else {
                build.addAction(new FingerprintAction(build, record));
            }
        }

        public String getIconFileName() {
            return "fingerprint.png";
        }

        public String getDisplayName() {
            return Messages.Fingerprinter_Action_DisplayName();
        }

        public String getUrlName() {
            return "fingerprints";
        }

        public AbstractBuild getBuild() {
            return build;
        }

        /**
         * Obtains the raw data.
         */
        public Map getRecords() {
            return record;
        }

        public void onLoad() {
            // This causes unnecessary loading of previous build.
            // The compacting to save memory may not be a big saving anymore
            // after lazy loading improvements.
//            Run pb = build.getPreviousBuild();
//            if (pb != null) {
//                FingerprintAction a = pb.getAction(FingerprintAction.class);
//                if (a != null) {
//                    compact(a);
//                }
//            }
        }

        public void onAttached(Run r) {
        }

        public void onBuildComplete() {
            onLoad();   // make compact
        }

        /**
         * Reuse string instances from another {@link FingerprintAction} to
         * reduce memory footprint.
         */
        protected void compact(FingerprintAction a) {
            Map intern = new HashMap(); // string intern map
            for (Entry e : a.record.entrySet()) {
                intern.put(e.getKey(), e.getKey());
                intern.put(e.getValue(), e.getValue());
            }

            Map b = new HashMap();
            for (Entry e : record.entrySet()) {
                String k = intern.get(e.getKey());
                if (k == null) {
                    k = e.getKey();
                }

                String v = intern.get(e.getValue());
                if (v == null) {
                    v = e.getValue();
                }

                b.put(k, v);
            }

            record = PackedMap.of(b);
        }

        /**
         * Map from file names of the fingerprinted file to its fingerprint
         * record.
         */
        public synchronized Map getFingerprints() {
            if (ref != null) {
                Map m = ref.get();
                if (m != null) {
                    return m;
                }
            }

            Hudson h = Hudson.getInstance();

            Map m = new TreeMap();
            for (Entry r : record.entrySet()) {
                try {
                    Fingerprint fp = h._getFingerprint(r.getValue());
                    if (fp != null) {
                        m.put(r.getKey(), fp);
                    }
                } catch (IOException e) {
                    logger.log(Level.WARNING, e.getMessage(), e);
                }
            }

            m = ImmutableMap.copyOf(m);
            ref = new WeakReference>(m);
            return m;
        }

        /**
         * Gets the dependency to other builds in a map. Returns build numbers
         * instead of {@link Build}, since log records may be gone.
         */
        public Map getDependencies() {
            Map r = new HashMap();

            for (Fingerprint fp : getFingerprints().values()) {
                BuildPtr bp = fp.getOriginal();
                if (bp == null) {
                    continue;       // outside Hudson
                }
                if (bp.is(build)) {
                    continue;   // we are the owner
                }
                AbstractProject job = bp.getJob();
                if (job == null) {
                    continue;   // no longer exists
                }
                if (job.getParent() == build.getParent()) {
                    continue;   // we are the parent of the build owner, that is almost like we are the owner 
                }
                Integer existing = r.get(job);
                if (existing != null && existing > bp.getNumber()) {
                    continue;   // the record in the map is already up to date
                }
                r.put(job, bp.getNumber());
            }

            return r;
        }
    }
    private static final Logger logger = Logger.getLogger(Fingerprinter.class.getName());
    private static final long serialVersionUID = 1L;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy