mirror of https://github.com/sbt/sbt.git
Enable Javadoc generation in `doc` task.
`docSetting` has been updated to do both Scaladoc and Javadoc. In Scala/Java hybrid projects, the output docs are rebased to `scala` or `java` sub-directory accordingly. But for pure scala or pure java projects the subdirectories aren't added to becompliant with user expectation as much as possible. We do hybrid mode iff both *.scala and *.java files exist; other doc resources (package.html, *.jpg etc.) don't influence the decision.
This commit is contained in:
parent
ca154d9b5e
commit
ad7aede533
|
|
@ -4,26 +4,48 @@
|
|||
package sbt
|
||||
package compiler
|
||||
|
||||
import java.io.File
|
||||
import java.io.{File, PrintWriter}
|
||||
|
||||
abstract class JavacContract(val name: String, val clazz: String) {
|
||||
def exec(args: Array[String], writer: PrintWriter): Int
|
||||
}
|
||||
trait JavaCompiler
|
||||
{
|
||||
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger): Unit
|
||||
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) {
|
||||
compile(JavaCompiler.javac, sources, classpath, outputDirectory, options)(log)
|
||||
}
|
||||
def doc(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: Logger) {
|
||||
compile(JavaCompiler.javadoc, sources, classpath, outputDirectory, options)(log)
|
||||
}
|
||||
def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger): Unit
|
||||
}
|
||||
object JavaCompiler
|
||||
{
|
||||
type Fork = (Seq[String], Logger) => Int
|
||||
type Fork = (JavacContract, Seq[String], Logger) => Int
|
||||
|
||||
def construct(f: (Seq[String], Logger) => Int, cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler =
|
||||
val javac = new JavacContract("javac", "com.sun.tools.javac.Main") {
|
||||
def exec(args: Array[String], writer: PrintWriter) = {
|
||||
val m = Class.forName(clazz).getDeclaredMethod("compile", classOf[Array[String]], classOf[PrintWriter])
|
||||
m.invoke(null, args, writer).asInstanceOf[java.lang.Integer].intValue
|
||||
}
|
||||
}
|
||||
val javadoc = new JavacContract("javadoc", "com.sun.tools.javadoc.Main") {
|
||||
def exec(args: Array[String], writer: PrintWriter) = {
|
||||
val m = Class.forName(clazz).getDeclaredMethod("execute", classOf[String], classOf[PrintWriter], classOf[PrintWriter], classOf[PrintWriter], classOf[String], classOf[Array[String]])
|
||||
m.invoke(null, name, writer, writer, writer, "com.sun.tools.doclets.standard.Standard", args).asInstanceOf[java.lang.Integer].intValue
|
||||
}
|
||||
}
|
||||
|
||||
def construct(f: Fork, cp: ClasspathOptions, scalaInstance: ScalaInstance): JavaCompiler =
|
||||
new JavaCompiler {
|
||||
def apply(sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) {
|
||||
def compile(contract: JavacContract, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String])(implicit log: Logger) {
|
||||
val augmentedClasspath = if(cp.autoBoot) classpath ++ Seq(scalaInstance.libraryJar) else classpath
|
||||
val javaCp = ClasspathOptions.javac(cp.compiler)
|
||||
val arguments = (new CompilerArguments(scalaInstance, javaCp))(sources, augmentedClasspath, outputDirectory, options)
|
||||
log.debug("running javac with arguments:\n\t" + arguments.mkString("\n\t"))
|
||||
val code: Int = f(arguments, log)
|
||||
log.debug("javac returned exit code: " + code)
|
||||
if( code != 0 ) throw new CompileFailed(arguments.toArray, "javac returned nonzero exit code")
|
||||
log.debug("Calling " + contract.name.capitalize + " with arguments:\n\t" + arguments.mkString("\n\t"))
|
||||
val code: Int = f(contract, arguments, log)
|
||||
log.debug(contract.name + " returned exit code: " + code)
|
||||
if( code != 0 ) throw new CompileFailed(arguments.toArray, contract.name + " returned nonzero exit code")
|
||||
}
|
||||
}
|
||||
def directOrFork(cp: ClasspathOptions, scalaInstance: ScalaInstance)(implicit doFork: Fork): JavaCompiler =
|
||||
|
|
@ -35,31 +57,29 @@ object JavaCompiler
|
|||
def fork(cp: ClasspathOptions, scalaInstance: ScalaInstance)(implicit doFork: Fork): JavaCompiler =
|
||||
construct(forkJavac, cp, scalaInstance)
|
||||
|
||||
def directOrForkJavac(implicit doFork: Fork) = (arguments: Seq[String], log: Logger) =>
|
||||
try { directJavac(arguments, log) }
|
||||
catch { case e: ClassNotFoundException =>
|
||||
log.debug("com.sun.tools.javac.Main not found; forking javac instead")
|
||||
forkJavac(doFork)(arguments, log)
|
||||
def directOrForkJavac(implicit doFork: Fork) = (contract: JavacContract, arguments: Seq[String], log: Logger) =>
|
||||
try { directJavac(contract, arguments, log) }
|
||||
catch { case e @ (_: ClassNotFoundException | _: NoSuchMethodException) =>
|
||||
log.debug(contract.clazz + " not found with appropriate method signature; forking " + contract.name + " instead")
|
||||
forkJavac(doFork)(contract, arguments, log)
|
||||
}
|
||||
|
||||
/** `doFork` should be a function that forks javac with the provided arguments and sends output to the given Logger.*/
|
||||
def forkJavac(implicit doFork: Fork) = (arguments: Seq[String], log: Logger) =>
|
||||
def forkJavac(implicit doFork: Fork) = (contract: JavacContract, arguments: Seq[String], log: Logger) =>
|
||||
{
|
||||
val (jArgs, nonJArgs) = arguments.partition(_.startsWith("-J"))
|
||||
def externalJavac(argFile: File) = doFork(jArgs :+ ("@" + normalizeSlash(argFile.getAbsolutePath)), log)
|
||||
def externalJavac(argFile: File) = doFork(contract, jArgs :+ ("@" + normalizeSlash(argFile.getAbsolutePath)), log)
|
||||
withArgumentFile(nonJArgs)(externalJavac)
|
||||
}
|
||||
val directJavac = (arguments: Seq[String], log: Logger) =>
|
||||
val directJavac = (contract: JavacContract, arguments: Seq[String], log: Logger) =>
|
||||
{
|
||||
val logger = new LoggerWriter(log)
|
||||
val writer = new java.io.PrintWriter(logger)
|
||||
val writer = new PrintWriter(logger)
|
||||
val argsArray = arguments.toArray
|
||||
val javac = Class.forName("com.sun.tools.javac.Main")
|
||||
log.debug("Calling javac directly.")
|
||||
val compileMethod = javac.getDeclaredMethod("compile", classOf[Array[String]], classOf[java.io.PrintWriter])
|
||||
log.debug("Attempting to call " + contract.name + " directly...")
|
||||
|
||||
var exitCode = -1
|
||||
try { exitCode = compileMethod.invoke(null, argsArray, writer).asInstanceOf[java.lang.Integer].intValue }
|
||||
try { exitCode = contract.exec(argsArray, writer) }
|
||||
finally { logger.flushLines( if(exitCode == 0) Level.Warn else Level.Error) }
|
||||
exitCode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ object Defaults extends BuildCommon
|
|||
|
||||
lazy val configPaths = sourceConfigPaths ++ resourceConfigPaths ++ outputConfigPaths
|
||||
lazy val sourceConfigPaths = Seq(
|
||||
sourceDirectory <<= configSrcSub( sourceDirectory),
|
||||
sourceDirectory <<= configSrcSub(sourceDirectory),
|
||||
sourceManaged <<= configSrcSub(sourceManaged),
|
||||
scalaSource <<= sourceDirectory / "scala",
|
||||
javaSource <<= sourceDirectory / "java",
|
||||
|
|
@ -445,14 +445,21 @@ object Defaults extends BuildCommon
|
|||
def docSetting(key: TaskKey[File]): Seq[Setting[_]] = inTask(key)(Seq(
|
||||
cacheDirectory ~= (_ / key.key.label),
|
||||
target <<= docDirectory, // deprecate docDirectory in favor of 'target in doc'; remove when docDirectory is removed
|
||||
scaladocOptions <<= scalacOptions, // deprecate scaladocOptions in favor of 'scalacOptions in doc'; remove when scaladocOptions is removed
|
||||
fullClasspath <<= dependencyClasspath,
|
||||
key in TaskGlobal <<= (sources, cacheDirectory, maxErrors, compilers, target, configuration, scaladocOptions, fullClasspath, streams) map { (srcs, cache, maxE, cs, out, config, options, cp, s) =>
|
||||
(new Scaladoc(maxE, cs.scalac)).cached(cache, nameForSrc(config.name), srcs, cp.files, out, options, s.log)
|
||||
scalacOptions <<= scaladocOptions or scalacOptions, // deprecate scaladocOptions in favor of 'scalacOptions in doc'; remove when scaladocOptions is removed
|
||||
compileInputs <<= compileInputsTask,
|
||||
key in TaskGlobal <<= (cacheDirectory, compileInputs, target, configuration, streams) map { (cache, in, out, config, s) =>
|
||||
// For Scala/Java hybrid projects, the output docs are rebased to `scala` or `java` sub-directory accordingly. We do hybrid
|
||||
// mode iff both *.scala and *.java files exist -- other doc resources (package.html, *.jpg etc.) don't influence the decision.
|
||||
val srcs = in.config.sources
|
||||
val hybrid = srcs.exists(_.name.endsWith(".scala")) && srcs.exists(_.name.endsWith(".java"))
|
||||
val (scalaOut, javaOut) = if (hybrid) (out / "scala", out / "java") else (out, out)
|
||||
val cp = in.config.classpath.toList - in.config.classesDirectory
|
||||
Doc(in.config.maxErrors, in.compilers.scalac).cached(cache / "scala", nameForSrc(config.name), srcs, cp, scalaOut, in.config.options, s.log)
|
||||
Doc(in.config.maxErrors, in.compilers.javac).cached(cache / "java", nameForSrc(config.name), srcs, cp, javaOut, in.config.javacOptions, s.log)
|
||||
out
|
||||
}
|
||||
))
|
||||
|
||||
|
||||
@deprecated("Use `docSetting` instead", "0.11.0") def docTask: Initialize[Task[File]] =
|
||||
(cacheDirectory, compileInputs, streams, docDirectory, configuration, scaladocOptions) map { (cache, in, s, target, config, options) =>
|
||||
val d = new Scaladoc(in.config.maxErrors, in.compilers.scalac)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ object Compiler
|
|||
|
||||
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], definesClass: DefinesClass, maxErrors: Int, order: CompileOrder.Value)(implicit compilers: Compilers, log: Logger): Inputs =
|
||||
{
|
||||
import Path._
|
||||
import Path._
|
||||
val classesDirectory = outputDirectory / "classes"
|
||||
val cacheDirectory = outputDirectory / "cache"
|
||||
val augClasspath = classesDirectory.asFile +: classpath
|
||||
|
|
@ -58,7 +58,7 @@ object Compiler
|
|||
val javac = directOrFork(instance, cpOptions, javaHome)
|
||||
compilers(instance, cpOptions, javac)
|
||||
}
|
||||
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javac: (Seq[String], Logger) => Int)(implicit app: AppConfiguration, log: Logger): Compilers =
|
||||
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javac: JavaCompiler.Fork)(implicit app: AppConfiguration, log: Logger): Compilers =
|
||||
{
|
||||
val javaCompiler = JavaCompiler.fork(cpOptions, instance)(javac)
|
||||
compilers(instance, cpOptions, javaCompiler)
|
||||
|
|
@ -78,18 +78,18 @@ object Compiler
|
|||
if(javaHome.isDefined)
|
||||
JavaCompiler.fork(cpOptions, instance)(forkJavac(javaHome))
|
||||
else
|
||||
JavaCompiler.directOrFork(cpOptions, instance)( forkJavac(None) )
|
||||
JavaCompiler.directOrFork(cpOptions, instance)(forkJavac(None))
|
||||
|
||||
def forkJavac(javaHome: Option[File]): (Seq[String], Logger) => Int =
|
||||
def forkJavac(javaHome: Option[File]): JavaCompiler.Fork =
|
||||
{
|
||||
import Path._
|
||||
val exec = javaHome match { case None => "javac"; case Some(jh) => (jh / "bin" / "javac").absolutePath }
|
||||
(args: Seq[String], log: Logger) => {
|
||||
log.debug("Forking javac: " + exec + " " + args.mkString(" "))
|
||||
def exec(jc: JavacContract) = javaHome match { case None => jc.name; case Some(jh) => (jh / "bin" / jc.name).absolutePath }
|
||||
(contract: JavacContract, args: Seq[String], log: Logger) => {
|
||||
log.debug("Forking " + contract.name + ": " + exec(contract) + " " + args.mkString(" "))
|
||||
val javacLogger = new JavacLogger(log)
|
||||
var exitCode = -1
|
||||
try {
|
||||
exitCode = Process(exec, args) ! javacLogger
|
||||
exitCode = Process(exec(contract), args) ! javacLogger
|
||||
} finally {
|
||||
javacLogger.flush(exitCode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah, Indrajit Raychaudhuri
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import compiler.AnalyzingCompiler
|
||||
import compiler.{AnalyzingCompiler, JavaCompiler}
|
||||
|
||||
import Predef.{conforms => _, _}
|
||||
import Types.:+:
|
||||
|
|
@ -15,24 +15,28 @@ package sbt
|
|||
import Tracked.{inputChanged, outputChanged}
|
||||
import FilesInfo.{exists, hash, lastModified}
|
||||
|
||||
final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler)
|
||||
{
|
||||
final def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger)
|
||||
{
|
||||
log.info(actionStartMessage(label))
|
||||
object Doc {
|
||||
def apply(maximumErrors: Int, compiler: AnalyzingCompiler) = new Scaladoc(maximumErrors, compiler)
|
||||
def apply(maximumErrors: Int, compiler: JavaCompiler) = new Javadoc(maximumErrors, compiler)
|
||||
}
|
||||
sealed trait Doc {
|
||||
type Gen = (Seq[File], Seq[File], File, Seq[String], Int, Logger) => Unit
|
||||
|
||||
def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger): Unit
|
||||
|
||||
final def generate(variant: String, label: String, docf: Gen, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], maxErrors: Int, log: Logger) {
|
||||
val logSnip = variant + " API documentation"
|
||||
if(sources.isEmpty)
|
||||
log.info(ActionNothingToDoMessage)
|
||||
log.info("No sources available, skipping " + logSnip + "...")
|
||||
else
|
||||
{
|
||||
log.info("Generating " + logSnip + " for " + label + " sources to " + outputDirectory.absolutePath + "...")
|
||||
IO.delete(outputDirectory)
|
||||
IO.createDirectory(outputDirectory)
|
||||
compiler.doc(sources, classpath, outputDirectory, options, maximumErrors, log)
|
||||
log.info(ActionSuccessfulMessage)
|
||||
docf(sources, classpath, outputDirectory, options, maxErrors, log)
|
||||
log.info(logSnip + " generation successful.")
|
||||
}
|
||||
}
|
||||
def actionStartMessage(label: String) = "Generating API documentation for " + label + " sources..."
|
||||
val ActionNothingToDoMessage = "No sources specified."
|
||||
val ActionSuccessfulMessage = "API documentation generation successful."
|
||||
|
||||
def cached(cache: File, label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger)
|
||||
{
|
||||
|
|
@ -48,7 +52,21 @@ final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler)
|
|||
log.debug("Doc uptodate: " + outputDirectory.getAbsolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
cachedDoc(inputs)(() => exists(outputDirectory.***.get.toSet))
|
||||
}
|
||||
}
|
||||
final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends Doc
|
||||
{
|
||||
def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger)
|
||||
{
|
||||
generate("Scala", label, compiler.doc, sources, classpath, outputDirectory, options, maximumErrors, log)
|
||||
}
|
||||
}
|
||||
final class Javadoc(maximumErrors: Int, compiler: JavaCompiler) extends Doc
|
||||
{
|
||||
def apply(label: String, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String], log: Logger)
|
||||
{
|
||||
// javadoc doesn't handle *.scala properly, so we evict them from javadoc sources list.
|
||||
generate("Java", label, compiler.doc, sources.filterNot(_.name.endsWith(".scala")), classpath, outputDirectory, options, maximumErrors, log)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package pkg;
|
||||
/** This is class J */
|
||||
public class J {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package pkg;
|
||||
/** This is class K */
|
||||
public class K {
|
||||
}
|
||||
|
|
@ -1,7 +1,34 @@
|
|||
> 'set crossPaths := false'
|
||||
|
||||
> 'set scalacOptions in (Compile, doc) += "-Xfatal-warnings"'
|
||||
|
||||
-> doc
|
||||
|
||||
> 'set sources in (Compile, doc) <<= sources in Compile map { _.filter(_.getName contains "A") }'
|
||||
> 'set sources in (Compile, doc) <<= sources in Compile map { _.filterNot(_.getName contains "B") }'
|
||||
|
||||
> doc
|
||||
# hybrid project, scaladoc and javadoc in respective directories
|
||||
> doc
|
||||
$ exists "target/api/scala"
|
||||
$ exists "target/api/java"
|
||||
|
||||
> 'set sources in (Compile, doc) <<= sources in (Compile, doc) map { _.filterNot(_.getName endsWith ".java") }'
|
||||
|
||||
> clean
|
||||
> doc
|
||||
|
||||
# pure scala project, only scaladoc at top level
|
||||
$ exists "target/api/index.js"
|
||||
$ absent "target/api/package-list"
|
||||
$ absent "target/api/scala"
|
||||
$ absent "target/api/java"
|
||||
|
||||
> 'set sources in (Compile, doc) <<= sources in Compile map { _.filter(_.getName endsWith ".java") }'
|
||||
|
||||
> clean
|
||||
> doc
|
||||
|
||||
# pure java project, only javadoc at top level
|
||||
$ exists "target/api/package-list"
|
||||
$ absent "target/api/index.js"
|
||||
$ absent "target/api/scala"
|
||||
$ absent "target/api/java"
|
||||
|
|
|
|||
Loading…
Reference in New Issue