Aggregate Javadoc for Android project with multiple library modules - android

I've gone through almost entire Internet in search of a way how to aggregate Javadocs in the project consisting of separate library modules into single Javadoc.
There is a plugin that apparently allows to do that:
https://github.com/nebula-plugins/gradle-aggregate-javadocs-plugin
However, if I run the command specified by the plugin Gradle finds the task and executes it but no output directory is generated.
Any help how to build single Javadoc from multiple modules much appreciated.

I managed to get it working some time ago, apologies for a late response. The solution for aggregatable Javadoc creation is the following:
In each of the subprojects maintained within the project create a task generating the Javadoc:
android.libraryVariants.all { variant ->
task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
destinationDir = project.file("$project.projectDir/javadoc/$project.PROJECT_NAME") //Project name in the Project's gradle.properties
title = "A title of my project - $project.PROJECT_VERSION_NAME" //Project version name in the Project's gradle.properties
description "Generates Javadoc for $variant.name."
source = variant.javaCompile.source
ext.androidJar ="${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) + project.files(android.getBootClasspath().join(File.pathSeparator))
options {
memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PUBLIC //change the modifier according to your needs
links "http://docs.oracle.com/javase/8/docs/api/"
linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"
}
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
task("bundle${variant.name.capitalize()}Javadoc", type: Jar) {
baseName = "Compass API - ($version)"
description "Bundles Javadoc into zip for $variant.name."
classifier = "javadoc"
from tasks["generate${variant.name.capitalize()}Javadoc"]
}
}
The configuration above adds a Javadoc generation task for each buildVariant of your subproject. At this point you can you can generate Javadoc for each module separately by typing
gradle :myRootProject:mySubproject:generateDebugJavadoc
gradle :myRootProject:mySubproject:generateReleaseJavadoc
gradle :myRootProject:mySubproject:generateMyFancyFlavourDebugJavadoc
gradle :myRootProject:mySubproject:generateMyFancyFlavourReleaseJavadoc
If you use JRE 8 the following configuration disables errors raised by doclint during the Javadoc build (explanation in greater detail here)
if (JavaVersion.current().isJava8Compatible()) {
tasks.withType(Javadoc) {
// disable the crazy super-strict doclint tool in Java 8
//noinspection SpellCheckingInspection
options.addStringOption('Xdoclint:none', '-quiet')
}
}
To aggregate Javadocs of each submodules into a single one create a Plugin in to build.gradle which will add a task to the submodule a partial Javadoc generation of which you are interested in:
class JavadocAggregationPlugin implements Plugin<Project> {
static final String AGGREGATE_JAVADOCS_TASK_NAME = 'aggregateJavadocs'
#Override
void apply(Project project) {
Project rootProject = project.rootProject
rootProject.gradle.projectsEvaluated {
Set<Project> librarySubprojects = getLibraryProjects(rootProject)
if (!librarySubprojects.isEmpty()) {
rootProject.task(AGGREGATE_JAVADOCS_TASK_NAME, type: Javadoc) {
description = 'Aggregates Javadoc API documentation of all subprojects.'
group = JavaBasePlugin.DOCUMENTATION_GROUP
dependsOn librarySubprojects.generateReleaseJavadoc //please note that generateReleaseJavadoc is the name of the separate Javadoc generation task in each library module
source librarySubprojects.generateReleaseJavadoc.source
destinationDir rootProject.file("$rootProject.buildDir/docs/javadoc") //Javadoc destination directory
classpath = rootProject.files(librarySubprojects.generateReleaseJavadoc.classpath)
}
}
}
}
private Set<Project> getLibraryProjects(Project rootProject) {
rootProject.subprojects.findAll { subproject -> subproject.plugins.findPlugin("com.android.library") } //In this case every library module is selected
}
}
Finally, include your plugin to the gradle configuration in the Project's build.gradle below your plugin definition.
apply plugin: JavadocAggregationPlugin
By doing this and rebuilding gradle's configuration you should be able to create aggregated Javadoc in specified directory by typing the following command via cli:
gradle aggregateJavadocs
Hope that helps somehow.
Helpful link: Android Gradle DSL

