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

org.revapi.maven.AbstractVersionModifyingMojo Maven / Gradle / Ivy

There is a newer version: 0.15.0
Show newest version
/*
 * Copyright $year Lukas Krejci
 *
 * Licensed 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.revapi.maven;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.maven.execution.MavenSession;
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.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.artifact.DefaultArtifact;

import com.ximpleware.AutoPilot;
import com.ximpleware.ModifyException;
import com.ximpleware.NavException;
import com.ximpleware.TranscodeException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XMLModifier;
import com.ximpleware.XPathParseException;

/**
 * @author Lukas Krejci
 * @since 0.4.0
 */
class AbstractVersionModifyingMojo extends AbstractRevapiMojo {

    @Component
    protected MavenSession mavenSession;

    /**
     * Set to true if all the projects in the current build should share a single version (the default is false).
     * This is useful if your distribution consists of several modules that make up one logical unit that is always
     * versioned the same.
     * 

* In this case all the projects in the current build are API-checked and the version is determined by the * "biggest" API change found in all the projects. I.e. if just a single module breaks API then all of the * modules will get the major version incremented. */ @Parameter(property = Props.singleVersionForAllModules.NAME, defaultValue = Props.singleVersionForAllModules.DEFAULT_VALUE) private boolean singleVersionForAllModules; /** * A comma-separated list of extensions (fully-qualified class names thereof) that are not taken into account during * API analysis for versioning purposes. By default, only the semver-ignore transform is not taken into account so * that it does not interfere with the purpose of modifying the version based on the semver rules. *

* You can modify this set if you use another extensions that change the found differences in a way that the * determined new version would not correspond to what it should be. */ @Parameter(property = Props.disallowedExtensionsInVersioning.NAME, defaultValue = Props.disallowedExtensionsInVersioning.DEFAULT_VALUE) protected String disallowedExtensions; private boolean preserveSuffix; private String replacementSuffix; public boolean isPreserveSuffix() { return preserveSuffix; } void setPreserveSuffix(boolean preserveSuffix) { this.preserveSuffix = preserveSuffix; } public String getReplacementSuffix() { return replacementSuffix; } void setReplacementSuffix(String replacementSuffix) { this.replacementSuffix = replacementSuffix; } public boolean isSingleVersionForAllModules() { return singleVersionForAllModules; } @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { return; } AnalysisResults analysisResults; if (!initializeComparisonArtifacts()) { //we've got non-file artifacts, for which there is no reason to run analysis DefaultArtifact oldArtifact = new DefaultArtifact(oldArtifacts[0]); analysisResults = new AnalysisResults(ApiChangeLevel.NO_CHANGE, oldArtifact.getVersion()); } else { analysisResults = analyzeProject(project); } if (analysisResults == null) { return; } ApiChangeLevel changeLevel = analysisResults.apiChangeLevel; if (singleVersionForAllModules) { File changesFile = getChangesFile(); try (PrintWriter out = new PrintWriter(new FileOutputStream(changesFile, true))) { out.println(project.getArtifact().toString() + "=" + changeLevel + "," + analysisResults.baseVersion); } catch (IOException e) { throw new MojoExecutionException("Failure while updating the changes tracking file.", e); } } else { Version v = nextVersion(analysisResults.baseVersion, changeLevel); updateProjectVersion(project, v); } if (singleVersionForAllModules && project.equals(mavenSession.getProjects().get(mavenSession.getProjects().size() - 1))) { try (BufferedReader rdr = new BufferedReader(new FileReader(getChangesFile()))) { Map projectChanges = new HashMap<>(); String line; while ((line = rdr.readLine()) != null) { int equalsIdx = line.indexOf('='); String projectGav = line.substring(0, equalsIdx); String changeAndBaseVersion = line.substring(equalsIdx + 1); int commaIdx = changeAndBaseVersion.indexOf(','); String change = changeAndBaseVersion.substring(0, commaIdx); String baseVersion = changeAndBaseVersion.substring(commaIdx + 1); changeLevel = ApiChangeLevel.valueOf(change); projectChanges.put(projectGav, new AnalysisResults(changeLevel, baseVersion)); } //establish the tree hierarchy of the projects Set roots = new HashSet<>(); Map> children = new HashMap<>(); Deque unprocessed = new ArrayDeque<>(mavenSession.getProjects()); while (!unprocessed.isEmpty()) { MavenProject pr = unprocessed.pop(); if (!projectChanges.containsKey(pr.getArtifact().toString())) { continue; } MavenProject pa = pr.getParent(); if (roots.contains(pa)) { roots.remove(pr); AnalysisResults paR = projectChanges.get(pa.getArtifact().toString()); AnalysisResults prR = projectChanges.get(pr.getArtifact().toString()); if (prR.apiChangeLevel.ordinal() > paR.apiChangeLevel.ordinal()) { paR.apiChangeLevel = prR.apiChangeLevel; } children.get(pa).add(pr); } else { roots.add(pr); } children.put(pr, new HashSet()); } Iterator it = roots.iterator(); while (it.hasNext()) { Deque tree = new ArrayDeque<>(); MavenProject p = it.next(); tree.add(p); it.remove(); AnalysisResults results = projectChanges.get(p.getArtifact().toString()); Version v = nextVersion(results.baseVersion, results.apiChangeLevel); while (!tree.isEmpty()) { MavenProject current = tree.pop(); updateProjectVersion(current, v); Set c = children.get(current); if (c != null) { for (MavenProject cp : c) { updateProjectParentVersion(cp, v); } tree.addAll(c); } } } } catch (IOException e) { throw new MojoExecutionException("Failure while reading the changes tracking file.", e); } } } private File getChangesFile() throws MojoExecutionException { File targetDir = new File(mavenSession.getExecutionRootDirectory(), "target"); if (!targetDir.exists() && !targetDir.mkdirs()) { throw new MojoExecutionException("Failed to create the target directory: " + targetDir); } File updateVersionsDir = new File(targetDir, "revapi-update-versions"); if (!updateVersionsDir.exists() && !updateVersionsDir.mkdirs()) { throw new MojoExecutionException("Failed to create the revapi-update-versions directory: " + targetDir); } return new File(updateVersionsDir, "per-project.changes"); } void updateProjectVersion(MavenProject project, Version version) throws MojoExecutionException { try { VTDGen gen = new VTDGen(); gen.enableIgnoredWhiteSpace(true); gen.parseFile(project.getFile().getAbsolutePath(), true); VTDNav nav = gen.getNav(); AutoPilot ap = new AutoPilot(nav); ap.selectXPath("namespace-uri(.)"); String ns = ap.evalXPathToString(); nav.toElementNS(VTDNav.FIRST_CHILD, ns, "version"); int pos = nav.getText(); XMLModifier mod = new XMLModifier(nav); mod.updateToken(pos, version.toString()); try (OutputStream out = new FileOutputStream(project.getFile())) { mod.output(out); } } catch (IOException | ModifyException | NavException | XPathParseException | TranscodeException e) { throw new MojoExecutionException("Failed to update the version of project " + project, e); } } void updateProjectParentVersion(MavenProject project, Version version) throws MojoExecutionException { try { VTDGen gen = new VTDGen(); gen.enableIgnoredWhiteSpace(true); gen.parseFile(project.getFile().getAbsolutePath(), true); VTDNav nav = gen.getNav(); AutoPilot ap = new AutoPilot(nav); ap.selectXPath("namespace-uri(.)"); String ns = ap.evalXPathToString(); nav.toElementNS(VTDNav.FIRST_CHILD, ns, "parent"); nav.toElementNS(VTDNav.FIRST_CHILD, ns, "version"); int pos = nav.getText(); XMLModifier mod = new XMLModifier(nav); mod.updateToken(pos, version.toString()); try (OutputStream out = new FileOutputStream(project.getFile())) { mod.output(out); } } catch (IOException | ModifyException | NavException | XPathParseException | TranscodeException e) { throw new MojoExecutionException("Failed to update the parent version of project " + project, e); } } private Version nextVersion(String baseVersion, ApiChangeLevel changeLevel) { Version v = Version.parse(project.getVersion()); boolean isDev = v.getMajor() == 0; switch (changeLevel) { case NO_CHANGE: break; case NON_BREAKING_CHANGES: if (isDev) { v.setPatch(v.getPatch() + 1); } else { v.setMinor(v.getMinor() + 1); v.setPatch(0); } break; case BREAKING_CHANGES: if (isDev) { v.setMinor(v.getMinor() + 1); v.setPatch(0); } else { v.setMajor(v.getMajor() + 1); v.setMinor(0); v.setPatch(0); } break; default: throw new IllegalArgumentException("Unhandled API change level: " + changeLevel); } if (replacementSuffix != null) { String sep = replacementSuffix.substring(0, 1); String suffix = replacementSuffix.substring(1); v.setSuffixSeparator(sep); v.setSuffix(suffix); } else if (!preserveSuffix) { v.setSuffix(null); v.setSuffixSeparator(null); } return v; } private AnalysisResults analyzeProject(MavenProject project) throws MojoExecutionException { ApiBreakageHintingReporter reporter = new ApiBreakageHintingReporter(); try (Analyzer analyzer = prepareAnalyzer(project, reporter)) { analyzer.resolveArtifacts(); if (analyzer.getResolvedOldApi() == null) { return null; } else { analyzer.analyze(); ApiChangeLevel level = reporter.getChangeLevel(); String baseVersion = ((MavenArchive) analyzer.getResolvedOldApi().getArchives().iterator().next()) .getVersion(); return new AnalysisResults(level, baseVersion); } } catch (Exception e) { throw new MojoExecutionException("Analysis failure", e); } } private static final class AnalysisResults { ApiChangeLevel apiChangeLevel; final String baseVersion; AnalysisResults(ApiChangeLevel apiChangeLevel, String baseVersion) { this.apiChangeLevel = apiChangeLevel; this.baseVersion = baseVersion; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy