spy
spy

Reputation: 3258

Android AAR depending on AAR fails with javadoc generation

I have an android gradle project structure that looks like this

Key facts

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.

Upvotes: 0

Views: 878

Answers (2)

Luis Olazo
Luis Olazo

Reputation: 59

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.

Upvotes: 0

Tom Rutchik
Tom Rutchik

Reputation: 1692

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)

    }
}

Upvotes: 0

Related Questions