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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Red Hat, Inc., Victor Glushenkov, Alan Harder, Olivier Lamy, Dominik Bartholdi
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 package hudson.maven;
26
27 import static hudson.model.Result.FAILURE;
28 import hudson.AbortException;
29 import hudson.EnvVars;
30 import hudson.FilePath;
31 import hudson.FilePath.FileCallable;
32 import hudson.Util;
33 import hudson.maven.MavenBuild.ProxyImpl2;
34 import hudson.maven.reporters.MavenAggregatedArtifactRecord;
35 import hudson.maven.reporters.MavenFingerprinter;
36 import hudson.maven.reporters.MavenMailer;
37 import hudson.model.AbstractProject;
38 import hudson.model.Action;
39 import hudson.model.Build;
40 import hudson.model.BuildListener;
41 import hudson.model.Cause.UpstreamCause;
42 import hudson.model.Computer;
43 import hudson.model.Environment;
44 import hudson.model.Executor;
45 import hudson.model.Fingerprint;
46 import hudson.model.Node;
47 import hudson.model.ParameterDefinition;
48 import hudson.model.ParametersAction;
49 import hudson.model.ParametersDefinitionProperty;
50 import hudson.model.Result;
51 import hudson.model.Run;
52 import hudson.model.StringParameterDefinition;
53 import hudson.model.TaskListener;
54 import hudson.remoting.VirtualChannel;
55 import hudson.scm.ChangeLogSet;
56 import hudson.tasks.BuildStep;
57 import hudson.tasks.BuildWrapper;
58 import hudson.tasks.MailSender;
59 import hudson.tasks.Maven.MavenInstallation;
60 import hudson.util.ArgumentListBuilder;
61 import hudson.util.IOUtils;
62 import hudson.util.StreamTaskListener;
63
64 import java.io.File;
65 import java.io.IOException;
66 import java.io.InterruptedIOException;
67 import java.io.PrintStream;
68 import java.text.MessageFormat;
69 import java.util.ArrayList;
70 import java.util.Collection;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.LinkedHashMap;
74 import java.util.LinkedHashSet;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Map.Entry;
78 import java.util.Properties;
79 import java.util.Set;
80 import java.util.logging.Level;
81 import java.util.logging.Logger;
82
83 import jenkins.model.Jenkins;
84 import jenkins.mvn.GlobalSettingsProvider;
85 import jenkins.mvn.SettingsProvider;
86
87 import org.apache.commons.io.FilenameUtils;
88 import org.apache.commons.lang.StringUtils;
89 import org.apache.maven.artifact.versioning.ComparableVersion;
90 import org.apache.maven.model.building.ModelBuildingRequest;
91 import org.apache.maven.project.MavenProject;
92 import org.apache.maven.project.ProjectBuildingException;
93 import org.codehaus.plexus.util.PathTool;
94 import org.kohsuke.stapler.StaplerRequest;
95 import org.kohsuke.stapler.StaplerResponse;
96 import org.kohsuke.stapler.export.Exported;
97 import org.sonatype.aether.transfer.TransferCancelledException;
98 import org.sonatype.aether.transfer.TransferEvent;
99 import org.sonatype.aether.transfer.TransferListener;
100
101 /**
102 * {@link Build} for {@link MavenModuleSet}.
103 *
104 * <p>
105 * A "build" of {@link MavenModuleSet} consists of:
106 *
107 * <ol>
108 * <li>Update the workspace.
109 * <li>Parse POMs
110 * <li>Trigger module builds.
111 * </ol>
112 *
113 * This object remembers the changelog and what {@link MavenBuild}s are done
114 * on this.
115 *
116 * @author Kohsuke Kawaguchi
117 */
118 public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,MavenModuleSetBuild> {
119
120 /**
121 * {@link MavenReporter}s that will contribute project actions.
122 * Can be null if there's none.
123 */
124 /*package*/ List<MavenReporter> projectActionReporters;
125
126 private String mavenVersionUsed;
127
128 private transient Object notifyModuleBuildLock = new Object();
129
130 public MavenModuleSetBuild(MavenModuleSet job) throws IOException {
131 super(job);
132 }
133
134 public MavenModuleSetBuild(MavenModuleSet project, File buildDir) throws IOException {
135 super(project, buildDir);
136 }
137
138 @Override
139 protected void onLoad() {
140 super.onLoad();
141 notifyModuleBuildLock = new Object();
142 }
143
144 /**
145 * Exposes {@code MAVEN_OPTS} to forked processes.
146 *
147 * When we fork Maven, we do so directly by executing Java, thus this environment variable
148 * is pointless (we have to tweak JVM launch option correctly instead, which can be seen in
149 * {@link MavenProcessFactory}), but setting the environment variable explicitly is still
150 * useful in case this Maven forks other Maven processes via normal way. See HUDSON-3644.
151 */
152 @Override
153 public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
154 EnvVars envs = super.getEnvironment(log);
155
156 // We need to add M2_HOME and the mvn binary to the PATH so if Maven
157 // needs to run Maven it will pick the correct one.
158 // This can happen if maven calls ANT which itself calls Maven
159 // or if Maven calls itself e.g. maven-release-plugin
160 MavenInstallation mvn = project.getMaven();
161 if (mvn == null)
162 throw new AbortException(Messages.MavenModuleSetBuild_NoMavenConfigured());
163
164
165 mvn = mvn.forEnvironment(envs);
166
167 Computer computer = Computer.currentComputer();
168 if (computer != null) { // just in case were not in a build
169 Node node = computer.getNode();
170 if (node != null) {
171 mvn = mvn.forNode(node, log);
172 mvn.buildEnvVars(envs);
173 }
174 }
175
176 return envs;
177 }
178
179 /**
180 * Displays the combined status of all modules.
181 * <p>
182 * More precisely, this picks up the status of this build itself,
183 * plus all the latest builds of the modules that belongs to this build.
184 */
185 @Override
186 public Result getResult() {
187 Result r = super.getResult();
188
189 for (MavenBuild b : getModuleLastBuilds().values()) {
190 Result br = b.getResult();
191 if(r==null)
192 r = br;
193 else
194 if(br==Result.NOT_BUILT)
195 continue; // UGLY: when computing combined status, ignore the modules that were not built
196 else
197 if(br!=null)
198 r = r.combine(br);
199 }
200
201 return r;
202 }
203
204 /**
205 * Returns the filtered changeset entries that match the given module.
206 */
207 /*package*/ List<ChangeLogSet.Entry> getChangeSetFor(final MavenModule mod) {
208 return new ArrayList<ChangeLogSet.Entry>() {
209 private static final long serialVersionUID = 5572368347535713298L;
210 {
211 // modules that are under 'mod'. lazily computed
212 List<MavenModule> subsidiaries = null;
213
214 for (ChangeLogSet.Entry e : getChangeSet()) {
215 if(isDescendantOf(e, mod)) {
216 if(subsidiaries==null)
217 subsidiaries = mod.getSubsidiaries();
218
219 // make sure at least one change belongs to this module proper,
220 // and not its subsidiary module
221 if (notInSubsidiary(subsidiaries, e))
222 add(e);
223 }
224 }
225 }
226
227 private boolean notInSubsidiary(List<MavenModule> subsidiaries, ChangeLogSet.Entry e) {
228 for (String path : e.getAffectedPaths())
229 if(!belongsToSubsidiary(subsidiaries, path))
230 return true;
231 return false;
232 }
233
234 private boolean belongsToSubsidiary(List<MavenModule> subsidiaries, String path) {
235 for (MavenModule sub : subsidiaries)
236 if (FilenameUtils.separatorsToUnix(path).startsWith(normalizePath(sub.getRelativePath())))
237 return true;
238 return false;
239 }
240
241 /**
242 * Does this change happen somewhere in the given module or its descendants?
243 */
244 private boolean isDescendantOf(ChangeLogSet.Entry e, MavenModule mod) {
245 for (String path : e.getAffectedPaths()) {
246 if (FilenameUtils.separatorsToUnix(path).startsWith(normalizePath(mod.getRelativePath())))
247 return true;
248 }
249 return false;
250 }
251 };
252 }
253
254 /**
255 * Computes the module builds that correspond to this build.
256 * <p>
257 * A module may be built multiple times (by the user action),
258 * so the value is a list.
259 */
260 public Map<MavenModule,List<MavenBuild>> getModuleBuilds() {
261 Collection<MavenModule> mods = getParent().getModules();
262
263 // identify the build number range. [start,end)
264 MavenModuleSetBuild nb = getNextBuild();
265 int end = nb!=null ? nb.getNumber() : Integer.MAX_VALUE;
266
267 // preserve the order by using LinkedHashMap
268 Map<MavenModule,List<MavenBuild>> r = new LinkedHashMap<MavenModule,List<MavenBuild>>(mods.size());
269
270 for (MavenModule m : mods) {
271 List<MavenBuild> builds = new ArrayList<MavenBuild>();
272 MavenBuild b = m.getNearestBuild(number);
273 while(b!=null && b.getNumber()<end) {
274 builds.add(b);
275 b = b.getNextBuild();
276 }
277 r.put(m,builds);
278 }
279
280 return r;
281 }
282
283 /**
284 * Returns the estimated duration for this builds.
285 * Takes only the modules into account which are actually being build in
286 * case of incremental builds.
287 *
288 * @return the estimated duration in milliseconds
289 * @since 1.383
290 */
291 @Override
292 public long getEstimatedDuration() {
293
294 if (!project.isIncrementalBuild()) {
295 return super.getEstimatedDuration();
296 }
297
298 long result = 0;
299
300 Map<MavenModule, List<MavenBuild>> moduleBuilds = getModuleBuilds();
301
302 boolean noModuleBuildsYet = true;
303
304 for (List<MavenBuild> builds : moduleBuilds.values()) {
305 if (!builds.isEmpty()) {
306 noModuleBuildsYet = false;
307 MavenBuild build = builds.get(0);
308 if (build.getResult() != Result.NOT_BUILT && build.getEstimatedDuration() != -1) {
309 result += build.getEstimatedDuration();
310 }
311 }
312 }
313
314 if (noModuleBuildsYet) {
315 // modules not determined, yet, i.e. POM not parsed.
316 // Use best estimation we have:
317 return super.getEstimatedDuration();
318 }
319
320 result += estimateModuleSetBuildDurationOverhead(3);
321
322 return result != 0 ? result : -1;
323 }
324
325 /**
326 * Estimates the duration overhead the {@link MavenModuleSetBuild} itself adds
327 * to the sum of durations of the module builds.
328 */
329 private long estimateModuleSetBuildDurationOverhead(int numberOfBuilds) {
330 List<MavenModuleSetBuild> moduleSetBuilds = getPreviousBuildsOverThreshold(numberOfBuilds, Result.UNSTABLE);
331
332 if (moduleSetBuilds.isEmpty()) {
333 return 0;
334 }
335
336 long overhead = 0;
337 for(MavenModuleSetBuild moduleSetBuild : moduleSetBuilds) {
338 long sumOfModuleBuilds = 0;
339 for (List<MavenBuild> builds : moduleSetBuild.getModuleBuilds().values()) {
340 if (!builds.isEmpty()) {
341 MavenBuild moduleBuild = builds.get(0);
342 sumOfModuleBuilds += moduleBuild.getDuration();
343 }
344 }
345
346 overhead += Math.max(0, moduleSetBuild.getDuration() - sumOfModuleBuilds);
347 }
348
349 return Math.round((double)overhead / moduleSetBuilds.size());
350 }
351
352 private static String normalizePath(String relPath) {
353 relPath = StringUtils.trimToEmpty( relPath );
354 if (StringUtils.isEmpty( relPath )) {
355 LOGGER.config("No need to normalize an empty path.");
356 } else {
357 if(FilenameUtils.indexOfLastSeparator( relPath ) == -1) {
358 LOGGER.config("No need to normalize "+relPath);
359 } else {
360 String tmp = FilenameUtils.normalize( relPath );
361 if(tmp == null) {
362 LOGGER.config("Path " + relPath + " can not be normalized (parent dir is unknown). Keeping as is.");
363 } else {
364 LOGGER.config("Normalized path " + relPath + " to "+tmp);
365 relPath = tmp;
366 }
367 relPath = FilenameUtils.separatorsToUnix( relPath );
368 }
369 }
370 LOGGER.fine("Returning path " + relPath);
371 return relPath;
372 }
373
374 /**
375 * Gets the version of Maven used for build.
376 *
377 * @return
378 * null if this build is done by earlier version of Jenkins that didn't record this information
379 * (this means the build was done by Maven2.x)
380 */
381 @Exported
382 public String getMavenVersionUsed() {
383 return mavenVersionUsed;
384 }
385
386 public void setMavenVersionUsed( String mavenVersionUsed ) throws IOException {
387 this.mavenVersionUsed = Util.intern(mavenVersionUsed);
388 save();
389 }
390
391 @Override
392 public synchronized void delete() throws IOException {
393 super.delete();
394 // Delete all contained module builds too
395 for (List<MavenBuild> list : getModuleBuilds().values())
396 for (MavenBuild build : list)
397 build.delete();
398 }
399
400 @Override
401 public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
402 // map corresponding module build under this object
403 if(token.indexOf('$')>0) {
404 MavenModule m = getProject().getModule(token);
405 if(m!=null) return m.getBuildByNumber(getNumber());
406 }
407 return super.getDynamic(token,req,rsp);
408 }
409
410 /**
411 * Information about artifacts produced by Maven.
412 */
413 @Exported
414 public MavenAggregatedArtifactRecord getMavenArtifacts() {
415 return getAction(MavenAggregatedArtifactRecord.class);
416 }
417
418 /**
419 * Computes the latest module builds that correspond to this build.
420 * (when individual modules are built, a new ModuleSetBuild is not created,
421 * but rather the new module build falls under the previous ModuleSetBuild)
422 */
423 public Map<MavenModule,MavenBuild> getModuleLastBuilds() {
424 Collection<MavenModule> mods = getParent().getModules();
425
426 // identify the build number range. [start,end)
427 MavenModuleSetBuild nb = getNextBuild();
428 int end = nb!=null ? nb.getNumber() : Integer.MAX_VALUE;
429
430 // preserve the order by using LinkedHashMap
431 Map<MavenModule,MavenBuild> r = new LinkedHashMap<MavenModule,MavenBuild>(mods.size());
432
433 for (MavenModule m : mods) {
434 MavenBuild b = m.getNearestOldBuild(end - 1);
435 if(b!=null && b.getNumber()>=getNumber())
436 r.put(m,b);
437 }
438
439 return r;
440 }
441
442 public void registerAsProjectAction(MavenReporter reporter) {
443 if(projectActionReporters==null)
444 projectActionReporters = new ArrayList<MavenReporter>();
445 projectActionReporters.add(reporter);
446 }
447
448 /**
449 * Finds {@link Action}s from all the module builds that belong to this
450 * {@link MavenModuleSetBuild}. One action per one {@link MavenModule},
451 * and newer ones take precedence over older ones.
452 */
453 public <T extends Action> List<T> findModuleBuildActions(Class<T> action) {
454 Collection<MavenModule> mods = getParent().getModules();
455 List<T> r = new ArrayList<T>(mods.size());
456
457 // identify the build number range. [start,end)
458 MavenModuleSetBuild nb = getNextBuild();
459 int end = nb!=null ? nb.getNumber()-1 : Integer.MAX_VALUE;
460
461 for (MavenModule m : mods) {
462 MavenBuild b = m.getNearestOldBuild(end);
463 while(b!=null && b.getNumber()>=number) {
464 T a = b.getAction(action);
465 if(a!=null) {
466 r.add(a);
467 break;
468 }
469 b = b.getPreviousBuild();
470 }
471 }
472
473 return r;
474 }
475
476 public void run() {
477 execute(new MavenModuleSetBuildExecution());
478 getProject().updateTransientActions();
479 }
480
481 @Override
482 public Fingerprint.RangeSet getDownstreamRelationship(@SuppressWarnings("rawtypes") AbstractProject that) {
483 Fingerprint.RangeSet rs = super.getDownstreamRelationship(that);
484 for(List<MavenBuild> builds : getModuleBuilds().values())
485 for (MavenBuild b : builds)
486 rs.add(b.getDownstreamRelationship(that));
487 return rs;
488 }
489
490 /**
491 * Called when a module build that corresponds to this module set build
492 * has completed.
493 */
494 /*package*/ void notifyModuleBuild(MavenBuild newBuild) {
495 try {
496 // update module set build number
497 getParent().updateNextBuildNumber();
498
499 // update actions
500 Map<MavenModule, List<MavenBuild>> moduleBuilds = getModuleBuilds();
501
502 // actions need to be replaced atomically especially
503 // given that two builds might complete simultaneously.
504 // use a separate lock object since this synchronized block calls into plugins,
505 // which in turn can access other MavenModuleSetBuild instances, which will result in a dead lock.
506 synchronized(notifyModuleBuildLock) {
507 boolean modified = false;
508
509 List<Action> actions = getActions();
510 Set<Class<? extends AggregatableAction>> individuals = new HashSet<Class<? extends AggregatableAction>>();
511 for (Action a : actions) {
512 if(a instanceof MavenAggregatedReport) {
513 MavenAggregatedReport mar = (MavenAggregatedReport) a;
514 mar.update(moduleBuilds,newBuild);
515 individuals.add(mar.getIndividualActionType());
516 modified = true;
517 }
518 }
519
520 // see if the new build has any new aggregatable action that we haven't seen.
521 for (AggregatableAction aa : newBuild.getActions(AggregatableAction.class)) {
522 if(individuals.add(aa.getClass())) {
523 // new AggregatableAction
524 MavenAggregatedReport mar = aa.createAggregatedAction(this, moduleBuilds);
525 mar.update(moduleBuilds,newBuild);
526 addAction(mar);
527 modified = true;
528 }
529 }
530
531 if(modified) {
532 save();
533 getProject().updateTransientActions();
534 }
535 }
536
537 // symlink to this module build
538 String moduleFsName = newBuild.getProject().getModuleName().toFileSystemName();
539 Util.createSymlink(getRootDir(),
540 "../../modules/"+ moduleFsName +"/builds/"+newBuild.getId() /*ugly!*/,
541 moduleFsName, StreamTaskListener.NULL);
542 } catch (IOException e) {
543 LOGGER.log(Level.WARNING,"Failed to update "+this,e);
544 } catch (InterruptedException e) {
545 LOGGER.log(Level.WARNING,"Failed to update "+this,e);
546 }
547 }
548
549 public String getMavenOpts(TaskListener listener, EnvVars envVars) {
550 return envVars.expand(expandTokens(listener, project.getMavenOpts()));
551 }
552
553 /**
554 * The sole job of the {@link MavenModuleSet} build is to update SCM
555 * and triggers module builds.
556 */
557 private class MavenModuleSetBuildExecution extends AbstractBuildExecution {
558 private Map<ModuleName,MavenBuild.ProxyImpl2> proxies;
559
560 protected Result doRun(final BuildListener listener) throws Exception {
561
562 Result r = null;
563 PrintStream logger = listener.getLogger();
564
565 try {
566
567 EnvVars envVars = getEnvironment(listener);
568 MavenInstallation mvn = project.getMaven();
569 if(mvn==null)
570 throw new AbortException(Messages.MavenModuleSetBuild_NoMavenConfigured());
571
572 mvn = mvn.forEnvironment(envVars).forNode(Computer.currentComputer().getNode(), listener);
573
574 MavenInformation mavenInformation = getModuleRoot().act( new MavenVersionCallable( mvn.getHome() ));
575
576 String mavenVersion = mavenInformation.getVersion();
577
578 MavenBuildInformation mavenBuildInformation = new MavenBuildInformation( mavenVersion );
579
580 setMavenVersionUsed( mavenVersion );
581
582 LOGGER.fine(getFullDisplayName()+" is building with mavenVersion " + mavenVersion + " from file " + mavenInformation.getVersionResourcePath());
583
584 if(!project.isAggregatorStyleBuild()) {
585 parsePoms(listener, logger, envVars, mvn, mavenVersion);
586 // start module builds
587 logger.println("Triggering "+project.getRootModule().getModuleName());
588 project.getRootModule().scheduleBuild(new UpstreamCause((Run<?,?>)MavenModuleSetBuild.this));
589 } else {
590 // do builds here
591 try {
592 List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>();
593 for (BuildWrapper w : project.getBuildWrappersList())
594 wrappers.add(w);
595 ParametersAction parameters = getAction(ParametersAction.class);
596 if (parameters != null)
597 parameters.createBuildWrappers(MavenModuleSetBuild.this,wrappers);
598
599 for( BuildWrapper w : wrappers) {
600 Environment e = w.setUp(MavenModuleSetBuild.this, launcher, listener);
601 if(e==null)
602 return (r = Result.FAILURE);
603 buildEnvironments.add(e);
604 e.buildEnvVars(envVars); // #3502: too late for getEnvironment to do this
605 }
606
607 // run pre build steps
608 if(!preBuild(listener,project.getPrebuilders())
609 || !preBuild(listener,project.getPostbuilders())
610 || !preBuild(listener,project.getPublishers())){
611 r = FAILURE;
612 return r;
613 }
614
615 if(!build(listener,project.getPrebuilders().toList())){
616 r = FAILURE;
617 return r;
618 }
619
620 parsePoms(listener, logger, envVars, mvn, mavenVersion); // #5428 : do pre-build *before* parsing pom
621 SplittableBuildListener slistener = new SplittableBuildListener(listener);
622 proxies = new HashMap<ModuleName, ProxyImpl2>();
623 List<ModuleName> changedModules = new ArrayList<ModuleName>();
624
625 if (project.isIncrementalBuild() && !getChangeSet().isEmptySet()) {
626 changedModules.addAll(getUnbuildModulesSinceLastSuccessfulBuild());
627 }
628
629 for (MavenModule m : project.sortedActiveModules) {
630 MavenBuild mb = m.newBuild();
631 // JENKINS-8418
632 mb.setBuiltOnStr( getBuiltOnStr() );
633 // Check if incrementalBuild is selected and that there are changes -
634 // we act as if incrementalBuild is not set if there are no changes.
635 if (!MavenModuleSetBuild.this.getChangeSet().isEmptySet()
636 && project.isIncrementalBuild()) {
637 //If there are changes for this module, add it.
638 // Also add it if we've never seen this module before,
639 // or if the previous build of this module failed or was unstable.
640 if ((mb.getPreviousBuiltBuild() == null) ||
641 (!getChangeSetFor(m).isEmpty())
642 || (mb.getPreviousBuiltBuild().getResult().isWorseThan(Result.SUCCESS))) {
643 changedModules.add(m.getModuleName());
644 }
645 }
646
647 mb.setWorkspace(getModuleRoot().child(m.getRelativePath()));
648 proxies.put(m.getModuleName(), mb.new ProxyImpl2(MavenModuleSetBuild.this,slistener));
649 }
650
651 // run the complete build here
652
653 // figure out the root POM location.
654 // choice of module root ('ws' in this method) is somewhat arbitrary
655 // when multiple CVS/SVN modules are checked out, so also check
656 // the path against the workspace root if that seems like what the user meant (see issue #1293)
657 String rootPOM = project.getRootPOM(envVars); // JENKINS-13822
658 FilePath pom = getModuleRoot().child(rootPOM);
659 FilePath parentLoc = getWorkspace().child(rootPOM);
660 if(!pom.exists() && parentLoc.exists())
661 pom = parentLoc;
662
663
664 final ProcessCache.MavenProcess process;
665
666 boolean maven3orLater = mavenBuildInformation.isMaven3OrLater();
667 if ( maven3orLater )
668 {
669 LOGGER.fine( "using maven 3 " + mavenVersion );
670 process =
671 MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
672 new Maven3ProcessFactory( project, launcher, envVars, getMavenOpts(listener, envVars),
673 pom.getParent() ) );
674 }
675 else
676 {
677 LOGGER.fine( "using maven 2 " + mavenVersion );
678 process =
679 MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
680 new MavenProcessFactory( project, launcher, envVars,getMavenOpts(listener, envVars),
681 pom.getParent() ) );
682 }
683 ArgumentListBuilder margs = new ArgumentListBuilder().add("-B").add("-f", pom.getRemote());
684 FilePath localRepo = project.getLocalRepository().locate(MavenModuleSetBuild.this);
685 if(localRepo!=null)
686 margs.add("-Dmaven.repo.local="+localRepo.getRemote());
687
688 FilePath remoteSettings = SettingsProvider.getSettingsFilePath(project.getSettings(), MavenModuleSetBuild.this, listener);
689 if (remoteSettings != null)
690 margs.add("-s" , remoteSettings.getRemote());
691
692 FilePath remoteGlobalSettings = GlobalSettingsProvider.getSettingsFilePath(project.getGlobalSettings(), MavenModuleSetBuild.this, listener);
693 if (remoteGlobalSettings != null)
694 margs.add("-gs" , remoteGlobalSettings.getRemote());
695
696 // If incrementalBuild is set
697 // and the previous build didn't specify that we need a full build
698 // and we're on Maven 2.1 or later
699 // and there's at least one module listed in changedModules,
700 // then do the Maven incremental build commands.
701 // If there are no changed modules, we're building everything anyway.
702 boolean maven2_1orLater = new ComparableVersion (mavenVersion).compareTo( new ComparableVersion ("2.1") ) >= 0;
703 boolean needsFullBuild = getPreviousCompletedBuild() != null &&
704 getPreviousCompletedBuild().getAction(NeedsFullBuildAction.class) != null;
705 if (project.isIncrementalBuild()) {
706 if (!needsFullBuild && maven2_1orLater && !changedModules.isEmpty()) {
707 margs.add("-amd");
708 margs.add("-pl", Util.join(changedModules, ","));
709 } else {
710 if (LOGGER.isLoggable(Level.FINE)) {
711 LOGGER.fine(String.format("Skipping incremental build: needsFullBuild=%s, maven2.1orLater=%s, changedModulesEmpty?=%s",
712 needsFullBuild, maven2_1orLater, changedModules.isEmpty()));
713 }
714 }
715 }
716
717
718
719 final List<MavenArgumentInterceptorAction> argInterceptors = this.getBuild().getActions(MavenArgumentInterceptorAction.class);
720
721 // find the correct maven goals and options, there might by an action overruling the defaults
722 String goals = project.getGoals(); // default
723 for (MavenArgumentInterceptorAction mavenArgInterceptor : argInterceptors) {
724 final String goalsAndOptions = mavenArgInterceptor.getGoalsAndOptions((MavenModuleSetBuild)this.getBuild());
725 if(StringUtils.isNotBlank(goalsAndOptions)){
726 goals = goalsAndOptions;
727 // only one interceptor is allowed to overwrite the whole "goals and options" string
728 break;
729 }
730 }
731 margs.addTokenized(envVars.expand(goals));
732
733 // enable the interceptors to change the whole command argument list
734 // all available interceptors are allowed to modify the argument list
735 for (MavenArgumentInterceptorAction mavenArgInterceptor : argInterceptors) {
736 final ArgumentListBuilder newMargs = mavenArgInterceptor.intercept(margs, (MavenModuleSetBuild)this.getBuild());
737 if (newMargs != null) {
738 margs = newMargs;
739 }
740 }
741
742 final AbstractMavenBuilder builder;
743 if (maven3orLater) {
744 builder =
745 new Maven3Builder( slistener, proxies, project.sortedActiveModules, margs.toList(), envVars, mavenBuildInformation );
746 } else {
747 builder =
748 new Maven2Builder(slistener, proxies, project.sortedActiveModules, margs.toList(), envVars, mavenBuildInformation);
749 }
750
751 MavenProbeAction mpa=null;
752 try {
753 mpa = new MavenProbeAction(project,process.channel);
754 addAction(mpa);
755 r = process.call(builder);
756 return r;
757 } finally {
758 builder.end(launcher);
759 getActions().remove(mpa);
760 process.discard();
761 }
762
763 } catch (InterruptedException e) {
764 r = Executor.currentExecutor().abortResult();
765 throw e;
766 } finally {
767 // only run post build steps if requested...
768 if (r==null || r.isBetterOrEqualTo(project.getRunPostStepsIfResult())) {
769 if(!build(listener,project.getPostbuilders().toList())){
770 r = FAILURE;
771 }
772 }
773
774 if (r != null) {
775 setResult(r);
776 }
777
778 // tear down in reverse order
779 boolean failed=false;
780 for( int i=buildEnvironments.size()-1; i>=0; i-- ) {
781 if (!buildEnvironments.get(i).tearDown(MavenModuleSetBuild.this,listener)) {
782 failed=true;
783 }
784 }
785 // WARNING The return in the finally clause will trump any return before
786 if (failed) return Result.FAILURE;
787 }
788 }
789
790
791 return r;
792 } catch (AbortException e) {
793 if(e.getMessage()!=null)
794 listener.error(e.getMessage());
795 return Result.FAILURE;
796 } catch (InterruptedIOException e) {
797 e.printStackTrace(listener.error("Aborted Maven execution for InterruptedIOException"));
798 return Executor.currentExecutor().abortResult();
799 } catch (IOException e) {
800 e.printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
801 return Result.FAILURE;
802 } catch (RunnerAbortedException e) {
803 return Result.FAILURE;
804 } catch (RuntimeException e) {
805 // bug in the code.
806 e.printStackTrace(listener.error("Processing failed due to a bug in the code. Please report this to jenkinsci-users@googlegroups.com"));
807 logger.println("project="+project);
808 logger.println("project.getModules()="+project.getModules());
809 logger.println("project.getRootModule()="+project.getRootModule());
810 throw e;
811 } finally {
812 }
813 }
814
815
816 private boolean build(BuildListener listener, Collection<hudson.tasks.Builder> steps) throws IOException, InterruptedException {
817 for( BuildStep bs : steps ){
818 if(!perform(bs,listener)) {
819 LOGGER.fine(MessageFormat.format("{1} failed", bs));
820 return false;
821 }
822 }
823 return true;
824 }
825
826 /**
827 * Returns the modules which have not been build since the last successful aggregator build
828 * though they should be because they had SCM changes.
829 * This can happen when the aggregator build fails before it reaches the module.
830 *
831 * See JENKINS-5764
832 */
833 private Collection<ModuleName> getUnbuildModulesSinceLastSuccessfulBuild() {
834 Collection<ModuleName> unbuiltModules = new ArrayList<ModuleName>();
835 MavenModuleSetBuild previousSuccessfulBuild = getPreviousSuccessfulBuild();
836 if (previousSuccessfulBuild == null) {
837 // no successful build, yet. Just take the 1st build
838 previousSuccessfulBuild = getParent().getFirstBuild();
839 }
840
841 if (previousSuccessfulBuild != null) {
842 MavenModuleSetBuild previousBuild = previousSuccessfulBuild;
843 do {
844 UnbuiltModuleAction unbuiltModuleAction = previousBuild.getAction(UnbuiltModuleAction.class);
845 if (unbuiltModuleAction != null) {
846 for (ModuleName name : unbuiltModuleAction.getUnbuildModules()) {
847 unbuiltModules.add(name);
848 }
849 }
850
851 previousBuild = previousBuild.getNextBuild();
852 } while (previousBuild != null && previousBuild != MavenModuleSetBuild.this);
853 }
854 return unbuiltModules;
855 }
856
857 private void parsePoms(BuildListener listener, PrintStream logger, EnvVars envVars, MavenInstallation mvn, String mavenVersion) throws IOException, InterruptedException {
858 logger.println("Parsing POMs");
859
860 List<PomInfo> poms;
861 try {
862 poms = getModuleRoot().act(new PomParser(listener, mvn, mavenVersion, envVars, MavenModuleSetBuild.this));
863 } catch (IOException e) {
864 if (project.isIncrementalBuild()) {
865 // If POM parsing failed we should do a full build next time.
866 // Otherwise only the modules which have a SCM change for the next build might
867 // be build next time.
868 getActions().add(new NeedsFullBuildAction());
869 }
870
871 if (e.getCause() instanceof AbortException)
872 throw (AbortException) e.getCause();
873 throw e;
874 } catch (MavenExecutionException e) {
875 // Maven failed to parse POM
876 e.getCause().printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
877 if (project.isIncrementalBuild()) {
878 getActions().add(new NeedsFullBuildAction());
879 }
880 throw new AbortException();
881 }
882
883 boolean needsDependencyGraphRecalculation = false;
884
885 // update the module list
886 Map<ModuleName,MavenModule> modules = project.modules;
887 synchronized(modules) {
888 Map<ModuleName,MavenModule> old = new HashMap<ModuleName, MavenModule>(modules);
889 List<MavenModule> sortedModules = new ArrayList<MavenModule>();
890
891 modules.clear();
892 if(debug)
893 logger.println("Root POM is "+poms.get(0).name);
894 project.reconfigure(poms.get(0));
895 for (PomInfo pom : poms) {
896 MavenModule mm = old.get(pom.name);
897 if(mm!=null) {// found an existing matching module
898 if(debug)
899 logger.println("Reconfiguring "+mm);
900 if (!mm.isSameModule(pom)) {
901 needsDependencyGraphRecalculation = true;
902 }
903 mm.reconfigure(pom);
904 modules.put(pom.name,mm);
905 } else {// this looks like a new module
906 logger.println(Messages.MavenModuleSetBuild_DiscoveredModule(pom.name,pom.displayName));
907 mm = new MavenModule(project,pom,getNumber());
908 mm.onCreatedFromScratch();
909 modules.put(mm.getModuleName(),mm);
910 needsDependencyGraphRecalculation = true;
911 }
912 sortedModules.add(mm);
913 mm.save();
914 }
915 // at this point the list contains all the live modules
916 project.sortedActiveModules = sortedModules;
917
918 // remaining modules are no longer active.
919 old.keySet().removeAll(modules.keySet());
920 for (MavenModule om : old.values()) {
921 if(debug)
922 logger.println("Disabling "+om);
923 om.makeDisabled(true);
924 needsDependencyGraphRecalculation = true;
925 }
926 modules.putAll(old);
927 }
928
929 // we might have added new modules
930 if (needsDependencyGraphRecalculation) {
931 logger.println("Modules changed, recalculating dependency graph");
932 Jenkins.getInstance().rebuildDependencyGraph();
933 }
934
935 // module builds must start with this build's number
936 for (MavenModule m : modules.values())
937 m.updateNextBuildNumber(getNumber());
938 }
939
940 protected void post2(BuildListener listener) throws Exception {
941 // asynchronous executions from the build might have left some unsaved state,
942 // so just to be safe, save them all.
943 for (MavenBuild b : getModuleLastBuilds().values())
944 b.save();
945
946 // at this point the result is all set, so ignore the return value
947 if (!performAllBuildSteps(listener, project.getPublishers(), true))
948 setResult(FAILURE);
949 if (!performAllBuildSteps(listener, project.getProperties(), true))
950 setResult(FAILURE);
951
952 // aggregate all module fingerprints to us,
953 // so that dependencies between module builds can be understood as
954 // dependencies between module set builds.
955 // TODO: we really want to implement this as a publisher,
956 // but we don't want to ask for a user configuration, nor should it
957 // show up in the persisted record.
958 MavenFingerprinter.aggregate(MavenModuleSetBuild.this);
959 }
960
961 @Override
962 public void cleanUp(BuildListener listener) throws Exception {
963 MavenMailer mailer = project.getReporters().get(MavenMailer.class);
964 if (mailer != null) {
965 new MailSender(mailer.recipients,
966 mailer.dontNotifyEveryUnstableBuild,
967 mailer.sendToIndividuals).execute(MavenModuleSetBuild.this, listener);
968 }
969
970 // too late to set the build result at this point. so ignore failures.
971 performAllBuildSteps(listener, project.getPublishers(), false);
972 performAllBuildSteps(listener, project.getProperties(), false);
973 super.cleanUp(listener);
974 }
975
976 }
977
978 /**
979 * Used to tunnel exception from Maven through remoting.
980 */
981 private static final class MavenExecutionException extends RuntimeException {
982 private MavenExecutionException(Exception cause) {
983 super(cause);
984 }
985
986 @Override
987 public Exception getCause() {
988 return (Exception)super.getCause();
989 }
990
991 private static final long serialVersionUID = 1L;
992 }
993
994 /**
995 * Executed on the slave to parse POM and extract information into {@link PomInfo},
996 * which will be then brought back to the master.
997 */
998 private static final class PomParser implements FileCallable<List<PomInfo>> {
999 private final BuildListener listener;
1000 private final String rootPOM;
1001 /**
1002 * Capture the value of the static field so that the debug flag
1003 * takes an effect even when {@link PomParser} runs in a slave.
1004 */
1005 private final boolean verbose = debug;
1006 private final MavenInstallation mavenHome;
1007 private final String profiles;
1008 private final Properties properties;
1009 private final String privateRepository;
1010 private final String alternateSettings;
1011 private final String globalSettings;
1012 private final boolean nonRecursive;
1013 // We're called against the module root, not the workspace, which can cause a lot of confusion.
1014 private final String workspaceProper;
1015 private final String mavenVersion;
1016
1017 private final String moduleRootPath;
1018
1019 private boolean resolveDependencies = false;
1020
1021 private boolean processPlugins = false;
1022
1023 private int mavenValidationLevel = -1;
1024
1025 private boolean updateSnapshots = false;
1026
1027 String rootPOMRelPrefix;
1028
1029 public PomParser(BuildListener listener, MavenInstallation mavenHome, String mavenVersion, EnvVars envVars, MavenModuleSetBuild build) {
1030 // project cannot be shipped to the remote JVM, so all the relevant properties need to be captured now.
1031 MavenModuleSet project = build.getProject();
1032 this.listener = listener;
1033 this.mavenHome = mavenHome;
1034 this.rootPOM = project.getRootPOM(envVars); // JENKINS-13822
1035 this.profiles = project.getProfiles();
1036 this.properties = project.getMavenProperties();
1037 this.updateSnapshots = isUpdateSnapshots(project.getGoals());
1038 ParametersDefinitionProperty parametersDefinitionProperty = project.getProperty( ParametersDefinitionProperty.class );
1039 if (parametersDefinitionProperty != null && parametersDefinitionProperty.getParameterDefinitions() != null) {
1040 for (ParameterDefinition parameterDefinition : parametersDefinitionProperty.getParameterDefinitions()) {
1041 // those must used as env var
1042 if (parameterDefinition instanceof StringParameterDefinition) {
1043 this.properties.put( "env." + parameterDefinition.getName(), ((StringParameterDefinition)parameterDefinition).getDefaultValue() );
1044 }
1045 }
1046 }
1047 if (envVars != null && !envVars.isEmpty()) {
1048 for (Entry<String,String> entry : envVars.entrySet()) {
1049 if (entry.getKey() != null && entry.getValue() != null) {
1050 this.properties.put( "env." + entry.getKey(), entry.getValue() );
1051 }
1052 }
1053 }
1054
1055 this.nonRecursive = project.isNonRecursive();
1056
1057 this.workspaceProper = build.getWorkspace().getRemote();
1058 LOGGER.fine("Workspace is " + workspaceProper);
1059 FilePath localRepo = project.getLocalRepository().locate(build);
1060 if (localRepo!=null) {
1061 this.privateRepository = localRepo.getRemote();
1062 } else {
1063 this.privateRepository = null;
1064 }
1065
1066 this.alternateSettings = SettingsProvider.getSettingsRemotePath(project.getSettings(), build, listener);
1067 this.globalSettings = GlobalSettingsProvider.getSettingsRemotePath(project.getGlobalSettings(), build, listener);
1068
1069 this.mavenVersion = mavenVersion;
1070 this.resolveDependencies = project.isResolveDependencies();
1071 this.processPlugins = project.isProcessPlugins();
1072
1073 this.moduleRootPath =
1074 project.getScm().getModuleRoot( build.getWorkspace(), project.getLastBuild() ).getRemote();
1075
1076 this.mavenValidationLevel = project.getMavenValidationLevel();
1077 }
1078
1079 private boolean isUpdateSnapshots(String goals) {
1080 return StringUtils.contains(goals, "-U") || StringUtils.contains(goals, "--update-snapshots");
1081 }
1082
1083 public List<PomInfo> invoke(File ws, VirtualChannel channel) throws IOException {
1084 File pom;
1085
1086 PrintStream logger = listener.getLogger();
1087
1088 if (IOUtils.isAbsolute(rootPOM)) {
1089 pom = new File(rootPOM);
1090 } else {
1091 // choice of module root ('ws' in this method) is somewhat arbitrary
1092 // when multiple CVS/SVN modules are checked out, so also check
1093 // the path against the workspace root if that seems like what the user meant (see issue #1293)
1094 pom = new File(ws, rootPOM);
1095 File parentLoc = new File(ws.getParentFile(),rootPOM);
1096 if(!pom.exists() && parentLoc.exists())
1097 pom = parentLoc;
1098 }
1099
1100 if(!pom.exists())
1101 throw new AbortException(Messages.MavenModuleSetBuild_NoSuchPOMFile(pom));
1102
1103 if (rootPOM.startsWith("../") || rootPOM.startsWith("..\\")) {
1104 File wsp = new File(workspaceProper);
1105
1106 if (!ws.equals(wsp)) {
1107 rootPOMRelPrefix = ws.getCanonicalPath().substring(wsp.getCanonicalPath().length()+1)+"/";
1108 } else {
1109 rootPOMRelPrefix = wsp.getName() + "/";
1110 }
1111 } else {
1112 rootPOMRelPrefix = "";
1113 }
1114
1115 if(verbose)
1116 logger.println("Parsing "
1117 + (nonRecursive ? "non-recursively " : "recursively ")
1118 + pom);
1119
1120 File settingsLoc;
1121
1122 if (alternateSettings == null) {
1123 settingsLoc = null;
1124 } else if (IOUtils.isAbsolute(alternateSettings)) {
1125 settingsLoc = new File(alternateSettings);
1126 } else {
1127 // Check for settings.xml first in the workspace proper, and then in the current directory,
1128 // which is getModuleRoot().
1129 // This is backwards from the order the root POM logic uses, but it's to be consistent with the Maven execution logic.
1130 settingsLoc = new File(workspaceProper, alternateSettings);
1131 File mrSettingsLoc = new File(workspaceProper, alternateSettings);
1132 if (!settingsLoc.exists() && mrSettingsLoc.exists())
1133 settingsLoc = mrSettingsLoc;
1134 }
1135 if (debug)
1136 {
1137 logger.println(Messages.MavenModuleSetBuild_SettinsgXmlAndPrivateRepository(settingsLoc,privateRepository));
1138 }
1139 if ((settingsLoc != null) && (!settingsLoc.exists())) {
1140 throw new AbortException(Messages.MavenModuleSetBuild_NoSuchAlternateSettings(settingsLoc.getAbsolutePath()));
1141 }
1142
1143 try {
1144 MavenEmbedderRequest mavenEmbedderRequest = new MavenEmbedderRequest( listener, mavenHome.getHomeDir(),
1145 profiles, properties,
1146 privateRepository, settingsLoc );
1147 mavenEmbedderRequest.setTransferListener( new SimpleTransferListener(listener) );
1148 mavenEmbedderRequest.setUpdateSnapshots( this.updateSnapshots );
1149
1150 mavenEmbedderRequest.setProcessPlugins( this.processPlugins );
1151 mavenEmbedderRequest.setResolveDependencies( this.resolveDependencies );
1152 if (globalSettings != null) {
1153 mavenEmbedderRequest.setGlobalSettings( new File(globalSettings) );
1154 }
1155
1156 // FIXME handle 3.1 level when version will be here : no rush :-)
1157 // or made something configurable tru the ui ?
1158 ReactorReader reactorReader = null;
1159 boolean maven3OrLater = MavenUtil.maven3orLater(mavenVersion);
1160 if (maven3OrLater) {
1161 mavenEmbedderRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
1162 } else {
1163 reactorReader = new ReactorReader( new HashMap<String, MavenProject>(), new File(workspaceProper) );
1164 mavenEmbedderRequest.setWorkspaceReader( reactorReader );
1165 }
1166
1167
1168 if (this.mavenValidationLevel >= 0) {
1169 mavenEmbedderRequest.setValidationLevel( this.mavenValidationLevel );
1170 }
1171
1172 //mavenEmbedderRequest.setClassLoader( MavenEmbedderUtils.buildClassRealm( mavenHome.getHomeDir(), null, null ) );
1173
1174 MavenEmbedder embedder = MavenUtil.createEmbedder( mavenEmbedderRequest );
1175
1176 MavenProject rootProject = null;
1177
1178 List<MavenProject> mps = new ArrayList<MavenProject>(0);
1179 if (maven3OrLater) {
1180 mps = embedder.readProjects( pom,!this.nonRecursive );
1181
1182 } else {
1183 // http://issues.jenkins-ci.org/browse/HUDSON-8390
1184 // we cannot read maven projects in one time for backward compatibility
1185 // but we have to use a ReactorReader to get some pom with bad inheritence configured
1186 MavenProject mavenProject = embedder.readProject( pom );
1187 rootProject = mavenProject;
1188 mps.add( mavenProject );
1189 reactorReader.addProject( mavenProject );
1190 if (!this.nonRecursive) {
1191 readChilds( mavenProject, embedder, mps, reactorReader );
1192 }
1193 }
1194 Map<String,MavenProject> canonicalPaths = new HashMap<String, MavenProject>( mps.size() );
1195 for(MavenProject mp : mps) {
1196 // Projects are indexed by POM path and not module path because
1197 // Maven allows to have several POMs with different names in the same directory
1198 canonicalPaths.put( mp.getFile().getCanonicalPath(), mp );
1199 }
1200 //MavenUtil.resolveModules(embedder,mp,getRootPath(rootPOMRelPrefix),relPath,listener,nonRecursive);
1201
1202 if(verbose) {
1203 for (Entry<String,MavenProject> e : canonicalPaths.entrySet())
1204 logger.printf("Discovered %s at %s\n",e.getValue().getId(),e.getKey());
1205 }
1206
1207 Set<PomInfo> infos = new LinkedHashSet<PomInfo>();
1208
1209 if (maven3OrLater) {
1210 for (MavenProject mp : mps) {
1211 if (mp.isExecutionRoot()) {
1212 rootProject = mp;
1213 continue;
1214 }
1215 }
1216 }
1217 // if rootProject is null but no reason :-) use the first one
1218 if (rootProject == null) {
1219 rootProject = mps.get( 0 );
1220 }
1221 toPomInfo(rootProject,null,canonicalPaths,infos);
1222
1223 for (PomInfo pi : infos)
1224 pi.cutCycle();
1225
1226 return new ArrayList<PomInfo>(infos);
1227 } catch (MavenEmbedderException e) {
1228 throw new MavenExecutionException(e);
1229 } catch (ProjectBuildingException e) {
1230 throw new MavenExecutionException(e);
1231 }
1232 }
1233
1234 /**
1235 * @see PomInfo#relativePath to understand relPath calculation
1236 */
1237 private void toPomInfo(MavenProject mp, PomInfo parent, Map<String,MavenProject> abslPath, Set<PomInfo> infos) throws IOException {
1238
1239 String relPath = PathTool.getRelativeFilePath( this.moduleRootPath, mp.getBasedir().getPath() );
1240 relPath = normalizePath(relPath);
1241
1242 if (parent == null ) {
1243 relPath = getRootPath(rootPOMRelPrefix);
1244 }
1245
1246 relPath = StringUtils.removeStart( relPath, "/" );
1247
1248 PomInfo pi = new PomInfo(mp, parent, relPath);
1249 infos.add(pi);
1250 if(!this.nonRecursive) {
1251 for (String modulePath : mp.getModules())
1252 {
1253 if (StringUtils.isBlank( modulePath )) {
1254 continue;
1255 }
1256 File path = new File(mp.getBasedir(), modulePath);
1257 // HUDSON-8391 : Modules are indexed by POM path thus
1258 // by default we have to add the default pom.xml file
1259 if(path.isDirectory())
1260 path = new File(mp.getBasedir(), modulePath+"/pom.xml");
1261 MavenProject child = abslPath.get( path.getCanonicalPath());
1262 if (child == null) {
1263 listener.getLogger().printf(Messages.MavenModuleSetBuild_FoundModuleWithoutProject(modulePath));
1264 continue;
1265 }
1266 toPomInfo(child,pi,abslPath,infos);
1267 }
1268 }
1269 }
1270
1271 private void readChilds(MavenProject mp, MavenEmbedder mavenEmbedder, List<MavenProject> mavenProjects, ReactorReader reactorReader)
1272 throws ProjectBuildingException, MavenEmbedderException {
1273 if (mp.getModules() == null || mp.getModules().isEmpty()) {
1274 return;
1275 }
1276 for (String module : mp.getModules()) {
1277 if ( Util.fixEmptyAndTrim( module ) != null ) {
1278 File pomFile = new File(mp.getFile().getParent(), module);
1279 MavenProject mavenProject2 = null;
1280 // take care of HUDSON-8445
1281 if (pomFile.isFile())
1282 mavenProject2 = mavenEmbedder.readProject( pomFile );
1283 else
1284 mavenProject2 = mavenEmbedder.readProject( new File(mp.getFile().getParent(), module + "/pom.xml") );
1285 mavenProjects.add( mavenProject2 );
1286 reactorReader.addProject( mavenProject2 );
1287 readChilds( mavenProject2, mavenEmbedder, mavenProjects, reactorReader );
1288 }
1289 }
1290 }
1291
1292 /**
1293 * Computes the path of {@link #rootPOM}.
1294 *
1295 * Returns "abc" if rootPOM="abc/pom.xml"
1296 * If rootPOM="pom.xml", this method returns "".
1297 */
1298 private String getRootPath(String prefix) {
1299 int idx = Math.max(rootPOM.lastIndexOf('/'), rootPOM.lastIndexOf('\\'));
1300 if(idx==-1) return "";
1301 return prefix + rootPOM.substring(0,idx);
1302 }
1303
1304
1305 private static final long serialVersionUID = 1L;
1306 }
1307
1308 private static final Logger LOGGER = Logger.getLogger(MavenModuleSetBuild.class.getName());
1309
1310 /**
1311 * Extra verbose debug switch.
1312 */
1313 public static boolean debug = Boolean.getBoolean( "hudson.maven.debug" );
1314
1315 @Override
1316 public MavenModuleSet getParent() {// don't know why, but javac wants this
1317 return super.getParent();
1318 }
1319
1320 /**
1321 * will log in the {@link TaskListener} when transferFailed and transferSucceeded
1322 * @author Olivier Lamy
1323 * @since
1324 */
1325 public static class SimpleTransferListener implements TransferListener
1326 {
1327 private TaskListener taskListener;
1328 public SimpleTransferListener(TaskListener taskListener)
1329 {
1330 this.taskListener = taskListener;
1331 }
1332
1333 public void transferCorrupted( TransferEvent arg0 )
1334 throws TransferCancelledException
1335 {
1336 // no op
1337 }
1338
1339 public void transferFailed( TransferEvent transferEvent )
1340 {
1341 taskListener.getLogger().println(Messages.MavenModuleSetBuild_FailedToTransfer(transferEvent.getException().getMessage()));
1342 }
1343
1344 public void transferInitiated( TransferEvent arg0 )
1345 throws TransferCancelledException
1346 {
1347 // no op
1348 }
1349
1350 public void transferProgressed( TransferEvent arg0 )
1351 throws TransferCancelledException
1352 {
1353 // no op
1354 }
1355
1356 public void transferStarted( TransferEvent arg0 )
1357 throws TransferCancelledException
1358 {
1359 // no op
1360 }
1361
1362 public void transferSucceeded( TransferEvent transferEvent )
1363 {
1364 taskListener.getLogger().println( Messages.MavenModuleSetBuild_DownloadedArtifact(
1365 transferEvent.getResource().getRepositoryUrl(),
1366 transferEvent.getResource().getResourceName()) );
1367 }
1368
1369 }
1370 }
1371