| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.plugins.artifact.buildinfo; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.factory.ArtifactFactory; |
| import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; |
| import org.apache.maven.artifact.resolver.ArtifactNotFoundException; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.rtinfo.RuntimeInformation; |
| import org.apache.maven.shared.utils.io.FileUtils; |
| import org.apache.maven.shared.utils.io.IOUtil; |
| import org.eclipse.aether.AbstractForwardingRepositorySystemSession; |
| import org.eclipse.aether.RepositorySystem; |
| import org.eclipse.aether.RepositorySystemSession; |
| import org.eclipse.aether.artifact.DefaultArtifact; |
| import org.eclipse.aether.repository.RemoteRepository; |
| import org.eclipse.aether.repository.WorkspaceReader; |
| import org.eclipse.aether.resolution.ArtifactRequest; |
| import org.eclipse.aether.resolution.ArtifactResult; |
| |
| /** |
| * Utility to download reference artifacts and download or generate reference buildinfo. |
| */ |
| class ReferenceBuildinfoUtil { |
| private static final Set<String> JAR_TYPES; |
| |
| static { |
| Set<String> types = new HashSet<>(); |
| types.add("jar"); |
| types.add("test-jar"); |
| types.add("war"); |
| types.add("ear"); |
| types.add("rar"); |
| types.add("maven-plugin"); |
| JAR_TYPES = Collections.unmodifiableSet(types); |
| } |
| |
| private final Log log; |
| |
| /** |
| * Directory of the downloaded reference files. |
| */ |
| private final File referenceDir; |
| |
| private final Map<Artifact, String> artifacts; |
| |
| private final ArtifactFactory artifactFactory; |
| |
| private final RepositorySystem repoSystem; |
| |
| private final RepositorySystemSession repoSession; |
| |
| private final ArtifactHandlerManager artifactHandlerManager; |
| |
| private final RuntimeInformation rtInformation; |
| |
| ReferenceBuildinfoUtil( |
| Log log, |
| File referenceDir, |
| Map<Artifact, String> artifacts, |
| ArtifactFactory artifactFactory, |
| RepositorySystem repoSystem, |
| RepositorySystemSession repoSession, |
| ArtifactHandlerManager artifactHandlerManager, |
| RuntimeInformation rtInformation) { |
| this.log = log; |
| this.referenceDir = referenceDir; |
| this.artifacts = artifacts; |
| this.artifactFactory = artifactFactory; |
| this.repoSystem = repoSystem; |
| this.repoSession = repoSession; |
| this.artifactHandlerManager = artifactHandlerManager; |
| this.rtInformation = rtInformation; |
| } |
| |
| File downloadOrCreateReferenceBuildinfo( |
| RemoteRepository repo, MavenProject project, File buildinfoFile, boolean mono) |
| throws MojoExecutionException { |
| File referenceBuildinfo = downloadReferenceBuildinfo(repo, project); |
| |
| if (referenceBuildinfo != null) { |
| log.warn("dropping downloaded reference buildinfo because it may be generated" |
| + " from different maven-artifact-plugin release..."); |
| // TODO keep a save? |
| referenceBuildinfo = null; |
| } |
| |
| if (referenceBuildinfo == null) { |
| // download reference artifacts and guess Java version and OS |
| String javaVersion = null; |
| String osName = null; |
| String currentJavaVersion = null; |
| String currentOsName = null; |
| Map<Artifact, File> referenceArtifacts = new HashMap<>(); |
| for (Artifact artifact : artifacts.keySet()) { |
| try { |
| // download |
| File file = downloadReference(repo, artifact); |
| referenceArtifacts.put(artifact, file); |
| |
| // guess Java version and OS |
| if ((javaVersion == null) && JAR_TYPES.contains(artifact.getType())) { |
| ReproducibleEnv env = extractEnv(file, artifact); |
| if ((env != null) && (env.javaVersion != null)) { |
| javaVersion = env.javaVersion; |
| osName = env.osName; |
| |
| ReproducibleEnv currentEnv = extractEnv(artifact.getFile(), artifact); |
| currentJavaVersion = currentEnv.javaVersion; |
| currentOsName = currentEnv.osName; |
| } |
| } |
| } catch (ArtifactNotFoundException e) { |
| log.warn("Reference artifact not found " + artifact); |
| } |
| } |
| |
| // generate buildinfo from reference artifacts |
| referenceBuildinfo = getReference(buildinfoFile); |
| try (PrintWriter p = new PrintWriter(new BufferedWriter( |
| new OutputStreamWriter(new FileOutputStream(referenceBuildinfo), StandardCharsets.UTF_8)))) { |
| BuildInfoWriter bi = new BuildInfoWriter(log, p, mono, artifactHandlerManager, rtInformation); |
| |
| if (javaVersion != null || osName != null) { |
| p.println("# effective build environment information"); |
| if (javaVersion != null) { |
| p.println("java.version=" + javaVersion); |
| log.info("Reference build java.version: " + javaVersion); |
| if (!javaVersion.equals(currentJavaVersion)) { |
| log.error("Current build java.version: " + currentJavaVersion); |
| } |
| } |
| if (osName != null) { |
| p.println("os.name=" + osName); |
| log.info("Reference build os.name: " + osName); |
| |
| // check against current line separator |
| if (!osName.equals(currentOsName)) { |
| log.error("Current build os.name: " + currentOsName); |
| } |
| String expectedLs = osName.startsWith("Windows") ? "\r\n" : "\n"; |
| if (!expectedLs.equals(System.lineSeparator())) { |
| log.warn("Current System.lineSeparator() does not match reference build OS"); |
| |
| String ls = System.getProperty("line.separator"); |
| if (!ls.equals(System.lineSeparator())) { |
| log.warn("System.lineSeparator() != System.getProperty( \"line.separator\" ): " |
| + "too late standard system property update..."); |
| } |
| } |
| } |
| } |
| |
| for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) { |
| Artifact artifact = entry.getKey(); |
| String prefix = entry.getValue(); |
| File referenceFile = referenceArtifacts.get(artifact); |
| if (referenceFile != null) { |
| bi.printFile(prefix, referenceFile); |
| } |
| } |
| |
| if (p.checkError()) { |
| throw new MojoExecutionException("Write error to " + referenceBuildinfo); |
| } |
| |
| log.info("Minimal buildinfo generated from downloaded artifacts: " + referenceBuildinfo); |
| } catch (IOException e) { |
| throw new MojoExecutionException("Error creating file " + referenceBuildinfo, e); |
| } |
| } |
| |
| return referenceBuildinfo; |
| } |
| |
| private ReproducibleEnv extractEnv(File file, Artifact artifact) { |
| log.debug("Guessing java.version and os.name from jar " + file); |
| try (JarFile jar = new JarFile(file)) { |
| Manifest manifest = jar.getManifest(); |
| if (manifest != null) { |
| String javaVersion = extractJavaVersion(manifest); |
| String osName = extractOsName(artifact, jar); |
| return new ReproducibleEnv(javaVersion, osName); |
| } else { |
| log.warn("no MANIFEST.MF found in jar " + file); |
| } |
| } catch (IOException e) { |
| log.warn("unable to open jar file " + file, e); |
| } |
| return null; |
| } |
| |
| private String extractJavaVersion(Manifest manifest) { |
| Attributes attr = manifest.getMainAttributes(); |
| |
| String value = attr.getValue("Build-Jdk-Spec"); // MSHARED-797 |
| if (value != null) { |
| return value + " (from MANIFEST.MF Build-Jdk-Spec)"; |
| } |
| |
| value = attr.getValue("Build-Jdk"); |
| if (value != null) { |
| return String.valueOf(value) + " (from MANIFEST.MF Build-Jdk)"; |
| } |
| |
| return null; |
| } |
| |
| private String extractOsName(Artifact a, JarFile jar) { |
| String entryName = "META-INF/maven/" + a.getGroupId() + '/' + a.getArtifactId() + "/pom.properties"; |
| ZipEntry zipEntry = jar.getEntry(entryName); |
| if (zipEntry == null) { |
| return null; |
| } |
| try (InputStream in = jar.getInputStream(zipEntry)) { |
| String content = IOUtil.toString(in, StandardCharsets.UTF_8.name()); |
| log.debug("Manifest content: " + content); |
| if (content.contains("\r\n")) { |
| return "Windows (from pom.properties newline)"; |
| } else if (content.contains("\n")) { |
| return "Unix (from pom.properties newline)"; |
| } |
| } catch (IOException e) { |
| log.warn("Unable to read " + entryName + " from " + jar, e); |
| } |
| return null; |
| } |
| |
| private File downloadReferenceBuildinfo(RemoteRepository repo, MavenProject project) throws MojoExecutionException { |
| Artifact buildinfo = artifactFactory.createArtifactWithClassifier( |
| project.getGroupId(), project.getArtifactId(), project.getVersion(), "buildinfo", ""); |
| try { |
| File file = downloadReference(repo, buildinfo); |
| |
| log.info("Reference buildinfo file found, copied to " + file); |
| |
| return file; |
| } catch (ArtifactNotFoundException e) { |
| log.info("Reference buildinfo file not found: " |
| + "it will be generated from downloaded reference artifacts"); |
| } |
| |
| return null; |
| } |
| |
| private File downloadReference(RemoteRepository repo, Artifact artifact) |
| throws MojoExecutionException, ArtifactNotFoundException { |
| try { |
| ArtifactRequest request = new ArtifactRequest(); |
| request.setArtifact(new DefaultArtifact( |
| artifact.getGroupId(), |
| artifact.getArtifactId(), |
| artifact.getClassifier(), |
| (artifact.getArtifactHandler() != null) |
| ? artifact.getArtifactHandler().getExtension() |
| : artifact.getType(), |
| artifact.getVersion())); |
| request.setRepositories(Collections.singletonList(repo)); |
| |
| ArtifactResult result = |
| repoSystem.resolveArtifact(new NoWorkspaceRepositorySystemSession(repoSession), request); |
| File resultFile = result.getArtifact().getFile(); |
| File destFile = getReference(resultFile); |
| |
| FileUtils.copyFile(resultFile, destFile); |
| |
| return destFile; |
| } catch (org.eclipse.aether.resolution.ArtifactResolutionException are) { |
| if (are.getResult().isMissing()) { |
| throw new ArtifactNotFoundException("Artifact not found " + artifact, artifact); |
| } |
| throw new MojoExecutionException("Error resolving reference artifact " + artifact, are); |
| } catch (IOException ioe) { |
| throw new MojoExecutionException("Error copying reference artifact " + artifact, ioe); |
| } |
| } |
| |
| private File getReference(File file) { |
| return new File(referenceDir, file.getName()); |
| } |
| |
| private static class NoWorkspaceRepositorySystemSession extends AbstractForwardingRepositorySystemSession { |
| private final RepositorySystemSession rss; |
| |
| NoWorkspaceRepositorySystemSession(RepositorySystemSession rss) { |
| this.rss = rss; |
| } |
| |
| @Override |
| protected RepositorySystemSession getSession() { |
| return rss; |
| } |
| |
| @Override |
| public WorkspaceReader getWorkspaceReader() { |
| return null; |
| } |
| } |
| |
| private static class ReproducibleEnv { |
| @SuppressWarnings("checkstyle:visibilitymodifier") |
| public final String javaVersion; |
| |
| @SuppressWarnings("checkstyle:visibilitymodifier") |
| public final String osName; |
| |
| ReproducibleEnv(String javaVersion, String osName) { |
| this.javaVersion = javaVersion; |
| this.osName = osName; |
| } |
| } |
| } |