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

hudson.maven.AbstractMavenProcessFactory Maven / Gradle / Ivy

Go to download

This plug-in provides deep integration of Hudson and Maven. This functionality used to be part of the Hudson core. Now it is a plug-in that is installed by default, but can be disabled.

There is a newer version: 2.2.0
Show newest version
package hudson.maven;

import static hudson.Util.fixNull;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Proc;
import hudson.maven.ProcessCache.NewProcess;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Executor;
import hudson.model.JDK;
import hudson.model.Node;
import hudson.model.TaskListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.RemoteInputStream;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.SocketInputStream;
import hudson.remoting.SocketOutputStream;
import hudson.remoting.Which;
import hudson.slaves.Channels;
import hudson.tasks.Maven.MavenInstallation;
import hudson.tasks._maven.MavenConsoleAnnotator;
import hudson.util.ArgumentListBuilder;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;

import org.kohsuke.stapler.framework.io.IOException2;

/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Olivier Lamy
 * 
 * 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.
 */

/**
 * @author Olivier Lamy
 */
public abstract class AbstractMavenProcessFactory
{

    private final MavenModuleSet mms;
    private final Launcher launcher;
    /**
     * Environment variables to be set to the maven process.
     * The same variables are exposed to the system property as well.
     */
    private final EnvVars envVars;

    /**
     * Optional working directory. Because of the process reuse, we can't always guarantee
     * that the returned Maven process has this as the working directory. But for the
     * aggregator style build, the process reuse is disabled, so in practice this always works.
     *
     * Also, Maven is supposed to work correctly regardless of the process current directory,
     * so a good behaving maven project shouldn't rely on the current project.
     */
    private final FilePath workDir;

    AbstractMavenProcessFactory(MavenModuleSet mms, Launcher launcher, EnvVars envVars, FilePath workDir) {
        this.mms = mms;
        this.launcher = launcher;
        this.envVars = envVars;
        this.workDir = workDir;
    }

