MavenModuleSetBuild.java 62.0 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
I
imod 已提交
5
 * Red Hat, Inc., Victor Glushenkov, Alan Harder, Olivier Lamy, Dominik Bartholdi
K
kohsuke 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
25 26
package hudson.maven;

S
Seiji Sogabe 已提交
27 28 29 30
import static hudson.model.Result.FAILURE;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
31
import hudson.FilePath.FileCallable;
S
Seiji Sogabe 已提交
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.maven.settings.GlobalMavenSettingsProvider;
38
import hudson.maven.settings.MavenSettingsProvider;
O
Olivier Lamy 已提交
39
import hudson.maven.settings.SettingsProviderUtils;
O
Olivier Lamy 已提交
40 41 42 43
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.BuildListener;
M
mdonohue 已提交
44
import hudson.model.Cause.UpstreamCause;
O
Olivier Lamy 已提交
45 46
import hudson.model.Computer;
import hudson.model.Environment;
47
import hudson.model.Executor;
O
Olivier Lamy 已提交
48
import hudson.model.Fingerprint;
49
import hudson.model.Node;
O
Olivier Lamy 已提交
50 51 52 53 54 55 56
import hudson.model.ParameterDefinition;
import hudson.model.ParametersAction;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.StringParameterDefinition;
import hudson.model.TaskListener;
57
import hudson.remoting.VirtualChannel;
K
km 已提交
58
import hudson.scm.ChangeLogSet;
I
imod 已提交
59
import hudson.tasks.BuildStep;
60
import hudson.tasks.BuildWrapper;
61
import hudson.tasks.MailSender;
62
import hudson.tasks.Maven.MavenInstallation;
63
import hudson.util.ArgumentListBuilder;
K
km 已提交
64
import hudson.util.IOUtils;
65
import hudson.util.StreamTaskListener;
S
Seiji Sogabe 已提交
66 67 68 69 70

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PrintStream;
I
imod 已提交
71
import java.text.MessageFormat;
S
Seiji Sogabe 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

I
imod 已提交
86 87
import jenkins.model.Jenkins;

S
Seiji Sogabe 已提交
88 89 90
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.versioning.ComparableVersion;
91
import org.apache.maven.model.building.ModelBuildingRequest;
92 93
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
94
import org.codehaus.plexus.util.PathTool;
95
import org.jenkinsci.lib.configprovider.model.Config;
96 97
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
98
import org.kohsuke.stapler.export.Exported;
S
Seiji Sogabe 已提交
99 100 101
import org.sonatype.aether.transfer.TransferCancelledException;
import org.sonatype.aether.transfer.TransferEvent;
import org.sonatype.aether.transfer.TransferListener;
102

103 104 105 106 107 108 109 110 111 112 113 114 115 116
/**
 * {@link Build} for {@link MavenModuleSet}.
 *
 * <p>
 * A "build" of {@link MavenModuleSet} consists of:
 *
 * <ol>
 * <li>Update the workspace.
 * <li>Parse POMs
 * <li>Trigger module builds.
 * </ol>
 *
 * This object remembers the changelog and what {@link MavenBuild}s are done
 * on this.
K
kohsuke 已提交
117
 *
118 119
 * @author Kohsuke Kawaguchi
 */
120
public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,MavenModuleSetBuild> {
I
imod 已提交
121
	
122 123 124 125 126 127
    /**
     * {@link MavenReporter}s that will contribute project actions.
     * Can be null if there's none.
     */
    /*package*/ List<MavenReporter> projectActionReporters;

128 129
    private String mavenVersionUsed;

130 131
    private transient Object notifyModuleBuildLock = new Object();

132 133 134 135
    public MavenModuleSetBuild(MavenModuleSet job) throws IOException {
        super(job);
    }

136
    public MavenModuleSetBuild(MavenModuleSet project, File buildDir) throws IOException {
137 138 139
        super(project, buildDir);
    }

140 141 142 143 144 145
    @Override
    protected void onLoad() {
        super.onLoad();
        notifyModuleBuildLock = new Object();
    }

146 147 148 149 150 151 152 153 154 155 156
    /**
     * Exposes {@code MAVEN_OPTS} to forked processes.
     *
     * When we fork Maven, we do so directly by executing Java, thus this environment variable
     * is pointless (we have to tweak JVM launch option correctly instead, which can be seen in
     * {@link MavenProcessFactory}), but setting the environment variable explicitly is still
     * useful in case this Maven forks other Maven processes via normal way. See HUDSON-3644.
     */
    @Override
    public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
        EnvVars envs = super.getEnvironment(log);
157

J
James Nord 已提交
158 159 160 161 162 163
        // We need to add M2_HOME and the mvn binary to the PATH so if Maven
        // needs to run Maven it will pick the correct one.
        // This can happen if maven calls ANT which itself calls Maven
        // or if Maven calls itself e.g. maven-release-plugin
        MavenInstallation mvn = project.getMaven();
        if (mvn == null)
K
i18n  
Kohsuke Kawaguchi 已提交
164
            throw new AbortException(Messages.MavenModuleSetBuild_NoMavenConfigured());
J
James Nord 已提交
165

166 167
        
        mvn = mvn.forEnvironment(envs);
168 169 170 171 172 173 174 175 176 177
        
        Computer computer = Computer.currentComputer();
        if (computer != null) { // just in case were not in a build
            Node node = computer.getNode();
            if (node != null) {
                mvn = mvn.forNode(node, log);
                
                envs.put("M2_HOME", mvn.getHome());
                envs.put("PATH+MAVEN", mvn.getHome() + "/bin");
            }
178 179
        }
        
180 181 182
        return envs;
    }

K
kohsuke 已提交
183
    /**
184 185 186
     * Displays the combined status of all modules.
     * <p>
     * More precisely, this picks up the status of this build itself,
K
kohsuke 已提交
187
     * plus all the latest builds of the modules that belongs to this build.
K
kohsuke 已提交
188 189 190 191 192
     */
    @Override
    public Result getResult() {
        Result r = super.getResult();

193 194 195 196 197
        for (MavenBuild b : getModuleLastBuilds().values()) {
            Result br = b.getResult();
            if(r==null)
                r = br;
            else
198 199 200
            if(br==Result.NOT_BUILT)
                continue;   // UGLY: when computing combined status, ignore the modules that were not built
            else
201 202 203
            if(br!=null)
                r = r.combine(br);
        }
K
kohsuke 已提交
204 205 206 207

        return r;
    }

208 209 210 211 212
    /**
     * Returns the filtered changeset entries that match the given module.
     */
    /*package*/ List<ChangeLogSet.Entry> getChangeSetFor(final MavenModule mod) {
        return new ArrayList<ChangeLogSet.Entry>() {
C
Christoph Kutzinski 已提交
213
            private static final long serialVersionUID = 5572368347535713298L;
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
            {
                // modules that are under 'mod'. lazily computed
                List<MavenModule> subsidiaries = null;

                for (ChangeLogSet.Entry e : getChangeSet()) {
                    if(isDescendantOf(e, mod)) {
                        if(subsidiaries==null)
                            subsidiaries = mod.getSubsidiaries();

                        // make sure at least one change belongs to this module proper,
                        // and not its subsidiary module
                        if (notInSubsidiary(subsidiaries, e))
                            add(e);
                    }
                }
            }

            private boolean notInSubsidiary(List<MavenModule> subsidiaries, ChangeLogSet.Entry e) {
                for (String path : e.getAffectedPaths())
                    if(!belongsToSubsidiary(subsidiaries, path))
                        return true;
                return false;
            }

            private boolean belongsToSubsidiary(List<MavenModule> subsidiaries, String path) {
                for (MavenModule sub : subsidiaries)
240
                    if (FilenameUtils.separatorsToUnix(path).startsWith(normalizePath(sub.getRelativePath())))
241 242 243 244 245 246 247 248
                        return true;
                return false;
            }

            /**
             * Does this change happen somewhere in the given module or its descendants?
             */
            private boolean isDescendantOf(ChangeLogSet.Entry e, MavenModule mod) {
249
                for (String path : e.getAffectedPaths()) {
250
                    if (FilenameUtils.separatorsToUnix(path).startsWith(normalizePath(mod.getRelativePath())))
251
                        return true;
252
                }
253 254 255 256 257
                return false;
            }
        };
    }

