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