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