258 259 260 261 262 263 264 265 266 267 268
    /**
     * Computes the module builds that correspond to this build.
     * <p>
     * A module may be built multiple times (by the user action),
     * so the value is a list.
     */
    public Map<MavenModule,List<MavenBuild>> getModuleBuilds() {
        Collection<MavenModule> mods = getParent().getModules();

        // identify the build number range. [start,end)
        MavenModuleSetBuild nb = getNextBuild();
269
        int end = nb!=null ? nb.getNumber() : Integer.MAX_VALUE;
270 271 272 273 274 275

        // preserve the order by using LinkedHashMap
        Map<MavenModule,List<MavenBuild>> r = new LinkedHashMap<MavenModule,List<MavenBuild>>(mods.size());

        for (MavenModule m : mods) {
            List<MavenBuild> builds = new ArrayList<MavenBuild>();
276
            MavenBuild b = m.getNearestBuild(number);
277 278 279 280 281 282 283 284 285
            while(b!=null && b.getNumber()<end) {
                builds.add(b);
                b = b.getNextBuild();
            }
            r.put(m,builds);
        }

        return r;
    }
286 287 288 289 290
    
    /**
     * Returns the estimated duration for this builds.
     * Takes only the modules into account which are actually being build in
     * case of incremental builds.
291 292 293
     * 
     * @return the estimated duration in milliseconds
     * @since 1.383
294 295 296 297 298 299 300 301 302 303 304
     */
    @Override
    public long getEstimatedDuration() {
        
        if (!project.isIncrementalBuild()) {
            return super.getEstimatedDuration();
        }

        long result = 0;
        
        Map<MavenModule, List<MavenBuild>> moduleBuilds = getModuleBuilds();
305 306 307
        
        boolean noModuleBuildsYet = true; 
        
308 309
        for (List<MavenBuild> builds : moduleBuilds.values()) {
            if (!builds.isEmpty()) {
310
                noModuleBuildsYet = false;
311
                MavenBuild build = builds.get(0);
K
kutzi 已提交
312 313
                if (build.getResult() != Result.NOT_BUILT && build.getEstimatedDuration() != -1) {
                    result += build.getEstimatedDuration();
314 315 316 317
                }
            }
        }
        
318 319 320 321 322 323
        if (noModuleBuildsYet) {
            // modules not determined, yet, i.e. POM not parsed.
            // Use best estimation we have:
            return super.getEstimatedDuration();
        }
        
324 325 326 327
        result += estimateModuleSetBuildDurationOverhead(3);
        
        return result != 0 ? result : -1;
    }
328

329 330
    /**
     * Estimates the duration overhead the {@link MavenModuleSetBuild} itself adds
331
     * to the sum of durations of the module builds.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
     */
    private long estimateModuleSetBuildDurationOverhead(int numberOfBuilds) {
        List<MavenModuleSetBuild> moduleSetBuilds = getPreviousBuildsOverThreshold(numberOfBuilds, Result.UNSTABLE);
        
        if (moduleSetBuilds.isEmpty()) {
            return 0;
        }
        
        long overhead = 0;
        for(MavenModuleSetBuild moduleSetBuild : moduleSetBuilds) {
            long sumOfModuleBuilds = 0;
            for (List<MavenBuild> builds : moduleSetBuild.getModuleBuilds().values()) {
                if (!builds.isEmpty()) {
                    MavenBuild moduleBuild = builds.get(0);
                    sumOfModuleBuilds += moduleBuild.getDuration();
                }
            }
            
            overhead += Math.max(0, moduleSetBuild.getDuration() - sumOfModuleBuilds);
        }
        
        return Math.round((double)overhead / moduleSetBuilds.size());
    }
355

356
    private static String normalizePath(String relPath) {
357 358 359
        relPath = StringUtils.trimToEmpty( relPath );
        if (StringUtils.isEmpty( relPath )) {
            LOGGER.config("No need to normalize an empty path.");
360
        } else {
361 362 363 364 365 366 367 368 369 370 371 372
            if(FilenameUtils.indexOfLastSeparator( relPath ) == -1) {
                LOGGER.config("No need to normalize "+relPath);
            } else {
                String tmp = FilenameUtils.normalize( relPath );
                if(tmp == null) {
                    LOGGER.config("Path " + relPath + " can not be normalized (parent dir is unknown). Keeping as is.");
                } else {
                    LOGGER.config("Normalized path " + relPath + " to "+tmp);
                    relPath = tmp;
                }
                relPath = FilenameUtils.separatorsToUnix( relPath );
            }
373
        }
374
        LOGGER.fine("Returning path " + relPath);
375 376 377
        return relPath;
    }

378 379 380 381
    /**
     * Gets the version of Maven used for build.
     *
     * @return
382
     *      null if this build is done by earlier version of Jenkins that didn't record this information
383 384
     *      (this means the build was done by Maven2.x)
     */
385
    @Exported
386 387 388 389 390
    public String getMavenVersionUsed() {
        return mavenVersionUsed;
    }

    public void setMavenVersionUsed( String mavenVersionUsed ) throws IOException {
391
        this.mavenVersionUsed = Util.intern(mavenVersionUsed);
392 393 394
        save();
    }

395 396 397 398 399 400 401 402 403
    @Override
    public synchronized void delete() throws IOException {
        super.delete();
        // Delete all contained module builds too
        for (List<MavenBuild> list : getModuleBuilds().values())
            for (MavenBuild build : list)
                build.delete();
    }

404
    @Override
405 406 407 408 409 410 411 412 413
    public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
        // map corresponding module build under this object
        if(token.indexOf('$')>0) {
            MavenModule m = getProject().getModule(token);
            if(m!=null) return m.getBuildByNumber(getNumber());
        }
        return super.getDynamic(token,req,rsp);
    }

414 415 416 417 418 419 420 421
    /**
     * Information about artifacts produced by Maven.
     */
    @Exported
    public MavenAggregatedArtifactRecord getMavenArtifacts() {
        return getAction(MavenAggregatedArtifactRecord.class);
    }

422 423
    /**
     * Computes the latest module builds that correspond to this build.
424
     * (when individual modules are built, a new ModuleSetBuild is not created,
425
     *  but rather the new module build falls under the previous ModuleSetBuild)
426 427 428 429 430 431 432 433 434 435 436 437 438
     */
    public Map<MavenModule,MavenBuild> getModuleLastBuilds() {
        Collection<MavenModule> mods = getParent().getModules();

        // identify the build number range. [start,end)
        MavenModuleSetBuild nb = getNextBuild();
        int end = nb!=null ? nb.getNumber() : Integer.MAX_VALUE;

        // preserve the order by using LinkedHashMap
        Map<MavenModule,MavenBuild> r = new LinkedHashMap<MavenModule,MavenBuild>(mods.size());

        for (MavenModule m : mods) {
            MavenBuild b = m.getNearestOldBuild(end - 1);
K
kohsuke 已提交
439
            if(b!=null && b.getNumber()>=getNumber())
440 441 442 443 444 445
                r.put(m,b);
        }

        return r;
    }

