MavenModuleSetBuild.java 65.0 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
S
Seiji Sogabe 已提交
5
 * Red Hat, Inc., Victor Glushenkov, Alan Harder, Olivier Lamy
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;

27
import static hudson.model.Result.ABORTED;
S
Seiji Sogabe 已提交
28 29 30 31
import static hudson.model.Result.FAILURE;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
32
import hudson.FilePath.FileCallable;
S
Seiji Sogabe 已提交
33 34
import hudson.Launcher;
import hudson.Util;
35
import hudson.maven.MavenBuild.ProxyImpl2;
36
import hudson.maven.reporters.MavenFingerprinter;
37
import hudson.maven.reporters.MavenMailer;
38
import hudson.model.AbstractBuild;
O
Olivier Lamy 已提交
39 40 41 42
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.BuildListener;
M
mdonohue 已提交
43
import hudson.model.Cause.UpstreamCause;
O
Olivier Lamy 已提交
44 45 46
import hudson.model.Computer;
import hudson.model.Environment;
import hudson.model.Fingerprint;
47
import jenkins.model.Jenkins;
O
Olivier Lamy 已提交
48 49 50 51 52 53 54
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;
55
import hudson.remoting.Channel;
56
import hudson.remoting.VirtualChannel;
K
km 已提交
57
import hudson.scm.ChangeLogSet;
58
import hudson.tasks.BuildWrapper;
59
import hudson.tasks.MailSender;
60
import hudson.tasks.Maven.MavenInstallation;
61
import hudson.util.ArgumentListBuilder;
K
km 已提交
62
import hudson.util.IOUtils;
S
Seiji Sogabe 已提交
63
import hudson.util.MaskingClassLoader;
64
import hudson.util.ReflectionUtils;
65
import hudson.util.StreamTaskListener;
S
Seiji Sogabe 已提交
66 67 68 69 70 71

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PrintStream;
import java.io.Serializable;
72
import java.lang.reflect.Method;
S
Seiji Sogabe 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
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;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
89
import org.apache.maven.BuildFailureException;
S
Seiji Sogabe 已提交
90
import org.apache.maven.artifact.versioning.ComparableVersion;
91 92 93
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ReactorManager;
import org.apache.maven.lifecycle.LifecycleExecutionException;
94
import org.apache.maven.model.building.ModelBuildingRequest;
95 96 97
import org.apache.maven.monitor.event.EventDispatcher;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
98
import org.codehaus.plexus.util.PathTool;
99 100
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
S
Seiji Sogabe 已提交
101 102 103
import org.sonatype.aether.transfer.TransferCancelledException;
import org.sonatype.aether.transfer.TransferEvent;
import org.sonatype.aether.transfer.TransferListener;
104

105 106 107 108 109 110 111 112 113 114 115 116 117 118
/**
 * {@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 已提交
119
 *
120 121
 * @author Kohsuke Kawaguchi
 */
122
public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,MavenModuleSetBuild> {
123 124 125 126 127 128
    /**
     * {@link MavenReporter}s that will contribute project actions.
     * Can be null if there's none.
     */
    /*package*/ List<MavenReporter> projectActionReporters;

129 130
    private String mavenVersionUsed;

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

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

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

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

147 148 149 150 151 152 153 154 155 156 157
    /**
     * 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);
158 159
        String opts = getMavenOpts(log);

K
kohsuke 已提交
160 161
        if(opts!=null)
            envs.put("MAVEN_OPTS", opts);
J
James Nord 已提交
162 163 164 165 166 167 168 169 170 171 172 173 174 175
        // 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)
            throw new AbortException(
                    "A Maven installation needs to be available for this project to be built.\n"
                    + "Either your server has no Maven installations defined, or the requested Maven version does not exist.");

        mvn = mvn.forEnvironment(envs).forNode(
                Computer.currentComputer().getNode(), log);
        envs.put("M2_HOME", mvn.getHome());
        envs.put("PATH+MAVEN", mvn.getHome() + "/bin");
176 177 178
        return envs;
    }

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

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

        return r;
    }

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
    /**
     * Returns the filtered changeset entries that match the given module.
     */
    /*package*/ List<ChangeLogSet.Entry> getChangeSetFor(final MavenModule mod) {
        return new ArrayList<ChangeLogSet.Entry>() {
            {
                // 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)
235
                    if (FilenameUtils.separatorsToUnix(path).startsWith(normalizePath(sub.getRelativePath())))
236 237 238 239 240 241 242 243
                        return true;
                return false;
            }

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

253 254 255 256 257 258 259 260 261 262 263
    /**
     * 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();
264
        int end = nb!=null ? nb.getNumber() : Integer.MAX_VALUE;
265 266 267 268 269 270

        // 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>();
271
            MavenBuild b = m.getNearestBuild(number);
272 273 274 275 276 277 278 279 280
            while(b!=null && b.getNumber()<end) {
                builds.add(b);
                b = b.getNextBuild();
            }
            r.put(m,builds);
        }

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

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

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
    /**
     * Estimates the duration overhead the {@link MavenModuleSetBuild} itself adds
     * to the sum of duration of the module builds.
     */
    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());
    }
350

351
    private static String normalizePath(String relPath) {
352 353 354
        relPath = StringUtils.trimToEmpty( relPath );
        if (StringUtils.isEmpty( relPath )) {
            LOGGER.config("No need to normalize an empty path.");
355
        } else {
356 357 358 359 360 361 362 363 364 365 366 367
            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 );
            }
368
        }
