Skip to content

Commit fb656e1

Browse files
authored
Allow to silently ignore signatures if the class is not found (#164)
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
1 parent 5dda144 commit fb656e1

14 files changed

+279
-33
lines changed

src/main/docs/ant-task.html

+20-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ <h2>Parameters</h2>
113113
<td>failOnUnresolvableSignatures</td>
114114
<td><code>boolean</code></td>
115115
<td><code>true</code></td>
116-
<td>Fail the build if a signature is not resolving. If this parameter is set to <code>false</code>, then such signatures are silently ignored.</td>
116+
<td>Fail the build if a signature is not resolving. If this parameter is set to
117+
to false, then such signatures are ignored.<br>
118+
When disabling this setting, the task still prints a warning to inform the user about
119+
broken signatures. This cannot be disabled. There is a second setting
120+
<code>ignoreSignaturesOfMissingClasses</code> that can be used to silently ignore
121+
signatures that refer to methods or field in classes that are not on classpath,
122+
e.g. This is useful in multi-module builds where a common set of signatures is used,
123+
that are not part of every sub-modules dependencies.<br>
124+
<strong>Deprecated.</strong> Use <code>ignoreSignaturesOfMissingClasses</code> instead.</td>
117125
</tr>
118126

119127
<tr>
@@ -137,6 +145,17 @@ <h2>Parameters</h2>
137145
<td>Ignore empty fileset/resource collection and print a warning instead.</td>
138146
</tr>
139147

148+
<tr>
149+
<td>ignoreSignaturesOfMissingClasses</td>
150+
<td><code>boolean</code></td>
151+
<td><code>false</code></td>
152+
<td>If a class is missing while parsing signatures files, all methods and fields from this
153+
class are silently ignored. This is useful in multi-module
154+
projects where only some modules have the dependency to which the signature file(s) apply.
155+
This settings prints no warning at all, so verify the signatures at least once with
156+
full dependencies.</td>
157+
</tr>
158+
140159
<tr>
141160
<td>suppressAnnotation</td>
142161
<td><code>class name</code></td>

src/main/java/de/thetaphi/forbiddenapis/Checker.java

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public static enum Option {
5454
FAIL_ON_MISSING_CLASSES,
5555
FAIL_ON_VIOLATION,
5656
FAIL_ON_UNRESOLVABLE_SIGNATURES,
57+
IGNORE_SIGNATURES_OF_MISSING_CLASSES,
5758
DISABLE_CLASSLOADING_CACHE
5859
}
5960

@@ -341,6 +342,11 @@ public boolean hasNoSignatures() {
341342
return forbiddenSignatures.hasNoSignatures();
342343
}
343344

345+
/** Returns if no signatures files / inline signatures were parsed */
346+
public boolean noSignaturesFilesParsed() {
347+
return forbiddenSignatures.noSignaturesFilesParsed();
348+
}
349+
344350
/** 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. */
345351
public void addClassToCheck(final InputStream in, String name) throws IOException {
346352
final ClassReader reader;

src/main/java/de/thetaphi/forbiddenapis/Constants.java

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public interface Constants {
2828

2929
final Pattern JDK_SIG_PATTERN = Pattern.compile("(jdk\\-.*?\\-)(\\d+)(\\.\\d+)?(\\.\\d+)*");
3030

31+
final String DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES =
32+
"The setting 'failOnUnresolvableSignatures' was deprecated and will be removed in next version. Use 'ignoreSignaturesOfMissingClasses' instead.";
33+
3134
final Type DEPRECATED_TYPE = Type.getType(Deprecated.class);
3235
final String DEPRECATED_DESCRIPTOR = DEPRECATED_TYPE.getDescriptor();
3336

src/main/java/de/thetaphi/forbiddenapis/Signatures.java

+33-8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class Signatures implements Constants {
4747
private static final String BUNDLED_PREFIX = "@includeBundled ";
4848
private static final String DEFAULT_MESSAGE_PREFIX = "@defaultMessage ";
4949
private static final String IGNORE_UNRESOLVABLE_LINE = "@ignoreUnresolvable";
50+
private static final String IGNORE_MISSING_CLASSES_LINE = "@ignoreMissingClasses";
5051

5152
private static enum UnresolvableReporting {
5253
FAIL(true) {
@@ -78,7 +79,7 @@ private UnresolvableReporting(boolean reportClassNotFound) {
7879

7980
private final RelatedClassLookup lookup;
8081
private final Logger logger;
81-
private final boolean failOnUnresolvableSignatures;
82+
private final boolean failOnUnresolvableSignatures, ignoreSignaturesOfMissingClasses;
8283

8384
/** Key is used to lookup forbidden signature in following formats. Keys are generated by the corresponding
8485
* {@link #getKey(String)} (classes), {@link #getKey(String, Method)} (methods),
@@ -91,14 +92,18 @@ private UnresolvableReporting(boolean reportClassNotFound) {
9192

9293
/** if enabled, the bundled signature to enable heuristics for detection of non-portable runtime calls is used */
9394
private boolean forbidNonPortableRuntime = false;
95+
96+
/** number of files that were interpreted as signatures file. If 0, no (bundled) signatures files were added at all */
97+
private int numberOfFiles = 0;
9498

9599
public Signatures(Checker checker) {
96-
this(checker, checker.logger, checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
100+
this(checker, checker.logger, checker.options.contains(Option.IGNORE_SIGNATURES_OF_MISSING_CLASSES), checker.options.contains(Option.FAIL_ON_UNRESOLVABLE_SIGNATURES));
97101
}
98102

99-
public Signatures(RelatedClassLookup lookup, Logger logger, boolean failOnUnresolvableSignatures) {
103+
public Signatures(RelatedClassLookup lookup, Logger logger, boolean ignoreSignaturesOfMissingClasses, boolean failOnUnresolvableSignatures) {
100104
this.lookup = lookup;
101105
this.logger = logger;
106+
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
102107
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
103108
}
104109

@@ -115,7 +120,8 @@ static String getKey(String internalClassName, Method method) {
115120
}
116121

117122
/** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */
118-
private void addSignature(final String line, final String defaultMessage, final UnresolvableReporting report, final Set<String> missingClasses) throws ParseException,IOException {
123+
private void addSignature(final String line, final String defaultMessage, final UnresolvableReporting report,
124+
final boolean localIgnoreMissingClasses, final Set<String> missingClasses) throws ParseException,IOException {
119125
final String clazz, field, signature;
120126
String message = null;
121127
final Method method;
@@ -171,6 +177,9 @@ private void addSignature(final String line, final String defaultMessage, final
171177
try {
172178
c = lookup.getClassFromClassLoader(clazz);
173179
} catch (ClassNotFoundException cnfe) {
180+
if (this.ignoreSignaturesOfMissingClasses || localIgnoreMissingClasses) {
181+
return;
182+
}
174183
if (report.reportClassNotFound) {
175184
report.parseFailed(logger, String.format(Locale.ENGLISH, "Class '%s' not found on classpath", cnfe.getMessage()), signature);
176185
} else {
@@ -235,6 +244,7 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean
235244
}
236245
if (BS_JDK_NONPORTABLE.equals(name)) {
237246
if (logging) logger.info("Reading bundled API signatures: " + name);
247+
numberOfFiles++;
238248
forbidNonPortableRuntime = true;
239249
return;
240250
}
@@ -254,14 +264,16 @@ private void addBundledSignatures(String name, String jdkTargetVersion, boolean
254264
parseSignaturesStream(in, true, missingClasses);
255265
}
256266

257-
private void parseSignaturesStream(InputStream in, boolean allowBundled, Set<String> missingClasses) throws IOException,ParseException {
258-
parseSignaturesFile(new InputStreamReader(in, StandardCharsets.UTF_8), allowBundled, missingClasses);
267+
private void parseSignaturesStream(InputStream in, boolean isBundled, Set<String> missingClasses) throws IOException,ParseException {
268+
parseSignaturesFile(new InputStreamReader(in, StandardCharsets.UTF_8), isBundled, missingClasses);
259269
}
260270

261271
private void parseSignaturesFile(Reader reader, boolean isBundled, Set<String> missingClasses) throws IOException,ParseException {
272+
numberOfFiles++;
262273
try (final BufferedReader r = new BufferedReader(reader)) {
263274
String line, defaultMessage = null;
264275
UnresolvableReporting reporter = failOnUnresolvableSignatures ? UnresolvableReporting.FAIL : UnresolvableReporting.WARNING;
276+
boolean localIgnoreMissingClasses = false;
265277
while ((line = r.readLine()) != null) {
266278
line = line.trim();
267279
if (line.length() == 0 || line.startsWith("#"))
@@ -274,12 +286,20 @@ private void parseSignaturesFile(Reader reader, boolean isBundled, Set<String> m
274286
defaultMessage = line.substring(DEFAULT_MESSAGE_PREFIX.length()).trim();
275287
if (defaultMessage.length() == 0) defaultMessage = null;
276288
} else if (line.equals(IGNORE_UNRESOLVABLE_LINE)) {
277-
reporter = isBundled ? UnresolvableReporting.SILENT : UnresolvableReporting.WARNING;
289+
if (isBundled) {
290+
reporter = UnresolvableReporting.SILENT;
291+
} else {
292+
logger.warn(String.format(Locale.ENGLISH, "'%s' inside signatures files is deprecated, prefer using '%s' to ignore signatures where the class is missing.",
293+
IGNORE_UNRESOLVABLE_LINE, IGNORE_MISSING_CLASSES_LINE));
294+
reporter = UnresolvableReporting.WARNING;
295+
}
296+
} else if (line.equals(IGNORE_MISSING_CLASSES_LINE)) {
297+
localIgnoreMissingClasses = true;
278298
} else {
279299
throw new ParseException("Invalid line in signature file: " + line);
280300
}
281301
} else {
282-
addSignature(line, defaultMessage, reporter, missingClasses);
302+
addSignature(line, defaultMessage, reporter, localIgnoreMissingClasses, missingClasses);
283303
}
284304
}
285305
}
@@ -315,6 +335,11 @@ public boolean hasNoSignatures() {
315335
(forbidNonPortableRuntime ? 1 : 0);
316336
}
317337

338+
/** Returns if no signatures files / inline signatures were parsed */
339+
public boolean noSignaturesFilesParsed() {
340+
return numberOfFiles == 0;
341+
}
342+
318343
/** Returns if bundled signature to enable heuristics for detection of non-portable runtime calls is used */
319344
public boolean isNonPortableRuntimeForbidden() {
320345
return this.forbidNonPortableRuntime;

src/main/java/de/thetaphi/forbiddenapis/ant/AntTask.java

+36-4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public class AntTask extends Task implements Constants {
6767
private boolean restrictClassFilename = true;
6868
private boolean failOnMissingClasses = true;
6969
private boolean failOnUnresolvableSignatures = true;
70+
private boolean ignoreSignaturesOfMissingClasses = false;
7071
private boolean failOnViolation = true;
7172
private boolean ignoreEmptyFileset = false;
7273
private String targetVersion = null;
@@ -108,7 +109,12 @@ public void info(String msg) {
108109
final EnumSet<Checker.Option> options = EnumSet.noneOf(Checker.Option.class);
109110
if (failOnMissingClasses) options.add(FAIL_ON_MISSING_CLASSES);
110111
if (failOnViolation) options.add(FAIL_ON_VIOLATION);
111-
if (failOnUnresolvableSignatures) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
112+
if (failOnUnresolvableSignatures) {
113+
options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
114+
} else {
115+
log.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES);
116+
}
117+
if (ignoreSignaturesOfMissingClasses) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES);
112118
if (disableClassloadingCache) options.add(DISABLE_CLASSLOADING_CACHE);
113119
final Checker checker = new Checker(log, loader, options);
114120

@@ -169,7 +175,12 @@ public void info(String msg) {
169175
}
170176

171177
if (checker.hasNoSignatures()) {
172-
throw new BuildException("No API signatures found; use signaturesFile=, <signatures*/>, <bundledSignatures/> or inner text to define those!");
178+
if (checker.noSignaturesFilesParsed()) {
179+
throw new BuildException("No signatures were added to task; use signaturesFile=, <signatures*/>, <bundledSignatures/> or inner text to define those!");
180+
} else {
181+
log.info("Skipping execution because no API signatures are available.");
182+
return;
183+
}
173184
}
174185

175186
log.info("Loading classes to check...");
@@ -325,13 +336,34 @@ public void setFailOnMissingClasses(boolean failOnMissingClasses) {
325336

326337
/**
327338
* Fail the build if a signature is not resolving. If this parameter is set to
328-
* to false, then such signatures are silently ignored.
329-
* Defaults to {@code true}.
339+
* to false, then such signatures are ignored. Defaults to {@code true}.
340+
* <p>When disabling this setting, the task still prints a warning to inform the user about
341+
* broken signatures. This cannot be disabled. There is a second setting
342+
* {@link #setIgnoreMissingSignaturesClasses()} that can be used to silently ignore
343+
* signatures that refer to methods or field in classes that are not on classpath,
344+
* e.g. This is useful in multi-module builds where a common set of signatures is used,
345+
* that are not part of every sub-modules dependencies.
346+
* @see #setIgnoreMissingSignaturesClasses()
347+
* @deprecated Use {@link #setIgnoreSignaturesOfMissingClasses(boolean)} instead.
330348
*/
349+
@Deprecated
331350
public void setFailOnUnresolvableSignatures(boolean failOnUnresolvableSignatures) {
332351
this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
333352
}
334353

354+
/**
355+
* If a class is missing while parsing signatures files, all methods and fields from this
356+
* class are silently ignored. This is useful in multi-module
357+
* projects where only some modules have the dependency to which the signature file(s) apply.
358+
* This settings prints no warning at all, so verify the signatures at least once with
359+
* full dependencies.
360+
* Defaults to {@code false}.
361+
* @since 3.0
362+
*/
363+
public void setIgnoreSignaturesOfMissingClasses(boolean ignoreSignaturesOfMissingClasses) {
364+
this.ignoreSignaturesOfMissingClasses = ignoreSignaturesOfMissingClasses;
365+
}
366+
335367
/** Automatically restrict resource names included to files with a name ending in '.class'.
336368
* This makes filesets easier, as the includes="**&#47;*.class" is not needed.
337369
* Defaults to {@code true}.

src/main/java/de/thetaphi/forbiddenapis/cli/CliMain.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
public final class CliMain implements Constants {
5454

5555
private final Option classpathOpt, dirOpt, includesOpt, excludesOpt, signaturesfileOpt, bundledsignaturesOpt, suppressannotationsOpt,
56-
allowmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt;
56+
allowmissingclassesOpt, ignoresignaturesofmissingclassesOpt, allowunresolvablesignaturesOpt, versionOpt, helpOpt;
5757
private final CommandLine cmd;
5858

5959
private static final Logger LOG = StdIoLogger.INSTANCE;
@@ -129,8 +129,12 @@ public CliMain(String... args) throws ExitException {
129129
.desc("don't fail if a referenced class is missing on classpath")
130130
.longOpt("allowmissingclasses")
131131
.build());
132+
options.addOption(ignoresignaturesofmissingclassesOpt = Option.builder()
133+
.desc("if a class is missing while parsing signatures files, all methods and fields from this class are silently ignored")
134+
.longOpt("ignoresignaturesofmissingclasses")
135+
.build());
132136
options.addOption(allowunresolvablesignaturesOpt = Option.builder()
133-
.desc("don't fail if a signature is not resolving")
137+
.desc("DEPRECATED: don't fail if a signature is not resolving")
134138
.longOpt("allowunresolvablesignatures")
135139
.build());
136140

@@ -212,7 +216,12 @@ public void run() throws ExitException {
212216
try (final URLClassLoader loader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader())) {
213217
final EnumSet<Checker.Option> options = EnumSet.of(FAIL_ON_VIOLATION);
214218
if (!cmd.hasOption(allowmissingclassesOpt.getLongOpt())) options.add(FAIL_ON_MISSING_CLASSES);
215-
if (!cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
219+
if (cmd.hasOption(allowunresolvablesignaturesOpt.getLongOpt())) {
220+
LOG.warn(DEPRECATED_WARN_FAIL_ON_UNRESOLVABLE_SIGNATURES);
221+
} else {
222+
options.add(FAIL_ON_UNRESOLVABLE_SIGNATURES);
223+
}
224+
if (cmd.hasOption(ignoresignaturesofmissingclassesOpt.getLongOpt())) options.add(IGNORE_SIGNATURES_OF_MISSING_CLASSES);
216225
final Checker checker = new Checker(LOG, loader, options);
217226

218227
if (!checker.isSupportedJDK) {
@@ -267,10 +276,15 @@ public void run() throws ExitException {
267276
}
268277

269278
if (checker.hasNoSignatures()) {
270-
throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH,
271-
"No API signatures found; use parameters '--%s' and/or '--%s' to specify those!",
272-
bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt()
273-
));
279+
if (checker.noSignaturesFilesParsed()) {
280+
throw new ExitException(EXIT_ERR_CMDLINE, String.format(Locale.ENGLISH,
281+
"No API signatures given as parameters; use '--%s' and/or '--%s' to specify those!",
282+
bundledsignaturesOpt.getLongOpt(), signaturesfileOpt.getLongOpt()
283+
));
284+
} else {
285+
LOG.info("Skipping execution because no API signatures are available.");
286+
return;
287+
}
274288
}
275289

276290
try {

0 commit comments

Comments
 (0)