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.handler.ArtifactHandler;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.plugin.logging.Log;
33  import org.apache.maven.project.MavenProject;
34  import org.apache.maven.shared.utils.PropertyUtils;
35  import org.apache.maven.toolchain.Toolchain;
36  
37  /**
38   * Buildinfo content writer.
39   */
40  class BuildInfoWriter
41  {
42      private final Log log;
43      private final PrintWriter p;
44      private final boolean mono;
45      private final Map<Artifact, String> artifacts = new LinkedHashMap<>();
46      private int projectCount = -1;
47      private boolean ignoreJavadoc = true;
48      private Set<String> ignore;
49      private Toolchain toolchain;
50  
51      BuildInfoWriter( Log log, PrintWriter p, boolean mono )
52      {
53          this.log = log;
54          this.p = p;
55          this.mono = mono;
56      }
57  
58      void printHeader( MavenProject project, MavenProject aggregate, boolean reproducible )
59      {
60          p.println( "# https://reproducible-builds.org/docs/jvm/" );
61          p.println( "buildinfo.version=1.0-SNAPSHOT" );
62          p.println();
63          p.println( "name=" + project.getName() );
64          p.println( "group-id=" + project.getGroupId() );
65          p.println( "artifact-id=" + project.getArtifactId() );
66          p.println( "version=" + project.getVersion() );
67          p.println();
68          printSourceInformation( project );
69          p.println();
70          p.println( "# build instructions" );
71          p.println( "build-tool=mvn" );
72          //p.println( "# optional build setup url, as plugin parameter" );
73          p.println();
74          if ( reproducible )
75          {
76              p.println( "# build environment information (simplified for reproducibility)" );
77              p.println( "java.version=" + extractJavaMajorVersion( System.getProperty( "java.version" ) ) );
78              String ls = System.getProperty( "line.separator" );
79              p.println( "os.name=" + ( "\n".equals( ls ) ? "Unix" : "Windows" ) );
80          }
81          else
82          {
83              p.println( "# effective build environment information" );
84              p.println( "java.version=" + System.getProperty( "java.version" ) );
85              p.println( "java.vendor=" + System.getProperty( "java.vendor" ) );
86              p.println( "os.name=" + System.getProperty( "os.name" ) );
87          }
88          p.println();
89          p.println( "# Maven rebuild instructions and effective environment" );
90          //p.println( "mvn.rebuild-args=" + rebuildArgs );
91          if ( !reproducible )
92          {
93              p.println( "mvn.version=" + MavenVersion.createMavenVersionString() );
94          }
95          if ( ( project.getPrerequisites() != null ) && ( project.getPrerequisites().getMaven() != null ) )
96          {
97              // TODO wrong algorithm, should reuse algorithm written in versions-maven-plugin
98              p.println( "mvn.minimum.version=" + project.getPrerequisites().getMaven() );
99          }
100         if ( toolchain != null )
101         {
102             String javaVersion = JdkToolchainUtil.getJavaVersion( toolchain );
103             if ( reproducible )
104             {
105                 javaVersion = extractJavaMajorVersion( javaVersion );
106             }
107             p.println( "mvn.toolchain.jdk=" + javaVersion );
108         }
109 
110         if ( !mono && ( aggregate != null ) )
111         {
112             p.println( "mvn.aggregate.artifact-id=" + aggregate.getArtifactId() );
113         }
114 
115         p.println();
116         p.println( "# " + ( mono ? "" : "aggregated " ) + "output" );
117     }
118 
119     private static String extractJavaMajorVersion( String javaVersion )
120     {
121         if ( javaVersion.startsWith( "1." ) )
122         {
123             javaVersion = javaVersion.substring( 2 );
124         }
125         return javaVersion.substring( 0, javaVersion.indexOf( '.' ) );
126     }
127 
128     private void printSourceInformation( MavenProject project )
129     {
130         boolean sourceAvailable = false;
131         p.println( "# source information" );
132         //p.println( "# TBD source.* artifact, url should be parameters" );
133         if ( project.getScm() != null )
134         {
135             sourceAvailable = true;
136             p.println( "source.scm.uri=" + project.getScm().getConnection() );
137             p.println( "source.scm.tag=" + project.getScm().getTag() );
138             if ( project.getArtifact().isSnapshot() )
139             {
140                 log.warn( "SCM source tag in buildinfo source.scm.tag=" + project.getScm().getTag()
141                     + " does not permit rebuilders reproducible source checkout" );
142                 // TODO is it possible to use Scm API to get SCM version?
143             }
144         }
145         else
146         {
147             p.println( "# no scm configured in pom.xml" );
148         }
149 
150         if ( !sourceAvailable )
151         {
152             log.warn( "No source information available in buildinfo for rebuilders..." );
153         }
154     }
155 
156     void printArtifacts( MavenProject project )
157         throws MojoExecutionException
158     {
159         if ( project.getArtifact() == null )
160         {
161             return;
162         }
163 
164         String prefix = "outputs.";
165         if ( !mono )
166         {
167             // aggregated buildinfo output
168             projectCount++;
169             prefix += projectCount + ".";
170             p.println();
171             p.println( prefix + "coordinates=" + project.getGroupId() + ':' + project.getArtifactId() );
172         }
173 
174         int n = 0;
175         if ( project.getArtifact().getFile() != null )
176         {
177             printArtifact( prefix, n++, project.getArtifact() );
178         }
179 
180         for ( Artifact attached : project.getAttachedArtifacts() )
181         {
182             if ( attached.getType().endsWith( ".asc" ) )
183             {
184                 // ignore pgp signatures
185                 continue;
186             }
187             if ( ignoreJavadoc && "javadoc".equals( attached.getClassifier() ) )
188             {
189                 // TEMPORARY ignore javadoc, waiting for MJAVADOC-627 in m-javadoc-p 3.2.0
190                 continue;
191             }
192             if ( isIgnore( attached ) )
193             {
194                 continue;
195             }
196             printArtifact( prefix, n++, attached );
197         }
198     }
199 
200     private void printArtifact( String prefix, int i, Artifact artifact )
201         throws MojoExecutionException
202     {
203         prefix = prefix + i;
204         if ( artifact.getFile().isDirectory() )
205         {
206             // edge case found in a distribution module with default packaging and skip set for
207             // m-jar-p: should use pom packaging instead
208             throw new MojoExecutionException( "Artifact " + artifact.getId() + " points to a directory: "
209                 + artifact.getFile() + ". Packaging should be 'pom'?" );
210         }
211         printFile( prefix, artifact.getFile(), getArtifactFilename( artifact ) );
212         artifacts.put( artifact, prefix );
213     }
214 
215     private String getArtifactFilename( Artifact artifact )
216     {
217         StringBuilder path = new StringBuilder( 128 );
218 
219         path.append( artifact.getArtifactId() ).append( '-' ).append( artifact.getBaseVersion() );
220 
221         if ( artifact.hasClassifier() )
222         {
223             path.append( '-' ).append( artifact.getClassifier() );
224         }
225 
226         ArtifactHandler artifactHandler = artifact.getArtifactHandler();
227 
228         if ( artifactHandler.getExtension() != null && artifactHandler.getExtension().length() > 0 )
229         {
230             path.append( '.' ).append( artifactHandler.getExtension() );
231         }
232 
233         return path.toString();
234     }
235 
236     void printFile( String prefix, File file )
237         throws MojoExecutionException
238     {
239         printFile( prefix, file, file.getName() );
240     }
241 
242     private void printFile( String prefix, File file, String filename )
243         throws MojoExecutionException
244     {
245         p.println();
246         p.println( prefix + ".filename=" + filename );
247         p.println( prefix + ".length=" + file.length() );
248         p.println( prefix + ".checksums.sha512=" + DigestHelper.calculateSha512( file ) );
249     }
250 
251     Map<Artifact, String> getArtifacts()
252     {
253         return artifacts;
254     }
255 
256     /**
257      * Load buildinfo file and extracts properties on output files.
258      *
259      * @param buildinfo the build info file
260      * @return output properties
261      * @throws MojoExecutionException
262      */
263     static Properties loadOutputProperties( File buildinfo )
264         throws MojoExecutionException
265     {
266         Properties prop = PropertyUtils.loadOptionalProperties( buildinfo );
267         for ( String name : prop.stringPropertyNames() )
268         {
269             if ( !name.startsWith( "outputs." ) || name.endsWith( ".coordinates" ) )
270             {
271                 prop.remove( name );
272             }
273         }
274         return prop;
275     }
276 
277     boolean getIgnoreJavadoc()
278     {
279         return ignoreJavadoc;
280     }
281 
282     void setIgnoreJavadoc( boolean ignoreJavadoc )
283     {
284         this.ignoreJavadoc = ignoreJavadoc;
285     }
286 
287     void setIgnore( Set<String> ignore )
288     {
289         this.ignore = ignore;
290     }
291 
292     private boolean isIgnore( Artifact attached )
293     {
294         String classifier = attached.getClassifier();
295         String extension = attached.getType();
296         String search = ( classifier == null ) ? "" : ( classifier + '.' ) + extension;
297         return ignore.contains( search );
298     }
299 
300     public void setToolchain( Toolchain toolchain )
301     {
302         this.toolchain = toolchain;
303     }
304 }