369
        LOGGER.fine("Returning path " + relPath);
370 371 372
        return relPath;
    }

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

    public void setMavenVersionUsed( String mavenVersionUsed ) throws IOException {
        this.mavenVersionUsed = mavenVersionUsed;
        save();
    }

389 390 391 392 393 394 395 396 397
    @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();
    }

398
    @Override
399 400 401 402 403 404 405 406 407
    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);
    }

408 409
    /**
     * Computes the latest module builds that correspond to this build.
410
     * (when individual modules are built, a new ModuleSetBuild is not created,
411
     *  but rather the new module build falls under the previous ModuleSetBuild)
412 413 414 415 416 417 418 419 420 421 422 423 424
     */
    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 已提交
425
            if(b!=null && b.getNumber()>=getNumber())
426 427 428 429 430 431
                r.put(m,b);
        }

        return r;
    }

432 433 434 435 436 437
    public void registerAsProjectAction(MavenReporter reporter) {
        if(projectActionReporters==null)
            projectActionReporters = new ArrayList<MavenReporter>();
        projectActionReporters.add(reporter);
    }

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    /**
     * 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;
    }

466 467
    public void run() {
        run(new RunnerImpl());
468
        getProject().updateTransientActions();
469 470
    }

471 472 473 474 475 476 477 478 479
    @Override
    public Fingerprint.RangeSet getDownstreamRelationship(AbstractProject that) {
        Fingerprint.RangeSet rs = super.getDownstreamRelationship(that);
        for(List<MavenBuild> builds : getModuleBuilds().values())
            for (MavenBuild b : builds)
                rs.add(b.getDownstreamRelationship(that));
        return rs;
    }

480 481 482 483
    /**
     * Called when a module build that corresponds to this module set build
     * has completed.
     */
484
    /*package*/ void notifyModuleBuild(MavenBuild newBuild) {
485 486 487 488 489 490 491 492 493
        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.
494 495 496
            // 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) {
497 498 499 500 501 502 503 504 505 506 507
                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;
                    }
508
                }
509

510
                // see if the new build has any new aggregatable action that we haven't seen.
K
kohsuke 已提交
511 512 513 514 515 516 517
                for (AggregatableAction aa : newBuild.getActions(AggregatableAction.class)) {
                    if(individuals.add(aa.getClass())) {
                        // new AggregatableAction
                        MavenAggregatedReport mar = aa.createAggregatedAction(this, moduleBuilds);
                        mar.update(moduleBuilds,newBuild);
                        actions.add(mar);
                        modified = true;
518 519 520
                    }
                }

521
                if(modified) {
522
                    save();
523 524
                    getProject().updateTransientActions();
                }
525
            }
526 527 528 529 530

            // symlink to this module build
            String moduleFsName = newBuild.getProject().getModuleName().toFileSystemName();
            Util.createSymlink(getRootDir(),
                    "../../modules/"+ moduleFsName +"/builds/"+newBuild.getId() /*ugly!*/,
K
kohsuke 已提交
531
                    moduleFsName, StreamTaskListener.NULL);
532 533
        } catch (IOException e) {
            LOGGER.log(Level.WARNING,"Failed to update "+this,e);
534 535
        } catch (InterruptedException e) {
            LOGGER.log(Level.WARNING,"Failed to update "+this,e);
536
        }
537 538
    }

539 540
    public String getMavenOpts(TaskListener listener) {
        String opts = project.getMavenOpts();
541
        if (opts == null ) return null;
542
        try {
543
            Class<?> clazz = Class.forName( "org.jenkinsci.plugins.tokenmacro.TokenMacro" );
544
            Method expandMethod =
545
                ReflectionUtils.findMethod(clazz, "expand", new Class[]{ AbstractBuild.class, TaskListener.class, String.class} );
546 547 548 549
            opts = (String) expandMethod.invoke( null, this, listener, opts );
            //opts = TokenMacro.expand(this, listener, opts);
        //} catch (MacroEvaluationException e) {
        //    listener.error( "Ignore MacroEvaluationException " + e.getMessage() );            
550 551
        }
        catch(Exception tokenException) {
552
            listener.error("Ignore Problem expanding maven opts macros " + tokenException.getMessage());
553 554 555 556 557 558 559 560
        }
        catch(LinkageError linkageError) {
            // Token plugin not present. Ignore, this is OK.
        }

        return opts;
    }

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

