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 javax.tools.DiagnosticListener;
22 import javax.tools.JavaCompiler;
23 import javax.tools.JavaFileObject;
24
25 import java.io.BufferedReader;
26 import java.io.BufferedWriter;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.Writer;
30 import java.lang.module.ModuleDescriptor;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.LinkedHashMap;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38
39 import org.apache.maven.api.JavaPathType;
40 import org.apache.maven.api.PathType;
41 import org.apache.maven.api.ProjectScope;
42
43 import static org.apache.maven.plugin.compiler.AbstractCompilerMojo.SUPPORT_LEGACY;
44
45
46
47
48
49
50
51 class ToolExecutorForTest extends ToolExecutor {
52
53
54
55
56
57
58 protected final Path mainOutputDirectory;
59
60
61
62
63 private final Path mainModulePath;
64
65
66
67
68
69
70
71
72
73
74
75 @Deprecated(since = "4.0.0")
76 private final boolean useModulePath;
77
78
79
80
81
82
83
84
85 @Deprecated(since = "4.0.0")
86 private final boolean hasTestModuleInfo;
87
88
89
90
91
92
93 private boolean testInItsOwnModule;
94
95
96
97
98
99 private boolean overwriteMainModuleInfo;
100
101
102
103
104
105
106
107
108
109 private String moduleName;
110
111
112
113
114
115 private boolean addedModuleOptions;
116
117
118
119
120
121
122
123
124
125
126
127 @Deprecated(since = "4.0.0")
128 private String directoryLevelToRemove;
129
130
131
132
133
134
135
136
137
138
139 @SuppressWarnings("deprecation")
140 ToolExecutorForTest(TestCompilerMojo mojo, DiagnosticListener<? super JavaFileObject> listener) throws IOException {
141 super(mojo, listener);
142 mainOutputDirectory = mojo.mainOutputDirectory;
143 mainModulePath = mojo.mainModulePath;
144 useModulePath = mojo.useModulePath;
145 hasTestModuleInfo = mojo.hasTestModuleInfo;
146
147
148
149
150 final var patchedModules = new LinkedHashMap<String, Set<Path>>();
151 for (SourceDirectory dir : sourceDirectories) {
152 String moduleToPatch = dir.moduleName;
153 if (moduleToPatch == null) {
154 moduleToPatch = getMainModuleName();
155 if (moduleToPatch.isEmpty()) {
156 continue;
157 }
158 if (SUPPORT_LEGACY) {
159 String testModuleName = mojo.getTestModuleName(sourceDirectories);
160 if (testModuleName != null) {
161 overwriteMainModuleInfo = testModuleName.equals(getMainModuleName());
162 if (!overwriteMainModuleInfo) {
163 testInItsOwnModule = true;
164 continue;
165 }
166 }
167 }
168 directoryLevelToRemove = moduleToPatch;
169 }
170 patchedModules.put(moduleToPatch, new LinkedHashSet<>());
171 }
172
173
174
175
176 mojo.getSourceRoots(ProjectScope.MAIN).forEach((root) -> {
177 root.module().ifPresent((moduleToPatch) -> {
178 Set<Path> paths = patchedModules.get(moduleToPatch);
179 if (paths != null) {
180 Path path = root.targetPath().orElseGet(() -> Path.of(moduleToPatch));
181 path = mainOutputDirectory.resolve(path);
182 paths.add(path);
183 }
184 });
185 });
186 patchedModules.values().removeIf(Set::isEmpty);
187 patchedModules.forEach((moduleToPatch, paths) -> {
188 dependencies(JavaPathType.patchModule(moduleToPatch)).addAll(paths);
189 });
190
191
192
193
194
195 if (patchedModules.isEmpty() && Files.exists(mainOutputDirectory)) {
196 PathType pathType = JavaPathType.CLASSES;
197 if (hasModuleDeclaration) {
198 pathType = JavaPathType.MODULES;
199 if (!testInItsOwnModule) {
200 String moduleToPatch = getMainModuleName();
201 if (!moduleToPatch.isEmpty()) {
202 pathType = JavaPathType.patchModule(moduleToPatch);
203 directoryLevelToRemove = moduleToPatch;
204 }
205 }
206 }
207 prependDependency(pathType, mainOutputDirectory);
208 }
209 }
210
211
212
213
214
215
216
217
218 private String getMainModuleName() throws IOException {
219 if (moduleName == null) {
220 if (mainModulePath != null) {
221 try (InputStream in = Files.newInputStream(mainModulePath)) {
222 moduleName = ModuleDescriptor.read(in).name();
223 }
224 } else {
225 moduleName = "";
226 }
227 }
228 return moduleName;
229 }
230
231
232
233
234 @Override
235 final String inferModuleNameIfMissing(String moduleName) throws IOException {
236 return (!testInItsOwnModule && moduleName.isEmpty()) ? getMainModuleName() : moduleName;
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250 @SuppressWarnings({"checkstyle:MissingSwitchDefault", "fallthrough"})
251 private void addModuleOptions(final Options configuration) throws IOException {
252 if (addedModuleOptions) {
253 return;
254 }
255 addedModuleOptions = true;
256 ModuleInfoPatch info = null;
257 ModuleInfoPatch defaultInfo = null;
258 final var patches = new LinkedHashMap<String, ModuleInfoPatch>();
259 for (SourceDirectory source : sourceDirectories) {
260 Path file = source.root.resolve(ModuleInfoPatch.FILENAME);
261 String module;
262 if (Files.notExists(file)) {
263 if (SUPPORT_LEGACY && useModulePath && hasTestModuleInfo && hasModuleDeclaration) {
264
265
266
267
268 continue;
269 }
270
271
272
273
274
275
276 module = source.moduleName;
277 if (module == null) {
278 module = getMainModuleName();
279 if (module.isEmpty()) {
280 continue;
281 }
282 }
283 if (defaultInfo != null) {
284 patches.putIfAbsent(module, null);
285 continue;
286 }
287 defaultInfo = new ModuleInfoPatch(module, info);
288 defaultInfo.setToDefaults();
289 info = defaultInfo;
290 } else {
291 info = new ModuleInfoPatch(getMainModuleName(), info);
292 try (BufferedReader reader = Files.newBufferedReader(file)) {
293 info.load(reader);
294 }
295 module = info.getModuleName();
296 }
297 if (patches.put(module, info) != null) {
298 throw new ModuleInfoPatchException("\"module-info-patch " + module + "\" is defined more than once.");
299 }
300 }
301
302
303
304
305
306 for (Map.Entry<String, ModuleInfoPatch> entry : patches.entrySet()) {
307 info = entry.getValue();
308 if (info != null) {
309 info.replaceProjectModules(sourceDirectories);
310 info.replaceTestModulePath(dependencyResolution);
311 } else {
312
313 entry.setValue(defaultInfo.patchWithSameReads(entry.getKey()));
314 }
315 }
316
317
318
319
320
321 if (!patches.isEmpty()) {
322 Path directory =
323 Files.createDirectories(outputDirectory.resolve("META-INF").resolve("maven"));
324 try (BufferedWriter out = Files.newBufferedWriter(directory.resolve("module-info-patch.args"))) {
325 for (ModuleInfoPatch m : patches.values()) {
326 m.writeTo(configuration, out);
327 }
328 }
329 }
330 }
331
332
333
334
335 @Override
336 public boolean applyIncrementalBuild(AbstractCompilerMojo mojo, Options configuration) throws IOException {
337 addModuleOptions(configuration);
338 return super.applyIncrementalBuild(mojo, configuration);
339 }
340
341
342
343
344 @Override
345 public boolean compile(JavaCompiler compiler, Options configuration, Writer otherOutput) throws IOException {
346 addModuleOptions(configuration);
347 try (var r = ModuleDirectoryRemover.create(outputDirectory, directoryLevelToRemove)) {
348 return super.compile(compiler, configuration, otherOutput);
349 }
350 }
351
352
353
354
355
356
357
358 @Override
359 final CompilationTaskSources[] toCompilationTasks(final SourcesForRelease unit) {
360 if (!(SUPPORT_LEGACY && useModulePath && hasTestModuleInfo && overwriteMainModuleInfo)) {
361 return super.toCompilationTasks(unit);
362 }
363 CompilationTaskSources moduleInfo = null;
364 final List<Path> files = unit.files;
365 for (int i = files.size(); --i >= 0; ) {
366 if (SourceDirectory.isModuleInfoSource(files.get(i))) {
367 moduleInfo = new CompilationTaskSources(List.of(files.remove(i)));
368 if (files.isEmpty()) {
369 return new CompilationTaskSources[] {moduleInfo};
370 }
371 break;
372 }
373 }
374 if (files.isEmpty()) {
375 return new CompilationTaskSources[0];
376 }
377 var task = new CompilationTaskSources(files) {
378
379
380
381
382
383 @Override
384 boolean compile(JavaCompiler.CompilationTask task) throws IOException {
385 try (unit) {
386 unit.substituteModuleInfos(mainOutputDirectory, outputDirectory);
387 return super.compile(task);
388 }
389 }
390 };
391 if (moduleInfo != null) {
392 return new CompilationTaskSources[] {moduleInfo, task};
393 } else {
394 return new CompilationTaskSources[] {task};
395 }
396 }
397 }