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.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
126 String javaVersion = null;
127 String osName = null;
128 String currentJavaVersion = null;
129 String currentOsName = null;
130 Map<Artifact, File> referenceArtifacts = new HashMap<>();
131 for (Artifact artifact : artifacts.keySet()) {
132 try {
133
134 File file = downloadReference(repo, artifact);
135 referenceArtifacts.put(artifact, file);
136
137
138 if ((javaVersion == null) && JAR_TYPES.contains(artifact.getType())) {
139 ReproducibleEnv env = extractEnv(file, artifact);
140 if ((env != null) && (env.javaVersion != null)) {
141 javaVersion = env.javaVersion;
142 osName = env.osName;
143
144 ReproducibleEnv currentEnv = extractEnv(artifact.getFile(), artifact);
145 currentJavaVersion = currentEnv.javaVersion;
146 currentOsName = currentEnv.osName;
147 }
148 }
149 } catch (ArtifactNotFoundException e) {
150 log.warn("Reference artifact not found " + artifact);
151 }
152 }
153
154
155 referenceBuildinfo = getReference(null, buildinfoFile);
156 try (PrintWriter p = new PrintWriter(new BufferedWriter(
157 new OutputStreamWriter(Files.newOutputStream(referenceBuildinfo.toPath()), StandardCharsets.UTF_8)))) {
158 BuildInfoWriter bi = new BuildInfoWriter(log, p, mono, artifactHandlerManager, rtInformation);
159
160 if (javaVersion != null || osName != null) {
161 p.println("# effective build environment information");
162 if (javaVersion != null) {
163 p.println("java.version=" + javaVersion);
164 log.info("Reference build java.version: " + javaVersion);
165 if (!javaVersion.equals(currentJavaVersion)) {
166 log.error("Current build java.version: " + currentJavaVersion);
167 }
168 }
169 if (osName != null) {
170 p.println("os.name=" + osName);
171 log.info("Reference build os.name: " + osName);
172
173
174 if (!osName.equals(currentOsName)) {
175 log.error("Current build os.name: " + currentOsName);
176 }
177 String expectedLs = osName.startsWith("Windows") ? "\r\n" : "\n";
178 if (!expectedLs.equals(System.lineSeparator())) {
179 log.warn("Current System.lineSeparator() does not match reference build OS");
180
181 String ls = System.getProperty("line.separator");
182 if (!ls.equals(System.lineSeparator())) {
183 log.warn("System.lineSeparator() != System.getProperty( \"line.separator\" ): "
184 + "too late standard system property update...");
185 }
186 }
187 }
188 }
189
190 for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) {
191 Artifact artifact = entry.getKey();
192 String prefix = entry.getValue();
193 File referenceFile = referenceArtifacts.get(artifact);
194 if (referenceFile != null) {
195 bi.printFile(prefix, artifact.getGroupId(), referenceFile);
196 }
197 }
198
199 if (p.checkError()) {
200 throw new MojoExecutionException("Write error to " + referenceBuildinfo);
201 }
202
203 log.info("Minimal buildinfo generated from downloaded artifacts: " + referenceBuildinfo);
204 } catch (IOException e) {
205 throw new MojoExecutionException("Error creating file " + referenceBuildinfo, e);
206 }
207
208 return referenceBuildinfo;
209 }
210
211 private ReproducibleEnv extractEnv(File file, Artifact artifact) {
212 log.debug("Guessing java.version and os.name from jar " + file);
213 try (JarFile jar = new JarFile(file)) {
214 Manifest manifest = jar.getManifest();
215 if (manifest != null) {
216 String javaVersion = extractJavaVersion(manifest);
217 String osName = extractOsName(artifact, jar);
218 return new ReproducibleEnv(javaVersion, osName);
219 } else {
220 log.warn("no MANIFEST.MF found in jar " + file);
221 }
222 } catch (IOException e) {
223 log.warn("unable to open jar file " + file, e);
224 }
225 return null;
226 }
227
228 private String extractJavaVersion(Manifest manifest) {
229 Attributes attr = manifest.getMainAttributes();
230
231 String value = attr.getValue("Build-Jdk-Spec");
232 if (value != null) {
233 return value + " (from MANIFEST.MF Build-Jdk-Spec)";
234 }
235
236 value = attr.getValue("Build-Jdk");
237 if (value != null) {
238 return String.valueOf(value) + " (from MANIFEST.MF Build-Jdk)";
239 }
240
241 return null;
242 }
243
244 private String extractOsName(Artifact a, JarFile jar) {
245 String entryName = "META-INF/maven/" + a.getGroupId() + '/' + a.getArtifactId() + "/pom.properties";
246 ZipEntry zipEntry = jar.getEntry(entryName);
247 if (zipEntry == null) {
248 return null;
249 }
250 try (InputStream in = jar.getInputStream(zipEntry)) {
251 String content = IOUtil.toString(in, StandardCharsets.UTF_8.name());
252 log.debug("Manifest content: " + content);
253 if (content.contains("\r\n")) {
254 return "Windows (from pom.properties newline)";
255 } else if (content.contains("\n")) {
256 return "Unix (from pom.properties newline)";
257 }
258 } catch (IOException e) {
259 log.warn("Unable to read " + entryName + " from " + jar, e);
260 }
261 return null;
262 }
263
264 private File downloadReferenceBuildinfo(RemoteRepository repo, MavenProject project) throws MojoExecutionException {
265 Artifact buildinfo = artifactFactory.createArtifactWithClassifier(
266 project.getGroupId(), project.getArtifactId(), project.getVersion(), "buildinfo", "");
267 try {
268 File file = downloadReference(repo, buildinfo);
269
270 log.info("Reference buildinfo file found, copied to " + file);
271
272 return file;
273 } catch (ArtifactNotFoundException e) {
274 log.info("Reference buildinfo file not found: "
275 + "it will be generated from downloaded reference artifacts");
276 }
277
278 return null;
279 }
280
281 private File downloadReference(RemoteRepository repo, Artifact artifact)
282 throws MojoExecutionException, ArtifactNotFoundException {
283 try {
284 ArtifactRequest request = new ArtifactRequest();
285 request.setArtifact(new DefaultArtifact(
286 artifact.getGroupId(),
287 artifact.getArtifactId(),
288 artifact.getClassifier(),
289 (artifact.getArtifactHandler() != null)
290 ? artifact.getArtifactHandler().getExtension()
291 : artifact.getType(),
292 artifact.getVersion()));
293 request.setRepositories(Collections.singletonList(repo));
294
295 ArtifactResult result =
296 repoSystem.resolveArtifact(new NoWorkspaceRepositorySystemSession(repoSession), request);
297 File resultFile = result.getArtifact().getFile();
298 File destFile = getReference(artifact.getGroupId(), resultFile);
299
300 FileUtils.copyFile(resultFile, destFile);
301
302 return destFile;
303 } catch (org.eclipse.aether.resolution.ArtifactResolutionException are) {
304 if (are.getResult().isMissing()) {
305 throw new ArtifactNotFoundException("Artifact not found " + artifact, artifact);
306 }
307 throw new MojoExecutionException("Error resolving reference artifact " + artifact, are);
308 } catch (IOException ioe) {
309 throw new MojoExecutionException("Error copying reference artifact " + artifact, ioe);
310 }
311 }
312
313 private File getReference(String groupId, File file) {
314 File dir;
315 if (groupId == null) {
316 dir = referenceDir;
317 } else {
318 dir = new File(referenceDir, groupId);
319 if (!dir.isDirectory()) {
320 dir.mkdir();
321 }
322 }
323 return new File(dir, file.getName());
324 }
325
326 private static class NoWorkspaceRepositorySystemSession extends AbstractForwardingRepositorySystemSession {
327 private final RepositorySystemSession rss;
328
329 NoWorkspaceRepositorySystemSession(RepositorySystemSession rss) {
330 this.rss = rss;
331 }
332
333 @Override
334 protected RepositorySystemSession getSession() {
335 return rss;
336 }
337
338 @Override
339 public WorkspaceReader getWorkspaceReader() {
340 return null;
341 }
342 }
343
344 private static class ReproducibleEnv {
345 @SuppressWarnings("checkstyle:visibilitymodifier")
346 public final String javaVersion;
347
348 @SuppressWarnings("checkstyle:visibilitymodifier")
349 public final String osName;
350
351 ReproducibleEnv(String javaVersion, String osName) {
352 this.javaVersion = javaVersion;
353 this.osName = osName;
354 }
355 }
356 }