568
        protected Result doRun(final BuildListener listener) throws Exception {
K
kohsuke 已提交
569
            PrintStream logger = listener.getLogger();
570
            Result r = null;
571
            try {
S
Seiji Sogabe 已提交
572
                
K
kohsuke 已提交
573
                EnvVars envVars = getEnvironment(listener);
574 575 576 577
                MavenInstallation mvn = project.getMaven();
                if(mvn==null)
                    throw new AbortException("A Maven installation needs to be available for this project to be built.\n"+
                                             "Either your server has no Maven installations defined, or the requested Maven version does not exist.");
578

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

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

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

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

614 615 616
                        if(!preBuild(listener, project.getPublishers()))
                            return Result.FAILURE;

617
                        parsePoms(listener, logger, envVars, mvn, mavenVersion); // #5428 : do pre-build *before* parsing pom
K
kohsuke 已提交
618 619
                        SplittableBuildListener slistener = new SplittableBuildListener(listener);
                        proxies = new HashMap<ModuleName, ProxyImpl2>();
620 621 622 623 624
                        List<ModuleName> changedModules = new ArrayList<ModuleName>();
                        
                        if (project.isIncrementalBuild() && !getChangeSet().isEmptySet()) {
                            changedModules.addAll(getUnbuildModulesSinceLastSuccessfulBuild());
                        }
625 626

                        for (MavenModule m : project.sortedActiveModules) {
627
                            MavenBuild mb = m.newBuild();
628
                            // JENKINS-8418
629
                            mb.setBuiltOnStr( getBuiltOnStr() );
630 631 632 633
                            // 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 已提交
634 635 636
                                //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.
637 638 639
                                if ((mb.getPreviousBuiltBuild() == null) ||
                                    (!getChangeSetFor(m).isEmpty()) 
                                    || (mb.getPreviousBuiltBuild().getResult().isWorseThan(Result.SUCCESS))) {
640
                                    changedModules.add(m.getModuleName());
641 642
                                }
                            }
643 644 645

                            mb.setWorkspace(getModuleRoot().child(m.getRelativePath()));
                            proxies.put(m.getModuleName(), mb.new ProxyImpl2(MavenModuleSetBuild.this,slistener));
646
                        }
K
kohsuke 已提交
647 648 649 650 651 652 653

                        // 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)
654
                        String rootPOM = project.getRootPOM();
K
kohsuke 已提交
655 656
                        FilePath pom = getModuleRoot().child(rootPOM);
                        FilePath parentLoc = getWorkspace().child(rootPOM);
K
kohsuke 已提交
657 658 659
                        if(!pom.exists() && parentLoc.exists())
                            pom = parentLoc;

S
Seiji Sogabe 已提交
660 661 662 663 664 665 666 667 668 669
                        
                        ProcessCache.MavenProcess process = null;
                        
                        boolean maven3orLater = MavenUtil.maven3orLater( mavenVersion ); 
                       
                        if ( maven3orLater )
                        {
                            LOGGER.info( "using maven 3 " + mavenVersion );
                            process =
                                MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
670
                                                                  new Maven3ProcessFactory( project, launcher, envVars, getMavenOpts(listener),
S
Seiji Sogabe 已提交
671 672 673 674
                                                                                            pom.getParent() ) );
                        }
                        else
                        {
675
                            LOGGER.info( "using maven 2 " + mavenVersion );
S
Seiji Sogabe 已提交
676 677
                            process =
                                MavenBuild.mavenProcessCache.get( launcher.getChannel(), slistener,
678
                                                                  new MavenProcessFactory( project, launcher, envVars,getMavenOpts(listener),
S
Seiji Sogabe 已提交
679 680
                                                                                           pom.getParent() ) );
                        }
K
kohsuke 已提交
681
                        ArgumentListBuilder margs = new ArgumentListBuilder().add("-B").add("-f", pom.getRemote());
682
                        if(project.usesPrivateRepository())
K
kohsuke 已提交
683
                            margs.add("-Dmaven.repo.local="+getWorkspace().child(".repository"));
684 685 686 687 688 689 690
                        
                        // 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 已提交
691
                        boolean maven2_1orLater = new ComparableVersion (mavenVersion).compareTo( new ComparableVersion ("2.1") ) >= 0;
692 693 694
                        boolean needsFullBuild = getPreviousCompletedBuild() != null &&
                            getPreviousCompletedBuild().getAction(NeedsFullBuildAction.class) != null;
                        if (project.isIncrementalBuild() && !needsFullBuild && maven2_1orLater && !changedModules.isEmpty()) {
695 696 697
                            margs.add("-amd");
                            margs.add("-pl", Util.join(changedModules, ","));
                        }
698 699

                        if (project.getAlternateSettings() != null) {
700 701 702 703 704 705 706 707 708 709
                            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());
                            }
710 711
                        }

712
                        margs.addTokenized(envVars.expand(project.getGoals()));
