View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * Utility to download reference artifacts and download or generate reference buildinfo.
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       * Directory of the downloaded reference files.
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             // TODO keep a save?
122             referenceBuildinfo = null;
123         }
124 
125         if (referenceBuildinfo == null) {
126             // download reference artifacts and guess Java version and OS
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                     // download
135                     File file = downloadReference(repo, artifact);
136                     referenceArtifacts.put(artifact, file);
137 
138                     // guess Java version and OS
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             // generate buildinfo from reference artifacts
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                         // check against current line separator
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"); // MSHARED-797
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 }