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.InputStream;
25 import java.io.OutputStreamWriter;
26 import java.io.PrintWriter;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.LinkOption;
30 import java.nio.file.StandardCopyOption;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.jar.Attributes;
37 import java.util.jar.JarFile;
38 import java.util.jar.Manifest;
39 import java.util.zip.ZipEntry;
40
41 import org.apache.commons.io.IOUtils;
42 import org.apache.maven.plugin.MojoExecutionException;
43 import org.apache.maven.plugin.logging.Log;
44 import org.apache.maven.project.MavenProject;
45 import org.apache.maven.rtinfo.RuntimeInformation;
46 import org.eclipse.aether.AbstractForwardingRepositorySystemSession;
47 import org.eclipse.aether.RepositorySystem;
48 import org.eclipse.aether.RepositorySystemSession;
49 import org.eclipse.aether.artifact.Artifact;
50 import org.eclipse.aether.artifact.DefaultArtifact;
51 import org.eclipse.aether.repository.RemoteRepository;
52 import org.eclipse.aether.repository.WorkspaceReader;
53 import org.eclipse.aether.resolution.ArtifactRequest;
54 import org.eclipse.aether.resolution.ArtifactResolutionException;
55 import org.eclipse.aether.resolution.ArtifactResult;
56
57
58
59
60 class ReferenceBuildinfoUtil {
61 private static final Set<String> JAR_EXTENSIONS;
62
63 static {
64 Set<String> extensions = new HashSet<>();
65 extensions.add("jar");
66 extensions.add("war");
67 extensions.add("ear");
68 extensions.add("rar");
69 JAR_EXTENSIONS = Collections.unmodifiableSet(extensions);
70 }
71
72 private final Log log;
73
74
75
76
77 private final File referenceDir;
78
79 private final Map<Artifact, String> artifacts;
80
81 private final RepositorySystem repoSystem;
82
83 private final RepositorySystemSession repoSession;
84
85 private final RuntimeInformation rtInformation;
86
87 ReferenceBuildinfoUtil(
88 Log log,
89 File referenceDir,
90 Map<Artifact, String> artifacts,
91 RepositorySystem repoSystem,
92 RepositorySystemSession repoSession,
93 RuntimeInformation rtInformation) {
94 this.log = log;
95 this.referenceDir = referenceDir;
96 this.artifacts = artifacts;
97 this.repoSystem = repoSystem;
98 this.repoSession = repoSession;
99 this.rtInformation = rtInformation;
100 }
101
102 File downloadOrCreateReferenceBuildinfo(
103 RemoteRepository repo, MavenProject project, File buildinfoFile, boolean mono)
104 throws MojoExecutionException {
105 File referenceBuildinfo = downloadReferenceBuildinfo(repo, project);
106
107 if (referenceBuildinfo != null) {
108 log.warn("dropping downloaded reference buildinfo because it may be generated"
109 + " from different maven-artifact-plugin release...");
110
111 referenceBuildinfo = null;
112 }
113
114
115 String javaVersion = null;
116 String osName = null;
117 String currentJavaVersion = null;
118 String currentOsName = null;
119 Map<Artifact, File> referenceArtifacts = new HashMap<>();
120 for (Artifact artifact : artifacts.keySet()) {
121 try {
122
123 File file = downloadReference(repo, artifact);
124 referenceArtifacts.put(artifact, file);
125
126
127 if ((javaVersion == null) && JAR_EXTENSIONS.contains(artifact.getExtension())) {
128 ReproducibleEnv env = extractEnv(file, artifact);
129 if ((env != null) && (env.javaVersion != null)) {
130 javaVersion = env.javaVersion;
131 osName = env.osName;
132
133 ReproducibleEnv currentEnv = extractEnv(artifact.getFile(), artifact);
134 currentJavaVersion = currentEnv.javaVersion;
135 currentOsName = currentEnv.osName;
136 }
137 }
138 } catch (ArtifactResolutionException e) {
139 log.warn("Reference artifact not found " + artifact);
140 }
141 }
142
143 try {
144
145 referenceBuildinfo = getReference(null, buildinfoFile);
146 try (PrintWriter p = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
147 Files.newOutputStream(referenceBuildinfo.toPath()), StandardCharsets.UTF_8)))) {
148 BuildInfoWriter bi = new BuildInfoWriter(log, p, mono, rtInformation);
149
150 if (javaVersion != null || osName != null) {
151 p.println("# effective build environment information");
152 if (javaVersion != null) {
153 p.println("java.version=" + javaVersion);
154 log.info("Reference build java.version: " + javaVersion);
155 if (!javaVersion.equals(currentJavaVersion)) {
156 log.error("Current build java.version: " + currentJavaVersion);
157 }
158 }
159 if (osName != null) {
160 p.println("os.name=" + osName);
161 log.info("Reference build os.name: " + osName);
162
163
164 if (!osName.equals(currentOsName)) {
165 log.error("Current build os.name: " + currentOsName);
166 }
167 String expectedLs = osName.startsWith("Windows") ? "\r\n" : "\n";
168 if (!expectedLs.equals(System.lineSeparator())) {
169 log.warn("Current System.lineSeparator() does not match reference build OS");
170
171 String ls = System.getProperty("line.separator");
172 if (!ls.equals(System.lineSeparator())) {
173 log.warn("System.lineSeparator() != System.getProperty( \"line.separator\" ): "
174 + "too late standard system property update...");
175 }
176 }
177 }
178 }
179
180 for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) {
181 Artifact artifact = entry.getKey();
182 String prefix = entry.getValue();
183 File referenceFile = referenceArtifacts.get(artifact);
184 if (referenceFile != null) {
185 bi.printFile(prefix, artifact.getGroupId(), referenceFile);
186 }
187 }
188
189 if (p.checkError()) {
190 throw new MojoExecutionException("Write error to " + referenceBuildinfo);
191 }
192
193 log.info("Minimal buildinfo generated from downloaded artifacts: " + referenceBuildinfo);
194 }
195 } catch (IOException e) {
196 throw new MojoExecutionException("Error creating file " + referenceBuildinfo, e);
197 }
198
199 return referenceBuildinfo;
200 }
201
202 private ReproducibleEnv extractEnv(File file, Artifact artifact) {
203 log.debug("Guessing java.version and os.name from jar " + file);
204 try (JarFile jar = new JarFile(file)) {
205 Manifest manifest = jar.getManifest();
206 if (manifest != null) {
207 String javaVersion = extractJavaVersion(manifest);
208 String osName = extractOsName(artifact, jar);
209 return new ReproducibleEnv(javaVersion, osName);
210 } else {
211 log.warn("no MANIFEST.MF found in jar " + file);
212 }
213 } catch (IOException e) {
214 log.warn("unable to open jar file " + file, e);
215 }
216 return null;
217 }
218
219 private String extractJavaVersion(Manifest manifest) {
220 Attributes attr = manifest.getMainAttributes();
221
222 String value = attr.getValue("Build-Jdk-Spec");
223 if (value != null) {
224 return value + " (from MANIFEST.MF Build-Jdk-Spec)";
225 }
226
227 value = attr.getValue("Build-Jdk");
228 if (value != null) {
229 return String.valueOf(value) + " (from MANIFEST.MF Build-Jdk)";
230 }
231
232 return null;
233 }
234
235 private String extractOsName(Artifact a, JarFile jar) {
236 String entryName = "META-INF/maven/" + a.getGroupId() + '/' + a.getArtifactId() + "/pom.properties";
237 ZipEntry zipEntry = jar.getEntry(entryName);
238 if (zipEntry == null) {
239 return null;
240 }
241 try (InputStream in = jar.getInputStream(zipEntry)) {
242 String content = IOUtils.toString(in, StandardCharsets.UTF_8);
243 log.debug("Manifest content: " + content);
244 if (content.contains("\r\n")) {
245 return "Windows (from pom.properties newline)";
246 } else if (content.contains("\n")) {
247 return "Unix (from pom.properties newline)";
248 }
249 } catch (IOException e) {
250 log.warn("Unable to read " + entryName + " from " + jar, e);
251 }
252 return null;
253 }
254
255 private File downloadReferenceBuildinfo(RemoteRepository repo, MavenProject project) throws MojoExecutionException {
256 Artifact buildinfo = new DefaultArtifact(
257 project.getGroupId(), project.getArtifactId(), null, "buildinfo", project.getVersion());
258 try {
259 File file = downloadReference(repo, buildinfo);
260
261 log.info("Reference buildinfo file found, copied to " + file);
262
263 return file;
264 } catch (ArtifactResolutionException e) {
265 log.info("Reference buildinfo file not found: "
266 + "it will be generated from downloaded reference artifacts");
267 }
268
269 return null;
270 }
271
272 private File downloadReference(RemoteRepository repo, Artifact artifact)
273 throws MojoExecutionException, ArtifactResolutionException {
274 try {
275 ArtifactRequest request = new ArtifactRequest();
276 request.setArtifact(artifact);
277 request.setRepositories(Collections.singletonList(repo));
278
279 ArtifactResult result =
280 repoSystem.resolveArtifact(new NoWorkspaceRepositorySystemSession(repoSession), request);
281 File resultFile = result.getArtifact().getFile();
282 File destFile = getReference(artifact.getGroupId(), resultFile);
283
284 Files.copy(
285 resultFile.toPath(),
286 destFile.toPath(),
287 LinkOption.NOFOLLOW_LINKS,
288 StandardCopyOption.REPLACE_EXISTING);
289
290 return destFile;
291 } catch (ArtifactResolutionException are) {
292 if (are.getResult().isMissing()) {
293 throw are;
294 }
295 throw new MojoExecutionException("Error resolving reference artifact " + artifact, are);
296 } catch (IOException ioe) {
297 throw new MojoExecutionException("Error copying reference artifact " + artifact, ioe);
298 }
299 }
300
301 private File getReference(String groupId, File file) throws IOException {
302 File dir;
303 if (groupId == null) {
304 dir = referenceDir;
305 } else {
306 dir = new File(referenceDir, groupId);
307 if (!dir.isDirectory()) {
308 Files.createDirectories(dir.toPath());
309 }
310 }
311 return new File(dir, file.getName());
312 }
313
314 private static class NoWorkspaceRepositorySystemSession extends AbstractForwardingRepositorySystemSession {
315 private final RepositorySystemSession rss;
316
317 NoWorkspaceRepositorySystemSession(RepositorySystemSession rss) {
318 this.rss = rss;
319 }
320
321 @Override
322 protected RepositorySystemSession getSession() {
323 return rss;
324 }
325
326 @Override
327 public WorkspaceReader getWorkspaceReader() {
328 return null;
329 }
330 }
331
332 private static class ReproducibleEnv {
333 @SuppressWarnings("checkstyle:visibilitymodifier")
334 public final String javaVersion;
335
336 @SuppressWarnings("checkstyle:visibilitymodifier")
337 public final String osName;
338
339 ReproducibleEnv(String javaVersion, String osName) {
340 this.javaVersion = javaVersion;
341 this.osName = osName;
342 }
343 }
344 }