S
Seiji Sogabe 已提交
713 714 715 716 717 718 719 720
                        if (maven3orLater)
                        {   
                            
                            Map<ModuleName,List<MavenReporter>> reporters = new HashMap<ModuleName, List<MavenReporter>>(project.sortedActiveModules.size());
                            for (MavenModule mavenModule : project.sortedActiveModules)
                            {
                                reporters.put( mavenModule.getModuleName(), mavenModule.createReporters() );
                            }
721 722
                            Maven3Builder maven3Builder = 
                                new Maven3Builder( slistener, proxies, reporters, margs.toList(), envVars, mavenBuildInformation );
S
Seiji Sogabe 已提交
723 724 725 726 727 728 729 730 731 732 733 734 735 736
                            MavenProbeAction mpa=null;
                            try {
                                mpa = new MavenProbeAction(project,process.channel);
                                addAction(mpa);
                                r = process.call(maven3Builder);
                                return r;
                            } finally {
                                maven3Builder.end(launcher);
                                getActions().remove(mpa);
                                process.discard();
                            }                            
                            
                        } else {
                         
737 738
                            Builder builder = 
                                new Builder(slistener, proxies, project.sortedActiveModules, margs.toList(), envVars, mavenBuildInformation);
S
Seiji Sogabe 已提交
739 740 741 742 743 744 745 746 747 748 749
                            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();
                            }
K
kohsuke 已提交
750
                        }
751 752 753
                    } catch (InterruptedException e) {
                        r = ABORTED;
                        throw e;
754
                    } finally {
755 756 757
                        if (r != null) {
                            setResult(r);
                        }
K
kohsuke 已提交
758
                        // tear down in reverse order
759 760 761 762 763 764 765 766
                        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;
767
                    }
K
kohsuke 已提交
768
                }
769
                
770
                return r;
771
            } catch (AbortException e) {
772 773
                if(e.getMessage()!=null)
                    listener.error(e.getMessage());
774
                return Result.FAILURE;
775
            } catch (InterruptedIOException e) {
776
                e.printStackTrace(listener.error("Aborted Maven execution for InterruptedIOException"));
777 778 779 780
                return Result.ABORTED;
            } catch (IOException e) {
                e.printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
                return Result.FAILURE;
781 782
            } catch (RunnerAbortedException e) {
                return Result.FAILURE;
783 784
            } catch (RuntimeException e) {
                // bug in the code.
785
                e.printStackTrace(listener.error("Processing failed due to a bug in the code. Please report this to jenkinsci-users@googlegroups.com"));
K
kohsuke 已提交
786 787 788
                logger.println("project="+project);
                logger.println("project.getModules()="+project.getModules());
                logger.println("project.getRootModule()="+project.getRootModule());
789 790 791 792
                throw e;
            }
        }