Related

How do I publish an AAR to Maven Local With JavaDocs

I need to publish my android library (aar) using Gradle to Maven local repo.
But the publication script needs to also generate the Javadocs, while ONLY including Public and Protected methods and classes.
Can't seem to find any information online, especially about the Javadocs part...
Help, I never published a library before.
Ok, after much research I found a solution, so I'm going to share it here if anyone will need this. (I don't want you to be frustrated like I was).
1) Create an android library as a new module inside your project.
2) Inside the build gradle of your library place this code:
plugins {
id 'com.android.library'
id 'maven-publish'
}
android {
nothing special here...
}
This is the code for creating the Javadocs(still inside build.gradle):
task androidJavadocs(type: Javadoc){
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
android.libraryVariants.all{ variant->
if (variant.name == 'release'){
owner.classpath += variant.javaCompileProvider.get().classpath
}
}
// excluding a specific class from being documented
exclude '**/NameOfClassToExclude.java'
title = null
options{
doclet = "com.google.doclava.Doclava"
docletpath = [file("libs/doclava-1.0.6.jar")]
noTimestamp = false
// show only Protected & Public
memberLevel = JavadocMemberLevel.PROTECTED
}
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs){
archiveClassifier.set('javadoc')
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar){
archiveClassifier.set('sources')
from android.sourceSets.main.java.srcDirs
}
This is to publish the library to MavenLocal(still inside build.gradle):
afterEvaluate {
publishing{
publications{
release(MavenPublication){
groupId = "com.example.mylibrary"
artifactId = "mycoollibrary"
version = "1.0"
// Applies the component for the release build variant
from components.release
// Adds javadocs and sources as separate jars.
artifact androidSourcesJar
artifact androidJavadocsJar
}
}
}
}
Your default dependencies block:
dependencies {
your dependencies...
}
3) Now you can download the doclava doclet:
Extract the zip, copy the doclava-1.0.6.jar and paste it into your LibraryName/libs folder (can be found using the project view).
You only need doclava if you want to be able to use #hide.
With this annotation, you can exclude specific methods from your Javadocs.
4) Build and publish your library:
Find the gradle tab at the top right side of android studio, or find it from the toolbar View->Tool Windows->Gradle.
Now find your library -> tasks -> publishing -> publishReleasePublicationToMavenLocal.
5) To consume the library from another project:
Go to the settings.gradle file (of the consuming project) and add MavenLocal() as the first repository in the the dependencyResolutionManagement block.
And inside the module build gradle add your library as a dependency:
dependencies{
implementation 'com.example.mylibrary:mycoollibrary:1.0'
}

Android AAR depending on AAR fails with javadoc generation

