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