793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
        /**
         * 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();
            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;
        }

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

822 823
            List<PomInfo> poms;
            try {
824
                poms = getModuleRoot().act(new PomParser(listener, mvn, project, mavenVersion, envVars));
825
            } catch (IOException e) {
826 827 828 829 830 831 832
                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());
                }
                
833 834 835
                if (e.getCause() instanceof AbortException)
                    throw (AbortException) e.getCause();
                throw e;
836 837
            } catch (MavenExecutionException e) {
                // Maven failed to parse POM
K
i18n  
kohsuke 已提交
838
                e.getCause().printStackTrace(listener.error(Messages.MavenModuleSetBuild_FailedToParsePom()));
839 840 841
                if (project.isIncrementalBuild()) {
                    getActions().add(new NeedsFullBuildAction());
                }
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
                throw new AbortException();
            }

            // 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);
                        mm.reconfigure(pom);
                        modules.put(pom.name,mm);
                    } else {// this looks like a new module
K
i18n  
kohsuke 已提交
863
                        logger.println(Messages.MavenModuleSetBuild_DiscoveredModule(pom.name,pom.displayName));
864 865 866 867 868 869 870 871 872 873 874 875 876 877
                        mm = new MavenModule(project,pom,getNumber());
                        modules.put(mm.getModuleName(),mm);
                    }
                    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);
878
                    om.makeDisabled(true);
879 880 881 882 883
                }
                modules.putAll(old);
            }

            // we might have added new modules
884
            Jenkins.getInstance().rebuildDependencyGraph();
885 886 887 888 889 890

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

891
        protected void post2(BuildListener listener) throws Exception {
892 893 894 895
            // 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();
896

897
            // at this point the result is all set, so ignore the return value
898
            if (!performAllBuildSteps(listener, project.getPublishers(), true))
899
                setResult(FAILURE);
900
            if (!performAllBuildSteps(listener, project.getProperties(), true))
901
                setResult(FAILURE);
902 903 904 905 906 907 908 909

            // 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);
910 911 912 913
        }

        @Override
        public void cleanUp(BuildListener listener) throws Exception {
914 915 916 917 918 919 920 921
            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.
922 923
            performAllBuildSteps(listener, project.getPublishers(), false);
            performAllBuildSteps(listener, project.getProperties(), false);
924
            super.cleanUp(listener);
925
        }
926

927
    }
928

929 930
    /**
     * Runs Maven and builds the project.
K
kohsuke 已提交
931 932 933
     *
     * This is only used for
     * {@link MavenModuleSet#isAggregatorStyleBuild() the aggregator style build}.
934 935
     */
    private static final class Builder extends MavenBuilder {
936
        private final Map<ModuleName,MavenBuildProxy2> proxies;
937
        private final Map<ModuleName,List<MavenReporter>> reporters = new HashMap<ModuleName,List<MavenReporter>>();
K
kohsuke 已提交
938 939 940
        private final Map<ModuleName,List<ExecutedMojo>> executedMojos = new HashMap<ModuleName,List<ExecutedMojo>>();
        private long mojoStartTime;

941
        private MavenBuildProxy2 lastProxy;
942

943 944 945 946 947
        /**
         * Kept so that we can finalize them in the end method.
         */
        private final transient Map<ModuleName,ProxyImpl2> sourceProxies;

948
        public Builder(BuildListener listener,Map<ModuleName,ProxyImpl2> proxies, Collection<MavenModule> modules, List<String> goals, Map<String,String> systemProps,  MavenBuildInformation mavenBuildInformation) {
949
            super(listener,goals,systemProps);
950 951 952
            this.sourceProxies = proxies;
            this.proxies = new HashMap<ModuleName, MavenBuildProxy2>(proxies);
            for (Entry<ModuleName,MavenBuildProxy2> e : this.proxies.entrySet())
953
                e.setValue(new FilterImpl(e.getValue(), mavenBuildInformation));
954 955 956 957 958

            for (MavenModule m : modules)
                reporters.put(m.getModuleName(),m.createReporters());
        }

959
        private class FilterImpl extends MavenBuildProxy2.Filter<MavenBuildProxy2> implements Serializable {
960 961 962 963
            
            private MavenBuildInformation mavenBuildInformation;
            
            public FilterImpl(MavenBuildProxy2 core, MavenBuildInformation mavenBuildInformation) {
964
                super(core);
O
Olivier Lamy 已提交
965
                this.mavenBuildInformation = mavenBuildInformation;
966 967
            }

968
            @Override
969 970 971 972
            public void executeAsync(final BuildCallable<?,?> program) throws IOException {
                futures.add(Channel.current().callAsync(new AsyncInvoker(core,program)));
            }

973 974 975 976
            public MavenBuildInformation getMavenBuildInformation() {
                return mavenBuildInformation;
            }            
            
977
            private static final long serialVersionUID = 1L;
978 979


980 981
        }

982 983 984 985
        /**
         * Invoked after the maven has finished running, and in the master, not in the maven process.
         */
        void end(Launcher launcher) throws IOException, InterruptedException {
986 987
            for (Map.Entry<ModuleName,ProxyImpl2> e : sourceProxies.entrySet()) {
                ProxyImpl2 p = e.getValue();
988
                for (MavenReporter r : reporters.get(e.getKey())) {
989
                    // we'd love to do this when the module build ends, but doing so requires
990 991 992 993 994 995 996 997
                    // we know how many task segments are in the current build.
                    r.end(p.owner(),launcher,listener);
                    p.appendLastLog();
                }
                p.close();
            }
        }

998
        @Override
999 1000
        public Result call() throws IOException {
            try {
S
Seiji Sogabe 已提交
1001 1002 1003
                if (debug) {
                    listener.getLogger().println("Builder extends MavenBuilder in call " + Thread.currentThread().getContextClassLoader());
                }
1004 1005 1006 1007 1008 1009 1010
                return super.call();
            } finally {
                if(lastProxy!=null)
                    lastProxy.appendLastLog();
            }
        }

S
Seiji Sogabe 已提交
1011

1012
        @Override
1013
        void preBuild(MavenSession session, ReactorManager rm, EventDispatcher dispatcher) throws BuildFailureException, LifecycleExecutionException, IOException, InterruptedException {
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
            // set all modules which are not actually being build (in incremental builds) to NOT_BUILD
            
            List<MavenProject> projects = rm.getSortedProjects();
            Set<ModuleName> buildingProjects = new HashSet<ModuleName>();
            for (MavenProject p : projects) {
                buildingProjects.add(new ModuleName(p));
            }
            
            for (Entry<ModuleName,MavenBuildProxy2> e : this.proxies.entrySet()) {
                if (! buildingProjects.contains(e.getKey())) {
1024 1025 1026 1027
                    MavenBuildProxy2 proxy = e.getValue();
                    proxy.start();
                    proxy.setResult(Result.NOT_BUILT);
                    proxy.end();
1028 1029
                }
            }
1030 1031 1032 1033 1034 1035 1036
        }

        void postBuild(MavenSession session, ReactorManager rm, EventDispatcher dispatcher) throws BuildFailureException, LifecycleExecutionException, IOException, InterruptedException {
            // TODO
        }

        void preModule(MavenProject project) throws InterruptedException, IOException, hudson.maven.agent.AbortException {
K
kohsuke 已提交
1037
            ModuleName name = new ModuleName(project);
K
kohsuke 已提交
1038 1039
            MavenBuildProxy2 proxy = proxies.get(name);
            listener.getLogger().flush();   // make sure the data until here are all written
K
kohsuke 已提交
1040 1041 1042 1043
            proxy.start();
            for (MavenReporter r : reporters.get(name))
                if(!r.preBuild(proxy,project,listener))
                    throw new hudson.maven.agent.AbortException(r+" failed");
1044 1045 1046
        }

        void postModule(MavenProject project) throws InterruptedException, IOException, hudson.maven.agent.AbortException {
K
kohsuke 已提交
1047
            ModuleName name = new ModuleName(project);
K
kohsuke 已提交
1048
            MavenBuildProxy2 proxy = proxies.get(name);
K
kohsuke 已提交
1049 1050 1051 1052 1053
            List<MavenReporter> rs = reporters.get(name);
            if(rs==null) { // probe for issue #906
                throw new AssertionError("reporters.get("+name+")==null. reporters="+reporters+" proxies="+proxies);
            }
            for (MavenReporter r : rs)
K
kohsuke 已提交
1054 1055
                if(!r.postBuild(proxy,project,listener))
                    throw new hudson.maven.agent.AbortException(r+" failed");
K
kohsuke 已提交
1056
            proxy.setExecutedMojos(executedMojos.get(name));
K
kohsuke 已提交
1057
            listener.getLogger().flush();   // make sure the data until here are all written
K
kohsuke 已提交
1058
            proxy.end();
1059
            lastProxy = proxy;
1060 1061 1062
        }

        void preExecute(MavenProject project, MojoInfo mojoInfo) throws IOException, InterruptedException, hudson.maven.agent.AbortException {
K
kohsuke 已提交
1063
            ModuleName name = new ModuleName(project);
K
kohsuke 已提交
1064
            MavenBuildProxy proxy = proxies.get(name);
K
kohsuke 已提交
1065 1066 1067
            for (MavenReporter r : reporters.get(name))
                if(!r.preExecute(proxy,project,mojoInfo,listener))
                    throw new hudson.maven.agent.AbortException(r+" failed");
K
kohsuke 已提交
1068 1069

            mojoStartTime = System.currentTimeMillis();
1070 1071 1072
        }

        void postExecute(MavenProject project, MojoInfo mojoInfo, Exception exception) throws IOException, InterruptedException, hudson.maven.agent.AbortException {
K
kohsuke 已提交
1073
            ModuleName name = new ModuleName(project);
K
kohsuke 已提交
1074 1075 1076 1077 1078 1079

            List<ExecutedMojo> mojoList = executedMojos.get(name);
            if(mojoList==null)
                executedMojos.put(name,mojoList=new ArrayList<ExecutedMojo>());
            mojoList.add(new ExecutedMojo(mojoInfo,System.currentTimeMillis()-mojoStartTime));

K
kohsuke 已提交
1080
            MavenBuildProxy2 proxy = proxies.get(name);
K
kohsuke 已提交
1081 1082 1083 1084 1085
            for (MavenReporter r : reporters.get(name))
                if(!r.postExecute(proxy,project,mojoInfo,listener,exception))
                    throw new hudson.maven.agent.AbortException(r+" failed");
            if(exception!=null)
                proxy.setResult(Result.FAILURE);
1086 1087
        }

1088
        void onReportGenerated(MavenProject project, MavenReportInfo report) throws IOException, InterruptedException, hudson.maven.agent.AbortException {
1089 1090 1091 1092 1093 1094
            ModuleName name = new ModuleName(project);
            MavenBuildProxy proxy = proxies.get(name);
            for (MavenReporter r : reporters.get(name))
                if(!r.reportGenerated(proxy,project,report,listener))
                    throw new hudson.maven.agent.AbortException(r+" failed");
        }
S
Seiji Sogabe 已提交
1095 1096
        
        
1097

1098
        private static final long serialVersionUID = 1L;
S
Seiji Sogabe 已提交
1099 1100 1101 1102 1103 1104

        @Override
        public ClassLoader getClassLoader()
        {
            return new MaskingClassLoader( super.getClassLoader() );
        }
1105
    }
