Javadoc task for Module A fails to generate HTML because javadoc tool errors on import of class from Module B?

Using Gradle 7.4.2.

I have a multi-module project that gets published to Maven Central, and has, in the past always “worked” where “worked” was defined as, “we know we have undefined symbols in our javadoc, but what we have is sufficient for our community.” The javadoc tool would report missing symbols, but would still happily generate html.

Near as I can tell, somewhere around JDK 11, the JDK/Javadoc team decided that undefined symbols would be a hard fail. We were relying upon the “best effort” generation of HTML, which worked well for us for many years. A hard failure means that this…

apply plugin: 'maven-publish'
apply plugin: 'signing'

task sdkDocs(type: Javadoc) {
    group = "Documentation"
    description = "Generate Javadoc"
    failOnError = false
    source = android.sourceSets.main.java.srcDirs
    exclude '**/internal/*'
    exclude '**/dk/*'
    exclude '**/build/*'
    exclude '**/androidx/*'
    classpath = project.files(android.getBootClasspath().join(File.pathSeparator))
    options.memberLevel = JavadocMemberLevel.PROTECTED
    options.addStringOption('Xmaxwarns', '1')
    options.addStringOption('Xmaxerrs', '1')
    options.links "http://developer.android.com/reference"
}

task javadocJar(type: Jar) {
    dependsOn sdkDocs
    from sdkDocs.destinationDir
    archiveClassifier = 'javadoc'
}

…fails silently. As the failOnError property causes gradle to return Success, but the underlying javadoc tool simply refused to generate any HTML.

The above snippet of gradle is applied via apply from: 'javadoc.gradle' to each module in our project.

When I set the failOnError property to true so that gradle will hard fail when javadoc hard fails, it immediately fails on the first import from a dependent module of the module that it is currently generating javadoc for.

I think the way to fix this is to somehow add the module’s classpath to the classpath property of the Javadoc task, but despite trying many different incantations, I’ve been unable to figure out how to do this, nor has Google been able to surface any examples given the inputs I’ve given Google.

The first option here How to configure classpath for javadoc task? - #2 by Lance

resulted in…

* What went wrong:
A problem occurred evaluating script.
> Could not get unknown property 'main' for SourceSet container of type org.gradle.api.internal.tasks.DefaultSourceSetContainer.

The second option…

classpath = configurations.foo

I don’t understand at all.

This option…

task copyAllDependencies(type: Copy) {
  //referring to the 'compileClasspath' configuration
  from configurations.compileClasspath
  into 'allLibs'
}

…which is described in the Gradle 7.4.1 dsl documentation, implying that configurations has a property called compiledClasspath with some opaque data attached to it (I can’t find documentation for compiledClasspath unfortunately).

Results in complete failure.

FAILURE: Build failed with an exception.

* Where:
Script 'C:\tmp\ftc_sdk\packagePublish.gradle' line: 14

* What went wrong:
A problem occurred evaluating script.
> Could not get unknown property 'compileClasspath' for configuration container of type org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer.

Which underlies one of the difficulties I have when trying to navigate gradle documentation. One sees all sorts of examples where a token just exists. Floating in space so to speak. ‘configurations’, what is that, what properties are defined on it, and what do the values of those properties look like. I’ve seen usages in example snippets of configurations.compile and I understand that compile is deprecated, that sequence of tokens fails, as does configurations.implementation

I tend to fail spectacularly when trying to navigate Gradle documentation. Am I alone?

A concrete example follows:

We have two libraries, Blocks and Hardware. Blocks depends upon RobotCore and so has a bunch of import statements for those dependencies.

When trying to generate the javadoc for the Blocks library, the javadoc tool fails on the first import statement that references RobotCore.

With failOnError = true, and nothing but android.getBootClasspath() on the classpath, when trying to build this results in…

> Task :Blocks:sdkDocs
C:\tmp\ftc_sdk\lib\Blocks\src\main\java\com\google\blocks\ftcrobotcontroller\hardware\HardwareItem.java:19: error: package com.qualcomm.robotcore.hardware does not exist
import com.qualcomm.robotcore.hardware.HardwareDevice;
                                      ^
1 error

> Task :Blocks:sdkDocs FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':Blocks:sdkDocs'.
> Javadoc generation failed. Generated Javadoc options file (useful for troubleshooting): 'C:\tmp\ftc_sdk\lib\Blocks\build\tmp\sdkDocs\javadoc.options'

With failOnError = false Gradle ignores the error(s) and reports SUCCESS, but no HTML has been generated.

Before upgrading to JDK 11, the javadoc tool was resilient to errors and would generate HTML on a best effort basis, Gradle would report SUCCESS, and we could push a module to Maven Central with at least Javadoc for the symbols that were defined within that module.

Now, I’m at a bit of a loss as to how to generate the Javadoc at all within the context of Gradle’s support for generating Javadoc.

configurations.foo is the same as configurations.getByName('foo')

See the ConfigurationContainer javadoc

The configurations in a container are accessible as read-only properties of the container, using the name of the configuration as the property name. For example:

configurations.create(‘myConfiguration’) configurations.myConfiguration.transitive = false

A dynamic method is added for each configuration which takes a configuration closure. This is equivalent to calling getByName(String, groovy.lang.Closure). For example:

configurations.create(‘myConfiguration’) configurations.myConfiguration { transitive = false }