diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java index f8c4bdd3..c0970ade 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java @@ -27,6 +27,7 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.EnumSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Set; @@ -43,7 +44,7 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.OutputDirectories; import org.gradle.api.tasks.ParallelizableTask; import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; @@ -109,23 +110,49 @@ public class CheckForbiddenApis extends DefaultTask implements PatternFilterable private final CheckForbiddenApisExtension data = new CheckForbiddenApisExtension(); private final PatternSet patternSet = new PatternSet().include("**/*.class"); - private File classesDir; + private FileCollection classesDirs; private FileCollection classpath; private String targetCompatibility; + /** + * Directories with the class files to check. + * Defaults to current sourseSet's output directory (Gradle 2/3) or output directories (Gradle 4.0+). + */ + @OutputDirectories + // no @InputDirectories, we use separate getter for a list of all input files + public FileCollection getClassesDirs() { + return classesDirs; + } + + /** @see #getClassesDirs */ + public void setClassesDirs(FileCollection classesDirs) { + if (classesDirs == null) throw new NullPointerException("classesDirs"); + this.classesDirs = classesDirs; + } + /** * Directory with the class files to check. - * Defaults to current sourseSet's output directory. + * Defaults to current sourseSet's output directory (Gradle 2/3 only). + * @deprecated use {@link #getClassesDirs()} instead. If there are more than one + * {@code classesDir} set by {@link #setClassesDirs(FileCollection)}, this getter may + * throw an exception! */ - @OutputDirectory - // no @InputDirectory, we use separate getter for a list of all input files + @Deprecated public File getClassesDir() { - return classesDir; + final FileCollection col = getClassesDirs(); + return (col == null) ? null : col.getSingleFile(); } - /** @see #getClassesDir */ + /** Sets the directory where to look for classes. Overwrites any value set by {@link #setClassesDirs(FileCollection)}! + * @deprecated use {@link #setClassesDirs(FileCollection)} instead. + * @see #getClassesDir + */ + @Deprecated public void setClassesDir(File classesDir) { - this.classesDir = classesDir; + if (classesDir == null) throw new NullPointerException("classesDir"); + getLogger().warn("The 'classesDir' property on the '{}' task is deprecated. Use 'classesDirs' of type FileCollection instead!", + getName()); + setClassesDirs(getProject().files(classesDir)); } /** Returns the pattern set to match against class files in {@link #getClassesDir()}. */ @@ -149,6 +176,7 @@ public FileCollection getClasspath() { /** @see #getClasspath */ public void setClasspath(FileCollection classpath) { + if (classpath == null) throw new NullPointerException("classpath"); this.classpath = classpath; } @@ -453,16 +481,16 @@ public CheckForbiddenApis include(@SuppressWarnings("rawtypes") Closure arg0) { @InputFiles @SkipWhenEmpty public FileTree getClassFiles() { - return getProject().files(getClassesDir()).getAsFileTree().matching(getPatternSet()); + return getClassesDirs().getAsFileTree().matching(getPatternSet()); } /** Executes the forbidden apis task. */ @TaskAction public void checkForbidden() throws ForbiddenApiException { - final File classesDir = getClassesDir(); + final FileCollection classesDirs = getClassesDirs(); final FileCollection classpath = getClasspath(); - if (classesDir == null || classpath == null) { - throw new InvalidUserDataException("Missing 'classesDir' or 'classpath' property."); + if (classesDirs == null || classpath == null) { + throw new InvalidUserDataException("Missing 'classesDirs' or 'classpath' property."); } final Logger log = new Logger() { @@ -482,14 +510,15 @@ public void info(String msg) { } }; - final Set cpElements = classpath.getFiles(); - final URL[] urls = new URL[cpElements.size() + 1]; + final Set cpElements = new LinkedHashSet(); + cpElements.addAll(classpath.getFiles()); + cpElements.addAll(classesDirs.getFiles()); + final URL[] urls = new URL[cpElements.size()]; try { int i = 0; for (final File cpElement : cpElements) { urls[i++] = cpElement.toURI().toURL(); } - urls[i++] = classesDir.toURI().toURL(); assert i == urls.length; } catch (MalformedURLException mfue) { throw new InvalidUserDataException("Failed to build classpath URLs.", mfue); diff --git a/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy b/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy index 2f575575..a5fdd250 100644 --- a/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy +++ b/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy @@ -45,19 +45,20 @@ def extensionProps = CheckForbiddenApisExtension.class.declaredFields.findAll{ f // Define our tasks (one for each SourceSet): def forbiddenTasks = project.sourceSets.collect{ sourceSet -> + def getClassesDirs = { sourceSet.output.hasProperty('classesDirs') ? sourceSet.output.classesDirs : project.files(sourceSet.output.classesDir) } project.tasks.create(sourceSet.getTaskName(FORBIDDEN_APIS_TASK_NAME, null), CheckForbiddenApis.class) { description = "Runs forbidden-apis checks on '${sourceSet.name}' classes."; conventionMapping.with{ extensionProps.each{ key -> map(key, { extension[key] }); } - classesDir = { sourceSet.output.classesDir } + classesDirs = { getClassesDirs() } classpath = { sourceSet.compileClasspath } targetCompatibility = { project.targetCompatibility?.toString() } } - // add dependency to compile task after evaluation, if the classesDir is from our SourceSet: - project.afterEvaluate{ - if (classesDir == sourceSet.output.classesDir) { + // add dependency to compile task after evaluation, if the classesDirs collection has overlaps with our SourceSet: + project.afterEvaluate{ + if (!classesDirs.minus(getClassesDirs()).isEmpty()) { dependsOn(sourceSet.output); } } diff --git a/src/test/gradle/build.gradle b/src/test/gradle/build.gradle index 397e526f..41e0bd7c 100644 --- a/src/test/gradle/build.gradle +++ b/src/test/gradle/build.gradle @@ -37,12 +37,13 @@ forbiddenApis { } forbiddenApisMain { - classesDir = new File(forbiddenRootDir, 'build/main') + classesDirs = files(new File(forbiddenRootDir, 'build/main')) classpath = files(forbiddenClasspath.tokenize(File.pathSeparator)) bundledSignatures += 'jdk-system-out' } forbiddenApisTest { + // use classesDir here to check backwards compatibility!: classesDir = new File(forbiddenRootDir, 'build/test') classpath = files(forbiddenTestClasspath.tokenize(File.pathSeparator)) }