1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.executor.forked;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.io.UncheckedIOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.concurrent.CountDownLatch;
34
35 import org.apache.maven.api.cli.Executor;
36 import org.apache.maven.api.cli.ExecutorException;
37 import org.apache.maven.api.cli.ExecutorRequest;
38
39 import static java.util.Objects.requireNonNull;
40 import static org.apache.maven.api.cli.ExecutorRequest.getCanonicalPath;
41
42
43
44
45
46 public class ForkedMavenExecutor implements Executor {
47 protected final boolean useMavenArgsEnv;
48
49 public ForkedMavenExecutor() {
50 this(true);
51 }
52
53 public ForkedMavenExecutor(boolean useMavenArgsEnv) {
54 this.useMavenArgsEnv = useMavenArgsEnv;
55 }
56
57 @Override
58 public int execute(ExecutorRequest executorRequest) throws ExecutorException {
59 requireNonNull(executorRequest);
60 validate(executorRequest);
61
62 return doExecute(executorRequest);
63 }
64
65 @Override
66 public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorException {
67 requireNonNull(executorRequest);
68 validate(executorRequest);
69 try {
70 Path cwd = Files.createTempDirectory("forked-executor-maven-version");
71 try {
72 ByteArrayOutputStream stdout = new ByteArrayOutputStream();
73 int exitCode = execute(executorRequest.toBuilder()
74 .cwd(cwd)
75 .arguments(List.of("--version", "--quiet"))
76 .stdOut(stdout)
77 .build());
78 if (exitCode == 0) {
79 if (stdout.size() > 0) {
80 return stdout.toString()
81 .replace("\n", "")
82 .replace("\r", "")
83 .trim();
84 }
85 return UNKNOWN_VERSION;
86 } else {
87 throw new ExecutorException(
88 "Maven version query unexpected exitCode=" + exitCode + "\nLog: " + stdout);
89 }
90 } finally {
91 Files.deleteIfExists(cwd);
92 }
93 } catch (IOException e) {
94 throw new ExecutorException("Failed to determine maven version", e);
95 }
96 }
97
98 protected void validate(ExecutorRequest executorRequest) throws ExecutorException {}
99
100 protected int doExecute(ExecutorRequest executorRequest) throws ExecutorException {
101 ArrayList<String> cmdAndArguments = new ArrayList<>();
102 cmdAndArguments.add(executorRequest
103 .installationDirectory()
104 .resolve("bin")
105 .resolve(IS_WINDOWS ? executorRequest.command() + ".cmd" : executorRequest.command())
106 .toString());
107
108 String mavenArgsEnv = System.getenv("MAVEN_ARGS");
109 if (useMavenArgsEnv && mavenArgsEnv != null && !mavenArgsEnv.isEmpty()) {
110 Arrays.stream(mavenArgsEnv.split(" "))
111 .filter(s -> !s.trim().isEmpty())
112 .forEach(cmdAndArguments::add);
113 }
114
115 cmdAndArguments.addAll(executorRequest.arguments());
116
117 ArrayList<String> jvmArgs = new ArrayList<>();
118 if (!executorRequest.userHomeDirectory().equals(getCanonicalPath(Paths.get(System.getProperty("user.home"))))) {
119 jvmArgs.add("-Duser.home=" + executorRequest.userHomeDirectory().toString());
120 }
121 if (executorRequest.jvmArguments().isPresent()) {
122 jvmArgs.addAll(executorRequest.jvmArguments().get());
123 }
124 if (executorRequest.jvmSystemProperties().isPresent()) {
125 jvmArgs.addAll(executorRequest.jvmSystemProperties().get().entrySet().stream()
126 .map(e -> "-D" + e.getKey() + "=" + e.getValue())
127 .toList());
128 }
129
130 HashMap<String, String> env = new HashMap<>();
131 if (executorRequest.environmentVariables().isPresent()) {
132 env.putAll(executorRequest.environmentVariables().get());
133 }
134 if (!jvmArgs.isEmpty()) {
135 String mavenOpts = env.getOrDefault("MAVEN_OPTS", "");
136 if (!mavenOpts.isEmpty()) {
137 mavenOpts += " ";
138 }
139 mavenOpts += String.join(" ", jvmArgs);
140 env.put("MAVEN_OPTS", mavenOpts);
141 }
142 env.remove("MAVEN_ARGS");
143
144 try {
145 ProcessBuilder pb = new ProcessBuilder()
146 .directory(executorRequest.cwd().toFile())
147 .command(cmdAndArguments);
148 if (!env.isEmpty()) {
149 pb.environment().putAll(env);
150 }
151
152 Process process = pb.start();
153 pump(process, executorRequest).await();
154 return process.waitFor();
155 } catch (IOException e) {
156 throw new ExecutorException("IO problem while executing command: " + cmdAndArguments, e);
157 } catch (InterruptedException e) {
158 throw new ExecutorException("Interrupted while executing command: " + cmdAndArguments, e);
159 }
160 }
161
162 protected CountDownLatch pump(Process p, ExecutorRequest executorRequest) {
163 CountDownLatch latch = new CountDownLatch(3);
164 String suffix = "-pump-" + p.pid();
165 Thread stdoutPump = new Thread(() -> {
166 try {
167 OutputStream stdout = executorRequest.stdOut().orElse(OutputStream.nullOutputStream());
168 p.getInputStream().transferTo(stdout);
169 stdout.flush();
170 } catch (IOException e) {
171 throw new UncheckedIOException(e);
172 } finally {
173 latch.countDown();
174 }
175 });
176 stdoutPump.setName("stdout" + suffix);
177 stdoutPump.start();
178 Thread stderrPump = new Thread(() -> {
179 try {
180 OutputStream stderr = executorRequest.stdErr().orElse(OutputStream.nullOutputStream());
181 p.getErrorStream().transferTo(stderr);
182 stderr.flush();
183 } catch (IOException e) {
184 throw new UncheckedIOException(e);
185 } finally {
186 latch.countDown();
187 }
188 });
189 stderrPump.setName("stderr" + suffix);
190 stderrPump.start();
191 Thread stdinPump = new Thread(() -> {
192 try {
193 OutputStream in = p.getOutputStream();
194 executorRequest.stdIn().orElse(InputStream.nullInputStream()).transferTo(in);
195 in.flush();
196 } catch (IOException e) {
197 throw new UncheckedIOException(e);
198 } finally {
199 latch.countDown();
200 }
201 });
202 stdinPump.setName("stdin" + suffix);
203 stdinPump.start();
204 return latch;
205 }
206 }