446 447 448 449 450 451
    public void registerAsProjectAction(MavenReporter reporter) {
        if(projectActionReporters==null)
            projectActionReporters = new ArrayList<MavenReporter>();
        projectActionReporters.add(reporter);
    }

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
    /**
     * Finds {@link Action}s from all the module builds that belong to this
     * {@link MavenModuleSetBuild}. One action per one {@link MavenModule},
     * and newer ones take precedence over older ones.
     */
    public <T extends Action> List<T> findModuleBuildActions(Class<T> action) {
        Collection<MavenModule> mods = getParent().getModules();
        List<T> r = new ArrayList<T>(mods.size());

        // identify the build number range. [start,end)
        MavenModuleSetBuild nb = getNextBuild();
        int end = nb!=null ? nb.getNumber()-1 : Integer.MAX_VALUE;

        for (MavenModule m : mods) {
            MavenBuild b = m.getNearestOldBuild(end);
            while(b!=null && b.getNumber()>=number) {
                T a = b.getAction(action);
                if(a!=null) {
                    r.add(a);
                    break;
                }
                b = b.getPreviousBuild();
            }
        }

        return r;
    }

480 481
    public void run() {
        run(new RunnerImpl());
482
        getProject().updateTransientActions();
483 484
    }

485
    @Override
C
Christoph Kutzinski 已提交
486
    public Fingerprint.RangeSet getDownstreamRelationship(@SuppressWarnings("rawtypes") AbstractProject that) {
487 488 489 490 491 492 493
        Fingerprint.RangeSet rs = super.getDownstreamRelationship(that);
        for(List<MavenBuild> builds : getModuleBuilds().values())
            for (MavenBuild b : builds)
                rs.add(b.getDownstreamRelationship(that));
        return rs;
    }

494 495 496 497
    /**
     * Called when a module build that corresponds to this module set build
     * has completed.
     */
498
    /*package*/ void notifyModuleBuild(MavenBuild newBuild) {
499 500 501 502 503 504 505 506 507
        try {
            // update module set build number
            getParent().updateNextBuildNumber();

            // update actions
            Map<MavenModule, List<MavenBuild>> moduleBuilds = getModuleBuilds();

            // actions need to be replaced atomically especially
            // given that two builds might complete simultaneously.
508 509 510
            // use a separate lock object since this synchronized block calls into plugins,
            // which in turn can access other MavenModuleSetBuild instances, which will result in a dead lock.
            synchronized(notifyModuleBuildLock) {
511 512 513 514 515 516 517 518 519 520 521
                boolean modified = false;

                List<Action> actions = getActions();
                Set<Class<? extends AggregatableAction>> individuals = new HashSet<Class<? extends AggregatableAction>>();
                for (Action a : actions) {
                    if(a instanceof MavenAggregatedReport) {
                        MavenAggregatedReport mar = (MavenAggregatedReport) a;
                        mar.update(moduleBuilds,newBuild);
                        individuals.add(mar.getIndividualActionType());
                        modified = true;
                    }
522
                }
523

524
                // see if the new build has any new aggregatable action that we haven't seen.
K
kohsuke 已提交
525 526 527 528 529
                for (AggregatableAction aa : newBuild.getActions(AggregatableAction.class)) {
                    if(individuals.add(aa.getClass())) {
                        // new AggregatableAction
                        MavenAggregatedReport mar = aa.createAggregatedAction(this, moduleBuilds);
                        mar.update(moduleBuilds,newBuild);
530
                        addAction(mar);
K
kohsuke 已提交
531
                        modified = true;
532 533 534
                    }
                }

535
                if(modified) {
536
                    save();
537 538
                    getProject().updateTransientActions();
                }
539
            }
540 541 542 543 544

            // symlink to this module build
            String moduleFsName = newBuild.getProject().getModuleName().toFileSystemName();
            Util.createSymlink(getRootDir(),
                    "../../modules/"+ moduleFsName +"/builds/"+newBuild.getId() /*ugly!*/,
K
kohsuke 已提交
545
                    moduleFsName, StreamTaskListener.NULL);
546 547
        } catch (IOException e) {
            LOGGER.log(Level.WARNING,"Failed to update "+this,e);
548 549
        } catch (InterruptedException e) {
            LOGGER.log(Level.WARNING,"Failed to update "+this,e);
550
        }
551 552
    }

553 554
    public String getMavenOpts(TaskListener listener, EnvVars envVars) {
        return envVars.expand(expandTokens(listener, project.getMavenOpts()));
555 556
    }