I have an android gradle project structure that looks like this
module1-aar
module2-aar
testapp-apk
Key facts
module2-aar depends on module1-aar
testapp-apk depends on module2-aar
JDK11
Gradle 7.4.2
Android gradle plugin 7.1.3
Without javadocs, gpg, signing, or publishing, everything builds just fine. App runs, everything is great.
When i started adding in tasks to generate javadocs, that's when everything went haywire. module1-aar will build and generate javadocs with no problem. module2-aar however always fails during the javadoc task.
Task is below. Most of it was borrowed from here How to generate javadoc for android library when it has dependencies which are also aar libraries?
project.task("javadoc", type: Javadoc) {
afterEvaluate {
configurations.all
.each {item ->
item.setCanBeResolved(true)
}
classpath += configurations.api
classpath += configurations.implementation
// Wait after evaluation to add the android classpath
// to avoid "buildToolsVersion is not specified" error
classpath += files(android.getBootClasspath())
// Process AAR dependencies
def aarDependencies = classpath.filter { it.name.endsWith('.aar') }
classpath -= aarDependencies
//fails here when an AAR depends on an AAR
aarDependencies.each { aar ->
// Extract classes.jar from the AAR dependency, and add it to the javadoc classpath
def outputPath = "$buildDir/tmp/aarJar/${aar.name.replace('.aar', '.jar')}"
classpath += files(outputPath)
// Use a task so the actual extraction only happens before the javadoc task is run
dependsOn task(name: "extract ${aar.name}").doLast {
extractEntry(aar, 'classes.jar', outputPath)
}
}
}
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += project.files(android.getBootClasspath())
classpath += configurations.implementation
classpath += fileTree(dir: project.buildDir.absolutePath + "/tmp/aarsToJars/")
classpath += files(project.buildDir.absolutePath + "/intermediates/compile_r_class_jar/release/R.jar")
classpath += files(project.buildDir.absolutePath + "/generated/source/buildConfig/release/release")
classpath += files(project.buildDir.absolutePath + "/generated/source/r/buildConfig/release/release")
destinationDir = file( project.buildDir.absolutePath + "/outputs/javadoc/")
failOnError true
options.charSet 'UTF-8'
options.docEncoding 'UTF-8'
options.encoding 'UTF-8'
options.addBooleanOption 'Xdoclint:none', true
exclude '**/BuildConfig.java'
exclude '**/R.java'
exclude '**/doc-files/*'
}
// Utility method to extract only one entry in a zip file
private def extractEntry(archive, entryPath, outputPath) {
if (!archive.exists()) {
throw new GradleException("archive $archive not found")
}
def zip = new java.util.zip.ZipFile(archive)
zip.entries().each {
if (it.name == entryPath) {
def path = new File(outputPath)
if (!path.exists()) {
path.getParentFile().mkdirs()
// Surely there's a simpler is->os utility except
// the one in java.nio.Files? Ah well...
def buf = new byte[1024]
def is = zip.getInputStream(it)
def os = new FileOutputStream(path)
def len
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len)
}
os.close()
}
}
}
zip.close()
}
//wires in the javadoc task to the normal build
tasks.named("build") { finalizedBy("generateJavadocJar") }
The error message i'm getting is the following
* What went wrong:
A problem occurred configuring project ':module2-aar'.
> Could not resolve all files for configuration ':module2-aar:implementation'.
> Could not resolve project :module1-aar.
Required by:
project :module2-aar
> Cannot choose between the following variants of project :module1-aar:
- debugRuntimeElements
- releaseRuntimeElements
All of them match the consumer attributes:
- Variant 'debugRuntimeElements' capability com.github.test:module1-aar:6.1.11-SNAPSHOT:
- Unmatched attributes:
- Provides com.android.build.api.attributes.AgpVersionAttr '7.1.3' but the consumer didn't ask for it
- Provides com.android.build.api.attributes.BuildTypeAttr 'debug' but the consumer didn't ask for it
- Provides com.android.build.gradle.internal.attributes.VariantAttr 'debug' but the consumer didn't ask for it
- Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
- Variant 'releaseRuntimeElements' capability com.github.test:module1-aar:6.1.11-SNAPSHOT:
- Unmatched attributes:
- Provides com.android.build.api.attributes.AgpVersionAttr '7.1.3' but the consumer didn't ask for it
- Provides com.android.build.api.attributes.BuildTypeAttr 'release' but the consumer didn't ask for it
- Provides com.android.build.gradle.internal.attributes.VariantAttr 'release' but the consumer didn't ask for it
- Provides org.gradle.usage 'java-runtime' but the consumer didn't ask for it
I've been playing around with the gradle task a bit and it seems that the error message is generated anytime i attempt to iterate over the classpath of the module2-aar.
I have tried a number of other suggestions, like changing module2-aar's dependency declaration from
api project(':module2-aar')
to
api project(path:':module2-aar')
However that doesn't do anything
I also tried this:
api project(path: ':module1-aar', configuration: 'default')
While the above resolves the reported issue, it causes a compile issue whereby module2-aar doesn't appear to have module1-aar in the classpath during compile...and it seems to compile before module1-aar.
Unfortunately, the documentation for what configuration means when referencing an android project is a bit thin, or perhaps I'm looking in the wrong place. I'm not sure what other valid values are available.
Anyhow, I'm not sure what's wrong here other than I've spent way too much time on this.
I going to publish my solution to the problem of using "aar" files in javadoc. In the course of trying to solve the problem, I too, had been getting the same error that spy was referring to. That actually error means it can differentiate whether it should be using release or debug libraries. It seemed to me to be too futile to try and correct that issue, so instead, I took a different approach to solving what I think is essentially the same problem.
In my case, I have a project that contains multiple subprojects, and when I produce my javadoc documentation I wanted to produce a merged javadoc document, that consisted of just some of the subprojects (not all of the subprojects). As far as I know, this is not a capability, built into Android Studio. The current version of Android Studio(2021.2.1) seems to have problems producing javadoc documentation for android library modules. There are two issues:
1.) the javadoc classpath doesn't have the android bootclasses added. You get errors for referencing any android SDK method such as "Context", "Typeface", etc.
2.) the javadoc classpath doesn't get any AndroidX libraries added to it. Many of the AndroidX libraries are "aar" files. Android Studio(2021.2.1) does not handle aar files correctly when using javadoc.
My environment is similar to spy, except that I'm using android gradle plugin 7.2.0. I've created a custom javadoc task in the "app" module's build.gradle.kts script. My "app" module is an android application module. The code needs to be place in any module that contains either the plugin "com.android.application" or "com.android.library". Some of the modules that I produce the merged javadoc for are java libraries, and that's okay.
// create a custom configuration
val javadocDeps = configurations.create("javadocDeps")
// add javadoc dependencies that you need.
dependencies {
javadocDeps(project(":OsgiFramework"))
// note: I'm using a libs version catalog for the dependencies
// you can add hardwired dependencies if you prefer
javadocDeps (libs.androidx.appcompat)
javadocDeps (libs.androidx.fragment)
javadocDeps (libs.androidx.navigation.fragment)
javadocDeps (libs.androidx.navigation.ui)
javadocDeps (libs.androidx.constraint.layout)
}
// register the createCoreJavadoc task
// in my case, "gradlew app:createCoreJavadoc" creates the merged javadoc
tasks {
register<Javadoc>("createCoreJavadoc") {
setFailOnError(true)
val docDir: File = File(project.projectDir.parentFile.parentFile, "Doc/Core")
println("javadoc destination dir: " + docDir.absolutePath)
// set the location where the documentation is produced in
setDestinationDir(docDir)
// select the projects to produce merged javadoc for
var sourcepaths: FileCollection = project(":CoreInterfaces").files("src/main/java")
sourcepaths =
sourcepaths.plus(project(":CoreInternalInterfaces").files("src/main/java"))
sourcepaths = sourcepaths.plus(project(":CoreAndroidInterfaces").files("src/main/java"))
sourcepaths =
sourcepaths.plus(project(":CoreAndroidInternalInterfaces").files("src/main/java"))
sourcepaths = sourcepaths.plus(project(":OsgiInterface").files("src/main/java"))
sourcepaths =
sourcepaths.plus(project(":InstallPlanInterfaces_1_0_0").files("src/main/java"))
setSource(sourcepaths.asFileTree)
// fix the problem with the missing android bootclasses
android.bootClasspath.forEach{
classpath += fileTree(it)
}
// create a temporary directory for storing the "classes.jar" file contained in the *.aar files
val tmpDir:File = File(project.buildDir, "\\tmpAar\\")
if (tmpDir.exists()) tmpDir.delete()
tmpDir.mkdirs()
// add the javadoc dependencies
javadocDeps.forEach {
// I've got a custom class that allows me to treat jar or zip files and a file system
// you could replace this using spy's zip file extraction method
val zipFileSystem: com.phinneyridge.ZipFileSystem = ZipFileSystem(it.absolutePath,null)
if (it.name.endsWith(".aar")) {
// extract the classes.jar file from the aar file to the tmpDir
// renaming it to name of the aar file, but change the extension to jar
val tmpFile:File = File(tmpDir, it.name.replace(".aar", ".jar"))
zipFileSystem.extractEntry("classes.jar", tmpFile)
} else {
// for jar files, we just add it to the path
classpath += fileTree(it)
}
}
// now add the tmpDir files to the javadoc classpath
classpath += fileTree(tmpDir)
// for diagnosis purposes, we'll print the classpath.
// Notice that you have a lot more path entries then you have javadocDeps
println("classpath: " + classpath.asPath)
}
}
I understand that you have a project structure like this: app -> module2 -> module1.
Where -> means the dependency stream. So I deployed a project with that same dependency flow, but using gradle 7.0.2 (because that's what I'm currently using), and had no problem generating javadoc for module2 and module1
Basically it boils down to implementing this in every gradle of every module: https://stackoverflow.com/a/73096187/9902249
I hope it works for you.

