Dokka: how to generate documentation for a multi module gradle build

Created on 16 May 2017  路  12Comments  路  Source: Kotlin/dokka

Hello,

just wanted to ask if you know a working configuration that either

  • aggregates all files from the subprojects or
  • generates a documentation including all subprojects at once

Tried something for the latter and came up with:

task dokka (type: org.jetbrains.dokka.gradle.DokkaTask, overwrite: true) {
    moduleName = "$rootProject.name"
    outputDirectory = "$buildDir/javadoc"
    outputFormat = "html"
    processConfigurations = []
    sourceDirs = files (subprojects.collect {
        p ->
            def path = "modules/${p.name}/src/main/kotlin"

            linkMapping {
                dir = path
                url = "https://....../blob/master/$path"
                suffix = "#L"
            }

            return "$rootProject.rootDir/$path"
    })
}

which works if the processConfigurations property is empty, but wont show any cross-reference in the generated html files then.

Any idea how to configure the classpath of related subprojects?

Thank you very much!

Most helpful comment

I'm closing this issue as it was a discussion for old dokka. New dokka (starting from 1.4.0-rc) is a complete rewrite and I encourage you to switch to it. It comes with multimodule tasks predefined:

  • dokkaHtmlCollector that gathers and merges documentation from all modules
  • dokkaHtmlMultimodule that documents modules as entities one level higher than packages
    They are not yet considered stable, but you can try them and give us some feedback. We are constantly tweaking them.

All 12 comments

First approach requires a lot of work with linking, so the second one is preferred.
What do yo mean by but wont show any cross-reference in the generated html files then.,
just checked with Dokka itself and your config, works fine for me.
Modified it a bit

task dokka (type: org.jetbrains.dokka.gradle.DokkaTask, overwrite: true) {
    moduleName = "$rootProject.name"
    outputDirectory = "$buildDir/ddoc"
    outputFormat = "html"
    processConfigurations = []

    sourceDirs = files(subprojects.collect {
        p ->

            def path = new File(p.projectDir, "/src/main/kotlin")

            def relativePath = rootDir.toPath().relativize(path.toPath()).toString()
            linkMapping {
                dir = path
                url = "https://....../blob/master/$relativePath"
                suffix = "#L"
            }

            return path
    })
}

Oh sorry, was not precise enough about the cross-reference.

The link mappings are fine and refer to the correct repository sources, but the documentation shows a lot of <ERROR CLASS> entries, due the missing classpath dependencies, like kotlin-stdlib and so on.

Thought that I have to configure the processConfigurations property, but can't manage this for the string values, which refer to the configuration names?

Would it be possible to use a different string format to refer to a subproject and the corresponding configuration, which gets evaluated at runtime?

e.g. $subproject-name:compile

Since each of my modules use Dokka for Javadoc generation, but I also wanted an interlinked Markdown version (for GitHub pages), I made a separate module that depended on all other modules (probably not necessary for just Dokka but I use it for other things as well):

dependencies {
    // Kotlin
    compile project.kotlinStdLib

    // Modules
    compile project(':module1')
    compile project(':module2')
    compile project(':moduleN')
    ...
}

apply plugin: 'org.jetbrains.dokka-android'
dokka {
    includes = ['Module.md']
    moduleName = 'dokka'
    outputFormat = 'gfm'
    outputDirectory = 'docs'
}

project.afterEvaluate {
    def mainDokkaTask = project.getTasksByName('dokka', false).first()
    rootProject.getTasksByName('dokka', true).forEach { libDokkaTask ->
        mainDokkaTask.sourceDirs = libDokkaTask.project.isAndroid ?
                libDokkaTask.project.android.sourceSets.main.java.srcDirs :
                libDokkaTask.project.sourceSets.main.allSource

        libDokkaTask.dependsOn 'assemble'

        mainDokkaTask.linkMapping {
            dir = "$rootDir/${libDokkaTask.project.name}/src/main/java"
            url = "https://github.com/user/project/tree/master/${libDokkaTask.project.name}/src/main/java"
            suffix = "#L"
        }

        if (libDokkaTask != mainDokkaTask) {
            mainDokkaTask.dependsOn libDokkaTask
        }
    }

    mainDokkaTask.dependsOn clearDokkaTask
    mainDokkaTask.dokkaFatJar project.dokkaFatJar
}

Having said all that, it would be nice if this was more officially supported so we could have proper bread crumbs that went from "Project > Module > Normal > Dokka > Path" etc.
Briefly looked into this but it did not look like a trivial task at all. x.x

Thank you for the example. I followed your suggestion and added the subprojects as module dependency to a custom configuration named dokkapath, which could be then used as value for processConfigurations = ['dokkapath'] to build the classpath.

configurations {
    dokkapath
    dokkapath.description = "dokka project classpath"
}

dependencies {
    subprojects.findAll { p -> dokkapath project (p.name) }
}

Had to set the repositories closure on the parent project for the dependency resolution, but all <ERROR CLASS> are gone now. There might be some caveat with jars provided by plugins (e.g. Android), but I've added the corresponding repositories manually to circumvent this issue.

Maybe you can add some options to the plugin to simplify this process.

Thanks everyone!

To have cross module references, you can also specify:

dependsOn {
    subprojects.collect {
        it.tasks.getByName("jar")
    }
}
doFirst {
    classpath = subprojects.collect { project -> project.jar.outputs.files.getFiles() }.flatten()
    classpath += files(subprojects.collect { it.sourceSets.main.compileClasspath })
}