557 558 559 560 561
    /**
     * The sole job of the {@link MavenModuleSet} build is to update SCM
     * and triggers module builds.
     */
    private class RunnerImpl extends AbstractRunner {
562 563
        private Map<ModuleName,MavenBuild.ProxyImpl2> proxies;

564
        protected Result doRun(final BuildListener listener) throws Exception {
I
imod 已提交
565 566 567

        	Result r = null;
        	PrintStream logger = listener.getLogger();
568 569
            FilePath remoteSettings = null, remoteGlobalSettings = null;

570
            try {
I
imod 已提交
571
            	
K
kohsuke 已提交
572
                EnvVars envVars = getEnvironment(listener);
573 574
                MavenInstallation mvn = project.getMaven();
                if(mvn==null)
K
i18n  
Kohsuke Kawaguchi 已提交
575
                    throw new AbortException(Messages.MavenModuleSetBuild_NoMavenConfigured());
576

577
                mvn = mvn.forEnvironment(envVars).forNode(Computer.currentComputer().getNode(), listener);
578
                
S
Seiji Sogabe 已提交
579 580 581 582
                MavenInformation mavenInformation = getModuleRoot().act( new MavenVersionCallable( mvn.getHome() ));
                
                String mavenVersion = mavenInformation.getVersion();
                
583 584
                MavenBuildInformation mavenBuildInformation = new MavenBuildInformation( mavenVersion );
                
585
                setMavenVersionUsed( mavenVersion );
586 587

                LOGGER.fine(getFullDisplayName()+" is building with mavenVersion " + mavenVersion + " from file " + mavenInformation.getVersionResourcePath());
S
Seiji Sogabe 已提交
588

589
                if(!project.isAggregatorStyleBuild()) {
590
                    parsePoms(listener, logger, envVars, mvn, mavenVersion);
591
                    // start module builds
K
kohsuke 已提交
592
                    logger.println("Triggering "+project.getRootModule().getModuleName());
593
                    project.getRootModule().scheduleBuild(new UpstreamCause((Run<?,?>)MavenModuleSetBuild.this));
K
kohsuke 已提交
594
                } else {
595
                    // do builds here
596
                    try {
K
kohsuke 已提交
597
                        List<BuildWrapper> wrappers = new ArrayList<BuildWrapper>();
598
                        for (BuildWrapper w : project.getBuildWrappersList())
K
kohsuke 已提交
599 600 601 602 603 604
                            wrappers.add(w);
                        ParametersAction parameters = getAction(ParametersAction.class);
                        if (parameters != null)
                            parameters.createBuildWrappers(MavenModuleSetBuild.this,wrappers);

                        for( BuildWrapper w : wrappers) {
H
huybrechts 已提交
605
                            Environment e = w.setUp(MavenModuleSetBuild.this, launcher, listener);
K
kohsuke 已提交
606
                            if(e==null)
607
                                return (r = Result.FAILURE);
K
kohsuke 已提交
608
                            buildEnvironments.add(e);
609
                            e.buildEnvVars(envVars); // #3502: too late for getEnvironment to do this
K
kohsuke 已提交
610 611
                        }

612
                    	// run pre build steps
613 614 615
                    	if(!preBuild(listener,project.getPrebuilders())
                        || !preBuild(listener,project.getPostbuilders())
                        || !preBuild(listener,project.getPublishers())){
616 617 618
                    		r = FAILURE;
                            return r;
                    	}
619

620 621 622 623
                    	if(!build(listener,project.getPrebuilders().toList())){
                    		r = FAILURE;
                            return r;
            			}
624 625

                        String settingsConfigId = project.getSettingConfigId();
626
                        if (StringUtils.isNotBlank(settingsConfigId)) {
627 628
                            Config settingsConfig = SettingsProviderUtils.findConfig( settingsConfigId, MavenSettingsProvider.class, org.jenkinsci.lib.configprovider.maven.MavenSettingsProvider.class );
                            if (settingsConfig == null) {
629 630 631
                                logger.println(" your Apache Maven build is setup to use a config with id " + settingsConfigId
                                                   + " but cannot find the config");
                            } else {
632 633 634
                                logger.println("using settings config with name " + settingsConfig.name);
                                if (settingsConfig.content != null ) {
                                    remoteSettings = SettingsProviderUtils.copyConfigContentToFilePath( settingsConfig, getWorkspace() );
635
                                    project.setAlternateSettings( remoteSettings.getRemote() );
636 637 638 639
                                }
                            }
                        }

640
                        String globalSettingsConfigId = project.getGlobalSettingConfigId();
641
                        if (StringUtils.isNotBlank(globalSettingsConfigId)) {
642 643
                            Config settingsConfig = SettingsProviderUtils.findConfig( globalSettingsConfigId, GlobalMavenSettingsProvider.class, org.jenkinsci.lib.configprovider.maven.GlobalMavenSettingsProvider.class );
                            if (settingsConfig == null) {
O
Olivier Lamy 已提交
644
                                logger.println(" your Apache Maven build is setup to use a global settings config with id " + globalSettingsConfigId
645 646
                                                   + " but cannot find the config");
                            } else {
647 648 649
                                logger.println("using global settings config with name " + settingsConfig.name);
                                if (settingsConfig.content != null ) {
                                    remoteGlobalSettings = SettingsProviderUtils.copyConfigContentToFilePath( settingsConfig, getWorkspace() );
650 651 652
                                    project.globalSettingConfigPath = remoteGlobalSettings.getRemote();
                                }
                            }
653 654 655 656
                        } else {
                        	// make sure the transient field is clean
                        	project.globalSettingConfigPath = null;
                        }
657

658
                        parsePoms(listener, logger, envVars, mvn, mavenVersion); // #5428 : do pre-build *before* parsing pom
K
kohsuke 已提交
659 660
                        SplittableBuildListener slistener = new SplittableBuildListener(listener);
                        proxies = new HashMap<ModuleName, ProxyImpl2>();
661 662 663 664 665
                        List<ModuleName> changedModules = new ArrayList<ModuleName>();
                        
                        if (project.isIncrementalBuild() && !getChangeSet().isEmptySet()) {
                            changedModules.addAll(getUnbuildModulesSinceLastSuccessfulBuild());
                        }
666 667

                        for (MavenModule m : project.sortedActiveModules) {
668
                            MavenBuild mb = m.newBuild();
669
                            // JENKINS-8418
670
                            mb.setBuiltOnStr( getBuiltOnStr() );
671 672 673 674
                            // Check if incrementalBuild is selected and that there are changes -
                            // we act as if incrementalBuild is not set if there are no changes.
                            if (!MavenModuleSetBuild.this.getChangeSet().isEmptySet()
                                && project.isIncrementalBuild()) {
S
Seiji Sogabe 已提交
675 676 677
                                //If there are changes for this module, add it.
                                // Also add it if we've never seen this module before,
                                // or if the previous build of this module failed or was unstable.
678 679 680
                                if ((mb.getPreviousBuiltBuild() == null) ||
                                    (!getChangeSetFor(m).isEmpty()) 
                                    || (mb.getPreviousBuiltBuild().getResult().isWorseThan(Result.SUCCESS))) {
681
                                    changedModules.add(m.getModuleName());
682 683
                                }
                            }
684 685 686

                            mb.setWorkspace(getModuleRoot().child(m.getRelativePath()));
                            proxies.put(m.getModuleName(), mb.new ProxyImpl2(MavenModuleSetBuild.this,slistener));
687
                        }
K
kohsuke 已提交
688 689 690 691 692 693 694

                        // run the complete build here

                        // figure out the root POM location.
                        // choice of module root ('ws' in this method) is somewhat arbitrary
                        // when multiple CVS/SVN modules are checked out, so also check
                        // the path against the workspace root if that seems like what the user meant (see issue #1293)
695
                        String rootPOM = project.getRootPOM();
K
kohsuke 已提交
696 697
                        FilePath pom = getModuleRoot().child(rootPOM);
                        FilePath parentLoc = getWorkspace().child(rootPOM);
K
kohsuke 已提交
698 699 700
                        if(!pom.exists() && parentLoc.exists())
                            pom = parentLoc;

S
Seiji Sogabe 已提交
701
                        
702
                        final ProcessCache.MavenProcess process;
S
Seiji Sogabe 已提交
703
                        
704
                        boolean maven3orLater = mavenBuildInformation.isMaven3OrLater(); 
S
Seiji Sogabe 已提交
705 706
                        if ( maven3orLater )
                        {
K
i18n  
Kohsuke Kawaguchi 已提交
707
                            LOGGER.fine( "using maven 3 " + mavenVersion );
S
Seiji Sogabe 已提交
708 709
                            process =
                                MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
710
                                                                  new Maven3ProcessFactory( project, launcher, envVars, getMavenOpts(listener, envVars),
S
Seiji Sogabe 已提交
711 712 713 714
                                                                                            pom.getParent() ) );
                        }
                        else
                        {
K
i18n  
Kohsuke Kawaguchi 已提交
715
                            LOGGER.fine( "using maven 2 " + mavenVersion );
S
Seiji Sogabe 已提交
716 717
                            process =
                                MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
718
                                                                  new MavenProcessFactory( project, launcher, envVars,getMavenOpts(listener, envVars),
S
Seiji Sogabe 已提交
719 720
                                                                                           pom.getParent() ) );
                        }
K
kohsuke 已提交
721
                        ArgumentListBuilder margs = new ArgumentListBuilder().add("-B").add("-f", pom.getRemote());
722
                        if(project.usesPrivateRepository())
K
kohsuke 已提交
723
                            margs.add("-Dmaven.repo.local="+getWorkspace().child(".repository"));
724

725
                        if (project.globalSettingConfigPath != null)
726
                            margs.add("-gs" , project.globalSettingConfigPath);
727

728

729 730 731 732 733 734 735
                        
                        // If incrementalBuild is set
                        // and the previous build didn't specify that we need a full build
                        // and we're on Maven 2.1 or later
                        // and there's at least one module listed in changedModules,
                        // then do the Maven incremental build commands.
                        // If there are no changed modules, we're building everything anyway.
S
Seiji Sogabe 已提交
736
                        boolean maven2_1orLater = new ComparableVersion (mavenVersion).compareTo( new ComparableVersion ("2.1") ) >= 0;
737 738 739
                        boolean needsFullBuild = getPreviousCompletedBuild() != null &&
                            getPreviousCompletedBuild().getAction(NeedsFullBuildAction.class) != null;
                        if (project.isIncrementalBuild() && !needsFullBuild && maven2_1orLater && !changedModules.isEmpty()) {
740 741 742
                            margs.add("-amd");
                            margs.add("-pl", Util.join(changedModules, ","));
                        }
743 744

                        if (project.getAlternateSettings() != null) {
745 746 747 748 749 750 751 752 753 754
                            if (IOUtils.isAbsolute(project.getAlternateSettings())) {
                                margs.add("-s").add(project.getAlternateSettings());
                            } else {
                                FilePath mrSettings = getModuleRoot().child(project.getAlternateSettings());
                                FilePath wsSettings = getWorkspace().child(project.getAlternateSettings());
                                if (!wsSettings.exists() && mrSettings.exists())
                                    wsSettings = mrSettings;
                                
                                margs.add("-s").add(wsSettings.getRemote());
                            }
755 756
                        }

757 758 759
                        
                        final List<MavenArgumentInterceptorAction> argInterceptors = this.getBuild().getActions(MavenArgumentInterceptorAction.class);
                        
I
imod 已提交
760
						// find the correct maven goals and options, there might by an action overruling the defaults
761 762
                        String goals = project.getGoals(); // default
                        for (MavenArgumentInterceptorAction mavenArgInterceptor : argInterceptors) {
I
imod 已提交
763
                        	final String goalsAndOptions = mavenArgInterceptor.getGoalsAndOptions((MavenModuleSetBuild)this.getBuild());
I
imod 已提交
764 765
							if(StringUtils.isNotBlank(goalsAndOptions)){
                        		goals = goalsAndOptions;
766 767 768 769
                                // only one interceptor is allowed to overwrite the whole "goals and options" string
                        		break;
                        	}
						}
I
imod 已提交
770 771 772 773 774
						margs.addTokenized(envVars.expand(goals));

						// enable the interceptors to change the whole command argument list
						// all available interceptors are allowed to modify the argument list
						for (MavenArgumentInterceptorAction mavenArgInterceptor : argInterceptors) {
I
imod 已提交
775
							final ArgumentListBuilder newMargs = mavenArgInterceptor.intercept(margs, (MavenModuleSetBuild)this.getBuild());
I
imod 已提交
776 777 778 779 780
							if (newMargs != null) {
								margs = newMargs;
							}
						}                        
                        
781 782 783
                        final AbstractMavenBuilder builder;
                        if (maven3orLater) {
                            builder =
C
Christoph Kutzinski 已提交
784
                                new Maven3Builder( slistener, proxies, project.sortedActiveModules, margs.toList(), envVars, mavenBuildInformation );
S
Seiji Sogabe 已提交
785
                        } else {
786
                            builder = 
787
                                new Maven2Builder(slistener, proxies, project.sortedActiveModules, margs.toList(), envVars, mavenBuildInformation);
K
kohsuke 已提交
788
                        }
789 790 791 792 793 794 795 796 797 798 799 800 801
                        
                        MavenProbeAction mpa=null;
                        try {
                            mpa = new MavenProbeAction(project,process.channel);
                            addAction(mpa);
                            r = process.call(builder);
                            return r;
                        } finally {
                            builder.end(launcher);
                            getActions().remove(mpa);
                            process.discard();
                        }                            
                        
802
                    } catch (InterruptedException e) {
803
                        r = Executor.currentExecutor().abortResult();
804
                        throw e;
805
                    } finally {
I
imod 已提交
806
            			// only run post build steps if requested...
807
                        if (r==null || r.isBetterOrEqualTo(project.getRunPostStepsIfResult())) {
808 809
                            if(!build(listener,project.getPostbuilders().toList())){
                                r = FAILURE;
I
imod 已提交
810 811 812
            				}
            			}
            			
813 814 815
                        if (r != null) {
                            setResult(r);
                        }
816

K
kohsuke 已提交
817
                        // tear down in reverse order
818 819 820 821 822 823 824 825
                        boolean failed=false;
                        for( int i=buildEnvironments.size()-1; i>=0; i-- ) {
                            if (!buildEnvironments.get(i).tearDown(MavenModuleSetBuild.this,listener)) {
                                failed=true;
                            }                    
                        }
                        // WARNING The return in the finally clause will trump any return before
                        if (failed) return Result.FAILURE;
826
                    }
K
kohsuke 已提交
827
                }
828
                
I
imod 已提交
829
                
830
                return r;
831
            } catch (AbortException e) {
832 833
                if(e.getMessage()!=null)
                    listener.error(e.getMessage());
834
                return Result.FAILURE;
835
            } catch (InterruptedIOException e) {
836
                e.printStackTrace(listener.error("Aborted Maven execution for InterruptedIOException"));
837
                return Executor.currentExecutor().abortResult();
838 839 840
            } catch (IOException e) {
                e.printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
                return Result.FAILURE;
841 842
            } catch (RunnerAbortedException e) {
                return Result.FAILURE;
843 844
            } catch (RuntimeException e) {
                // bug in the code.
845
                e.printStackTrace(listener.error("Processing failed due to a bug in the code. Please report this to jenkinsci-users@googlegroups.com"));
K
kohsuke 已提交
846 847 848
                logger.println("project="+project);
                logger.println("project.getModules()="+project.getModules());
                logger.println("project.getRootModule()="+project.getRootModule());
849
                throw e;
850
            } finally {
851
                if (StringUtils.isNotBlank(project.getSettingConfigId())) {
852 853 854 855 856 857 858 859
                    // restore to null if as was modified
                    project.setAlternateSettings( null );
                    project.save();
                }
                // delete tmp files used for MavenSettingsProvider
                if (remoteSettings != null) {
                    remoteSettings.delete();
                }
C
Christoph Kutzinski 已提交
860
                if (remoteGlobalSettings != null ) {
861 862
                    remoteGlobalSettings.delete();
                }
863 864 865
            }
        }

