View Javadoc
1   package org.apache.maven.plugins.artifact.buildinfo;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.PrintWriter;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.Properties;
27  import java.util.Set;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.artifact.DefaultArtifact;
31  import org.apache.maven.artifact.handler.ArtifactHandler;
32  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.logging.Log;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.shared.utils.PropertyUtils;
37  import org.apache.maven.toolchain.Toolchain;
38  
39  /**
40   * Buildinfo content writer.
41   */
42  class BuildInfoWriter
43  {
44      private final Log log;
45      private final PrintWriter p;
46      private final boolean mono;
47      private final ArtifactHandlerManager artifactHandlerManager;
48      private final Map<Artifact, String> artifacts = new LinkedHashMap<>();
49      private int projectCount = -1;
50      private boolean ignoreJavadoc = true;
51      private Set<String> ignore;
52      private Toolchain toolchain;
53  
54      BuildInfoWriter( Log log, PrintWriter p, boolean mono, ArtifactHandlerManager artifactHandlerManager )
55      {
56          this.log = log;
57          this.p = p;
58          this.mono = mono;
59          this.artifactHandlerManager = artifactHandlerManager;
60      }
61  
62      void printHeader( MavenProject project, MavenProject aggregate, boolean reproducible )
63      {
64          p.println( "# https://reproducible-builds.org/docs/jvm/" );
65          p.println( "buildinfo.version=1.0-SNAPSHOT" );
66          p.println();
67          p.println( "name=" + project.getName() );
68          p.println( "group-id=" + project.getGroupId() );
69          p.println( "artifact-id=" + project.getArtifactId() );
70          p.println( "version=" + project.getVersion() );
71          p.println();
72          printSourceInformation( project );
73          p.println();
74          p.println( "# build instructions" );
75          p.println( "build-tool=mvn" );
76          //p.println( "# optional build setup url, as plugin parameter" );
77          p.println();
78          if ( reproducible )
79          {
80              p.println( "# build environment information (simplified for reproducibility)" );
81              p.println( "java.version=" + extractJavaMajorVersion( System.getProperty( "java.version" ) ) );
82              String ls = System.getProperty( "line.separator" );
83              p.println( "os.name=" + ( "\n".equals( ls ) ? "Unix" : "Windows" ) );
84          }
85          else
86          {
87              p.println( "# effective build environment information" );
88              p.println( "java.version=" + System.getProperty( "java.version" ) );
89              p.println( "java.vendor=" + System.getProperty( "java.vendor" ) );
90              p.println( "os.name=" + System.getProperty( "os.name" ) );
91          }
92          p.println();
93          p.println( "# Maven rebuild instructions and effective environment" );
94          if ( !reproducible )
95          {
96              p.println( "mvn.version=" + MavenVersion.createMavenVersionString() );
97          }
98          if ( ( project.getPrerequisites() != null ) && ( project.getPrerequisites().getMaven() != null ) )
99          {
100             // TODO wrong algorithm, should reuse algorithm written in versions-maven-plugin
101             p.println( "mvn.minimum.version=" + project.getPrerequisites().getMaven() );
102         }
103         if ( toolchain != null )
104         {
105             String javaVersion = JdkToolchainUtil.getJavaVersion( toolchain );
106             if ( reproducible )
107             {
108                 javaVersion = extractJavaMajorVersion( javaVersion );
109             }
110             p.println( "mvn.toolchain.jdk=" + javaVersion );
111         }
112 
113         if ( !mono && ( aggregate != null ) )
114         {
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     {
124         if ( javaVersion.startsWith( "1." ) )
125         {
126             javaVersion = javaVersion.substring( 2 );
127         }
128         int index = javaVersion.indexOf( '.' ); // for example 8.0_202
129         if ( index < 0 )
130         {
131             index = javaVersion.indexOf( '-' ); // for example 17-ea
132         }
133         return ( index < 0 ) ? javaVersion : javaVersion.substring( 0, index );
134     }
135 
136     private void printSourceInformation( MavenProject project )
137     {
138         boolean sourceAvailable = false;
139         p.println( "# source information" );
140         //p.println( "# TBD source.* artifact, url should be parameters" );
141         if ( project.getScm() != null )
142         {
143             sourceAvailable = true;
144             p.println( "source.scm.uri=" + project.getScm().getConnection() );
145             p.println( "source.scm.tag=" + project.getScm().getTag() );
146             if ( project.getArtifact().isSnapshot() )
147             {
148                 log.warn( "SCM source tag in buildinfo source.scm.tag=" + project.getScm().getTag()
149                     + " does not permit rebuilders reproducible source checkout" );
150                 // TODO is it possible to use Scm API to get SCM version?
151             }
152         }
153         else
154         {
155             p.println( "# no scm configured in pom.xml" );
156         }
157 
158         if ( !sourceAvailable )
159         {
160             log.warn( "No source information available in buildinfo for rebuilders..." );
161         }
162     }
163 
164     void printArtifacts( MavenProject project )
165         throws MojoExecutionException
166     {
167         String prefix = "outputs.";
168         if ( !mono )
169         {
170             // aggregated buildinfo output
171             projectCount++;
172             prefix += projectCount + ".";
173             p.println();
174             p.println( prefix + "coordinates=" + project.getGroupId() + ':' + project.getArtifactId() );
175         }
176 
177         int n = 0;
178         Artifact pomArtifact =
179             new DefaultArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), null, "pom", "",
180                                  artifactHandlerManager.getArtifactHandler( "pom" ) );
181         pomArtifact.setFile( project.getFile() );
182 
183         artifacts.put( pomArtifact, prefix + n );
184         printFile( prefix + n++, project.getFile(), project.getArtifactId() + '-' + project.getVersion() + ".pom" );
185 
186         if ( project.getArtifact() == null )
187         {
188             return;
189         }
190 
191         if ( project.getArtifact().getFile() != null )
192         {
193             printArtifact( prefix, n++, project.getArtifact() );
194         }
195 
196         for ( Artifact attached : project.getAttachedArtifacts() )
197         {
198             if ( attached.getType().endsWith( ".asc" ) )
199             {
200                 // ignore pgp signatures
201                 continue;
202             }
203             if ( ignoreJavadoc && "javadoc".equals( attached.getClassifier() ) )
204             {
205                 // TEMPORARY ignore javadoc, waiting for MJAVADOC-627 in m-javadoc-p 3.2.0
206                 continue;
207             }
208             if ( isIgnore( attached ) )
209             {
210                 continue;
211             }
212             printArtifact( prefix, n++, attached );
213         }
214     }
215 
216     private void printArtifact( String prefix, int i, Artifact artifact )
217         throws MojoExecutionException
218     {
219         prefix = prefix + i;
220         File artifactFile = artifact.getFile();
221         if ( artifactFile.isDirectory() )
222         {
223             if ( "pom".equals( artifact.getType() ) )
224             {
225                 // ignore .pom files: they should not be treated as Artifacts
226                 return;
227             }
228             // edge case found in a distribution module with default packaging and skip set for
229             // m-jar-p: should use pom packaging instead
230             throw new MojoExecutionException( "Artifact " + artifact.getId() + " points to a directory: "
231                 + artifactFile + ". Packaging should be 'pom'?" );
232         }
233         if ( !artifactFile.isFile() )
234         {
235             log.warn( "Ignoring artifact " + artifact.getId() + " because it points to inexistent " + artifactFile );
236             return;
237         }
238 
239         printFile( prefix, artifact.getFile(), getArtifactFilename( artifact ) );
240         artifacts.put( artifact, prefix );
241     }
242 
243     private String getArtifactFilename( Artifact artifact )
244     {
245         StringBuilder path = new StringBuilder( 128 );
246 
247         path.append( artifact.getArtifactId() ).append( '-' ).append( artifact.getBaseVersion() );
248 
249         if ( artifact.hasClassifier() )
250         {
251             path.append( '-' ).append( artifact.getClassifier() );
252         }
253 
254         ArtifactHandler artifactHandler = artifact.getArtifactHandler();
255 
256         if ( artifactHandler.getExtension() != null && artifactHandler.getExtension().length() > 0 )
257         {
258             path.append( '.' ).append( artifactHandler.getExtension() );
259         }
260 
261         return path.toString();
262     }
263 
264     void printFile( String prefix, File file )
265         throws MojoExecutionException
266     {
267         printFile( prefix, file, file.getName() );
268     }
269 
270     private void printFile( String prefix, File file, String filename )
271         throws MojoExecutionException
272     {
273         p.println();
274         p.println( prefix + ".filename=" + filename );
275         p.println( prefix + ".length=" + file.length() );
276         p.println( prefix + ".checksums.sha512=" + DigestHelper.calculateSha512( file ) );
277     }
278 
279     Map<Artifact, String> getArtifacts()
280     {
281         return artifacts;
282     }
283 
284     /**
285      * Load buildinfo file and extracts properties on output files.
286      *
287      * @param buildinfo the build info file
288      * @return output properties
289      * @throws MojoExecutionException
290      */
291     static Properties loadOutputProperties( File buildinfo )
292         throws MojoExecutionException
293     {
294         Properties prop = PropertyUtils.loadOptionalProperties( buildinfo );
295         for ( String name : prop.stringPropertyNames() )
296         {
297             if ( !name.startsWith( "outputs." ) || name.endsWith( ".coordinates" ) )
298             {
299                 prop.remove( name );
300             }
301         }
302         return prop;
303     }
304 
305     boolean getIgnoreJavadoc()
306     {
307         return ignoreJavadoc;
308     }
309 
310     void setIgnoreJavadoc( boolean ignoreJavadoc )
311     {
312         this.ignoreJavadoc = ignoreJavadoc;
313     }
314 
315     void setIgnore( Set<String> ignore )
316     {
317         this.ignore = ignore;
318     }
319 
320     private boolean isIgnore( Artifact attached )
321     {
322         String classifier = attached.getClassifier();
323         String extension = attached.getType();
324         String search = ( classifier == null ) ? "" : ( classifier + '.' ) + extension;
325         return ignore.contains( search );
326     }
327 
328     public void setToolchain( Toolchain toolchain )
329     {
330         this.toolchain = toolchain;
331     }
332 }