How do I interpret Javadoc for Gradle tasks?

I’m using the forbidden-apis plugin. Starting from a config like this:

allprojects {
  //...

  forbiddenApis {
    bundledSignatures = [
      "commons-io-unsafe-2.4",
      'jdk-unsafe-1.8',
      'jdk-deprecated-1.8'
    ]

    signaturesFiles = files("${rootDir}/more-forbidden-signatures.txt")
  }
}

The signatures file contains references to things which can often be in third-party libraries:

@defaultMessage We're using our own NumberTool which formats Arabic digits correctly.
org.apache.velocity.tools.generic.NumberTool

But Velocity is not on the classpath of every subproject, nor should it be. forbiddenApis uses the corresponding compile classpath to resolve things, though, so when I run the build, I get:

> Parsing signatures failed: Class 'org.apache.velocity.tools.generic.NumberTool' not found on classpath while parsing signature: org.apache.velocity.tools.generic.NumberTool

Fair enough. I know I can ignore such resolution failures, but I would rather not do that. So I figure I want to update the classpath. Now, unfortunately the docs for this extension are lacking in this regard, so I dug my way through to the Javadoc for the task, where I see:

void setClasspath(org.gradle.api.file.FileCollection classpath)

Suggesting that there is an undocumented property called ‘classpath’ which I might be able to use.

So I tried to modify my config to include everything that is used at runtime for the integration subproject, since that contains all dependencies of all other projects:

allprojects {
  //...

  forbiddenApis {
    bundledSignatures = [
      "commons-io-unsafe-2.4",
      'jdk-unsafe-1.8',
      'jdk-deprecated-1.8'
    ]

    signaturesFiles = files("${rootDir}/more-forbidden-signatures.txt")

    classpath = project(':integration').sourceSets.test.runtimeClasspath
  }
}

But that just gives:

> No such property: classpath for class: de.thetaphi.forbiddenapis.gradle.CheckForbiddenApisExtension_Decorated

So I don’t get it. If I look at things like signaturesFiles, I do see a setSignaturesFiles method on the Javadoc. All the other properties we’re setting also have corresponding setters on the API docs. However even though I see setClasspath on the Javadoc, Gradle can’t find classpath. I already checked the history of the class as well, and yes, setClasspath was there from the initial commit. It isn’t just that they added it in a later version than what we’re using.

So how exactly can we figure out how the Javadoc for a task corresponds to the properties we’re actually allowed to call?

The forbiddenApi { } closure delegates to a CheckForbiddenApisExtension. The setClasspath() method you are looking for lives on the CheckForbiddenApis task. The plugin looks to create a task per source set so you’ll probably want to configure all of them.

allprojects {
    tasks.withType(de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis) {
        classpath = project(':integration').sourceSets.test.runtimeClasspath
    }
}

Awesome, that works. Although I’m still not entirely sure how…

While I was poking around printing out the classnames of everything to try and find some way to get at the actual task object, I found that tasks showed the _Decorared thing on the end of their names as well.

This one too… is some kind of magic collection, is it? Where the closure somehow gets called on every task despite nothing here ever looking like it’s looping. This stuff is all so confusing. :confused: Sometimes a little more explicitness can make a system that much more understandable.

Most Gradle types are decorated at runtime with some additional capabilities, thus the “_Decorated” when you inspect the class. This is really just an implementation detail for the most part.

That’s basically exactly what’s happening. The passed closure is being called on every task of that type.

https://docs.gradle.org/current/javadoc/org/gradle/api/DomainObjectCollection.html#withType(java.lang.Class, groovy.lang.Closure)