1 package org.apache.maven.plugins.artifact.buildinfo;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.artifact.Artifact;
23 import org.apache.maven.artifact.factory.ArtifactFactory;
24 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
25 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugin.logging.Log;
28 import org.apache.maven.project.MavenProject;
29 import org.apache.maven.shared.utils.io.IOUtil;
30 import org.apache.maven.shared.utils.io.FileUtils;
31 import org.eclipse.aether.AbstractForwardingRepositorySystemSession;
32 import org.eclipse.aether.RepositorySystem;
33 import org.eclipse.aether.RepositorySystemSession;
34 import org.eclipse.aether.artifact.DefaultArtifact;
35 import org.eclipse.aether.repository.RemoteRepository;
36 import org.eclipse.aether.repository.WorkspaceReader;
37 import org.eclipse.aether.resolution.ArtifactRequest;
38 import org.eclipse.aether.resolution.ArtifactResult;
39
40 import java.io.BufferedWriter;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStreamWriter;
46 import java.io.PrintWriter;
47 import java.nio.charset.StandardCharsets;
48 import java.util.Collections;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.jar.Attributes;
54 import java.util.jar.JarFile;
55 import java.util.jar.Manifest;
56 import java.util.zip.ZipEntry;
57
58
59
60
61 class ReferenceBuildinfoUtil
62 {
63 private static final Set<String> JAR_TYPES;
64
65 static
66 {
67 Set<String> types = new HashSet<>();
68 types.add( "jar" );
69 types.add( "test-jar" );
70 types.add( "war" );
71 types.add( "ear" );
72 types.add( "rar" );
73 types.add( "maven-plugin" );
74 JAR_TYPES = Collections.unmodifiableSet( types );
75 }
76
77 private final Log log;
78
79
80
81
82 private final File referenceDir;
83
84 private final Map<Artifact, String> artifacts;
85
86 private final ArtifactFactory artifactFactory;
87
88 private final RepositorySystem repoSystem;
89
90 private final RepositorySystemSession repoSession;
91
92 private final ArtifactHandlerManager artifactHandlerManager;
93
94 ReferenceBuildinfoUtil( Log log, File referenceDir, Map<Artifact, String> artifacts,
95 ArtifactFactory artifactFactory, RepositorySystem repoSystem,
96 RepositorySystemSession repoSession,
97 ArtifactHandlerManager artifactHandlerManager )
98 {
99 this.log = log;
100 this.referenceDir = referenceDir;
101 this.artifacts = artifacts;
102 this.artifactFactory = artifactFactory;
103 this.repoSystem = repoSystem;
104 this.repoSession = repoSession;
105 this.artifactHandlerManager = artifactHandlerManager;
106 }
107
108 File downloadOrCreateReferenceBuildinfo( RemoteRepository repo, MavenProject project, File buildinfoFile,
109 boolean mono )
110 throws MojoExecutionException
111 {
112 File referenceBuildinfo = downloadReferenceBuildinfo( repo, project );
113
114 if ( referenceBuildinfo == null )
115 {
116
117 String javaVersion = null;
118 String osName = null;
119 String currentJavaVersion = null;
120 String currentOsName = null;
121 Map<Artifact, File> referenceArtifacts = new HashMap<>();
122 for ( Artifact artifact : artifacts.keySet() )
123 {
124 try
125 {
126
127 File file = downloadReference( repo, artifact );
128 referenceArtifacts.put( artifact, file );
129
130
131 if ( ( javaVersion == null ) && JAR_TYPES.contains( artifact.getType() ) )
132 {
133 ReproducibleEnv env = extractEnv( file, artifact );
134 if ( ( env != null ) && ( env.javaVersion != null ) )
135 {
136 javaVersion = env.javaVersion;
137 osName = env.osName;
138
139 ReproducibleEnv currentEnv = extractEnv( artifact.getFile(), artifact );
140 currentJavaVersion = currentEnv.javaVersion;
141 currentOsName = currentEnv.osName;
142 }
143 }
144 }
145 catch ( ArtifactNotFoundException e )
146 {
147 log.warn( "Reference artifact not found " + artifact );
148 }
149 }
150
151
152 referenceBuildinfo = getReference( buildinfoFile );
153 try ( PrintWriter p =
154 new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream( referenceBuildinfo ),
155 StandardCharsets.UTF_8 ) ) ) )
156 {
157 BuildInfoWriter bi = new BuildInfoWriter( log, p, mono, artifactHandlerManager );
158
159 if ( javaVersion != null || osName != null )
160 {
161 p.println( "# effective build environment information" );
162 if ( javaVersion != null )
163 {
164 p.println( "java.version=" + javaVersion );
165 log.info( "Reference build java.version: " + javaVersion );
166 if ( !javaVersion.equals( currentJavaVersion ) )
167 {
168 log.error( "Current build java.version: " + currentJavaVersion );
169 }
170 }
171 if ( osName != null )
172 {
173 p.println( "os.name=" + osName );
174 log.info( "Reference build os.name: " + osName );
175
176
177 if ( !osName.equals( currentOsName ) )
178 {
179 log.error( "Current build os.name: " + currentOsName );
180 }
181 String expectedLs = osName.startsWith( "Windows" ) ? "\r\n" : "\n";
182 if ( !expectedLs.equals( System.lineSeparator() ) )
183 {
184 log.warn( "Current System.lineSeparator() does not match reference build OS" );
185
186 String ls = System.getProperty( "line.separator" );
187 if ( !ls.equals( System.lineSeparator() ) )
188 {
189 log.warn( "System.lineSeparator() != System.getProperty( \"line.separator\" ): "
190 + "too late standard system property update..." );
191 }
192 }
193 }
194 }
195
196 for ( Map.Entry<Artifact, String> entry : artifacts.entrySet() )
197 {
198 Artifact artifact = entry.getKey();
199 String prefix = entry.getValue();
200 File referenceFile = referenceArtifacts.get( artifact );
201 if ( referenceFile != null )
202 {
203 bi.printFile( prefix, referenceFile );
204 }
205 }
206
207 if ( p.checkError() )
208 {
209 throw new MojoExecutionException( "Write error to " + referenceBuildinfo );
210 }
211
212 log.info( "Minimal buildinfo generated from downloaded artifacts: " + referenceBuildinfo );
213 }
214 catch ( IOException e )
215 {
216 throw new MojoExecutionException( "Error creating file " + referenceBuildinfo, e );
217 }
218 }
219
220 return referenceBuildinfo;
221 }
222
223 private ReproducibleEnv extractEnv( File file, Artifact artifact )
224 {
225 log.debug( "Guessing java.version and os.name from jar " + file );
226 try ( JarFile jar = new JarFile( file ) )
227 {
228 Manifest manifest = jar.getManifest();
229 if ( manifest != null )
230 {
231 String javaVersion = extractJavaVersion( manifest );
232 String osName = extractOsName( artifact, jar );
233 return new ReproducibleEnv( javaVersion, osName );
234 }
235 else
236 {
237 log.warn( "no MANIFEST.MF found in jar " + file );
238 }
239 }
240 catch ( IOException e )
241 {
242 log.warn( "unable to open jar file " + file, e );
243 }
244 return null;
245 }
246
247 private String extractJavaVersion( Manifest manifest )
248 {
249 Attributes attr = manifest.getMainAttributes();
250
251 String value = attr.getValue( "Build-Jdk-Spec" );
252 if ( value != null )
253 {
254 return value + " (from MANIFEST.MF Build-Jdk-Spec)";
255 }
256
257 value = attr.getValue( "Build-Jdk" );
258 if ( value != null )
259 {
260 return String.valueOf( value ) + " (from MANIFEST.MF Build-Jdk)";
261 }
262
263 return null;
264 }
265
266 private String extractOsName( Artifact a, JarFile jar )
267 {
268 String entryName = "META-INF/maven/" + a.getGroupId() + '/' + a.getArtifactId() + "/pom.properties";
269 ZipEntry zipEntry = jar.getEntry( entryName );
270 if ( zipEntry == null )
271 {
272 return null;
273 }
274 try ( InputStream in = jar.getInputStream( zipEntry ) )
275 {
276 String content = IOUtil.toString( in, StandardCharsets.UTF_8.name() );
277 log.debug( "Manifest content: " + content );
278 if ( content.contains( "\r\n" ) )
279 {
280 return "Windows (from pom.properties newline)";
281 }
282 else if ( content.contains( "\n" ) )
283 {
284 return "Unix (from pom.properties newline)";
285 }
286 }
287 catch ( IOException e )
288 {
289 log.warn( "Unable to read " + entryName + " from " + jar, e );
290 }
291 return null;
292 }
293
294 private File downloadReferenceBuildinfo( RemoteRepository repo, MavenProject project )
295 throws MojoExecutionException
296 {
297 Artifact buildinfo =
298 artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
299 project.getVersion(), "buildinfo", "" );
300 try
301 {
302 File file = downloadReference( repo, buildinfo );
303
304 log.info( "Reference buildinfo file found, copied to " + file );
305
306 return file;
307 }
308 catch ( ArtifactNotFoundException e )
309 {
310 log.info( "Reference buildinfo file not found: "
311 + "it will be generated from downloaded reference artifacts" );
312 }
313
314 return null;
315 }
316
317 private File downloadReference( RemoteRepository repo, Artifact artifact )
318 throws MojoExecutionException, ArtifactNotFoundException
319 {
320 try
321 {
322 ArtifactRequest request = new ArtifactRequest();
323 request.setArtifact( new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(),
324 artifact.getClassifier(),
325 ( artifact.getArtifactHandler() != null )
326 ? artifact.getArtifactHandler().getExtension()
327 : artifact.getType(),
328 artifact.getVersion() ) );
329 request.setRepositories( Collections.singletonList( repo ) );
330
331 ArtifactResult result =
332 repoSystem.resolveArtifact( new NoWorkspaceRepositorySystemSession( repoSession ), request );
333 File resultFile = result.getArtifact().getFile();
334 File destFile = getReference( resultFile );
335
336 FileUtils.copyFile( resultFile, destFile );
337
338 return destFile;
339 }
340 catch ( org.eclipse.aether.resolution.ArtifactResolutionException are )
341 {
342 if ( are.getResult().isMissing() )
343 {
344 throw new ArtifactNotFoundException( "Artifact not found " + artifact, artifact );
345 }
346 throw new MojoExecutionException( "Error resolving reference artifact " + artifact, are );
347 }
348 catch ( IOException ioe )
349 {
350 throw new MojoExecutionException( "Error copying reference artifact " + artifact, ioe );
351 }
352 }
353
354 private File getReference( File file )
355 {
356 return new File( referenceDir, file.getName() );
357 }
358
359 private static class NoWorkspaceRepositorySystemSession
360 extends AbstractForwardingRepositorySystemSession
361 {
362 private final RepositorySystemSession rss;
363
364 NoWorkspaceRepositorySystemSession( RepositorySystemSession rss )
365 {
366 this.rss = rss;
367 }
368
369 @Override
370 protected RepositorySystemSession getSession()
371 {
372 return rss;
373 }
374
375 @Override
376 public WorkspaceReader getWorkspaceReader()
377 {
378 return null;
379 }
380 }
381
382 private static class ReproducibleEnv
383 {
384 @SuppressWarnings( "checkstyle:visibilitymodifier" )
385 public final String javaVersion;
386
387 @SuppressWarnings( "checkstyle:visibilitymodifier" )
388 public final String osName;
389
390 ReproducibleEnv( String javaVersion, String osName )
391 {
392 this.javaVersion = javaVersion;
393 this.osName = osName;
394 }
395 }
396 }