From 2f24b42a50f8a674338c4f9b984009f75e758e93 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Wed, 22 Apr 2020 17:58:48 +0200 Subject: [PATCH 1/5] Allow to silently ignore signatures if the class is not found (e.g., multi-module builds with common signatures). The old setting failOnUnresolvableSignatures was deprecated in favour of ignoreSignaturesOfMissingClasses. This closes #83 --- src/main/docs/ant-task.html | 21 +++++++++- .../de/thetaphi/forbiddenapis/Checker.java | 6 +++ .../de/thetaphi/forbiddenapis/Constants.java | 3 ++ .../de/thetaphi/forbiddenapis/Signatures.java | 24 ++++++++--- .../thetaphi/forbiddenapis/ant/AntTask.java | 40 ++++++++++++++++-- .../thetaphi/forbiddenapis/cli/CliMain.java | 28 +++++++++---- .../gradle/CheckForbiddenApis.java | 42 ++++++++++++++++--- .../gradle/CheckForbiddenApisExtension.java | 1 + .../maven/AbstractCheckMojo.java | 34 +++++++++++++-- .../forbiddenapis/CheckerSetupTest.java | 9 ++++ 10 files changed, 182 insertions(+), 26 deletions(-) diff --git a/src/main/docs/ant-task.html b/src/main/docs/ant-task.html index ed0c3e5a..9a7c6295 100644 --- a/src/main/docs/ant-task.html +++ b/src/main/docs/ant-task.html @@ -113,7 +113,15 @@

Parameters

failOnUnresolvableSignatures boolean true - Fail the build if a signature is not resolving. If this parameter is set to false, then such signatures are silently ignored. + Fail the build if a signature is not resolving. If this parameter is set to + to false, then such signatures are ignored.
+ When disabling this setting, the task still prints a warning to inform the user about + broken signatures. This cannot be disabled. There is a second setting + ignoreSignaturesOfMissingClasses that can be used to silently ignore + signatures that refer to methods or field in classes that are not on classpath, + e.g. This is useful in multi-module builds where a common set of signatures is used, + that are not part of every sub-modules dependencies.
+ Deprecated. Use ignoreSignaturesOfMissingClasses instead. @@ -137,6 +145,17 @@

Parameters

