1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.artifact.buildinfo;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.file.Files;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Properties;
28 import java.util.Set;
29
30 import org.apache.maven.execution.MavenSession;
31 import org.apache.maven.lifecycle.LifecycleExecutor;
32 import org.apache.maven.lifecycle.MavenExecutionPlan;
33 import org.apache.maven.model.Plugin;
34 import org.apache.maven.plugin.AbstractMojo;
35 import org.apache.maven.plugin.MojoExecution;
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugins.annotations.Component;
38 import org.apache.maven.plugins.annotations.Mojo;
39 import org.apache.maven.plugins.annotations.Parameter;
40 import org.apache.maven.project.MavenProject;
41 import org.eclipse.aether.util.version.GenericVersionScheme;
42 import org.eclipse.aether.version.InvalidVersionSpecificationException;
43 import org.eclipse.aether.version.Version;
44 import org.eclipse.aether.version.VersionScheme;
45
46
47
48
49
50
51 @Mojo(name = "check-buildplan", threadSafe = true, requiresProject = true)
52 public class CheckBuildPlanMojo extends AbstractMojo {
53 @Component
54 private MavenProject project;
55
56 @Component
57 private MavenSession session;
58
59 @Component
60 private LifecycleExecutor lifecycleExecutor;
61
62
63 @Parameter(property = "check.buildplan.tasks", defaultValue = "deploy")
64 private String[] tasks;
65
66
67
68
69
70
71 @Parameter(defaultValue = "${project.build.outputTimestamp}")
72 private String outputTimestamp;
73
74
75
76
77
78
79 @Parameter(property = "diagnose", defaultValue = "false")
80 private boolean diagnose;
81
82
83
84
85 @Parameter(property = "check.plugin-issues")
86 private File pluginIssues;
87
88
89
90
91 @Parameter(property = "check.failOnNonReproducible", defaultValue = "true")
92 private boolean failOnNonReproducible;
93
94 private final VersionScheme versionScheme = new GenericVersionScheme();
95
96 protected MavenExecutionPlan calculateExecutionPlan() throws MojoExecutionException {
97 try {
98 return lifecycleExecutor.calculateExecutionPlan(session, tasks);
99 } catch (Exception e) {
100 throw new MojoExecutionException("Cannot calculate Maven execution plan" + e.getMessage(), e);
101 }
102 }
103
104 @Override
105 public void execute() throws MojoExecutionException {
106 boolean fail = AbstractBuildinfoMojo.hasBadOutputTimestamp(
107 outputTimestamp, getLog(), project, session.getProjects(), diagnose);
108
109
110
111 Properties issues = loadIssues();
112
113 MavenExecutionPlan plan = calculateExecutionPlan();
114
115 Set<String> plugins = new HashSet<>();
116 int okCount = 0;
117 for (MojoExecution exec : plan.getMojoExecutions()) {
118 Plugin plugin = exec.getPlugin();
119 String id = plugin.getId();
120
121 if (plugins.add(id)) {
122
123 String issue = issues.getProperty(plugin.getKey());
124 if (issue == null) {
125 okCount++;
126 getLog().debug("No known issue with " + id);
127 } else if (issue.startsWith("fail:")) {
128 String logMessage = "plugin without solution " + id + ", see " + issue.substring(5);
129 if (failOnNonReproducible) {
130 getLog().error(logMessage);
131 } else {
132 getLog().warn(logMessage);
133 }
134 fail = true;
135 } else {
136 try {
137 Version minimum = versionScheme.parseVersion(issue);
138 Version version = versionScheme.parseVersion(plugin.getVersion());
139 if (version.compareTo(minimum) < 0) {
140 String logMessage =
141 "plugin with non-reproducible output: " + id + ", require minimum " + issue;
142 if (failOnNonReproducible) {
143 getLog().error(logMessage);
144 } else {
145 getLog().warn(logMessage);
146 }
147 fail = true;
148 } else {
149 okCount++;
150 getLog().debug("No known issue with " + id + " (>= " + issue + ")");
151 }
152 } catch (InvalidVersionSpecificationException e) {
153 throw new MojoExecutionException(e);
154 }
155 }
156 }
157 }
158 if (okCount > 0) {
159 getLog().info("No known issue in " + okCount + " plugins");
160 }
161
162 if (fail) {
163 getLog().info("current module pom.xml is " + project.getBasedir() + "/pom.xml");
164 MavenProject parent = project;
165 while (true) {
166 parent = parent.getParent();
167 if ((parent == null) || !session.getProjects().contains(parent)) {
168 break;
169 }
170 getLog().info(" parent pom.xml is " + parent.getBasedir() + "/pom.xml");
171 }
172 String message = "non-reproducible plugin or configuration found with fix available";
173 if (failOnNonReproducible) {
174 throw new MojoExecutionException(message);
175 } else {
176 getLog().warn(message);
177 }
178 }
179 }
180
181 private Properties loadIssues() throws MojoExecutionException {
182 try (InputStream in = (pluginIssues == null)
183 ? getClass().getResourceAsStream("not-reproducible-plugins.properties")
184 : Files.newInputStream(pluginIssues.toPath())) {
185 Properties prop = new Properties();
186 prop.load(in);
187
188 Properties result = new Properties();
189 for (Map.Entry<Object, Object> entry : prop.entrySet()) {
190 String plugin = entry.getKey().toString().replace('+', ':');
191 if (!plugin.contains(":")) {
192 plugin = "org.apache.maven.plugins:" + plugin;
193 }
194 result.put(plugin, entry.getValue());
195 }
196 return result;
197 } catch (IOException ioe) {
198 throw new MojoExecutionException("Cannot load issues file", ioe);
199 }
200 }
201 }