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