diff options
Diffstat (limited to 'runners')
38 files changed, 1668 insertions, 1005 deletions
diff --git a/runners/android-gradle-plugin/build.gradle b/runners/android-gradle-plugin/build.gradle deleted file mode 100644 index 28b0cbb9..00000000 --- a/runners/android-gradle-plugin/build.gradle +++ /dev/null @@ -1,101 +0,0 @@ -import com.gradle.publish.DependenciesBuilder -import org.jetbrains.CorrectShadowPublishing - -apply plugin: 'java' -apply plugin: 'kotlin' - - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: "com.gradle.plugin-publish" - -sourceCompatibility = 1.8 - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict" - languageVersion = "1.2" - apiVersion = "1.1" - jvmTarget = "1.8" - } -} - -repositories { - jcenter() -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - - shadow project(path: ':runners:gradle-plugin', configuration: 'shadow') - compileOnly project(':integration') - - compileOnly gradleApi() - compileOnly localGroovy() -} - -task sourceJar(type: Jar) { - from sourceSets.main.allSource -} - -processResources { - eachFile { - if (it.name == "org.jetbrains.dokka-android.properties") { - it.filter { line -> - line.replace("<version>", dokka_version) - } - } - } -} - -shadowJar { - baseName = 'dokka-android-gradle-plugin' - classifier = '' -} - -apply plugin: 'maven-publish' - -publishing { - publications { - dokkaAndroidGradlePlugin(MavenPublication) { MavenPublication publication -> - artifactId = 'dokka-android-gradle-plugin' - - artifact sourceJar { - classifier "sources" - } - - CorrectShadowPublishing.configure(publication, project) - } - } -} - -bintrayPublication(project, ['dokkaAndroidGradlePlugin']) - -configurations.archives.artifacts.clear() -artifacts { - archives shadowJar -} - -pluginBundle { - website = 'https://www.kotlinlang.org/' - vcsUrl = 'https://github.com/kotlin/dokka.git' - description = 'Dokka, the Kotlin documentation tool' - tags = ['dokka', 'kotlin', 'kdoc', 'android'] - - plugins { - dokkaAndroidGradlePlugin { - id = 'org.jetbrains.dokka-android' - displayName = 'Dokka Android plugin' - } - } - - withDependencies { List<Dependency> list -> - list.clear() - def builder = new DependenciesBuilder() - builder.addUniqueScopedDependencies(list, configurations.shadow, "compile") - } - - mavenCoordinates { - groupId = "org.jetbrains.dokka" - artifactId = "dokka-android-gradle-plugin" - } -}
\ No newline at end of file diff --git a/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt b/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt deleted file mode 100644 index b1996da0..00000000 --- a/runners/android-gradle-plugin/src/main/kotlin/mainAndroid.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.jetbrains.dokka.gradle - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.tasks.Input -import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder -import java.io.File - -open class DokkaAndroidPlugin : Plugin<Project> { - override fun apply(project: Project) { - DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka-android.properties")) - project.tasks.create("dokka", DokkaAndroidTask::class.java).apply { - dokkaRuntime = project.configurations.create("dokkaRuntime") - moduleName = project.name - outputDirectory = File(project.buildDir, "dokka").absolutePath - } - } -} - -private val ANDROID_REFERENCE_URL = Builder("https://developer.android.com/reference/").build() - -open class DokkaAndroidTask : DokkaTask() { - - @Input var noAndroidSdkLink: Boolean = false - - override fun collectSuppressedFiles(sourceRoots: List<SourceRoot>): List<String> { - val generatedRoot = project.buildDir.resolve("generated").absoluteFile - return sourceRoots - .map { File(it.path) } - .filter { it.startsWith(generatedRoot) } - .flatMap { it.walk().toList() } - .map { it.absolutePath } - } - - init { - project.afterEvaluate { - if (!noAndroidSdkLink) externalDocumentationLinks.add(ANDROID_REFERENCE_URL) - } - } -} diff --git a/runners/android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.dokka-android.properties b/runners/android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.dokka-android.properties deleted file mode 100644 index b204da7b..00000000 --- a/runners/android-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.dokka-android.properties +++ /dev/null @@ -1,2 +0,0 @@ -implementation-class=org.jetbrains.dokka.gradle.DokkaAndroidPlugin -dokka-version=<version>
\ No newline at end of file diff --git a/runners/ant/build.gradle b/runners/ant/build.gradle index e7dcd441..216420c6 100644 --- a/runners/ant/build.gradle +++ b/runners/ant/build.gradle @@ -5,8 +5,8 @@ sourceCompatibility = 1.8 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" - languageVersion = "1.2" - apiVersion = languageVersion + languageVersion = language_version + apiVersion = language_version jvmTarget = "1.8" } } diff --git a/runners/ant/src/main/kotlin/ant/dokka.kt b/runners/ant/src/main/kotlin/ant/dokka.kt index b23328e0..3ecc7b94 100644 --- a/runners/ant/src/main/kotlin/ant/dokka.kt +++ b/runners/ant/src/main/kotlin/ant/dokka.kt @@ -17,54 +17,103 @@ class AntLogger(val task: Task): DokkaLogger { class AntSourceLinkDefinition(var path: String? = null, var url: String? = null, var lineSuffix: String? = null) -class AntSourceRoot(var path: String? = null, var platforms: String? = null) { - fun toSourceRoot(): SourceRootImpl? = path?.let { - path -> - SourceRootImpl(path, platforms?.split(',').orEmpty()) +class AntSourceRoot(var path: String? = null) { + fun toSourceRoot(): SourceRootImpl? = path?.let { path -> + SourceRootImpl(path) } } -class AntPackageOptions( - override var prefix: String = "", - override var includeNonPublic: Boolean = false, - override var reportUndocumented: Boolean = true, - override var skipDeprecated: Boolean = false, - override var suppress: Boolean = false) : DokkaConfiguration.PackageOptions - +class TextProperty(var value: String = "") + +class AntPassConfig(task: Task) : DokkaConfiguration.PassConfiguration { + override var moduleName: String = "" + override val classpath: List<String> + get() = buildClassPath.list().toList() + + override val sourceRoots: List<DokkaConfiguration.SourceRoot> + get() = sourcePath.list().map { SourceRootImpl(it) } + antSourceRoots.mapNotNull { it.toSourceRoot() } + + override val samples: List<String> + get() = samplesPath.list().toList() + override val includes: List<String> + get() = includesPath.list().toList() + override var includeNonPublic: Boolean = false + override var includeRootPackage: Boolean = true + override var reportUndocumented: Boolean = false + override var skipEmptyPackages: Boolean = true + override var skipDeprecated: Boolean = false + override var jdkVersion: Int = 6 + override val sourceLinks: List<DokkaConfiguration.SourceLinkDefinition> + get() = antSourceLinkDefinition.map { + val path = it.path!! + val url = it.url!! + SourceLinkDefinitionImpl(File(path).canonicalFile.absolutePath, url, it.lineSuffix) + } + override val perPackageOptions: MutableList<DokkaConfiguration.PackageOptions> = mutableListOf() + override val externalDocumentationLinks: List<ExternalDocumentationLink> + get() = buildExternalLinksBuilders.map { it.build() } + defaultExternalDocumentationLinks + + override var languageVersion: String? = null + override var apiVersion: String? = null + override var noStdlibLink: Boolean = false + override var noJdkLink: Boolean = false + override var suppressedFiles: MutableList<String> = mutableListOf() + override var collectInheritedExtensionsFromLibraries: Boolean = false + override var analysisPlatform: Platform = Platform.DEFAULT + override var targets: List<String> = listOf() + get() = buildTargets.filter { it.value != "" } + .map { it.value } + + override var sinceKotlin: String? = null + + private val samplesPath: Path by lazy { Path(task.project) } + private val includesPath: Path by lazy { Path(task.project) } + private val buildClassPath: Path by lazy { Path(task.project) } + val sourcePath: Path by lazy { Path(task.project) } + val antSourceRoots: MutableList<AntSourceRoot> = mutableListOf() + + private val buildTargets: MutableList<TextProperty> = mutableListOf() + private val buildExternalLinksBuilders: MutableList<ExternalDocumentationLink.Builder> = mutableListOf() + val antSourceLinkDefinition: MutableList<AntSourceLinkDefinition> = mutableListOf() + + private val defaultExternalDocumentationLinks: List<DokkaConfiguration.ExternalDocumentationLink> + get() { + val links = mutableListOf<DokkaConfiguration.ExternalDocumentationLink>() + if (!noJdkLink) + links += DokkaConfiguration.ExternalDocumentationLink.Builder("https://docs.oracle.com/javase/$jdkVersion/docs/api/").build() + + if (!noStdlibLink) + links += DokkaConfiguration.ExternalDocumentationLink.Builder("https://kotlinlang.org/api/latest/jvm/stdlib/").build() + return links + } -class DokkaAntTask: Task() { - var moduleName: String? = null - var outputDir: String? = null - var outputFormat: String = "html" - var impliedPlatforms: String = "" - var jdkVersion: Int = 6 - var noStdlibLink: Boolean = false - var noJdkLink: Boolean = false + fun setSamples(ref: Path) { + samplesPath.append(ref) + } - var skipDeprecated: Boolean = false + fun setSamplesRef(ref: Reference) { + samplesPath.createPath().refid = ref + } - var cacheRoot: String? = null + fun setInclude(ref: Path) { + includesPath.append(ref) + } - var languageVersion: String? = null - var apiVersion: String? = null + fun setClasspath(classpath: Path) { + buildClassPath.append(classpath) + } - val compileClasspath: Path by lazy { Path(getProject()) } - val sourcePath: Path by lazy { Path(getProject()) } - val samplesPath: Path by lazy { Path(getProject()) } - val includesPath: Path by lazy { Path(getProject()) } + fun createPackageOptions(): AntPackageOptions = AntPackageOptions().apply { perPackageOptions.add(this) } - val antSourceLinks: MutableList<AntSourceLinkDefinition> = arrayListOf() - val antSourceRoots: MutableList<AntSourceRoot> = arrayListOf() - val antPackageOptions: MutableList<AntPackageOptions> = arrayListOf() - val antExternalDocumentationLinks = mutableListOf<ExternalDocumentationLink.Builder>() + fun createSourceRoot(): AntSourceRoot = AntSourceRoot().apply { antSourceRoots.add(this) } - fun setClasspath(classpath: Path) { - compileClasspath.append(classpath) + fun createTarget(): TextProperty = TextProperty().apply { + buildTargets.add(this) } fun setClasspathRef(ref: Reference) { - compileClasspath.createPath().refid = ref + buildClassPath.createPath().refid = ref } fun setSrc(src: Path) { @@ -75,73 +124,69 @@ class DokkaAntTask: Task() { sourcePath.createPath().refid = ref } - fun setSamples(samples: Path) { - samplesPath.append(samples) - } - - fun setSamplesRef(ref: Reference) { - samplesPath.createPath().refid = ref - } - - fun setInclude(include: Path) { - includesPath.append(include) - } - fun createSourceLink(): AntSourceLinkDefinition { val def = AntSourceLinkDefinition() - antSourceLinks.add(def) + antSourceLinkDefinition.add(def) return def } - fun createSourceRoot(): AntSourceRoot = AntSourceRoot().apply { antSourceRoots.add(this) } + fun createExternalDocumentationLink() = + ExternalDocumentationLink.Builder().apply { buildExternalLinksBuilders.add(this) } + +} + +class AntPackageOptions( + override var prefix: String = "", + override var includeNonPublic: Boolean = false, + override var reportUndocumented: Boolean = true, + override var skipDeprecated: Boolean = false, + override var suppress: Boolean = false) : DokkaConfiguration.PackageOptions + +class DokkaAntTask: Task(), DokkaConfiguration { - fun createPackageOptions(): AntPackageOptions = AntPackageOptions().apply { antPackageOptions.add(this) } + override var format: String = "html" + override var generateIndexPages: Boolean = false + override var outputDir: String = "" + override var impliedPlatforms: List<String> = listOf() + get() = buildImpliedPlatforms.map { it.value }.toList() + private val buildImpliedPlatforms: MutableList<TextProperty> = mutableListOf() + + override var cacheRoot: String? = null + override val passesConfigurations: MutableList<AntPassConfig> = mutableListOf() + + fun createPassConfig() = AntPassConfig(this).apply { passesConfigurations.add(this) } + fun createImpliedPlatform(): TextProperty = TextProperty().apply { buildImpliedPlatforms.add(this) } - fun createExternalDocumentationLink() = ExternalDocumentationLink.Builder().apply { antExternalDocumentationLinks.add(this) } override fun execute() { - if (sourcePath.list().isEmpty() && antSourceRoots.isEmpty()) { - throw BuildException("At least one source path needs to be specified") - } - if (moduleName == null) { - throw BuildException("Module name needs to be specified") - } - if (outputDir == null) { - throw BuildException("Output directory needs to be specified") - } - val sourceLinks = antSourceLinks.map { - val path = it.path ?: throw BuildException("'path' attribute of a <sourceLink> element is required") - if (path.contains("\\")) { - throw BuildException("'dir' attribute of a <sourceLink> - incorrect value, only Unix based path allowed.") + for (passConfig in passesConfigurations) { + if (passConfig.sourcePath.list().isEmpty() && passConfig.antSourceRoots.isEmpty()) { + throw BuildException("At least one source path needs to be specified") } - val url = it.url ?: throw BuildException("'url' attribute of a <sourceLink> element is required") - SourceLinkDefinitionImpl(File(path).canonicalFile.absolutePath, url, it.lineSuffix) + if (passConfig.moduleName == "") { + throw BuildException("Module name needs to be specified and not empty") + } + + for (sourceLink in passConfig.antSourceLinkDefinition) { + if (sourceLink.path == null) { + throw BuildException("'path' attribute of a <sourceLink> element is required") + } + if (sourceLink.path!!.contains("\\")) { + throw BuildException("'dir' attribute of a <sourceLink> - incorrect value, only Unix based path allowed") + } + + if (sourceLink.url == null) { + throw BuildException("'url' attribute of a <sourceLink> element is required") + } + } + } + + if (outputDir == "") { + throw BuildException("Output directory needs to be specified and not empty") } - val generator = DokkaGenerator( - AntLogger(this), - compileClasspath.list().toList(), - sourcePath.list().map { SourceRootImpl(it) } + antSourceRoots.mapNotNull { it.toSourceRoot() }, - samplesPath.list().toList(), - includesPath.list().toList(), - moduleName!!, - DocumentationOptions( - outputDir!!, - outputFormat, - skipDeprecated = skipDeprecated, - sourceLinks = sourceLinks, - jdkVersion = jdkVersion, - impliedPlatforms = impliedPlatforms.split(','), - perPackageOptions = antPackageOptions, - externalDocumentationLinks = antExternalDocumentationLinks.map { it.build() }, - noStdlibLink = noStdlibLink, - noJdkLink = noJdkLink, - cacheRoot = cacheRoot, - languageVersion = languageVersion, - apiVersion = apiVersion - ) - ) + val generator = DokkaGenerator(this, AntLogger(this)) generator.generate() } }
\ No newline at end of file diff --git a/runners/cli/build.gradle b/runners/cli/build.gradle index 7f733140..24db0b1e 100644 --- a/runners/cli/build.gradle +++ b/runners/cli/build.gradle @@ -4,13 +4,13 @@ sourceCompatibility = 1.8 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { - languageVersion = "1.2" - apiVersion = languageVersion + languageVersion = language_version + apiVersion = language_version jvmTarget = "1.8" } } dependencies { - compile project(":core") - compile "com.github.spullara.cli-parser:cli-parser:1.1.1" + implementation "org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-3" + implementation project(":core") } diff --git a/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt b/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt new file mode 100644 index 00000000..5d795da7 --- /dev/null +++ b/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt @@ -0,0 +1,185 @@ +package org.jetbrains.dokka + +import kotlinx.cli.* +import kotlin.reflect.KProperty + +class ParseContext(val cli: CommandLineInterface = CommandLineInterface("dokka")) { + private val transformActions = mutableMapOf<KProperty<*>, (String) -> Unit>() + private val flagActions = mutableMapOf<KProperty<*>, () -> Unit>() + + fun registerFlagAction( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: () -> Unit + ) { + if (property !in flagActions.keys) { + cli.flagAction(keys, help) { + flagActions[property]!!() + } + } + flagActions[property] = invoke + + } + + fun registerSingleOption( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: (String) -> Unit + ) { + if (property !in transformActions.keys) { + cli.singleAction(keys, help) { + transformActions[property]!!(it) + } + } + transformActions[property] = invoke + } + + fun registerRepeatableOption( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: (String) -> Unit + ) { + if (property !in transformActions.keys) { + cli.repeatingAction(keys, help) { + transformActions[property]!!(it) + } + } + transformActions[property] = invoke + } + + fun parse(args: Array<String>) { + cli.parseArgs(*args) + } + +} + +fun CommandLineInterface.singleAction( + keys: List<String>, + help: String, + invoke: (String) -> Unit +) = registerAction( + object : FlagActionBase(keys, help) { + override fun invoke(arguments: ListIterator<String>) { + if (arguments.hasNext()) { + val msg = arguments.next() + invoke(msg) + } + } + + override fun invoke() { + error("should be never called") + } + } +) + +fun CommandLineInterface.repeatingAction( + keys: List<String>, + help: String, + invoke: (String) -> Unit +) = registerAction( + object : FlagActionBase(keys, help) { + override fun invoke(arguments: ListIterator<String>) { + while (arguments.hasNext()) { + val message = arguments.next() + + if (this@repeatingAction.getFlagAction(message) != null) { + arguments.previous() + break + } + invoke(message) + } + } + + override fun invoke() { + error("should be never called") + } + } + +) + + +class DokkaArgumentsParser(val args: Array<String>, val parseContext: ParseContext) { + class OptionDelegate<T>( + var value: T, + private val action: (delegate: OptionDelegate<T>, property: KProperty<*>) -> Unit + ) { + operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value + operator fun provideDelegate(thisRef: Any, property: KProperty<*>): OptionDelegate<T> { + action(this, property) + return this + } + } + + fun <T> parseInto(dest: T): T { + // TODO: constructor: (DokkaArgumentsParser) -> T + parseContext.parse(args) + return dest + } + + fun <T> repeatableOption( + keys: List<String>, + help: String, + transform: (String) -> T + ) = OptionDelegate(mutableListOf<T>()) { delegate, property -> + parseContext.registerRepeatableOption(keys, help, property) { + delegate.value.add(transform(it)) + } + } + + fun <T : String?> repeatableOption( + keys: List<String>, + help: String + ) = repeatableOption(keys, help) { it as T } + + fun <T> repeatableFlag( + keys: List<String>, + help: String, + initElement: (ParseContext) -> T + ) = OptionDelegate(mutableListOf<T>()) { delegate, property -> + parseContext.registerFlagAction(keys, help, property) { + delegate.value.add(initElement(parseContext)) + } + } + + fun <T> singleFlag( + keys: List<String>, + help: String, + initElement: (ParseContext) -> T, + transform: () -> T + ) = OptionDelegate(initElement(parseContext)) { delegate, property -> + parseContext.registerFlagAction(keys, help, property) { + delegate.value = transform() + } + } + + fun singleFlag( + keys: List<String>, + help: String + ) = singleFlag(keys, help, { false }, { true }) + + fun <T : String?> stringOption( + keys: List<String>, + help: String, + defaultValue: T + ) = singleOption(keys, help, { it as T }, { defaultValue }) + + fun <T> singleOption( + keys: List<String>, + help: String, + transform: (String) -> T, + initElement: (ParseContext) -> T + ) = OptionDelegate(initElement(parseContext)) { delegate, property -> + parseContext.registerSingleOption(keys, help, property) { + val toAdd = transform(it) + delegate.value = toAdd + } + } +} + + +//`(-perPackage fqName [-include-non-public] [...other flags])*` (edited) +//`(-sourceLink dir url [-urlSuffix value])*` +//`(-extLink url [packageListUrl])*`
\ No newline at end of file diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index f871f406..52815e75 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -1,142 +1,190 @@ package org.jetbrains.dokka - -import com.sampullara.cli.Args -import com.sampullara.cli.Argument import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink - import java.io.File import java.net.MalformedURLException import java.net.URL import java.net.URLClassLoader -class DokkaArguments { - @set:Argument(value = "src", description = "Source file or directory (allows many paths separated by the system path separator)") - var src: String = "" - - @set:Argument(value = "srcLink", description = "Mapping between a source directory and a Web site for browsing the code") - var srcLink: String = "" - - @set:Argument(value = "include", description = "Markdown files to load (allows many paths separated by the system path separator)") - var include: String = "" - - @set:Argument(value = "samples", description = "Source root for samples") - var samples: String = "" - - @set:Argument(value = "output", description = "Output directory path") - var outputDir: String = "out/doc/" - - @set:Argument(value = "format", description = "Output format (text, html, markdown, jekyll, kotlin-website)") - var outputFormat: String = "html" - - @set:Argument(value = "module", description = "Name of the documentation module") - var moduleName: String = "" - - @set:Argument(value = "classpath", description = "Classpath for symbol resolution") - var classpath: String = "" - - @set:Argument(value = "nodeprecated", description = "Exclude deprecated members from documentation") - var nodeprecated: Boolean = false - - @set:Argument(value = "jdkVersion", description = "Version of JDK to use for linking to JDK JavaDoc") - var jdkVersion: Int = 6 - - @set:Argument(value = "impliedPlatforms", description = "List of implied platforms (comma-separated)") - var impliedPlatforms: String = "" - - @set:Argument(value = "packageOptions", description = "List of package options in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" ") - var packageOptions: String = "" - - @set:Argument(value = "links", description = "External documentation links in format url^packageListUrl^^url2...") - var links: String = "" - - @set:Argument(value = "noStdlibLink", description = "Disable documentation link to stdlib") - var noStdlibLink: Boolean = false - - @set:Argument(value = "noJdkLink", description = "Disable documentation link to jdk") - var noJdkLink: Boolean = false - - @set:Argument(value = "cacheRoot", description = "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled") - var cacheRoot: String? = null - - @set:Argument(value = "languageVersion", description = "Language Version to pass to Kotlin Analysis") - var languageVersion: String? = null - - @set:Argument(value = "apiVersion", description = "Kotlin Api Version to pass to Kotlin Analysis") - var apiVersion: String? = null - - @set:Argument(value = "collectInheritedExtensionsFromLibraries", description = "Search for applicable extensions in libraries") - var collectInheritedExtensionsFromLibraries: Boolean = false - +open class GlobalArguments(parser: DokkaArgumentsParser) : DokkaConfiguration { + override val outputDir: String by parser.stringOption( + listOf("-output"), + "Output directory path", + "") + + override val format: String by parser.stringOption( + listOf("-format"), + "Output format (text, html, markdown, jekyll, kotlin-website)", + "") + + override val generateIndexPages: Boolean by parser.singleFlag( + listOf("-generateIndexPages"), + "Generate index page" + ) + + override val cacheRoot: String? by parser.stringOption( + listOf("-cacheRoot"), + "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled", + null) + + override val impliedPlatforms: List<String> = emptyList() + + override val passesConfigurations: List<Arguments> by parser.repeatableFlag( + listOf("-pass"), + "Single dokka pass" + ) { + Arguments(parser) + } } +class Arguments(val parser: DokkaArgumentsParser) : DokkaConfiguration.PassConfiguration { + override val moduleName: String by parser.stringOption( + listOf("-module"), + "Name of the documentation module", + "") + + override val classpath: List<String> by parser.repeatableOption( + listOf("-classpath"), + "Classpath for symbol resolution" + ) + + override val sourceRoots: List<DokkaConfiguration.SourceRoot> by parser.repeatableOption( + listOf("-src"), + "Source file or directory (allows many paths separated by the system path separator)" + ) { SourceRootImpl(it) } + + override val samples: List<String> by parser.repeatableOption( + listOf("-sample"), + "Source root for samples" + ) + + override val includes: List<String> by parser.repeatableOption( + listOf("-include"), + "Markdown files to load (allows many paths separated by the system path separator)" + ) + + override val includeNonPublic: Boolean by parser.singleFlag( + listOf("-includeNonPublic"), + "Include non public") + + override val includeRootPackage: Boolean by parser.singleFlag( + listOf("-includeRootPackage"), + "Include root package") + + override val reportUndocumented: Boolean by parser.singleFlag( + listOf("-reportUndocumented"), + "Report undocumented members") + + override val skipEmptyPackages: Boolean by parser.singleFlag( + listOf("-skipEmptyPackages"), + "Do not create index pages for empty packages") + + override val skipDeprecated: Boolean by parser.singleFlag( + listOf("-skipDeprecated"), + "Do not output deprecated members") + + override val jdkVersion: Int by parser.singleOption( + listOf("-jdkVersion"), + "Version of JDK to use for linking to JDK JavaDoc", + { it.toInt() }, + { 6 } + ) + + override val languageVersion: String? by parser.stringOption( + listOf("-languageVersion"), + "Language Version to pass to Kotlin Analysis", + null) + + override val apiVersion: String? by parser.stringOption( + listOf("-apiVersion"), + "Kotlin Api Version to pass to Kotlin Analysis", + null + ) + + override val noStdlibLink: Boolean by parser.singleFlag( + listOf("-noStdlibLink"), + "Disable documentation link to stdlib") + + override val noJdkLink: Boolean by parser.singleFlag( + listOf("-noJdkLink"), + "Disable documentation link to JDK") + + override val suppressedFiles: List<String> by parser.repeatableOption( + listOf("-suppressedFile"), + "" + ) + + override val sinceKotlin: String? by parser.stringOption( + listOf("-sinceKotlin"), + "Kotlin Api version to use as base version, if none specified", + null + ) + + override val collectInheritedExtensionsFromLibraries: Boolean by parser.singleFlag( + listOf("-collectInheritedExtensionsFromLibraries"), + "Search for applicable extensions in libraries") + + override val analysisPlatform: Platform by parser.singleOption( + listOf("-analysisPlatform"), + "Platform for analysis", + { Platform.fromString(it) }, + { Platform.DEFAULT } + ) + + override val targets: List<String> by parser.repeatableOption( + listOf("-target"), + "Generation targets" + ) + + override val perPackageOptions: MutableList<DokkaConfiguration.PackageOptions> by parser.singleOption( + listOf("-packageOptions"), + "List of package passConfiguration in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" ", + { parsePerPackageOptions(it).toMutableList() }, + { mutableListOf() } + ) + + override val externalDocumentationLinks: MutableList<DokkaConfiguration.ExternalDocumentationLink> by parser.singleOption( + listOf("-links"), + "External documentation links in format url^packageListUrl^^url2...", + { MainKt.parseLinks(it).toMutableList() }, + { mutableListOf() } + ) + + override val sourceLinks: MutableList<DokkaConfiguration.SourceLinkDefinition> by parser.repeatableOption( + listOf("-srcLink"), + "Mapping between a source directory and a Web site for browsing the code" + ) { + if (it.isNotEmpty() && it.contains("=")) + SourceLinkDefinitionImpl.parseSourceLinkDefinition(it) + else { + throw IllegalArgumentException("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") + } + } +} object MainKt { - fun parseLinks(links: String): List<ExternalDocumentationLink> { val (parsedLinks, parsedOfflineLinks) = links.split("^^") - .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } } - .filter { it.isNotEmpty() } - .partition { it.size == 1 } + .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } } + .filter { it.isNotEmpty() } + .partition { it.size == 1 } return parsedLinks.map { (root) -> ExternalDocumentationLink.Builder(root).build() } + parsedOfflineLinks.map { (root, packageList) -> val rootUrl = URL(root) val packageListUrl = - try { - URL(packageList) - } catch (ex: MalformedURLException) { - File(packageList).toURI().toURL() - } + try { + URL(packageList) + } catch (ex: MalformedURLException) { + File(packageList).toURI().toURL() + } ExternalDocumentationLink.Builder(rootUrl, packageListUrl).build() } } @JvmStatic - fun entry(args: Array<String>) { - val arguments = DokkaArguments() - val freeArgs: List<String> = Args.parse(arguments, args) ?: listOf() - val sources = if (arguments.src.isNotEmpty()) arguments.src.split(File.pathSeparatorChar).toList() + freeArgs else freeArgs - val samples = if (arguments.samples.isNotEmpty()) arguments.samples.split(File.pathSeparatorChar).toList() else listOf() - val includes = if (arguments.include.isNotEmpty()) arguments.include.split(File.pathSeparatorChar).toList() else listOf() - - val sourceLinks = if (arguments.srcLink.isNotEmpty() && arguments.srcLink.contains("=")) - listOf(SourceLinkDefinitionImpl.parseSourceLinkDefinition(arguments.srcLink)) - else { - if (arguments.srcLink.isNotEmpty()) { - println("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") - } - listOf() - } - - val classPath = arguments.classpath.split(File.pathSeparatorChar).toList() - - val documentationOptions = DocumentationOptions( - arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' }, - arguments.outputFormat, - skipDeprecated = arguments.nodeprecated, - sourceLinks = sourceLinks, - impliedPlatforms = arguments.impliedPlatforms.split(','), - perPackageOptions = parsePerPackageOptions(arguments.packageOptions), - jdkVersion = arguments.jdkVersion, - externalDocumentationLinks = parseLinks(arguments.links), - noStdlibLink = arguments.noStdlibLink, - cacheRoot = arguments.cacheRoot, - languageVersion = arguments.languageVersion, - apiVersion = arguments.apiVersion, - collectInheritedExtensionsFromLibraries = arguments.collectInheritedExtensionsFromLibraries, - noJdkLink = arguments.noJdkLink - ) - - val generator = DokkaGenerator( - DokkaConsoleLogger, - classPath, - sources.map(SourceRootImpl.Companion::parseSourceRoot), - samples, - includes, - arguments.moduleName, - documentationOptions) - + fun entry(configuration: DokkaConfiguration) { + val generator = DokkaGenerator(configuration, DokkaConsoleLogger) generator.generate() DokkaConsoleLogger.report() } @@ -162,27 +210,65 @@ object MainKt { return URLClassLoader(urls, ClassLoader.getSystemClassLoader().parent) } - fun startWithToolsJar(args: Array<String>) { + fun startWithToolsJar(configuration: DokkaConfiguration) { try { javaClass.classLoader.loadClass("com.sun.tools.doclets.formats.html.HtmlDoclet") - entry(args) + entry(configuration) } catch (e: ClassNotFoundException) { val classLoader = createClassLoaderWithTools() classLoader.loadClass("org.jetbrains.dokka.MainKt") - .methods.find { it.name == "entry" }!! - .invoke(null, args) + .methods.find { it.name == "entry" }!! + .invoke(null, configuration) + } + } + + fun createConfiguration(args: Array<String>): GlobalArguments { + val parseContext = ParseContext() + val parser = DokkaArgumentsParser(args, parseContext) + val configuration = GlobalArguments(parser) + + parseContext.cli.singleAction( + listOf("-globalPackageOptions"), + "List of package passConfiguration in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" " + ) { link -> + configuration.passesConfigurations.all { it.perPackageOptions.addAll(parsePerPackageOptions(link)) } } + + parseContext.cli.singleAction( + listOf("-globalLinks"), + "External documentation links in format url^packageListUrl^^url2..." + ) { link -> + configuration.passesConfigurations.all { it.externalDocumentationLinks.addAll(parseLinks(link)) } + } + + parseContext.cli.repeatingAction( + listOf("-globalSrcLink"), + "Mapping between a source directory and a Web site for browsing the code" + ) { + val newSourceLinks = if (it.isNotEmpty() && it.contains("=")) + listOf(SourceLinkDefinitionImpl.parseSourceLinkDefinition(it)) + else { + if (it.isNotEmpty()) { + println("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") + } + listOf() + } + + configuration.passesConfigurations.all { it.sourceLinks.addAll(newSourceLinks) } + } + + parser.parseInto(configuration) + return configuration } @JvmStatic fun main(args: Array<String>) { - val arguments = DokkaArguments() - Args.parse(arguments, args) + val configuration = createConfiguration(args) - if (arguments.outputFormat == "javadoc") - startWithToolsJar(args) + if (configuration.format.toLowerCase() == "javadoc") + startWithToolsJar(configuration) else - entry(args) + entry(configuration) } } diff --git a/runners/gradle-integration-tests/build.gradle b/runners/gradle-integration-tests/build.gradle index 297a175a..2430d46b 100644 --- a/runners/gradle-integration-tests/build.gradle +++ b/runners/gradle-integration-tests/build.gradle @@ -1,5 +1,3 @@ - - apply plugin: 'kotlin' sourceCompatibility = 1.8 @@ -7,16 +5,16 @@ sourceCompatibility = 1.8 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" - languageVersion = "1.2" - apiVersion = "1.0" + languageVersion = language_version + apiVersion = language_version jvmTarget = "1.8" } } configurations { dokkaPlugin - dokkaAndroidPlugin dokkaFatJar + kotlinGradle } dependencies { @@ -26,9 +24,10 @@ dependencies { testCompile ideaRT() dokkaPlugin project(path: ':runners:gradle-plugin', configuration: 'shadow') - dokkaAndroidPlugin project(path: ':runners:android-gradle-plugin', configuration: 'shadow') dokkaFatJar project(path: ":runners:fatjar", configuration: 'shadow') + kotlinGradle "org.jetbrains.kotlin:kotlin-gradle-plugin" + testCompile group: 'junit', name: 'junit', version: '4.12' testCompile gradleTestKit() } @@ -38,14 +37,14 @@ dependencies { task createClasspathManifest { def outputDir = file("$buildDir/$name") - inputs.files(configurations.dokkaPlugin + configurations.dokkaAndroidPlugin + configurations.dokkaFatJar) + inputs.files(configurations.dokkaPlugin + configurations.dokkaFatJar) outputs.dir outputDir doLast { outputDir.mkdirs() file("$outputDir/dokka-plugin-classpath.txt").text = configurations.dokkaPlugin.join("\n") - file("$outputDir/android-dokka-plugin-classpath.txt").text = configurations.dokkaAndroidPlugin.join("\n") file("$outputDir/fatjar.txt").text = configurations.dokkaFatJar.join("\n") + file("$outputDir/kotlin-gradle.txt").text = configurations.kotlinGradle.join("\n") } } diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaAndroidGradleTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaAndroidGradleTest.kt index 06753342..334fc7c8 100644 --- a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaAndroidGradleTest.kt +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaAndroidGradleTest.kt @@ -5,7 +5,7 @@ import java.io.File abstract class AbstractDokkaAndroidGradleTest : AbstractDokkaGradleTest() { - override val pluginClasspath: List<File> = androidPluginClasspathData.toFile().readLines().map { File(it) } + override val pluginClasspath: List<File> = pluginClasspathData.toFile().readLines().map { File(it) } companion object { @@ -31,7 +31,7 @@ abstract class AbstractDokkaAndroidGradleTest : AbstractDokkaGradleTest() { acceptedLicenses.listFiles().forEach { licenseFile -> val target = sdkLicensesDir.resolve(licenseFile.name) if(!target.exists() || target.readText() != licenseFile.readText()) { - val overwrite = System.getProperty("android.licenses.overwrite", "false").toBoolean() + val overwrite = System.getProperty("android.licenses.overwrite", "false")!!.toBoolean() if (!target.exists() || overwrite) { licenseFile.copyTo(target, true) println("Accepted ${licenseFile.name}, by copying $licenseFile to $target") diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaGradleTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaGradleTest.kt index 255138a9..4814e707 100644 --- a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaGradleTest.kt +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AbstractDokkaGradleTest.kt @@ -7,15 +7,15 @@ import org.junit.Rule import org.junit.rules.TemporaryFolder import java.io.File import java.nio.file.Files +import java.nio.file.Path import java.nio.file.Paths -val testDataFolder = Paths.get("testData") +val testDataFolder: Path = Paths.get("testData") -val pluginClasspathData = Paths.get("build", "createClasspathManifest", "dokka-plugin-classpath.txt") -val androidPluginClasspathData = pluginClasspathData.resolveSibling("android-dokka-plugin-classpath.txt") +val pluginClasspathData: Path = Paths.get("build", "createClasspathManifest", "dokka-plugin-classpath.txt") -val dokkaFatJarPathData = pluginClasspathData.resolveSibling("fatjar.txt") +val dokkaFatJarPathData: Path = pluginClasspathData.resolveSibling("fatjar.txt") val androidLocalProperties = testDataFolder.resolve("android.local.properties").let { if (Files.exists(it)) it else null } diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidLibDependsOnJavaLibTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidLibDependsOnJavaLibTest.kt index 2a4ce712..9bc52273 100644 --- a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidLibDependsOnJavaLibTest.kt +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/AndroidLibDependsOnJavaLibTest.kt @@ -2,8 +2,6 @@ package org.jetbrains.dokka.gradle import org.gradle.testkit.runner.TaskOutcome import org.junit.Test -import java.nio.file.Path -import java.nio.file.Paths import kotlin.test.assertEquals class AndroidLibDependsOnJavaLibTest: AbstractDokkaAndroidGradleTest() { diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/BasicTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/BasicTest.kt index ebaf1653..2e1a0d41 100644 --- a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/BasicTest.kt +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/BasicTest.kt @@ -37,10 +37,6 @@ class BasicTest : AbstractDokkaGradleTest() { """<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a>""") } - @Test fun `test kotlin 1_1_2 and gradle 3_5`() { - doTest("3.5", "1.1.2") - } - @Test fun `test kotlin 1_0_7 and gradle 2_14_1`() { doTest("2.14.1", "1.0.7") } diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/MultiplatformProjectTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/MultiplatformProjectTest.kt new file mode 100644 index 00000000..3e61d79e --- /dev/null +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/MultiplatformProjectTest.kt @@ -0,0 +1,54 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Test +import java.io.File +import kotlin.test.assertEquals + +class MultiplatformProjectTest : AbstractDokkaGradleTest() { + + fun prepareTestData(testDataRootPath: String) { + val testDataRoot = testDataFolder.resolve(testDataRootPath) + val tmpRoot = testProjectDir.root.toPath() + + testDataRoot.apply { + resolve("build.gradle").copy(tmpRoot.resolve("build.gradle")) + resolve("settings.gradle").copy(tmpRoot.resolve("settings.gradle")) + resolve("src").copy(tmpRoot.resolve("src")) + } + } + + private fun doTest(gradleVersion: String, kotlinVersion: String) { + val kotlinGradlePlugin = pluginClasspathData.resolveSibling("kotlin-gradle.txt").toFile().readLines().map { File(it) } + prepareTestData("multiplatformProject") + + // Remove withDebug(false) when https://github.com/gradle/gradle/issues/6862 is solved + val result = configure(gradleVersion, kotlinVersion, arguments = arrayOf("dokka", "--stacktrace")) + .withDebug(false) + .withPluginClasspath(pluginClasspath.union(kotlinGradlePlugin)) + .build() + + println(result.output) + + assertEquals(TaskOutcome.SUCCESS, result.task(":dokka")?.outcome) + + val docsOutput = "build/dokka" + + checkOutputStructure("multiplatformProject/fileTree.txt", docsOutput) + + checkNoErrorClasses(docsOutput) + checkNoUnresolvedLinks(docsOutput) + } + + @Test fun `test kotlin 1_3_30 and gradle 4_7`() { + doTest("4.7", "1.3.30") + } + + @Test fun `test kotlin 1_3_40 and gradle 4_10_3`() { + doTest("4.10.3", "1.3.40") + } + + @Test fun `test kotlin 1_3_40 and gradle 5_6_1`() { + doTest("5.6.1", "1.3.50") + } +}
\ No newline at end of file diff --git a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/RebuildAfterSourceChangeTest.kt b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/RebuildAfterSourceChangeTest.kt index f712998c..8b2db560 100644 --- a/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/RebuildAfterSourceChangeTest.kt +++ b/runners/gradle-integration-tests/src/test/kotlin/org/jetbrains/dokka/gradle/RebuildAfterSourceChangeTest.kt @@ -30,7 +30,7 @@ class RebuildAfterSourceChangeTest : AbstractDokkaGradleTest() { } - configure(gradleVersion, kotlinVersion, arguments = arrayOf("dokka", "--stacktrace")).build().let { result -> + configure(gradleVersion, kotlinVersion, arguments = arrayOf("dokka", "-i", "--stacktrace")).build().let { result -> println(result.output) assertEquals(TaskOutcome.UP_TO_DATE, result.task(":dokka")?.outcome) @@ -53,13 +53,13 @@ class RebuildAfterSourceChangeTest : AbstractDokkaGradleTest() { @Test - fun `test kotlin 1_1_2 and gradle 3_5`() { - doTest("3.5", "1.1.2") + fun `test kotlin 1_0_7 and gradle 2_14_1`() { + doTest("2.14.1", "1.0.7") } @Test - fun `test kotlin 1_0_7 and gradle 2_14_1`() { - doTest("2.14.1", "1.0.7") + fun `test kotlin 1_1_2 and gradle 3_5`() { + doTest("3.5", "1.1.2") } @Test diff --git a/runners/gradle-integration-tests/testData/androidApp/app/build.gradle b/runners/gradle-integration-tests/testData/androidApp/app/build.gradle index 1555de9f..2420107c 100644 --- a/runners/gradle-integration-tests/testData/androidApp/app/build.gradle +++ b/runners/gradle-integration-tests/testData/androidApp/app/build.gradle @@ -9,14 +9,13 @@ buildscript { } plugins { - id 'org.jetbrains.dokka-android' + id 'org.jetbrains.dokka' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'org.jetbrains.dokka-android' android { compileSdkVersion Integer.parseInt(sdk_version) @@ -42,9 +41,5 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$test_kotlin_version" -} - - -dokka { - dokkaFatJar = new File(dokka_fatjar) + dokkaRuntime files(dokka_fatjar) }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/androidAppJavadoc/app/build.gradle b/runners/gradle-integration-tests/testData/androidAppJavadoc/app/build.gradle index 6a053a5e..66421f52 100644 --- a/runners/gradle-integration-tests/testData/androidAppJavadoc/app/build.gradle +++ b/runners/gradle-integration-tests/testData/androidAppJavadoc/app/build.gradle @@ -9,13 +9,12 @@ buildscript { } plugins { - id 'org.jetbrains.dokka-android' + id 'org.jetbrains.dokka' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.dokka-android' android { compileSdkVersion Integer.parseInt(sdk_version) @@ -41,10 +40,10 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$test_kotlin_version" + dokkaRuntime files(dokka_fatjar) } dokka { outputFormat = "javadoc" - dokkaFatJar = new File(dokka_fatjar) }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/androidLibDependsOnJavaLib/lib/build.gradle b/runners/gradle-integration-tests/testData/androidLibDependsOnJavaLib/lib/build.gradle index 0f27d365..b1ee52ab 100644 --- a/runners/gradle-integration-tests/testData/androidLibDependsOnJavaLib/lib/build.gradle +++ b/runners/gradle-integration-tests/testData/androidLibDependsOnJavaLib/lib/build.gradle @@ -7,13 +7,12 @@ buildscript { plugins { - id 'org.jetbrains.dokka-android' + id 'org.jetbrains.dokka' } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'org.jetbrains.dokka-android' android { @@ -27,13 +26,14 @@ android { dependencies { api(project(":jlib")) + dokkaRuntime files(dokka_fatjar) } dokka { - dokkaFatJar = new File(dokka_fatjar) - - externalDocumentationLink { - url = new URL("https://example.com") - packageListUrl = file("$rootDir/package-list").toURI().toURL() + configuration { + externalDocumentationLink { + url = new URL("https://example.com") + packageListUrl = file("$rootDir/package-list").toURI().toURL() + } } }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/app/build.gradle b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/app/build.gradle index ee68ba6d..660257ab 100644 --- a/runners/gradle-integration-tests/testData/androidMultiFlavourApp/app/build.gradle +++ b/runners/gradle-integration-tests/testData/androidMultiFlavourApp/app/build.gradle @@ -9,14 +9,13 @@ buildscript { } plugins { - id 'org.jetbrains.dokka-android' + id 'org.jetbrains.dokka' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'org.jetbrains.dokka-android' android { compileSdkVersion Integer.parseInt(sdk_version) @@ -57,19 +56,20 @@ android { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$test_kotlin_version" + dokkaRuntime files(dokka_fatjar) } dokka { outputDirectory = "$buildDir/dokka/all" - dokkaFatJar = new File(dokka_fatjar) } -task dokkaFullFlavourOnly(type: org.jetbrains.dokka.gradle.DokkaAndroidTask) { - kotlinTasks { - ["compileFullReleaseKotlin"] - } - dokkaFatJar = new File(dokka_fatjar) +task dokkaFullFlavourOnly(type: org.jetbrains.dokka.gradle.DokkaTask) { outputDirectory = "$buildDir/dokka/fullOnly" - moduleName = "full" + configuration { + moduleName = "full" + kotlinTasks { + ["compileFullReleaseKotlin"] + } + } }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/basic/build.gradle b/runners/gradle-integration-tests/testData/basic/build.gradle index a3116751..79645204 100644 --- a/runners/gradle-integration-tests/testData/basic/build.gradle +++ b/runners/gradle-integration-tests/testData/basic/build.gradle @@ -29,12 +29,14 @@ repositories { } dependencies { + dokkaRuntime files(dokka_fatjar) compile group: 'org.jetbrains.kotlin', name: 'kotlin-runtime', version: test_kotlin_version compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: test_kotlin_version } dokka { - dokkaFatJar = new File(dokka_fatjar) - classpath += files("$projectDir/classDir") + configuration { + classpath += "$projectDir/classDir" + } }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle b/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle index 4f561472..0ea86d4c 100644 --- a/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle +++ b/runners/gradle-integration-tests/testData/multiProjectSingleOut/build.gradle @@ -22,11 +22,16 @@ subprojects { } } +dependencies { + dokkaRuntime files(dokka_fatjar) +} + apply plugin: 'org.jetbrains.dokka' dokka { - kotlinTasks { - [":subA:compileKotlin", ":subB:compileKotlin"] + configuration { + kotlinTasks { + [":subA:compileKotlin", ":subB:compileKotlin"] + } } - dokkaFatJar = new File(dokka_fatjar) }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/multiplatformProject/build.gradle b/runners/gradle-integration-tests/testData/multiplatformProject/build.gradle new file mode 100644 index 00000000..b5454c55 --- /dev/null +++ b/runners/gradle-integration-tests/testData/multiplatformProject/build.gradle @@ -0,0 +1,60 @@ +buildscript { + repositories { + mavenCentral() + jcenter() + maven { url "https://dl.bintray.com/kotlin/kotlin-eap-1.1" } + maven { url "https://dl.bintray.com/kotlin/kotlin-dev" } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$test_kotlin_version" + } +} + +plugins { + id 'org.jetbrains.dokka' +} + +repositories { + jcenter() + mavenLocal() +} + +group 'org.test' +version '1.0-SNAPSHOT' + +apply plugin: "org.jetbrains.kotlin.multiplatform" + +kotlin { + jvm() // Create a JVM target with the default name 'jvm' + js() + sourceSets { + jsMain { + dependencies{ + implementation "org.jetbrains.kotlin:kotlin-stdlib-js" + } + } + jvmMain { + dependencies { + implementation kotlin('stdlib-jdk8') + } + } + } +} + +dependencies { + dokkaRuntime files(dokka_fatjar) +} + +apply plugin: 'org.jetbrains.dokka' + +dokka { + + multiplatform { + javascript { + targets = ["js"] + platform = "js" + kotlinTasks { [tasks.getByPath(":compileKotlinJs")] } + } + jvm {} + } +} diff --git a/runners/gradle-integration-tests/testData/multiplatformProject/fileTree.txt b/runners/gradle-integration-tests/testData/multiplatformProject/fileTree.txt new file mode 100644 index 00000000..51a5df94 --- /dev/null +++ b/runners/gradle-integration-tests/testData/multiplatformProject/fileTree.txt @@ -0,0 +1,17 @@ +/ + multiplatform-project-root/ + alltypes/ + index.html + index-outline.html + index.html + org.kotlintestmpp/ + get-current-date.html + index.html + js.html + jvm.html + kotlin.-string/ + index.html + main.html + shared.html + package-list + style.css diff --git a/runners/gradle-integration-tests/testData/multiplatformProject/settings.gradle b/runners/gradle-integration-tests/testData/multiplatformProject/settings.gradle new file mode 100644 index 00000000..0bb1e91b --- /dev/null +++ b/runners/gradle-integration-tests/testData/multiplatformProject/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "multiplatformProjectRoot" diff --git a/runners/gradle-integration-tests/testData/multiplatformProject/src/jsMain/kotlin/org/kotlintestmpp/main.kt b/runners/gradle-integration-tests/testData/multiplatformProject/src/jsMain/kotlin/org/kotlintestmpp/main.kt new file mode 100644 index 00000000..a77b50f9 --- /dev/null +++ b/runners/gradle-integration-tests/testData/multiplatformProject/src/jsMain/kotlin/org/kotlintestmpp/main.kt @@ -0,0 +1,14 @@ +package org.kotlintestmpp + +fun main(args : Array<String>) { + console.log("Hello, world!") +} + +fun js(){} +fun shared(){} + +fun getCurrentDate(): String { + return "test" +} + +fun String.myExtension() = println("test")
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/multiplatformProject/src/jvmMain/kotlin/org/kotlintestmpp/main.kt b/runners/gradle-integration-tests/testData/multiplatformProject/src/jvmMain/kotlin/org/kotlintestmpp/main.kt new file mode 100644 index 00000000..96d725fc --- /dev/null +++ b/runners/gradle-integration-tests/testData/multiplatformProject/src/jvmMain/kotlin/org/kotlintestmpp/main.kt @@ -0,0 +1,20 @@ +package org.kotlintestmpp + + +fun main(args : Array<String>) { + println("Hello, world!") +} + +/** + * comment for this class + */ +fun jvm(){} +fun shared(){} + +fun getCurrentDate(): String { + return "test" +} + +fun String.myExtension() = println("test2") + + diff --git a/runners/gradle-integration-tests/testData/sourcesChange/build.gradle b/runners/gradle-integration-tests/testData/sourcesChange/build.gradle index 4627e8ef..a6270e23 100644 --- a/runners/gradle-integration-tests/testData/sourcesChange/build.gradle +++ b/runners/gradle-integration-tests/testData/sourcesChange/build.gradle @@ -29,11 +29,7 @@ repositories { } dependencies { + dokkaRuntime files(dokka_fatjar) compile group: 'org.jetbrains.kotlin', name: 'kotlin-runtime', version: test_kotlin_version compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: test_kotlin_version -} - - -dokka { - dokkaFatJar = new File(dokka_fatjar) }
\ No newline at end of file diff --git a/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle b/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle index 327cead8..8688ae41 100644 --- a/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle +++ b/runners/gradle-integration-tests/testData/typeSafeConfiguration/build.gradle @@ -8,6 +8,7 @@ import java.util.concurrent.Callable buildscript { repositories { jcenter() + mavenLocal() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$test_kotlin_version" @@ -23,57 +24,45 @@ apply plugin: 'kotlin' @CompileStatic def configureDokkaTypeSafely(DokkaTask dokka) { dokka.with { - moduleName = "some String" outputFormat = "some String" outputDirectory = "some String" - classpath = Collections.singleton(file("someClassDir")) - includes = Collections.emptyList() - linkMappings = new ArrayList<LinkMapping>() - samples = Collections.emptyList() + cacheRoot = null as String + impliedPlatforms = new ArrayList<String>() + } + dokka.configuration.with { + moduleName = "some String" + classpath = Arrays.asList("someClassDir") + includes = Collections.<String> emptyList() + samples = Collections.<String> emptyList() jdkVersion = 6 - sourceDirs = Collections.<File>emptyList() - sourceRoots = new ArrayList<SourceRoot>() - dokkaFatJar = file("some File") + sourceRoots = new ArrayList<GradleSourceRootImpl>() as List<DokkaConfiguration.SourceRoot> + includeNonPublic = false skipDeprecated = false skipEmptyPackages = true reportUndocumented = true - perPackageOptions = new ArrayList<PackageOptions>() - impliedPlatforms = Collections.<String>emptyList() + perPackageOptions = new ArrayList<GradlePackageOptionsImpl>() as List<DokkaConfiguration.PackageOptions> externalDocumentationLinks = new ArrayList<DokkaConfiguration.ExternalDocumentationLink>() noStdlibLink = false - cacheRoot = null as String languageVersion = null as String apiVersion = null as String - kotlinTasks(new Callable<List<Object>>() { - @Override - List<Object> call() { - return defaultKotlinTasks() - } - }) - linkMapping(new Action<LinkMapping>() { + sourceRoot(new Action<GradleSourceRootImpl>() { @Override - void execute(LinkMapping mapping) { - mapping.dir = "some String" - mapping.url = "some String" - } - }) - sourceRoot(new Action<SourceRoot>() { - @Override - void execute(SourceRoot sourceRoot) { + void execute(GradleSourceRootImpl sourceRoot) { sourceRoot.path = "some String" } }) - packageOptions(new Action<PackageOptions>() { + externalDocumentationLink(new Action<GradleExternalDocumentationLinkImpl>() { @Override - void execute(PackageOptions packageOptions) { - packageOptions.prefix = "some String" + void execute(GradleExternalDocumentationLinkImpl link) { + link.url = uri("some URI").toURL() + link.packageListUrl = uri("some URI").toURL() } }) - externalDocumentationLink(new Action<DokkaConfiguration.ExternalDocumentationLink.Builder>() { + kotlinTasks(new Callable<List<Object>>() { @Override - void execute(DokkaConfiguration.ExternalDocumentationLink.Builder builder) { - builder.url = uri("some URI").toURL() + List<Object> call() { + return defaultKotlinTasks() } }) } diff --git a/runners/gradle-plugin/build.gradle b/runners/gradle-plugin/build.gradle index 8e59a7be..ceb03bae 100644 --- a/runners/gradle-plugin/build.gradle +++ b/runners/gradle-plugin/build.gradle @@ -12,12 +12,17 @@ sourceCompatibility = 1.8 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" - languageVersion = "1.2" - apiVersion = "1.1" + languageVersion = language_version + apiVersion = language_version jvmTarget = "1.8" } } +repositories { + jcenter() + google() +} + dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' @@ -26,8 +31,13 @@ dependencies { compile project(":integration") + compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin" + compileOnly("com.android.tools.build:gradle:3.0.0") + compileOnly("com.android.tools.build:gradle-core:3.0.0") + compileOnly("com.android.tools.build:builder-model:3.0.0") compileOnly gradleApi() compileOnly localGroovy() + implementation "com.google.code.gson:gson:$gson_version" } task sourceJar(type: Jar) { @@ -54,7 +64,6 @@ apply plugin: 'maven-publish' publishing { publications { dokkaGradlePlugin(MavenPublication) { publication -> - artifactId = 'dokka-gradle-plugin' artifact sourceJar { @@ -77,7 +86,7 @@ pluginBundle { website = 'https://www.kotlinlang.org/' vcsUrl = 'https://github.com/kotlin/dokka.git' description = 'Dokka, the Kotlin documentation tool' - tags = ['dokka', 'kotlin', 'kdoc'] + tags = ['dokka', 'kotlin', 'kdoc', 'android'] plugins { dokkaGradlePlugin { diff --git a/runners/gradle-plugin/src/main/kotlin/main.kt b/runners/gradle-plugin/src/main/kotlin/main.kt deleted file mode 100644 index f4adc1c3..00000000 --- a/runners/gradle-plugin/src/main/kotlin/main.kt +++ /dev/null @@ -1,499 +0,0 @@ -package org.jetbrains.dokka.gradle - -import groovy.lang.Closure -import org.gradle.api.Action -import org.gradle.api.DefaultTask -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.FileCollection -import org.gradle.api.plugins.JavaBasePlugin -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.* -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.compile.AbstractCompile -import org.jetbrains.dokka.* -import org.jetbrains.dokka.ReflectDsl.isNotInstance -import org.jetbrains.dokka.gradle.ClassloaderContainer.fatJarClassLoader -import org.jetbrains.dokka.gradle.DokkaVersion.version -import ru.yole.jkid.JsonExclude -import ru.yole.jkid.serialization.serialize -import java.io.File -import java.io.InputStream -import java.io.Serializable -import java.net.URLClassLoader -import java.util.* -import java.util.concurrent.Callable -import java.util.function.BiConsumer - -open class DokkaPlugin : Plugin<Project> { - - override fun apply(project: Project) { - DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties")) - project.tasks.create("dokka", DokkaTask::class.java).apply { - dokkaRuntime = project.configurations.create("dokkaRuntime") - moduleName = project.name - outputDirectory = File(project.buildDir, "dokka").absolutePath - } - } -} - -object DokkaVersion { - var version: String? = null - - fun loadFrom(stream: InputStream) { - version = Properties().apply { - load(stream) - }.getProperty("dokka-version") - } -} - - -object ClassloaderContainer { - @JvmField - var fatJarClassLoader: ClassLoader? = null -} - -const val `deprecationMessage reportNotDocumented` = "Will be removed in 0.9.17, see dokka#243" - -open class DokkaTask : DefaultTask() { - - fun defaultKotlinTasks() = with(ReflectDsl) { - val abstractKotlinCompileClz = try { - project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE) - } catch (cnfe: ClassNotFoundException) { - logger.warn("$ABSTRACT_KOTLIN_COMPILE class not found, default kotlin tasks ignored") - return@with emptyList<Task>() - } - - return@with project.tasks.filter { it isInstance abstractKotlinCompileClz }.filter { "Test" !in it.name } - } - - init { - group = JavaBasePlugin.DOCUMENTATION_GROUP - description = "Generates dokka documentation for Kotlin" - - @Suppress("LeakingThis") - dependsOn(Callable { kotlinTasks.map { it.taskDependencies } }) - } - - @Input - var moduleName: String = "" - @Input - var outputFormat: String = "html" - var outputDirectory: String = "" - var dokkaRuntime: Configuration? = null - - @Deprecated("Going to be removed in 0.9.16, use classpath + sourceDirs instead if kotlinTasks is not suitable for you") - @Input var processConfigurations: List<Any?> = emptyList() - - @InputFiles var classpath: Iterable<File> = arrayListOf() - - @Input - var includes: List<Any?> = arrayListOf() - @Input - var linkMappings: ArrayList<LinkMapping> = arrayListOf() - @Input - var samples: List<Any?> = arrayListOf() - @Input - var jdkVersion: Int = 6 - @Input - var sourceDirs: Iterable<File> = emptyList() - - @Input - var sourceRoots: MutableList<SourceRoot> = arrayListOf() - - @Input - var dokkaFatJar: Any = "org.jetbrains.dokka:dokka-fatjar:$version" - - @Input var includeNonPublic = false - @Input var skipDeprecated = false - @Input var skipEmptyPackages = true - - @Deprecated(`deprecationMessage reportNotDocumented`, replaceWith = ReplaceWith("reportUndocumented")) - var reportNotDocumented - get() = reportUndocumented - set(value) { - logger.warn("Dokka: reportNotDocumented is deprecated and " + `deprecationMessage reportNotDocumented`.decapitalize()) - reportUndocumented = value - } - - @Input var reportUndocumented = true - @Input var perPackageOptions: MutableList<PackageOptions> = arrayListOf() - @Input var impliedPlatforms: MutableList<String> = arrayListOf() - - @Input var externalDocumentationLinks = mutableListOf<DokkaConfiguration.ExternalDocumentationLink>() - - @Input var noStdlibLink: Boolean = false - - @Input - var noJdkLink: Boolean = false - - @Optional @Input - var cacheRoot: String? = null - - - @Optional @Input - var languageVersion: String? = null - - @Optional @Input - var apiVersion: String? = null - - @Input - var collectInheritedExtensionsFromLibraries: Boolean = false - - @get:Internal - internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() } - - - private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() } - private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() } - - fun kotlinTasks(taskSupplier: Callable<List<Any>>) { - kotlinTasksConfigurator = { taskSupplier.call() } - } - - fun kotlinTasks(closure: Closure<Any?>) { - kotlinTasksConfigurator = { closure.call() as? List<Any?> } - } - - fun linkMapping(action: Action<LinkMapping>) { - val mapping = LinkMapping() - action.execute(mapping) - - if (mapping.path.isEmpty()) { - throw IllegalArgumentException("Link mapping should have dir") - } - if (mapping.url.isEmpty()) { - throw IllegalArgumentException("Link mapping should have url") - } - - linkMappings.add(mapping) - } - - fun linkMapping(closure: Closure<Unit>) { - linkMapping(Action { mapping -> - closure.delegate = mapping - closure.call() - }) - } - - fun sourceRoot(action: Action<SourceRoot>) { - val sourceRoot = SourceRoot() - action.execute(sourceRoot) - sourceRoots.add(sourceRoot) - } - - fun sourceRoot(closure: Closure<Unit>) { - sourceRoot(Action { sourceRoot -> - closure.delegate = sourceRoot - closure.call() - }) - } - - fun packageOptions(action: Action<PackageOptions>) { - val packageOptions = PackageOptions() - action.execute(packageOptions) - perPackageOptions.add(packageOptions) - } - - fun packageOptions(closure: Closure<Unit>) { - packageOptions(Action { packageOptions -> - closure.delegate = packageOptions - closure.call() - }) - } - - fun externalDocumentationLink(action: Action<DokkaConfiguration.ExternalDocumentationLink.Builder>) { - val builder = DokkaConfiguration.ExternalDocumentationLink.Builder() - action.execute(builder) - externalDocumentationLinks.add(builder.build()) - } - - fun externalDocumentationLink(closure: Closure<Unit>) { - externalDocumentationLink(Action { builder -> - closure.delegate = builder - closure.call() - }) - } - - fun tryResolveFatJar(project: Project): Set<File> { - return try { - dokkaRuntime!!.resolve() - } catch (e: Exception) { - project.parent?.let { tryResolveFatJar(it) } ?: throw e - } - } - - fun loadFatJar() { - if (fatJarClassLoader == null) { - val jars = if (dokkaFatJar is File) - setOf(dokkaFatJar as File) - else - tryResolveFatJar(project) - fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent) - } - } - - internal data class ClasspathAndSourceRoots(val classpathFileCollection: FileCollection, val sourceRoots: List<File>) : Serializable - - private fun extractKotlinCompileTasks(): List<Task> { - val inputList = (kotlinTasksConfigurator.invoke() ?: emptyList()).filterNotNull() - val (paths, other) = inputList.partition { it is String } - - val taskContainer = project.tasks - - val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") } - - other - .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) } - .forEach { throw IllegalArgumentException("Illegal entry in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE or String, but was $it") } - - tasksByPath - .filter { it == null || it isNotInstance getAbstractKotlinCompileFor(it) } - .forEach { throw IllegalArgumentException("Illegal task path in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE, but was $it") } - - - return (tasksByPath + other) as List<Task> - } - - private fun extractClasspathAndSourceRootsFromKotlinTasks(): ClasspathAndSourceRoots { - - val allTasks = kotlinTasks - - val allClasspath = mutableSetOf<File>() - var allClasspathFileCollection: FileCollection = project.files() - val allSourceRoots = mutableSetOf<File>() - - allTasks.forEach { - - logger.debug("Dokka found AbstractKotlinCompile task: $it") - with(ReflectDsl) { - val taskSourceRoots: List<File> = it["sourceRootsContainer"]["sourceRoots"].v() - - val abstractKotlinCompileClz = getAbstractKotlinCompileFor(it)!! - - val taskClasspath: Iterable<File> = - (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke() - ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v() - ?: it["getClasspath", abstractKotlinCompileClz]()) - - if (taskClasspath is FileCollection) { - allClasspathFileCollection += taskClasspath - } else { - allClasspath += taskClasspath - } - allSourceRoots += taskSourceRoots.filter { it.exists() } - } - } - - return ClasspathAndSourceRoots(allClasspathFileCollection + project.files(allClasspath), allSourceRoots.toList()) - } - - private fun Iterable<File>.toSourceRoots(): List<SourceRoot> = this.filter { it.exists() }.map { SourceRoot().apply { path = it.path } } - - protected open fun collectSuppressedFiles(sourceRoots: List<SourceRoot>): List<String> = emptyList() - - @TaskAction - fun generate() { - if (dokkaRuntime == null){ - dokkaRuntime = project.configurations.getByName("dokkaRuntime") - } - - dokkaRuntime?.defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create(dokkaFatJar)) } - val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false" - System.setProperty(COLORS_ENABLED_PROPERTY, "false") - try { - loadFatJar() - - val (tasksClasspath, tasksSourceRoots) = kotlinCompileBasedClasspathAndSourceRoots - - val project = project - val sourceRoots = collectSourceRoots() + tasksSourceRoots.toSourceRoots() - - if (sourceRoots.isEmpty()) { - logger.warn("No source directories found: skipping dokka generation") - return - } - - val fullClasspath = collectClasspathFromOldSources() + tasksClasspath + classpath - - val bootstrapClass = fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl") - - val bootstrapInstance = bootstrapClass.constructors.first().newInstance() - - val bootstrapProxy: DokkaBootstrap = automagicTypedProxy(javaClass.classLoader, bootstrapInstance) - - val configuration = SerializeOnlyDokkaConfiguration( - moduleName, - fullClasspath.map { it.absolutePath }, - sourceRoots, - samples.filterNotNull().map { project.file(it).absolutePath }, - includes.filterNotNull().map { project.file(it).absolutePath }, - outputDirectory, - outputFormat, - includeNonPublic, - false, - reportUndocumented, - skipEmptyPackages, - skipDeprecated, - jdkVersion, - true, - linkMappings, - impliedPlatforms, - perPackageOptions, - externalDocumentationLinks, - noStdlibLink, - noJdkLink, - cacheRoot, - collectSuppressedFiles(sourceRoots), - languageVersion, - apiVersion, - collectInheritedExtensionsFromLibraries - ) - - - bootstrapProxy.configure( - BiConsumer { level, message -> - when (level) { - "info" -> logger.info(message) - "warn" -> logger.warn(message) - "error" -> logger.error(message) - } - }, - serialize(configuration) - ) - - bootstrapProxy.generate() - - } finally { - System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore) - } - } - - private fun collectClasspathFromOldSources(): List<File> { - - val allConfigurations = project.configurations - - val fromConfigurations = - processConfigurations.flatMap { allConfigurations.getByName(it.toString()) } - - return fromConfigurations - } - - private fun collectSourceRoots(): List<SourceRoot> { - val sourceDirs = if (sourceDirs.any()) { - logger.info("Dokka: Taking source directories provided by the user") - sourceDirs.toSet() - } else if (kotlinTasks.isEmpty()) { - project.convention.findPlugin(JavaPluginConvention::class.java)?.let { javaPluginConvention -> - logger.info("Dokka: Taking source directories from default java plugin") - val sourceSets = javaPluginConvention.sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME) - sourceSets?.allSource?.srcDirs - } - } else { - emptySet() - } - - return sourceRoots + (sourceDirs?.toSourceRoots() ?: emptyList()) - } - - - @Classpath - fun getInputClasspath(): FileCollection { - val (classpathFileCollection) = extractClasspathAndSourceRootsFromKotlinTasks() - return project.files(collectClasspathFromOldSources() + classpath) + classpathFileCollection - } - - @InputFiles - fun getInputFiles(): FileCollection { - val (_, tasksSourceRoots) = extractClasspathAndSourceRootsFromKotlinTasks() - return project.files(tasksSourceRoots.map { project.fileTree(it) }) + - project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) + - project.files(includes) + - project.files(samples.filterNotNull().map { project.fileTree(it) }) - } - - @OutputDirectory - fun getOutputDirectoryAsFile(): File = project.file(outputDirectory) - - companion object { - const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled" - const val ABSTRACT_KOTLIN_COMPILE = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" - - private fun getAbstractKotlinCompileFor(task: Task) = try { - task.project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE) - } catch (e: ClassNotFoundException) { - null - } - } -} - -class SourceRoot : DokkaConfiguration.SourceRoot, Serializable { - override var path: String = "" - set(value) { - field = File(value).absolutePath - } - - override var platforms: List<String> = arrayListOf() - - override fun toString(): String { - return "${platforms.joinToString()}::$path" - } -} - -open class LinkMapping : Serializable, DokkaConfiguration.SourceLinkDefinition { - @JsonExclude - var dir: String - get() = path - set(value) { - if (value.contains("\\")) - throw java.lang.IllegalArgumentException("Incorrect dir property, only Unix based path allowed.") - else path = value - } - - override var path: String = "" - override var url: String = "" - - @JsonExclude - var suffix: String? - get() = lineSuffix - set(value) { - lineSuffix = value - } - - override var lineSuffix: String? = null - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other?.javaClass != javaClass) return false - - other as LinkMapping - - if (path != other.path) return false - if (url != other.url) return false - if (lineSuffix != other.lineSuffix) return false - - return true - } - - override fun hashCode(): Int { - var result = path.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + (lineSuffix?.hashCode() ?: 0) - return result - } - - companion object { - const val serialVersionUID: Long = -8133501684312445981L - } -} - -class PackageOptions : Serializable, DokkaConfiguration.PackageOptions { - override var prefix: String = "" - override var includeNonPublic: Boolean = false - override var reportUndocumented: Boolean = true - override var skipDeprecated: Boolean = false - override var suppress: Boolean = false -} diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt new file mode 100644 index 00000000..c66998d9 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ConfigurationExtractor.kt @@ -0,0 +1,217 @@ +package org.jetbrains.dokka.gradle + +import com.android.build.gradle.* +import com.android.build.gradle.api.BaseVariant +import com.android.builder.core.BuilderConstants +import org.gradle.api.NamedDomainObjectCollection +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.UnknownDomainObjectException +import org.gradle.api.artifacts.ResolveException +import org.gradle.api.file.FileCollection +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.compile.AbstractCompile +import org.jetbrains.dokka.ReflectDsl +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult +import java.io.File +import java.io.Serializable + +class ConfigurationExtractor(private val project: Project) { + + fun extractFromSinglePlatform(variantName: String? = null): PlatformData? { + val target: KotlinTarget + try { + target = project.extensions.getByType(KotlinSingleTargetExtension::class.java).target + } catch (e: Throwable) { + when (e){ + is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> + return null + else -> throw e + } + } + + return try { + PlatformData(null, getClasspath(target, variantName), getSourceSet(target, variantName), getPlatformName(target.platformType)) + } catch(e: NoSuchMethodError){ + null + } + } + + fun extractFromMultiPlatform(): List<PlatformData>? { + val targets: NamedDomainObjectCollection<KotlinTarget> + try { + targets = project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets + } catch (e: Throwable) { + when (e){ + is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> + return null + else -> throw e + } + } + + val commonTargetPlatformData = targets.find { it.platformType == KotlinPlatformType.common }?.let { + PlatformData("common", getClasspath(it), getSourceSet(it), "common") + } + val config = targets.filter { it.platformType != KotlinPlatformType.common }.map { + PlatformData(it.name, getClasspath(it), getSourceSet(it), it.platformType.toString()) + } + + return (config + commonTargetPlatformData).filterNotNull() + } + + fun extractFromJavaPlugin(): PlatformData? = + project.convention.findPlugin(JavaPluginConvention::class.java) + ?.run { sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)?.allSource?.srcDirs } + ?.let { PlatformData(null, emptyList(), it.toList(), "") } + + fun extractFromKotlinTasks(kotlinTasks: List<Task>): PlatformData? = + try { + kotlinTasks.map { extractFromKotlinTask(it) }.let { platformDataList -> + PlatformData(null, platformDataList.flatMap { it.classpath }, platformDataList.flatMap { it.sourceRoots }, "") + } + } catch (e: Throwable) { + when (e){ + is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> + extractFromKotlinTasksTheHardWay(kotlinTasks) + else -> throw e + } + } + + private fun extractFromKotlinTask(task: Task): PlatformData = + try { + project.extensions.getByType(KotlinSingleTargetExtension::class.java).target + .compilations + .find { it.compileKotlinTask == task } + } catch (e: Throwable) { + when (e){ + is UnknownDomainObjectException, is NoClassDefFoundError, is ClassNotFoundException -> + project.extensions.getByType(KotlinMultiplatformExtension::class.java).targets + .firstNotNullResult { target -> target.compilations.find { it.compileKotlinTask == task } } + else -> throw e + } + }.let { PlatformData(task.name, getClasspath(it), getSourceSet(it), it?.platformType?.toString() ?: "") } + + private fun extractFromKotlinTasksTheHardWay(kotlinTasks: List<Task>): PlatformData? { + val allClasspath = mutableSetOf<File>() + var allClasspathFileCollection: FileCollection = project.files() + val allSourceRoots = mutableSetOf<File>() + + kotlinTasks.forEach { + with(ReflectDsl) { + val taskSourceRoots: List<File> + val abstractKotlinCompileClz: Class<out Any> + try { + taskSourceRoots = it["sourceRootsContainer"]["sourceRoots"].v() + abstractKotlinCompileClz = DokkaTask.getAbstractKotlinCompileFor(it)!! + } catch (e: NullPointerException) { + println("Error during extraction of sources from kotlinTasks. This may be a result of outdated Kotlin Gradle Plugin") + return null + } + + val taskClasspath: Iterable<File> = + (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke() + ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v() + ?: it["getClasspath", abstractKotlinCompileClz]()) + + if (taskClasspath is FileCollection) { + allClasspathFileCollection += taskClasspath + } else { + allClasspath += taskClasspath + } + allSourceRoots += taskSourceRoots.filter { it.exists() } + } + } + val classpath: MutableList<File> = try { + allClasspathFileCollection.toMutableList() + } catch (e: ResolveException) { + mutableListOf() + } + classpath.addAll (project.files(allClasspath).toList()) + + return PlatformData(null, classpath, allSourceRoots.toList(), "") + } + + private fun getSourceSet(target: KotlinTarget, variantName: String? = null): List<File> = + if(variantName != null) + getSourceSet(getCompilation(target, variantName)) + else + getSourceSet(getMainCompilation(target)) + + private fun getClasspath(target: KotlinTarget, variantName: String? = null): List<File> = if (target.isAndroidTarget()) { + if(variantName != null) + getClasspathFromAndroidTask(getCompilation(target, variantName)) + else + getClasspathFromAndroidTask(getMainCompilation(target)) + } else { + getClasspath(getMainCompilation(target)) + } + + private fun getSourceSet(compilation: KotlinCompilation<*>?): List<File> = compilation + ?.allKotlinSourceSets + ?.flatMap { it.kotlin.sourceDirectories } + ?.filter { it.exists() } + .orEmpty() + + private fun getClasspath(compilation: KotlinCompilation<*>?): List<File> = compilation + ?.compileDependencyFiles + ?.files + ?.toList() + ?.filter { it.exists() } + .orEmpty() + + // This is a workaround for KT-33893 + private fun getClasspathFromAndroidTask(compilation: KotlinCompilation<*>): List<File> = (compilation + .compileKotlinTask as? KotlinCompile) + ?.classpath?.files?.toList() ?: getClasspath(compilation) + + private fun getMainCompilation(target: KotlinTarget) = + getCompilation(target, getMainCompilationName(target)) + + private fun getCompilation(target: KotlinTarget, name: String) = + target.compilations.getByName(name) + + private fun getMainCompilationName(target: KotlinTarget) = if (target.isAndroidTarget()) + getVariants(project).filter { it.buildType.name == BuilderConstants.RELEASE }.map { it.name }.first() + else + KotlinCompilation.MAIN_COMPILATION_NAME + + private fun getVariants(project: Project): Set<BaseVariant> { + val androidExtension = project.extensions.getByName("android") + val baseVariants = when (androidExtension) { + is AppExtension -> androidExtension.applicationVariants.toSet() + is LibraryExtension -> { + androidExtension.libraryVariants.toSet() + + if (androidExtension is FeatureExtension) { + androidExtension.featureVariants.toSet() + } else { + emptySet<BaseVariant>() + } + } + is TestExtension -> androidExtension.applicationVariants.toSet() + else -> emptySet() + } + val testVariants = if (androidExtension is TestedExtension) { + androidExtension.testVariants.toSet() + androidExtension.unitTestVariants.toSet() + } else { + emptySet<BaseVariant>() + } + + return baseVariants + testVariants + } + + private fun getPlatformName(platform: KotlinPlatformType): String = + if (platform == KotlinPlatformType.androidJvm) KotlinPlatformType.jvm.toString() else platform.toString() + + data class PlatformData(val name: String?, + val classpath: List<File>, + val sourceRoots: List<File>, + val platform: String) : Serializable +}
\ No newline at end of file diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt new file mode 100644 index 00000000..5153ae1c --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/DokkaTask.kt @@ -0,0 +1,353 @@ +package org.jetbrains.dokka.gradle + +import com.google.gson.GsonBuilder +import org.gradle.api.* +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.plugins.DslObject +import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.api.tasks.* +import org.jetbrains.dokka.DokkaBootstrap +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink.Builder +import org.jetbrains.dokka.DokkaConfiguration.SourceRoot +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.ReflectDsl +import org.jetbrains.dokka.ReflectDsl.isNotInstance +import org.jetbrains.dokka.gradle.ConfigurationExtractor.PlatformData +import java.io.File +import java.net.URLClassLoader +import java.util.concurrent.Callable +import java.util.function.BiConsumer + +open class DokkaTask : DefaultTask() { + private val ANDROID_REFERENCE_URL = Builder("https://developer.android.com/reference/").build() + private val GLOBAL_PLATFORM_NAME = "global" // Used for copying perPackageOptions to other platforms + + @Suppress("MemberVisibilityCanBePrivate") + fun defaultKotlinTasks(): List<Task> = with(ReflectDsl) { + val abstractKotlinCompileClz = try { + project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE) + } catch (cnfe: ClassNotFoundException) { + logger.warn("$ABSTRACT_KOTLIN_COMPILE class not found, default kotlin tasks ignored") + return@with emptyList<Task>() + } + + return@with project.tasks.filter { it isInstance abstractKotlinCompileClz }.filter { "Test" !in it.name } + } + + init { + group = JavaBasePlugin.DOCUMENTATION_GROUP + description = "Generates dokka documentation for Kotlin" + + @Suppress("LeakingThis") + dependsOn(Callable { kotlinTasks.map { it.taskDependencies } }) + } + + @Input + var outputFormat: String = "html" + + @Input + var outputDirectory: String = "" + + var dokkaRuntime: Configuration? = null + + @Input + var impliedPlatforms: MutableList<String> = arrayListOf() + + @Optional + @Input + var cacheRoot: String? = null + + var multiplatform: NamedDomainObjectContainer<GradlePassConfigurationImpl> + @Suppress("UNCHECKED_CAST") + @Nested get() = (DslObject(this).extensions.getByName(MULTIPLATFORM_EXTENSION_NAME) as NamedDomainObjectContainer<GradlePassConfigurationImpl>) + internal set(value) = DslObject(this).extensions.add(MULTIPLATFORM_EXTENSION_NAME, value) + + var configuration: GradlePassConfigurationImpl + @Suppress("UNCHECKED_CAST") + @Nested get() = DslObject(this).extensions.getByType(GradlePassConfigurationImpl::class.java) + internal set(value) = DslObject(this).extensions.add(CONFIGURATION_EXTENSION_NAME, value) + + // Configure Dokka with closure in Gradle Kotlin DSL + fun configuration(action: Action<in GradlePassConfigurationImpl>) = action.execute(configuration) + + private var externalDocumentationLinks: MutableList<DokkaConfiguration.ExternalDocumentationLink> = mutableListOf() + + private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks(configuration.collectKotlinTasks ?: { defaultKotlinTasks() }) } + + private val configExtractor = ConfigurationExtractor(project) + + @Input + var subProjects: List<String> = emptyList() + + @Input + var disableAutoconfiguration: Boolean = false + + private var outputDiagnosticInfo: Boolean = false // Workaround for Gradle, which fires some methods (like collectConfigurations()) multiple times in its lifecycle + + private fun tryResolveFatJar(configuration: Configuration?): Set<File> { + return try { + configuration!!.resolve() + } catch (e: Exception) { + project.parent?.let { tryResolveFatJar(configuration) } ?: throw e + } + } + + private fun loadFatJar() { + if (ClassloaderContainer.fatJarClassLoader == null) { + val jars = tryResolveFatJar(dokkaRuntime).toList() + ClassloaderContainer.fatJarClassLoader = URLClassLoader(jars.map { it.toURI().toURL() }.toTypedArray(), ClassLoader.getSystemClassLoader().parent) + } + } + + private fun extractKotlinCompileTasks(collectTasks: () -> List<Any?>?): List<Task> { + val inputList = (collectTasks.invoke() ?: emptyList()).filterNotNull() + val (paths, other) = inputList.partition { it is String } + + val taskContainer = project.tasks + + val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") } + + other + .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) } + .forEach { throw IllegalArgumentException("Illegal entry in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE or String, but was $it") } + + tasksByPath + .filter { it isNotInstance getAbstractKotlinCompileFor(it) } + .forEach { throw IllegalArgumentException("Illegal task path in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE, but was $it") } + + @Suppress("UNCHECKED_CAST") + return (tasksByPath + other) as List<Task> + } + + private fun Iterable<File>.toSourceRoots(): List<GradleSourceRootImpl> = this.filter { it.exists() }.map { GradleSourceRootImpl().apply { path = it.path } } + private fun Iterable<String>.toProjects(): List<Project> = project.subprojects.toList().filter { this.contains(it.name) } + + private fun collectSuppressedFiles(sourceRoots: List<SourceRoot>) = + if(project.isAndroidProject()) { + val generatedRoot = project.buildDir.resolve("generated").absoluteFile + sourceRoots + .map { File(it.path) } + .filter { it.startsWith(generatedRoot) } + .flatMap { it.walk().toList() } + .map { it.absolutePath } + } else { + emptyList() + } + + @TaskAction + fun generate() { + outputDiagnosticInfo = true + val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false" + System.setProperty(COLORS_ENABLED_PROPERTY, "false") + try { + loadFatJar() + + val bootstrapClass = ClassloaderContainer.fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl") + val bootstrapInstance = bootstrapClass.constructors.first().newInstance() + val bootstrapProxy: DokkaBootstrap = + automagicTypedProxy(javaClass.classLoader, bootstrapInstance) + + val gson = GsonBuilder().setPrettyPrinting().create() + + val globalConfig = multiplatform.toList().find { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME } + val passConfigurationList = collectConfigurations() + .map { defaultPassConfiguration(globalConfig, it) } + + val configuration = GradleDokkaConfigurationImpl() + configuration.outputDir = outputDirectory + configuration.format = outputFormat + configuration.generateIndexPages = true + configuration.cacheRoot = cacheRoot + configuration.impliedPlatforms = impliedPlatforms + configuration.passesConfigurations = passConfigurationList + + bootstrapProxy.configure( + BiConsumer { level, message -> + when (level) { + "info" -> logger.info(message) + "warn" -> logger.warn(message) + "error" -> logger.error(message) + } + }, + gson.toJson(configuration) + ) + + bootstrapProxy.generate() + + } finally { + System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore) + } + } + + private fun collectConfigurations(): List<GradlePassConfigurationImpl> = + if (this.isMultiplatformProject()) collectFromMultiPlatform() else collectFromSinglePlatform() + + private fun collectFromMultiPlatform(): List<GradlePassConfigurationImpl> { + val userConfig = multiplatform + .filterNot { it.name.toLowerCase() == GLOBAL_PLATFORM_NAME } + .map { + if (it.collectKotlinTasks != null) { + configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!)) + ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it + } else { + it + } + } + + if (disableAutoconfiguration) return userConfig + + val baseConfig = mergeUserAndAutoConfigurations( + userConfig, + configExtractor.extractFromMultiPlatform().orEmpty() + ) + + return if (subProjects.isNotEmpty()) + subProjects.toProjects().fold(baseConfig) { list, subProject -> + mergeUserAndAutoConfigurations(list, ConfigurationExtractor(subProject).extractFromMultiPlatform().orEmpty()) + } + else + baseConfig + } + + private fun collectFromSinglePlatform(): List<GradlePassConfigurationImpl> { + val userConfig = configuration.let { + if (it.collectKotlinTasks != null) { + configExtractor.extractFromKotlinTasks(extractKotlinCompileTasks(it.collectKotlinTasks!!)) + ?.let { platformData -> mergeUserConfigurationAndPlatformData(it, platformData) } ?: it + } else { + it + } + } + + if (disableAutoconfiguration) return listOf(userConfig) + + val extractedConfig = configExtractor.extractFromSinglePlatform(userConfig.androidVariant) + val baseConfig = if (extractedConfig != null) + listOf(mergeUserConfigurationAndPlatformData(userConfig, extractedConfig)) + else + collectFromSinglePlatformOldPlugin() + + return if (subProjects.isNotEmpty()) { + try { + subProjects.toProjects().fold(baseConfig) { list, subProject -> + listOf(mergeUserConfigurationAndPlatformData( + list.first(), + ConfigurationExtractor(subProject).extractFromSinglePlatform()!! + )) + } + } catch(e: NullPointerException) { + logger.warn("Cannot extract sources from subProjects. Do you have the Kotlin plugin in version 1.3.30+ " + + "and the Kotlin plugin applied in the root project?") + baseConfig + } + } else { + baseConfig + } + } + + private fun collectFromSinglePlatformOldPlugin(): List<GradlePassConfigurationImpl> { + val kotlinTasks = configExtractor.extractFromKotlinTasks(kotlinTasks) + return if (kotlinTasks != null) { + listOf(mergeUserConfigurationAndPlatformData(configuration, kotlinTasks)) + } else { + val javaPlugin = configExtractor.extractFromJavaPlugin() + if (javaPlugin != null) + listOf(mergeUserConfigurationAndPlatformData(configuration, javaPlugin)) else listOf(configuration) + } + } + + private fun mergeUserAndAutoConfigurations(userConfigurations: List<GradlePassConfigurationImpl>, + autoConfigurations: List<PlatformData>): List<GradlePassConfigurationImpl> { + val merged: MutableList<GradlePassConfigurationImpl> = mutableListOf() + merged.addAll( + userConfigurations.map { userConfig -> + val autoConfig = autoConfigurations.find { autoConfig -> autoConfig.name == userConfig.name } + if (autoConfig != null) { + mergeUserConfigurationAndPlatformData(userConfig, autoConfig) + } else { + if(outputDiagnosticInfo) { + logger.warn( + "Could not find platform with name: ${userConfig.name} in Kotlin Gradle Plugin, " + + "using only user provided configuration for this platform" + ) + } + userConfig + } + } + ) + return merged.toList() + } + + private fun mergeUserConfigurationAndPlatformData(userConfig: GradlePassConfigurationImpl, + autoConfig: PlatformData): GradlePassConfigurationImpl = + userConfig.copy().apply { + sourceRoots.addAll(userConfig.sourceRoots.union(autoConfig.sourceRoots.toSourceRoots()).distinct()) + classpath = userConfig.classpath.union(autoConfig.classpath.map { it.absolutePath }).distinct() + if (userConfig.platform == null && autoConfig.platform != "") + platform = autoConfig.platform + } + + private fun defaultPassConfiguration(globalConfig: GradlePassConfigurationImpl?, config: GradlePassConfigurationImpl): GradlePassConfigurationImpl { + if (config.moduleName == "") { + config.moduleName = project.name + } + if (config.targets.isEmpty() && multiplatform.isNotEmpty()){ + config.targets = listOf(config.name) + } + config.classpath = (config.classpath as List<Any>).map { it.toString() }.distinct() // Workaround for Groovy's GStringImpl + config.sourceRoots = config.sourceRoots.distinct().toMutableList() + config.samples = config.samples.map { project.file(it).absolutePath } + config.includes = config.includes.map { project.file(it).absolutePath } + config.suppressedFiles += collectSuppressedFiles(config.sourceRoots) + if (project.isAndroidProject() && !config.noAndroidSdkLink) { // TODO: introduce Android as a separate Dokka platform? + config.externalDocumentationLinks.add(ANDROID_REFERENCE_URL) + } + config.externalDocumentationLinks.addAll(externalDocumentationLinks) + if (config.platform != null && config.platform.toString().isNotEmpty()) { + config.analysisPlatform = dokkaPlatformFromString(config.platform.toString()) + } + if (globalConfig != null) { + config.perPackageOptions.addAll(globalConfig.perPackageOptions) + config.externalDocumentationLinks.addAll(globalConfig.externalDocumentationLinks) + config.sourceLinks.addAll(globalConfig.sourceLinks) + config.samples += globalConfig.samples.map { project.file(it).absolutePath } + config.includes += globalConfig.includes.map { project.file(it).absolutePath } + } + return config + } + + private fun dokkaPlatformFromString(platform: String) = when (platform.toLowerCase()) { + "androidjvm", "android" -> Platform.jvm + else -> Platform.fromString(platform) + } + + // Needed for Gradle incremental build + @OutputDirectory + fun getOutputDirectoryAsFile(): File = project.file(outputDirectory) + + // Needed for Gradle incremental build + @InputFiles + fun getInputFiles(): FileCollection { + val config = collectConfigurations() + return project.files(config.flatMap { it.sourceRoots }.map { project.fileTree(File(it.path)) }) + + project.files(config.flatMap { it.includes }) + + project.files(config.flatMap { it.samples }.map { project.fileTree(File(it)) }) + } + + @Classpath + fun getInputClasspath(): FileCollection = + project.files((collectConfigurations().flatMap { it.classpath } as List<Any>).map { project.fileTree(File(it.toString())) }) + + companion object { + const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled" + const val ABSTRACT_KOTLIN_COMPILE = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" + + internal fun getAbstractKotlinCompileFor(task: Task) = try { + task.project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE) + } catch (e: ClassNotFoundException) { + null + } + } +} diff --git a/runners/gradle-plugin/src/main/kotlin/ProxyUtils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt index 7bdf2f9d..f8965993 100644 --- a/runners/gradle-plugin/src/main/kotlin/ProxyUtils.kt +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/ProxyUtils.kt @@ -1,4 +1,4 @@ -package org.jetbrains.dokka +package org.jetbrains.dokka.gradle import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationTargetException @@ -26,9 +26,9 @@ inline fun <reified T : Any> automagicTypedProxy(targetClassLoader: ClassLoader, */ fun automagicProxy(targetClassLoader: ClassLoader, targetType: Class<*>, delegate: Any): Any = Proxy.newProxyInstance( - targetClassLoader, - arrayOf(targetType), - DelegatedInvocationHandler(delegate) + targetClassLoader, + arrayOf(targetType), + DelegatedInvocationHandler(delegate) ) class DelegatedInvocationHandler(private val delegate: Any) : InvocationHandler { diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt new file mode 100644 index 00000000..767bf4f4 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/configurationImplementations.kt @@ -0,0 +1,150 @@ +package org.jetbrains.dokka.gradle + +import groovy.lang.Closure +import org.gradle.api.Action +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.util.ConfigureUtil +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfiguration.* +import org.jetbrains.dokka.Platform +import java.io.File +import java.io.Serializable +import java.net.URL +import java.util.concurrent.Callable +import kotlin.reflect.KMutableProperty +import kotlin.reflect.full.memberProperties + +class GradleSourceRootImpl: SourceRoot, Serializable { + override var path: String = "" + set(value) { + field = File(value).absolutePath + } + + override fun toString(): String = path +} + +open class GradlePassConfigurationImpl(@Transient val name: String = ""): PassConfiguration { + @Input @Optional override var classpath: List<String> = emptyList() + @Input override var moduleName: String = "" + @Input override var sourceRoots: MutableList<SourceRoot> = mutableListOf() + @Input override var samples: List<String> = emptyList() + @Input override var includes: List<String> = emptyList() + @Input override var includeNonPublic: Boolean = false + @Input override var includeRootPackage: Boolean = false + @Input override var reportUndocumented: Boolean = false + @Input override var skipEmptyPackages: Boolean = false + @Input override var skipDeprecated: Boolean = false + @Input override var jdkVersion: Int = 6 + @Input override var sourceLinks: MutableList<SourceLinkDefinition> = mutableListOf() + @Input override var perPackageOptions: MutableList<PackageOptions> = mutableListOf() + @Input override var externalDocumentationLinks: MutableList<ExternalDocumentationLink> = mutableListOf() + @Input @Optional override var languageVersion: String? = null + @Input @Optional override var apiVersion: String? = null + @Input override var noStdlibLink: Boolean = false + @Input override var noJdkLink: Boolean = false + @Input var noAndroidSdkLink: Boolean = false + @Input override var suppressedFiles: List<String> = emptyList() + @Input override var collectInheritedExtensionsFromLibraries: Boolean = false + @Input override var analysisPlatform: Platform = Platform.DEFAULT + @Input @Optional var platform: String? = null + @Input override var targets: List<String> = emptyList() + @Input @Optional override var sinceKotlin: String? = null + @Transient var collectKotlinTasks: (() -> List<Any?>?)? = null + @Input @Optional @Transient var androidVariant: String? = null + + fun kotlinTasks(taskSupplier: Callable<List<Any>>) { + collectKotlinTasks = { taskSupplier.call() } + } + + fun kotlinTasks(closure: Closure<Any?>) { + collectKotlinTasks = { closure.call() as? List<Any?> } + } + + fun sourceRoot(c: Closure<Unit>) { + val configured = ConfigureUtil.configure(c, GradleSourceRootImpl()) + sourceRoots.add(configured) + } + + fun sourceRoot(action: Action<in GradleSourceRootImpl>) { + val sourceRoot = GradleSourceRootImpl() + action.execute(sourceRoot) + sourceRoots.add(sourceRoot) + } + + fun sourceLink(c: Closure<Unit>) { + val configured = ConfigureUtil.configure(c, GradleSourceLinkDefinitionImpl()) + sourceLinks.add(configured) + } + + fun sourceLink(action: Action<in GradleSourceLinkDefinitionImpl>) { + val sourceLink = GradleSourceLinkDefinitionImpl() + action.execute(sourceLink) + sourceLinks.add(sourceLink) + } + + fun perPackageOption(c: Closure<Unit>) { + val configured = ConfigureUtil.configure(c, GradlePackageOptionsImpl()) + perPackageOptions.add(configured) + } + + fun perPackageOption(action: Action<in GradlePackageOptionsImpl>) { + val option = GradlePackageOptionsImpl() + action.execute(option) + perPackageOptions.add(option) + } + + fun externalDocumentationLink(c: Closure<Unit>) { + val link = ConfigureUtil.configure(c, GradleExternalDocumentationLinkImpl()) + externalDocumentationLinks.add(link) + } + + fun externalDocumentationLink(action: Action<in GradleExternalDocumentationLinkImpl>) { + val link = GradleExternalDocumentationLinkImpl() + action.execute(link) + externalDocumentationLinks.add(link) + } +} + +class GradleSourceLinkDefinitionImpl : SourceLinkDefinition, Serializable { + override var path: String = "" + override var url: String = "" + override var lineSuffix: String? = null +} + +class GradleExternalDocumentationLinkImpl : ExternalDocumentationLink, Serializable { + override var url: URL = URL("http://") + override var packageListUrl: URL = URL("http://") +} + +class GradleDokkaConfigurationImpl: DokkaConfiguration { + override var outputDir: String = "" + override var format: String = "html" + override var generateIndexPages: Boolean = false + override var cacheRoot: String? = null + override var impliedPlatforms: List<String> = emptyList() + override var passesConfigurations: List<GradlePassConfigurationImpl> = emptyList() +} + +class GradlePackageOptionsImpl: PackageOptions, Serializable { + override var prefix: String = "" + override var includeNonPublic: Boolean = false + override var reportUndocumented: Boolean = true + override var skipDeprecated: Boolean = true + override var suppress: Boolean = false +} + +fun GradlePassConfigurationImpl.copy(): GradlePassConfigurationImpl { + val newObj = GradlePassConfigurationImpl(this.name) + this::class.memberProperties.forEach { field -> + if (field is KMutableProperty<*>) { + val value = field.getter.call(this) + if (value is Collection<*>) { + field.setter.call(newObj, value.toMutableList()) + } else { + field.setter.call(newObj, field.getter.call(this)) + } + } + } + return newObj +}
\ No newline at end of file diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt new file mode 100644 index 00000000..6f8d55e4 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/main.kt @@ -0,0 +1,58 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.util.GradleVersion +import java.io.File +import java.io.InputStream +import java.util.* + +internal const val CONFIGURATION_EXTENSION_NAME = "configuration" +internal const val MULTIPLATFORM_EXTENSION_NAME = "multiplatform" + +open class DokkaPlugin : Plugin<Project> { + private val taskName = "dokka" + + override fun apply(project: Project) { + loadDokkaVersion() + val dokkaRuntimeConfiguration = addConfiguration(project) + addTasks(project, dokkaRuntimeConfiguration, DokkaTask::class.java) + } + + private fun loadDokkaVersion() = DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties")) + + private fun addConfiguration(project: Project) = + project.configurations.create("dokkaRuntime").apply { + defaultDependencies{ dependencies -> dependencies.add(project.dependencies.create("org.jetbrains.dokka:dokka-fatjar:${DokkaVersion.version}")) } + } + + private fun addTasks(project: Project, runtimeConfiguration: Configuration, taskClass: Class<out DokkaTask>) { + if(GradleVersion.current() >= GradleVersion.version("4.10")) { + project.tasks.register(taskName, taskClass) + } else { + project.tasks.create(taskName, taskClass) + } + project.tasks.withType(taskClass) { task -> + task.multiplatform = project.container(GradlePassConfigurationImpl::class.java) + task.configuration = GradlePassConfigurationImpl() + task.dokkaRuntime = runtimeConfiguration + task.outputDirectory = File(project.buildDir, taskName).absolutePath + } + } +} + +object DokkaVersion { + var version: String? = null + + fun loadFrom(stream: InputStream) { + version = Properties().apply { + load(stream) + }.getProperty("dokka-version") + } +} + +object ClassloaderContainer { + @JvmField + var fatJarClassLoader: ClassLoader? = null +}
\ No newline at end of file diff --git a/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt new file mode 100644 index 00000000..d70b0499 --- /dev/null +++ b/runners/gradle-plugin/src/main/kotlin/org/jetbrains/dokka/gradle/utils.kt @@ -0,0 +1,19 @@ +package org.jetbrains.dokka.gradle + +import org.gradle.api.Project +import org.gradle.api.UnknownDomainObjectException +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget + + +fun Project.isAndroidProject() = try { + project.extensions.getByName("android") + true +} catch(e: UnknownDomainObjectException) { + false +} catch(e: ClassNotFoundException) { + false +} + +fun KotlinTarget.isAndroidTarget() = this.platformType == KotlinPlatformType.androidJvm +fun DokkaTask.isMultiplatformProject() = this.multiplatform.isNotEmpty()
\ No newline at end of file diff --git a/runners/maven-plugin/build.gradle b/runners/maven-plugin/build.gradle index 2e9d0b1b..76fab68d 100644 --- a/runners/maven-plugin/build.gradle +++ b/runners/maven-plugin/build.gradle @@ -52,8 +52,8 @@ task setupMaven(type: Sync) { def mavenBuildDir = "$buildDir/maven" -sourceSets.main.resources { - srcDirs += "$mavenBuildDir/classes/java/main" +sourceSets.main.resources { + srcDirs += "$mavenBuildDir/classes/java/main" exclude "**/*.class" } diff --git a/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt b/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt index 324703a0..fb11ecac 100644 --- a/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt +++ b/runners/maven-plugin/src/main/kotlin/DokkaMojo.kt @@ -15,14 +15,14 @@ import java.io.File import java.net.URL class SourceLinkMapItem { - @Parameter(name = "dir", required = true) - var dir: String = "" + @Parameter(name = "path", required = true) + var path: String = "" @Parameter(name = "url", required = true) var url: String = "" - @Parameter(name = "urlSuffix") - var urlSuffix: String? = null + @Parameter(name = "lineSuffix") + var lineSuffix: String? = null } class ExternalDocumentationLinkBuilder : DokkaConfiguration.ExternalDocumentationLink.Builder() { @@ -37,9 +37,6 @@ abstract class AbstractDokkaMojo : AbstractMojo() { class SourceRoot : DokkaConfiguration.SourceRoot { @Parameter(required = true) override var path: String = "" - - @Parameter - override var platforms: List<String> = emptyList() } class PackageOptions : DokkaConfiguration.PackageOptions { @@ -62,11 +59,7 @@ abstract class AbstractDokkaMojo : AbstractMojo() { var sourceRoots: List<SourceRoot> = emptyList() @Parameter - var samplesDirs: List<String> = emptyList() - - @Parameter - @Deprecated("Use <includes> instead") - var includeDirs: List<String> = emptyList() + var samples: List<String> = emptyList() @Parameter var includes: List<String> = emptyList() @@ -75,7 +68,7 @@ abstract class AbstractDokkaMojo : AbstractMojo() { var classpath: List<String> = emptyList() @Parameter - var sourceLinks: Array<SourceLinkMapItem> = emptyArray() + var sourceLinks: List<SourceLinkMapItem> = emptyList() @Parameter(required = true, defaultValue = "\${project.artifactId}") var moduleName: String = "" @@ -87,11 +80,11 @@ abstract class AbstractDokkaMojo : AbstractMojo() { var jdkVersion: Int = 6 @Parameter - var skipDeprecated = false + var skipDeprecated: Boolean = false @Parameter - var skipEmptyPackages = true + var skipEmptyPackages: Boolean = true @Parameter - var reportNotDocumented = true + var reportUndocumented: Boolean = true @Parameter var impliedPlatforms: List<String> = emptyList() @@ -117,7 +110,32 @@ abstract class AbstractDokkaMojo : AbstractMojo() { @Parameter var apiVersion: String? = null + @Parameter + var includeRootPackage: Boolean = false + + @Parameter + var suppressedFiles: List<String> = emptyList() + + @Parameter + var collectInheritedExtensionsFromLibraries: Boolean = false + + @Parameter + var platform: String = "" + + @Parameter + var targets: List<String> = emptyList() + + @Parameter + var sinceKotlin: String? = null + + @Parameter + var includeNonPublic: Boolean = false + + @Parameter + var generateIndexPages: Boolean = false + protected abstract fun getOutDir(): String + protected abstract fun getOutFormat(): String override fun execute() { @@ -127,35 +145,55 @@ abstract class AbstractDokkaMojo : AbstractMojo() { } sourceLinks.forEach { - if (it.dir.contains("\\")) { - throw MojoExecutionException("Incorrect dir property, only Unix based path allowed.") + if (it.path.contains("\\")) { + throw MojoExecutionException("Incorrect path property, only Unix based path allowed.") } } - val gen = DokkaGenerator( - MavenDokkaLogger(log), - classpath, - sourceDirectories.map { SourceRootImpl(it) } + sourceRoots, - samplesDirs, - includeDirs + includes, - moduleName, - DocumentationOptions(getOutDir(), getOutFormat(), - sourceLinks = sourceLinks.map { SourceLinkDefinitionImpl(it.dir, it.url, it.urlSuffix) }, - jdkVersion = jdkVersion, - skipDeprecated = skipDeprecated, - skipEmptyPackages = skipEmptyPackages, - reportUndocumented = reportNotDocumented, - impliedPlatforms = impliedPlatforms, - perPackageOptions = perPackageOptions, - externalDocumentationLinks = externalDocumentationLinks.map { it.build() }, - noStdlibLink = noStdlibLink, - noJdkLink = noJdkLink, - cacheRoot = cacheRoot, - languageVersion = languageVersion, - apiVersion = apiVersion - ) + val passConfiguration = PassConfigurationImpl( + classpath = classpath, + sourceRoots = sourceDirectories.map { SourceRootImpl(it) } + sourceRoots.map { SourceRootImpl(path = it.path) }, + samples = samples, + includes = includes, + collectInheritedExtensionsFromLibraries = collectInheritedExtensionsFromLibraries, // TODO: Should we implement this? + sourceLinks = sourceLinks.map { SourceLinkDefinitionImpl(it.path, it.url, it.lineSuffix) }, + jdkVersion = jdkVersion, + skipDeprecated = skipDeprecated, + skipEmptyPackages = skipEmptyPackages, + reportUndocumented = reportUndocumented, + perPackageOptions = perPackageOptions.map { + PackageOptionsImpl( + prefix = it.prefix, + includeNonPublic = it.includeNonPublic, + reportUndocumented = it.reportUndocumented, + skipDeprecated = it.skipDeprecated, + suppress = it.suppress + )}, + externalDocumentationLinks = externalDocumentationLinks.map { it.build() as ExternalDocumentationLinkImpl }, + noStdlibLink = noStdlibLink, + noJdkLink = noJdkLink, + languageVersion = languageVersion, + apiVersion = apiVersion, + moduleName = moduleName, + suppressedFiles = suppressedFiles, + sinceKotlin = sinceKotlin, + analysisPlatform = if (platform.isNotEmpty()) Platform.fromString(platform) else Platform.DEFAULT, + targets = targets, + includeNonPublic = includeNonPublic, + includeRootPackage = includeRootPackage ) + val configuration = DokkaConfigurationImpl( + outputDir = getOutDir(), + format = getOutFormat(), + impliedPlatforms = impliedPlatforms, + cacheRoot = cacheRoot, + passesConfigurations = listOf(passConfiguration), + generateIndexPages = generateIndexPages + ) + + val gen = DokkaGenerator(configuration, MavenDokkaLogger(log)) + gen.generate() } } @@ -246,11 +284,11 @@ class DokkaJavadocJarMojo : AbstractDokkaMojo() { val javadocJar = File(jarOutputDirectory, jarFileName) val archiver = MavenArchiver() - archiver.setArchiver(jarArchiver) + archiver.archiver = jarArchiver archiver.setOutputFile(javadocJar) archiver.archiver.addDirectory(File(outputDir), arrayOf("**/**"), arrayOf()) - archive.setAddMavenDescriptor(false) + archive.isAddMavenDescriptor = false archiver.createArchive(session, project, archive) return javadocJar |