diff --git a/build.xml b/build.xml index f5a111cc..c4d1dec7 100644 --- a/build.xml +++ b/build.xml @@ -153,6 +153,7 @@ + + + + + + + + + + + + + + + + + + diff --git a/ivy.xml b/ivy.xml index cc54519d..a23b42fe 100644 --- a/ivy.xml +++ b/ivy.xml @@ -27,6 +27,12 @@ + + + + + + @@ -39,7 +45,6 @@ - diff --git a/src/main/java/de/thetaphi/forbiddenapis/Checker.java b/src/main/java/de/thetaphi/forbiddenapis/Checker.java index 4a0d6cc3..b0e00530 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Checker.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Checker.java @@ -23,6 +23,7 @@ import org.objectweb.asm.commons.Method; import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -351,6 +352,11 @@ public final void parseSignaturesFile(InputStream in) throws IOException,ParseEx parseSignaturesFile(in, false); } + /** Reads a list of API signatures from the given file. */ + public final void parseSignaturesFile(File f) throws IOException,ParseException { + parseSignaturesFile(new FileInputStream(f)); + } + /** Reads a list of API signatures from a String. */ public final void parseSignaturesString(String signatures) throws IOException,ParseException { parseSignaturesFile(new StringReader(signatures), false); @@ -405,6 +411,11 @@ public final void addClassToCheck(final InputStream in) throws IOException { classesToCheck.put(reader.getClassName(), new ClassSignature(reader, false, true)); } + /** Parses and adds a class from the given file to the list of classes to check. */ + public final void addClassToCheck(File f) throws IOException { + addClassToCheck(new FileInputStream(f)); + } + public final boolean hasNoSignatures() { return forbiddenMethods.isEmpty() && forbiddenFields.isEmpty() && forbiddenClasses.isEmpty() && forbiddenClassPatterns.isEmpty() && (!options.contains(Option.INTERNAL_RUNTIME_FORBIDDEN)); } diff --git a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java index 3322036f..673d50f3 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java +++ b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java @@ -20,7 +20,6 @@ import java.io.Closeable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; @@ -266,7 +265,7 @@ public void run() throws ExitException { if (signaturesFiles != null) for (String sf : signaturesFiles) { final File f = new File(sf).getAbsoluteFile(); LOG.info("Reading API signatures: " + f); - checker.parseSignaturesFile(new FileInputStream(f)); + checker.parseSignaturesFile(f); } } catch (IOException ioe) { throw new ExitException(EXIT_ERR_OTHER, "IO problem while reading files with API signatures: " + ioe); @@ -284,7 +283,7 @@ public void run() throws ExitException { LOG.info("Loading classes to check..."); try { for (String f : files) { - checker.addClassToCheck(new FileInputStream(new File(classesDirectory, f))); + checker.addClassToCheck(new File(classesDirectory, f)); } } catch (IOException ioe) { throw new ExitException(EXIT_ERR_OTHER, "Failed to load one of the given class files: " + ioe); diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java new file mode 100644 index 00000000..84d19498 --- /dev/null +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java @@ -0,0 +1,471 @@ +package de.thetaphi.forbiddenapis.gradle; + +/* + * (C) Copyright Uwe Schindler (Generics Policeman) and others. + * + * 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. + */ + +import static de.thetaphi.forbiddenapis.Checker.Option.FAIL_ON_MISSING_CLASSES; +import static de.thetaphi.forbiddenapis.Checker.Option.FAIL_ON_UNRESOLVABLE_SIGNATURES; +import static de.thetaphi.forbiddenapis.Checker.Option.FAIL_ON_VIOLATION; +import static de.thetaphi.forbiddenapis.Checker.Option.INTERNAL_RUNTIME_FORBIDDEN; +import groovy.lang.Closure; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.lang.annotation.RetentionPolicy; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.JavaVersion; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileTree; +import org.gradle.api.file.FileTreeElement; +import org.gradle.api.resources.ResourceException; +import org.gradle.api.specs.Spec; +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.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.VerificationTask; +import org.gradle.api.tasks.util.PatternFilterable; +import org.gradle.api.tasks.util.PatternSet; + +import de.thetaphi.forbiddenapis.Checker; +import de.thetaphi.forbiddenapis.ForbiddenApiException; +import de.thetaphi.forbiddenapis.Logger; +import de.thetaphi.forbiddenapis.ParseException; + +/** + * ForbiddenApis Gradle Task + * @since 1.9 + */ +public class CheckForbiddenApis extends DefaultTask implements PatternFilterable,VerificationTask { + + private final CheckForbiddenApisExtension data = new CheckForbiddenApisExtension(); + private final PatternSet patternSet = new PatternSet().include("**/*.class"); + private File classesDir; + private FileCollection classpath; + + /** + * Directory with the class files to check. + */ + @OutputDirectory + public File getClassesDir() { + return classesDir; + } + + /** @see #getClassesDir */ + public void setClassesDir(File classesDir) { + this.classesDir = classesDir; + } + + /** + * A {@link FileCollection} containing all files, which contain signatures and comments for forbidden API calls. + * The signatures are resolved against the compile classpath. + * @since 1.0 + */ + @InputFiles + public FileCollection getClasspath() { + return classpath; + } + + /** @see #getClasspath */ + public void setClasspath(FileCollection classpath) { + this.classpath = classpath; + } + + /** + * A {@link FileCollection} used to configure the classpath. + */ + @InputFiles + @Optional + public FileCollection getSignaturesFiles() { + return data.signaturesFiles; + } + + /** @see #getSignaturesFiles */ + public void setSignaturesFiles(FileCollection signaturesFiles) { + data.signaturesFiles = signaturesFiles; + } + + /** + * Gives multiple API signatures that are joined with newlines and + * parsed like a single {@link #signaturesFiles}. + * The signatures are resolved against the compile classpath. + * @since 1.0 + */ + @Input + @Optional + public List getSignatures() { + return data.signatures; + } + + /** @see #getSignatures */ + public void setSignatures(List signatures) { + data.signatures.clear(); + data.signatures.addAll(signatures); + } + + /** + * Specifies built-in signatures files (e.g., deprecated APIs for specific Java versions, + * unsafe method calls using default locale, default charset,...) + * @since 1.0 + */ + @Input + @Optional + public List getBundledSignatures() { + return data.bundledSignatures; + } + + /** @see #getBundledSignatures */ + public void setBundledSignatures(List bundledSignatures) { + data.bundledSignatures.clear(); + data.bundledSignatures.addAll(bundledSignatures); + } + + /** + * Forbids calls to classes from the internal java runtime (like sun.misc.Unsafe) + * @since 1.0 + */ + @Input + public boolean getInternalRuntimeForbidden() { + return data.internalRuntimeForbidden; + } + + /** @see #getInternalRuntimeForbidden */ + public void setInternalRuntimeForbidden(boolean internalRuntimeForbidden) { + data.internalRuntimeForbidden = internalRuntimeForbidden; + } + + /** + * Fail the build, if the bundled ASM library cannot read the class file format + * of the runtime library or the runtime library cannot be discovered. + * @since 1.0 + */ + @Input + public boolean getFailOnUnsupportedJava() { + return data.failOnUnsupportedJava; + } + + /** @see #getFailOnUnsupportedJava */ + public void setFailOnUnsupportedJava(boolean failOnUnsupportedJava) { + data.failOnUnsupportedJava = failOnUnsupportedJava; + } + + /** + * Fail the build, if a class referenced in the scanned code is missing. This requires + * that you pass the whole classpath including all dependencies to this task + * (Gradle does this by default). + * @since 1.0 + */ + @Input + public boolean getFailOnMissingClasses() { + return data.failOnMissingClasses; + } + + /** @see #getFailOnMissingClasses */ + public void setFailOnMissingClasses(boolean failOnMissingClasses) { + data.failOnMissingClasses = failOnMissingClasses; + } + + /** + * Fail the build if a signature is not resolving. If this parameter is set to + * to false, then such signatures are silently ignored. This is useful in multi-module Maven + * projects where only some modules have the dependency to which the signature file(s) apply. + * @since 1.4 + */ + @Input + public boolean getFailOnUnresolvableSignatures() { + return data.failOnUnresolvableSignatures; + } + + /** @see #getFailOnUnresolvableSignatures */ + public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) { + data.failOnUnresolvableSignatures = failOnUnresolvableSignatures; + } + + /** + * @{inheritDoc} + *

+ * This setting is to conform with {@link VerificationTask} interface. + * Other ForbiddenApis implementations use another name: {@code failOnViolation} + * Default is {@code false}. + */ + public boolean getIgnoreFailures() { + return data.ignoreFailures; + } + + public void setIgnoreFailures(boolean ignoreFailures) { + data.ignoreFailures = ignoreFailures; + } + + /** + * List of a custom Java annotations (full class names) that are used in the checked + * code to suppress errors. Those annotations must have at least + * {@link RetentionPolicy#CLASS}. They can be applied to classes, their methods, + * or fields. By default, {@code @de.thetaphi.forbiddenapis.SuppressForbidden} + * can always be used, but needs the {@code forbidden-apis.jar} file in classpath + * of compiled project, which may not be wanted. + * Instead of a full class name, a glob pattern may be used (e.g., + * {@code **.SuppressForbidden}). + * @since 1.8 + */ + @Input + @Optional + public List getSuppressAnnotations() { + return data.suppressAnnotations; + } + + /** @see #getSuppressAnnotations */ + public void setSuppressAnnotations(List suppressAnnotations) { + data.suppressAnnotations.clear(); + data.suppressAnnotations.addAll(suppressAnnotations); + } + + // PatternFilterable implementation: + + /** + * {@inheritDoc} + *

+ * Set of patterns matching all class files to be parsed from the classesDirectory. + * Can be changed to e.g. exclude several files (using excludes). + * The default is a single include with pattern '**/*.class' + * @since 1.0 + */ + @Input + public Set getIncludes() { + return getPatternSet().getIncludes(); + } + + public CheckForbiddenApis setIncludes(Iterable includes) { + getPatternSet().setIncludes(includes); + return this; + } + + /** + * {@inheritDoc} + *

+ * Set of patterns matching class files to be excluded from checking. + * @since 1.0 + */ + @Input + public Set getExcludes() { + return getPatternSet().getExcludes(); + } + + public CheckForbiddenApis setExcludes(Iterable excludes) { + getPatternSet().setExcludes(excludes); + return this; + } + + public CheckForbiddenApis exclude(String... arg0) { + getPatternSet().exclude(arg0); + return this; + } + + public CheckForbiddenApis exclude(Iterable arg0) { + getPatternSet().exclude(arg0); + return this; + } + + public CheckForbiddenApis exclude(Spec arg0) { + getPatternSet().exclude(arg0); + return this; + } + + public CheckForbiddenApis exclude(@SuppressWarnings("rawtypes") Closure arg0) { + getPatternSet().exclude(arg0); + return this; + } + + public CheckForbiddenApis include(String... arg0) { + getPatternSet().include(arg0); + return this; + } + + public CheckForbiddenApis include(Iterable arg0) { + getPatternSet().include(arg0); + return this; + } + + public CheckForbiddenApis include(Spec arg0) { + getPatternSet().include(arg0); + return this; + } + + public CheckForbiddenApis include(@SuppressWarnings("rawtypes") Closure arg0) { + getPatternSet().include(arg0); + return this; + } + + public PatternSet getPatternSet() { + return patternSet; + } + + public void setPatternSet(PatternSet patternSet) { + patternSet.copyFrom(patternSet); + } + + /** Returns the classes to check. */ + @InputFiles + @SkipWhenEmpty + public FileTree getClassFiles() { + return getProject().files(getClassesDir()).getAsFileTree().matching(getPatternSet()); + } + + @TaskAction + public void checkForbidden() throws ForbiddenApiException { + final File classesDir = getClassesDir(); + final FileCollection classpath = getClasspath(); + if (classesDir == null || classpath == null) { + throw new InvalidUserDataException("Missing 'classesDir' or 'classpath' property."); + } + + final Logger log = new Logger() { + public void error(String msg) { + getLogger().error(msg); + } + + public void warn(String msg) { + getLogger().warn(msg); + } + + public void info(String msg) { + getLogger().info(msg); + } + }; + + final Collection cpElements = classpath.getFiles(); + final URL[] urls = new URL[cpElements.size() + 1]; + 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); + } + + URLClassLoader urlLoader = null; + final ClassLoader loader = (urls.length > 0) ? + (urlLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader())) : + ClassLoader.getSystemClassLoader(); + + try { + final EnumSet options = EnumSet.noneOf(Checker.Option.class); + if (getInternalRuntimeForbidden()) options.add(INTERNAL_RUNTIME_FORBIDDEN); + if (getFailOnMissingClasses()) options.add(FAIL_ON_MISSING_CLASSES); + if (!getIgnoreFailures()) options.add(FAIL_ON_VIOLATION); + if (getFailOnUnresolvableSignatures()) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + final Checker checker = new Checker(log, loader, options); + + if (!checker.isSupportedJDK) { + final String msg = String.format(Locale.ENGLISH, + "Your Java runtime (%s %s) is not supported by the forbiddenapis plugin. Please run the checks with a supported JDK!", + System.getProperty("java.runtime.name"), System.getProperty("java.runtime.version")); + if (getFailOnUnsupportedJava()) { + throw new GradleException(msg); + } else { + log.warn(msg); + return; + } + } + + final List suppressAnnotations = getSuppressAnnotations(); + if (suppressAnnotations != null) { + for (String a : suppressAnnotations) { + checker.addSuppressAnnotation(a); + } + } + + try { + final List signatures = getSignatures(); + if (signatures != null && !signatures.isEmpty()) { + log.info("Reading inline API signatures..."); + final StringBuilder sb = new StringBuilder(); + for (String line : signatures) { + sb.append(line).append('\n'); + } + checker.parseSignaturesString(sb.toString()); + } + final List bundledSignatures = getBundledSignatures(); + if (bundledSignatures != null) { + final JavaVersion targetVersion = (JavaVersion) getProject().property("targetCompatibility"); + if (targetVersion == null) { + log.warn("The 'targetCompatibility' project property is missing. " + + "Trying to read bundled JDK signatures without compiler target. " + + "You have to explicitely specify the version in the resource name."); + } + for (String bs : bundledSignatures) { + log.info("Reading bundled API signatures: " + bs); + checker.parseBundledSignatures(bs, targetVersion == null ? null : targetVersion.toString()); + } + } + final FileCollection signaturesFiles = getSignaturesFiles(); + if (signaturesFiles != null) for (final File f : signaturesFiles) { + log.info("Reading API signatures: " + f); + checker.parseSignaturesFile(f); + } + } catch (IOException ioe) { + throw new ResourceException("IO problem while reading files with API signatures.", ioe); + } catch (ParseException pe) { + throw new InvalidUserDataException("Parsing signatures failed: " + pe.getMessage()); + } + + if (checker.hasNoSignatures()) { + if (options.contains(FAIL_ON_UNRESOLVABLE_SIGNATURES)) { + throw new InvalidUserDataException("No API signatures found; use parameters 'signatures', 'bundledSignatures', and/or 'signaturesFiles' to define those!"); + } else { + log.info("Skipping execution because no API signatures are available."); + return; + } + } + + log.info("Loading classes to check..."); + try { + for (File f : getClassFiles()) { + checker.addClassToCheck(f); + } + } catch (IOException ioe) { + throw new ResourceException("Failed to load one of the given class files.", ioe); + } + + log.info("Scanning for API signatures and dependencies..."); + checker.run(); + } finally { + // Java 7 supports closing URLClassLoader, so check for Closeable interface: + if (urlLoader instanceof Closeable) try { + ((Closeable) urlLoader).close(); + } catch (IOException ioe) { + // ignore + } + } + } + +} diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java new file mode 100644 index 00000000..36b29244 --- /dev/null +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java @@ -0,0 +1,55 @@ +package de.thetaphi.forbiddenapis.gradle; + +/* + * (C) Copyright Uwe Schindler (Generics Policeman) and others. + * + * 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. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.gradle.api.file.FileCollection; + +/** + * Extension for the ForbiddenApis Gradle Task to store defaults. + * For description of the properties refer to the {@link CheckForbiddenApis} + * task documentation. + */ +public class CheckForbiddenApisExtension { + + /** Fields used for the convention mapping, keep up-to-date with class members! */ + static final List PROPS = Arrays.asList( + "signaturesFiles", + "signatures", + "bundledSignatures", + "suppressAnnotations", + "internalRuntimeForbidden", + "failOnUnsupportedJava", + "failOnMissingClasses", + "failOnUnresolvableSignatures", + "ignoreFailures" + ); + + public FileCollection signaturesFiles; + public List signatures = new ArrayList(), + bundledSignatures = new ArrayList(), + suppressAnnotations = new ArrayList(); + public boolean internalRuntimeForbidden = false, + failOnUnsupportedJava = false, + failOnMissingClasses = true, + failOnUnresolvableSignatures = true, + ignoreFailures = false; + +} diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/ForbiddenApisPlugin.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/ForbiddenApisPlugin.java new file mode 100644 index 00000000..b5dc856c --- /dev/null +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/ForbiddenApisPlugin.java @@ -0,0 +1,70 @@ +package de.thetaphi.forbiddenapis.gradle; + +/* + * (C) Copyright Uwe Schindler (Generics Policeman) and others. + * + * 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. + */ + +import groovy.lang.Binding; +import groovy.lang.GroovyShell; +import groovy.util.DelegatingScript; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; + +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.ImportCustomizer; +import org.codehaus.groovy.runtime.ResourceGroovyMethods; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.PluginInstantiationException; + +/** + * Forbiddenapis Gradle Plugin + * @since 1.9 + */ +public class ForbiddenApisPlugin implements Plugin { + + /** Resource with Groovy script that initializes the plugin. */ + public static final String PLUGIN_INIT_SCRIPT = "plugin-init.groovy"; + + /** Name of the base task that depends on one for every SourceSet */ + public static final String FORBIDDEN_APIS_TASK_NAME = "forbiddenApis"; + + /** Name of the base task that depends on one for every SourceSet */ + public static final String FORBIDDEN_APIS_EXTENSION_NAME = "forbiddenApis"; + + // Not before Java 6: @Override + public void apply(final Project project) { + final String scriptText; + try { + final URL scriptUrl = ForbiddenApisPlugin.class.getResource(PLUGIN_INIT_SCRIPT); + if (scriptUrl == null) { + throw new PluginInstantiationException("Cannot find resource with " + PLUGIN_INIT_SCRIPT + " script."); + } + scriptText = ResourceGroovyMethods.getText(scriptUrl, "UTF-8"); + } catch (IOException ioe) { + throw new PluginInstantiationException("Cannot load " + PLUGIN_INIT_SCRIPT + " script.", ioe); + } + final ImportCustomizer importCustomizer = new ImportCustomizer().addStarImports(ForbiddenApisPlugin.class.getPackage().getName()); + final CompilerConfiguration configuration = new CompilerConfiguration().addCompilationCustomizers(importCustomizer); + configuration.setScriptBaseClass(DelegatingScript.class.getName()); + final GroovyShell shell = new GroovyShell(ForbiddenApisPlugin.class.getClassLoader(), new Binding(Collections.singletonMap("project", project)), configuration); + final DelegatingScript script = (DelegatingScript) shell.parse(scriptText, PLUGIN_INIT_SCRIPT); + script.setDelegate(this); + script.run(); + } + +} diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java index c0f73788..7a44009d 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java +++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java @@ -32,7 +32,6 @@ import java.io.Closeable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.lang.annotation.RetentionPolicy; import java.net.URLClassLoader; @@ -195,10 +194,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { // set default param: if (includes == null) includes = new String[] {"**/*.class"}; - final URL[] urls; + final List cp = getClassPathElements(); + final URL[] urls = new URL[cp.size()]; try { - final List cp = getClassPathElements(); - urls = new URL[cp.size()]; int i = 0; for (final String cpElement : cp) { urls[i++] = new File(cpElement).toURI().toURL(); @@ -294,7 +292,7 @@ public void info(String msg) { } if (signaturesFiles != null) for (final File f : signaturesFiles) { log.info("Reading API signatures: " + f); - checker.parseSignaturesFile(new FileInputStream(f)); + checker.parseSignaturesFile(f); } } catch (IOException ioe) { throw new MojoExecutionException("IO problem while reading files with API signatures: " + ioe); @@ -314,7 +312,7 @@ public void info(String msg) { log.info("Loading classes to check..."); try { for (String f : files) { - checker.addClassToCheck(new FileInputStream(new File(classesDirectory, f))); + checker.addClassToCheck(new File(classesDirectory, f)); } } catch (IOException ioe) { throw new MojoExecutionException("Failed to load one of the given class files: " + ioe); diff --git a/src/main/resources/META-INF/gradle-plugins/de.thetaphi.forbiddenapis.properties b/src/main/resources/META-INF/gradle-plugins/de.thetaphi.forbiddenapis.properties new file mode 100644 index 00000000..dfd15d10 --- /dev/null +++ b/src/main/resources/META-INF/gradle-plugins/de.thetaphi.forbiddenapis.properties @@ -0,0 +1 @@ +implementation-class=de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin diff --git a/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy b/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy new file mode 100644 index 00000000..00f29c68 --- /dev/null +++ b/src/main/resources/de/thetaphi/forbiddenapis/gradle/plugin-init.groovy @@ -0,0 +1,57 @@ +/* + * (C) Copyright Uwe Schindler (Generics Policeman) and others. + * + * 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. + */ + +/** Initializes the plugin and binds it to project lifecycle. */ + +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.plugins.PluginInstantiationException; + +if (project.plugins.withType(JavaBasePlugin.class).isEmpty()) { + throw new PluginInstantiationException('Forbidden-apis only works in projects using the java plugin.'); +} + +// create Extension for defaults: +def extension = project.extensions.create(FORBIDDEN_APIS_EXTENSION_NAME, CheckForbiddenApisExtension.class); + +// Define our tasks (one for each SourceSet): +def forbiddenTasks = project.sourceSets.collect { sourceSet -> + project.tasks.create(sourceSet.getTaskName(FORBIDDEN_APIS_TASK_NAME, null), CheckForbiddenApis.class) { + description = "Runs forbidden-apis checks on '${sourceSet.name}' classes."; + conventionMapping.with { + CheckForbiddenApisExtension.PROPS.each { key -> + map(key, { extension[key] }); + } + classesDir = { sourceSet.output.classesDir } + classpath = { sourceSet.compileClasspath } + } + // add dependency to compile task after evaluation, if the classesDir is from our SourceSet: + project.afterEvaluate { + if (classesDir == sourceSet.output.classesDir) { + dependsOn(sourceSet.output); + } + } + } +} + +// Create a task for all checks +def forbiddenTask = project.tasks.create(FORBIDDEN_APIS_TASK_NAME) { + description = "Runs forbidden-apis checks."; + group = JavaBasePlugin.VERIFICATION_GROUP; + dependsOn(forbiddenTasks); +} + +// Add our task as dependency to chain +project.tasks[JavaBasePlugin.CHECK_TASK_NAME].dependsOn(forbiddenTask);