Ignore empty fileset/resource collection and print a warning instead. + + ignoreSignaturesOfMissingClasses + boolean + false + If a class is missing while parsing signatures files, all methods and fields from this + class are silently ignored. This is useful in multi-module + projects where only some modules have the dependency to which the signature file(s) apply. + This settings prints no warning at all, so verify the signatures at least once with + full dependencies. + + suppressAnnotation class name diff --git a/src/main/java/de/thetaphi/forbiddenapis/Checker.java b/src/main/java/de/thetaphi/forbiddenapis/Checker.java index 1a928757..3f236224 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Checker.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Checker.java @@ -54,6 +54,7 @@ public static enum Option { FAIL_ON_MISSING_CLASSES, FAIL_ON_VIOLATION, FAIL_ON_UNRESOLVABLE_SIGNATURES, + IGNORE_SIGNATURES_OF_MISSING_CLASSES, DISABLE_CLASSLOADING_CACHE } @@ -341,6 +342,11 @@ public boolean hasNoSignatures() { return forbiddenSignatures.hasNoSignatures(); } + /** Returns if no signatures files / inline signatures were parsed */ + public boolean noSignaturesFilesParsed() { + return forbiddenSignatures.noSignaturesFilesParsed(); + } + /** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)! Does not log anything. */ public void addClassToCheck(final InputStream in, String name) throws IOException { final ClassReader reader; diff --git a/src/main/java/de/thetaphi/forbiddenapis/Constants.java b/src/main/java/de/thetaphi/forbiddenapis/Constants.java index 5c3a9818..582eacd6 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Constants.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Constants.java @@ -28,6 +28,9 @@ public interface Constants { final Pattern JDK_SIG_PATTERN = Pattern.compile("(jdk\\-.*?\\-)(\\d+)(\\.\\d+)?(\\.\\d+)*"); + final String DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES = + "The setting 'failOnUnresolvableSignatures' was deprecated and will be removed in next version. Use 'ignoreSignaturesOfMissingClasses' instead."; + final Type DEPRECATED_TYPE = Type.getType(Deprecated.class); final String DEPRECATED_DESCRIPTOR = DEPRECATED_TYPE.getDescriptor(); diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java index bdb98088..f4e7acfc 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java @@ -77,7 +77,7 @@ private UnresolvableReporting(boolean reportClassNotFound) { private final RelatedClassLookup lookup; private final Logger logger; - private final boolean failOnUnresolvableSignatures; + private final boolean failOnUnresolvableSignatures, ignoreSignaturesOfMissingClasses; /** Key is used to lookup forbidden signature in following formats. Keys are generated by the corresponding * {@link #getKey(String)} (classes), {@link #getKey(String, Method)} (methods), @@ -90,14 +90,18 @@ private UnresolvableReporting(boolean reportClassNotFound) { /** if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used */ private boolean forbidNonPortableRuntime = false; + + /** number of files that were interpreted as signatures file. If 0, no (bundled) signatures files were added at all */ + private int numberOfFiles = 0; public Signatures(Checker checker) { - this(checker, checker.logger, checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES)); + this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES)); } - public Signatures(RelatedClassLookup lookup, Logger logger, boolean failOnUnresolvableSignatures) { + public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures) { this.lookup = lookup; this.logger = logger; + this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses; this.failOnUnresolvableSignatures = failOnUnresolvableSignatures; } @@ -170,6 +174,9 @@ private void addSignature(final String line, final String defaultMessage, final try { c = lookup.getClassFromClassLoader(clazz); } catch (ClassNotFoundException cnfe) { + if (this.ignoreSignaturesOfMissingClasses) { + return; + } if (report.reportClassNotFound) { report.parseFailed(logger, String.format(Locale.ENGLISH, "Class '%s' not found on classpath", cnfe.getMessage()), signature); } else { @@ -234,6 +241,7 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean } if (BS_JDK_NONPORTABLE.equals(name)) { if (logging) logger.info("Reading bundled API signatures: " + name); + numberOfFiles++; forbidNonPortableRuntime = true; return; } @@ -253,11 +261,12 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean parseSignaturesStream(in, true, missingClasses); } - private void parseSignaturesStream(InputStream in, boolean allowBundled, Set missingClasses) throws IOException,ParseException { - parseSignaturesFile(new InputStreamReader(in, "UTF-8"), allowBundled, missingClasses); + private void parseSignaturesStream(InputStream in, boolean isBundled, Set missingClasses) throws IOException,ParseException { + parseSignaturesFile(new InputStreamReader(in, "UTF-8"), isBundled, missingClasses); } private void parseSignaturesFile(Reader reader, boolean isBundled, Set missingClasses) throws IOException,ParseException { + numberOfFiles++; try (final BufferedReader r = new BufferedReader(reader)) { String line, defaultMessage = null; UnresolvableReporting reporter = failOnUnresolvableSignatures ? UnresolvableReporting.FAIL : UnresolvableReporting.WARNING; @@ -314,6 +323,11 @@ public boolean hasNoSignatures() { (forbidNonPortableRuntime ? 1 : 0); } + /** Returns if no signatures files / inline signatures were parsed */ + public boolean noSignaturesFilesParsed() { + return numberOfFiles == 0; + } + /** Returns if bundled signature to enable heuristics for detection of non-portable runtime calls is used */ public boolean isNonPortableRuntimeForbidden() { return this.forbidNonPortableRuntime; diff --git a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java index 9a884be3..9476f201 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java @@ -67,6 +67,7 @@ public class AntTask extends Task implements Constants { private boolean restrictClassFilename = true; private boolean failOnMissingClasses = true; private boolean failOnUnresolvableSignatures = true; + private boolean ignoreSignaturesOfMissingClasses = false; private boolean failOnViolation = true; private boolean ignoreEmptyFileset = false; private String targetVersion = null; @@ -108,7 +109,12 @@ public void info(String msg) { final EnumSet options = EnumSet.noneOf(Checker.Option.class); if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES); if (failOnViolation) options.add(FAIL_ON_VIOLATION); - if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + if (failOnUnresolvableSignatures) { + options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + } else { + log.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES); + } + if (ignoreSignaturesOfMissingClasses) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES); if (disableClassloadingCache) options.add(DISABLE_CLASSLOADING_CACHE); final Checker checker = new Checker(log, loader, options); @@ -169,7 +175,12 @@ public void info(String msg) { } if (checker.hasNoSignatures()) { - throw new BuildException("No API signatures found; use signaturesFile=, , or inner text to define those!"); + if (checker.noSignaturesFilesParsed()) { + throw new BuildException("No API signatures found; use signaturesFile=, , or inner text to define those!"); + } else { + log.info("Skipping execution because no API signatures are available."); + return; + } } log.info("Loading classes to check..."); @@ -325,13 +336,34 @@ public void setFailOnMissingClasses(boolean failOnMissingClasses) { /** * Fail the build if a signature is not resolving. If this parameter is set to - * to false, then such signatures are silently ignored. - * Defaults to {@code true}. + * to false, then such signatures are ignored. Defaults to {@code true}. + *

When disabling this setting, the task still prints a warning to inform the user about + * broken signatures. This cannot be disabled. There is a second setting + * {@link #setIgnoreMissingSignaturesClasses()} that can be used to silently ignore + * signatures that refer to methods or field in classes that are not on classpath, + * e.g. This is useful in multi-module builds where a common set of signatures is used, + * that are not part of every sub-modules dependencies. + * @see #setIgnoreMissingSignaturesClasses() + * @deprecated Use {@link #setIgnoreSignaturesOfMissingClasses(boolean)} instead. */ + @Deprecated public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) { this.failOnUnresolvableSignatures = failOnUnresolvableSignatures; } + /** + * If a class is missing while parsing signatures files, all methods and fields from this + * class are silently ignored. This is useful in multi-module + * projects where only some modules have the dependency to which the signature file(s) apply. + * This settings prints no warning at all, so verify the signatures at least once with + * full dependencies. + * Defaults to {@code false}. + * @since 3.0 + */ + public void setIgnoreSignaturesOfMissingClasses(boolean ignoreSignaturesOfMissingClasses) { + this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses; + } + /** Automatically restrict resource names included to files with a name ending in '.class'. * This makes filesets easier, as the includes="**/*.class" is not needed. * Defaults to {@code true}. diff --git a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java index b10ca5d3..3bdf8da7 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java +++ b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java @@ -53,7 +53,7 @@ public final class CliMain implements Constants { private final Option classpathOpt, dirOpt, includesOpt, excludesOpt, signaturesfileOpt, bundledsignaturesOpt, suppressannotationsOpt, - allowmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt; + allowmissingclassesOpt, ignoresignaturesofmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt; private final CommandLine cmd; private static final Logger LOG = StdIoLogger.INSTANCE; @@ -129,8 +129,12 @@ public CliMain(String... args) throws ExitException { .desc("don't fail if a referenced class is missing on classpath") .longOpt("allowmissingclasses") .build()); + options.addOption(ignoresignaturesofmissingclassesOpt = Option.builder() + .desc("if a class is missing while parsing signatures files, all methods and fields from this class are silently ignored") + .longOpt("ignoresignaturesofmissingclasses") + .build()); options.addOption(allowunresolvablesignaturesOpt = Option.builder() - .desc("don't fail if a signature is not resolving") + .desc("DEPRECATED: don't fail if a signature is not resolving") .longOpt("allowunresolvablesignatures") .build()); @@ -212,7 +216,12 @@ public void run() throws ExitException { try (final URLClassLoader loader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader())) { final EnumSet options = EnumSet.of(FAIL_ON_VIOLATION); if (!cmd.hasOption(allowmissingclassesOpt.getLongOpt())) options.add(FAIL_ON_MISSING_CLASSES); - if (!cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + if (cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) { + LOG.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES); + } else { + options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + } + if (cmd.hasOption(ignoresignaturesofmissingclassesOpt.getLongOpt())) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES); final Checker checker = new Checker(LOG, loader, options); if (!checker.isSupportedJDK) { @@ -267,10 +276,15 @@ public void run() throws ExitException { } if (checker.hasNoSignatures()) { - throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH, - "No API signatures found; use parameters '--%s' and/or '--%s' to specify those!", - bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt() - )); + if (checker.noSignaturesFilesParsed()) { + throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH, + "No API signatures found; use parameters '--%s' and/or '--%s' to specify those!", + bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt() + )); + } else { + LOG.info("Skipping execution because no API signatures are available."); + return; + } } try { diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java index 5a5d3c2b..6370672c 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java @@ -258,20 +258,47 @@ public void setFailOnMissingClasses(boolean 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. - * Defaults to {@code true}. + * to false, then such signatures are ignored. Defaults to {@code true}. + *

When disabling this setting, the task still prints a warning to inform the user about + * broken signatures. This cannot be disabled. There is a second setting + * {@link #getIgnoreSignaturesOfMissingClasses()} that can be used to silently ignore + * signatures that refer to methods or field in classes that are not on classpath, + * e.g. This is useful in multi-module Gradle builds where a common set of signatures is used, + * that are not part of every sub-modules dependencies. + * @see #getIgnoreSignaturesOfMissingClasses() + * @deprecated Use {@link #getIgnoreSignaturesOfMissingClasses()} instead. */ @Input + @Deprecated public boolean getFailOnUnresolvableSignatures() { return data.failOnUnresolvableSignatures; } /** @see #getFailOnUnresolvableSignatures */ + @Deprecated public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) { data.failOnUnresolvableSignatures = failOnUnresolvableSignatures; } + /** + * If a class is missing while parsing signatures files, all methods and fields from this + * class are silently ignored. This is useful in multi-module Gradle + * projects where only some modules have the dependency to which the signature file(s) apply. + * This settings prints no warning at all, so verify the signatures at least once with + * full dependencies. + * Defaults to {@code false}. + * @since 3.0 + */ + @Input + public boolean getIgnoreSignaturesOfMissingClasses() { + return data.ignoreSignaturesOfMissingClasses; + } + + /** @see #getFailOnUnresolvableSignatures */ + public void setIgnoreSignaturesOfMissingClasses(boolean ignoreSignaturesOfMissingClasses) { + data.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses; + } + /** * {@inheritDoc} *

@@ -492,7 +519,12 @@ public void info(String msg) { final EnumSet options = EnumSet.noneOf(Checker.Option.class); if (getFailOnMissingClasses()) options.add(FAIL_ON_MISSING_CLASSES); if (!getIgnoreFailures()) options.add(FAIL_ON_VIOLATION); - if (getFailOnUnresolvableSignatures()) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + if (getFailOnUnresolvableSignatures()) { + options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + } else { + log.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES); + } + if (getIgnoreSignaturesOfMissingClasses()) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES); if (getDisableClassloadingCache()) options.add(DISABLE_CLASSLOADING_CACHE); final Checker checker = new Checker(log, loader, options); @@ -552,7 +584,7 @@ public void info(String msg) { } if (checker.hasNoSignatures()) { - if (options.contains(FAIL_ON_UNRESOLVABLE_SIGNATURES)) { + if (checker.noSignaturesFilesParsed()) { throw new InvalidUserDataException("No API signatures found; use properties 'signatures', 'bundledSignatures', 'signaturesURLs', and/or 'signaturesFiles' to define those!"); } else { log.info("Skipping execution because no API signatures are available."); diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java index 6fe4b9f7..775fbe86 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApisExtension.java @@ -46,6 +46,7 @@ public CheckForbiddenApisExtension(Project project) { failOnMissingClasses = true, failOnUnresolvableSignatures = true, ignoreFailures = false, + ignoreSignaturesOfMissingClasses = false, disableClassloadingCache = ForbiddenApisPlugin.DEFAULT_DISABLE_CLASSLOADING_CACHE; } diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java index 611dd742..5a15adb3 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java +++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java @@ -130,12 +130,33 @@ public abstract class AbstractCheckMojo extends AbstractMojo implements Constant /** * 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. + * to false, then such signatures are ignored. Defaults to {@code true}. + *

When disabling this setting, the task still prints a warning to inform the user about + * broken signatures. This cannot be disabled. There is a second setting + * {@link #ignoreSignaturesOfMissingClasses} that can be used to silently ignore + * signatures that refer to methods or field in classes that are not on classpath, + * e.g. This is useful in multi-module Maven builds where a common set of signatures is used, + * that are not part of every sub-modules dependencies. + * @see #ignoreSignaturesOfMissingClasses) + * @deprecated The setting 'failOnUnresolvableSignatures' was deprecated and will be removed in next version. Use 'ignoreSignaturesOfMissingClasses' instead. * @since 1.4 */ + @Deprecated @Parameter(required = false, defaultValue = "true") private boolean failOnUnresolvableSignatures; + + /** + * If a class is missing while parsing signatures files, all methods and fields from this + * class 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. + * This settings prints no warning at all, so verify the signatures at least once with + * full dependencies. + * Defaults to {@code false}. + * @since 3.0 + */ + @Parameter(required = false, defaultValue = "false") + private boolean ignoreSignaturesOfMissingClasses; + /** * Fail the build if violations have been found. Defaults to {@code true}. @@ -311,7 +332,12 @@ public void info(String msg) { final EnumSet options = EnumSet.noneOf(Checker.Option.class); if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES); if (failOnViolation) options.add(FAIL_ON_VIOLATION); - if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + if (failOnUnresolvableSignatures) { + options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES); + } else { + // no warning needed, Maven does this automatically based on @deprecated annotation + } + if (ignoreSignaturesOfMissingClasses) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES); if (disableClassloadingCache) options.add(DISABLE_CLASSLOADING_CACHE); final Checker checker = new Checker(log, loader, options); @@ -411,7 +437,7 @@ public void info(String msg) { } if (checker.hasNoSignatures()) { - if (failOnUnresolvableSignatures) { + if (checker.noSignaturesFilesParsed()) { throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', 'signaturesFiles', and/or 'signaturesArtifacts' to define those!"); } else { log.info("Skipping execution because no API signatures are available."); diff --git a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java index 7b031ca5..1b0dbca2 100644 --- a/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java +++ b/src/test/java/de/thetaphi/forbiddenapis/CheckerSetupTest.java @@ -46,6 +46,7 @@ public void testEmpty() { assertEquals(Collections.emptyMap(), forbiddenSignatures.signatures); assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); assertTrue(checker.hasNoSignatures()); + assertTrue(checker.noSignaturesFilesParsed()); } @Test @@ -53,6 +54,8 @@ public void testClassSignature() throws Exception { checker.parseSignaturesString("java.lang.Object @ Foobar"); assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object"), "java.lang.Object [Foobar]"), forbiddenSignatures.signatures); assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); } @Test @@ -61,6 +64,8 @@ public void testClassPatternSignature() throws Exception { assertEquals(Collections.emptyMap(), forbiddenSignatures.signatures); assertEquals(Collections.singleton(new ClassPatternRule("java.lang.**", "Foobar")), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); } @Test @@ -69,6 +74,8 @@ public void testFieldSignature() throws Exception { assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/String", "CASE_INSENSITIVE_ORDER"), "java.lang.String#CASE_INSENSITIVE_ORDER [Foobar]"), forbiddenSignatures.signatures); assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); } @Test @@ -77,6 +84,8 @@ public void testMethodSignature() throws Exception { assertEquals(Collections.singletonMap(Signatures.getKey("java/lang/Object", new Method("toString", "()Ljava/lang/String;")), "java.lang.Object#toString() [Foobar]"), forbiddenSignatures.signatures); assertEquals(Collections.emptySet(), forbiddenSignatures.classPatterns); + assertFalse(checker.hasNoSignatures()); + assertFalse(checker.noSignaturesFilesParsed()); } @Test From 770e827910fd4730a858d60b50439d56502d91af Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Wed, 22 Apr 2020 20:37:34 +0200 Subject: [PATCH 2/5] Add @ignoreMissingClasses to signatures syntax; deprecate @ignoreUnresolvable --- .../de/thetaphi/forbiddenapis/Signatures.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java index af9a1300..5c5e7e2d 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/Signatures.java +++ b/src/main/java/de/thetaphi/forbiddenapis/Signatures.java @@ -47,6 +47,7 @@ public final class Signatures implements Constants { private static final String BUNDLED_PREFIX = "@includeBundled "; private static final String DEFAULT_MESSAGE_PREFIX = "@defaultMessage "; private static final String IGNORE_UNRESOLVABLE_LINE = "@ignoreUnresolvable"; + private static final String IGNORE_MISSING_CLASSES_LINE = "@ignoreMissingClasses"; private static enum UnresolvableReporting { FAIL(true) { @@ -119,7 +120,8 @@ static String getKey(String internalClassName, Method method) { } /** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */ - private void addSignature(final String line, final String defaultMessage, final UnresolvableReporting report, final Set missingClasses) throws ParseException,IOException { + private void addSignature(final String line, final String defaultMessage, final UnresolvableReporting report, + final boolean localIgnoreMissingClasses, final Set missingClasses) throws ParseException,IOException { final String clazz, field, signature; String message = null; final Method method; @@ -175,7 +177,7 @@ private void addSignature(final String line, final String defaultMessage, final try { c = lookup.getClassFromClassLoader(clazz); } catch (ClassNotFoundException cnfe) { - if (this.ignoreSignaturesOfMissingClasses) { + if (this.ignoreSignaturesOfMissingClasses || localIgnoreMissingClasses) { return; } if (report.reportClassNotFound) { @@ -271,6 +273,7 @@ private void parseSignaturesFile(Reader reader, boolean isBundled, Set m try (final BufferedReader r = new BufferedReader(reader)) { String line, defaultMessage = null; UnresolvableReporting reporter = failOnUnresolvableSignatures ? UnresolvableReporting.FAIL : UnresolvableReporting.WARNING; + boolean localIgnoreMissingClasses = false; while ((line = r.readLine()) != null) { line = line.trim(); if (line.length() == 0 || line.startsWith("#")) @@ -283,12 +286,20 @@ private void parseSignaturesFile(Reader reader, boolean isBundled, Set m defaultMessage = line.substring(DEFAULT_MESSAGE_PREFIX.length()).trim(); if (defaultMessage.length() == 0) defaultMessage = null; } else if (line.equals(IGNORE_UNRESOLVABLE_LINE)) { - reporter = isBundled ? UnresolvableReporting.SILENT : UnresolvableReporting.WARNING; + if (isBundled) { + reporter = UnresolvableReporting.SILENT; + } else { + logger.warn(String.format(Locale.ENGLISH, "'%s' inside signatures files is deprecated, prefer using '%s' to ignore signatures where the class is missing.", + IGNORE_UNRESOLVABLE_LINE, IGNORE_MISSING_CLASSES_LINE)); + reporter = UnresolvableReporting.WARNING; + } + } else if (line.equals(IGNORE_MISSING_CLASSES_LINE)) { + localIgnoreMissingClasses = true; } else { throw new ParseException("Invalid line in signature file: " + line); } } else { - addSignature(line, defaultMessage, reporter, missingClasses); + addSignature(line, defaultMessage, reporter, localIgnoreMissingClasses, missingClasses); } } } From 4e4379ecfb100362055253008c9bd6e776a29d0d Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Wed, 22 Apr 2020 20:37:52 +0200 Subject: [PATCH 3/5] Add tests --- src/test/antunit/TestInlineSignatures.xml | 57 ++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/test/antunit/TestInlineSignatures.xml b/src/test/antunit/TestInlineSignatures.xml index 84a34b29..cd8bc703 100644 --- a/src/test/antunit/TestInlineSignatures.xml +++ b/src/test/antunit/TestInlineSignatures.xml @@ -95,8 +95,8 @@ foo.bar.ForbiddenApis#testMethod() @ should be ignored java.lang.String#forbiddenFoobarMethod() @ should be ignored java.lang.String#forbiddenFoobarField @ should be ignored - java.awt.Color @ Color is disallowed, thats not bad, because ANT has no colors... (this was just added to don't fail because of missing signatures) + @@ -109,12 +109,65 @@ foo.bar.ForbiddenApis#testMethod() @ should be ignored java.lang.String#forbiddenFoobarMethod() @ should be ignored java.lang.String#forbiddenFoobarField @ should be ignored - java.awt.Color @ Color is disallowed, thats not bad, because ANT has no colors... (this was just added to don't fail because of missing signatures) + + + + + @ignoreMissingClasses + foo.bar.ForbiddenApis#testMethod() @ should be ignored + foo.bar.ForbiddenApis#field @ should be ignored + foo.bar.ForbiddenApis @ should be ignored + + + + + + + + foo.bar.ForbiddenApis#testMethod() @ should be ignored + foo.bar.ForbiddenApis#field @ should be ignored + foo.bar.ForbiddenApis @ should be ignored + + + + + + + + + foo.bar.ForbiddenApis#testMethod() @ should be ignored + java.lang.String#forbiddenFoobarMethod() @ should not be ignored + java.lang.String#forbiddenFoobarField @ should not be ignored + + + + + + + + + + + @ignoreMissingClasses + foo.bar.ForbiddenApis#testMethod() @ should be ignored + foo.bar.ForbiddenApis#field @ should be ignored + foo.bar.ForbiddenApis @ should be ignored + + + foo.bar.ForbiddenApis2#testMethod() @ should fail + foo.bar.ForbiddenApis2#field @ should fail + foo.bar.ForbiddenApis2 @ should fail + + + + + + \ No newline at end of file From 7549a90059726ac9f4349310290cd19173d510c2 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 23 Apr 2020 19:48:43 +0200 Subject: [PATCH 4/5] Change error message to better reflect reality --- src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java | 2 +- src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java | 2 +- .../de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java | 2 +- .../java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java index 9476f201..471ea63e 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java +++ b/src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java @@ -176,7 +176,7 @@ public void info(String msg) { if (checker.hasNoSignatures()) { if (checker.noSignaturesFilesParsed()) { - throw new BuildException("No API signatures found; use signaturesFile=, , or inner text to define those!"); + throw new BuildException("No signatures were added to task; use signaturesFile=, , or inner text to define those!"); } else { log.info("Skipping execution because no API signatures are available."); return; diff --git a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java index 3bdf8da7..f4b0f0c3 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java +++ b/src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java @@ -278,7 +278,7 @@ public void run() throws ExitException { if (checker.hasNoSignatures()) { if (checker.noSignaturesFilesParsed()) { throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH, - "No API signatures found; use parameters '--%s' and/or '--%s' to specify those!", + "No API signatures given as parameters; use '--%s' and/or '--%s' to specify those!", bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt() )); } else { diff --git a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java index 6370672c..ded6554f 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java +++ b/src/main/java/de/thetaphi/forbiddenapis/gradle/CheckForbiddenApis.java @@ -585,7 +585,7 @@ public void info(String msg) { if (checker.hasNoSignatures()) { if (checker.noSignaturesFilesParsed()) { - throw new InvalidUserDataException("No API signatures found; use properties 'signatures', 'bundledSignatures', 'signaturesURLs', and/or 'signaturesFiles' to define those!"); + throw new InvalidUserDataException("No signatures were added to task; use properties 'signatures', 'bundledSignatures', 'signaturesURLs', and/or 'signaturesFiles' to define those!"); } else { log.info("Skipping execution because no API signatures are available."); return; diff --git a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java index 5a15adb3..a6cb8abc 100644 --- a/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java +++ b/src/main/java/de/thetaphi/forbiddenapis/maven/AbstractCheckMojo.java @@ -438,7 +438,7 @@ public void info(String msg) { if (checker.hasNoSignatures()) { if (checker.noSignaturesFilesParsed()) { - throw new MojoExecutionException("No API signatures found; use parameters 'signatures', 'bundledSignatures', 'signaturesFiles', and/or 'signaturesArtifacts' to define those!"); + throw new MojoExecutionException("No signatures were added to mojo; use parameters 'signatures', 'bundledSignatures', 'signaturesFiles', and/or 'signaturesArtifacts' to define those!"); } else { log.info("Skipping execution because no API signatures are available."); return; From c28a052b4dbb26001d27b14b35cd9794235b8ad6 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Thu, 23 Apr 2020 20:14:29 +0200 Subject: [PATCH 5/5] Add tests for Maven --- src/test/antunit/TestMavenMojo.xml | 24 ++++++++++++++++++++++++ src/test/antunit/pom-generator.xsl | 1 + src/test/antunit/pom-sigArtifacts.xml | 1 + 3 files changed, 26 insertions(+) diff --git a/src/test/antunit/TestMavenMojo.xml b/src/test/antunit/TestMavenMojo.xml index f572bbf4..096e1147 100644 --- a/src/test/antunit/TestMavenMojo.xml +++ b/src/test/antunit/TestMavenMojo.xml @@ -96,6 +96,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/antunit/pom-generator.xsl b/src/test/antunit/pom-generator.xsl index f1ee380a..8ced350f 100644 --- a/src/test/antunit/pom-generator.xsl +++ b/src/test/antunit/pom-generator.xsl @@ -47,6 +47,7 @@ ${version} ${antunit.failOnUnresolvableSignatures} + ${antunit.ignoreSignaturesOfMissingClasses} ${antunit.failOnViolation} jdk-unsafe diff --git a/src/test/antunit/pom-sigArtifacts.xml b/src/test/antunit/pom-sigArtifacts.xml index c87ba533..53db1c34 100644 --- a/src/test/antunit/pom-sigArtifacts.xml +++ b/src/test/antunit/pom-sigArtifacts.xml @@ -37,6 +37,7 @@ ${artifactId} ${version} jar + de/thetaphi/forbiddenapis/signatures/jdk-deprecated-${jdk.version}.txt