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.io.PrintWriter;
25 import java.nio.file.FileSystem;
26 import java.nio.file.FileSystems;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.PathMatcher;
30 import java.nio.file.Paths;
31 import java.nio.file.StandardCopyOption;
32 import java.util.LinkedHashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Properties;
36 import java.util.stream.Collectors;
37
38 import org.apache.commons.codec.digest.DigestUtils;
39 import org.apache.maven.RepositoryUtils;
40 import org.apache.maven.plugin.MojoExecutionException;
41 import org.apache.maven.plugin.logging.Log;
42 import org.apache.maven.project.MavenProject;
43 import org.apache.maven.rtinfo.RuntimeInformation;
44 import org.apache.maven.toolchain.Toolchain;
45 import org.eclipse.aether.artifact.Artifact;
46 import org.eclipse.aether.artifact.DefaultArtifact;
47 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
48
49
50
51
52 class BuildInfoWriter {
53 private final Log log;
54 private final PrintWriter p;
55 private final boolean mono;
56 private final RuntimeInformation rtInformation;
57 private final Map<Artifact, String> artifacts = new LinkedHashMap<>();
58 private int projectCount = -1;
59 private boolean ignoreJavadoc = true;
60 private List<PathMatcher> ignore;
61 private Toolchain toolchain;
62
63 BuildInfoWriter(Log log, PrintWriter p, boolean mono, RuntimeInformation rtInformation) {
64 this.log = log;
65 this.p = p;
66 this.mono = mono;
67 this.rtInformation = rtInformation;
68 }
69
70 void printHeader(MavenProject project, MavenProject aggregate, boolean reproducible) {
71 p.println("# https://reproducible-builds.org/docs/jvm/");
72 p.println("buildinfo.version=1.0-SNAPSHOT");
73 p.println();
74 p.println("name=" + project.getName());
75 p.println("group-id=" + project.getGroupId());
76 p.println("artifact-id=" + project.getArtifactId());
77 p.println("version=" + project.getVersion());
78 p.println();
79 printSourceInformation(project);
80 p.println();
81 p.println("# build instructions");
82 p.println("build-tool=mvn");
83
84 p.println();
85 if (reproducible) {
86 p.println("# build environment information (simplified for reproducibility)");
87 p.println("java.version=" + extractJavaMajorVersion(System.getProperty("java.version")));
88 String ls = System.lineSeparator();
89 p.println("os.name=" + ("\n".equals(ls) ? "Unix" : "Windows"));
90 } else {
91 p.println("# effective build environment information");
92 p.println("java.version=" + System.getProperty("java.version"));
93 p.println("java.vendor=" + System.getProperty("java.vendor"));
94 p.println("os.name=" + System.getProperty("os.name"));
95 p.println("os.version=" + System.getProperty("os.version"));
96 p.println("os.arch=" + System.getProperty("os.arch"));
97 p.println("line.separator="
98 + System.lineSeparator().replace("\r", "\\r").replace("\n", "\\n"));
99 }
100 p.println();
101 p.println("# Maven rebuild instructions and effective environment");
102 if (!reproducible) {
103 p.println("mvn.version=" + rtInformation.getMavenVersion());
104 }
105 if ((project.getPrerequisites() != null) && (project.getPrerequisites().getMaven() != null)) {
106
107 p.println("mvn.minimum.version=" + project.getPrerequisites().getMaven());
108 }
109 if (toolchain != null) {
110 String javaVersion = JdkToolchainUtil.getJavaVersion(toolchain);
111 if (reproducible) {
112 javaVersion = extractJavaMajorVersion(javaVersion);
113 }
114 p.println("mvn.toolchain.jdk=" + javaVersion);
115 }
116
117 if (!mono && (aggregate != null)) {
118 p.println("mvn.aggregate.artifact-id=" + aggregate.getArtifactId());
119 }
120
121 p.println();
122 p.println("# " + (mono ? "" : "aggregated ") + "output");
123 }
124
125 private static String extractJavaMajorVersion(String javaVersion) {
126 if (javaVersion.startsWith("1.")) {
127 javaVersion = javaVersion.substring(2);
128 }
129 int index = javaVersion.indexOf('.');
130 if (index < 0) {
131 index = javaVersion.indexOf('-');
132 }
133 return (index < 0) ? javaVersion : javaVersion.substring(0, index);
134 }
135
136 private void printSourceInformation(MavenProject project) {
137 boolean sourceAvailable = false;
138 p.println("# source information");
139
140 if (project.getScm() != null) {
141 sourceAvailable = true;
142 p.println("source.scm.uri=" + project.getScm().getConnection());
143 p.println("source.scm.tag=" + project.getScm().getTag());
144 } else {
145 p.println("# no scm configured in pom.xml");
146 }
147
148 if (!sourceAvailable) {
149 log.warn("No source information available in buildinfo for rebuilders...");
150 }
151 }
152
153 void printArtifacts(MavenProject project) throws MojoExecutionException {
154 String prefix = "outputs.";
155 if (!mono) {
156
157 projectCount++;
158 prefix += projectCount + ".";
159 p.println();
160 p.println(prefix + "coordinates=" + project.getGroupId() + ':' + project.getArtifactId());
161 }
162
163
164 Artifact consumerPom = RepositoryUtils.toArtifacts(project.getAttachedArtifacts()).stream()
165 .filter(a -> "pom".equals(a.getExtension()) && "consumer".equals(a.getClassifier()))
166 .findAny()
167 .orElse(null);
168
169 int n = 0;
170 Artifact pomArtifact =
171 new DefaultArtifact(project.getGroupId(), project.getArtifactId(), null, "pom", project.getVersion());
172 if (consumerPom != null) {
173
174
175 try {
176 Path pomFile = Files.createTempFile(Paths.get(project.getBuild().getDirectory()), "consumer-", ".pom");
177 Files.copy(consumerPom.getFile().toPath(), pomFile, StandardCopyOption.REPLACE_EXISTING);
178 pomArtifact = pomArtifact.setFile(pomFile.toFile());
179 } catch (IOException e) {
180 p.println("Error processing consumer POM: " + e);
181 }
182 } else {
183 pomArtifact = pomArtifact.setFile(project.getFile());
184 }
185
186 artifacts.put(pomArtifact, prefix + n);
187 if (isIgnore(pomArtifact)) {
188 p.println("# ignored " + getArtifactFilename(pomArtifact));
189 } else {
190 printFile(
191 prefix + n++,
192 pomArtifact.getGroupId(),
193 pomArtifact.getFile(),
194 project.getArtifactId() + '-' + project.getVersion() + ".pom");
195 }
196
197 if (consumerPom != null) {
198
199 Artifact buildPomArtifact = new DefaultArtifact(
200 project.getGroupId(), project.getArtifactId(), "build", "pom", project.getVersion());
201 buildPomArtifact = buildPomArtifact.setFile(project.getFile());
202
203 if (isIgnore(buildPomArtifact)) {
204 p.println("# ignored " + getArtifactFilename(buildPomArtifact));
205 } else {
206 artifacts.put(buildPomArtifact, prefix + n);
207 printFile(
208 prefix + n++,
209 buildPomArtifact.getGroupId(),
210 buildPomArtifact.getFile(),
211 project.getArtifactId() + '-' + project.getVersion() + "-build.pom");
212 }
213 }
214
215 if (project.getArtifact() == null) {
216 return;
217 }
218
219 if (project.getArtifact().getFile() != null) {
220 Artifact main = RepositoryUtils.toArtifact(project.getArtifact());
221 if (isIgnore(main)) {
222 p.println("# ignored " + getArtifactFilename(main));
223 } else {
224 printArtifact(prefix, n++, RepositoryUtils.toArtifact(project.getArtifact()));
225 }
226 }
227
228 for (Artifact attached : RepositoryUtils.toArtifacts(project.getAttachedArtifacts())) {
229 if ("pom".equals(attached.getExtension()) && "consumer".equals(attached.getClassifier())) {
230
231 continue;
232 }
233 if (attached.getExtension().endsWith(".asc")) {
234
235 continue;
236 }
237 if (ignoreJavadoc && "javadoc".equals(attached.getClassifier())) {
238
239 continue;
240 }
241 if (isIgnore(attached)) {
242 p.println("# ignored " + getArtifactFilename(attached));
243 artifacts.put(attached, null);
244 continue;
245 }
246 printArtifact(prefix, n++, attached);
247 }
248 }
249
250 private void printArtifact(String prefix, int i, Artifact artifact) throws MojoExecutionException {
251 prefix = prefix + i;
252 File artifactFile = artifact.getFile();
253 if (artifactFile.isDirectory()) {
254 if ("pom".equals(artifact.getExtension())) {
255
256 return;
257 }
258
259
260 throw new MojoExecutionException("Artifact " + ArtifactIdUtils.toId(artifact) + " points to a directory: "
261 + artifactFile + ". Packaging should be 'pom'?");
262 }
263 if (!artifactFile.isFile()) {
264 log.warn("Ignoring artifact " + ArtifactIdUtils.toId(artifact) + " because it points to inexistent "
265 + artifactFile);
266 return;
267 }
268
269 printFile(prefix, artifact.getGroupId(), artifact.getFile(), getArtifactFilename(artifact));
270 artifacts.put(artifact, prefix);
271 }
272
273 static String getArtifactFilename(Artifact artifact) {
274 StringBuilder path = new StringBuilder(128);
275
276 path.append(artifact.getArtifactId()).append('-').append(artifact.getBaseVersion());
277
278 if (!artifact.getClassifier().isEmpty()) {
279 path.append('-').append(artifact.getClassifier());
280 }
281
282 if (!artifact.getExtension().isEmpty()) {
283 path.append('.').append(artifact.getExtension());
284 }
285
286 return path.toString();
287 }
288
289 void printFile(String prefix, String groupId, File file) throws MojoExecutionException {
290 printFile(prefix, groupId, file, file.getName());
291 }
292
293 private void printFile(String prefix, String groupId, File file, String filename) throws MojoExecutionException {
294 p.println();
295 p.println(prefix + ".groupId=" + groupId);
296 p.println(prefix + ".filename=" + filename);
297 p.println(prefix + ".length=" + file.length());
298 try (InputStream is = Files.newInputStream(file.toPath())) {
299 p.println(prefix + ".checksums.sha512=" + DigestUtils.sha512Hex(is));
300 } catch (IOException ioe) {
301 throw new MojoExecutionException("Error processing file " + file, ioe);
302 } catch (IllegalArgumentException iae) {
303 throw new MojoExecutionException("Could not get hash algorithm", iae.getCause());
304 }
305 }
306
307 Map<Artifact, String> getArtifacts() {
308 return artifacts;
309 }
310
311
312
313
314
315
316
317
318 static Properties loadOutputProperties(File buildinfo) throws MojoExecutionException {
319 Properties prop = new Properties();
320 if (buildinfo != null) {
321 try (InputStream is = Files.newInputStream(buildinfo.toPath())) {
322 prop.load(is);
323 } catch (IOException e) {
324
325 }
326 }
327 for (String name : prop.stringPropertyNames()) {
328 if (!name.startsWith("outputs.") || name.endsWith(".coordinates")) {
329 prop.remove(name);
330 }
331 }
332 return prop;
333 }
334
335 boolean getIgnoreJavadoc() {
336 return ignoreJavadoc;
337 }
338
339 void setIgnoreJavadoc(boolean ignoreJavadoc) {
340 this.ignoreJavadoc = ignoreJavadoc;
341 }
342
343 void setIgnore(List<String> ignore) {
344 FileSystem fs = FileSystems.getDefault();
345 this.ignore = ignore.stream().map(i -> fs.getPathMatcher("glob:" + i)).collect(Collectors.toList());
346 }
347
348 boolean isIgnore(Artifact attached) {
349 Path path = Paths.get(attached.getGroupId() + '/' + getArtifactFilename(attached));
350 return ignore.stream().anyMatch(m -> m.matches(path));
351 }
352
353 public void setToolchain(Toolchain toolchain) {
354 this.toolchain = toolchain;
355 }
356 }