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

1 package hudson.maven;
2
3 import static hudson.Util.fixNull;
4
5 import hudson.AbortException;
6 import hudson.EnvVars;
7 import hudson.FilePath;
8 import hudson.Launcher;
9 import hudson.Platform;
10 import hudson.Proc;
11 import hudson.maven.ProcessCache.NewProcess;
12 import hudson.model.BuildListener;
13 import hudson.model.Computer;
14 import hudson.model.Executor;
15 import hudson.model.JDK;
16 import hudson.model.Node;
17 import hudson.model.TaskListener;
18 import hudson.model.Run.RunnerAbortedException;
19 import hudson.remoting.Callable;
20 import hudson.remoting.Channel;
21 import hudson.remoting.RemoteInputStream;
22 import hudson.remoting.RemoteOutputStream;
23 import hudson.remoting.SocketInputStream;
24 import hudson.remoting.SocketOutputStream;
25 import hudson.remoting.Which;
26 import hudson.slaves.Channels;
27 import hudson.tasks.Maven.MavenInstallation;
28 import hudson.tasks._maven.MavenConsoleAnnotator;
29 import hudson.util.ArgumentListBuilder;
30 import java.io.BufferedInputStream;
31 import java.io.BufferedOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.io.Serializable;
36 import java.net.ServerSocket;
37 import java.net.Socket;
38 import java.net.SocketTimeoutException;
39 import java.nio.charset.Charset;
40 import java.nio.charset.UnsupportedCharsetException;
41 import java.util.Arrays;
42
43 import jenkins.model.Jenkins;
44
45 import org.kohsuke.stapler.framework.io.IOException2;
46
47 /*
48 * The MIT License
49 *
50 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Olivier Lamy
51 *
52 * Permission is hereby granted, free of charge, to any person obtaining a copy
53 * of this software and associated documentation files (the "Software"), to deal
54 * in the Software without restriction, including without limitation the rights
55 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
56 * copies of the Software, and to permit persons to whom the Software is
57 * furnished to do so, subject to the following conditions:
58 *
59 * The above copyright notice and this permission notice shall be included in
60 * all copies or substantial portions of the Software.
61 *
62 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
63 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
64 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
65 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
66 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
67 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
68 * THE SOFTWARE.
69 */
70
71 /**
72 * @author Olivier Lamy
73 */
74 public abstract class AbstractMavenProcessFactory
75 {
76
77 private final MavenModuleSet mms;
78 private final Launcher launcher;
79 /**
80 * Environment variables to be set to the maven process.
81 * The same variables are exposed to the system property as well.
82 */
83 private final EnvVars envVars;
84
85 /**
86 * Optional working directory. Because of the process reuse, we can't always guarantee
87 * that the returned Maven process has this as the working directory. But for the
88 * aggregator style build, the process reuse is disabled, so in practice this always works.
89 *
90 * Also, Maven is supposed to work correctly regardless of the process current directory,
91 * so a good behaving maven project shouldn't rely on the current project.
92 */
93 private final FilePath workDir;
94
95 private final String mavenOpts;
96
97 AbstractMavenProcessFactory(MavenModuleSet mms, Launcher launcher, EnvVars envVars, String mavenOpts, FilePath workDir) {
98 this.mms = mms;
99 this.launcher = launcher;
100 this.envVars = envVars;
101 this.workDir = workDir;
102 this.mavenOpts = mavenOpts;
103 }
104
105 /**
106 * Represents a bi-directional connection.
107 *
108 * <p>
109 * This implementation is remoting aware, so it can be safely sent to the remote callable object.
110 *
111 * <p>
112 * When we run Maven on a slave, the master may not have a direct TCP/IP connectivty to the slave.
113 * That means the {@link Channel} between the master and the Maven needs to be tunneled through
114 * the channel between master and the slave, then go to TCP socket to the Maven.
115 */
116 private static final class Connection implements Serializable {
117 public InputStream in;
118 public OutputStream out;
119
120 Connection(InputStream in, OutputStream out) {
121 this.in = in;
122 this.out = out;
123 }
124
125 private Object writeReplace() {
126 return new Connection(new RemoteInputStream(in),new RemoteOutputStream(out));
127 }
128
129 private Object readResolve() {
130 // ObjectInputStream seems to access data at byte-level and do not do any buffering,
131 // so if we are remoted, buffering would be crucial.
132 this.in = new BufferedInputStream(in);
133 this.out = new BufferedOutputStream(out);
134 return this;
135 }
136
137 private static final long serialVersionUID = 1L;
138 }
139
140 interface Acceptor {
141 Connection accept() throws IOException;
142 int getPort();
143 }
144
145 /**
146 * Opens a server socket and returns {@link Acceptor} so that
147 * we can accept a connection later on it.
148 */
149 private static final class SocketHandler implements Callable<Acceptor,IOException> {
150 public Acceptor call() throws IOException {
151 return new AcceptorImpl();
152 }
153
154 private static final long serialVersionUID = 1L;
155
156 static final class AcceptorImpl implements Acceptor, Serializable {
157 private static final long serialVersionUID = -2226788819948521018L;
158 private transient final ServerSocket serverSocket;
159 private transient Socket socket;
160
161 AcceptorImpl() throws IOException {
162 // open a TCP socket to talk to the launched Maven process.
163 // let the OS pick up a random open port
164 this.serverSocket = new ServerSocket();
165 serverSocket.bind(null); // new InetSocketAddress(InetAddress.getLocalHost(),0));
166 // prevent a hang at the accept method in case the forked process didn't start successfully
167 serverSocket.setSoTimeout(MavenProcessFactory.socketTimeOut);
168 }
169
170 public Connection accept() throws IOException {
171 socket = serverSocket.accept();
172 // we'd only accept one connection
173 serverSocket.close();
174
175 return new Connection(new SocketInputStream(socket),new SocketOutputStream(socket));
176 }
177
178 public int getPort() {
179 return serverSocket.getLocalPort();
180 }
181
182 /**
183 * When sent to the remote node, send a proxy.
184 */
185 private Object writeReplace() {
186 return Channel.current().export(Acceptor.class, this);
187 }
188 }
189 }
190
191 private static final class GetCharset implements Callable<String,IOException> {
192 private static final long serialVersionUID = 3459269768733083577L;
193
194 public String call() throws IOException {
195 return System.getProperty("file.encoding");
196 }
197 }
198
199 /**
200 * Starts maven process.
201 */
202 public ProcessCache.NewProcess newProcess(BuildListener listener, OutputStream out) throws IOException, InterruptedException {
203 if(MavenProcessFactory.debug)
204 listener.getLogger().println("Using env variables: "+ envVars);
205 try {
206 //launcher.getChannel().export( type, instance )
207 final Acceptor acceptor = launcher.getChannel().call(new SocketHandler());
208 Charset charset;
209 try {
210 charset = Charset.forName(launcher.getChannel().call(new GetCharset()));
211 } catch (UnsupportedCharsetException e) {
212 // choose the bit preserving charset. not entirely sure if iso-8859-1 does that though.
213 charset = Charset.forName("iso-8859-1");
214 }
215
216 MavenConsoleAnnotator mca = new MavenConsoleAnnotator(out,charset);
217
218 if ( mavenRemoteUseInet ) {
219 envVars.put(MAVEN_REMOTE_USEINET_ENV_VAR_NAME , "true" );
220 }
221 final ArgumentListBuilder cmdLine = buildMavenAgentCmdLine( listener,acceptor.getPort());
222 String[] cmds = cmdLine.toCommandArray();
223 final Proc proc = launcher.launch().cmds(cmds).envs(envVars).stdout(mca).pwd(workDir).start();
224
225 Connection con;
226 try {
227 con = acceptor.accept();
228 } catch (SocketTimeoutException e) {
229 // failed to connect. Is the process dead?
230 // if so, the error should have been provided by the launcher already.
231 // so abort gracefully without a stack trace.
232 if(!proc.isAlive())
233 throw new AbortException("Failed to launch Maven. Exit code = "+proc.join());
234 throw e;
235 }
236
237 return new NewProcess(
238 Channels.forProcess("Channel to Maven "+ Arrays.toString(cmds),
239 Computer.threadPoolForRemoting, new BufferedInputStream(con.in), new BufferedOutputStream(con.out),
240 listener.getLogger(), proc),
241 proc);
242 } catch (IOException e) {
243 if(fixNull(e.getMessage()).contains("java: not found")) {
244 // diagnose issue #659
245 JDK jdk = mms.getJDK();
246 if(jdk==null)
247 throw new IOException2(mms.getDisplayName()+" is not configured with a JDK, but your PATH doesn't include Java",e);
248 }
249 throw e;
250 }
251 }
252
253 /**
254 * Builds the command line argument list to launch the maven process.
255 */
256 protected ArgumentListBuilder buildMavenAgentCmdLine(BuildListener listener,int tcpPort) throws IOException, InterruptedException {
257 MavenInstallation mvn = getMavenInstallation(listener);
258 if(mvn==null) {
259 listener.error("Maven version is not configured for this project. Can't determine which Maven to run");
260 throw new RunnerAbortedException();
261 }
262 if(mvn.getHome()==null) {
263 listener.error("Maven '%s' doesn't have its home set",mvn.getName());
264 throw new RunnerAbortedException();
265 }
266
267 boolean isMaster = getCurrentNode()== Jenkins.getInstance();
268 FilePath slaveRoot=null;
269 if(!isMaster)
270 slaveRoot = getCurrentNode().getRootPath();
271
272 ArgumentListBuilder args = new ArgumentListBuilder();
273 JDK jdk = getJava(listener);
274 if(jdk==null) {
275 args.add("java");
276 } else {
277 args.add(jdk.getHome()+"/bin/java"); // use JDK.getExecutable() here ?
278 }
279
280 if(debugPort!=0)
281 args.add("-Xrunjdwp:transport=dt_socket,server=y,address="+debugPort);
282 if(yjp)
283 args.add("-agentlib:yjpagent=tracing");
284
285 args.addTokenized(getMavenOpts());
286
287 args.add( "-cp" );
288 args.add(getMavenAgentClassPath(mvn,isMaster,slaveRoot,listener));
289 args.add(getMainClassName());
290
291 // M2_HOME
292 args.add(mvn.getHome());
293
294 // remoting.jar
295 String remotingJar = getLauncher().getChannel().call(new GetRemotingJar());
296 if(remotingJar==null) {// this shouldn't be possible, but there are still reports indicating this, so adding a probe here.
297 listener.error("Failed to determine the location of slave.jar");
298 throw new RunnerAbortedException();
299 }
300 args.add(remotingJar);
301
302 // interceptor.jar
303 args.add(getMavenInterceptorClassPath(mvn,isMaster,slaveRoot));
304
305 // TCP/IP port to establish the remoting infrastructure
306 args.add(tcpPort);
307
308 String interceptorOverride = getMavenInterceptorOverride(mvn,isMaster,slaveRoot);
309 if (interceptorOverride!=null) {
310 args.add(interceptorOverride);
311 }
312
313 return args;
314 }
315
316 /**
317 * Returns the classpath string for the maven-agent jar including classworlds
318 */
319 protected abstract String getMavenAgentClassPath(MavenInstallation mvn,boolean isMaster,FilePath slaveRoot,BuildListener listener) throws IOException, InterruptedException;
320
321 /**
322 * Returns the classpath string for the maven-interceptor jar
323 */
324 protected abstract String getMavenInterceptorClassPath(MavenInstallation mvn,boolean isMaster,FilePath slaveRoot) throws IOException, InterruptedException;
325
326 /**
327 * For Maven 2.1.x - 2.2.x we need an additional jar which overrides some classes in the other interceptor jar.
328 */
329 protected abstract String getMavenInterceptorOverride(MavenInstallation mvn,boolean isMaster,FilePath slaveRoot) throws IOException, InterruptedException;
330
331 /**
332 * Returns the name of the Maven main class.
333 */
334 protected abstract String getMainClassName();
335
336 public String getMavenOpts() {
337 if( this.mavenOpts != null )
338 return addRunHeadLessOption(this.mavenOpts);
339
340 String mavenOpts = mms.getMavenOpts();
341
342 if ((mavenOpts==null) || (mavenOpts.trim().length()==0)) {
343 Node n = getCurrentNode();
344 if (n!=null) {
345 try {
346 String localMavenOpts = n.toComputer().getEnvironment().get("MAVEN_OPTS");
347
348 if ((localMavenOpts!=null) && (localMavenOpts.trim().length()>0)) {
349 mavenOpts = localMavenOpts;
350 }
351 } catch (IOException e) {
352 } catch (InterruptedException e) {
353 // Don't do anything - this just means the slave isn't running, so we
354 // don't want to use its MAVEN_OPTS anyway.
355 }
356
357 }
358 }
359
360 mavenOpts = addRunHeadLessOption(mavenOpts);
361
362 return envVars.expand(mavenOpts);
363 }
364
365 protected String addRunHeadLessOption(String mavenOpts) {
366
367 if (mms.runHeadless()) {
368 // Configure headless process
369 if (mavenOpts == null) {
370 mavenOpts = "-Djava.awt.headless=true";
371 } else {
372 mavenOpts += " -Djava.awt.headless=true";
373 }
374 } else {
375 if (Platform.isDarwin()) {
376 // Would be cool to replace the generic Java icon with jenkins logo, but requires
377 // the file absolute path to be available on slave *before* the process run on it :-/
378 // Maybe we could enforce this from the DMG installer on OSX
379 // TODO mavenOpts += " -Xdock:name=Jenkins -Xdock:icon=jenkins.png";
380 }
381 }
382 return mavenOpts;
383 }
384
385
386 public MavenInstallation getMavenInstallation(TaskListener log) throws IOException, InterruptedException {
387 MavenInstallation mi = mms.getMaven();
388 if (mi != null) mi = mi.forNode(getCurrentNode(), log).forEnvironment(envVars);
389 return mi;
390
391 }
392
393 public JDK getJava(TaskListener log) throws IOException, InterruptedException {
394 JDK jdk = mms.getJDK();
395 if (jdk != null) jdk = jdk.forNode(getCurrentNode(), log).forEnvironment(envVars);
396 return jdk;
397 }
398
399
400 protected static final class GetRemotingJar implements Callable<String,IOException> {
401 private static final long serialVersionUID = 6022357183425911351L;
402
403 public String call() throws IOException {
404 return Which.jarFile(hudson.remoting.Launcher.class).getPath();
405 }
406 }
407
408 /**
409 * Returns the current {@link Node} on which we are buildling.
410 */
411 protected Node getCurrentNode() {
412 return Executor.currentExecutor().getOwner().getNode();
413 }
414
415
416 protected MavenModuleSet getMavenModuleSet() {
417 return mms;
418 }
419
420 protected Launcher getLauncher() {
421 return launcher;
422 }
423
424 protected EnvVars getEnvVars() {
425 return envVars;
426 }
427
428 public static boolean mavenRemoteUseInet = Boolean.getBoolean("maven.remote.useinet");
429
430 public static final String MAVEN_REMOTE_USEINET_ENV_VAR_NAME = "MAVEN_REMOTE_USEINET";
431
432 /**
433 * If true, launch Maven with YJP offline profiler agent.
434 */
435 public static boolean yjp = Boolean.getBoolean("hudson.maven.yjp");
436
437 /**
438 * If not 0, launch Maven with a debugger port.
439 */
440 public static int debugPort;
441
442 static {
443 String port = System.getProperty("hudson.maven.debugPort");
444 if(port!=null)
445 debugPort = Integer.parseInt(port);
446 }
447 }
448