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