aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/main.kt
diff options
context:
space:
mode:
authorDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:11 +0100
committerDmitry Jemerov <yole@jetbrains.com>2015-12-03 16:22:49 +0100
commit39631054c58df5841ea268b7002b820ec55f6e0a (patch)
treecefedd8411c859243bd181568e16fcdd372a38c8 /core/src/main/kotlin/main.kt
parent797cb4732c53bf1e3b2091add8cf731fc436607f (diff)
downloaddokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.gz
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.tar.bz2
dokka-39631054c58df5841ea268b7002b820ec55f6e0a.zip
restructure Dokka build to use Gradle for everything except for the Maven plugin
Diffstat (limited to 'core/src/main/kotlin/main.kt')
-rw-r--r--core/src/main/kotlin/main.kt262
1 files changed, 262 insertions, 0 deletions
diff --git a/core/src/main/kotlin/main.kt b/core/src/main/kotlin/main.kt
new file mode 100644
index 00000000..22e82991
--- /dev/null
+++ b/core/src/main/kotlin/main.kt
@@ -0,0 +1,262 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Guice
+import com.google.inject.Injector
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiManager
+import com.sampullara.cli.Args
+import com.sampullara.cli.Argument
+import org.jetbrains.dokka.Utilities.DokkaModule
+import org.jetbrains.kotlin.cli.common.arguments.ValueDescription
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
+import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.config.CommonConfigurationKeys
+import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzerForTopLevel
+import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
+import org.jetbrains.kotlin.utils.PathUtil
+import java.io.File
+import kotlin.util.measureTimeMillis
+
+class DokkaArguments {
+ @set:Argument(value = "src", description = "Source file or directory (allows many paths separated by the system path separator)")
+ @ValueDescription("<path>")
+ public var src: String = ""
+
+ @set:Argument(value = "srcLink", description = "Mapping between a source directory and a Web site for browsing the code")
+ @ValueDescription("<path>=<url>[#lineSuffix]")
+ public var srcLink: String = ""
+
+ @set:Argument(value = "include", description = "Markdown files to load (allows many paths separated by the system path separator)")
+ @ValueDescription("<path>")
+ public var include: String = ""
+
+ @set:Argument(value = "samples", description = "Source root for samples")
+ @ValueDescription("<path>")
+ public var samples: String = ""
+
+ @set:Argument(value = "output", description = "Output directory path")
+ @ValueDescription("<path>")
+ public var outputDir: String = "out/doc/"
+
+ @set:Argument(value = "format", description = "Output format (text, html, markdown, jekyll, kotlin-website)")
+ @ValueDescription("<name>")
+ public var outputFormat: String = "html"
+
+ @set:Argument(value = "module", description = "Name of the documentation module")
+ @ValueDescription("<name>")
+ public var moduleName: String = ""
+
+ @set:Argument(value = "classpath", description = "Classpath for symbol resolution")
+ @ValueDescription("<path>")
+ public var classpath: String = ""
+
+ @set:Argument(value = "nodeprecated", description = "Exclude deprecated members from documentation")
+ public var nodeprecated: Boolean = false
+
+}
+
+private fun parseSourceLinkDefinition(srcLink: String): SourceLinkDefinition {
+ val (path, urlAndLine) = srcLink.split('=')
+ return SourceLinkDefinition(File(path).absolutePath,
+ urlAndLine.substringBefore("#"),
+ urlAndLine.substringAfter("#", "").let { if (it.isEmpty()) null else "#" + it })
+}
+
+public fun main(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(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 generator = DokkaGenerator(
+ DokkaConsoleLogger,
+ classPath,
+ sources,
+ samples,
+ includes,
+ arguments.moduleName,
+ arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' },
+ arguments.outputFormat,
+ sourceLinks,
+ arguments.nodeprecated)
+
+ generator.generate()
+ DokkaConsoleLogger.report()
+}
+
+interface DokkaLogger {
+ fun info(message: String)
+ fun warn(message: String)
+ fun error(message: String)
+}
+
+object DokkaConsoleLogger: DokkaLogger {
+ var warningCount: Int = 0
+
+ override fun info(message: String) = println(message)
+ override fun warn(message: String) {
+ println("WARN: $message")
+ warningCount++
+ }
+
+ override fun error(message: String) = println("ERROR: $message")
+
+ fun report() {
+ if (warningCount > 0) {
+ println("generation completed with $warningCount warnings")
+ } else {
+ println("generation completed successfully")
+ }
+ }
+}
+
+class DokkaMessageCollector(val logger: DokkaLogger): MessageCollector {
+ override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
+ logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location))
+ }
+}
+
+class DokkaGenerator(val logger: DokkaLogger,
+ val classpath: List<String>,
+ val sources: List<String>,
+ val samples: List<String>,
+ val includes: List<String>,
+ val moduleName: String,
+ val outputDir: String,
+ val outputFormat: String,
+ val sourceLinks: List<SourceLinkDefinition>,
+ val skipDeprecated: Boolean = false) {
+ fun generate() {
+ val environment = createAnalysisEnvironment()
+
+ logger.info("Module: $moduleName")
+ logger.info("Output: ${File(outputDir)}")
+ logger.info("Sources: ${environment.sources.joinToString()}")
+ logger.info("Classpath: ${environment.classpath.joinToString()}")
+
+ logger.info("Analysing sources and libraries... ")
+ val startAnalyse = System.currentTimeMillis()
+
+ val options = DocumentationOptions(outputDir, outputFormat, false, sourceLinks = sourceLinks, skipDeprecated = skipDeprecated)
+
+ val injector = Guice.createInjector(DokkaModule(environment, options, logger))
+
+ val documentation = buildDocumentationModule(injector, moduleName, { isSample(it) }, includes)
+
+ val timeAnalyse = System.currentTimeMillis() - startAnalyse
+ logger.info("done in ${timeAnalyse / 1000} secs")
+
+ val timeBuild = measureTimeMillis {
+ logger.info("Generating pages... ")
+ injector.getInstance(Generator::class.java).buildAll(documentation)
+ }
+ logger.info("done in ${timeBuild / 1000} secs")
+
+ Disposer.dispose(environment)
+ }
+
+ fun createAnalysisEnvironment(): AnalysisEnvironment {
+ val environment = AnalysisEnvironment(DokkaMessageCollector(logger))
+
+ environment.apply {
+ addClasspath(PathUtil.getJdkClassesRoots())
+ // addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
+ for (element in this@DokkaGenerator.classpath) {
+ addClasspath(File(element))
+ }
+
+ addSources(this@DokkaGenerator.sources)
+ addSources(this@DokkaGenerator.samples)
+ }
+
+ return environment
+ }
+
+ fun isSample(file: PsiFile): Boolean {
+ val sourceFile = File(file.virtualFile!!.path)
+ return samples.none { sample ->
+ val canonicalSample = File(sample).canonicalPath
+ val canonicalSource = sourceFile.canonicalPath
+ canonicalSource.startsWith(canonicalSample)
+ }
+ }
+}
+
+fun buildDocumentationModule(injector: Injector,
+ moduleName: String,
+ filesToDocumentFilter: (PsiFile) -> Boolean = { file -> true },
+ includes: List<String> = listOf()): DocumentationModule {
+
+ val coreEnvironment = injector.getInstance(KotlinCoreEnvironment::class.java)
+ val fragmentFiles = coreEnvironment.getSourceFiles().filter(filesToDocumentFilter)
+
+ val resolutionFacade = injector.getInstance(DokkaResolutionFacade::class.java)
+ val analyzer = resolutionFacade.getFrontendService(LazyTopDownAnalyzerForTopLevel::class.java)
+ analyzer.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, fragmentFiles)
+
+ val fragments = fragmentFiles
+ .map { resolutionFacade.resolveSession.getPackageFragment(it.packageFqName) }
+ .filterNotNull()
+ .distinct()
+
+ val packageDocs = injector.getInstance(PackageDocs::class.java)
+ for (include in includes) {
+ packageDocs.parse(include, fragments.firstOrNull())
+ }
+ val documentationModule = DocumentationModule(moduleName, packageDocs.moduleContent)
+
+ with(injector.getInstance(DocumentationBuilder::class.java)) {
+ documentationModule.appendFragments(fragments, packageDocs.packageContent,
+ injector.getInstance(PackageDocumentationBuilder::class.java))
+ }
+
+ val javaFiles = coreEnvironment.getJavaSourceFiles().filter(filesToDocumentFilter)
+ with(injector.getInstance(JavaDocumentationBuilder::class.java)) {
+ javaFiles.map { appendFile(it, documentationModule, packageDocs.packageContent) }
+ }
+
+ injector.getInstance(NodeReferenceGraph::class.java).resolveReferences()
+
+ return documentationModule
+}
+
+
+fun KotlinCoreEnvironment.getJavaSourceFiles(): List<PsiJavaFile> {
+ val sourceRoots = configuration.get(CommonConfigurationKeys.CONTENT_ROOTS)
+ ?.filterIsInstance<JavaSourceRoot>()
+ ?.map { it.file }
+ ?: listOf()
+
+ val result = arrayListOf<PsiJavaFile>()
+ val localFileSystem = VirtualFileManager.getInstance().getFileSystem("file")
+ sourceRoots.forEach { sourceRoot ->
+ sourceRoot.absoluteFile.walkTopDown().forEach {
+ val vFile = localFileSystem.findFileByPath(it.path)
+ if (vFile != null) {
+ val psiFile = PsiManager.getInstance(project).findFile(vFile)
+ if (psiFile is PsiJavaFile) {
+ result.add(psiFile)
+ }
+ }
+ }
+ }
+ return result
+}