/
AbstractInvokerMojo.java
2666 lines (2381 loc) · 98.8 KB
/
AbstractInvokerMojo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
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
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.apache.maven.plugins.invoker;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.invoker.model.BuildJob;
import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Writer;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.SettingsUtils;
import org.apache.maven.settings.TrackableBase;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuilder;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsBuildingRequest;
import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
import org.apache.maven.shared.invoker.CommandLineConfigurationException;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.apache.maven.shared.scriptinterpreter.ScriptException;
import org.apache.maven.shared.scriptinterpreter.ScriptReturnException;
import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
import org.apache.maven.shared.utils.logging.MessageBuilder;
import org.apache.maven.toolchain.MisconfiguredToolchainException;
import org.apache.maven.toolchain.ToolchainManagerPrivate;
import org.apache.maven.toolchain.ToolchainPrivate;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.InterpolationFilterReader;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.ReflectionUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
/**
* Provides common code for mojos invoking sub builds.
*
* @author Stephen Connolly
* @since 15-Aug-2009 09:09:29
*/
public abstract class AbstractInvokerMojo
extends AbstractMojo
{
/**
* The zero-based column index where to print the invoker result.
*/
private static final int RESULT_COLUMN = 60;
/**
* Flag used to suppress certain invocations. This is useful in tailoring the build using profiles.
*
* @since 1.1
*/
@Parameter( property = "invoker.skip", defaultValue = "false" )
private boolean skipInvocation;
/**
* Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>, the
* only indication of the build's success or failure will be the effect it has on the main build (if it fails, the
* main build should fail as well). If {@link #streamLogs} is enabled, the sub-build summary will also provide an
* indication.
*/
@Parameter( defaultValue = "false" )
protected boolean suppressSummaries;
/**
* Flag used to determine whether the build logs should be output to the normal mojo log.
*/
@Parameter( property = "invoker.streamLogs", defaultValue = "false" )
private boolean streamLogs;
/**
* The local repository for caching artifacts. It is strongly recommended to specify a path to an isolated
* repository like <code>${project.build.directory}/it-repo</code>. Otherwise, your ordinary local repository will
* be used, potentially soiling it with broken artifacts.
*/
@Parameter( property = "invoker.localRepositoryPath", defaultValue = "${settings.localRepository}" )
private File localRepositoryPath;
/**
* Directory to search for integration tests.
*/
@Parameter( property = "invoker.projectsDirectory", defaultValue = "${basedir}/src/it/" )
private File projectsDirectory;
/**
* Base directory where all build reports are written to. Every execution of an integration test will produce an XML
* file which contains the information about success or failure of that particular build job. The format of the
* resulting XML file is documented in the given <a href="./build-job.html">build-job</a> reference.
*
* @since 1.4
*/
@Parameter( property = "invoker.reportsDirectory", defaultValue = "${project.build.directory}/invoker-reports" )
private File reportsDirectory;
/**
* A flag to disable the generation of build reports.
*
* @since 1.4
*/
@Parameter( property = "invoker.disableReports", defaultValue = "false" )
private boolean disableReports;
/**
* Directory to which projects should be cloned prior to execution. If set to {@code null}, each integration test
* will be run in the directory in which the corresponding IT POM was found. In this case, you most likely want to
* configure your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory.
* (<b>Exception</b> when project using invoker plugin is of <i>maven-plugin</i> packaging:
* In such case IT projects will be cloned to and executed in <code>target/its</code> by default.)
*
* @since 1.1
*/
@Parameter( property = "invoker.cloneProjectsTo" )
private File cloneProjectsTo;
// CHECKSTYLE_OFF: LineLength
/**
* Some files are normally excluded when copying the IT projects from the directory specified by the parameter
* projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>,
* <code>*~</code>, etc: see <a href=
* "https://codehaus-plexus.github.io/plexus-utils/apidocs/org/codehaus/plexus/util/AbstractScanner.html#DEFAULTEXCLUDES">
* reference</a> for full list). Setting this parameter to <code>true</code> will cause all files to be copied to
* the <code>cloneProjectsTo</code> directory.
*
* @since 1.2
*/
@Parameter( defaultValue = "false" )
private boolean cloneAllFiles;
// CHECKSTYLE_ON: LineLength
/**
* Ensure the {@link #cloneProjectsTo} directory is not polluted with files from earlier invoker runs.
*
* @since 1.6
*/
@Parameter( defaultValue = "true" )
private boolean cloneClean;
/**
* A single POM to build, skipping any scanning parameters and behavior.
*/
@Parameter( property = "invoker.pom" )
private File pom;
/**
* Include patterns for searching the integration test directory for projects. This parameter is meant to be set
* from the POM. If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one
* directory below {@link #projectsDirectory} (i.e. <code>*/pom.xml</code>).<br>
* <br>
* Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include
* pattern <code>*</code> will run Maven builds on all immediate sub directories of {@link #projectsDirectory},
* regardless if they contain a <code>pom.xml</code>. This allows to perform builds that need/should not depend on
* the existence of a POM.
*/
@Parameter
private List<String> pomIncludes = Collections.singletonList( "*/pom.xml" );
/**
* Exclude patterns for searching the integration test directory. This parameter is meant to be set from the POM. By
* default, no POM files are excluded. For the convenience of using an include pattern like <code>*</code>, the
* custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically.
*/
@Parameter
private List<String> pomExcludes = Collections.emptyList();
/**
* Include patterns for searching the projects directory for projects that need to be run before the other projects.
* This parameter allows to declare projects that perform setup tasks like installing utility artifacts into the
* local repository. Projects matched by these patterns are implicitly excluded from the scan for ordinary projects.
* Also, the exclusions defined by the parameter {@link #pomExcludes} apply to the setup projects, too. Default
* value is: <code>setup*/pom.xml</code>.
*
* @since 1.3
*/
@Parameter
private List<String> setupIncludes = Collections.singletonList( "setup*/pom.xml" );
/**
* The list of goals to execute on each project. Default value is: <code>package</code>.
*/
@Parameter
private List<String> goals = Collections.singletonList( "package" );
/**
*/
@Component
private Invoker invoker;
@Component
private SettingsBuilder settingsBuilder;
@Component
private ToolchainManagerPrivate toolchainManagerPrivate;
/**
* Relative path of a selector script to run prior in order to decide if the build should be executed. This script
* may be written with either BeanShell or Groovy. If the file extension is omitted (e.g. <code>selector</code>),
* the plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and
* <code>.groovy</code>. If this script exists for a particular project but returns any non-null value different
* from <code>true</code>, the corresponding build is flagged as skipped. In this case, none of the pre-build hook
* script, Maven nor the post-build hook script will be invoked. If this script throws an exception, the
* corresponding build is flagged as in error, and none of the pre-build hook script, Maven not the post-build hook
* script will be invoked.
*
* @since 1.5
*/
@Parameter( property = "invoker.selectorScript", defaultValue = "selector" )
private String selectorScript;
/**
* Relative path of a pre-build hook script to run prior to executing the build. This script may be written with
* either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>prebuild</code>), the plugin
* searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. If this
* script exists for a particular project but returns any non-null value different from <code>true</code> or throws
* an exception, the corresponding build is flagged as a failure. In this case, neither Maven nor the post-build
* hook script will be invoked.
*/
@Parameter( property = "invoker.preBuildHookScript", defaultValue = "prebuild" )
private String preBuildHookScript;
/**
* Relative path of a cleanup/verification hook script to run after executing the build. This script may be written
* with either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>verify</code>), the
* plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
* If this script exists for a particular project but returns any non-null value different from <code>true</code> or
* throws an exception, the corresponding build is flagged as a failure.
*/
@Parameter( property = "invoker.postBuildHookScript", defaultValue = "postbuild" )
private String postBuildHookScript;
/**
* Location of a properties file that defines CLI properties for the test.
*/
@Parameter( property = "invoker.testPropertiesFile", defaultValue = "test.properties" )
private String testPropertiesFile;
/**
* Common set of properties to pass in on each project's command line, via -D parameters.
*
* @since 1.1
*/
@Parameter
private Map<String, String> properties;
/**
* Whether to show errors in the build output.
*/
@Parameter( property = "invoker.showErrors", defaultValue = "false" )
private boolean showErrors;
/**
* Whether to show debug statements in the build output.
*/
@Parameter( property = "invoker.debug", defaultValue = "false" )
private boolean debug;
/**
* Suppress logging to the <code>build.log</code> file.
*/
@Parameter( property = "invoker.noLog", defaultValue = "false" )
private boolean noLog;
/**
* By default a {@code build.log} is created in the root of the project. By setting this folder
* files are written to a different folder, respecting the structure of the projectsDirectory.
*
* @since 3.2.0
*/
@Parameter
private File logDirectory;
/**
* List of profile identifiers to explicitly trigger in the build.
*
* @since 1.1
*/
@Parameter
private List<String> profiles;
/**
* A list of additional properties which will be used to filter tokens in POMs and goal files.
*
* @since 1.3
*/
@Parameter
private Map<String, String> filterProperties;
/**
* The Maven Project Object
*
* @since 1.1
*/
@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
@Parameter( defaultValue = "${mojoExecution}", readonly = true, required = true )
private MojoExecution mojoExecution;
/**
* A comma separated list of projectname patterns to run. Specify this parameter to run individual tests by file
* name, overriding the {@link #setupIncludes}, {@link #pomIncludes} and {@link #pomExcludes} parameters. Each
* pattern you specify here will be used to create an include/exclude pattern formatted like
* <code>${projectsDirectory}/<i>pattern</i></code>. To exclude a test, prefix the pattern with a '<code>!</code>'.
* So you can just type <nobr><code>-Dinvoker.test=SimpleTest,Comp*Test,!Compare*</code></nobr> to run builds in
* <code>${projectsDirectory}/SimpleTest</code> and <code>${projectsDirectory}/ComplexTest</code>, but not
* <code>${projectsDirectory}/CompareTest</code>
*
* @since 1.1 (exclusion since 1.8)
*/
@Parameter( property = "invoker.test" )
private String invokerTest;
/**
* Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the
* <code><localRepository></code> element of this settings file is always ignored, i.e. the path given by the
* parameter {@link #localRepositoryPath} is dominant.
*
* @since 1.2
*/
@Parameter( property = "invoker.settingsFile" )
private File settingsFile;
/**
* The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for
* individual integration tests by using {@link #invokerPropertiesFile}.
*
* @since 1.2
*/
@Parameter( property = "invoker.mavenOpts" )
private String mavenOpts;
/**
* The home directory of the Maven installation to use for the forked builds. Defaults to the current Maven
* installation.
*
* @since 1.3
*/
@Parameter( property = "invoker.mavenHome" )
private File mavenHome;
/**
* mavenExecutable can either be a file relative to <code>${maven.home}/bin/</code>, test project workspace
* or an absolute file.
*
* @since 1.8
*/
@Parameter( property = "invoker.mavenExecutable" )
private File mavenExecutable;
/**
* The <code>JAVA_HOME</code> environment variable to use for forked Maven invocations. Defaults to the current Java
* home directory.
*
* @since 1.3
*/
@Parameter( property = "invoker.javaHome" )
private File javaHome;
/**
* The file encoding for the pre-/post-build scripts and the list files for goals and profiles.
*
* @since 1.2
*/
@Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
private String encoding;
/**
* The current user system settings for use in Maven.
*
* @since 1.2
*/
@Parameter( defaultValue = "${settings}", readonly = true, required = true )
private Settings settings;
/**
* A flag whether the test class path of the project under test should be included in the class path of the
* pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of the
* <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the <code>true</code>,
* the project's test class path will be prepended to the interpreter class path. Among others, this feature allows
* the scripts to access utility classes from the test sources of your project.
*
* @since 1.2
*/
@Parameter( property = "invoker.addTestClassPath", defaultValue = "false" )
private boolean addTestClassPath;
/**
* The test class path of the project under test.
*/
@Parameter( defaultValue = "${project.testClasspathElements}", readonly = true )
private List<String> testClassPath;
/**
* The name of an optional project-specific file that contains properties used to specify settings for an individual
* Maven invocation. Any property present in the file will override the corresponding setting from the plugin
* configuration. The values of the properties are filtered and may use expressions like
* <code>${project.version}</code> to reference project properties or values from the parameter
* {@link #filterProperties}.<p/>
*
* <p>
* As of 3.2.0 it is possible to put this folder in any of the ancestor folders, where properties will be inherited.
* This way you can provide a single properties file for a group of projects
* </p>
*
* The snippet below describes the supported properties:
* <pre>
* # A comma or space separated list of goals/phases to execute, may
* # specify an empty list to execute the default goal of the IT project.
* # Environment variables used by maven plugins can be added here
* invoker.goals = clean install -Dplugin.variable=value
*
* # Or you can give things like this if you need.
* invoker.goals = -T2 clean verify
*
* # Optionally, a list of goals to run during further invocations of Maven
* invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:run
*
* # A comma or space separated list of profiles to activate
* # can be indexed
* invoker.profiles = its,jdk15
*
* # The path to an alternative POM or base directory to invoke Maven on, defaults to the
* # project that was originally specified in the plugin configuration
* # Since plugin version 1.4
* # can be indexed
* invoker.project = sub-module
*
* # The maven executable can either be a file relative to <code>${maven.home}/bin/</code>, test project workspace
* # or an absolute file.
* # Since plugin version 3.3.0
* # can be indexed
* invoker.mavenExecutable = mvnw
*
* # The value for the environment variable MAVEN_OPTS
* # can be indexed
* invoker.mavenOpts = -Dfile.encoding=UTF-16 -Xms32m -Xmx256m
*
* # Possible values are "fail-fast" (default), "fail-at-end" and "fail-never"
* # can be indexed
* invoker.failureBehavior = fail-never
*
* # The expected result of the build, possible values are "success" (default) and "failure"
* # can be indexed
* invoker.buildResult = failure
*
* # A boolean value controlling the aggregator mode of Maven, defaults to "false"
*
* # can be indexed
* invoker.nonRecursive = true
*
* # A boolean value controlling the network behavior of Maven, defaults to "false"
* # Since plugin version 1.4
* # can be indexed
* invoker.offline = true
*
* # The path to the properties file from which to load system properties, defaults to the
* # filename given by the plugin parameter testPropertiesFile
* # Since plugin version 1.4
* # can be indexed
* invoker.systemPropertiesFile = test.properties
*
* # An optional human friendly name and description for this build job.
* # Both name and description have to be set to be included in the build reports.
* # Since plugin version 1.4
* invoker.name = Test Build 01
* invoker.description = Checks the support for build reports.
*
* # A comma separated list of JRE versions on which this build job should be run.
* # Since plugin version 1.4
* invoker.java.version = 1.4+, !1.4.1, 1.7-
*
* # A comma separated list of OS families on which this build job should be run.
* # Since plugin version 1.4
* invoker.os.family = !windows, unix, mac
*
* # A comma separated list of Maven versions on which this build should be run.
* # Since plugin version 1.5
* invoker.maven.version = 2.0.10+, !2.1.0, !2.2.0
*
* # A mapping for toolchain to ensure it exists
* # Since plugin version 3.2.0
* invoker.toolchain.<type>.<provides> = value
* invoker.toolchain.jdk.version = 11
*
* # For java.version, maven.version, os.family and toolchain it is possible to define multiple selectors.
* # If one of the indexed selectors matches, the test is executed.
* # With the invoker.x.y equivalents you can specify global matchers.
* selector.1.java.version = 1.8+
* selector.1.maven.version = 3.2.5+
* selector.1.os.family = !windows
* selector.2.maven.version = 3.0+
* selector.3.java.version = 9+
*
* # A boolean value controlling the debug logging level of Maven, , defaults to "false"
* # Since plugin version 1.8
* # can be indexed
* invoker.debug = true
*
* The execution timeout in seconds.
* # Since plugin version 3.0.2
* # can be indexed
* invoker.timeoutInSeconds = 5
*
* # Path to an alternate settings.xml to use for Maven invocation with this IT.
* # Since plugin version 3.0.1
* # can be indexed
* invoker.settingsFile = ../
*
* # An integer value to control run order of projects. sorted in the descending order of the ordinal.
* # In other words, the BuildJobs with the highest numbers will be executed first
* # Default value is 0 (zero)
* # Since plugin version 3.2.1
* invoker.ordinal = 3
*
* # The additional value for the environment variable.
* # Since plugin version 3.2.2
* invoker.environmentVariables.<variableName> = variableValue
* invoker.environmentVariables.MY_ENV_NAME = myEnvValue
*
* </pre>
*
* @since 1.2
*/
@Parameter( property = "invoker.invokerPropertiesFile", defaultValue = "invoker.properties" )
private String invokerPropertiesFile;
/**
* flag to enable show mvn version used for running its (cli option : -V,--show-version )
*
* @since 1.4
*/
@Parameter( property = "invoker.showVersion", defaultValue = "false" )
private boolean showVersion;
/**
* <p>Number of threads for running tests in parallel. This will be the number of maven forked process in parallel.
* When terminated with "C", the number part is multiplied by the number of processors (cores) available
* to the Java virtual machine. Floating point value are only accepted together with "C".</p>
*
* <p>Example values: "1.5C", "4"</p>
*
* @since 1.6
*/
@Parameter( property = "invoker.parallelThreads", defaultValue = "1" )
private String parallelThreads;
/**
* @since 1.6
*/
@Parameter( property = "plugin.artifacts", required = true, readonly = true )
private List<Artifact> pluginArtifacts;
/**
* If enable and if you have a settings file configured for the execution, it will be merged with your user
* settings.
*
* @since 1.6
*/
@Parameter( property = "invoker.mergeUserSettings", defaultValue = "false" )
private boolean mergeUserSettings;
/**
* Additional environment variables to set on the command line.
*
* @since 1.8
*/
@Parameter
private Map<String, String> environmentVariables;
/**
* Additional variables for use in the hook scripts.
*
* @since 1.9
*/
@Parameter
private Map<String, String> scriptVariables;
/**
*
* @since 3.0.2
*/
@Parameter( defaultValue = "0", property = "invoker.timeoutInSeconds" )
private int timeoutInSeconds;
/**
* Write test result in junit format.
* @since 3.1.2
*/
@Parameter( defaultValue = "false", property = "invoker.writeJunitReport" )
private boolean writeJunitReport;
/**
* The package name use in junit report
* @since 3.1.2
*/
@Parameter( defaultValue = "maven.invoker.it", property = "invoker.junitPackageName" )
private String junitPackageName = "maven.invoker.it";
/**
* Only invoke maven projects if their sources have been modified since
* they were last built. Only works in conjunction with <code>cloneProjectsTo</code>.
* @since 3.2.2
*/
@Parameter( defaultValue = "false", property = "invoker.updateOnly" )
private boolean updateOnly = false;
/**
* The scripter runner that is responsible to execute hook scripts.
*/
private ScriptRunner scriptRunner;
/**
* A string used to prefix the file name of the filtered POMs in case the POMs couldn't be filtered in-place (i.e.
* the projects were not cloned to a temporary directory), can be <code>null</code>. This will be set to
* <code>null</code> if the POMs have already been filtered during cloning.
*/
private String filteredPomPrefix = "interpolated-";
/**
* The format for elapsed build time.
*/
private final DecimalFormat secFormat = new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) );
/**
* The version of Maven which is used to run the builds
*/
private String actualMavenVersion;
/**
* Invokes Maven on the configured test projects.
*
* @throws org.apache.maven.plugin.MojoExecutionException If the goal encountered severe errors.
* @throws org.apache.maven.plugin.MojoFailureException If any of the Maven builds failed.
*/
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( skipInvocation )
{
getLog().info( "Skipping invocation per configuration."
+ " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
return;
}
if ( StringUtils.isEmpty( encoding ) )
{
getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+ ", i.e. build is platform dependent!" );
}
// done it here to prevent issues with concurrent access in case of parallel run
if ( !disableReports )
{
setupReportsFolder();
}
List<BuildJob> buildJobs;
if ( pom == null )
{
try
{
buildJobs = getBuildJobs();
}
catch ( final IOException e )
{
throw new MojoExecutionException( "Error retrieving POM list from includes, "
+ "excludes, and projects directory. Reason: " + e.getMessage(), e );
}
}
else
{
try
{
projectsDirectory = pom.getCanonicalFile().getParentFile();
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failed to discover projectsDirectory from "
+ "pom File parameter. Reason: " + e.getMessage(), e );
}
buildJobs = Collections.singletonList( new BuildJob( pom.getName() ) );
}
if ( buildJobs.isEmpty() )
{
doFailIfNoProjects();
getLog().info( "No projects were selected for execution." );
return;
}
setupActualMavenVersion();
handleScriptRunnerWithScriptClassPath();
Collection<String> collectedProjects = new LinkedHashSet<>();
for ( BuildJob buildJob : buildJobs )
{
collectProjects( projectsDirectory, buildJob.getProject(), collectedProjects, true );
}
File projectsDir = projectsDirectory;
if ( cloneProjectsTo == null && "maven-plugin".equals( project.getPackaging() ) )
{
cloneProjectsTo = new File( project.getBuild().getDirectory(), "its" );
}
if ( updateOnly )
{
if ( cloneProjectsTo == null )
{
getLog().warn( "updateOnly functionality is not supported without cloning the projects" );
}
else if ( lastModifiedRecursive( projectsDirectory ) <= lastModifiedRecursive( cloneProjectsTo ) )
{
getLog().debug( "Skipping invocation as cloned projects are up-to-date "
+ "and updateOnly parameter is set to true." );
return;
}
else
{
getLog().debug( "Cloned projects are out of date" );
}
}
if ( cloneProjectsTo != null )
{
cloneProjects( collectedProjects );
projectsDir = cloneProjectsTo;
}
else
{
getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" );
}
// First run setup jobs.
List<BuildJob> setupBuildJobs = getSetupJobs( buildJobs );
if ( !setupBuildJobs.isEmpty() )
{
// Run setup jobs in single thread mode.
//
// Jobs are ordered according to ordinal value from invoker.properties
getLog().info( "Running " + setupBuildJobs.size() + " setup job"
+ ( ( setupBuildJobs.size() < 2 ) ? "" : "s" ) + ":" );
runBuilds( projectsDir, setupBuildJobs, 1 );
getLog().info( "Setup done." );
}
// Afterwards run all other jobs.
List<BuildJob> nonSetupBuildJobs = getNonSetupJobs( buildJobs );
// We will run the non setup jobs with the configured
// parallelThreads number.
runBuilds( projectsDir, nonSetupBuildJobs, getParallelThreadsCount() );
writeSummaryFile( nonSetupBuildJobs );
processResults( new InvokerSession( nonSetupBuildJobs ) );
}
private void setupActualMavenVersion() throws MojoExecutionException
{
if ( mavenHome != null )
{
try
{
actualMavenVersion = SelectorUtils.getMavenVersion( mavenHome );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
}
else
{
actualMavenVersion = SelectorUtils.getMavenVersion();
}
}
/**
* Find the latest lastModified recursively within a directory structure.
*
* @param file the root file to check.
* @return the latest lastModified time found.
*/
private long lastModifiedRecursive( File file )
{
long lastModified = file.lastModified();
final File[] entries = file.listFiles();
if ( entries != null )
{
for ( File entry : entries )
{
lastModified = Math.max( lastModified, lastModifiedRecursive( entry ) );
}
}
return lastModified;
}
/**
* This will create the necessary folders for the reports.
*
* @throws MojoExecutionException in case of failure during creation of the reports folder.
*/
private void setupReportsFolder()
throws MojoExecutionException
{
// If it exists from previous run...
if ( reportsDirectory.exists() )
{
try
{
FileUtils.deleteDirectory( reportsDirectory );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failure while trying to delete "
+ reportsDirectory.getAbsolutePath(), e );
}
}
if ( !reportsDirectory.mkdirs() )
{
throw new MojoExecutionException( "Failure while creating the " + reportsDirectory.getAbsolutePath() );
}
}
private List<BuildJob> getSetupJobs( List<BuildJob> buildJobs )
{
return buildJobs.stream().
filter( buildJob -> buildJob.getType().equals( BuildJob.Type.SETUP ) ).
collect( Collectors.toList() );
}
private List<BuildJob> getNonSetupJobs( List<BuildJob> buildJobs )
{
return buildJobs.stream().
filter( buildJob -> !buildJob.getType().equals( BuildJob.Type.SETUP ) ).
collect( Collectors.toList() );
}
private void handleScriptRunnerWithScriptClassPath()
{
final List<String> scriptClassPath;
if ( addTestClassPath )
{
scriptClassPath = new ArrayList<>( testClassPath );
for ( Artifact pluginArtifact : pluginArtifacts )
{
scriptClassPath.remove( pluginArtifact.getFile().getAbsolutePath() );
}
}
else
{
scriptClassPath = null;
}
scriptRunner = new ScriptRunner( );
scriptRunner.setScriptEncoding( encoding );
scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath );
scriptRunner.setGlobalVariable( "mavenVersion", actualMavenVersion );
if ( scriptVariables != null )
{
scriptVariables.forEach( ( key, value ) -> scriptRunner.setGlobalVariable( key, value ) );
}
scriptRunner.setClassPath( scriptClassPath );
}
private void writeSummaryFile( List<BuildJob> buildJobs )
throws MojoExecutionException
{
File summaryReportFile = new File( reportsDirectory, "invoker-summary.txt" );
try ( Writer writer = new BufferedWriter( new FileWriter( summaryReportFile ) ) )
{
for ( BuildJob buildJob : buildJobs )
{
if ( !buildJob.getResult().equals( BuildJob.Result.SUCCESS ) )
{
writer.append( buildJob.getResult() );
writer.append( " [" );
writer.append( buildJob.getProject() );
writer.append( "] " );
if ( buildJob.getFailureMessage() != null )
{
writer.append( " " );
writer.append( buildJob.getFailureMessage() );
}
writer.append( "\n" );
}
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Failed to write summary report " + summaryReportFile, e );
}
}
protected void doFailIfNoProjects()
throws MojoFailureException
{
// should only be used during run and verify
}
/**
* Processes the results of invoking the build jobs.
*
* @param invokerSession The session with the build jobs, must not be <code>null</code>.
* @throws MojoFailureException If the mojo had failed as a result of invoking the build jobs.
* @since 1.4
*/
abstract void processResults( InvokerSession invokerSession )
throws MojoFailureException;
/**
* Collects all projects locally reachable from the specified project. The method will as such try to read the POM
* and recursively follow its parent/module elements.
*