All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.artifact.buildinfo.CompareMojo Maven / Gradle / Ivy

package org.apache.maven.plugins.artifact.buildinfo;

/*
 * 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.
 */

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.plugin.MojoExecutionException;

import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.shared.utils.PropertyUtils;
import org.apache.maven.shared.utils.StringUtils;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Compare current build output with reference either previously installed or downloaded from a remote repository:
 * results go to {@code .buildcompare} file.
 *
 * @since 3.2.0
 */
@Mojo( name = "compare" )
public class CompareMojo
    extends AbstractBuildinfoMojo
{
    /**
     * Repository for reference build, containing either reference buildinfo file or reference artifacts.
* Format: id or url or id::url *
*
id
*
The repository id
*
url
*
The url of the repository
*
* @see repository definition */ @Parameter( property = "reference.repo", defaultValue = "central" ) private String referenceRepo; /** * Compare aggregate only (ie wait for the last module) or do buildcompare on each module. * @since 3.2.0 */ @Parameter( property = "compare.aggregate.only", defaultValue = "false" ) private boolean aggregateOnly; @Component private ArtifactFactory artifactFactory; /** * The entry point to Maven Artifact Resolver, i.e. the component doing all the work. */ @Component private RepositorySystem repoSystem; /** * The current repository/network configuration of Maven. */ @Parameter( defaultValue = "${repositorySystemSession}", readonly = true ) private RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution. */ @Parameter( defaultValue = "${project.remoteProjectRepositories}", readonly = true ) private List remoteRepos; @Component private ArtifactRepositoryLayout artifactRepositoryLayout; @Override public void execute( Map artifacts ) throws MojoExecutionException { getLog().info( "Checking against reference build from " + referenceRepo + "..." ); checkAgainstReference( artifacts, reactorProjects.size() == 1 ); } @Override protected void skip( MavenProject last ) throws MojoExecutionException { if ( aggregateOnly ) { return; } // try to download reference artifacts for current project and check if there are issues to give early feedback checkAgainstReference( generateBuildinfo( true ), true ); } /** * Check current build result with reference. * * @artifacts a Map of artifacts added to the build info with their associated property key prefix * (outputs.[#module.].#artifact) * @throws MojoExecutionException */ private void checkAgainstReference( Map artifacts, boolean mono ) throws MojoExecutionException { MavenProject root = mono ? project : getExecutionRoot(); File referenceDir = new File( root.getBuild().getDirectory(), "reference" ); referenceDir.mkdirs(); // download or create reference buildinfo File referenceBuildinfo = downloadOrCreateReferenceBuildinfo( mono, artifacts, referenceDir ); // compare outputs from reference buildinfo vs actual compareWithReference( artifacts, referenceBuildinfo ); } private File downloadOrCreateReferenceBuildinfo( boolean mono, Map artifacts, File referenceDir ) throws MojoExecutionException { RemoteRepository repo = createReferenceRepo(); ReferenceBuildinfoUtil rmb = new ReferenceBuildinfoUtil( getLog(), referenceDir, artifacts, artifactFactory, repoSystem, repoSession, artifactHandlerManager ); return rmb.downloadOrCreateReferenceBuildinfo( repo, project, buildinfoFile, mono ); } private void compareWithReference( Map artifacts, File referenceBuildinfo ) throws MojoExecutionException { Properties actual = BuildInfoWriter.loadOutputProperties( buildinfoFile ); Properties reference = BuildInfoWriter.loadOutputProperties( referenceBuildinfo ); int ok = 0; List okFilenames = new ArrayList<>(); List koFilenames = new ArrayList<>(); List diffoscopes = new ArrayList<>(); File referenceDir = referenceBuildinfo.getParentFile(); for ( Map.Entry entry : artifacts.entrySet() ) { Artifact artifact = entry.getKey(); String prefix = entry.getValue(); String[] checkResult = checkArtifact( artifact, prefix, reference, actual, referenceDir ); String filename = checkResult[0]; String diffoscope = checkResult[1]; if ( diffoscope == null ) { ok++; okFilenames.add( filename ); } else { koFilenames.add( filename ); diffoscopes.add( diffoscope ); } } int ko = artifacts.size() - ok; int missing = reference.size() / 3 /* 3 property keys par file: filename, length and checksums.sha512 */; if ( ko + missing > 0 ) { getLog().warn( "Reproducible Build output summary: " + MessageUtils.buffer().success( ok + " files ok" ) + ", " + MessageUtils.buffer().failure( ko + " different" ) + ( ( missing == 0 ) ? "" : ( ", " + MessageUtils.buffer().warning( missing + " missing" ) ) ) ); getLog().warn( "see " + MessageUtils.buffer().project( "diff " + relative( referenceBuildinfo ) + " " + relative( buildinfoFile ) ).toString() ); getLog().warn( "see also https://maven.apache.org/guides/mini/guide-reproducible-builds.html" ); } else { getLog().info( "Reproducible Build output summary: " + MessageUtils.buffer().success( ok + " files ok" ) ); } // save .compare file File buildcompare = new File( buildinfoFile.getParentFile(), buildinfoFile.getName().replaceFirst( ".buildinfo$", ".buildcompare" ) ); try ( PrintWriter p = new PrintWriter( new BufferedWriter( new OutputStreamWriter( new FileOutputStream( buildcompare ), StandardCharsets.UTF_8 ) ) ) ) { p.println( "version=" + project.getVersion() ); p.println( "ok=" + ok ); p.println( "ko=" + ko ); p.println( "okFiles=\"" + StringUtils.join( okFilenames.iterator(), " " ) + '"' ); p.println( "koFiles=\"" + StringUtils.join( koFilenames.iterator(), " " ) + '"' ); Properties ref = PropertyUtils.loadOptionalProperties( referenceBuildinfo ); String v = ref.getProperty( "java.version" ); if ( v != null ) { p.println( "reference_java_version=\"" + v + '"' ); } v = ref.getProperty( "os.name" ); if ( v != null ) { p.println( "reference_os_name=\"" + v + '"' ); } for ( String diffoscope : diffoscopes ) { p.print( "# " ); p.println( diffoscope ); } getLog().info( "Reproducible Build output comparison saved to " + buildcompare ); } catch ( IOException e ) { throw new MojoExecutionException( "Error creating file " + buildcompare, e ); } copyAggregateToRoot( buildcompare ); } // { filename, diffoscope } private String[] checkArtifact( Artifact artifact, String prefix, Properties reference, Properties actual, File referenceDir ) { String actualFilename = (String) actual.remove( prefix + ".filename" ); String actualLength = (String) actual.remove( prefix + ".length" ); String actualSha512 = (String) actual.remove( prefix + ".checksums.sha512" ); String referencePrefix = findPrefix( reference, actualFilename ); String referenceLength = (String) reference.remove( referencePrefix + ".length" ); String referenceSha512 = (String) reference.remove( referencePrefix + ".checksums.sha512" ); String issue = null; if ( !actualLength.equals( referenceLength ) ) { issue = "size"; } else if ( !actualSha512.equals( referenceSha512 ) ) { issue = "sha512"; } if ( issue != null ) { String diffoscope = diffoscope( artifact, referenceDir ); getLog().warn( issue + " mismatch " + MessageUtils.buffer().strong( actualFilename ) + ": investigate with " + MessageUtils.buffer().project( diffoscope ) ); return new String[] { actualFilename, diffoscope }; } return new String[] { actualFilename, null }; } private String diffoscope( Artifact a, File referenceDir ) { File actual = a.getFile(); // notice: actual file name may have been defined in pom // reference file name is taken from repository format File reference = new File( referenceDir, getRepositoryFilename( a ) ); if ( ( actual == null ) || ( reference == null ) ) { return "missing file for " + a.getId() + " reference = " + ( reference == null ? "null" : relative( reference ) ) + " actual = " + ( actual == null ? "null" : relative( actual ) ); } return "diffoscope " + relative( reference ) + " " + relative( actual ); } private String getRepositoryFilename( Artifact a ) { String path = artifactRepositoryLayout.pathOf( a ); return path.substring( path.lastIndexOf( '/' ) ); } private String relative( File file ) { File basedir = getExecutionRoot().getBasedir(); int length = basedir.getPath().length(); String path = file.getPath(); return path.substring( length + 1 ); } private static String findPrefix( Properties reference, String actualFilename ) { for ( String name : reference.stringPropertyNames() ) { if ( name.endsWith( ".filename" ) && actualFilename.equals( reference.getProperty( name ) ) ) { reference.remove( name ); return name.substring( 0, name.length() - ".filename".length() ); } } return null; } private RemoteRepository createReferenceRepo() throws MojoExecutionException { if ( referenceRepo.contains( "::" ) ) { // id::url int index = referenceRepo.indexOf( "::" ); String id = referenceRepo.substring( 0, index ); String url = referenceRepo.substring( index + 2 ); return createDeploymentArtifactRepository( id, url ); } else if ( referenceRepo.contains( ":" ) ) { // url, will use default "reference" id return createDeploymentArtifactRepository( "reference", referenceRepo ); } // id for ( RemoteRepository repo : remoteRepos ) { if ( referenceRepo.equals( repo.getId() ) ) { return repo; } } throw new MojoExecutionException( "Could not find repository with id = " + referenceRepo ); } private static RemoteRepository createDeploymentArtifactRepository( String id, String url ) { return new RemoteRepository.Builder( id, "default", url ).build(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy