com.xored.maven.integrity.VerifyMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of integrity-maven-plugin Show documentation
Show all versions of integrity-maven-plugin Show documentation
Maven plugin, which allows to verify the project's integrity by looking for potential modules,
which might have been missed from the build.
The newest version!
package com.xored.maven.integrity;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.MatchPatterns;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Scans project directory structure in attempt to find missed (not included to the build) modules. If it finds any
* missed modules, it marks the build failed.
*/
@Mojo(name = "verify-modules", defaultPhase = LifecyclePhase.VALIDATE, aggregator = true)
public class VerifyMojo extends AbstractMojo {
private static final String DEFAULT_INCLUDE = "pom.xml";
@Parameter(defaultValue = "${reactorProjects}", readonly = true)
private List reactorProjects;
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
/**
* Specifies, which directories should be considered modules.
* Ant patterns, which are applied to paths, relative to the project root. It is implied,
* that each pattern starts with **/ and ends with /**. You should not specify this explicitly.
* If not specified, each directory, which contains pom.xml file, is a module.
* Exact module directory is determined by the first path element, which matches the pattern
* (not counting the implied **/).
*/
@Parameter(property = "integrity.modules.includes")
private String[] includes;
/**
* Specifies, which directories should not be considered modules, even if they meet includes criteria.
* Ant patterns, which are applied to paths, relative to the project root. It is implied,
* that each pattern starts and ends with **/. You should not specify this explicitly.
* Exact module directory is determined by the first path element, which matches the pattern
* (not counting the implied **/).
*/
@Parameter(property = "integrity.modules.excludes")
private String[] excludes;
/**
* Allows to do case-sensitive search. Use with caution, since this may make builds platform-dependent.
*/
@Parameter(alias = "case-sensitive", property = "integrity.modules.case-sensitive", defaultValue = "false")
private boolean isCaseSensitive;
private String[] nativeIncludes;
private String[] nativeExcludes;
private String[] extendedIncludes;
private String[] extendedExcludes;
private MatchPatterns[] includesPatterns;
private MatchPatterns[] extendedIncludesPatterns;
private Set relativeKnownPaths;
private String[] targetDirExcludes;
private Set missedPaths = new HashSet();
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// TODO: is there a better way to execute this mojo only once?
if (reactorProjects.isEmpty() || !project.getBasedir().equals(reactorProjects.get(0).getBasedir())) {
return;
}
if (null == includes || 0 == includes.length) {
includes = new String[]{DEFAULT_INCLUDE};
}
nativeIncludes = PathUtils.toNative(includes);
nativeExcludes = PathUtils.toNative(excludes);
extendedIncludes = extendPatterns(nativeIncludes, false);
extendedExcludes = extendPatterns(nativeExcludes, false);
includesPatterns = createMatchPatterns(nativeIncludes);
extendedIncludesPatterns = createMatchPatterns(extendedIncludes);
Set knownPaths = getKnownPaths();
File root = getRoot(knownPaths);
relativeKnownPaths = PathUtils.relativizePaths(root, knownPaths);
Set knownTargetPaths = getKnownTargetPaths();
targetDirExcludes = extendPatterns(PathUtils.relativizePaths(root, knownTargetPaths).toArray(new String[0]),
true);
getLog().info("Detected project root: " + root);
getLog().info("Searching for missed modules...");
if (getLog().isDebugEnabled()) {
getLog().debug("Found known paths: \n" + finePrint(relativeKnownPaths));
getLog().debug(" Includes: " + Arrays.toString(nativeIncludes));
getLog().debug(" Excludes: " + Arrays.toString(nativeExcludes));
getLog().debug(" Targets: " + Arrays.toString(targetDirExcludes));
}
visitDir(root);
if (missedPaths.isEmpty()) {
getLog().info(" no missed modules found");
} else {
String[] missed = PathUtils.toUnix(missedPaths.toArray(new String[0]));
Arrays.sort(missed);
throw new MojoFailureException(null, getShortFailureMsg(), getLongFailureMsg(missed));
}
}
private static String getShortFailureMsg() {
// for some reason Maven repeats this message 2 times. It looks ugly, so I set it empty
return "";
}
private static String getLongFailureMsg(String[] missed) {
return "Directories below look like modules, but they are not in the build. " +
"If they are indeed modules, add them to the build. " +
"If they're not modules, modify include/exclude criteria so, that they are not recognized as modules:\n" +
finePrint(Arrays.asList(missed));
}
private static MatchPatterns[] createMatchPatterns(String[] patterns) {
MatchPatterns[] ret = new MatchPatterns[patterns.length];
for (int i = 0; i < patterns.length; ++i) {
ret[i] = MatchPatterns.from(patterns[i]);
}
return ret;
}
private static String[] extendPatterns(String[] patterns, boolean tailOnly) throws MojoFailureException {
if (null == patterns) {
return null;
}
String[] ret = new String[patterns.length];
for (int i = 0; i < patterns.length; ++i) {
ret[i] = extendPattern(patterns[i], tailOnly);
}
return ret;
}
private static String extendPattern(String pattern, boolean tailOnly) throws MojoFailureException {
final String separator = File.separator;
final String prefix = "**" + separator;
final String suffix = separator + "**";
String ret = pattern;
if (ret.startsWith(separator)) {
throw new MojoFailureException("Includes/excludes pattern must not start with " + separator + ": " +
pattern);
}
if (ret.endsWith(separator)) {
throw new MojoFailureException("Includes/excludes pattern must not end with " + separator + ": " +
pattern);
}
if (ret.startsWith(prefix)) {
throw new MojoFailureException("Includes/excludes pattern must not start with " + prefix + ": " + pattern);
}
if (tailOnly) {
return ret + suffix;
} else {
return prefix + ret + suffix;
}
}
private static String finePrint(Iterable strings) {
StringBuilder ret = new StringBuilder();
for (String s : strings) {
ret.append("* ");
ret.append(s);
ret.append("\n");
}
return ret.toString();
}
private static File getRoot(Set paths) {
return new File(PathUtils.getCommonPath(paths.toArray(new String[0])));
}
private Set getKnownPaths() {
Set ret = new HashSet();
for (MavenProject p : reactorProjects) {
ret.add(PathUtils.toNative(p.getBasedir().getAbsolutePath()));
}
return ret;
}
private Set getKnownTargetPaths() {
Set ret = new HashSet();
for (MavenProject p : reactorProjects) {
ret.add(PathUtils.toNative(p.getBuild().getDirectory()));
}
return ret;
}
private void visitDir(File dir) {
for (File k : getFilesToProcess(dir)) {
process(dir, k);
}
}
private List getFilesToProcess(File dir) {
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(dir);
scanner.setIncludes(extendedIncludes);
List allExcludes = new ArrayList();
allExcludes.addAll(Arrays.asList(extendedExcludes));
allExcludes.addAll(Arrays.asList(targetDirExcludes));
scanner.setExcludes(allExcludes.toArray(new String[0]));
scanner.setCaseSensitive(isCaseSensitive);
scanner.scan();
List ret = new ArrayList();
for (String n : scanner.getIncludedFiles()) {
ret.add(new File(dir, n));
}
return ret;
}
private void process(File dir, File f) {
String p = PathUtils.toNative(f.getAbsolutePath());
String rel = PathUtils.relativizePath(dir, p);
String module = rel;
for (int i = 0; i < extendedIncludesPatterns.length; ++i) {
MatchPatterns mp = extendedIncludesPatterns[i];
if (mp.matches(rel, isCaseSensitive)) {
module = getUnmatchedPrefix(rel, includesPatterns[i]);
break;
}
}
if (!relativeKnownPaths.contains(module)) {
missedPaths.add(module);
}
}
private String getUnmatchedPrefix(String path, MatchPatterns p) {
final String separator = File.separator;
int i = -1;
do {
i = path.indexOf(separator, i + 1);
if (p.matches(path.substring(i + 1), isCaseSensitive)) {
return -1 == i ? "" : path.substring(0, i);
}
} while (i > -1);
return path;
}
}