1106 1107 1108 1109 1110 1111 1112 1113 1114

    /**
     * Used to tunnel exception from Maven through remoting.
     */
    private static final class MavenExecutionException extends RuntimeException {
        private MavenExecutionException(Exception cause) {
            super(cause);
        }

1115
        @Override
1116 1117 1118 1119 1120 1121 1122
        public Exception getCause() {
            return (Exception)super.getCause();
        }

        private static final long serialVersionUID = 1L;
    }

1123 1124 1125 1126 1127
    /**
     * 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>> {
1128
        private final BuildListener listener;
1129
        private final String rootPOM;
K
kohsuke 已提交
1130 1131 1132 1133
        /**
         * Capture the value of the static field so that the debug flag
         * takes an effect even when {@link PomParser} runs in a slave.
         */
1134
        private final boolean verbose = debug;
1135
        private final MavenInstallation mavenHome;
1136
        private final String profiles;
K
kohsuke 已提交
1137
        private final Properties properties;
1138
        private final String privateRepository;
1139
        private final String alternateSettings;
K
kohsuke 已提交
1140
        private final boolean nonRecursive;
1141 1142
        // We're called against the module root, not the workspace, which can cause a lot of confusion.
        private final String workspaceProper;
1143
        private final String mavenVersion;
1144
        
1145 1146
        private final String moduleRootPath;
        
1147 1148 1149 1150
        private boolean resolveDependencies = false;
  
        private boolean processPlugins = false;
        
1151 1152
        private int mavenValidationLevel = -1;
        
