1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.compiler;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.stream.Collectors;
37 import java.util.stream.Stream;
38
39 import org.apache.maven.api.*;
40 import org.apache.maven.api.plugin.MojoException;
41 import org.apache.maven.api.plugin.annotations.Mojo;
42 import org.apache.maven.api.plugin.annotations.Parameter;
43 import org.apache.maven.api.services.MessageBuilderFactory;
44 import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
45 import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
46 import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
47 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
48 import org.codehaus.plexus.languages.java.jpms.LocationManager;
49 import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
50 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
51 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
52 import org.codehaus.plexus.util.StringUtils;
53
54
55
56
57
58
59
60
61
62
63
64 @Mojo(name = "compile", defaultPhase = "compile")
65 public class CompilerMojo extends AbstractCompilerMojo {
66
67
68
69 @Parameter
70 protected List<String> compileSourceRoots;
71
72
73
74
75 @Parameter(defaultValue = "${project.mainArtifact}", readonly = true, required = true)
76 protected Artifact projectArtifact;
77
78
79
80
81 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
82 protected Path outputDirectory;
83
84
85
86
87 @Parameter
88 protected Set<String> includes = new HashSet<>();
89
90
91
92
93 @Parameter
94 protected Set<String> excludes = new HashSet<>();
95
96
97
98
99
100 @Parameter
101 protected Set<String> incrementalExcludes = new HashSet<>();
102
103
104
105
106
107
108
109
110 @Parameter(defaultValue = "${project.build.directory}/generated-sources/annotations")
111 protected Path generatedSourcesDirectory;
112
113
114
115
116
117 @Parameter(property = "maven.main.skip")
118 protected boolean skipMain;
119
120 @Parameter
121 protected List<String> compilePath;
122
123
124
125
126
127
128
129
130
131
132
133
134
135 @Parameter
136 protected boolean multiReleaseOutput;
137
138
139
140
141
142
143 @Parameter(defaultValue = "javac")
144 protected String debugFileName;
145
146 final LocationManager locationManager = new LocationManager();
147
148 private List<String> classpathElements;
149
150 private List<String> modulepathElements;
151
152 private Map<String, JavaModuleDescriptor> pathElements;
153
154 protected List<Path> getCompileSourceRoots() {
155 if (compileSourceRoots == null || compileSourceRoots.isEmpty()) {
156 return projectManager.getCompileSourceRoots(getProject(), ProjectScope.MAIN);
157 } else {
158 return compileSourceRoots.stream().map(Paths::get).collect(Collectors.toList());
159 }
160 }
161
162 @Override
163 protected List<String> getClasspathElements() {
164 return classpathElements;
165 }
166
167 @Override
168 protected List<String> getModulepathElements() {
169 return modulepathElements;
170 }
171
172 @Override
173 protected Map<String, JavaModuleDescriptor> getPathElements() {
174 return pathElements;
175 }
176
177 protected Path getOutputDirectory() {
178 Path dir;
179 if (!multiReleaseOutput) {
180 dir = outputDirectory;
181 } else {
182 dir = outputDirectory.resolve("META-INF/versions/" + release);
183 }
184 return dir;
185 }
186
187 public void execute() throws MojoException {
188 if (skipMain) {
189 getLog().info("Not compiling main sources");
190 return;
191 }
192
193 if (multiReleaseOutput && release == null) {
194 throw new MojoException("When using 'multiReleaseOutput' the release must be set");
195 }
196
197 super.execute();
198
199 if (Files.isDirectory(outputDirectory) && projectArtifact != null) {
200 artifactManager.setPath(projectArtifact, outputDirectory);
201 }
202 }
203
204 @Override
205 protected Set<String> getIncludes() {
206 return includes;
207 }
208
209 @Override
210 protected Set<String> getExcludes() {
211 return excludes;
212 }
213
214 @Override
215 protected void preparePaths(Set<Path> sourceFiles) {
216
217 List<String> compilePath = this.compilePath;
218 if (compilePath == null) {
219 Stream<String> s1 = Stream.of(getOutputDirectory().toString());
220 Stream<String> s2 = session.resolveDependencies(getProject(), PathScope.MAIN_COMPILE).stream()
221 .map(Path::toString);
222 compilePath = Stream.concat(s1, s2).collect(Collectors.toList());
223 }
224
225 Path moduleDescriptorPath = null;
226
227 boolean hasModuleDescriptor = false;
228 for (Path sourceFile : sourceFiles) {
229 if ("module-info.java".equals(sourceFile.getFileName().toString())) {
230 moduleDescriptorPath = sourceFile;
231 hasModuleDescriptor = true;
232 break;
233 }
234 }
235
236 if (hasModuleDescriptor) {
237
238
239
240
241 modulepathElements = new ArrayList<>(compilePath.size());
242 classpathElements = new ArrayList<>(compilePath.size());
243 pathElements = new LinkedHashMap<>(compilePath.size());
244
245 ResolvePathsResult<File> resolvePathsResult;
246 try {
247 Collection<File> dependencyArtifacts = getCompileClasspathElements(getProject()).stream()
248 .map(Path::toFile)
249 .collect(Collectors.toList());
250
251 ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts)
252 .setIncludeStatic(true)
253 .setMainModuleDescriptor(moduleDescriptorPath.toFile());
254
255 Optional<Toolchain> toolchain = getToolchain();
256 if (toolchain.isPresent() && toolchain.get() instanceof JavaToolchain) {
257 request.setJdkHome(new File(((JavaToolchain) toolchain.get()).getJavaHome()));
258 }
259
260 resolvePathsResult = locationManager.resolvePaths(request);
261
262 for (Entry<File, Exception> pathException :
263 resolvePathsResult.getPathExceptions().entrySet()) {
264 Throwable cause = pathException.getValue();
265 while (cause.getCause() != null) {
266 cause = cause.getCause();
267 }
268 String fileName = pathException.getKey().getName();
269 getLog().warn("Can't extract module name from " + fileName + ": " + cause.getMessage());
270 }
271
272 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
273
274 detectFilenameBasedAutomodules(resolvePathsResult, moduleDescriptor);
275
276 for (Map.Entry<File, JavaModuleDescriptor> entry :
277 resolvePathsResult.getPathElements().entrySet()) {
278 pathElements.put(entry.getKey().getPath(), entry.getValue());
279 }
280
281 if (compilerArgs == null) {
282 compilerArgs = new ArrayList<>();
283 }
284
285 for (File file : resolvePathsResult.getClasspathElements()) {
286 classpathElements.add(file.getPath());
287
288 if (multiReleaseOutput) {
289 if (getOutputDirectory().startsWith(file.getPath())) {
290 compilerArgs.add("--patch-module");
291 compilerArgs.add(String.format("%s=%s", moduleDescriptor.name(), file.getPath()));
292 }
293 }
294 }
295
296 for (File file : resolvePathsResult.getModulepathElements().keySet()) {
297 modulepathElements.add(file.getPath());
298 }
299
300 compilerArgs.add("--module-version");
301 compilerArgs.add(getProject().getVersion());
302
303 } catch (IOException e) {
304 getLog().warn(e.getMessage());
305 }
306 } else {
307 classpathElements = new ArrayList<>();
308 for (Path element : getCompileClasspathElements(getProject())) {
309 classpathElements.add(element.toString());
310 }
311 modulepathElements = Collections.emptyList();
312 }
313 }
314
315 private void detectFilenameBasedAutomodules(
316 final ResolvePathsResult<File> resolvePathsResult, final JavaModuleDescriptor moduleDescriptor) {
317 List<String> automodulesDetected = new ArrayList<>();
318 for (Entry<File, ModuleNameSource> entry :
319 resolvePathsResult.getModulepathElements().entrySet()) {
320 if (ModuleNameSource.FILENAME.equals(entry.getValue())) {
321 automodulesDetected.add(entry.getKey().getName());
322 }
323 }
324
325 if (!automodulesDetected.isEmpty()) {
326 final String message = "Required filename-based automodules detected: "
327 + automodulesDetected + ". "
328 + "Please don't publish this project to a public artifact repository!";
329
330 if (moduleDescriptor.exports().isEmpty()) {
331
332 getLog().info(message);
333 } else {
334
335 writeBoxedWarning(message);
336 }
337 }
338 }
339
340 private List<Path> getCompileClasspathElements(Project project) {
341 List<Path> artifacts = session.resolveDependencies(project, PathScope.MAIN_COMPILE);
342
343
344 List<Path> list = new ArrayList<>(artifacts.size() + 3);
345
346 if (multiReleaseOutput) {
347 Path versionsFolder = outputDirectory.resolve("META-INF/versions");
348
349
350 for (int version = Integer.parseInt(getRelease()) - 1; version >= 9; version--) {
351 Path versionSubFolder = versionsFolder.resolve(String.valueOf(version));
352 if (Files.exists(versionSubFolder)) {
353 list.add(versionSubFolder);
354 }
355 }
356 }
357
358 list.add(outputDirectory);
359
360 list.addAll(artifacts);
361
362 return list;
363 }
364
365 protected SourceInclusionScanner getSourceInclusionScanner(int staleMillis) {
366 if (includes.isEmpty() && excludes.isEmpty() && incrementalExcludes.isEmpty()) {
367 return new StaleSourceScanner(staleMillis);
368 }
369
370 if (includes.isEmpty()) {
371 includes.add("**/*.java");
372 }
373
374 Set<String> excludesIncr = new HashSet<>(excludes);
375 excludesIncr.addAll(this.incrementalExcludes);
376 return new StaleSourceScanner(staleMillis, includes, excludesIncr);
377 }
378
379 protected SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding) {
380
381 String defaultIncludePattern = "**/*" + (inputFileEnding.startsWith(".") ? "" : ".") + inputFileEnding;
382
383 if (includes.isEmpty()) {
384 includes.add(defaultIncludePattern);
385 }
386 Set<String> excludesIncr = new HashSet<>(excludes);
387 excludesIncr.addAll(excludesIncr);
388 return new SimpleSourceInclusionScanner(includes, excludesIncr);
389 }
390
391 @Override
392 protected String getSource() {
393 return source;
394 }
395
396 @Override
397 protected String getTarget() {
398 return target;
399 }
400
401 @Override
402 protected String getRelease() {
403 return release;
404 }
405
406 @Override
407 protected String getCompilerArgument() {
408 return compilerArgument;
409 }
410
411 protected Path getGeneratedSourcesDirectory() {
412 return generatedSourcesDirectory;
413 }
414
415 @Override
416 protected String getDebugFileName() {
417 return debugFileName;
418 }
419
420 private void writeBoxedWarning(String message) {
421 String line = StringUtils.repeat("*", message.length() + 4);
422 getLog().warn(line);
423 getLog().warn("* " + strong(message) + " *");
424 getLog().warn(line);
425 }
426
427 private String strong(String message) {
428 return session.getService(MessageBuilderFactory.class)
429 .builder()
430 .strong(message)
431 .build();
432 }
433 }