+1 for native support!

I believe I have succeeded in creating a roll-up documentation with a mixture of what is described by @alex2069 and @semoro. That being, I have created a standalone module for the purposes of documentation that is purely for depending on all other subprojects and bringing in their source directories.

It was my original goal to do this without a unique documentation module, and I could get most of this working in the root project build.gradle. However, any Android types were showing up as <Error Class>. I know this has to do with the classpath, but I could not figure out how to use the 'dokka-android' plugin within the root project to accomplish this. This is what led me to move this all to a subproject with the 'com.android.library' plugin applied and it just worked.

Does anyone know what's needed to get this to work in the root project or is it best to do as a submodule if I need to reference Android?

Here's the block I was using in my root project build.gradle:

apply plugin: 'org.jetbrains.dokka-android'
dokka {
    moduleName = "$rootProject.name"
    outputDirectory = "$buildDir/docs"
    outputFormat = "html"
    jdkVersion = 8

    sourceDirs = files(subprojects.collect { p -> new File(p.projectDir, "/src/main/java") })
}

Here is a complete 'gradle-native' solution that worked for me based on @sdeleuze answer (fixed a little to define sourceDirs).

In modules that require documentation to be generated (just to mark them relevantly):

apply plugin: 'org.jetbrains.dokka'

In top-most build.gradle:

/**
 * Finds sub-projects that need dokka generation
 * Place `apply plugin: 'org.jetbrains.dokka'` there
 * @return A set of projects that have dokka task
 */
Set<Project> findDokkaProjects() {
    subprojects.findAll {
        !it.tasks.findAll { "dokka" == it.getName() }.isEmpty()
    }
}

task dokka (type: org.jetbrains.dokka.gradle.DokkaTask, overwrite: true) {
    outputDirectory = docDir
    outputFormat = "gfm"

    dependsOn {
        findDokkaProjects().collect {
            it.tasks.getByName("dokka")
        }
    }

    doFirst {
        def dokkaProjects = findDokkaProjects()

        classpath = dokkaProjects.collect { project -> project.jar.outputs.files.getFiles() }.flatten()
        classpath += files(dokkaProjects.collect { project -> project.sourceSets.main.compileClasspath })

        sourceDirs = files(dokkaProjects.collect { project -> "${project.projectDir}/src/main/kotlin" })
    }
}

Hi there, I've made a basic example of a multiproject generation. I think it might help you :slightly_smiling_face: https://github.com/JetBrains/kotlin-examples/pull/103

To build off of @fwpascual, this is what I am using in an Android SDK that contains multiple modules:

  1. documentation module, that basically just links the sourceSets for each of the actual modules I want documentation for, and the dokka config:
    ```groovy
    apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'

android {
compileSdkVersion 28

defaultConfig {
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"

}

sourceSets {
    main.java.srcDirs += '../moduleA/src/main/java'
    main.java.srcDirs += '../moduleB/src/main/java'
}

}

apply from: rootProject.file('dokka.gradle')

2. dokka setup (`dokka.gradle`) (pretty standard setup)

```groovy
apply plugin: 'org.jetbrains.dokka-android'

/**
 * See https://github.com/Kotlin/dokka
 */
dokka {
    moduleName = "$rootProject.name"
    outputFormat = 'html'
    outputDirectory = "$buildDir/javadoc"

    // Do not output deprecated members
    skipDeprecated = true

    // Emit warnings about not documented members.
    reportUndocumented = true

    // Do not create index pages for empty packages
    skipEmptyPackages = true
}
  1. then run ./gradlew documentation:dokka to generate merged documentation!

First approach requires a lot of work with linking, so the second one is preferred.
What do yo mean by but wont show any cross-reference in the generated html files then.,
just checked with Dokka itself and your config, works fine for me.
Modified it a bit

task dokka (type: org.jetbrains.dokka.gradle.DokkaTask, overwrite: true) {
    moduleName = "$rootProject.name"
    outputDirectory = "$buildDir/ddoc"
    outputFormat = "html"
    processConfigurations = []

    sourceDirs = files(subprojects.collect {
        p ->

            def path = new File(p.projectDir, "/src/main/kotlin")

            def relativePath = rootDir.toPath().relativize(path.toPath()).toString()
            linkMapping {
                dir = path
                url = "https://....../blob/master/$relativePath"
                suffix = "#L"
            }

            return path
    })
}

Could not set unknown property 'moduleName' for task ':dokka' of type org.jetbrains.dokka.gradle.DokkaTask.

@Armaxis
a model in Android.
i set sourceDirs = files("XXX","XXX") in dokka {}, but the error : Could not set unknown property 'sourceDirs' for task ':model:dokka' of type org.jetbrains.dokka.gradle.DokkaTask.
i can't find any complete example for an android model to Generate api documentation in kotlin. This is incredible.

I'm closing this issue as it was a discussion for old dokka. New dokka (starting from 1.4.0-rc) is a complete rewrite and I encourage you to switch to it. It comes with multimodule tasks predefined:

  • dokkaHtmlCollector that gathers and merges documentation from all modules
  • dokkaHtmlMultimodule that documents modules as entities one level higher than packages
    They are not yet considered stable, but you can try them and give us some feedback. We are constantly tweaking them.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Egorand picture Egorand  路  3Comments

joserobjr picture joserobjr  路  4Comments

norswap picture norswap  路  4Comments

gabrielittner picture gabrielittner  路  3Comments

martinkocjan-tomtom picture martinkocjan-tomtom  路  6Comments