I
imod 已提交
866 867 868 869 870 871 872 873 874 875 876
        
        private boolean build(BuildListener listener, Collection<hudson.tasks.Builder> steps) throws IOException, InterruptedException {
            for( BuildStep bs : steps ){
                if(!perform(bs,listener)) {
                	LOGGER.fine(MessageFormat.format("{1} failed", bs));
                    return false;
                }
            }
            return true;
        }

877 878 879 880 881 882 883 884 885 886
        /**
         * Returns the modules which have not been build since the last successful aggregator build
         * though they should be because they had SCM changes.
         * This can happen when the aggregator build fails before it reaches the module.
         * 
         * See JENKINS-5764
         */
        private Collection<ModuleName> getUnbuildModulesSinceLastSuccessfulBuild() {
            Collection<ModuleName> unbuiltModules = new ArrayList<ModuleName>();
            MavenModuleSetBuild previousSuccessfulBuild = getPreviousSuccessfulBuild();
887 888 889 890 891
            if (previousSuccessfulBuild == null) {
                // no successful build, yet. Just take the 1st build
                previousSuccessfulBuild = getParent().getFirstBuild();
            }
            
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
            if (previousSuccessfulBuild != null) {
                MavenModuleSetBuild previousBuild = previousSuccessfulBuild;
                do {
                    UnbuiltModuleAction unbuiltModuleAction = previousBuild.getAction(UnbuiltModuleAction.class);
                    if (unbuiltModuleAction != null) {
                        for (ModuleName name : unbuiltModuleAction.getUnbuildModules()) {
                            unbuiltModules.add(name);
                        }
                    }
                    
                    previousBuild = previousBuild.getNextBuild();
                } while (previousBuild != null && previousBuild != MavenModuleSetBuild.this);
            }
            return unbuiltModules;
        }

