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.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   * Buildinfo content writer.
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          // p.println( "# optional build setup url, as plugin parameter" );
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             // TODO wrong algorithm, should reuse algorithm written in versions-maven-plugin
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('.'); // for example 8.0_202
127         if (index < 0) {
128             index = javaVersion.indexOf('-'); // for example 17-ea
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         // p.println( "# TBD source.* artifact, url should be parameters" );
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                 // TODO is it possible to use Scm API to get SCM version?
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             // aggregated buildinfo output
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                 // ignore pgp signatures
190                 continue;
191             }
192             if (ignoreJavadoc && "javadoc".equals(attached.getClassifier())) {
193                 // TEMPORARY ignore javadoc, waiting for MJAVADOC-627 in m-javadoc-p 3.2.0
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                 // ignore .pom files: they should not be treated as Artifacts
209                 return;
210             }
211             // edge case found in a distribution module with default packaging and skip set for
212             // m-jar-p: should use pom packaging instead
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      * Load buildinfo file and extracts properties on output files.
267      *
268      * @param buildinfo the build info file
269      * @return output properties
270      * @throws MojoExecutionException
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 }