File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\tasks\Fingerprinter.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.tasks;
25
26 import com.google.common.collect.ImmutableMap;
27 import hudson.EnvVars;
28 import hudson.Extension;
29 import hudson.FilePath;
30 import hudson.FilePath.FileCallable;
31 import hudson.Launcher;
32 import hudson.Util;
33 import hudson.matrix.MatrixConfiguration;
34 import hudson.model.AbstractBuild;
35 import hudson.model.AbstractProject;
36 import hudson.model.Action;
37 import hudson.model.BuildListener;
38 import jenkins.model.DependencyDeclarer;
39 import hudson.model.DependencyGraph;
40 import hudson.model.DependencyGraph.Dependency;
41 import hudson.model.Fingerprint;
42 import hudson.model.Fingerprint.BuildPtr;
43 import hudson.model.FingerprintMap;
44 import jenkins.model.Jenkins;
45 import hudson.model.Result;
46 import hudson.model.Run;
47 import hudson.model.RunAction;
48 import hudson.model.TaskListener;
49 import hudson.remoting.VirtualChannel;
50 import hudson.util.FormValidation;
51 import hudson.util.IOException2;
52 import hudson.util.PackedMap;
53 import hudson.util.RunList;
54 import net.sf.json.JSONObject;
55 import org.apache.tools.ant.DirectoryScanner;
56 import org.apache.tools.ant.types.FileSet;
57 import org.kohsuke.stapler.AncestorInPath;
58 import org.kohsuke.stapler.DataBoundConstructor;
59 import org.kohsuke.stapler.QueryParameter;
60 import org.kohsuke.stapler.StaplerRequest;
61
62 import java.io.File;
63 import java.io.IOException;
64 import java.io.Serializable;
65 import java.lang.ref.WeakReference;
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.ListIterator;
71 import java.util.Map;
72 import java.util.Map.Entry;
73 import java.util.Random;
74 import java.util.Set;
75 import java.util.TreeMap;
76 import java.util.logging.Level;
77 import java.util.logging.Logger;
78
79 /**
80 * Records fingerprints of the specified files.
81 *
82 * @author Kohsuke Kawaguchi
83 */
84 public class Fingerprinter extends Recorder implements Serializable, DependencyDeclarer {
85 public static boolean enableFingerprintsInDependencyGraph = Boolean.parseBoolean(System.getProperty(Fingerprinter.class.getName() + ".enableFingerprintsInDependencyGraph", "false"));
86
87 /**
88 * Comma-separated list of files/directories to be fingerprinted.
89 */
90 private final String targets;
91
92 /**
93 * Also record all the finger prints of the build artifacts.
94 */
95 private final boolean recordBuildArtifacts;
96
97 @DataBoundConstructor
98 public Fingerprinter(String targets, boolean recordBuildArtifacts) {
99 this.targets = targets;
100 this.recordBuildArtifacts = recordBuildArtifacts;
101 }
102
103 public String getTargets() {
104 return targets;
105 }
106
107 public boolean getRecordBuildArtifacts() {
108 return recordBuildArtifacts;
109 }
110
111 @Override
112 public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException {
113 try {
114 listener.getLogger().println(Messages.Fingerprinter_Recording());
115
116 Map<String,String> record = new HashMap<String,String>();
117
118 EnvVars environment = build.getEnvironment(listener);
119 if(targets.length()!=0) {
120 String expandedTargets = environment.expand(targets);
121 record(build, listener, record, expandedTargets);
122 }
123
124 if(recordBuildArtifacts) {
125 ArtifactArchiver aa = build.getProject().getPublishersList().get(ArtifactArchiver.class);
126 if(aa==null) {
127 // configuration error
128 listener.error(Messages.Fingerprinter_NoArchiving());
129 build.setResult(Result.FAILURE);
130 return true;
131 }
132 String expandedArtifacts = environment.expand(aa.getArtifacts());
133 record(build, listener, record, expandedArtifacts);
134 }
135
136 build.getActions().add(new FingerprintAction(build,record));
137
138 if (enableFingerprintsInDependencyGraph) {
139 Jenkins.getInstance().rebuildDependencyGraph();
140 }
141 } catch (IOException e) {
142 e.printStackTrace(listener.error(Messages.Fingerprinter_Failed()));
143 build.setResult(Result.FAILURE);
144 }
145
146 // failing to record fingerprints is an error but not fatal
147 return true;
148 }
149
150 public BuildStepMonitor getRequiredMonitorService() {
151 return BuildStepMonitor.NONE;
152 }
153
154 public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
155 if (enableFingerprintsInDependencyGraph) {
156 RunList builds = owner.getBuilds();
157 Set<String> seenUpstreamProjects = new HashSet<String>();
158
159 for ( ListIterator iter = builds.listIterator(); iter.hasNext(); ) {
160 Run build = (Run) iter.next();
161 List<FingerprintAction> fingerprints = build.getActions(FingerprintAction.class);
162 for (FingerprintAction action : fingerprints) {
163 Map<AbstractProject,Integer> deps = action.getDependencies();
164 for (AbstractProject key : deps.keySet()) {
165 if (key == owner) {
166 continue; // Avoid self references
167 }
168
169 AbstractProject p = key;
170 if (key instanceof MatrixConfiguration) {
171 p = key.getRootProject();
172 }
173
174 if (seenUpstreamProjects.contains(p.getName())) {
175 continue;
176 }
177
178 seenUpstreamProjects.add(p.getName());
179 graph.addDependency(new Dependency(p, owner) {
180 @Override
181 public boolean shouldTriggerBuild(AbstractBuild build,
182 TaskListener listener,
183 List<Action> actions) {
184 // Fingerprints should not trigger builds.
185 return false;
186 }
187 });
188 }
189 }
190 }
191 }
192 }
193
194 private void record(AbstractBuild<?,?> build, BuildListener listener, Map<String,String> record, final String targets) throws IOException, InterruptedException {
195 final class Record implements Serializable {
196 final boolean produced;
197 final String relativePath;
198 final String fileName;
199 final String md5sum;
200
201 public Record(boolean produced, String relativePath, String fileName, String md5sum) {
202 this.produced = produced;
203 this.relativePath = relativePath;
204 this.fileName = fileName;
205 this.md5sum = md5sum;
206 }
207
208 Fingerprint addRecord(AbstractBuild build) throws IOException {
209 FingerprintMap map = Jenkins.getInstance().getFingerprintMap();
210 return map.getOrCreate(produced?build:null, fileName, md5sum);
211 }
212
213 private static final long serialVersionUID = 1L;
214 }
215
216 final long buildTimestamp = build.getTimeInMillis();
217
218 FilePath ws = build.getWorkspace();
219 if(ws==null) {
220 listener.error(Messages.Fingerprinter_NoWorkspace());
221 build.setResult(Result.FAILURE);
222 return;
223 }
224
225 List<Record> records = ws.act(new FileCallable<List<Record>>() {
226 public List<Record> invoke(File baseDir, VirtualChannel channel) throws IOException {
227 List<Record> results = new ArrayList<Record>();
228
229 FileSet src = Util.createFileSet(baseDir,targets);
230
231 DirectoryScanner ds = src.getDirectoryScanner();
232 for( String f : ds.getIncludedFiles() ) {
233 File file = new File(baseDir,f);
234
235 // consider the file to be produced by this build only if the timestamp
236 // is newer than when the build has started.
237 // 2000ms is an error margin since since VFAT only retains timestamp at 2sec precision
238 boolean produced = buildTimestamp <= file.lastModified()+2000;
239
240 try {
241 results.add(new Record(produced,f,file.getName(),new FilePath(file).digest()));
242 } catch (IOException e) {
243 throw new IOException2(Messages.Fingerprinter_DigestFailed(file),e);
244 } catch (InterruptedException e) {
245 throw new IOException2(Messages.Fingerprinter_Aborted(),e);
246 }
247 }
248
249 return results;
250 }
251 });
252
253 for (Record r : records) {
254 Fingerprint fp = r.addRecord(build);
255 if(fp==null) {
256 listener.error(Messages.Fingerprinter_FailedFor(r.relativePath));
257 continue;
258 }
259 fp.add(build);
260 record.put(r.relativePath,fp.getHashString());
261 }
262 }
263
264 @Extension
265 public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
266 public String getDisplayName() {
267 return Messages.Fingerprinter_DisplayName();
268 }
269
270 @Override
271 public String getHelpFile() {
272 return "/help/project-config/fingerprint.html";
273 }
274
275 /**
276 * Performs on-the-fly validation on the file mask wildcard.
277 */
278 public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException {
279 return FilePath.validateFileMask(project.getSomeWorkspace(),value);
280 }
281
282 @Override
283 public Publisher newInstance(StaplerRequest req, JSONObject formData) {
284 return req.bindJSON(Fingerprinter.class, formData);
285 }
286
287 public boolean isApplicable(Class<? extends AbstractProject> jobType) {
288 return true;
289 }
290 }
291
292 /**
293 * Action for displaying fingerprints.
294 */
295 public static final class FingerprintAction implements RunAction {
296
297 private final AbstractBuild build;
298
299 private static final Random rand = new Random();
300
301 /**
302 * From file name to the digest.
303 */
304 private /*almost final*/ PackedMap<String,String> record;
305
306 private transient WeakReference<Map<String,Fingerprint>> ref;
307
308 public FingerprintAction(AbstractBuild build, Map<String, String> record) {
309 this.build = build;
310 this.record = PackedMap.of(record);
311 onLoad(); // make compact
312 }
313
314 public void add(Map<String,String> moreRecords) {
315 Map<String,String> r = new HashMap<String, String>(record);
316 r.putAll(moreRecords);
317 record = PackedMap.of(r);
318 ref = null;
319 onLoad();
320 }
321
322 public String getIconFileName() {
323 return "fingerprint.png";
324 }
325
326 public String getDisplayName() {
327 return Messages.Fingerprinter_Action_DisplayName();
328 }
329
330 public String getUrlName() {
331 return "fingerprints";
332 }
333
334 public AbstractBuild getBuild() {
335 return build;
336 }
337
338 /**
339 * Obtains the raw data.
340 */
341 public Map<String,String> getRecords() {
342 return record;
343 }
344
345 public void onLoad() {
346 // share data structure with nearby builds, but to keep lazy loading efficient,
347 // don't go back the history forever.
348 if (rand.nextInt(2)!=0) {
349 Run pb = build.getPreviousBuild();
350 if (pb!=null) {
351 FingerprintAction a = pb.getAction(FingerprintAction.class);
352 if (a!=null)
353 compact(a);
354 }
355 }
356 }
357
358 public void onAttached(Run r) {
359 }
360
361 public void onBuildComplete() {
362 }
363
364 /**
365 * Reuse string instances from another {@link FingerprintAction} to reduce memory footprint.
366 */
367 protected void compact(FingerprintAction a) {
368 Map<String,String> intern = new HashMap<String, String>(); // string intern map
369 for (Entry<String, String> e : a.record.entrySet()) {
370 intern.put(e.getKey(),e.getKey());
371 intern.put(e.getValue(),e.getValue());
372 }
373
374 Map<String,String> b = new HashMap<String, String>();
375 for (Entry<String,String> e : record.entrySet()) {
376 String k = intern.get(e.getKey());
377 if (k==null) k = e.getKey();
378
379 String v = intern.get(e.getValue());
380 if (v==null) v = e.getValue();
381
382 b.put(k,v);
383 }
384
385 record = PackedMap.of(b);
386 }
387
388 /**
389 * Map from file names of the fingerprinted file to its fingerprint record.
390 */
391 public synchronized Map<String,Fingerprint> getFingerprints() {
392 if(ref!=null) {
393 Map<String,Fingerprint> m = ref.get();
394 if(m!=null)
395 return m;
396 }
397
398 Jenkins h = Jenkins.getInstance();
399
400 Map<String,Fingerprint> m = new TreeMap<String,Fingerprint>();
401 for (Entry<String, String> r : record.entrySet()) {
402 try {
403 Fingerprint fp = h._getFingerprint(r.getValue());
404 if(fp!=null)
405 m.put(r.getKey(), fp);
406 } catch (IOException e) {
407 logger.log(Level.WARNING,e.getMessage(),e);
408 }
409 }
410
411 m = ImmutableMap.copyOf(m);
412 ref = new WeakReference<Map<String,Fingerprint>>(m);
413 return m;
414 }
415
416 /**
417 * Gets the dependency to other existing builds in a map.
418 */
419 public Map<AbstractProject,Integer> getDependencies() {
420 return getDependencies(false);
421 }
422
423 /**
424 * Gets the dependency to other builds in a map.
425 *
426 * @param includeMissing true if the original build should be included in
427 * the result, even if it doesn't exist
428 * @since 1.430
429 */
430 public Map<AbstractProject,Integer> getDependencies(boolean includeMissing) {
431 Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
432
433 for (Fingerprint fp : getFingerprints().values()) {
434 BuildPtr bp = fp.getOriginal();
435 if(bp==null) continue; // outside Hudson
436 if(bp.is(build)) continue; // we are the owner
437 AbstractProject job = bp.getJob();
438 if (job==null) continue; // project no longer exists
439 if (job.getParent()==build.getParent())
440 continue; // we are the parent of the build owner, that is almost like we are the owner
441 if(!includeMissing && job.getBuildByNumber(bp.getNumber())==null)
442 continue; // build no longer exists
443
444 Integer existing = r.get(job);
445 if(existing!=null && existing>bp.getNumber())
446 continue; // the record in the map is already up to date
447 r.put(job,bp.getNumber());
448 }
449
450 return r;
451 }
452 }
453
454 private static final Logger logger = Logger.getLogger(Fingerprinter.class.getName());
455
456 private static final long serialVersionUID = 1L;
457 }
458