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