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