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