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