![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.maven.plugins.artifact.buildinfo.CompareMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven-artifact-plugin Show documentation
Show all versions of maven-artifact-plugin Show documentation
Plugin to manage artifacts tasks
The newest version!
/*
* 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 javax.inject.Inject;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.ToolchainManager;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
import static org.apache.maven.plugins.artifact.buildinfo.BuildInfoWriter.getArtifactFilename;
/**
* Compare current build output (from {@code package}) against reference either previously {@code install}-ed or downloaded from a remote
* repository: comparison results go to {@code .buildcompare} file.
*
* @since 3.2.0
*/
@Mojo(name = "compare", threadSafe = false)
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 also compare on each module.
* @since 3.2.0
*/
@Parameter(property = "compare.aggregate.only", defaultValue = "false")
private boolean aggregateOnly;
/**
* 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;
/**
* Fail the build if differences are found against reference build.
* @since 3.5.0
*/
@Parameter(property = "compare.fail", defaultValue = "true")
private boolean fail;
/**
* The entry point to Maven Artifact Resolver, i.e. the component doing all the work.
*/
private final RepositorySystem repoSystem;
@Inject
public CompareMojo(
ToolchainManager toolchainManager,
RuntimeInformation rtInformation,
MavenProject project,
MavenSession session,
RepositorySystem repoSystem) {
super(toolchainManager, rtInformation, project, session);
this.repoSystem = repoSystem;
}
@Override
public void execute(Map artifacts) throws MojoExecutionException {
getLog().info("Checking against reference build from " + referenceRepo + "...");
checkAgainstReference(artifacts, session.getProjects().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.
*
* @param artifacts a Map of artifacts added to the build info with their associated property key prefix
* (outputs.[#module.].#artifact
)
* @throws MojoExecutionException if anything goes wrong
*/
private void checkAgainstReference(Map artifacts, boolean mono) throws MojoExecutionException {
MavenProject root = mono ? project : session.getTopLevelProject();
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, repoSystem, repoSession, rtInformation);
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<>();
List ignored = new ArrayList<>();
File referenceDir = referenceBuildinfo.getParentFile();
for (Map.Entry entry : artifacts.entrySet()) {
Artifact artifact = entry.getKey();
String prefix = entry.getValue();
if (prefix == null) {
// ignored file
ignored.add(getArtifactFilename(artifact));
continue;
}
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 - ignored.size();
int missing = reference.size() / 3 /* 3 property keys par file: filename, length and checksums.sha512 */;
if (ko + missing > 0) {
getLog().error("Reproducible Build output summary: "
+ MessageUtils.buffer().success(ok + " files ok")
+ ", " + MessageUtils.buffer().failure(ko + " different")
+ ((missing == 0) ? "" : (", " + MessageUtils.buffer().failure(missing + " missing")))
+ ((ignored.isEmpty()) ? "" : (", " + MessageUtils.buffer().warning(ignored.size() + " ignored"))));
getLog().error("see "
+ MessageUtils.buffer()
.project("diff " + relative(referenceBuildinfo) + " " + relative(buildinfoFile))
.build());
getLog().error("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")
+ ((ignored.isEmpty()) ? "" : (", " + MessageUtils.buffer().warning(ignored.size() + " ignored"))));
}
// save .compare file
File buildcompare = new File(
buildinfoFile.getParentFile(), buildinfoFile.getName().replaceFirst(".buildinfo$", ".buildcompare"));
try (PrintWriter p = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(buildcompare.toPath()), StandardCharsets.UTF_8)))) {
p.println("version=" + project.getVersion());
p.println("ok=" + ok);
p.println("ko=" + ko);
p.println("ignored=" + ignored.size());
p.println("okFiles=\"" + String.join(" ", okFilenames) + '"');
p.println("koFiles=\"" + String.join(" ", koFilenames) + '"');
p.println("ignoredFiles=\"" + String.join(" ", ignored) + '"');
Properties ref = new Properties();
if (referenceBuildinfo != null) {
try (InputStream in = Files.newInputStream(referenceBuildinfo.toPath())) {
ref.load(in);
} catch (IOException e) {
// nothing
}
}
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);
if (fail && (ko + missing > 0)) {
throw new MojoExecutionException("Build artifacts are different from reference");
}
}
// { 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, artifact.getGroupId(), actualFilename);
String referenceLength = (String) reference.remove(referencePrefix + ".length");
String referenceSha512 = (String) reference.remove(referencePrefix + ".checksums.sha512");
reference.remove(referencePrefix + ".groupId");
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().error(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(new File(referenceDir, a.getGroupId()), getRepositoryFilename(a));
if (actual == null) {
return "missing file for " + ArtifactIdUtils.toId(a) + " reference = " + relative(reference)
+ " actual = null";
}
return "diffoscope " + relative(reference) + " " + relative(actual);
}
private String getRepositoryFilename(Artifact a) {
String path = session.getRepositorySession().getLocalRepositoryManager().getPathForLocalArtifact(a);
return path.substring(path.lastIndexOf('/'));
}
private String relative(File file) {
File basedir = session.getTopLevelProject().getBasedir();
int length = basedir.getPath().length();
String path = file.getPath();
return path.substring(length + 1);
}
private static String findPrefix(Properties reference, String actualGroupId, String actualFilename) {
for (String name : reference.stringPropertyNames()) {
if (name.endsWith(".filename") && actualFilename.equals(reference.getProperty(name))) {
String prefix = name.substring(0, name.length() - ".filename".length());
if (actualGroupId.equals(reference.getProperty(prefix + ".groupId"))) {
reference.remove(name);
return prefix;
}
}
}
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