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