Unable to get transitive dependencies of my library in my Android app

I created an Android Library in Android studio, which has some external dependencies(Retrofit, for example).
But when i tried to use this Library in an Android app, The app doesn't include the transitive dependencies(the ones included in the library).
I've already tried publishing the library to Bintray, change the 'implementation' keyword in app-gradle file to 'api'.
I've also tried setting transitive = true in my app's gradle file
When trying to build the Android app, It shows Resource Linking Failed for CardView which's used in my Library.
I faced the same problem while building a Library for Android.
I was trying to directly upload the .aar file from the android studio to the bintray. But appparantly, it was not including the pom.xml file.
So I followed this tutorial to generate the zip file.
Then create a new version at bintray and then manually upload the zip file using the UI upload option
Also, do not forget to check the explode this Archive option while uploading.
Your can then, publish the library and use it with all its transitive dependencies.
Here's an example build.gradle code:
def version = 'your.version'
def localReleaseDest = "${buildDir}/release/${version}"
uploadArchives {
repositories.mavenDeployer {
pom.groupId = 'your.package.name'
pom.artifactId = 'yourModuleName'
pom.version = 'your.version'
// Add other pom properties here if you want (developer details / licenses)
repository(url: "file://${localReleaseDest}")
}
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
task zipRelease(type: Zip) {
from localReleaseDest
destinationDir buildDir
archiveName "release-${version}.zip"
}
task generateRelease {
doLast {
println "Release ${version} can be found at ${localReleaseDest}/"
println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip"
}
}
generateRelease.dependsOn(uploadArchives)
generateRelease.dependsOn(zipRelease)
put this code outside all blocks in your app level gradle after modifying it as needed.
Now, sync project and open terminal inside the Android studio, and execute this command:
./gradlew clean build generateRelease
this will generate a .zip file in your app/build directory which you can upload to bintray like described above

Obfuscated interface parameter names in gradle library

I have made a library with some interfaces (HybridMediaPlayer on github). When I import it from gradle in new project and use those interfaces I get changed parameter names such as:
player.setOnPositionDiscontinuityListener(new ExoMediaPlayer.OnPositionDiscontinuityListener() {
#Override
public void onPositionDiscontinuity(int i, int i1) {
}
});
Where the "i" is "reason" and "i1" is "currentWindowIndex".
Why it is changed in other project and how to fix that? Proguard is disabled.
I think this is because on the Maven repo only a Android Archive Library (AAR) exists and no source jar. The AAR only contains the compiled classes and in compiled classes the full variable name is not known anymore. So when your IDE implements the method it doesn't know the names anymore so it defaults to standard naming based on argument types (hence the i for integer).
If you want the correct variable names you should publish a source jar of your project as well to the Jitpack repo. This answer might provide a way to also publish a source jar next to the AAR. When a source jar is also published an IDE will use Gradle to also pull the source jar into the project and will use this when implementing methods to get argument names and such.
The solution for jitpack was adding javadoc for interfaces and this in lib gradle build file:
// build a jar with source files
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.sourceFiles
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.compile
}
// build a jar with javadoc
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocJar
}
There are two reasons why this could happen:
1. You haven't included any documentation as artifact in your AAR
If this the case then you need to add following tasks in AAR's build.gradle as follows:
task javadoc(type: Javadoc) {
description "Generates Javadoc for ${archivesBaseName}-${version} API"
group JavaBasePlugin.DOCUMENTATION_GROUP
title = "${archivesBaseName}-${version} API References"
source android.sourceSets.main.java.srcDirs, configurations.doc.collect { zipTree(it) }
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
options {
memberLevel = JavadocMemberLevel.PUBLIC
linksOffline('http://developer.android.com/reference/', "${android.sdkDirectory}/docs/reference");
}
include '<path of your public api>/*.java'
exclude '**/BuildConfig.java'
exclude '**/R.java'
exclude '**/test/**/*.java'
failOnError false
}
task androidJavadocsJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives androidJavadocsJar
}
2. You have already done step 1
If this is the case you need to inform android studio to download the javaDoc. Place following code in your target app's main build.gradle
apply plugin: 'idea'
...
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
Alternatively, you can follow this SO to do it through android studio.
Note: You need to properly document the Javadoc download in User guide of your AAR so that user of your AAR knows how to overcome the impediment