1153 1154
        String rootPOMRelPrefix;
        
1155
        public PomParser(BuildListener listener, MavenInstallation mavenHome, MavenModuleSet project,String mavenVersion,EnvVars envVars) {
K
kohsuke 已提交
1156
            // project cannot be shipped to the remote JVM, so all the relevant properties need to be captured now.
1157
            this.listener = listener;
1158
            this.mavenHome = mavenHome;
K
kohsuke 已提交
1159 1160 1161
            this.rootPOM = project.getRootPOM();
            this.profiles = project.getProfiles();
            this.properties = project.getMavenProperties();
1162 1163 1164 1165 1166 1167 1168 1169 1170
            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() );
                    }
                }
            }
1171 1172 1173 1174 1175 1176 1177 1178
            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 已提交
1179
            this.nonRecursive = project.isNonRecursive();
1180
            this.workspaceProper = project.getLastBuild().getWorkspace().getRemote();
K
kohsuke 已提交
1181
            if (project.usesPrivateRepository()) {
1182
                this.privateRepository = project.getLastBuild().getWorkspace().child(".repository").getRemote();
K
kohsuke 已提交
1183
            } else {
1184 1185
                this.privateRepository = null;
            }
1186 1187
            // TODO maybe in goals with -s,--settings
            // or -Dmaven.repo.local
1188
            this.alternateSettings = project.getAlternateSettings();
1189
            this.mavenVersion = mavenVersion;
1190 1191
            this.resolveDependencies = project.isResolveDependencies();
            this.processPlugins = project.isProcessPlugins();
1192 1193 1194 1195
            
            this.moduleRootPath = 
                project.getScm().getModuleRoot( project.getLastBuild().getWorkspace(), project.getLastBuild() ).getRemote();            
            
1196
            this.mavenValidationLevel = project.getMavenValidationLevel();
1197

1198 1199
        }

O
Olivier Lamy 已提交
1200
        
1201
        public List<PomInfo> invoke(File ws, VirtualChannel channel) throws IOException {
K
km 已提交
1202
            File pom;
1203
            
K
kohsuke 已提交
1204 1205
            PrintStream logger = listener.getLogger();

K
km 已提交
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
            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;
            }
1217

1218
            if(!pom.exists())
1219
                throw new AbortException(Messages.MavenModuleSetBuild_NoSuchPOMFile(pom));
1220

1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232
            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 = "";
            }            
            
1233
            if(verbose)
1234 1235 1236 1237
                logger.println("Parsing "
			       + (nonRecursive ? "non-recursively " : "recursively ")
			       + pom);
	    
1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
            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 已提交
1253 1254 1255 1256
            if (debug)
            {
                logger.println("use settingsLoc " + settingsLoc + " , privateRepository " + privateRepository);
            }
1257 1258 1259 1260
            if ((settingsLoc != null) && (!settingsLoc.exists())) {
                throw new AbortException(Messages.MavenModuleSetBuild_NoSuchAlternateSettings(settingsLoc.getAbsolutePath()));
            }

1261
            try {
S
Seiji Sogabe 已提交
1262 1263 1264 1265
                MavenEmbedderRequest mavenEmbedderRequest = new MavenEmbedderRequest( listener, mavenHome.getHomeDir(),
                                                                                      profiles, properties,
                                                                                      privateRepository, settingsLoc );
                mavenEmbedderRequest.setTransferListener( new SimpleTransferListener(listener) );
1266
                
1267 1268
                mavenEmbedderRequest.setProcessPlugins( this.processPlugins );
                mavenEmbedderRequest.setResolveDependencies( this.resolveDependencies );
1269 1270 1271
                
                // FIXME handle 3.1 level when version will be here : no rush :-)
                // or made something configurable tru the ui ?
1272
                ReactorReader reactorReader = null;
1273
                boolean maven3OrLater = new ComparableVersion (mavenVersion).compareTo( new ComparableVersion ("3.0") ) >= 0;
1274
                if (maven3OrLater) {
1275
                    mavenEmbedderRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
1276 1277 1278 1279
                } else {
                    reactorReader = new ReactorReader( new HashMap<String, MavenProject>(), new File(workspaceProper) );
                    mavenEmbedderRequest.setWorkspaceReader( reactorReader );
                }
1280 1281 1282 1283 1284 1285
                
                
                if (this.mavenValidationLevel >= 0) {
                    mavenEmbedderRequest.setValidationLevel( this.mavenValidationLevel );
                }
                
O
Olivier Lamy 已提交
1286
                //mavenEmbedderRequest.setClassLoader( MavenEmbedderUtils.buildClassRealm( mavenHome.getHomeDir(), null, null ) );
1287
                
S
Seiji Sogabe 已提交
1288 1289
                MavenEmbedder embedder = MavenUtil.createEmbedder( mavenEmbedderRequest );
                
1290 1291 1292 1293
                MavenProject rootProject = null;
                
                List<MavenProject> mps = new ArrayList<MavenProject>(0);
                if (maven3OrLater) {
1294
                    mps = embedder.readProjects( pom,!this.nonRecursive );
1295 1296

                } else {
K
Kohsuke Kawaguchi 已提交
1297
                    // http://issues.jenkins-ci.org/browse/HUDSON-8390
1298 1299 1300 1301 1302 1303
                    // 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 );
1304 1305 1306
                    if (!this.nonRecursive) {
                        readChilds( mavenProject, embedder, mps, reactorReader );
                    }
1307
                }
S
Seiji Sogabe 已提交
1308 1309
                Map<String,MavenProject> canonicalPaths = new HashMap<String, MavenProject>( mps.size() );
                for(MavenProject mp : mps) {
1310 1311 1312
                    // 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 );
1313
                }                
S
Seiji Sogabe 已提交
1314
                //MavenUtil.resolveModules(embedder,mp,getRootPath(rootPOMRelPrefix),relPath,listener,nonRecursive);
1315

1316
                if(verbose) {
S
Seiji Sogabe 已提交
1317 1318
                    for (Entry<String,MavenProject> e : canonicalPaths.entrySet())
                        logger.printf("Discovered %s at %s\n",e.getValue().getId(),e.getKey());
K
kohsuke 已提交
1319 1320
                }

S
Seiji Sogabe 已提交
1321
                Set<PomInfo> infos = new LinkedHashSet<PomInfo>();
1322 1323 1324 1325 1326 1327 1328
                
                if (maven3OrLater) {
                    for (MavenProject mp : mps) {
                        if (mp.isExecutionRoot()) {
                            rootProject = mp;
                            continue;
                        }
S
Seiji Sogabe 已提交
1329 1330 1331 1332 1333 1334 1335
                    }
                }
                // 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 已提交
1336 1337 1338

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

S
Seiji Sogabe 已提交
1340
                return new ArrayList<PomInfo>(infos);
1341
            } catch (MavenEmbedderException e) {
1342
                throw new MavenExecutionException(e);
1343
            } catch (ProjectBuildingException e) {
1344
                throw new MavenExecutionException(e);
1345 1346 1347
            }
        }

