package aQute.bnd.maven.baseline.plugin; import static org.apache.maven.plugins.annotations.LifecyclePhase.VERIFY; import java.io.IOException; import java.util.Formatter; import java.util.List; import java.util.Locale; import org.apache.maven.RepositoryUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.eclipse.aether.RepositoryException; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.resolution.VersionRangeRequest; import org.eclipse.aether.resolution.VersionRangeResolutionException; import org.eclipse.aether.resolution.VersionRangeResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import aQute.bnd.differ.Baseline; import aQute.bnd.differ.Baseline.BundleInfo; import aQute.bnd.differ.Baseline.Info; import aQute.bnd.differ.DiffPluginImpl; import aQute.bnd.osgi.Jar; import aQute.libg.reporter.ReporterAdapter; import aQute.service.reporter.Reporter; /** * Exports project dependencies to OSGi R5 index format. */ @Mojo(name = "baseline", defaultPhase = VERIFY) public class BaselineMojo extends AbstractMojo { private static final Logger logger = LoggerFactory.getLogger(BaselineMojo.class); @Parameter(defaultValue = "${project}", readonly = true, required = true) private MavenProject project; @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) private RepositorySystemSession session; @Parameter(property = "bnd.baseline.fail.on.missing", defaultValue = "true") private boolean failOnMissing; @Parameter(property = "bnd.baseline.include.distribution.management", defaultValue = "true") private boolean includeDistributionManagement; @Parameter(property = "bnd.baseline.full.report", defaultValue = "false") private boolean fullReport; @Parameter(property = "bnd.baseline.continue.on.error", defaultValue = "false") private boolean continueOnError; @Parameter(readonly = true) private Base base; @Parameter(property = "bnd.baseline.skip", defaultValue = "false") private boolean skip; @Component private RepositorySystem system; public void execute() throws MojoExecutionException, MojoFailureException { if ( skip ) { logger.debug("skip project as configured"); return; } Artifact artifact = RepositoryUtils.toArtifact(project.getArtifact()); List<RemoteRepository> aetherRepos = getRepositories(artifact); setupBase(artifact); try { if (base.getVersion() == null || base.getVersion().isEmpty()) { searchForBaseVersion(artifact, aetherRepos); } if (base.getVersion() != null && !base.getVersion().isEmpty()) { ArtifactResult artifactResult = locateBaseJar(aetherRepos); Reporter reporter; if (fullReport) { reporter = new ReporterAdapter(System.out); ((ReporterAdapter) reporter).setTrace(true); } else { reporter = new ReporterAdapter(); } Baseline baseline = new Baseline(reporter, new DiffPluginImpl()); if (checkFailures(artifact, artifactResult, baseline)) { if (continueOnError) { logger.warn( "The baselining check failed when checking {} against {} but the bnd-baseline-maven-plugin is configured not to fail the build.", artifact, artifactResult.getArtifact()); } else { throw new MojoExecutionException("The baselining plugin detected versioning errors"); } } else { logger.info("Baselining check succeeded checking {} against {}", artifact, artifactResult.getArtifact()); } } else { if (failOnMissing) { throw new MojoExecutionException("Unable to locate a previous version of the artifact"); } else { logger.warn("No previous version of {} could be found to baseline against", artifact); } } } catch (RepositoryException re) { throw new MojoExecutionException("Unable to locate a previous version of the artifact", re); } catch (Exception e) { throw new MojoExecutionException("An error occurred while calculating the baseline", e); } } private List<RemoteRepository> getRepositories(Artifact artifact) { List<RemoteRepository> aetherRepos = RepositoryUtils.toRepos(project.getRemoteArtifactRepositories()); if (includeDistributionManagement) { RemoteRepository releaseDistroRepo; if (artifact.isSnapshot()) { MavenProject tmpClone = project.clone(); org.apache.maven.artifact.Artifact tmpArtifact = project.getArtifact(); tmpClone.setArtifact(tmpArtifact); releaseDistroRepo = RepositoryUtils.toRepo(tmpClone.getDistributionManagementArtifactRepository()); } else { releaseDistroRepo = RepositoryUtils.toRepo(project.getDistributionManagementArtifactRepository()); } // Issue #2040: // Don't fail on projects without distributionManagement if (releaseDistroRepo != null) { aetherRepos.add(0, releaseDistroRepo); } } return aetherRepos; } private void setupBase(Artifact artifact) { if (base == null) { base = new Base(); } if (base.getGroupId() == null || base.getGroupId().isEmpty()) { base.setGroupId(project.getGroupId()); } if (base.getArtifactId() == null || base.getArtifactId().isEmpty()) { base.setArtifactId(project.getArtifactId()); } if (base.getClassifier() == null || base.getClassifier().isEmpty()) { base.setClassifier(artifact.getClassifier()); } if (base.getExtension() == null || base.getExtension().isEmpty()) { base.setExtension(artifact.getExtension()); } logger.debug("Baselining against {}, fail on missing: {}", base, failOnMissing); } private void searchForBaseVersion(Artifact artifact, List<RemoteRepository> aetherRepos) throws VersionRangeResolutionException { logger.info("Automatically determining the baseline version for {} using repositories {}", artifact, aetherRepos); Artifact toFind = new DefaultArtifact(base.getGroupId(), base.getArtifactId(), base.getClassifier(), base.getExtension(), base.getVersion()); Artifact toCheck = toFind.setVersion("(," + artifact.getVersion() + ")"); VersionRangeRequest request = new VersionRangeRequest(toCheck, aetherRepos, "baseline"); VersionRangeResult versions = system.resolveVersionRange(session, request); logger.debug("Found versions {}", versions.getVersions()); base.setVersion(versions.getHighestVersion() != null ? versions.getHighestVersion().toString() : null); logger.info("The baseline version was found to be {}", base.getVersion()); } private ArtifactResult locateBaseJar(List<RemoteRepository> aetherRepos) throws ArtifactResolutionException { Artifact toFind = new DefaultArtifact(base.getGroupId(), base.getArtifactId(), base.getClassifier(), base.getExtension(), base.getVersion()); return system.resolveArtifact(session, new ArtifactRequest(toFind, aetherRepos, "baseline")); } private boolean checkFailures(Artifact artifact, ArtifactResult artifactResult, Baseline baseline) throws Exception, IOException { StringBuffer sb = new StringBuffer(); try (Formatter f = new Formatter(sb, Locale.US); Jar newer = new Jar(artifact.getFile()); Jar older = new Jar(artifactResult.getArtifact().getFile())) { boolean failed = false; for (Info info : baseline.baseline(newer, older, null)) { if (info.mismatch) { failed = true; if (logger.isErrorEnabled()) { sb.setLength(0); f.format( "Baseline mismatch for package %s, %s change. Current is %s, repo is %s, suggest %s or %s", info.packageName, info.packageDiff.getDelta(), info.newerVersion, info.olderVersion, info.suggestedVersion, info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders); if (fullReport) { f.format("%n%#S", info.packageDiff); } logger.error(f.toString()); } } } BundleInfo binfo = baseline.getBundleInfo(); if (binfo.mismatch) { failed = true; if (logger.isErrorEnabled()) { sb.setLength(0); f.format("The bundle version change (%s to %s) is too low, the new version must be at least %s", binfo.olderVersion, binfo.newerVersion, binfo.suggestedVersion); if (fullReport) { f.format("%n%#S", baseline.getDiff()); } logger.error(f.toString()); } } return failed; } } }