    /**
     * Represents a bi-directional connection.
     *
     * 

* This implementation is remoting aware, so it can be safely sent to the remote callable object. * *

* When we run Maven on a slave, the master may not have a direct TCP/IP connectivty to the slave. * That means the {@link Channel} between the master and the Maven needs to be tunneled through * the channel between master and the slave, then go to TCP socket to the Maven. */ private static final class Connection implements Serializable { public InputStream in; public OutputStream out; Connection(InputStream in, OutputStream out) { this.in = in; this.out = out; } private Object writeReplace() { return new Connection(new RemoteInputStream(in),new RemoteOutputStream(out)); } private Object readResolve() { // ObjectInputStream seems to access data at byte-level and do not do any buffering, // so if we are remoted, buffering would be crucial. this.in = new BufferedInputStream(in); this.out = new BufferedOutputStream(out); return this; } private static final long serialVersionUID = 1L; } interface Acceptor { Connection accept() throws IOException; int getPort(); } /** * Opens a server socket and returns {@link Acceptor} so that * we can accept a connection later on it. */ private static final class SocketHandler implements Callable { public Acceptor call() throws IOException { return new AcceptorImpl(); } private static final long serialVersionUID = 1L; static final class AcceptorImpl implements Acceptor, Serializable { private transient final ServerSocket serverSocket; private transient Socket socket; AcceptorImpl() throws IOException { // open a TCP socket to talk to the launched Maven process. // let the OS pick up a random open port this.serverSocket = new ServerSocket(); serverSocket.bind(null); // new InetSocketAddress(InetAddress.getLocalHost(),0)); // prevent a hang at the accept method in case the forked process didn't start successfully serverSocket.setSoTimeout(MavenProcessFactory.socketTimeOut); } public Connection accept() throws IOException { socket = serverSocket.accept(); // we'd only accept one connection serverSocket.close(); return new Connection(new SocketInputStream(socket),new SocketOutputStream(socket)); } public int getPort() { return serverSocket.getLocalPort(); } /** * When sent to the remote node, send a proxy. */ private Object writeReplace() { return Channel.current().export(Acceptor.class, this); } } } private static final class GetCharset implements Callable { public String call() throws IOException { return System.getProperty("file.encoding"); } } /** * Starts maven process. */ public ProcessCache.NewProcess newProcess(BuildListener listener, OutputStream out) throws IOException, InterruptedException { if(MavenProcessFactory.debug) listener.getLogger().println("Using env variables: "+ envVars); try { //launcher.getChannel().export( type, instance ) final Acceptor acceptor = launcher.getChannel().call(new SocketHandler()); Charset charset; try { charset = Charset.forName(launcher.getChannel().call(new GetCharset())); } catch (UnsupportedCharsetException e) { // choose the bit preserving charset. not entirely sure if iso-8859-1 does that though. charset = Charset.forName("iso-8859-1"); } MavenConsoleAnnotator mca = new MavenConsoleAnnotator(out,charset); final ArgumentListBuilder cmdLine = buildMavenAgentCmdLine( listener,acceptor.getPort()); String[] cmds = cmdLine.toCommandArray(); final Proc proc = launcher.launch().cmds(cmds).envs(envVars).stdout(mca).pwd(workDir).start(); Connection con; try { con = acceptor.accept(); } catch (SocketTimeoutException e) { // failed to connect. Is the process dead? // if so, the error should have been provided by the launcher already. // so abort gracefully without a stack trace. if(!proc.isAlive()) throw new AbortException("Failed to launch Maven. Exit code = "+proc.join()); throw e; } return new NewProcess( Channels.forProcess("Channel to Maven "+ Arrays.toString(cmds), Computer.threadPoolForRemoting, new BufferedInputStream(con.in), new BufferedOutputStream(con.out), listener.getLogger(), proc), proc); } catch (IOException e) { if(fixNull(e.getMessage()).contains("java: not found")) { // diagnose issue #659 JDK jdk = mms.getJDK(); if(jdk==null) throw new IOException2(mms.getDisplayName()+" is not configured with a JDK, but your PATH doesn't include Java",e); } throw e; } } /** * Builds the command line argument list to launch the maven process. * */ protected abstract ArgumentListBuilder buildMavenAgentCmdLine(BuildListener listener,int tcpPort) throws IOException, InterruptedException; public String getMavenOpts() { String mavenOpts = mms.getMavenOpts(); if ((mavenOpts==null) || (mavenOpts.trim().length()==0)) { Node n = getCurrentNode(); if (n!=null) { try { String localMavenOpts = n.toComputer().getEnvironment().get("MAVEN_OPTS"); if ((localMavenOpts!=null) && (localMavenOpts.trim().length()>0)) { mavenOpts = localMavenOpts; } } catch (IOException e) { } catch (InterruptedException e) { // Don't do anything - this just means the slave isn't running, so we // don't want to use its MAVEN_OPTS anyway. } } } return envVars.expand(mavenOpts); } public MavenInstallation getMavenInstallation(TaskListener log) throws IOException, InterruptedException { MavenInstallation mi = mms.getMaven(); if (mi != null) mi = mi.forNode(getCurrentNode(), log).forEnvironment(envVars); return mi; } public JDK getJava(TaskListener log) throws IOException, InterruptedException { JDK jdk = mms.getJDK(); if (jdk != null) jdk = jdk.forNode(getCurrentNode(), log).forEnvironment(envVars); return jdk; } protected static final class GetRemotingJar implements Callable { public String call() throws IOException { return Which.jarFile(hudson.remoting.Launcher.class).getPath(); } } /** * Returns the current {@link Node} on which we are buildling. */ protected Node getCurrentNode() { return Executor.currentExecutor().getOwner().getNode(); } protected MavenModuleSet getMavenModuleSet() { return mms; } protected Launcher getLauncher() { return launcher; } protected EnvVars getEnvVars() { return envVars; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy