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