908
        private void parsePoms(BuildListener listener, PrintStream logger, EnvVars envVars, MavenInstallation mvn, String mavenVersion) throws IOException, InterruptedException {
909
            logger.println("Parsing POMs");
910

911 912
            List<PomInfo> poms;
            try {
913
                poms = getModuleRoot().act(new PomParser(listener, mvn, project, mavenVersion, envVars, getWorkspace()));
914
            } catch (IOException e) {
915 916 917 918 919 920 921
                if (project.isIncrementalBuild()) {
                    // If POM parsing failed we should do a full build next time.
                    // Otherwise only the modules which have a SCM change for the next build might
                    // be build next time.
                    getActions().add(new NeedsFullBuildAction());
                }
                
922 923 924
                if (e.getCause() instanceof AbortException)
                    throw (AbortException) e.getCause();
                throw e;
925 926
            } catch (MavenExecutionException e) {
                // Maven failed to parse POM
K
i18n  
kohsuke 已提交
927
                e.getCause().printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
928 929 930
                if (project.isIncrementalBuild()) {
                    getActions().add(new NeedsFullBuildAction());
                }
931 932
                throw new AbortException();
            }
933 934
            
            boolean needsDependencyGraphRecalculation = false;
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950

            // update the module list
            Map<ModuleName,MavenModule> modules = project.modules;
            synchronized(modules) {
                Map<ModuleName,MavenModule> old = new HashMap<ModuleName, MavenModule>(modules);
                List<MavenModule> sortedModules = new ArrayList<MavenModule>();

                modules.clear();
                if(debug)
                    logger.println("Root POM is "+poms.get(0).name);
                project.reconfigure(poms.get(0));
                for (PomInfo pom : poms) {
                    MavenModule mm = old.get(pom.name);
                    if(mm!=null) {// found an existing matching module
                        if(debug)
                            logger.println("Reconfiguring "+mm);
951 952 953
                        if (!mm.isSameModule(pom)) {
                            needsDependencyGraphRecalculation = true;
                        }
954 955 956
                        mm.reconfigure(pom);
                        modules.put(pom.name,mm);
                    } else {// this looks like a new module
K
i18n  
kohsuke 已提交
957
                        logger.println(Messages.MavenModuleSetBuild_DiscoveredModule(pom.name,pom.displayName));
958 959
                        mm = new MavenModule(project,pom,getNumber());
                        modules.put(mm.getModuleName(),mm);
960
                        needsDependencyGraphRecalculation = true;
961 962 963 964 965 966 967 968 969 970 971 972
                    }
                    sortedModules.add(mm);
                    mm.save();
                }
                // at this point the list contains all the live modules
                project.sortedActiveModules = sortedModules;

                // remaining modules are no longer active.
                old.keySet().removeAll(modules.keySet());
                for (MavenModule om : old.values()) {
                    if(debug)
                        logger.println("Disabling "+om);
973
                    om.makeDisabled(true);
974
                    needsDependencyGraphRecalculation = true;
975 976 977 978 979
                }
                modules.putAll(old);
            }

            // we might have added new modules
980 981 982 983
            if (needsDependencyGraphRecalculation) {
                logger.println("Modules changed, recalculating dependency graph");
                Jenkins.getInstance().rebuildDependencyGraph();
            }
984 985 986 987 988 989

            // module builds must start with this build's number
            for (MavenModule m : modules.values())
                m.updateNextBuildNumber(getNumber());
        }

990
        protected void post2(BuildListener listener) throws Exception {
991 992 993 994
            // asynchronous executions from the build might have left some unsaved state,
            // so just to be safe, save them all.
            for (MavenBuild b : getModuleLastBuilds().values())
                b.save();
995

996
            // at this point the result is all set, so ignore the return value
997
            if (!performAllBuildSteps(listener, project.getPublishers(), true))
998
                setResult(FAILURE);
999
            if (!performAllBuildSteps(listener, project.getProperties(), true))
1000
                setResult(FAILURE);
1001 1002 1003 1004 1005 1006 1007 1008

            // aggregate all module fingerprints to us,
            // so that dependencies between module builds can be understood as
            // dependencies between module set builds.
            // TODO: we really want to implement this as a publisher,
            // but we don't want to ask for a user configuration, nor should it
            // show up in the persisted record.
            MavenFingerprinter.aggregate(MavenModuleSetBuild.this);
1009 1010 1011 1012
        }

        @Override
        public void cleanUp(BuildListener listener) throws Exception {
1013 1014 1015 1016 1017 1018 1019 1020
            MavenMailer mailer = project.getReporters().get(MavenMailer.class);
            if (mailer != null) {
                new MailSender(mailer.recipients,
                        mailer.dontNotifyEveryUnstableBuild,
                        mailer.sendToIndividuals).execute(MavenModuleSetBuild.this, listener);
            }

            // too late to set the build result at this point. so ignore failures.
1021 1022
            performAllBuildSteps(listener, project.getPublishers(), false);
            performAllBuildSteps(listener, project.getProperties(), false);
1023
            super.cleanUp(listener);
1024
        }
1025

1026
    }
1027

1028 1029 1030 1031 1032 1033 1034 1035
    /**
     * Used to tunnel exception from Maven through remoting.
     */
    private static final class MavenExecutionException extends RuntimeException {
        private MavenExecutionException(Exception cause) {
            super(cause);
        }

1036
        @Override
1037 1038 1039 1040 1041 1042 1043
        public Exception getCause() {
            return (Exception)super.getCause();
        }

        private static final long serialVersionUID = 1L;
    }

