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