1 package org.apache.maven.plugins.invoker;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.FileWriter;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.Reader;
31 import java.io.Writer;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.text.DecimalFormat;
36 import java.text.DecimalFormatSymbols;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.LinkedHashMap;
44 import java.util.LinkedHashSet;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
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.ReaderFactory;
96 import org.codehaus.plexus.util.ReflectionUtils;
97 import org.codehaus.plexus.util.StringUtils;
98 import org.codehaus.plexus.util.WriterFactory;
99 import org.codehaus.plexus.util.cli.CommandLineException;
100 import org.codehaus.plexus.util.cli.CommandLineUtils;
101 import org.codehaus.plexus.util.cli.Commandline;
102 import org.codehaus.plexus.util.cli.StreamConsumer;
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
115 extends AbstractMojo
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 @Parameter( property = "invoker.mavenOpts" )
417 private String mavenOpts;
418
419
420
421
422
423
424
425 @Parameter( property = "invoker.mavenHome" )
426 private File mavenHome;
427
428
429
430
431
432
433
434 @Parameter( property = "invoker.mavenExecutable" )
435 private File mavenExecutable;
436
437
438
439
440
441
442
443 @Parameter( property = "invoker.javaHome" )
444 private File javaHome;
445
446
447
448
449
450
451 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
452 private String encoding;
453
454
455
456
457
458
459
460
461
462
463 @Parameter( property = "invoker.addTestClassPath", defaultValue = "false" )
464 private boolean addTestClassPath;
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 @Parameter( property = "invoker.invokerPropertiesFile", defaultValue = "invoker.properties" )
603 private String invokerPropertiesFile;
604
605
606
607
608
609
610 @Parameter( property = "invoker.showVersion", defaultValue = "false" )
611 private boolean showVersion;
612
613
614
615
616
617
618
619
620
621
622 @Parameter( property = "invoker.parallelThreads", defaultValue = "1" )
623 private String parallelThreads;
624
625
626
627
628
629
630
631 @Parameter( property = "invoker.mergeUserSettings", defaultValue = "false" )
632 private boolean mergeUserSettings;
633
634
635
636
637
638
639 @Parameter
640 private Map<String, String> environmentVariables;
641
642
643
644
645
646
647 @Parameter
648 private Map<String, String> scriptVariables;
649
650
651
652
653
654 @Parameter( defaultValue = "0", property = "invoker.timeoutInSeconds" )
655 private int timeoutInSeconds;
656
657
658
659
660
661 @Parameter( defaultValue = "false", property = "invoker.writeJunitReport" )
662 private boolean writeJunitReport;
663
664
665
666
667
668 @Parameter( defaultValue = "maven.invoker.it", property = "invoker.junitPackageName" )
669 private String junitPackageName = "maven.invoker.it";
670
671
672
673
674
675
676
677 @Parameter( defaultValue = "false", property = "invoker.updateOnly" )
678 private boolean updateOnly = false;
679
680
681
682
683
684
685 private ScriptRunner scriptRunner;
686
687
688
689
690
691
692 private String filteredPomPrefix = "interpolated-";
693
694
695
696
697 private final DecimalFormat secFormat = new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) );
698
699
700
701
702 private String actualMavenVersion;
703
704
705
706 @Parameter( property = "plugin.artifacts", required = true, readonly = true )
707 private List<Artifact> pluginArtifacts;
708
709 @Parameter( defaultValue = "${project.testClasspathElements}", readonly = true )
710 private List<String> testClassPath;
711
712 @Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
713 private MojoExecution mojoExecution;
714
715 @Parameter( defaultValue = "${project}", readonly = true, required = true )
716 private MavenProject project;
717
718 @Parameter( defaultValue = "${session}", readonly = true, required = true )
719 private MavenSession session;
720
721 @Parameter( defaultValue = "${settings}", readonly = true, required = true )
722 private Settings settings;
723
724 @Component
725 private Invoker invoker;
726
727 @Component
728 private SettingsBuilder settingsBuilder;
729
730 @Component
731 private ToolchainManagerPrivate toolchainManagerPrivate;
732
733
734
735
736
737
738
739 public void execute()
740 throws MojoExecutionException, MojoFailureException
741 {
742 if ( skipInvocation )
743 {
744 getLog().info( "Skipping invocation per configuration."
745 + " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
746 return;
747 }
748
749 if ( StringUtils.isEmpty( encoding ) )
750 {
751 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
752 + ", i.e. build is platform dependent!" );
753 }
754
755
756 if ( !disableReports )
757 {
758 setupReportsFolder();
759 }
760
761 List<BuildJob> buildJobs;
762 if ( pom == null )
763 {
764 try
765 {
766 buildJobs = getBuildJobs();
767 }
768 catch ( final IOException e )
769 {
770 throw new MojoExecutionException( "Error retrieving POM list from includes, "
771 + "excludes, and projects directory. Reason: " + e.getMessage(), e );
772 }
773 }
774 else
775 {
776 try
777 {
778 projectsDirectory = pom.getCanonicalFile().getParentFile();
779 }
780 catch ( IOException e )
781 {
782 throw new MojoExecutionException( "Failed to discover projectsDirectory from "
783 + "pom File parameter. Reason: " + e.getMessage(), e );
784 }
785
786 buildJobs = Collections.singletonList( new BuildJob( pom.getName() ) );
787 }
788
789 if ( buildJobs.isEmpty() )
790 {
791 doFailIfNoProjects();
792
793 getLog().info( "No projects were selected for execution." );
794 return;
795 }
796
797 setupActualMavenVersion();
798
799 handleScriptRunnerWithScriptClassPath();
800
801 Collection<String> collectedProjects = new LinkedHashSet<>();
802 for ( BuildJob buildJob : buildJobs )
803 {
804 collectProjects( projectsDirectory, buildJob.getProject(), collectedProjects, true );
805 }
806
807 File projectsDir = projectsDirectory;
808
809 if ( cloneProjectsTo == null && "maven-plugin".equals( project.getPackaging() ) )
810 {
811 cloneProjectsTo = new File( project.getBuild().getDirectory(), "its" );
812 }
813
814 if ( updateOnly )
815 {
816 if ( cloneProjectsTo == null )
817 {
818 getLog().warn( "updateOnly functionality is not supported without cloning the projects" );
819 }
820 else if ( lastModifiedRecursive( projectsDirectory ) <= lastModifiedRecursive( cloneProjectsTo ) )
821 {
822 getLog().debug( "Skipping invocation as cloned projects are up-to-date "
823 + "and updateOnly parameter is set to true." );
824 return;
825 }
826 else
827 {
828 getLog().debug( "Cloned projects are out of date" );
829 }
830 }
831
832 if ( cloneProjectsTo != null )
833 {
834 cloneProjects( collectedProjects );
835 projectsDir = cloneProjectsTo;
836 }
837 else
838 {
839 getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" );
840 }
841
842
843 List<BuildJob> setupBuildJobs = getSetupJobs( buildJobs );
844
845 if ( !setupBuildJobs.isEmpty() )
846 {
847
848
849
850 getLog().info( "Running " + setupBuildJobs.size() + " setup job"
851 + ( ( setupBuildJobs.size() < 2 ) ? "" : "s" ) + ":" );
852 runBuilds( projectsDir, setupBuildJobs, 1 );
853 getLog().info( "Setup done." );
854 }
855
856
857 List<BuildJob> nonSetupBuildJobs = getNonSetupJobs( buildJobs );
858
859 if ( setupBuildJobs.isEmpty() || setupBuildJobs.stream().allMatch( BuildJob::isNotError ) )
860 {
861
862
863 runBuilds( projectsDir, nonSetupBuildJobs, getParallelThreadsCount() );
864 }
865 else
866 {
867 for ( BuildJob buildJob : nonSetupBuildJobs )
868 {
869 buildJob.setResult( BuildJob.Result.SKIPPED );
870 buildJob.setFailureMessage( "Skipped due to setup job(s) failure" );
871 writeBuildReport( buildJob );
872 }
873 }
874
875 writeSummaryFile( buildJobs );
876 processResults( new InvokerSession( buildJobs ) );
877 }
878
879 private void setupActualMavenVersion() throws MojoExecutionException
880 {
881 if ( mavenHome != null )
882 {
883 try
884 {
885 actualMavenVersion = SelectorUtils.getMavenVersion( mavenHome );
886 }
887 catch ( IOException e )
888 {
889 throw new MojoExecutionException( e.getMessage(), e );
890 }
891 }
892 else
893 {
894 actualMavenVersion = SelectorUtils.getMavenVersion();
895 }
896 }
897
898
899
900
901
902
903
904 private long lastModifiedRecursive( File file )
905 {
906 long lastModified = file.lastModified();
907
908 final File[] entries = file.listFiles();
909
910 if ( entries != null )
911 {
912 for ( File entry : entries )
913 {
914 lastModified = Math.max( lastModified, lastModifiedRecursive( entry ) );
915 }
916 }
917
918 return lastModified;
919 }
920
921
922
923
924
925
926 private void setupReportsFolder()
927 throws MojoExecutionException
928 {
929
930 if ( reportsDirectory.exists() )
931 {
932 try
933 {
934 FileUtils.deleteDirectory( reportsDirectory );
935 }
936 catch ( IOException e )
937 {
938 throw new MojoExecutionException( "Failure while trying to delete "
939 + reportsDirectory.getAbsolutePath(), e );
940 }
941 }
942 if ( !reportsDirectory.mkdirs() )
943 {
944 throw new MojoExecutionException( "Failure while creating the " + reportsDirectory.getAbsolutePath() );
945 }
946 }
947
948 private List<BuildJob> getSetupJobs( List<BuildJob> buildJobs )
949 {
950 return buildJobs.stream().
951 filter( buildJob -> buildJob.getType().equals( BuildJob.Type.SETUP ) ).
952 collect( Collectors.toList() );
953 }
954
955 private List<BuildJob> getNonSetupJobs( List<BuildJob> buildJobs )
956 {
957 return buildJobs.stream().
958 filter( buildJob -> !buildJob.getType().equals( BuildJob.Type.SETUP ) ).
959 collect( Collectors.toList() );
960 }
961
962 private void handleScriptRunnerWithScriptClassPath()
963 {
964 final List<String> scriptClassPath;
965 if ( addTestClassPath )
966 {
967 scriptClassPath = new ArrayList<>( testClassPath );
968 for ( Artifact pluginArtifact : pluginArtifacts )
969 {
970 scriptClassPath.remove( pluginArtifact.getFile().getAbsolutePath() );
971 }
972 }
973 else
974 {
975 scriptClassPath = null;
976 }
977 scriptRunner = new ScriptRunner( );
978 scriptRunner.setScriptEncoding( encoding );
979 scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath );
980 scriptRunner.setGlobalVariable( "mavenVersion", actualMavenVersion );
981 if ( scriptVariables != null )
982 {
983 scriptVariables.forEach( ( key, value ) -> scriptRunner.setGlobalVariable( key, value ) );
984 }
985 scriptRunner.setClassPath( scriptClassPath );
986 }
987
988 private void writeSummaryFile( List<BuildJob> buildJobs )
989 throws MojoExecutionException
990 {
991
992 File summaryReportFile = new File( reportsDirectory, "invoker-summary.txt" );
993
994 try ( Writer writer = new BufferedWriter( new FileWriter( summaryReportFile ) ) )
995 {
996 for ( BuildJob buildJob : buildJobs )
997 {
998 if ( !BuildJob.Result.SUCCESS.equals( buildJob.getResult() ) )
999 {
1000 writer.append( buildJob.getResult() );
1001 writer.append( " [" );
1002 writer.append( buildJob.getProject() );
1003 writer.append( "] " );
1004 if ( buildJob.getFailureMessage() != null )
1005 {
1006 writer.append( " " );
1007 writer.append( buildJob.getFailureMessage() );
1008 }
1009 writer.append( "\n" );
1010 }
1011 }
1012 }
1013 catch ( IOException e )
1014 {
1015 throw new MojoExecutionException( "Failed to write summary report " + summaryReportFile, e );
1016 }
1017 }
1018
1019 protected void doFailIfNoProjects()
1020 throws MojoFailureException
1021 {
1022
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032 abstract void processResults( InvokerSession invokerSession )
1033 throws MojoFailureException;
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 private void collectProjects( File projectsDir, String projectPath, Collection<String> projectPaths,
1050 boolean included )
1051 throws MojoExecutionException
1052 {
1053 projectPath = projectPath.replace( '\\', '/' );
1054 File pomFile = new File( projectsDir, projectPath );
1055 if ( pomFile.isDirectory() )
1056 {
1057 pomFile = new File( pomFile, "pom.xml" );
1058 if ( !pomFile.exists() )
1059 {
1060 if ( included )
1061 {
1062 projectPaths.add( projectPath );
1063 }
1064 return;
1065 }
1066 if ( !projectPath.endsWith( "/" ) )
1067 {
1068 projectPath += '/';
1069 }
1070 projectPath += "pom.xml";
1071 }
1072 else if ( !pomFile.isFile() )
1073 {
1074 return;
1075 }
1076 if ( !projectPaths.add( projectPath ) )
1077 {
1078 return;
1079 }
1080 getLog().debug( "Collecting parent/child projects of " + projectPath );
1081
1082 Model model = PomUtils.loadPom( pomFile );
1083
1084 try
1085 {
1086 String projectsRoot = projectsDir.getCanonicalPath();
1087 String projectDir = pomFile.getParent();
1088
1089 String parentPath = "../pom.xml";
1090 if ( model.getParent() != null && StringUtils.isNotEmpty( model.getParent().getRelativePath() ) )
1091 {
1092 parentPath = model.getParent().getRelativePath();
1093 }
1094 String parent = relativizePath( new File( projectDir, parentPath ), projectsRoot );
1095 if ( parent != null )
1096 {
1097 collectProjects( projectsDir, parent, projectPaths, false );
1098 }
1099
1100 Collection<String> modulePaths = new LinkedHashSet<>( model.getModules() );
1101
1102 model.getProfiles().forEach( profile -> modulePaths.addAll( profile.getModules() ) );
1103
1104
1105 for ( String modulePath : modulePaths )
1106 {
1107 String module = relativizePath( new File( projectDir, modulePath ), projectsRoot );
1108 if ( module != null )
1109 {
1110 collectProjects( projectsDir, module, projectPaths, false );
1111 }
1112 }
1113 }
1114 catch ( IOException e )
1115 {
1116 throw new MojoExecutionException( "Failed to analyze POM: " + pomFile, e );
1117 }
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128 private void cloneProjects( Collection<String> projectPaths )
1129 throws MojoExecutionException
1130 {
1131 if ( !cloneProjectsTo.mkdirs() && cloneClean )
1132 {
1133 try
1134 {
1135 FileUtils.cleanDirectory( cloneProjectsTo );
1136 }
1137 catch ( IOException e )
1138 {
1139 throw new MojoExecutionException( "Could not clean the cloneProjectsTo directory. Reason: "
1140 + e.getMessage(), e );
1141 }
1142 }
1143
1144
1145 Collection<String> dirs = new LinkedHashSet<>();
1146 for ( String projectPath : projectPaths )
1147 {
1148 if ( !new File( projectsDirectory, projectPath ).isDirectory() )
1149 {
1150 projectPath = getParentPath( projectPath );
1151 }
1152 dirs.add( projectPath );
1153 }
1154
1155 boolean filter;
1156
1157
1158 try
1159 {
1160 filter = !cloneProjectsTo.getCanonicalFile().equals( projectsDirectory.getCanonicalFile() );
1161
1162 List<String> clonedSubpaths = new ArrayList<>();
1163
1164 for ( String subpath : dirs )
1165 {
1166
1167 if ( !".".equals( subpath ) && dirs.contains( getParentPath( subpath ) ) )
1168 {
1169 continue;
1170 }
1171
1172
1173 if ( !alreadyCloned( subpath, clonedSubpaths ) )
1174 {
1175
1176 if ( ".".equals( subpath ) )
1177 {
1178 String cloneSubdir = relativizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() );
1179
1180
1181 if ( cloneSubdir != null )
1182 {
1183 File temp = Files.createTempDirectory( "pre-invocation-clone." ).toFile();
1184
1185 copyDirectoryStructure( projectsDirectory, temp );
1186
1187 FileUtils.deleteDirectory( new File( temp, cloneSubdir ) );
1188
1189 copyDirectoryStructure( temp, cloneProjectsTo );
1190 }
1191 else
1192 {
1193 copyDirectoryStructure( projectsDirectory, cloneProjectsTo );
1194 }
1195 }
1196 else
1197 {
1198 File srcDir = new File( projectsDirectory, subpath );
1199 File dstDir = new File( cloneProjectsTo, subpath );
1200 copyDirectoryStructure( srcDir, dstDir );
1201 }
1202
1203 clonedSubpaths.add( subpath );
1204 }
1205 }
1206 }
1207 catch ( IOException e )
1208 {
1209 throw new MojoExecutionException( "Failed to clone projects from: " + projectsDirectory + " to: "
1210 + cloneProjectsTo + ". Reason: " + e.getMessage(), e );
1211 }
1212
1213
1214 if ( filter )
1215 {
1216 for ( String projectPath : projectPaths )
1217 {
1218 File pomFile = new File( cloneProjectsTo, projectPath );
1219 if ( pomFile.isFile() )
1220 {
1221 buildInterpolatedFile( pomFile, pomFile );
1222 }
1223
1224
1225
1226
1227 File baseDir = pomFile.getParentFile();
1228 File mvnDir = new File( baseDir, ".mvn" );
1229 if ( mvnDir.isDirectory() )
1230 {
1231 File extensionsFile = new File( mvnDir, "extensions.xml" );
1232 if ( extensionsFile.isFile() )
1233 {
1234 buildInterpolatedFile( extensionsFile, extensionsFile );
1235 }
1236 }
1237
1238 }
1239 filteredPomPrefix = null;
1240 }
1241 }
1242
1243
1244
1245
1246
1247
1248
1249 private String getParentPath( String path )
1250 {
1251 int lastSep = Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ) );
1252 return ( lastSep < 0 ) ? "." : path.substring( 0, lastSep );
1253 }
1254
1255
1256
1257
1258
1259
1260
1261
1262 private void copyDirectoryStructure( File sourceDir, File destDir )
1263 throws IOException
1264 {
1265 DirectoryScanner scanner = new DirectoryScanner();
1266 scanner.setBasedir( sourceDir );
1267 if ( !cloneAllFiles )
1268 {
1269 scanner.addDefaultExcludes();
1270 }
1271 scanner.scan();
1272
1273
1274
1275
1276 destDir.mkdirs();
1277
1278 FileUtils.mkDirs( sourceDir, scanner.getIncludedDirectories(), destDir );
1279
1280 for ( String includedFile : scanner.getIncludedFiles() )
1281 {
1282 File sourceFile = new File( sourceDir, includedFile );
1283 File destFile = new File( destDir, includedFile );
1284 FileUtils.copyFile( sourceFile, destFile );
1285
1286
1287 destFile.setWritable( true );
1288 }
1289 }
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300 static boolean alreadyCloned( String subpath, List<String> clonedSubpaths )
1301 {
1302 for ( String path : clonedSubpaths )
1303 {
1304 if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) )
1305 {
1306 return true;
1307 }
1308 }
1309
1310 return false;
1311 }
1312
1313
1314
1315
1316
1317
1318
1319
1320 private void runBuilds( final File projectsDir, List<BuildJob> buildJobs, int runWithParallelThreads )
1321 throws MojoExecutionException
1322 {
1323 if ( !localRepositoryPath.exists() )
1324 {
1325 localRepositoryPath.mkdirs();
1326 }
1327
1328
1329
1330
1331
1332 File interpolatedSettingsFile = interpolateSettings( settingsFile );
1333
1334 final File mergedSettingsFile = mergeSettings( interpolatedSettingsFile );
1335
1336 final CharSequence actualJreVersion;
1337
1338 if ( javaHome != null )
1339 {
1340 actualJreVersion = resolveExternalJreVersion();
1341 }
1342 else
1343 {
1344 actualJreVersion = SelectorUtils.getJreVersion();
1345 }
1346
1347 final Path projectsPath = this.projectsDirectory.toPath();
1348
1349 Set<Path> folderGroupSet = new HashSet<>();
1350 folderGroupSet.add( Paths.get( "." ) );
1351 for ( BuildJob buildJob : buildJobs )
1352 {
1353 Path p = Paths.get( buildJob.getProject() );
1354
1355 if ( Files.isRegularFile( projectsPath.resolve( p ) ) )
1356 {
1357 p = p.getParent();
1358 }
1359
1360 if ( p != null )
1361 {
1362 p = p.getParent();
1363 }
1364
1365 while ( p != null && folderGroupSet.add( p ) )
1366 {
1367 p = p.getParent();
1368 }
1369 }
1370
1371 List<Path> folderGroup = new ArrayList<>( folderGroupSet );
1372 Collections.sort( folderGroup );
1373
1374 final Map<Path, Properties> globalInvokerProperties = new HashMap<>();
1375
1376 for ( Path path : folderGroup )
1377 {
1378 Properties ancestorProperties = globalInvokerProperties.get( projectsPath.resolve( path ).getParent() );
1379
1380 Path currentInvokerProperties = projectsPath.resolve( path ).resolve( invokerPropertiesFile );
1381
1382 Properties currentProperties;
1383 if ( Files.isRegularFile( currentInvokerProperties ) )
1384 {
1385 if ( ancestorProperties != null )
1386 {
1387 currentProperties = new Properties( ancestorProperties );
1388
1389 }
1390 else
1391 {
1392 currentProperties = new Properties();
1393 }
1394 }
1395 else
1396 {
1397 currentProperties = ancestorProperties;
1398 }
1399
1400 if ( Files.isRegularFile( currentInvokerProperties ) )
1401 {
1402 try ( InputStream in = new FileInputStream( currentInvokerProperties.toFile() ) )
1403 {
1404 currentProperties.load( in );
1405 }
1406 catch ( IOException e )
1407 {
1408 throw new MojoExecutionException( "Failed to read invoker properties: "
1409 + currentInvokerProperties );
1410 }
1411 }
1412
1413 if ( currentProperties != null )
1414 {
1415 globalInvokerProperties.put( projectsPath.resolve( path ).normalize(), currentProperties );
1416 }
1417 }
1418
1419 try
1420 {
1421 if ( runWithParallelThreads > 1 )
1422 {
1423 getLog().info( "use parallelThreads " + runWithParallelThreads );
1424 }
1425
1426 JobExecutor jobExecutor = new JobExecutor( buildJobs, runWithParallelThreads );
1427 jobExecutor.forEach( job ->
1428 {
1429 Path ancestorFolder = getAncestorFolder( projectsPath.resolve( job.getProject() ) );
1430
1431 runBuild( projectsDir, job, mergedSettingsFile, javaHome, actualJreVersion,
1432 globalInvokerProperties.get( ancestorFolder ) );
1433
1434 } );
1435 }
1436 finally
1437 {
1438 if ( interpolatedSettingsFile != null && cloneProjectsTo == null )
1439 {
1440 interpolatedSettingsFile.delete();
1441 }
1442 if ( mergedSettingsFile != null && mergedSettingsFile.exists() )
1443 {
1444 mergedSettingsFile.delete();
1445 }
1446 }
1447 }
1448
1449 private Path getAncestorFolder( Path p )
1450 {
1451 Path ancestor = p;
1452 if ( Files.isRegularFile( ancestor ) )
1453 {
1454 ancestor = ancestor.getParent();
1455 }
1456 if ( ancestor != null )
1457 {
1458 ancestor = ancestor.getParent();
1459 }
1460 return ancestor;
1461 }
1462
1463
1464
1465
1466
1467
1468
1469
1470 private File interpolateSettings( File settingsFile )
1471 throws MojoExecutionException
1472 {
1473 File interpolatedSettingsFile = null;
1474 if ( settingsFile != null )
1475 {
1476 if ( cloneProjectsTo != null )
1477 {
1478 interpolatedSettingsFile = new File( cloneProjectsTo, "interpolated-" + settingsFile.getName() );
1479 }
1480 else
1481 {
1482 interpolatedSettingsFile =
1483 new File( settingsFile.getParentFile(), "interpolated-" + settingsFile.getName() );
1484 }
1485 buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
1486 }
1487 return interpolatedSettingsFile;
1488 }
1489
1490
1491
1492
1493
1494
1495
1496
1497 private File mergeSettings( File interpolatedSettingsFile )
1498 throws MojoExecutionException
1499 {
1500 File mergedSettingsFile;
1501 Settings mergedSettings = this.settings;
1502 if ( mergeUserSettings )
1503 {
1504 if ( interpolatedSettingsFile != null )
1505 {
1506
1507 try
1508 {
1509 SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
1510 request.setGlobalSettingsFile( interpolatedSettingsFile );
1511
1512 Settings dominantSettings = settingsBuilder.build( request ).getEffectiveSettings();
1513 Settings recessiveSettings = cloneSettings();
1514 SettingsUtils.merge( dominantSettings, recessiveSettings, TrackableBase.USER_LEVEL );
1515
1516 mergedSettings = dominantSettings;
1517 getLog().debug( "Merged specified settings file with settings of invoking process" );
1518 }
1519 catch ( SettingsBuildingException e )
1520 {
1521 throw new MojoExecutionException( "Could not read specified settings file", e );
1522 }
1523 }
1524 }
1525
1526 if ( this.settingsFile != null && !mergeUserSettings )
1527 {
1528 mergedSettingsFile = interpolatedSettingsFile;
1529 }
1530 else
1531 {
1532 try
1533 {
1534 mergedSettingsFile = writeMergedSettingsFile( mergedSettings );
1535 }
1536 catch ( IOException e )
1537 {
1538 throw new MojoExecutionException( "Could not create temporary file for invoker settings.xml", e );
1539 }
1540 }
1541 return mergedSettingsFile;
1542 }
1543
1544 private File writeMergedSettingsFile( Settings mergedSettings )
1545 throws IOException
1546 {
1547 File mergedSettingsFile;
1548 mergedSettingsFile = File.createTempFile( "invoker-settings", ".xml" );
1549
1550 SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
1551
1552
1553 try ( FileWriter fileWriter = new FileWriter( mergedSettingsFile ) )
1554 {
1555 settingsWriter.write( fileWriter, mergedSettings );
1556 }
1557
1558 if ( getLog().isDebugEnabled() )
1559 {
1560 getLog().debug( "Created temporary file for invoker settings.xml: "
1561 + mergedSettingsFile.getAbsolutePath() );
1562 }
1563 return mergedSettingsFile;
1564 }
1565
1566 private Settings cloneSettings()
1567 {
1568 Settings recessiveSettings = SettingsUtils.copySettings( this.settings );
1569
1570
1571 resetSourceLevelSet( recessiveSettings );
1572 for ( org.apache.maven.settings.Mirror mirror : recessiveSettings.getMirrors() )
1573 {
1574 resetSourceLevelSet( mirror );
1575 }
1576 for ( org.apache.maven.settings.Server server : recessiveSettings.getServers() )
1577 {
1578 resetSourceLevelSet( server );
1579 }
1580 for ( org.apache.maven.settings.Proxy proxy : recessiveSettings.getProxies() )
1581 {
1582 resetSourceLevelSet( proxy );
1583 }
1584 for ( org.apache.maven.settings.Profile profile : recessiveSettings.getProfiles() )
1585 {
1586 resetSourceLevelSet( profile );
1587 }
1588
1589 return recessiveSettings;
1590 }
1591
1592 private void resetSourceLevelSet( org.apache.maven.settings.TrackableBase trackable )
1593 {
1594 try
1595 {
1596 ReflectionUtils.setVariableValueInObject( trackable, "sourceLevelSet", Boolean.FALSE );
1597 getLog().debug( "sourceLevelSet: "
1598 + ReflectionUtils.getValueIncludingSuperclasses( "sourceLevelSet", trackable ) );
1599 }
1600 catch ( IllegalAccessException e )
1601 {
1602
1603 }
1604 }
1605
1606 private CharSequence resolveExternalJreVersion()
1607 {
1608 Artifact pluginArtifact = mojoExecution.getMojoDescriptor().getPluginDescriptor().getPluginArtifact();
1609 pluginArtifact.getFile();
1610
1611 Commandline commandLine = new Commandline();
1612 commandLine.setExecutable( new File( javaHome, "bin/java" ).getAbsolutePath() );
1613 commandLine.createArg().setValue( "-cp" );
1614 commandLine.createArg().setFile( pluginArtifact.getFile() );
1615 commandLine.createArg().setValue( SystemPropertyPrinter.class.getName() );
1616 commandLine.createArg().setValue( "java.version" );
1617
1618 final StringBuilder actualJreVersion = new StringBuilder();
1619 StreamConsumer consumer = actualJreVersion::append;
1620 try
1621 {
1622 CommandLineUtils.executeCommandLine( commandLine, consumer, null );
1623 }
1624 catch ( CommandLineException e )
1625 {
1626 getLog().warn( e.getMessage() );
1627 }
1628 return actualJreVersion;
1629 }
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640 private File interpolatePomFile( File pomFile, File basedir )
1641 throws MojoExecutionException
1642 {
1643 File interpolatedPomFile = null;
1644 if ( pomFile != null )
1645 {
1646 if ( StringUtils.isNotEmpty( filteredPomPrefix ) )
1647 {
1648 interpolatedPomFile = new File( basedir, filteredPomPrefix + pomFile.getName() );
1649 buildInterpolatedFile( pomFile, interpolatedPomFile );
1650 }
1651 else
1652 {
1653 interpolatedPomFile = pomFile;
1654 }
1655 }
1656 return interpolatedPomFile;
1657 }
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669 private void runBuild( File projectsDir, BuildJob buildJob, File settingsFile, File actualJavaHome,
1670 CharSequence actualJreVersion, Properties globalInvokerProperties )
1671 throws MojoExecutionException
1672 {
1673
1674 File pomFile = new File( projectsDir, buildJob.getProject() );
1675 File basedir;
1676 if ( pomFile.isDirectory() )
1677 {
1678 basedir = pomFile;
1679 pomFile = new File( basedir, "pom.xml" );
1680 if ( !pomFile.exists() )
1681 {
1682 pomFile = null;
1683 }
1684 else
1685 {
1686 buildJob.setProject( buildJob.getProject() + File.separator + "pom.xml" );
1687 }
1688 }
1689 else
1690 {
1691 basedir = pomFile.getParentFile();
1692 }
1693
1694 File interpolatedPomFile = interpolatePomFile( pomFile, basedir );
1695
1696
1697 getLog().info( buffer().a( "Building: " ).strong( buildJob.getProject() ).toString() );
1698
1699 InvokerProperties invokerProperties = getInvokerProperties( basedir, globalInvokerProperties );
1700
1701
1702 buildJob.setName( invokerProperties.getJobName() );
1703 buildJob.setDescription( invokerProperties.getJobDescription() );
1704
1705 try
1706 {
1707 int selection = getSelection( invokerProperties, actualJreVersion );
1708 if ( selection == 0 )
1709 {
1710 long milliseconds = System.currentTimeMillis();
1711 boolean executed;
1712
1713 FileLogger buildLogger = setupBuildLogFile( basedir );
1714 if ( buildLogger != null )
1715 {
1716 buildJob.setBuildlog( buildLogger.getOutputFile().getAbsolutePath() );
1717 }
1718
1719 try
1720 {
1721 executed = runBuild( basedir, interpolatedPomFile, settingsFile, actualJavaHome,
1722 invokerProperties, buildLogger );
1723 }
1724 finally
1725 {
1726 milliseconds = System.currentTimeMillis() - milliseconds;
1727 buildJob.setTime( milliseconds / 1000.0 );
1728
1729 if ( buildLogger != null )
1730 {
1731 buildLogger.close();
1732 }
1733 }
1734
1735 if ( executed )
1736 {
1737 buildJob.setResult( BuildJob.Result.SUCCESS );
1738
1739 if ( !suppressSummaries )
1740 {
1741 getLog().info( pad( buildJob ).success( "SUCCESS" ).a( ' ' )
1742 + formatTime( buildJob.getTime() ) );
1743 }
1744 }
1745 else
1746 {
1747 buildJob.setResult( BuildJob.Result.SKIPPED );
1748
1749 if ( !suppressSummaries )
1750 {
1751 getLog().info( pad( buildJob ).warning( "SKIPPED" ).a( ' ' )
1752 + formatTime( buildJob.getTime() ) );
1753 }
1754 }
1755 }
1756 else
1757 {
1758 buildJob.setResult( BuildJob.Result.SKIPPED );
1759
1760 List<String> messages = new ArrayList<>();
1761
1762 if ( selection == Selector.SELECTOR_MULTI )
1763 {
1764 messages.add( "non-matching selectors" );
1765 }
1766 else
1767 {
1768 if ( ( selection & Selector.SELECTOR_MAVENVERSION ) != 0 )
1769 {
1770 messages.add( "Maven version" );
1771 }
1772 if ( ( selection & Selector.SELECTOR_JREVERSION ) != 0 )
1773 {
1774 messages.add( "JRE version" );
1775 }
1776 if ( ( selection & Selector.SELECTOR_OSFAMILY ) != 0 )
1777 {
1778 messages.add( "OS" );
1779 }
1780 if ( ( selection & Selector.SELECTOR_TOOLCHAIN ) != 0 )
1781 {
1782 messages.add( "Toolchain" );
1783 }
1784 }
1785
1786 String message = String.join( ", ", messages );
1787 if ( !suppressSummaries )
1788 {
1789 getLog().info( pad( buildJob ).warning( "SKIPPED" ) + " due to " + message );
1790 }
1791
1792
1793
1794 buildJob.setFailureMessage( "Skipped due to " + message );
1795 }
1796 }
1797 catch ( RunFailureException e )
1798 {
1799 buildJob.setResult( e.getType() );
1800 buildJob.setFailureMessage( e.getMessage() );
1801
1802 if ( !suppressSummaries )
1803 {
1804 getLog().info( " " + e.getMessage() );
1805 getLog().info( pad( buildJob ).failure( "FAILED" ).a( ' ' ) + formatTime( buildJob.getTime() ) );
1806 }
1807 }
1808 finally
1809 {
1810 deleteInterpolatedPomFile( interpolatedPomFile );
1811 writeBuildReport( buildJob );
1812 }
1813 }
1814
1815 private MessageBuilder pad( BuildJob buildJob )
1816 {
1817 MessageBuilder buffer = buffer( 128 );
1818
1819 buffer.a( " " );
1820 buffer.a( buildJob.getProject() );
1821
1822 int l = 10 + buildJob.getProject().length();
1823
1824 if ( l < RESULT_COLUMN )
1825 {
1826 buffer.a( ' ' );
1827 l++;
1828
1829 if ( l < RESULT_COLUMN )
1830 {
1831 for ( int i = RESULT_COLUMN - l; i > 0; i-- )
1832 {
1833 buffer.a( '.' );
1834 }
1835 }
1836 }
1837
1838 return buffer.a( ' ' );
1839 }
1840
1841
1842
1843
1844
1845
1846 private void deleteInterpolatedPomFile( File interpolatedPomFile )
1847 {
1848 if ( interpolatedPomFile != null && StringUtils.isNotEmpty( filteredPomPrefix ) )
1849 {
1850 interpolatedPomFile.delete();
1851 }
1852 }
1853
1854
1855
1856
1857
1858
1859
1860
1861 private int getSelection( InvokerProperties invokerProperties, CharSequence actualJreVersion )
1862 {
1863 return new Selector( actualMavenVersion, actualJreVersion.toString(),
1864 getToolchainPrivateManager() ).getSelection( invokerProperties );
1865 }
1866
1867 private ToolchainPrivateManager getToolchainPrivateManager()
1868 {
1869 return new ToolchainPrivateManager( toolchainManagerPrivate, session );
1870 }
1871
1872
1873
1874
1875
1876
1877
1878 private void writeBuildReport( BuildJob buildJob )
1879 throws MojoExecutionException
1880 {
1881 if ( disableReports )
1882 {
1883 return;
1884 }
1885
1886 String safeFileName = buildJob.getProject().replace( '/', '_' ).replace( '\\', '_' ).replace( ' ', '_' );
1887 if ( safeFileName.endsWith( "_pom.xml" ) )
1888 {
1889 safeFileName = safeFileName.substring( 0, safeFileName.length() - "_pom.xml".length() );
1890 }
1891
1892 File reportFile = new File( reportsDirectory, "BUILD-" + safeFileName + ".xml" );
1893 try ( FileOutputStream fos = new FileOutputStream( reportFile );
1894 Writer osw = new OutputStreamWriter( fos, buildJob.getModelEncoding() ) )
1895 {
1896 BuildJobXpp3Writer writer = new BuildJobXpp3Writer();
1897
1898 writer.write( osw, buildJob );
1899 }
1900 catch ( IOException e )
1901 {
1902 throw new MojoExecutionException( "Failed to write build report " + reportFile, e );
1903 }
1904
1905 if ( writeJunitReport )
1906 {
1907 writeJunitReport( buildJob, safeFileName );
1908 }
1909 }
1910
1911 private void writeJunitReport( BuildJob buildJob, String safeFileName )
1912 throws MojoExecutionException
1913 {
1914 File reportFile = new File( reportsDirectory, "TEST-" + safeFileName + ".xml" );
1915 Xpp3Dom testsuite = new Xpp3Dom( "testsuite" );
1916 testsuite.setAttribute( "name", junitPackageName + "." + safeFileName );
1917 testsuite.setAttribute( "time", Double.toString( buildJob.getTime() ) );
1918
1919
1920 testsuite.setAttribute( "tests", "1" );
1921 testsuite.setAttribute( "errors", "0" );
1922 testsuite.setAttribute( "skipped", "0" );
1923 testsuite.setAttribute( "failures", "0" );
1924
1925 Xpp3Dom testcase = new Xpp3Dom( "testcase" );
1926 testsuite.addChild( testcase );
1927 switch ( buildJob.getResult() )
1928 {
1929 case BuildJob.Result.SUCCESS:
1930 break;
1931 case BuildJob.Result.SKIPPED:
1932 testsuite.setAttribute( "skipped", "1" );
1933
1934 Xpp3Dom skipped = new Xpp3Dom( "skipped" );
1935 testcase.addChild( skipped );
1936 skipped.setValue( buildJob.getFailureMessage() );
1937 break;
1938 case BuildJob.Result.ERROR:
1939 testsuite.setAttribute( "errors", "1" );
1940 break;
1941 default:
1942 testsuite.setAttribute( "failures", "1" );
1943
1944 Xpp3Dom failure = new Xpp3Dom( "failure" );
1945 testcase.addChild( failure );
1946 failure.setAttribute( "message", buildJob.getFailureMessage() );
1947 }
1948 testcase.setAttribute( "classname", junitPackageName + "." + safeFileName );
1949 testcase.setAttribute( "name", safeFileName );
1950 testcase.setAttribute( "time", Double.toString( buildJob.getTime() ) );
1951 Xpp3Dom systemOut = new Xpp3Dom( "system-out" );
1952 testcase.addChild( systemOut );
1953
1954
1955 File buildLogFile = buildJob.getBuildlog() != null ? new File( buildJob.getBuildlog() ) : null;
1956
1957 if ( buildLogFile != null && buildLogFile.exists() )
1958 {
1959 getLog().debug( "fileLogger:" + buildLogFile );
1960 try
1961 {
1962 systemOut.setValue( FileUtils.fileRead( buildLogFile ) );
1963 }
1964 catch ( IOException e )
1965 {
1966 throw new MojoExecutionException( "Failed to read logfile " + buildLogFile, e );
1967 }
1968 }
1969 else
1970 {
1971 getLog().debug( safeFileName + "not exists buildLogFile = " + buildLogFile );
1972 }
1973
1974 try ( FileOutputStream fos = new FileOutputStream( reportFile );
1975 Writer osw = new OutputStreamWriter( fos, buildJob.getModelEncoding() ) )
1976 {
1977 Xpp3DomWriter.write( osw, testsuite );
1978 } catch ( IOException e )
1979 {
1980 throw new MojoExecutionException( "Failed to write JUnit build report " + reportFile, e );
1981 }
1982 }
1983
1984
1985
1986
1987
1988
1989
1990 private String formatTime( double seconds )
1991 {
1992 return secFormat.format( seconds );
1993 }
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010 private boolean runBuild( File basedir, File pomFile, File settingsFile, File actualJavaHome,
2011 InvokerProperties invokerProperties, FileLogger logger )
2012 throws MojoExecutionException, RunFailureException
2013 {
2014 if ( getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty() )
2015 {
2016 Properties props = invokerProperties.getProperties();
2017 getLog().debug( "Using invoker properties:" );
2018 for ( String key : new TreeSet<>( props.stringPropertyNames() ) )
2019 {
2020 String value = props.getProperty( key );
2021 getLog().debug( " " + key + " = " + value );
2022 }
2023 }
2024
2025 Map<String, Object> context = new LinkedHashMap<>();
2026
2027 boolean selectorResult = true;
2028
2029 try
2030 {
2031 try
2032 {
2033 scriptRunner.run( "selector script", basedir, selectorScript, context, logger );
2034 }
2035 catch ( ScriptReturnException e )
2036 {
2037 selectorResult = false;
2038 return false;
2039 }
2040 catch ( ScriptException e )
2041 {
2042 throw new RunFailureException( BuildJob.Result.ERROR, e );
2043 }
2044
2045 try
2046 {
2047 scriptRunner.run( "pre-build script", basedir, preBuildHookScript, context, logger );
2048 }
2049 catch ( ScriptException e )
2050 {
2051 throw new RunFailureException( BuildJob.Result.FAILURE_PRE_HOOK, e );
2052 }
2053
2054 for ( int invocationIndex = 1;; invocationIndex++ )
2055 {
2056 if ( invocationIndex > 1 && !invokerProperties.isInvocationDefined( invocationIndex ) )
2057 {
2058 break;
2059 }
2060
2061 final InvocationRequest request = new DefaultInvocationRequest();
2062
2063 request.setBatchMode( true );
2064
2065
2066 request.setLocalRepositoryDirectory( localRepositoryPath );
2067 request.setShowErrors( showErrors );
2068 request.setShowVersion( showVersion );
2069 request.setJavaHome( actualJavaHome );
2070 request.setMavenHome( mavenHome );
2071 setupLoggerForBuildJob( logger, request );
2072
2073 request.setBaseDirectory( basedir );
2074 request.setPomFile( pomFile );
2075
2076 String customSettingsFile = invokerProperties.getSettingsFile( invocationIndex );
2077 if ( customSettingsFile != null )
2078 {
2079 File interpolateSettingsFile = interpolateSettings( new File( customSettingsFile ) );
2080 File mergeSettingsFile = mergeSettings( interpolateSettingsFile );
2081
2082 request.setUserSettingsFile( mergeSettingsFile );
2083 }
2084 else
2085 {
2086 request.setUserSettingsFile( settingsFile );
2087 }
2088
2089 Properties systemProperties =
2090 getSystemProperties( basedir, invokerProperties.getSystemPropertiesFile( invocationIndex ) );
2091 request.setProperties( systemProperties );
2092
2093 invokerProperties.configureInvocation( request, invocationIndex );
2094
2095 if ( getLog().isDebugEnabled() )
2096 {
2097 try
2098 {
2099 getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() );
2100 getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) );
2101 }
2102 catch ( CommandLineConfigurationException e )
2103 {
2104 getLog().debug( "Failed to display command line: " + e.getMessage() );
2105 }
2106 }
2107
2108 try
2109 {
2110 InvocationResult result = invoker.execute( request );
2111 verify( result, invocationIndex, invokerProperties, logger );
2112 }
2113 catch ( final MavenInvocationException e )
2114 {
2115 getLog().debug( "Error invoking Maven: " + e.getMessage(), e );
2116 throw new RunFailureException( "Maven invocation failed. " + e.getMessage(),
2117 BuildJob.Result.FAILURE_BUILD );
2118 }
2119 }
2120 }
2121 catch ( IOException e )
2122 {
2123 throw new MojoExecutionException( e.getMessage(), e );
2124 }
2125 finally
2126 {
2127 if ( selectorResult )
2128 {
2129 runPostBuildHook( basedir, context, logger );
2130 }
2131 }
2132 return true;
2133 }
2134
2135 int getParallelThreadsCount()
2136 {
2137 if ( parallelThreads.endsWith( "C" ) )
2138 {
2139 double parallelThreadsMultiple = Double.parseDouble(
2140 parallelThreads.substring( 0, parallelThreads.length() - 1 ) );
2141 return (int) ( parallelThreadsMultiple * Runtime.getRuntime().availableProcessors() );
2142 }
2143 else
2144 {
2145 return Integer.parseInt( parallelThreads );
2146 }
2147 }
2148
2149 private void runPostBuildHook( File basedir, Map<String, Object> context, FileLogger logger )
2150 throws MojoExecutionException, RunFailureException
2151 {
2152 try
2153 {
2154 scriptRunner.run( "post-build script", basedir, postBuildHookScript, context, logger );
2155 }
2156 catch ( IOException e )
2157 {
2158 throw new MojoExecutionException( e.getMessage(), e );
2159 }
2160 catch ( ScriptException e )
2161 {
2162 throw new RunFailureException( e.getMessage(), BuildJob.Result.FAILURE_POST_HOOK, e );
2163 }
2164 }
2165 private void setupLoggerForBuildJob( final FileLogger logger, final InvocationRequest request )
2166 {
2167 if ( logger != null )
2168 {
2169 request.setErrorHandler( logger );
2170 request.setOutputHandler( logger );
2171 }
2172 }
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182 private FileLogger setupBuildLogFile( File basedir )
2183 throws MojoExecutionException
2184 {
2185 FileLogger logger = null;
2186
2187 if ( !noLog )
2188 {
2189 Path projectLogDirectory;
2190 if ( logDirectory == null )
2191 {
2192 projectLogDirectory = basedir.toPath();
2193 }
2194 else if ( cloneProjectsTo != null )
2195 {
2196 projectLogDirectory =
2197 logDirectory.toPath().resolve( cloneProjectsTo.toPath().relativize( basedir.toPath() ) );
2198 }
2199 else
2200 {
2201 projectLogDirectory =
2202 logDirectory.toPath().resolve( projectsDirectory.toPath().relativize( basedir.toPath() ) );
2203 }
2204
2205 try
2206 {
2207 if ( streamLogs )
2208 {
2209 logger = new FileLogger( projectLogDirectory.resolve( "build.log" ).toFile(), getLog() );
2210 }
2211 else
2212 {
2213 logger = new FileLogger( projectLogDirectory.resolve( "build.log" ).toFile() );
2214 }
2215
2216 getLog().debug( "Build log initialized in: " + projectLogDirectory );
2217 }
2218 catch ( IOException e )
2219 {
2220 throw new MojoExecutionException( "Error initializing build logfile in: " + projectLogDirectory, e );
2221 }
2222 }
2223
2224 return logger;
2225 }
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236 private Properties getSystemProperties( final File basedir, final String filename )
2237 throws MojoExecutionException
2238 {
2239 Properties collectedTestProperties = new Properties();
2240
2241 if ( properties != null )
2242 {
2243
2244 for ( Map.Entry<String, String> entry : properties.entrySet() )
2245 {
2246 if ( entry.getValue() != null )
2247 {
2248 collectedTestProperties.put( entry.getKey(), entry.getValue() );
2249 }
2250 }
2251 }
2252
2253 File propertiesFile = null;
2254 if ( filename != null )
2255 {
2256 propertiesFile = new File( basedir, filename );
2257 }
2258 else if ( testPropertiesFile != null )
2259 {
2260 propertiesFile = new File( basedir, testPropertiesFile );
2261 }
2262
2263 if ( propertiesFile != null && propertiesFile.isFile() )
2264 {
2265
2266 try ( InputStream fin = new FileInputStream( propertiesFile ) )
2267 {
2268 Properties loadedProperties = new Properties();
2269 loadedProperties.load( fin );
2270 collectedTestProperties.putAll( loadedProperties );
2271 }
2272 catch ( IOException e )
2273 {
2274 throw new MojoExecutionException( "Error reading system properties from " + propertiesFile );
2275 }
2276 }
2277
2278 return collectedTestProperties;
2279 }
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289 private void verify( InvocationResult result, int invocationIndex, InvokerProperties invokerProperties,
2290 FileLogger logger )
2291 throws RunFailureException
2292 {
2293 if ( result.getExecutionException() != null )
2294 {
2295 throw new RunFailureException( "The Maven invocation failed. "
2296 + result.getExecutionException().getMessage(), BuildJob.Result.ERROR );
2297 }
2298 else if ( !invokerProperties.isExpectedResult( result.getExitCode(), invocationIndex ) )
2299 {
2300 StringBuilder buffer = new StringBuilder( 256 );
2301 buffer.append( "The build exited with code " ).append( result.getExitCode() ).append( ". " );
2302 if ( logger != null )
2303 {
2304 buffer.append( "See " );
2305 buffer.append( logger.getOutputFile().getAbsolutePath() );
2306 buffer.append( " for details." );
2307 }
2308 else
2309 {
2310 buffer.append( "See console output for details." );
2311 }
2312 throw new RunFailureException( buffer.toString(), BuildJob.Result.FAILURE_BUILD );
2313 }
2314 }
2315
2316 private List<String> calculateIncludes()
2317 {
2318 if ( invokerTest != null )
2319 {
2320 String[] testRegexes = StringUtils.split( invokerTest, "," );
2321 return Arrays.stream( testRegexes )
2322 .map( String::trim )
2323 .filter( s -> !s.isEmpty() )
2324 .filter( s -> !s.startsWith( "!" ) )
2325 .collect( Collectors.toList() );
2326 }
2327 else
2328 {
2329 Set<String> uniqueIncludes = new HashSet<>();
2330 uniqueIncludes.addAll( pomIncludes );
2331 uniqueIncludes.addAll( setupIncludes );
2332 return new ArrayList<>( uniqueIncludes );
2333 }
2334 }
2335
2336 private List<String> calculateExcludes()
2337 throws IOException
2338 {
2339 List<String> excludes;
2340
2341 if ( invokerTest != null )
2342 {
2343 String[] testRegexes = StringUtils.split( invokerTest, "," );
2344 excludes = Arrays.stream( testRegexes )
2345 .map( String::trim )
2346 .filter( s -> !s.isEmpty() )
2347 .filter( s -> s.startsWith( "!" ) )
2348 .map( s -> s.substring( 1 ) )
2349 .collect( Collectors.toList() );
2350 }
2351 else
2352 {
2353 excludes = pomExcludes != null ? new ArrayList<>( pomExcludes ) : new ArrayList<>();
2354 }
2355
2356 if ( this.settingsFile != null )
2357 {
2358 String exclude = relativizePath( this.settingsFile, projectsDirectory.getCanonicalPath() );
2359 if ( exclude != null )
2360 {
2361 excludes.add( exclude.replace( '\\', '/' ) );
2362 getLog().debug( "Automatically excluded " + exclude + " from project scanning" );
2363 }
2364 }
2365 return excludes;
2366
2367 }
2368
2369
2370
2371
2372
2373
2374
2375 List<BuildJob> getBuildJobs()
2376 throws IOException, MojoExecutionException
2377 {
2378
2379 List<String> includes = calculateIncludes();
2380 List<String> excludes = calculateExcludes();
2381 List<BuildJob> buildJobsAll = scanProjectsDirectory( includes, excludes );
2382 List<BuildJob> buildJobsSetup = scanProjectsDirectory( setupIncludes, excludes );
2383
2384 List<String> setupProjects = buildJobsSetup.stream()
2385 .map( BuildJob::getProject )
2386 .collect( Collectors.toList() );
2387
2388
2389 for ( BuildJob job : buildJobsAll )
2390 {
2391 if ( setupProjects.contains( job.getProject() ) )
2392 {
2393 job.setType( BuildJob.Type.SETUP );
2394 }
2395 InvokerProperties invokerProperties =
2396 getInvokerProperties( new File( projectsDirectory, job.getProject() ).getParentFile(),
2397 null );
2398 job.setOrdinal( invokerProperties.getOrdinal() );
2399 }
2400
2401 relativizeProjectPaths( buildJobsAll );
2402
2403 return buildJobsAll;
2404 }
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417 private List<BuildJob> scanProjectsDirectory( List<String> includes, List<String> excludes )
2418 throws IOException
2419 {
2420 if ( !projectsDirectory.isDirectory() )
2421 {
2422 return Collections.emptyList();
2423 }
2424
2425 DirectoryScanner scanner = new DirectoryScanner();
2426 scanner.setBasedir( projectsDirectory.getCanonicalFile() );
2427 scanner.setFollowSymlinks( false );
2428 if ( includes != null )
2429 {
2430 scanner.setIncludes( includes.toArray( new String[0] ) );
2431 }
2432 if ( excludes != null )
2433 {
2434 scanner.setExcludes( excludes.toArray( new String[0] ) );
2435 }
2436 scanner.addDefaultExcludes();
2437 scanner.scan();
2438
2439 Map<String, BuildJob> matches = new LinkedHashMap<>();
2440
2441 for ( String includedFile : scanner.getIncludedFiles() )
2442 {
2443 matches.put( includedFile, new BuildJob( includedFile ) );
2444 }
2445
2446 for ( String includedDir : scanner.getIncludedDirectories() )
2447 {
2448 String includedFile = includedDir + File.separatorChar + "pom.xml";
2449 if ( new File( scanner.getBasedir(), includedFile ).isFile() )
2450 {
2451 matches.put( includedFile, new BuildJob( includedFile ) );
2452 }
2453 else
2454 {
2455 matches.put( includedDir, new BuildJob( includedDir ) );
2456 }
2457 }
2458
2459 return new ArrayList<>( matches.values() );
2460 }
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471 private void relativizeProjectPaths( List<BuildJob> buildJobs )
2472 throws IOException
2473 {
2474 String projectsDirPath = projectsDirectory.getCanonicalPath();
2475
2476 for ( BuildJob buildJob : buildJobs )
2477 {
2478 String projectPath = buildJob.getProject();
2479
2480 File file = new File( projectPath );
2481
2482 if ( !file.isAbsolute() )
2483 {
2484 file = new File( projectsDirectory, projectPath );
2485 }
2486
2487 String relativizedPath = relativizePath( file, projectsDirPath );
2488
2489 if ( relativizedPath == null )
2490 {
2491 relativizedPath = projectPath;
2492 }
2493
2494 buildJob.setProject( relativizedPath );
2495 }
2496 }
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508 private String relativizePath( File path, String basedir )
2509 throws IOException
2510 {
2511 String relativizedPath = path.getCanonicalPath();
2512
2513 if ( relativizedPath.startsWith( basedir ) )
2514 {
2515 relativizedPath = relativizedPath.substring( basedir.length() );
2516 if ( relativizedPath.startsWith( File.separator ) )
2517 {
2518 relativizedPath = relativizedPath.substring( File.separator.length() );
2519 }
2520
2521 return relativizedPath;
2522 }
2523 else
2524 {
2525 return null;
2526 }
2527 }
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537 private Map<String, Object> getInterpolationValueSource( final boolean escapeXml )
2538 {
2539 Map<String, Object> props = new HashMap<>();
2540
2541 if ( filterProperties != null )
2542 {
2543 props.putAll( filterProperties );
2544 }
2545 props.put( "basedir", this.project.getBasedir().getAbsolutePath() );
2546 props.put( "baseurl", toUrl( this.project.getBasedir().getAbsolutePath() ) );
2547 if ( settings.getLocalRepository() != null )
2548 {
2549 props.put( "localRepository", settings.getLocalRepository() );
2550 props.put( "localRepositoryUrl", toUrl( settings.getLocalRepository() ) );
2551 }
2552
2553 return new CompositeMap( this.project, props, escapeXml );
2554 }
2555
2556
2557
2558
2559
2560
2561
2562
2563 private static String toUrl( String filename )
2564 {
2565
2566
2567
2568
2569 String url = "file://" + new File( filename ).toURI().getPath();
2570 if ( url.endsWith( "/" ) )
2571 {
2572 url = url.substring( 0, url.length() - 1 );
2573 }
2574 return url;
2575 }
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590 void buildInterpolatedFile( File originalFile, File interpolatedFile )
2591 throws MojoExecutionException
2592 {
2593 getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() );
2594
2595 try
2596 {
2597 String xml;
2598
2599 Map<String, Object> composite = getInterpolationValueSource( true );
2600
2601
2602 try ( Reader reader =
2603 new InterpolationFilterReader( ReaderFactory.newXmlReader( originalFile ), composite, "@", "@" ) )
2604 {
2605 xml = IOUtil.toString( reader );
2606 }
2607
2608 try ( Writer writer = WriterFactory.newXmlWriter( interpolatedFile ) )
2609 {
2610 interpolatedFile.getParentFile().mkdirs();
2611
2612 writer.write( xml );
2613 }
2614 }
2615 catch ( IOException e )
2616 {
2617 throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e );
2618 }
2619 }
2620
2621
2622
2623
2624
2625
2626
2627
2628 private InvokerProperties getInvokerProperties( final File projectDirectory, Properties globalInvokerProperties )
2629 throws MojoExecutionException
2630 {
2631 Properties props;
2632 if ( globalInvokerProperties != null )
2633 {
2634 props = new Properties( globalInvokerProperties );
2635 }
2636 else
2637 {
2638 props = new Properties();
2639 }
2640
2641 File propertiesFile = new File( projectDirectory, invokerPropertiesFile );
2642 if ( propertiesFile.isFile() )
2643 {
2644 try ( InputStream in = new FileInputStream( propertiesFile ) )
2645 {
2646 props.load( in );
2647 }
2648 catch ( IOException e )
2649 {
2650 throw new MojoExecutionException( "Failed to read invoker properties: " + propertiesFile, e );
2651 }
2652 }
2653
2654 Interpolator interpolator = new RegexBasedInterpolator();
2655 interpolator.addValueSource( new MapBasedValueSource( getInterpolationValueSource( false ) ) );
2656
2657 for ( String key : props.stringPropertyNames() )
2658 {
2659 String value = props.getProperty( key );
2660 try
2661 {
2662 value = interpolator.interpolate( value, "" );
2663 }
2664 catch ( InterpolationException e )
2665 {
2666 throw new MojoExecutionException( "Failed to interpolate invoker properties: " + propertiesFile,
2667 e );
2668 }
2669 props.setProperty( key, value );
2670 }
2671
2672 InvokerProperties invokerProperties = new InvokerProperties( props );
2673
2674
2675 invokerProperties.setDefaultDebug( debug );
2676 invokerProperties.setDefaultQuiet( quiet );
2677 invokerProperties.setDefaultGoals( goals );
2678 invokerProperties.setDefaultProfiles( profiles );
2679 invokerProperties.setDefaultMavenExecutable( mavenExecutable );
2680 invokerProperties.setDefaultMavenOpts( mavenOpts );
2681 invokerProperties.setDefaultTimeoutInSeconds( timeoutInSeconds );
2682 invokerProperties.setDefaultEnvironmentVariables( environmentVariables );
2683
2684
2685 return invokerProperties;
2686 }
2687
2688 static class ToolchainPrivateManager
2689 {
2690 private ToolchainManagerPrivate manager;
2691
2692 private MavenSession session;
2693
2694 ToolchainPrivateManager( ToolchainManagerPrivate manager, MavenSession session )
2695 {
2696 this.manager = manager;
2697 this.session = session;
2698 }
2699
2700 ToolchainPrivate[] getToolchainPrivates( String type ) throws MisconfiguredToolchainException
2701 {
2702 return manager.getToolchainsForType( type, session );
2703 }
2704 }
2705 }