1044 1045 1046 1047 1048
    /**
     * Executed on the slave to parse POM and extract information into {@link PomInfo},
     * which will be then brought back to the master.
     */
    private static final class PomParser implements FileCallable<List<PomInfo>> {
1049
        private final BuildListener listener;
1050
        private final String rootPOM;
K
kohsuke 已提交
1051 1052 1053 1054
        /**
         * Capture the value of the static field so that the debug flag
         * takes an effect even when {@link PomParser} runs in a slave.
         */
1055
        private final boolean verbose = debug;
1056
        private final MavenInstallation mavenHome;
1057
        private final String profiles;
K
kohsuke 已提交
1058
        private final Properties properties;
1059
        private final String privateRepository;
1060
        private final String alternateSettings;
1061
        private final String globalSetings;
K
kohsuke 已提交
1062
        private final boolean nonRecursive;
1063 1064
        // We're called against the module root, not the workspace, which can cause a lot of confusion.
        private final String workspaceProper;
1065
        private final String mavenVersion;
1066
        
1067 1068
        private final String moduleRootPath;
        
1069 1070 1071 1072
        private boolean resolveDependencies = false;
  
        private boolean processPlugins = false;
        
1073 1074
        private int mavenValidationLevel = -1;
        
1075 1076
        String rootPOMRelPrefix;
        
1077
        public PomParser(BuildListener listener, MavenInstallation mavenHome, MavenModuleSet project, String mavenVersion, EnvVars envVars, FilePath workspace) {
K
kohsuke 已提交
1078
            // project cannot be shipped to the remote JVM, so all the relevant properties need to be captured now.
1079
            this.listener = listener;
1080
            this.mavenHome = mavenHome;
K
kohsuke 已提交
1081 1082 1083
            this.rootPOM = project.getRootPOM();
            this.profiles = project.getProfiles();
            this.properties = project.getMavenProperties();
1084 1085 1086 1087 1088 1089 1090 1091 1092
            ParametersDefinitionProperty parametersDefinitionProperty = project.getProperty( ParametersDefinitionProperty.class );
            if (parametersDefinitionProperty != null && parametersDefinitionProperty.getParameterDefinitions() != null) {
                for (ParameterDefinition parameterDefinition : parametersDefinitionProperty.getParameterDefinitions()) {
                    // those must used as env var
                    if (parameterDefinition instanceof StringParameterDefinition) {
                        this.properties.put( "env." + parameterDefinition.getName(), ((StringParameterDefinition)parameterDefinition).getDefaultValue() );
                    }
                }
            }
1093 1094 1095 1096 1097 1098 1099 1100
            if (envVars != null && !envVars.isEmpty()) {
                for (Entry<String,String> entry : envVars.entrySet()) {
                    if (entry.getKey() != null && entry.getValue() != null) {
                        this.properties.put( "env." + entry.getKey(), entry.getValue() );
                    }
                }
            }
            
K
kohsuke 已提交
1101
            this.nonRecursive = project.isNonRecursive();
1102 1103 1104

            this.workspaceProper = workspace.getRemote();
            LOGGER.fine("Workspace is " + workspaceProper);
K
kohsuke 已提交
1105
            if (project.usesPrivateRepository()) {
1106
                this.privateRepository = workspace.child(".repository").getRemote();
K
kohsuke 已提交
1107
            } else {
1108 1109
                this.privateRepository = null;
            }
1110 1111
            // TODO maybe in goals with -s,--settings
            // or -Dmaven.repo.local
1112
            this.alternateSettings = project.getAlternateSettings();
1113
            this.mavenVersion = mavenVersion;
1114 1115
            this.resolveDependencies = project.isResolveDependencies();
            this.processPlugins = project.isProcessPlugins();
1116 1117
            
            this.moduleRootPath = 
1118
                project.getScm().getModuleRoot( workspace, project.getLastBuild() ).getRemote();
1119
            
1120
            this.mavenValidationLevel = project.getMavenValidationLevel();
1121
            this.globalSetings = project.globalSettingConfigPath;
1122 1123
        }

O
Olivier Lamy 已提交
1124
        
1125
        public List<PomInfo> invoke(File ws, VirtualChannel channel) throws IOException {
K
km 已提交
1126
            File pom;
1127
            
K
kohsuke 已提交
1128 1129
            PrintStream logger = listener.getLogger();

K
km 已提交
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
            if (IOUtils.isAbsolute(rootPOM)) {
                pom = new File(rootPOM);
            } else {
                // choice of module root ('ws' in this method) is somewhat arbitrary
                // when multiple CVS/SVN modules are checked out, so also check
                // the path against the workspace root if that seems like what the user meant (see issue #1293)
                pom = new File(ws, rootPOM);
                File parentLoc = new File(ws.getParentFile(),rootPOM);
                if(!pom.exists() && parentLoc.exists())
                    pom = parentLoc;
            }
1141

1142
            if(!pom.exists())
1143
                throw new AbortException(Messages.MavenModuleSetBuild_NoSuchPOMFile(pom));
1144

1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
            if (rootPOM.startsWith("../") || rootPOM.startsWith("..\\")) {
                File wsp = new File(workspaceProper);
                               
                if (!ws.equals(wsp)) {
                    rootPOMRelPrefix = ws.getCanonicalPath().substring(wsp.getCanonicalPath().length()+1)+"/";
                } else {
                    rootPOMRelPrefix = wsp.getName() + "/";
                }
            } else {
                rootPOMRelPrefix = "";
            }            
            
1157
            if(verbose)
1158 1159 1160 1161
                logger.println("Parsing "
			       + (nonRecursive ? "non-recursively " : "recursively ")
			       + pom);
	    
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
            File settingsLoc;

            if (alternateSettings == null) {
                settingsLoc = null;
            } else if (IOUtils.isAbsolute(alternateSettings)) {
                settingsLoc = new File(alternateSettings);
            } else {
                // Check for settings.xml first in the workspace proper, and then in the current directory,
                // which is getModuleRoot().
                // This is backwards from the order the root POM logic uses, but it's to be consistent with the Maven execution logic.
                settingsLoc = new File(workspaceProper, alternateSettings);
                File mrSettingsLoc = new File(workspaceProper, alternateSettings);
                if (!settingsLoc.exists() && mrSettingsLoc.exists())
                    settingsLoc = mrSettingsLoc;
            }
S
Seiji Sogabe 已提交
1177 1178
            if (debug)
            {
K
i18n  
Kohsuke Kawaguchi 已提交
1179
                logger.println(Messages.MavenModuleSetBuild_SettinsgXmlAndPrivateRepository(settingsLoc,privateRepository));
S
Seiji Sogabe 已提交
1180
            }
1181 1182 1183 1184
            if ((settingsLoc != null) && (!settingsLoc.exists())) {
                throw new AbortException(Messages.MavenModuleSetBuild_NoSuchAlternateSettings(settingsLoc.getAbsolutePath()));
            }

1185
            try {
S
Seiji Sogabe 已提交
1186 1187 1188 1189
                MavenEmbedderRequest mavenEmbedderRequest = new MavenEmbedderRequest( listener, mavenHome.getHomeDir(),
                                                                                      profiles, properties,
                                                                                      privateRepository, settingsLoc );
                mavenEmbedderRequest.setTransferListener( new SimpleTransferListener(listener) );
1190
                
1191 1192
                mavenEmbedderRequest.setProcessPlugins( this.processPlugins );
                mavenEmbedderRequest.setResolveDependencies( this.resolveDependencies );
1193 1194 1195
                if (globalSetings != null) {
                    mavenEmbedderRequest.setGlobalSettings( new File(globalSetings) );
                }
1196 1197 1198
                
                // FIXME handle 3.1 level when version will be here : no rush :-)
                // or made something configurable tru the ui ?
1199
                ReactorReader reactorReader = null;
1200
                boolean maven3OrLater = MavenUtil.maven3orLater(mavenVersion);
1201
                if (maven3OrLater) {
1202
                    mavenEmbedderRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
1203 1204 1205 1206
                } else {
                    reactorReader = new ReactorReader( new HashMap<String, MavenProject>(), new File(workspaceProper) );
                    mavenEmbedderRequest.setWorkspaceReader( reactorReader );
                }
1207 1208 1209 1210 1211 1212
                
                
                if (this.mavenValidationLevel >= 0) {
                    mavenEmbedderRequest.setValidationLevel( this.mavenValidationLevel );
                }
                
O
Olivier Lamy 已提交
1213
                //mavenEmbedderRequest.setClassLoader( MavenEmbedderUtils.buildClassRealm( mavenHome.getHomeDir(), null, null ) );
1214
                
S
Seiji Sogabe 已提交
1215 1216
                MavenEmbedder embedder = MavenUtil.createEmbedder( mavenEmbedderRequest );
                
1217 1218 1219 1220
                MavenProject rootProject = null;
                
                List<MavenProject> mps = new ArrayList<MavenProject>(0);
                if (maven3OrLater) {
1221
                    mps = embedder.readProjects( pom,!this.nonRecursive );
1222 1223

                } else {
K
Kohsuke Kawaguchi 已提交
1224
                    // http://issues.jenkins-ci.org/browse/HUDSON-8390
1225 1226 1227 1228 1229 1230
                    // we cannot read maven projects in one time for backward compatibility
                    // but we have to use a ReactorReader to get some pom with bad inheritence configured
                    MavenProject mavenProject = embedder.readProject( pom );
                    rootProject = mavenProject;
                    mps.add( mavenProject );
                    reactorReader.addProject( mavenProject );
1231 1232 1233
                    if (!this.nonRecursive) {
                        readChilds( mavenProject, embedder, mps, reactorReader );
                    }
1234
                }
S
Seiji Sogabe 已提交
1235 1236
                Map<String,MavenProject> canonicalPaths = new HashMap<String, MavenProject>( mps.size() );
                for(MavenProject mp : mps) {
1237 1238 1239
                    // Projects are indexed by POM path and not module path because
                    // Maven allows to have several POMs with different names in the same directory
                    canonicalPaths.put( mp.getFile().getCanonicalPath(), mp );
1240
                }                
S
Seiji Sogabe 已提交
1241
                //MavenUtil.resolveModules(embedder,mp,getRootPath(rootPOMRelPrefix),relPath,listener,nonRecursive);
1242

1243
                if(verbose) {
S
Seiji Sogabe 已提交
1244 1245
                    for (Entry<String,MavenProject> e : canonicalPaths.entrySet())
                        logger.printf("Discovered %s at %s\n",e.getValue().getId(),e.getKey());
K
kohsuke 已提交
1246 1247
                }

S
Seiji Sogabe 已提交
1248
                Set<PomInfo> infos = new LinkedHashSet<PomInfo>();
1249 1250 1251 1252 1253 1254 1255
                
                if (maven3OrLater) {
                    for (MavenProject mp : mps) {
                        if (mp.isExecutionRoot()) {
                            rootProject = mp;
                            continue;
                        }
S
Seiji Sogabe 已提交
1256 1257 1258 1259 1260 1261 1262
                    }
                }
                // if rootProject is null but no reason :-) use the first one
                if (rootProject == null) {
                    rootProject = mps.get( 0 );
                }
                toPomInfo(rootProject,null,canonicalPaths,infos);
K
kohsuke 已提交
1263 1264 1265

                for (PomInfo pi : infos)
                    pi.cutCycle();
K
kohsuke 已提交
1266

S
Seiji Sogabe 已提交
1267
                return new ArrayList<PomInfo>(infos);
1268
            } catch (MavenEmbedderException e) {
1269
                throw new MavenExecutionException(e);
1270
            } catch (ProjectBuildingException e) {
1271
                throw new MavenExecutionException(e);
1272 1273 1274
            }
        }

1275 1276 1277
        /**
         * @see PomInfo#relativePath to understand relPath calculation
         */
S
Seiji Sogabe 已提交
1278
        private void toPomInfo(MavenProject mp, PomInfo parent, Map<String,MavenProject> abslPath, Set<PomInfo> infos) throws IOException {
1279
            
1280
            String relPath = PathTool.getRelativeFilePath( this.moduleRootPath, mp.getBasedir().getPath() );
1281 1282
            relPath = normalizePath(relPath);

1283 1284
            if (parent == null ) {
                relPath = getRootPath(rootPOMRelPrefix);
S
Seiji Sogabe 已提交
1285 1286
            }
            
1287 1288
            relPath = StringUtils.removeStart( relPath, "/" );
            
S
Seiji Sogabe 已提交
1289
            PomInfo pi = new PomInfo(mp, parent, relPath);
K
kohsuke 已提交
1290
            infos.add(pi);
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303
            if(!this.nonRecursive) {
                for (String modulePath : mp.getModules())
                {
                    if (StringUtils.isBlank( modulePath )) {
                        continue;
                    }
                    File path = new File(mp.getBasedir(), modulePath);
                    // HUDSON-8391 : Modules are indexed by POM path thus
                    // by default we have to add the default pom.xml file
                    if(path.isDirectory())
                      path = new File(mp.getBasedir(), modulePath+"/pom.xml");
                    MavenProject child = abslPath.get( path.getCanonicalPath());
                    if (child == null) {
K
i18n  
Kohsuke Kawaguchi 已提交
1304
                        listener.getLogger().printf(Messages.MavenModuleSetBuild_FoundModuleWithoutProject(modulePath));
1305 1306 1307
                        continue;
                    }
                    toPomInfo(child,pi,abslPath,infos);
S
Seiji Sogabe 已提交
1308 1309
                }
            }
1310
        }
1311 1312 1313 1314 1315 1316 1317 1318
        
        private void readChilds(MavenProject mp, MavenEmbedder mavenEmbedder, List<MavenProject> mavenProjects, ReactorReader reactorReader) 
            throws ProjectBuildingException, MavenEmbedderException {
            if (mp.getModules() == null || mp.getModules().isEmpty()) {
                return;
            }
            for (String module : mp.getModules()) {
                if ( Util.fixEmptyAndTrim( module ) != null ) {
1319 1320 1321
                    File pomFile = new File(mp.getFile().getParent(), module);
                    MavenProject mavenProject2 = null;
                    // take care of HUDSON-8445
1322
                    if (pomFile.isFile())
1323 1324 1325
                        mavenProject2 = mavenEmbedder.readProject( pomFile );
                    else
                        mavenProject2 = mavenEmbedder.readProject( new File(mp.getFile().getParent(), module + "/pom.xml") );
1326 1327 1328 1329 1330 1331
                    mavenProjects.add( mavenProject2 );
                    reactorReader.addProject( mavenProject2 );
                    readChilds( mavenProject2, mavenEmbedder, mavenProjects, reactorReader );
                }
            }
        }
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
        
        /**
         * Computes the path of {@link #rootPOM}.
         *
         * Returns "abc" if rootPOM="abc/pom.xml"
         * If rootPOM="pom.xml", this method returns "".
         */
        private String getRootPath(String prefix) {
            int idx = Math.max(rootPOM.lastIndexOf('/'), rootPOM.lastIndexOf('\\'));
            if(idx==-1) return "";
            return prefix + rootPOM.substring(0,idx);
        }
        
1345 1346 1347

        private static final long serialVersionUID = 1L;
    }