1348 1349 1350
        /**
         * @see PomInfo#relativePath to understand relPath calculation
         */
S
Seiji Sogabe 已提交
1351
        private void toPomInfo(MavenProject mp, PomInfo parent, Map<String,MavenProject> abslPath, Set<PomInfo> infos) throws IOException {
1352
            
1353
            String relPath = PathTool.getRelativeFilePath( this.moduleRootPath, mp.getBasedir().getPath() );
1354 1355
            relPath = normalizePath(relPath);

1356 1357
            if (parent == null ) {
                relPath = getRootPath(rootPOMRelPrefix);
S
Seiji Sogabe 已提交
1358 1359
            }
            
1360 1361
            relPath = StringUtils.removeStart( relPath, "/" );
            
S
Seiji Sogabe 已提交
1362
            PomInfo pi = new PomInfo(mp, parent, relPath);
K
kohsuke 已提交
1363
            infos.add(pi);
1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
            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) {
                        listener.getLogger().printf("Found a module with path " + modulePath + " but no associated project");
                        continue;
                    }
                    toPomInfo(child,pi,abslPath,infos);
S
Seiji Sogabe 已提交
1381 1382
                }
            }
1383
        }
1384 1385 1386 1387 1388 1389 1390 1391
        
        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 ) {
1392 1393 1394 1395 1396 1397 1398
                    File pomFile = new File(mp.getFile().getParent(), module);
                    MavenProject mavenProject2 = null;
                    // take care of HUDSON-8445
                    if (pomFile.isFile() && pomFile.exists())
                        mavenProject2 = mavenEmbedder.readProject( pomFile );
                    else
                        mavenProject2 = mavenEmbedder.readProject( new File(mp.getFile().getParent(), module + "/pom.xml") );
1399 1400 1401 1402 1403 1404
                    mavenProjects.add( mavenProject2 );
                    reactorReader.addProject( mavenProject2 );
                    readChilds( mavenProject2, mavenEmbedder, mavenProjects, reactorReader );
                }
            }
        }
1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417
        
        /**
         * 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);
        }
        
1418 1419 1420

        private static final long serialVersionUID = 1L;
    }
1421
        
1422
    private static final Logger LOGGER = Logger.getLogger(MavenModuleSetBuild.class.getName());
K
kohsuke 已提交
1423 1424

    /**
1425
     * Extra verbose debug switch.
K
kohsuke 已提交
1426
     */
S
Seiji Sogabe 已提交
1427
    public static boolean debug = Boolean.getBoolean( "hudson.maven.debug" );
1428

1429 1430 1431 1432
    @Override
    public MavenModuleSet getParent() {// don't know why, but javac wants this
        return super.getParent();
    }
S
Seiji Sogabe 已提交
1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482
    
    /**
     * 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 )
        {
            taskListener.getLogger().println("failed to transfer " + transferEvent.getException().getMessage());
        }

        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 )
        {
            taskListener.getLogger().println( "downloaded artifact " + transferEvent.getResource().getRepositoryUrl()
                                                  + "/" + transferEvent.getResource().getResourceName() );
        }
        
    }
1483
}