How to generate javadoc for android library when it has dependencies which are also aar libraries?

I have android library project which depends on other android library projects. I need to generate javadoc for library but it fails because gradle puts to javadoc classpath path to .aar locations but javadoc expects .jar files.
simplified gradle file:
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
configurations {
javadocDeps
}
defaultConfig {
minSdkVersion 7
targetSdkVersion 23
versionCode 1
versionName "0.1.0"
}
}
dependencies {
compile 'com.android.support:support-v4:23.2.0'
compile 'com.android.support:appcompat-v7:23.2.0'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.annimon:stream:1.0.7'
javadocDeps 'com.android.support:support-annotations:23.2.0'
javadocDeps 'com.nineoldandroids:library:2.4.0'
javadocDeps 'com.android.support:support-v4:23.2.0'
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc, dependsOn: explodeAars) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.javadocDeps
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
3 solutions possible:
1) somehow to add to the classpath path classes.jar from every aar library it depends build/intermidiates/exploded-aar/library/version/jars/classes.jar
I don't know how to include these paths in javadoc task.
2) manually unpack classes.jar from aar file and add them to classpath of javadoc task
3) very dirty hack - hardcoded paths to library - but I think this is so WRONG.
How to achieve 1 or 2 with gradle dsl?
I managed to automate the solution of Guillaume Perrot by extracting the classes.jar contained in each AAR file, and adding it to the classpath of the javadoc task.
It seems to work for AAR dependencies and AAR modules on Android Studio 2.3 and Gradle 3.3
import java.nio.file.Files
import java.nio.file.Paths
import java.io.FileOutputStream
import java.util.zip.ZipFile
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += configurations.compile
classpath += configurations.provided
afterEvaluate {
// Wait after evaluation to add the android classpath
// to avoid "buildToolsVersion is not specified" error
classpath += files(android.getBootClasspath())
// Process AAR dependencies
def aarDependencies = classpath.filter { it.name.endsWith('.aar') }
classpath -= aarDependencies
aarDependencies.each { aar ->
// Extract classes.jar from the AAR dependency, and add it to the javadoc classpath
def outputPath = "$buildDir/tmp/aarJar/${aar.name.replace('.aar', '.jar')}"
classpath += files(outputPath)
// Use a task so the actual extraction only happens before the javadoc task is run
dependsOn task(name: "extract ${aar.name}").doLast {
extractEntry(aar, 'classes.jar', outputPath)
}
}
}
}
// Utility method to extract only one entry in a zip file
private def extractEntry(archive, entryPath, outputPath) {
if (!archive.exists()) {
throw new GradleException("archive $archive not found")
}
def zip = new ZipFile(archive)
zip.entries().each {
if (it.name == entryPath) {
def path = Paths.get(outputPath)
if (!Files.exists(path)) {
Files.createDirectories(path.getParent())
Files.copy(zip.getInputStream(it), path)
}
}
}
zip.close()
}
The solution from #rve is now broken on Android Studio 2.3 / Gradle 3.3 as the exploded-aar no longer exists (with no alternative inside the build directory).
If the aar you depend on is not a module in your project, you will need first to extract the classes.jar before referencing it in the classpath (basically re-create intermediates/exploded-aar manually).
If the aar you depend on is just another module in your project you can also make your javadoc task depends on the compile task of that module and reference the intermediates/classes/release of that module (if you make javadoc depends on assembleRelease for example). An example of that workaround: https://github.com/Microsoft/mobile-center-sdk-android/pull/345/files
I really wish someone comes up with a better solution though.
This only works for Android Studio older than 2.3 and/or Gradle older than 3.3
To add the JARs from the AARs you can add the following doFirst to the javadoc task:
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
}
.doFirst {
classpath += fileTree(dir: "$buildDir/intermediates/exploded-aar/", include:"**/classes.jar")
}
It will add all .jar files from all the AARs to the javadoc classpath. (option 1 from your proposed solutions)
This is how I solved this issue, using zipTree. Configuration: Gradle 4.10, Gradle Plugin: 3.3.2, Android Studio: 3.4.
task javadoc(type: Javadoc) {
doFirst {
configurations.implementation
.filter { it.name.endsWith('.aar') }
.each { aar ->
copy {
from zipTree(aar)
include "**/classes.jar"
into "$buildDir/tmp/aarsToJars/${aar.name.replace('.aar', '')}/"
}
}
}
configurations.implementation.setCanBeResolved(true)
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.implementation
classpath += fileTree(dir: "$buildDir/tmp/aarsToJars/")
destinationDir = file("${project.buildDir}/outputs/javadoc/")
failOnError false
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
I am running the new Android Studio 3.0-beta7, and tried to use #nicopico's answer, but it failed with a number of different errors, so here's an adaptation of it that doesn't rely on the non-existent java.nio utilities.
task javadoc(type: Javadoc) {
failOnError false
source = android.sourceSets.main.java.srcDirs
// Also add the generated R class to avoid errors...
// TODO: debug is hard-coded
source += "$buildDir/generated/source/r/debug/"
// ... but exclude the R classes from the docs
excludes += "**/R.java"
// TODO: "compile" is deprecated in Gradle 4.1,
// but "implementation" and "api" are not resolvable :(
classpath += configurations.compile
afterEvaluate {
// Wait after evaluation to add the android classpath
// to avoid "buildToolsVersion is not specified" error
classpath += files(android.getBootClasspath())
// Process AAR dependencies
def aarDependencies = classpath.filter { it.name.endsWith('.aar') }
classpath -= aarDependencies
aarDependencies.each { aar ->
System.out.println("Adding classpath for aar: " + aar.name)
// Extract classes.jar from the AAR dependency, and add it to the javadoc classpath
def outputPath = "$buildDir/tmp/exploded-aar/${aar.name.replace('.aar', '.jar')}"
classpath += files(outputPath)
// Use a task so the actual extraction only happens before the javadoc task is run
dependsOn task(name: "extract ${aar.name}").doLast {
extractEntry(aar, 'classes.jar', outputPath)
}
}
}
}
// Utility method to extract only one entry in a zip file
private def extractEntry(archive, entryPath, outputPath) {
if (!archive.exists()) {
throw new GradleException("archive $archive not found")
}
def zip = new java.util.zip.ZipFile(archive)
zip.entries().each {
if (it.name == entryPath) {
def path = new File(outputPath)
if (!path.exists()) {
path.getParentFile().mkdirs()
// Surely there's a simpler is->os utility except
// the one in java.nio.Files? Ah well...
def buf = new byte[1024]
def is = zip.getInputStream(it)
def os = new FileOutputStream(path)
def len
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len)
}
os.close()
}
}
}
zip.close()
}
It bothers me that we need all this code to produce a freaking javadoc for a library, but at least I got this working. However, I do need to find a workaround for configuration.api and configuration.implementation not being resolvable.
All of the solutions listed here are out of date if you are developing an Android app/library using Kotlin. To generate javadocs as well as documentation in several other formats, use KDoc and Dokka:
https://kotlinlang.org/docs/kotlin-doc.html
https://kotlin.github.io/dokka/1.5.0/
https://github.com/Kotlin/dokka
I posted a solution for this problem at Android AAR depending on AAR fails with javadoc generation. I think Johann comment that the listed solutions are out of date is probably correct, but mike192 solution looks pretty good, although I think it might have a problem handling androidx dependencies. I haven't tried KDoc and Dokka yet, but in looking at the documentation, that looks promising. Hopefully it works for android java libraries. The android studio's built-in javadoc tool (2021.2.1) has issues handling that module type; hence the need to build a custom javadoc task to work around those issues.

Categories

Resources