1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.plugins.invoker;
20  
21  import java.io.BufferedWriter;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.Reader;
30  import java.io.Writer;
31  import java.nio.charset.Charset;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.nio.file.Paths;
35  import java.text.MessageFormat;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.Collections;
40  import java.util.HashMap;
41  import java.util.HashSet;
42  import java.util.LinkedHashMap;
43  import java.util.LinkedHashSet;
44  import java.util.List;
45  import java.util.Locale;
46  import java.util.Map;
47  import java.util.Objects;
48  import java.util.Properties;
49  import java.util.Set;
50  import java.util.TreeSet;
51  import java.util.stream.Collectors;
52  
53  import org.apache.maven.artifact.Artifact;
54  import org.apache.maven.execution.MavenSession;
55  import org.apache.maven.model.Model;
56  import org.apache.maven.plugin.AbstractMojo;
57  import org.apache.maven.plugin.MojoExecution;
58  import org.apache.maven.plugin.MojoExecutionException;
59  import org.apache.maven.plugin.MojoFailureException;
60  import org.apache.maven.plugins.annotations.Component;
61  import org.apache.maven.plugins.annotations.Parameter;
62  import org.apache.maven.plugins.invoker.model.BuildJob;
63  import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Writer;
64  import org.apache.maven.project.MavenProject;
65  import org.apache.maven.settings.Settings;
66  import org.apache.maven.settings.SettingsUtils;
67  import org.apache.maven.settings.TrackableBase;
68  import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
69  import org.apache.maven.settings.building.SettingsBuilder;
70  import org.apache.maven.settings.building.SettingsBuildingException;
71  import org.apache.maven.settings.building.SettingsBuildingRequest;
72  import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
73  import org.apache.maven.shared.invoker.CommandLineConfigurationException;
74  import org.apache.maven.shared.invoker.DefaultInvocationRequest;
75  import org.apache.maven.shared.invoker.InvocationRequest;
76  import org.apache.maven.shared.invoker.InvocationResult;
77  import org.apache.maven.shared.invoker.Invoker;
78  import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
79  import org.apache.maven.shared.invoker.MavenInvocationException;
80  import org.apache.maven.shared.scriptinterpreter.ScriptException;
81  import org.apache.maven.shared.scriptinterpreter.ScriptReturnException;
82  import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
83  import org.apache.maven.shared.utils.logging.MessageBuilder;
84  import org.apache.maven.toolchain.MisconfiguredToolchainException;
85  import org.apache.maven.toolchain.ToolchainManagerPrivate;
86  import org.apache.maven.toolchain.ToolchainPrivate;
87  import org.codehaus.plexus.interpolation.InterpolationException;
88  import org.codehaus.plexus.interpolation.Interpolator;
89  import org.codehaus.plexus.interpolation.MapBasedValueSource;
90  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
91  import org.codehaus.plexus.util.DirectoryScanner;
92  import org.codehaus.plexus.util.FileUtils;
93  import org.codehaus.plexus.util.IOUtil;
94  import org.codehaus.plexus.util.InterpolationFilterReader;
95  import org.codehaus.plexus.util.NioFiles;
96  import org.codehaus.plexus.util.ReflectionUtils;
97  import org.codehaus.plexus.util.cli.CommandLineException;
98  import org.codehaus.plexus.util.cli.CommandLineUtils;
99  import org.codehaus.plexus.util.cli.Commandline;
100 import org.codehaus.plexus.util.cli.StreamConsumer;
101 import org.codehaus.plexus.util.xml.XmlStreamReader;
102 import org.codehaus.plexus.util.xml.XmlStreamWriter;
103 import org.codehaus.plexus.util.xml.Xpp3Dom;
104 import org.codehaus.plexus.util.xml.Xpp3DomWriter;
105 
106 import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
107 
108 
109 
110 
111 
112 
113 
114 public abstract class AbstractInvokerMojo extends AbstractMojo {
115     private static final float ONE_SECOND = 1000.0f;
116 
117     
118 
119 
120     private static final int RESULT_COLUMN = 60;
121 
122     
123 
124 
125 
126 
127     @Parameter(property = "invoker.skip", defaultValue = "false")
128     private boolean skipInvocation;
129 
130     
131 
132 
133 
134 
135 
136 
137 
138     @Parameter(defaultValue = "false")
139     protected boolean suppressSummaries;
140 
141     
142 
143 
144 
145 
146     @Parameter(property = "invoker.streamLogs", defaultValue = "false")
147     private boolean streamLogs;
148 
149     
150 
151 
152 
153 
154 
155 
156     @Parameter(property = "invoker.localRepositoryPath", defaultValue = "${settings.localRepository}")
157     private File localRepositoryPath;
158 
159     
160 
161 
162 
163 
164     @Parameter(property = "invoker.projectsDirectory", defaultValue = "${basedir}/src/it/")
165     private File projectsDirectory;
166 
167     
168 
169 
170 
171 
172 
173 
174     @Parameter(property = "invoker.reportsDirectory", defaultValue = "${project.build.directory}/invoker-reports")
175     private File reportsDirectory;
176 
177     
178 
179 
180 
181 
182     @Parameter(property = "invoker.disableReports", defaultValue = "false")
183     private boolean disableReports;
184 
185     
186 
187 
188 
189 
190 
191 
192 
193 
194     @Parameter(property = "invoker.cloneProjectsTo")
195     private File cloneProjectsTo;
196 
197     
198     
199 
200 
201 
202 
203 
204 
205 
206 
207 
208     @Parameter(defaultValue = "false")
209     private boolean cloneAllFiles;
210     
211 
212     
213 
214 
215 
216 
217     @Parameter(defaultValue = "true")
218     private boolean cloneClean;
219 
220     
221 
222 
223 
224 
225     @Parameter(property = "invoker.pom")
226     private File pom;
227 
228     
229 
230 
231 
232 
233 
234 
235 
236 
237 
238 
239 
240     @Parameter
241     private List<String> pomIncludes = Collections.singletonList("*/pom.xml");
242 
243     
244 
245 
246 
247 
248 
249 
250     @Parameter
251     private List<String> pomExcludes = Collections.emptyList();
252 
253     
254 
255 
256 
257 
258 
259 
260 
261 
262     @Parameter
263     private List<String> setupIncludes = Collections.singletonList("setup*/pom.xml");
264 
265     
266 
267 
268 
269 
270     @Parameter
271     private List<String> goals = Collections.singletonList("package");
272 
273     
274 
275 
276 
277 
278 
279 
280 
281 
282 
283 
284 
285     @Parameter(property = "invoker.selectorScript", defaultValue = "selector")
286     private String selectorScript;
287 
288     
289 
290 
291 
292 
293 
294 
295 
296 
297 
298     @Parameter(property = "invoker.preBuildHookScript", defaultValue = "prebuild")
299     private String preBuildHookScript;
300 
301     
302 
303 
304 
305 
306 
307 
308 
309 
310     @Parameter(property = "invoker.postBuildHookScript", defaultValue = "postbuild")
311     private String postBuildHookScript;
312 
313     
314 
315 
316 
317 
318     @Parameter(property = "invoker.testPropertiesFile", defaultValue = "test.properties")
319     private String testPropertiesFile;
320 
321     
322 
323 
324 
325 
326     @Parameter
327     private Map<String, String> properties;
328 
329     
330 
331 
332 
333 
334     @Parameter(property = "invoker.showErrors", defaultValue = "false")
335     private boolean showErrors;
336 
337     
338 
339 
340 
341 
342     @Parameter(property = "invoker.debug", defaultValue = "false")
343     private boolean debug;
344 
345     
346 
347 
348 
349 
350     @Parameter(property = "invoker.quiet", defaultValue = "false")
351     private boolean quiet;
352 
353     
354 
355 
356 
357 
358     @Parameter(property = "invoker.noLog", defaultValue = "false")
359     private boolean noLog;
360 
361     
362 
363 
364 
365 
366 
367     @Parameter
368     private File logDirectory;
369 
370     
371 
372 
373 
374 
375     @Parameter
376     private List<String> profiles;
377 
378     
379 
380 
381 
382 
383     @Parameter
384     private Map<String, String> filterProperties;
385 
386     
387 
388 
389 
390 
391 
392 
393 
394 
395 
396 
397     @Parameter(property = "invoker.test")
398     private String invokerTest;
399 
400     
401 
402 
403 
404 
405 
406 
407     @Parameter(property = "invoker.settingsFile")
408     private File settingsFile;
409 
410     
411 
412 
413 
414 
415 
416 
417 
418 
419 
420     @Parameter(property = "invoker.mavenOpts")
421     private String mavenOpts;
422 
423     
424 
425 
426 
427 
428 
429     @Parameter(property = "invoker.mavenHome")
430     private File mavenHome;
431 
432     
433 
434 
435 
436 
437 
438     @Parameter(property = "invoker.mavenExecutable")
439     private String mavenExecutable;
440 
441     
442 
443 
444 
445 
446 
447     @Parameter(property = "invoker.javaHome")
448     private File javaHome;
449 
450     
451 
452 
453 
454 
455     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
456     private String encoding;
457 
458     
459 
460 
461 
462 
463 
464 
465 
466 
467     @Parameter(property = "invoker.addTestClassPath", defaultValue = "false")
468     private boolean addTestClassPath;
469 
470     
471 
472 
473 
474 
475 
476 
477 
478 
479 
480 
481 
482 
483 
484 
485 
486 
487 
488 
489 
490 
491 
492 
493 
494 
495 
496 
497 
498 
499 
500 
501 
502 
503 
504 
505 
506 
507 
508 
509 
510 
511 
512 
513 
514 
515 
516 
517 
518 
519 
520 
521 
522 
523 
524 
525 
526 
527 
528 
529 
530 
531 
532 
533 
534 
535 
536 
537 
538 
539 
540 
541 
542 
543 
544 
545 
546 
547 
548 
549 
550 
551 
552 
553 
554 
555 
556 
557 
558 
559 
560 
561 
562 
563 
564 
565 
566 
567 
568 
569 
570 
571 
572 
573 
574 
575 
576 
577 
578 
579 
580 
581 
582 
583 
584 
585 
586 
587 
588 
589 
590 
591 
592 
593 
594 
595 
596 
597 
598 
599 
600 
601 
602 
603 
604 
605 
606 
607 
608 
609 
610 
611     @Parameter(property = "invoker.invokerPropertiesFile", defaultValue = "invoker.properties")
612     private String invokerPropertiesFile;
613 
614     
615 
616 
617 
618 
619     @Parameter(property = "invoker.showVersion", defaultValue = "false")
620     private boolean showVersion;
621 
622     
623 
624 
625 
626 
627 
628 
629 
630 
631     @Parameter(property = "invoker.parallelThreads", defaultValue = "1")
632     private String parallelThreads;
633 
634     
635 
636 
637 
638 
639 
640     @Parameter(property = "invoker.mergeUserSettings", defaultValue = "false")
641     private boolean mergeUserSettings;
642 
643     
644 
645 
646 
647 
648     @Parameter
649     private Map<String, String> environmentVariables;
650 
651     
652 
653 
654 
655 
656     @Parameter
657     private Map<String, String> scriptVariables;
658 
659     
660 
661 
662 
663     @Parameter(defaultValue = "0", property = "invoker.timeoutInSeconds")
664     private int timeoutInSeconds;
665 
666     
667 
668 
669 
670     @Parameter(defaultValue = "false", property = "invoker.writeJunitReport")
671     private boolean writeJunitReport;
672 
673     
674 
675 
676 
677     @Parameter(defaultValue = "maven.invoker.it", property = "invoker.junitPackageName")
678     private String junitPackageName = "maven.invoker.it";
679 
680     
681 
682 
683 
684 
685 
686     @Parameter(defaultValue = "false", property = "invoker.updateOnly")
687     private boolean updateOnly = false;
688 
689     
690 
691 
692 
693 
694 
695     @Parameter(defaultValue = "false", property = "invoker.updateSnapshots")
696     private boolean updateSnapshots;
697 
698     
699 
700     
701 
702 
703     private ScriptRunner scriptRunner;
704 
705     
706 
707 
708 
709 
710     private String filteredPomPrefix = "interpolated-";
711 
712     
713 
714 
715     private String actualMavenVersion;
716 
717     
718 
719     @Parameter(property = "plugin.artifacts", required = true, readonly = true)
720     private List<Artifact> pluginArtifacts;
721 
722     @Parameter(defaultValue = "${project.testClasspathElements}", readonly = true)
723     private List<String> testClassPath;
724 
725     @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
726     private MojoExecution mojoExecution;
727 
728     @Parameter(defaultValue = "${project}", readonly = true, required = true)
729     private MavenProject project;
730 
731     @Parameter(defaultValue = "${session}", readonly = true, required = true)
732     private MavenSession session;
733 
734     @Parameter(defaultValue = "${settings}", readonly = true, required = true)
735     private Settings settings;
736 
737     @Component
738     private Invoker invoker;
739 
740     @Component
741     private SettingsBuilder settingsBuilder;
742 
743     @Component
744     private ToolchainManagerPrivate toolchainManagerPrivate;
745 
746     @Component
747     private InterpolatorUtils interpolatorUtils;
748 
749     
750 
751 
752 
753 
754 
755     public void execute() throws MojoExecutionException, MojoFailureException {
756         if (skipInvocation) {
757             getLog().info("Skipping invocation per configuration."
758                     + " If this is incorrect, ensure the skipInvocation parameter is not set to true.");
759             return;
760         }
761 
762         if (encoding == null || encoding.isEmpty()) {
763             getLog().warn("File encoding has not been set, using platform encoding "
764                     + Charset.defaultCharset().displayName() + ", i.e. build is platform dependent!");
765         }
766 
767         
768         if (!disableReports) {
769             setupReportsFolder();
770         }
771 
772         List<BuildJob> buildJobs;
773         if (pom == null) {
774             try {
775                 buildJobs = getBuildJobs();
776             } catch (final IOException e) {
777                 throw new MojoExecutionException(
778                         "Error retrieving POM list from includes, " + "excludes, and projects directory. Reason: "
779                                 + e.getMessage(),
780                         e);
781             }
782         } else {
783             try {
784                 projectsDirectory = pom.getCanonicalFile().getParentFile();
785             } catch (IOException e) {
786                 throw new MojoExecutionException(
787                         "Failed to discover projectsDirectory from " + "pom File parameter. Reason: " + e.getMessage(),
788                         e);
789             }
790 
791             buildJobs = Collections.singletonList(new BuildJob(pom.getName()));
792         }
793 
794         if (buildJobs.isEmpty()) {
795             doFailIfNoProjects();
796 
797             getLog().info("No projects were selected for execution.");
798             return;
799         }
800 
801         setupActualMavenVersion();
802 
803         handleScriptRunnerWithScriptClassPath();
804 
805         Collection<String> collectedProjects = new LinkedHashSet<>();
806         for (BuildJob buildJob : buildJobs) {
807             collectProjects(projectsDirectory, buildJob.getProject(), collectedProjects, true);
808         }
809 
810         File projectsDir = projectsDirectory;
811 
812         if (cloneProjectsTo == null && "maven-plugin".equals(project.getPackaging())) {
813             cloneProjectsTo = new File(project.getBuild().getDirectory(), "its");
814         }
815 
816         if (updateOnly) {
817             if (cloneProjectsTo == null) {
818                 getLog().warn("updateOnly functionality is not supported without cloning the projects");
819             } else if (lastModifiedRecursive(projectsDirectory) <= lastModifiedRecursive(cloneProjectsTo)) {
820                 getLog().debug("Skipping invocation as cloned projects are up-to-date "
821                         + "and updateOnly parameter is set to true.");
822                 return;
823             } else {
824                 getLog().debug("Cloned projects are out of date");
825             }
826         }
827 
828         if (cloneProjectsTo != null) {
829             cloneProjects(collectedProjects);
830             addMissingDotMvnDirectory(cloneProjectsTo, buildJobs);
831             projectsDir = cloneProjectsTo;
832         } else {
833             getLog().warn("Filtering of parent/child POMs is not supported without cloning the projects");
834         }
835 
836         
837         List<BuildJob> setupBuildJobs = getSetupJobs(buildJobs);
838 
839         if (!setupBuildJobs.isEmpty()) {
840             
841             
842             
843             getLog().info("Running " + setupBuildJobs.size() + " setup job" + ((setupBuildJobs.size() < 2) ? "" : "s")
844                     + ":");
845             runBuilds(projectsDir, setupBuildJobs, 1);
846             getLog().info("Setup done.");
847         }
848 
849         List<BuildJob> nonSetupBuildJobs = getNonSetupJobs(buildJobs);
850 
851         if (setupBuildJobs.isEmpty() || setupBuildJobs.stream().allMatch(BuildJob::isNotError)) {
852             
853             
854             runBuilds(projectsDir, nonSetupBuildJobs, getParallelThreadsCount());
855         } else {
856             for (BuildJob buildJob : nonSetupBuildJobs) {
857                 buildJob.setResult(BuildJob.Result.SKIPPED);
858                 buildJob.setFailureMessage("Skipped due to setup job(s) failure");
859                 writeBuildReport(buildJob);
860             }
861         }
862 
863         writeSummaryFile(buildJobs);
864         processResults(new InvokerSession(buildJobs));
865     }
866 
867     
868 
869 
870 
871 
872 
873     private void addMissingDotMvnDirectory(File projectsDir, List<BuildJob> buildJobs) throws MojoExecutionException {
874         for (BuildJob buildJob : buildJobs) {
875             Path projectPath = projectsDir.toPath().resolve(buildJob.getProject());
876 
877             if (Files.isRegularFile(projectPath)) {
878                 projectPath = projectPath.getParent();
879             }
880 
881             Path mvnDotPath = projectPath.resolve(".mvn");
882             if (!Files.exists(mvnDotPath)) {
883                 try {
884                     Files.createDirectories(mvnDotPath);
885                 } catch (IOException e) {
886                     throw new MojoExecutionException(e.getMessage(), e);
887                 }
888             }
889         }
890     }
891 
892     private void setupActualMavenVersion() throws MojoExecutionException {
893         if (mavenHome != null) {
894             try {
895                 actualMavenVersion = SelectorUtils.getMavenVersion(mavenHome);
896             } catch (IOException e) {
897                 throw new MojoExecutionException(e.getMessage(), e);
898             }
899         } else {
900             actualMavenVersion = SelectorUtils.getMavenVersion();
901         }
902     }
903 
904     
905 
906 
907 
908 
909 
910     private long lastModifiedRecursive(File file) {
911         long lastModified = file.lastModified();
912 
913         final File[] entries = file.listFiles();
914 
915         if (entries != null) {
916             for (File entry : entries) {
917                 lastModified = Math.max(lastModified, lastModifiedRecursive(entry));
918             }
919         }
920 
921         return lastModified;
922     }
923 
924     
925 
926 
927 
928 
929     private void setupReportsFolder() throws MojoExecutionException {
930         
931         if (reportsDirectory.exists()) {
932             try {
933                 FileUtils.deleteDirectory(reportsDirectory);
934             } catch (IOException e) {
935                 throw new MojoExecutionException(
936                         "Failure while trying to delete " + reportsDirectory.getAbsolutePath(), e);
937             }
938         }
939         if (!reportsDirectory.mkdirs()) {
940             throw new MojoExecutionException("Failure while creating the " + reportsDirectory.getAbsolutePath());
941         }
942     }
943 
944     private List<BuildJob> getSetupJobs(List<BuildJob> buildJobs) {
945         return buildJobs.stream()
946                 .filter(buildJob -> buildJob.getType().equals(BuildJob.Type.SETUP))
947                 .collect(Collectors.toList());
948     }
949 
950     private List<BuildJob> getNonSetupJobs(List<BuildJob> buildJobs) {
951         return buildJobs.stream()
952                 .filter(buildJob -> !buildJob.getType().equals(BuildJob.Type.SETUP))
953                 .collect(Collectors.toList());
954     }
955 
956     private void handleScriptRunnerWithScriptClassPath() {
957         final List<String> scriptClassPath;
958         if (addTestClassPath) {
959             scriptClassPath = new ArrayList<>(testClassPath);
960             for (Artifact pluginArtifact : pluginArtifacts) {
961                 scriptClassPath.remove(pluginArtifact.getFile().getAbsolutePath());
962             }
963         } else {
964             scriptClassPath = null;
965         }
966         scriptRunner = new ScriptRunner();
967         scriptRunner.setScriptEncoding(encoding);
968         scriptRunner.setGlobalVariable("localRepositoryPath", localRepositoryPath);
969         scriptRunner.setGlobalVariable("mavenVersion", actualMavenVersion);
970         if (scriptVariables != null) {
971             scriptVariables.forEach((key, value) -> scriptRunner.setGlobalVariable(key, value));
972         }
973         scriptRunner.setClassPath(scriptClassPath);
974     }
975 
976     private void writeSummaryFile(List<BuildJob> buildJobs) throws MojoExecutionException {
977 
978         File summaryReportFile = new File(reportsDirectory, "invoker-summary.txt");
979 
980         try (Writer writer = new BufferedWriter(new FileWriter(summaryReportFile))) {
981             for (BuildJob buildJob : buildJobs) {
982                 if (!BuildJob.Result.SUCCESS.equals(buildJob.getResult())) {
983                     writer.append(buildJob.getResult());
984                     writer.append(" [");
985                     writer.append(buildJob.getProject());
986                     writer.append("] ");
987                     if (buildJob.getFailureMessage() != null) {
988                         writer.append(" ");
989                         writer.append(buildJob.getFailureMessage());
990                     }
991                     writer.append("\n");
992                 }
993             }
994         } catch (IOException e) {
995             throw new MojoExecutionException("Failed to write summary report " + summaryReportFile, e);
996         }
997     }
998 
999     protected void doFailIfNoProjects() throws MojoFailureException {
1000         
1001     }
1002 
1003     
1004 
1005 
1006 
1007 
1008 
1009 
1010     abstract void processResults(InvokerSession invokerSession) throws MojoFailureException;
1011 
1012     
1013 
1014 
1015 
1016 
1017 
1018 
1019 
1020 
1021 
1022 
1023 
1024 
1025 
1026     private void collectProjects(
1027             File projectsDir, String projectPath, Collection<String> projectPaths, boolean included)
1028             throws MojoExecutionException {
1029         projectPath = projectPath.replace('\\', '/');
1030         File pomFile = new File(projectsDir, projectPath);
1031         if (pomFile.isDirectory()) {
1032             pomFile = new File(pomFile, "pom.xml");
1033             if (!pomFile.exists()) {
1034                 if (included) {
1035                     projectPaths.add(projectPath);
1036                 }
1037                 return;
1038             }
1039             if (!projectPath.endsWith("/")) {
1040                 projectPath += '/';
1041             }
1042             projectPath += "pom.xml";
1043         } else if (!pomFile.isFile()) {
1044             return;
1045         }
1046         if (!projectPaths.add(projectPath)) {
1047             return;
1048         }
1049         getLog().debug("Collecting parent/child projects of " + projectPath);
1050 
1051         Model model = PomUtils.loadPom(pomFile);
1052 
1053         try {
1054             String projectsRoot = projectsDir.getCanonicalPath();
1055             String projectDir = pomFile.getParent();
1056 
1057             String parentPath = "../pom.xml";
1058             if (model.getParent() != null && isNotEmpty(model.getParent().getRelativePath())) {
1059                 parentPath = model.getParent().getRelativePath();
1060             }
1061             String parent = relativizePath(new File(projectDir, parentPath), projectsRoot);
1062             if (parent != null) {
1063                 collectProjects(projectsDir, parent, projectPaths, false);
1064             }
1065 
1066             Collection<String> modulePaths = new LinkedHashSet<>(model.getModules());
1067 
1068             model.getProfiles().forEach(profile -> modulePaths.addAll(profile.getModules()));
1069 
1070             for (String modulePath : modulePaths) {
1071                 String module = relativizePath(new File(projectDir, modulePath), projectsRoot);
1072                 if (module != null) {
1073                     collectProjects(projectsDir, module, projectPaths, false);
1074                 }
1075             }
1076         } catch (IOException e) {
1077             throw new MojoExecutionException("Failed to analyze POM: " + pomFile, e);
1078         }
1079     }
1080 
1081     private boolean isNotEmpty(String s) {
1082         return Objects.nonNull(s) && !s.isEmpty();
1083     }
1084 
1085     
1086 
1087 
1088 
1089 
1090 
1091 
1092 
1093     private void cloneProjects(Collection<String> projectPaths) throws MojoExecutionException {
1094         if (!cloneProjectsTo.mkdirs() && cloneClean) {
1095             try {
1096                 FileUtils.cleanDirectory(cloneProjectsTo);
1097             } catch (IOException e) {
1098                 throw new MojoExecutionException(
1099                         "Could not clean the cloneProjectsTo directory. Reason: " + e.getMessage(), e);
1100             }
1101         }
1102 
1103         
1104         Collection<String> dirs = new LinkedHashSet<>();
1105         for (String projectPath : projectPaths) {
1106             if (!new File(projectsDirectory, projectPath).isDirectory()) {
1107                 projectPath = getParentPath(projectPath);
1108             }
1109             dirs.add(projectPath);
1110         }
1111 
1112         boolean filter;
1113 
1114         
1115         try {
1116             filter = !cloneProjectsTo.getCanonicalFile().equals(projectsDirectory.getCanonicalFile());
1117 
1118             List<String> clonedSubpaths = new ArrayList<>();
1119 
1120             for (String subpath : dirs) {
1121                 
1122                 if (!".".equals(subpath) && dirs.contains(getParentPath(subpath))) {
1123                     continue;
1124                 }
1125 
1126                 
1127                 if (!alreadyCloned(subpath, clonedSubpaths)) {
1128                     
1129                     if (".".equals(subpath)) {
1130                         String cloneSubdir = relativizePath(cloneProjectsTo, projectsDirectory.getCanonicalPath());
1131 
1132                         
1133                         if (cloneSubdir != null) {
1134                             File temp = Files.createTempDirectory("pre-invocation-clone.")
1135                                     .toFile();
1136 
1137                             copyDirectoryStructure(projectsDirectory, temp);
1138 
1139                             FileUtils.deleteDirectory(new File(temp, cloneSubdir));
1140 
1141                             copyDirectoryStructure(temp, cloneProjectsTo);
1142                         } else {
1143                             copyDirectoryStructure(projectsDirectory, cloneProjectsTo);
1144                         }
1145                     } else {
1146                         File srcDir = new File(projectsDirectory, subpath);
1147                         File dstDir = new File(cloneProjectsTo, subpath);
1148                         copyDirectoryStructure(srcDir, dstDir);
1149                     }
1150 
1151                     clonedSubpaths.add(subpath);
1152                 }
1153             }
1154         } catch (IOException e) {
1155             throw new MojoExecutionException(
1156                     "Failed to clone projects from: " + projectsDirectory + " to: " + cloneProjectsTo + ". Reason: "
1157                             + e.getMessage(),
1158                     e);
1159         }
1160 
1161         
1162         if (filter) {
1163             for (String projectPath : projectPaths) {
1164                 File pomFile = new File(cloneProjectsTo, projectPath);
1165                 if (pomFile.isFile()) {
1166                     buildInterpolatedFile(pomFile, pomFile);
1167                 }
1168 
1169                 
1170                 
1171                 
1172                 File baseDir = pomFile.getParentFile();
1173                 File mvnDir = new File(baseDir, ".mvn");
1174                 if (mvnDir.isDirectory()) {
1175                     File extensionsFile = new File(mvnDir, "extensions.xml");
1176                     if (extensionsFile.isFile()) {
1177                         buildInterpolatedFile(extensionsFile, extensionsFile);
1178                     }
1179                 }
1180                 
1181             }
1182             filteredPomPrefix = null;
1183         }
1184     }
1185 
1186     
1187 
1188 
1189 
1190 
1191 
1192     private String getParentPath(String path) {
1193         int lastSep = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
1194         return (lastSep < 0) ? "." : path.substring(0, lastSep);
1195     }
1196 
1197     
1198 
1199 
1200 
1201 
1202 
1203 
1204     private void copyDirectoryStructure(File sourceDir, File destDir) throws IOException {
1205         DirectoryScanner scanner = new DirectoryScanner();
1206         scanner.setBasedir(sourceDir);
1207         if (!cloneAllFiles) {
1208             scanner.addDefaultExcludes();
1209         }
1210         scanner.scan();
1211 
1212         
1213 
1214 
1215         Files.createDirectories(destDir.toPath());
1216         
1217         FileUtils.mkDirs(sourceDir, scanner.getIncludedDirectories(), destDir);
1218 
1219         for (String includedFile : scanner.getIncludedFiles()) {
1220             File sourceFile = new File(sourceDir, includedFile);
1221             File destFile = new File(destDir, includedFile);
1222             if (NioFiles.isSymbolicLink(sourceFile)) {
1223                 NioFiles.createSymbolicLink(destFile, NioFiles.readSymbolicLink(sourceFile));
1224             } else {
1225                 FileUtils.copyFile(sourceFile, destFile);
1226             }
1227 
1228             
1229             destFile.setWritable(true);
1230         }
1231     }
1232 
1233     
1234 
1235 
1236 
1237 
1238 
1239 
1240 
1241 
1242     static boolean alreadyCloned(String subpath, List<String> clonedSubpaths) {
1243         for (String path : clonedSubpaths) {
1244             if (".".equals(path) || subpath.equals(path) || subpath.startsWith(path + File.separator)) {
1245                 return true;
1246             }
1247         }
1248 
1249         return false;
1250     }
1251 
1252     
1253 
1254 
1255 
1256 
1257 
1258 
1259     private void runBuilds(final File projectsDir, List<BuildJob> buildJobs, int runWithParallelThreads)
1260             throws MojoExecutionException {
1261         if (!localRepositoryPath.exists()) {
1262             localRepositoryPath.mkdirs();
1263         }
1264 
1265         
1266         
1267         
1268 
1269         File interpolatedSettingsFile = interpolateSettings(settingsFile);
1270 
1271         final File mergedSettingsFile = mergeSettings(interpolatedSettingsFile);
1272 
1273         final CharSequence actualJreVersion;
1274         
1275         if (javaHome != null) {
1276             actualJreVersion = resolveExternalJreVersion();
1277         } else {
1278             actualJreVersion = SelectorUtils.getJreVersion();
1279         }
1280 
1281         final Path projectsPath = this.projectsDirectory.toPath();
1282 
1283         Set<Path> folderGroupSet = new HashSet<>();
1284         folderGroupSet.add(Paths.get("."));
1285         for (BuildJob buildJob : buildJobs) {
1286             Path p = Paths.get(buildJob.getProject());
1287 
1288             if (Files.isRegularFile(projectsPath.resolve(p))) {
1289                 p = p.getParent();
1290             }
1291 
1292             if (p != null) {
1293                 p = p.getParent();
1294             }
1295 
1296             while (p != null && folderGroupSet.add(p)) {
1297                 p = p.getParent();
1298             }
1299         }
1300 
1301         List<Path> folderGroup = new ArrayList<>(folderGroupSet);
1302         Collections.sort(folderGroup);
1303 
1304         final Map<Path, Properties> globalInvokerProperties = new HashMap<>();
1305 
1306         for (Path path : folderGroup) {
1307             Properties ancestorProperties =
1308                     globalInvokerProperties.get(projectsPath.resolve(path).getParent());
1309 
1310             Path currentInvokerProperties = projectsPath.resolve(path).resolve(invokerPropertiesFile);
1311 
1312             Properties currentProperties;
1313             if (Files.isRegularFile(currentInvokerProperties)) {
1314                 if (ancestorProperties != null) {
1315                     currentProperties = new Properties(ancestorProperties);
1316 
1317                 } else {
1318                     currentProperties = new Properties();
1319                 }
1320             } else {
1321                 currentProperties = ancestorProperties;
1322             }
1323 
1324             if (Files.isRegularFile(currentInvokerProperties)) {
1325                 try (InputStream in = new FileInputStream(currentInvokerProperties.toFile())) {
1326                     currentProperties.load(in);
1327                 } catch (IOException e) {
1328                     throw new MojoExecutionException("Failed to read invoker properties: " + currentInvokerProperties);
1329                 }
1330             }
1331 
1332             if (currentProperties != null) {
1333                 globalInvokerProperties.put(projectsPath.resolve(path).normalize(), currentProperties);
1334             }
1335         }
1336 
1337         try {
1338             if (runWithParallelThreads > 1) {
1339                 getLog().info("use parallelThreads " + runWithParallelThreads);
1340             }
1341 
1342             JobExecutor jobExecutor = new JobExecutor(buildJobs, runWithParallelThreads);
1343             jobExecutor.forEach(job -> {
1344                 Path ancestorFolder = getAncestorFolder(projectsPath.resolve(job.getProject()));
1345 
1346                 runBuild(
1347                         projectsDir,
1348                         job,
1349                         mergedSettingsFile,
1350                         javaHome,
1351                         actualJreVersion,
1352                         globalInvokerProperties.get(ancestorFolder));
1353             });
1354         } finally {
1355             if (interpolatedSettingsFile != null && cloneProjectsTo == null) {
1356                 interpolatedSettingsFile.delete();
1357             }
1358             if (mergedSettingsFile != null && mergedSettingsFile.exists()) {
1359                 mergedSettingsFile.delete();
1360             }
1361         }
1362     }
1363 
1364     private Path getAncestorFolder(Path p) {
1365         Path ancestor = p;
1366         if (Files.isRegularFile(ancestor)) {
1367             ancestor = ancestor.getParent();
1368         }
1369         if (ancestor != null) {
1370             ancestor = ancestor.getParent();
1371         }
1372         return ancestor;
1373     }
1374 
1375     
1376 
1377 
1378 
1379 
1380 
1381 
1382     private File interpolateSettings(File settingsFile) throws MojoExecutionException {
1383         File interpolatedSettingsFile = null;
1384         if (settingsFile != null) {
1385             if (cloneProjectsTo != null) {
1386                 interpolatedSettingsFile = new File(cloneProjectsTo, "interpolated-" + settingsFile.getName());
1387             } else {
1388                 interpolatedSettingsFile =
1389                         new File(settingsFile.getParentFile(), "interpolated-" + settingsFile.getName());
1390             }
1391             buildInterpolatedFile(settingsFile, interpolatedSettingsFile);
1392         }
1393         return interpolatedSettingsFile;
1394     }
1395 
1396     
1397 
1398 
1399 
1400 
1401 
1402 
1403     private File mergeSettings(File interpolatedSettingsFile) throws MojoExecutionException {
1404         File mergedSettingsFile;
1405         Settings mergedSettings = this.settings;
1406         if (mergeUserSettings) {
1407             if (interpolatedSettingsFile != null) {
1408                 
1409                 try {
1410                     SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
1411                     request.setGlobalSettingsFile(interpolatedSettingsFile);
1412 
1413                     Settings dominantSettings = settingsBuilder.build(request).getEffectiveSettings();
1414                     Settings recessiveSettings = cloneSettings();
1415                     SettingsUtils.merge(dominantSettings, recessiveSettings, TrackableBase.USER_LEVEL);
1416 
1417                     mergedSettings = dominantSettings;
1418                     getLog().debug("Merged specified settings file with settings of invoking process");
1419                 } catch (SettingsBuildingException e) {
1420                     throw new MojoExecutionException("Could not read specified settings file", e);
1421                 }
1422             }
1423         }
1424 
1425         if (this.settingsFile != null && !mergeUserSettings) {
1426             mergedSettingsFile = interpolatedSettingsFile;
1427         } else {
1428             try {
1429                 mergedSettingsFile = writeMergedSettingsFile(mergedSettings);
1430             } catch (IOException e) {
1431                 throw new MojoExecutionException("Could not create temporary file for invoker settings.xml", e);
1432             }
1433         }
1434         return mergedSettingsFile;
1435     }
1436 
1437     private File writeMergedSettingsFile(Settings mergedSettings) throws IOException {
1438         File mergedSettingsFile;
1439         mergedSettingsFile = Files.createTempFile("invoker-settings", ".xml").toFile();
1440 
1441         SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
1442 
1443         try (FileWriter fileWriter = new FileWriter(mergedSettingsFile)) {
1444             settingsWriter.write(fileWriter, mergedSettings);
1445         }
1446 
1447         if (getLog().isDebugEnabled()) {
1448             getLog().debug("Created temporary file for invoker settings.xml: " + mergedSettingsFile.getAbsolutePath());
1449         }
1450         return mergedSettingsFile;
1451     }
1452 
1453     private Settings cloneSettings() {
1454         Settings recessiveSettings = SettingsUtils.copySettings(this.settings);
1455 
1456         
1457         resetSourceLevelSet(recessiveSettings);
1458         for (org.apache.maven.settings.Mirror mirror : recessiveSettings.getMirrors()) {
1459             resetSourceLevelSet(mirror);
1460         }
1461         for (org.apache.maven.settings.Server server : recessiveSettings.getServers()) {
1462             resetSourceLevelSet(server);
1463         }
1464         for (org.apache.maven.settings.Proxy proxy : recessiveSettings.getProxies()) {
1465             resetSourceLevelSet(proxy);
1466         }
1467         for (org.apache.maven.settings.Profile profile : recessiveSettings.getProfiles()) {
1468             resetSourceLevelSet(profile);
1469         }
1470 
1471         return recessiveSettings;
1472     }
1473 
1474     private void resetSourceLevelSet(org.apache.maven.settings.TrackableBase trackable) {
1475         try {
1476             ReflectionUtils.setVariableValueInObject(trackable, "sourceLevelSet", Boolean.FALSE);
1477             getLog().debug("sourceLevelSet: "
1478                     + ReflectionUtils.getValueIncludingSuperclasses("sourceLevelSet", trackable));
1479         } catch (IllegalAccessException e) {
1480             
1481         }
1482     }
1483 
1484     private CharSequence resolveExternalJreVersion() {
1485         Artifact pluginArtifact =
1486                 mojoExecution.getMojoDescriptor().getPluginDescriptor().getPluginArtifact();
1487         pluginArtifact.getFile();
1488 
1489         Commandline commandLine = new Commandline();
1490         commandLine.setExecutable(new File(javaHome, "bin/java").getAbsolutePath());
1491         commandLine.createArg().setValue("-cp");
1492         commandLine.createArg().setFile(pluginArtifact.getFile());
1493         commandLine.createArg().setValue(SystemPropertyPrinter.class.getName());
1494         commandLine.createArg().setValue("java.version");
1495 
1496         final StringBuilder actualJreVersion = new StringBuilder();
1497         StreamConsumer consumer = actualJreVersion::append;
1498         try {
1499             CommandLineUtils.executeCommandLine(commandLine, consumer, null);
1500         } catch (CommandLineException e) {
1501             getLog().warn(e.getMessage());
1502         }
1503         return actualJreVersion;
1504     }
1505 
1506     
1507 
1508 
1509 
1510 
1511 
1512 
1513 
1514 
1515     private File interpolatePomFile(File pomFile, File basedir) throws MojoExecutionException {
1516         File interpolatedPomFile = null;
1517         if (pomFile != null) {
1518             if (filteredPomPrefix != null && !filteredPomPrefix.isEmpty()) {
1519                 interpolatedPomFile = new File(basedir, filteredPomPrefix + pomFile.getName());
1520                 buildInterpolatedFile(pomFile, interpolatedPomFile);
1521             } else {
1522                 interpolatedPomFile = pomFile;
1523             }
1524         }
1525         return interpolatedPomFile;
1526     }
1527 
1528     
1529 
1530 
1531 
1532 
1533 
1534 
1535 
1536 
1537 
1538     private void runBuild(
1539             File projectsDir,
1540             BuildJob buildJob,
1541             File settingsFile,
1542             File actualJavaHome,
1543             CharSequence actualJreVersion,
1544             Properties globalInvokerProperties)
1545             throws MojoExecutionException {
1546         
1547         File pomFile = new File(projectsDir, buildJob.getProject());
1548         File basedir;
1549         if (pomFile.isDirectory()) {
1550             basedir = pomFile;
1551             pomFile = new File(basedir, "pom.xml");
1552             if (!pomFile.exists()) {
1553                 pomFile = null;
1554             } else {
1555                 buildJob.setProject(buildJob.getProject() + File.separator + "pom.xml");
1556             }
1557         } else {
1558             basedir = pomFile.getParentFile();
1559         }
1560 
1561         File interpolatedPomFile = interpolatePomFile(pomFile, basedir);
1562         
1563 
1564         getLog().info(buffer().a("Building: ").strong(buildJob.getProject()).build());
1565 
1566         InvokerProperties invokerProperties = getInvokerProperties(basedir, globalInvokerProperties);
1567 
1568         
1569         buildJob.setName(invokerProperties.getJobName());
1570         buildJob.setDescription(invokerProperties.getJobDescription());
1571 
1572         try {
1573             int selection = getSelection(invokerProperties, actualJreVersion);
1574             if (selection == 0) {
1575                 long startTime = System.currentTimeMillis();
1576                 boolean executed;
1577 
1578                 FileLogger buildLogger = setupBuildLogFile(basedir);
1579                 if (buildLogger != null) {
1580                     buildJob.setBuildlog(buildLogger.getOutputFile().getAbsolutePath());
1581                 }
1582 
1583                 try {
1584                     executed = runBuild(
1585                             basedir, interpolatedPomFile, settingsFile, actualJavaHome, invokerProperties, buildLogger);
1586                 } finally {
1587                     long elapsedTime = System.currentTimeMillis() - startTime;
1588                     buildJob.setTime(elapsedTime / ONE_SECOND);
1589 
1590                     if (buildLogger != null) {
1591                         buildLogger.close();
1592                     }
1593                 }
1594 
1595                 if (executed) {
1596                     buildJob.setResult(BuildJob.Result.SUCCESS);
1597 
1598                     if (!suppressSummaries) {
1599                         getLog().info(pad(buildJob).success("SUCCESS").a(' ') + "("
1600                                 + formatElapsedTime(buildJob.getTime()) + ")");
1601                     }
1602                 } else {
1603                     buildJob.setResult(BuildJob.Result.SKIPPED);
1604 
1605                     if (!suppressSummaries) {
1606                         getLog().info(pad(buildJob).warning("SKIPPED").a(' ') + "("
1607                                 + formatElapsedTime(buildJob.getTime()) + ")");
1608                     }
1609                 }
1610             } else {
1611                 buildJob.setResult(BuildJob.Result.SKIPPED);
1612 
1613                 List<String> messages = new ArrayList<>();
1614 
1615                 if (selection == Selector.SELECTOR_MULTI) {
1616                     messages.add("non-matching selectors");
1617                 } else {
1618                     if ((selection & Selector.SELECTOR_MAVENVERSION) != 0) {
1619                         messages.add("Maven version");
1620                     }
1621                     if ((selection & Selector.SELECTOR_JREVERSION) != 0) {
1622                         messages.add("JRE version");
1623                     }
1624                     if ((selection & Selector.SELECTOR_OSFAMILY) != 0) {
1625                         messages.add("OS");
1626                     }
1627                     if ((selection & Selector.SELECTOR_TOOLCHAIN) != 0) {
1628                         messages.add("Toolchain");
1629                     }
1630                 }
1631 
1632                 String message = String.join(", ", messages);
1633                 if (!suppressSummaries) {
1634                     getLog().info(pad(buildJob).warning("SKIPPED") + " due to " + message);
1635                 }
1636 
1637                 
1638                 
1639                 buildJob.setFailureMessage("Skipped due to " + message);
1640             }
1641         } catch (RunFailureException e) {
1642             buildJob.setResult(e.getType());
1643             buildJob.setFailureMessage(e.getMessage());
1644 
1645             if (!suppressSummaries) {
1646                 getLog().info("  " + e.getMessage());
1647                 getLog().info(pad(buildJob).failure("FAILED").a(' ') + "(" + formatElapsedTime(buildJob.getTime())
1648                         + ")");
1649             }
1650         } finally {
1651             deleteInterpolatedPomFile(interpolatedPomFile);
1652             writeBuildReport(buildJob);
1653         }
1654     }
1655 
1656     private MessageBuilder pad(BuildJob buildJob) {
1657         MessageBuilder buffer = buffer(128);
1658 
1659         buffer.a("          ");
1660         buffer.a(buildJob.getProject());
1661 
1662         int l = 10 + buildJob.getProject().length();
1663 
1664         if (l < RESULT_COLUMN) {
1665             buffer.a(' ');
1666             l++;
1667 
1668             if (l < RESULT_COLUMN) {
1669                 for (int i = RESULT_COLUMN - l; i > 0; i--) {
1670                     buffer.a('.');
1671                 }
1672             }
1673         }
1674 
1675         return buffer.a(' ');
1676     }
1677 
1678     
1679 
1680 
1681 
1682 
1683     private void deleteInterpolatedPomFile(File interpolatedPomFile) {
1684         if (interpolatedPomFile != null && (filteredPomPrefix != null && !filteredPomPrefix.isEmpty())) {
1685             interpolatedPomFile.delete();
1686         }
1687     }
1688 
1689     
1690 
1691 
1692 
1693 
1694 
1695 
1696     private int getSelection(InvokerProperties invokerProperties, CharSequence actualJreVersion) {
1697         return new Selector(actualMavenVersion, actualJreVersion.toString(), getToolchainPrivateManager())
1698                 .getSelection(invokerProperties);
1699     }
1700 
1701     private ToolchainPrivateManager getToolchainPrivateManager() {
1702         return new ToolchainPrivateManager(toolchainManagerPrivate, session);
1703     }
1704 
1705     
1706 
1707 
1708 
1709 
1710 
1711     private void writeBuildReport(BuildJob buildJob) throws MojoExecutionException {
1712         if (disableReports) {
1713             return;
1714         }
1715 
1716         String safeFileName =
1717                 buildJob.getProject().replace('/', '_').replace('\\', '_').replace(' ', '_');
1718         if (safeFileName.endsWith("_pom.xml")) {
1719             safeFileName = safeFileName.substring(0, safeFileName.length() - "_pom.xml".length());
1720         }
1721 
1722         File reportFile = new File(reportsDirectory, "BUILD-" + safeFileName + ".xml");
1723         try (FileOutputStream fos = new FileOutputStream(reportFile);
1724                 Writer osw = new OutputStreamWriter(fos, buildJob.getModelEncoding())) {
1725             BuildJobXpp3Writer writer = new BuildJobXpp3Writer();
1726 
1727             writer.write(osw, buildJob);
1728         } catch (IOException e) {
1729             throw new MojoExecutionException("Failed to write build report " + reportFile, e);
1730         }
1731 
1732         if (writeJunitReport) {
1733             writeJunitReport(buildJob, safeFileName);
1734         }
1735     }
1736 
1737     private void writeJunitReport(BuildJob buildJob, String safeFileName) throws MojoExecutionException {
1738         File reportFile = new File(reportsDirectory, "TEST-" + safeFileName + ".xml");
1739         Xpp3Dom testsuite = new Xpp3Dom("testsuite");
1740         testsuite.setAttribute("name", junitPackageName + "." + safeFileName);
1741         testsuite.setAttribute("time", Float.toString(buildJob.getTime()));
1742 
1743         
1744         testsuite.setAttribute("tests", "1");
1745         testsuite.setAttribute("errors", "0");
1746         testsuite.setAttribute("skipped", "0");
1747         testsuite.setAttribute("failures", "0");
1748 
1749         Xpp3Dom testcase = new Xpp3Dom("testcase");
1750         testsuite.addChild(testcase);
1751         switch (buildJob.getResult()) {
1752             case BuildJob.Result.SUCCESS:
1753                 break;
1754             case BuildJob.Result.SKIPPED:
1755                 testsuite.setAttribute("skipped", "1");
1756                 
1757                 Xpp3Dom skipped = new Xpp3Dom("skipped");
1758                 testcase.addChild(skipped);
1759                 skipped.setValue(buildJob.getFailureMessage());
1760                 break;
1761             case BuildJob.Result.ERROR:
1762                 testsuite.setAttribute("errors", "1");
1763                 break;
1764             default:
1765                 testsuite.setAttribute("failures", "1");
1766                 
1767                 Xpp3Dom failure = new Xpp3Dom("failure");
1768                 testcase.addChild(failure);
1769                 failure.setAttribute("message", buildJob.getFailureMessage());
1770         }
1771         testcase.setAttribute("classname", junitPackageName + "." + safeFileName);
1772         testcase.setAttribute("name", safeFileName);
1773         testcase.setAttribute("time", Float.toString(buildJob.getTime()));
1774         Xpp3Dom systemOut = new Xpp3Dom("system-out");
1775         testcase.addChild(systemOut);
1776 
1777         File buildLogFile = buildJob.getBuildlog() != null ? new File(buildJob.getBuildlog()) : null;
1778 
1779         if (buildLogFile != null && buildLogFile.exists()) {
1780             getLog().debug("fileLogger:" + buildLogFile);
1781             try {
1782                 systemOut.setValue(FileUtils.fileRead(buildLogFile));
1783             } catch (IOException e) {
1784                 throw new MojoExecutionException("Failed to read logfile " + buildLogFile, e);
1785             }
1786         } else {
1787             getLog().debug(safeFileName + "not exists buildLogFile = " + buildLogFile);
1788         }
1789 
1790         try (FileOutputStream fos = new FileOutputStream(reportFile);
1791                 Writer osw = new OutputStreamWriter(fos, buildJob.getModelEncoding())) {
1792             Xpp3DomWriter.write(osw, testsuite);
1793         } catch (IOException e) {
1794             throw new MojoExecutionException("Failed to write JUnit build report " + reportFile, e);
1795         }
1796     }
1797 
1798     
1799 
1800 
1801 
1802 
1803 
1804     private String formatElapsedTime(float time) {
1805         
1806 
1807 
1808 
1809         final MessageFormat elapsedTimeFormat = new MessageFormat(
1810                 "{0,choice,0#0|0.0<{0,number,0.000}|10#{0,number,0.00}|100#{0,number,0.0}|1000#{0,number,0}} s",
1811                 Locale.ROOT);
1812         return elapsedTimeFormat.format(new Object[] {time});
1813     }
1814 
1815     
1816 
1817 
1818 
1819 
1820 
1821 
1822 
1823 
1824 
1825 
1826 
1827 
1828 
1829 
1830     private boolean runBuild(
1831             File basedir,
1832             File pomFile,
1833             File settingsFile,
1834             File actualJavaHome,
1835             InvokerProperties invokerProperties,
1836             FileLogger logger)
1837             throws MojoExecutionException, RunFailureException {
1838         if (getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty()) {
1839             Properties props = invokerProperties.getProperties();
1840             getLog().debug("Using invoker properties:");
1841             for (String key : new TreeSet<>(props.stringPropertyNames())) {
1842                 String value = props.getProperty(key);
1843                 getLog().debug("  " + key + " = " + value);
1844             }
1845         }
1846 
1847         Map<String, Object> context = new LinkedHashMap<>();
1848         Properties scriptUserProperties = new Properties();
1849         context.put("userProperties", scriptUserProperties);
1850 
1851         if (!runSelectorHook(basedir, context, logger)) {
1852             return false;
1853         }
1854 
1855         try {
1856             runPreBuildHook(basedir, context, logger);
1857 
1858             for (int invocationIndex = 1; ; invocationIndex++) {
1859                 if (invocationIndex > 1 && !invokerProperties.isInvocationDefined(invocationIndex)) {
1860                     break;
1861                 }
1862 
1863                 final InvocationRequest request = new DefaultInvocationRequest();
1864 
1865                 request.setBatchMode(true);
1866 
1867                 
1868                 request.setLocalRepositoryDirectory(localRepositoryPath);
1869                 request.setShowErrors(showErrors);
1870                 request.setShowVersion(showVersion);
1871                 request.setJavaHome(actualJavaHome);
1872                 request.setMavenHome(mavenHome);
1873                 setupLoggerForBuildJob(logger, request);
1874 
1875                 request.setBaseDirectory(basedir);
1876                 request.setPomFile(pomFile);
1877 
1878                 String customSettingsFile = invokerProperties.getSettingsFile(invocationIndex);
1879                 if (customSettingsFile != null) {
1880                     File interpolateSettingsFile = interpolateSettings(new File(customSettingsFile));
1881                     File mergeSettingsFile = mergeSettings(interpolateSettingsFile);
1882 
1883                     request.setUserSettingsFile(mergeSettingsFile);
1884                 } else {
1885                     request.setUserSettingsFile(settingsFile);
1886                 }
1887 
1888                 Properties userProperties =
1889                         getUserProperties(basedir, invokerProperties.getUserPropertiesFile(invocationIndex));
1890                 userProperties.putAll(scriptUserProperties);
1891                 request.setProperties(userProperties);
1892 
1893                 invokerProperties.configureInvocation(request, invocationIndex);
1894 
1895                 if (getLog().isDebugEnabled()) {
1896                     try {
1897                         getLog().debug("Using MAVEN_OPTS: " + request.getMavenOpts());
1898                         getLog().debug("Executing: " + new MavenCommandLineBuilder().build(request));
1899                     } catch (CommandLineConfigurationException e) {
1900                         getLog().debug("Failed to display command line: " + e.getMessage());
1901                     }
1902                 }
1903 
1904                 try {
1905                     InvocationResult result = invoker.execute(request);
1906                     verify(result, invocationIndex, invokerProperties, logger);
1907                 } catch (final MavenInvocationException e) {
1908                     getLog().debug("Error invoking Maven: " + e.getMessage(), e);
1909                     throw new RunFailureException(
1910                             "Maven invocation failed. " + e.getMessage(), BuildJob.Result.FAILURE_BUILD);
1911                 }
1912             }
1913         } finally {
1914             runPostBuildHook(basedir, context, logger);
1915         }
1916         return true;
1917     }
1918 
1919     int getParallelThreadsCount() {
1920         if (parallelThreads.endsWith("C")) {
1921             float parallelThreadsMultiple =
1922                     Float.parseFloat(parallelThreads.substring(0, parallelThreads.length() - 1));
1923             return (int) (parallelThreadsMultiple * Runtime.getRuntime().availableProcessors());
1924         } else {
1925             return Integer.parseInt(parallelThreads);
1926         }
1927     }
1928 
1929     private boolean runSelectorHook(File basedir, Map<String, Object> context, FileLogger logger)
1930             throws MojoExecutionException, RunFailureException {
1931         try {
1932             scriptRunner.run("selector script", basedir, selectorScript, context, logger);
1933         } catch (ScriptReturnException e) {
1934             return false;
1935         } catch (ScriptException e) {
1936             throw new RunFailureException(BuildJob.Result.ERROR, e);
1937         } catch (IOException e) {
1938             throw new MojoExecutionException(e.getMessage(), e);
1939         }
1940         return true;
1941     }
1942 
1943     private void runPreBuildHook(File basedir, Map<String, Object> context, FileLogger logger)
1944             throws MojoExecutionException, RunFailureException {
1945         try {
1946             scriptRunner.run("pre-build script", basedir, preBuildHookScript, context, logger);
1947         } catch (ScriptException e) {
1948             throw new RunFailureException(BuildJob.Result.FAILURE_PRE_HOOK, e);
1949         } catch (IOException e) {
1950             throw new MojoExecutionException(e.getMessage(), e);
1951         }
1952     }
1953 
1954     private void runPostBuildHook(File basedir, Map<String, Object> context, FileLogger logger)
1955             throws MojoExecutionException, RunFailureException {
1956         try {
1957             scriptRunner.run("post-build script", basedir, postBuildHookScript, context, logger);
1958         } catch (IOException e) {
1959             throw new MojoExecutionException(e.getMessage(), e);
1960         } catch (ScriptException e) {
1961             throw new RunFailureException(e.getMessage(), BuildJob.Result.FAILURE_POST_HOOK, e);
1962         }
1963     }
1964 
1965     private void setupLoggerForBuildJob(final FileLogger logger, final InvocationRequest request) {
1966         if (logger != null) {
1967             request.setErrorHandler(logger);
1968             request.setOutputHandler(logger);
1969         }
1970     }
1971 
1972     
1973 
1974 
1975 
1976 
1977 
1978 
1979 
1980     private FileLogger setupBuildLogFile(File basedir) throws MojoExecutionException {
1981         FileLogger logger = null;
1982 
1983         if (!noLog) {
1984             Path projectLogDirectory;
1985             if (logDirectory == null) {
1986                 projectLogDirectory = basedir.toPath();
1987             } else if (cloneProjectsTo != null) {
1988                 projectLogDirectory =
1989                         logDirectory.toPath().resolve(cloneProjectsTo.toPath().relativize(basedir.toPath()));
1990             } else {
1991                 projectLogDirectory =
1992                         logDirectory.toPath().resolve(projectsDirectory.toPath().relativize(basedir.toPath()));
1993             }
1994 
1995             try {
1996                 if (streamLogs) {
1997                     logger = new FileLogger(
1998                             projectLogDirectory.resolve("build.log").toFile(), getLog());
1999                 } else {
2000                     logger = new FileLogger(
2001                             projectLogDirectory.resolve("build.log").toFile());
2002                 }
2003 
2004                 getLog().debug("Build log initialized in: " + projectLogDirectory);
2005             } catch (IOException e) {
2006                 throw new MojoExecutionException("Error initializing build logfile in: " + projectLogDirectory, e);
2007             }
2008         }
2009 
2010         return logger;
2011     }
2012 
2013     
2014 
2015 
2016 
2017 
2018 
2019 
2020 
2021 
2022     private Properties getUserProperties(final File basedir, final String filename) throws MojoExecutionException {
2023         Properties collectedTestProperties = new Properties();
2024 
2025         if (properties != null) {
2026             
2027             for (Map.Entry<String, String> entry : properties.entrySet()) {
2028                 if (entry.getValue() != null) {
2029                     collectedTestProperties.put(entry.getKey(), entry.getValue());
2030                 }
2031             }
2032         }
2033 
2034         File propertiesFile = null;
2035         if (filename != null) {
2036             propertiesFile = new File(basedir, filename);
2037         }
2038 
2039         if (propertiesFile != null && propertiesFile.isFile()) {
2040 
2041             try (InputStream fin = Files.newInputStream(propertiesFile.toPath())) {
2042                 Properties loadedProperties = new Properties();
2043                 loadedProperties.load(fin);
2044                 collectedTestProperties.putAll(loadedProperties);
2045             } catch (IOException e) {
2046                 throw new MojoExecutionException("Error reading user properties from " + propertiesFile);
2047             }
2048         }
2049 
2050         return collectedTestProperties;
2051     }
2052 
2053     
2054 
2055 
2056 
2057 
2058 
2059 
2060 
2061     private void verify(
2062             InvocationResult result, int invocationIndex, InvokerProperties invokerProperties, FileLogger logger)
2063             throws RunFailureException {
2064         if (result.getExecutionException() != null) {
2065             throw new RunFailureException(
2066                     "The Maven invocation failed. "
2067                             + result.getExecutionException().getMessage(),
2068                     BuildJob.Result.ERROR);
2069         } else if (!invokerProperties.isExpectedResult(result.getExitCode(), invocationIndex)) {
2070             StringBuilder buffer = new StringBuilder(256);
2071             buffer.append("The build exited with code ")
2072                     .append(result.getExitCode())
2073                     .append(". ");
2074             if (logger != null) {
2075                 buffer.append("See ");
2076                 buffer.append(logger.getOutputFile().getAbsolutePath());
2077                 buffer.append(" for details.");
2078             } else {
2079                 buffer.append("See console output for details.");
2080             }
2081             throw new RunFailureException(buffer.toString(), BuildJob.Result.FAILURE_BUILD);
2082         }
2083     }
2084 
2085     private List<String> calculateIncludes() {
2086         if (invokerTest != null) {
2087             String[] testRegexes = invokerTest.split(",+");
2088             return Arrays.stream(testRegexes)
2089                     .map(String::trim)
2090                     .filter(s -> !s.isEmpty())
2091                     .filter(s -> !s.startsWith("!"))
2092                     .collect(Collectors.toList());
2093         } else {
2094             Set<String> uniqueIncludes = new HashSet<>();
2095             uniqueIncludes.addAll(pomIncludes);
2096             uniqueIncludes.addAll(setupIncludes);
2097             return new ArrayList<>(uniqueIncludes);
2098         }
2099     }
2100 
2101     private List<String> calculateExcludes() throws IOException {
2102         List<String> excludes;
2103 
2104         if (invokerTest != null) {
2105             String[] testRegexes = invokerTest.split(",+");
2106             excludes = Arrays.stream(testRegexes)
2107                     .map(String::trim)
2108                     .filter(s -> !s.isEmpty())
2109                     .filter(s -> s.startsWith("!"))
2110                     .map(s -> s.substring(1))
2111                     .collect(Collectors.toList());
2112         } else {
2113             excludes = pomExcludes != null ? new ArrayList<>(pomExcludes) : new ArrayList<>();
2114         }
2115 
2116         if (this.settingsFile != null) {
2117             String exclude = relativizePath(this.settingsFile, projectsDirectory.getCanonicalPath());
2118             if (exclude != null) {
2119                 excludes.add(exclude.replace('\\', '/'));
2120                 getLog().debug("Automatically excluded " + exclude + " from project scanning");
2121             }
2122         }
2123         return excludes;
2124     }
2125 
2126     
2127 
2128 
2129 
2130 
2131 
2132     List<BuildJob> getBuildJobs() throws IOException, MojoExecutionException {
2133 
2134         List<String> includes = calculateIncludes();
2135         List<String> excludes = calculateExcludes();
2136         List<BuildJob> buildJobsAll = scanProjectsDirectory(includes, excludes);
2137         List<BuildJob> buildJobsSetup = scanProjectsDirectory(setupIncludes, excludes);
2138 
2139         List<String> setupProjects =
2140                 buildJobsSetup.stream().map(BuildJob::getProject).collect(Collectors.toList());
2141 
2142         for (BuildJob job : buildJobsAll) {
2143             if (setupProjects.contains(job.getProject())) {
2144                 job.setType(BuildJob.Type.SETUP);
2145             }
2146             InvokerProperties invokerProperties =
2147                     getInvokerProperties(new File(projectsDirectory, job.getProject()).getParentFile(), null);
2148             job.setOrdinal(invokerProperties.getOrdinal());
2149         }
2150 
2151         relativizeProjectPaths(buildJobsAll);
2152 
2153         return buildJobsAll;
2154     }
2155 
2156     
2157 
2158 
2159 
2160 
2161 
2162 
2163 
2164 
2165 
2166 
2167     private List<BuildJob> scanProjectsDirectory(List<String> includes, List<String> excludes) throws IOException {
2168         if (!projectsDirectory.isDirectory()) {
2169             return Collections.emptyList();
2170         }
2171 
2172         DirectoryScanner scanner = new DirectoryScanner();
2173         scanner.setBasedir(projectsDirectory.getCanonicalFile());
2174         scanner.setFollowSymlinks(false);
2175         if (includes != null) {
2176             scanner.setIncludes(includes.toArray(new String[0]));
2177         }
2178         if (excludes != null) {
2179             if ((includes == null || includes.isEmpty()) && !excludes.isEmpty()) {
2180                 scanner.setIncludes(new String[] {"*"});
2181             }
2182             scanner.setExcludes(excludes.toArray(new String[0]));
2183         }
2184         scanner.addDefaultExcludes();
2185         scanner.scan();
2186 
2187         Map<String, BuildJob> matches = new LinkedHashMap<>();
2188 
2189         for (String includedFile : scanner.getIncludedFiles()) {
2190             matches.put(includedFile, new BuildJob(includedFile));
2191         }
2192 
2193         for (String includedDir : scanner.getIncludedDirectories()) {
2194             String includedFile = includedDir + File.separatorChar + "pom.xml";
2195             if (new File(scanner.getBasedir(), includedFile).isFile()) {
2196                 matches.put(includedFile, new BuildJob(includedFile));
2197             } else {
2198                 matches.put(includedDir, new BuildJob(includedDir));
2199             }
2200         }
2201 
2202         return new ArrayList<>(matches.values());
2203     }
2204 
2205     
2206 
2207 
2208 
2209 
2210 
2211 
2212 
2213 
2214     private void relativizeProjectPaths(List<BuildJob> buildJobs) throws IOException {
2215         String projectsDirPath = projectsDirectory.getCanonicalPath();
2216 
2217         for (BuildJob buildJob : buildJobs) {
2218             String projectPath = buildJob.getProject();
2219 
2220             File file = new File(projectPath);
2221 
2222             if (!file.isAbsolute()) {
2223                 file = new File(projectsDirectory, projectPath);
2224             }
2225 
2226             String relativizedPath = relativizePath(file, projectsDirPath);
2227 
2228             if (relativizedPath == null) {
2229                 relativizedPath = projectPath;
2230             }
2231 
2232             buildJob.setProject(relativizedPath);
2233         }
2234     }
2235 
2236     
2237 
2238 
2239 
2240 
2241 
2242 
2243 
2244 
2245 
2246     private String relativizePath(File path, String basedir) throws IOException {
2247         String relativizedPath = path.getCanonicalPath();
2248 
2249         if (relativizedPath.startsWith(basedir)) {
2250             relativizedPath = relativizedPath.substring(basedir.length());
2251             if (relativizedPath.startsWith(File.separator)) {
2252                 relativizedPath = relativizedPath.substring(File.separator.length());
2253             }
2254 
2255             return relativizedPath;
2256         } else {
2257             return null;
2258         }
2259     }
2260 
2261     
2262 
2263 
2264 
2265 
2266 
2267 
2268 
2269     private Map<String, Object> getInterpolationValueSource(final boolean escapeXml) {
2270         Map<String, Object> props = new HashMap<>();
2271 
2272         if (filterProperties != null) {
2273             props.putAll(filterProperties);
2274         }
2275         props.put("basedir", this.project.getBasedir().getAbsolutePath());
2276         props.put("baseurl", toUrl(this.project.getBasedir().getAbsolutePath()));
2277         if (settings.getLocalRepository() != null) {
2278             props.put("localRepository", settings.getLocalRepository());
2279             props.put("localRepositoryUrl", toUrl(settings.getLocalRepository()));
2280         }
2281 
2282         return new CompositeMap(this.project, props, escapeXml);
2283     }
2284 
2285     
2286 
2287 
2288 
2289 
2290 
2291 
2292     private static String toUrl(String filename) {
2293         
2294 
2295 
2296 
2297         String url = "file://" + new File(filename).toURI().getPath();
2298         if (url.endsWith("/")) {
2299             url = url.substring(0, url.length() - 1);
2300         }
2301         return url;
2302     }
2303 
2304     
2305 
2306 
2307 
2308 
2309 
2310 
2311 
2312 
2313 
2314 
2315 
2316 
2317     void buildInterpolatedFile(File originalFile, File interpolatedFile) throws MojoExecutionException {
2318         getLog().debug("Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath());
2319 
2320         try {
2321             String xml;
2322 
2323             Map<String, Object> composite = getInterpolationValueSource(true);
2324 
2325             
2326             try (Reader reader =
2327                     new InterpolationFilterReader(new XmlStreamReader(originalFile), composite, "@", "@")) {
2328                 xml = IOUtil.toString(reader);
2329             }
2330 
2331             try (Writer writer = new XmlStreamWriter(interpolatedFile)) {
2332                 interpolatedFile.getParentFile().mkdirs();
2333                 writer.write(xml);
2334             }
2335         } catch (IOException e) {
2336             throw new MojoExecutionException("Failed to interpolate file " + originalFile.getPath(), e);
2337         }
2338     }
2339 
2340     
2341 
2342 
2343 
2344 
2345 
2346 
2347     private InvokerProperties getInvokerProperties(final File projectDirectory, Properties globalInvokerProperties)
2348             throws MojoExecutionException {
2349         Properties props;
2350         if (globalInvokerProperties != null) {
2351             props = new Properties(globalInvokerProperties);
2352         } else {
2353             props = new Properties();
2354         }
2355 
2356         File propertiesFile = new File(projectDirectory, invokerPropertiesFile);
2357         if (propertiesFile.isFile()) {
2358             try (InputStream in = new FileInputStream(propertiesFile)) {
2359                 props.load(in);
2360             } catch (IOException e) {
2361                 throw new MojoExecutionException("Failed to read invoker properties: " + propertiesFile, e);
2362             }
2363         }
2364 
2365         Interpolator interpolator = new RegexBasedInterpolator();
2366         interpolator.addValueSource(new MapBasedValueSource(getInterpolationValueSource(false)));
2367         
2368         for (String key : props.stringPropertyNames()) {
2369             String value = props.getProperty(key);
2370             try {
2371                 value = interpolator.interpolate(value, "");
2372             } catch (InterpolationException e) {
2373                 throw new MojoExecutionException("Failed to interpolate invoker properties: " + propertiesFile, e);
2374             }
2375             props.setProperty(key, value);
2376         }
2377 
2378         InvokerProperties invokerProperties = new InvokerProperties(props);
2379 
2380         
2381         invokerProperties.setDefaultDebug(debug);
2382         invokerProperties.setDefaultQuiet(quiet);
2383         invokerProperties.setDefaultGoals(goals);
2384         invokerProperties.setDefaultProfiles(profiles);
2385         invokerProperties.setDefaultMavenExecutable(mavenExecutable);
2386         invokerProperties.setDefaultMavenOpts(interpolatorUtils.interpolateAtPattern(mavenOpts));
2387         invokerProperties.setDefaultTimeoutInSeconds(timeoutInSeconds);
2388         invokerProperties.setDefaultEnvironmentVariables(environmentVariables);
2389         invokerProperties.setDefaultUpdateSnapshots(updateSnapshots);
2390         invokerProperties.setDefaultUserPropertiesFiles(testPropertiesFile);
2391 
2392         return invokerProperties;
2393     }
2394 
2395     static class ToolchainPrivateManager {
2396         private ToolchainManagerPrivate manager;
2397 
2398         private MavenSession session;
2399 
2400         ToolchainPrivateManager(ToolchainManagerPrivate manager, MavenSession session) {
2401             this.manager = manager;
2402             this.session = session;
2403         }
2404 
2405         ToolchainPrivate[] getToolchainPrivates(String type) throws MisconfiguredToolchainException {
2406             return manager.getToolchainsForType(type, session);
2407         }
2408     }
2409 }