1348
        
1349
    private static final Logger LOGGER = Logger.getLogger(MavenModuleSetBuild.class.getName());
K
kohsuke 已提交
1350 1351

    /**
1352
     * Extra verbose debug switch.
K
kohsuke 已提交
1353
     */
S
Seiji Sogabe 已提交
1354
    public static boolean debug = Boolean.getBoolean( "hudson.maven.debug" );
1355

1356 1357 1358 1359
    @Override
    public MavenModuleSet getParent() {// don't know why, but javac wants this
        return super.getParent();
    }
S
Seiji Sogabe 已提交
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381
    
    /**
     * will log in the {@link TaskListener} when transferFailed and transferSucceeded
     * @author Olivier Lamy
     * @since 
     */
    public static class SimpleTransferListener implements TransferListener
    {
        private TaskListener taskListener;
        public SimpleTransferListener(TaskListener taskListener)
        {
            this.taskListener = taskListener;
        }

        public void transferCorrupted( TransferEvent arg0 )
            throws TransferCancelledException
        {
            // no op
        }

        public void transferFailed( TransferEvent transferEvent )
        {
K
i18n  
Kohsuke Kawaguchi 已提交
1382
            taskListener.getLogger().println(Messages.MavenModuleSetBuild_FailedToTransfer(transferEvent.getException().getMessage()));
S
Seiji Sogabe 已提交
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404
        }

        public void transferInitiated( TransferEvent arg0 )
            throws TransferCancelledException
        {
            // no op
        }

        public void transferProgressed( TransferEvent arg0 )
            throws TransferCancelledException
        {
            // no op            
        }

        public void transferStarted( TransferEvent arg0 )
            throws TransferCancelledException
        {
            // no op
        }

        public void transferSucceeded( TransferEvent transferEvent )
        {
K
i18n  
Kohsuke Kawaguchi 已提交
1405 1406 1407
            taskListener.getLogger().println( Messages.MavenModuleSetBuild_DownloadedArtifact(
                    transferEvent.getResource().getRepositoryUrl(),
                    transferEvent.getResource().getResourceName()) );
S
Seiji Sogabe 已提交
1408 1409 1410
        }
        
    }
1411
}