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.BufferedWriter;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.io.PrintWriter;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.*;
28 import java.text.SimpleDateFormat;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.stream.Collectors;
33
34 import org.apache.maven.archiver.MavenArchiver;
35 import org.apache.maven.artifact.Artifact;
36 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
37 import org.apache.maven.execution.MavenSession;
38 import org.apache.maven.plugin.AbstractMojo;
39 import org.apache.maven.plugin.MojoExecutionException;
40 import org.apache.maven.plugin.logging.Log;
41 import org.apache.maven.plugins.annotations.Component;
42 import org.apache.maven.plugins.annotations.Parameter;
43 import org.apache.maven.project.MavenProject;
44 import org.apache.maven.rtinfo.RuntimeInformation;
45 import org.apache.maven.shared.utils.io.FileUtils;
46 import org.apache.maven.toolchain.Toolchain;
47 import org.apache.maven.toolchain.ToolchainManager;
48
49
50
51
52
53
54 public abstract class AbstractBuildinfoMojo extends AbstractMojo {
55
56
57
58 @Component
59 protected MavenProject project;
60
61
62
63
64 @Parameter(
65 defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}.buildinfo",
66 required = true,
67 readonly = true)
68 protected File buildinfoFile;
69
70
71
72
73 @Parameter(property = "buildinfo.ignoreJavadoc", defaultValue = "true")
74 private boolean ignoreJavadoc;
75
76
77
78
79
80 @Parameter(property = "buildinfo.ignore", defaultValue = "")
81 private List<String> ignore;
82
83
84
85
86 @Parameter(property = "buildinfo.detect.skip", defaultValue = "true")
87 private boolean detectSkip;
88
89
90
91
92
93 @Parameter(property = "buildinfo.skipModules")
94 private List<String> skipModules;
95
96 private List<PathMatcher> skipModulesMatcher = null;
97
98
99
100
101
102
103
104 @Parameter(property = "buildinfo.reproducible", defaultValue = "false")
105 private boolean reproducible;
106
107
108
109
110 @Component
111 protected MavenSession session;
112
113
114
115
116
117
118
119
120 @Parameter(defaultValue = "${project.build.outputTimestamp}")
121 private String outputTimestamp;
122
123
124
125
126 @Component
127 private ToolchainManager toolchainManager;
128
129 @Component
130 protected ArtifactHandlerManager artifactHandlerManager;
131
132 @Component
133 protected RuntimeInformation rtInformation;
134
135 @Override
136 public void execute() throws MojoExecutionException {
137 boolean mono = session.getProjects().size() == 1;
138
139 hasBadOutputTimestamp(outputTimestamp, getLog(), project, session.getProjects());
140
141 if (!mono) {
142
143 if (isSkip(project)) {
144 getLog().info("Skipping goal because module skips install and/or deploy");
145 return;
146 }
147
148 MavenProject last = getLastProject();
149 if (project != last) {
150 skip(last);
151 return;
152 }
153 }
154
155
156 Map<Artifact, String> artifacts = generateBuildinfo(mono);
157 getLog().info("Saved " + (mono ? "" : "aggregate ") + "info on build to " + buildinfoFile);
158
159 copyAggregateToRoot(buildinfoFile);
160
161 execute(artifacts);
162 }
163
164 static boolean hasBadOutputTimestamp(
165 String outputTimestamp, Log log, MavenProject project, List<MavenProject> reactorProjects) {
166 MavenArchiver archiver = new MavenArchiver();
167 Date timestamp = archiver.parseOutputTimestamp(outputTimestamp);
168 if (timestamp == null) {
169 log.error("Reproducible Build not activated by project.build.outputTimestamp property: "
170 + "see https://maven.apache.org/guides/mini/guide-reproducible-builds.html");
171 return true;
172 }
173
174 if (log.isDebugEnabled()) {
175 log.debug("project.build.outputTimestamp = \"" + outputTimestamp + "\" => "
176 + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(timestamp));
177 }
178
179
180 boolean parentInReactor = false;
181 MavenProject reactorParent = project;
182 while (reactorProjects.contains(reactorParent.getParent())) {
183 parentInReactor = true;
184 reactorParent = reactorParent.getParent();
185 }
186 String prop = reactorParent.getOriginalModel().getProperties().getProperty("project.build.outputTimestamp");
187 if (prop == null) {
188 log.warn("<project.build.outputTimestamp> property is inherited"
189 + (parentInReactor ? " from outside the reactor" : "") + ", it should be defined in "
190 + (parentInReactor ? "parent POM from reactor " + reactorParent.getFile() : "pom.xml"));
191 return false;
192 }
193
194 return false;
195 }
196
197
198
199
200
201
202
203 abstract void execute(Map<Artifact, String> artifacts) throws MojoExecutionException;
204
205 protected void skip(MavenProject last) throws MojoExecutionException {
206 getLog().info("Skipping intermediate goal run, aggregate will be " + last.getArtifactId());
207 }
208
209 protected void copyAggregateToRoot(File aggregate) throws MojoExecutionException {
210 if (session.getProjects().size() == 1) {
211
212 return;
213 }
214
215
216 MavenProject root = getExecutionRoot();
217 String extension = aggregate.getName().substring(aggregate.getName().lastIndexOf('.'));
218 File rootCopy =
219 new File(root.getBuild().getDirectory(), root.getArtifactId() + '-' + root.getVersion() + extension);
220 try {
221 FileUtils.copyFile(aggregate, rootCopy);
222 getLog().info("Aggregate " + extension.substring(1) + " copied to " + rootCopy);
223 } catch (IOException ioe) {
224 throw new MojoExecutionException("Could not copy " + aggregate + " to " + rootCopy, ioe);
225 }
226 }
227
228
229
230
231
232
233
234
235
236 protected Map<Artifact, String> generateBuildinfo(boolean mono) throws MojoExecutionException {
237 MavenProject root = mono ? project : getExecutionRoot();
238
239 buildinfoFile.getParentFile().mkdirs();
240
241 try (PrintWriter p = new PrintWriter(new BufferedWriter(
242 new OutputStreamWriter(Files.newOutputStream(buildinfoFile.toPath()), StandardCharsets.UTF_8)))) {
243 BuildInfoWriter bi = new BuildInfoWriter(getLog(), p, mono, artifactHandlerManager, rtInformation);
244 bi.setIgnoreJavadoc(ignoreJavadoc);
245 bi.setIgnore(ignore);
246 bi.setToolchain(getToolchain());
247
248 bi.printHeader(root, mono ? null : project, reproducible);
249
250
251 if (mono) {
252 bi.printArtifacts(project);
253 } else {
254 for (MavenProject project : session.getProjects()) {
255 if (!isSkip(project)) {
256 bi.printArtifacts(project);
257 }
258 }
259 }
260
261 if (p.checkError()) {
262 throw new MojoExecutionException("Write error to " + buildinfoFile);
263 }
264
265 return bi.getArtifacts();
266 } catch (IOException e) {
267 throw new MojoExecutionException("Error creating file " + buildinfoFile, e);
268 }
269 }
270
271 protected MavenProject getExecutionRoot() {
272 for (MavenProject p : session.getProjects()) {
273 if (p.isExecutionRoot()) {
274 return p;
275 }
276 }
277 return null;
278 }
279
280 private MavenProject getLastProject() {
281 int i = session.getProjects().size();
282 while (i > 0) {
283 MavenProject project = session.getProjects().get(--i);
284 if (!isSkip(project)) {
285 return project;
286 }
287 }
288 return null;
289 }
290
291 private boolean isSkip(MavenProject project) {
292
293 boolean skipModule = false;
294 if (skipModules != null && !skipModules.isEmpty()) {
295 if (skipModulesMatcher == null) {
296 FileSystem fs = FileSystems.getDefault();
297 skipModulesMatcher = skipModules.stream()
298 .map(i -> fs.getPathMatcher("glob:" + i))
299 .collect(Collectors.toList());
300 }
301 Path path = Paths.get(project.getGroupId() + '/' + project.getArtifactId());
302 skipModule = skipModulesMatcher.stream().anyMatch(m -> m.matches(path));
303 }
304
305 return skipModule || (detectSkip && PluginUtil.isSkip(project));
306 }
307
308 private Toolchain getToolchain() {
309 Toolchain tc = null;
310 if (toolchainManager != null) {
311 tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
312 }
313
314 return tc;
315 }
316 }