diff options
author | Gilles Dubochet <gilles.dubochet@epfl.ch> | 2006-03-08 16:29:16 +0000 |
---|---|---|
committer | Gilles Dubochet <gilles.dubochet@epfl.ch> | 2006-03-08 16:29:16 +0000 |
commit | c7e1b5449f349d135cce6eceef2a906411d4fd41 (patch) | |
tree | 1475a8979bbc646ed4dbef827d3a1e1aaceaef53 | |
parent | 2534d32a6e6f6905c5e801493f980f64c7a739fc (diff) | |
download | scala-c7e1b5449f349d135cce6eceef2a906411d4fd41.tar.gz scala-c7e1b5449f349d135cce6eceef2a906411d4fd41.tar.bz2 scala-c7e1b5449f349d135cce6eceef2a906411d4fd41.zip |
Added a Scaladoc ant task and extended SABBUS t...
Added a Scaladoc ant task and extended SABBUS to use it.
-rw-r--r-- | build.number (renamed from build.product.properties) | 0 | ||||
-rw-r--r-- | build.xml | 47 | ||||
-rw-r--r-- | src/compiler/scala/tools/ant/Scaladoc.scala | 417 | ||||
-rw-r--r-- | src/compiler/scala/tools/ant/resources/script.js | 5 | ||||
-rw-r--r-- | src/compiler/scala/tools/ant/resources/style.css | 124 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/doc/DocGenerator.scala | 8 |
6 files changed, 582 insertions, 19 deletions
diff --git a/build.product.properties b/build.number index b07aac33c4..b07aac33c4 100644 --- a/build.product.properties +++ b/build.number @@ -35,7 +35,7 @@ PROPERTIES <!-- User properties --> <property file="${basedir}/build.properties"/> <!-- General properties --> - <property file="${basedir}/build.product.properties"/> + <property file="${basedir}/build.number"/> <property name="copyright" value="(c) 2002-2006 LAMP/EPFL"/> <!-- NSC configuration properties --> <property name="nsc.logging" value="none"/> @@ -58,6 +58,7 @@ PROPERTIES <property name="locker.dir" value="${build.dir}/locker"/> <property name="quick.dir" value="${build.dir}/quick"/> <property name="strap.dir" value="${build.dir}/strap"/> + <property name="scaladoc.dir" value="${build.dir}/scaladoc"/> <!-- Location of source and build elements names properties --> <property name="lib.dir.name" value="library"/> <property name="comp.dir.name" value="compiler"/> @@ -81,6 +82,8 @@ PROPERTIES <property name="quick.comp.dir" value="${quick.dir}/${comp.dir.name}"/> <property name="strap.lib.dir" value="${strap.dir}/${lib.dir.name}"/> <property name="strap.comp.dir" value="${strap.dir}/${comp.dir.name}"/> + <property name="scaladoc.lib.dir" value="${scaladoc.dir}/${lib.dir.name}"/> + <property name="scaladoc.comp.dir" value="${scaladoc.dir}/${comp.dir.name}"/> <!-- sbaz properties --> <property name="scala.sbaz.name" value="scala"/> <property name="scalac.sbaz.name" value="scalac"/> @@ -272,18 +275,11 @@ INITIALISATION select="\1" defaultValue="x" /> - <!-- see http://www.xfree86.org/4.1.0/Versions2.html --> - <!-- or http://mail-index.netbsd.org/tech-kern/2004/09/30/0020.html --> - <property name="version.number" - value="${version.major}.${version.minor}.${version.patch}.${svn.revision}"/> + <property + name="version.number" + value="${version.major}.${version.minor}.${version.patch}.${svn.revision}" + /> <echo level="verbose" message="version.number=${version.number}"/> - <condition property="version.number.valid"> - <length string="${version.number}" when="greater" length="6"/> - </condition> - <fail unless="version.number.valid"> - Version number is invalid. Please check the 'version.*' properties - in file ${basedir}/build.product.properties. - </fail> <property name="dist.current.dir" value="${dist.dir}/${dist.name}-${version.number}" @@ -335,6 +331,11 @@ INITIALISATION classname="scala.tools.ant.ScalaBazaar" classpathref="quick.classpath" /> + <taskdef + name="quickdoc" + classname="scala.tools.ant.Scaladoc" + classpathref="quick.classpath" + /> </target> <!-- =========================================================================== @@ -348,12 +349,16 @@ BUILD SUPPORT MACROS <fileset dir="${src.dir}/${lib.dir.name}"> <include name="**/*.tmpl"/> <include name="**/*.xml"/> + <include name="**/*.js"/> + <include name="**/*.css"/> </fileset> </copy> <copy todir="@{build.dir}/${comp.dir.name}"> <fileset dir="${src.dir}/${comp.dir.name}"> <include name="**/*.tmpl"/> <include name="**/*.xml"/> + <include name="**/*.js"/> + <include name="**/*.css"/> </fileset> </copy> </sequential> @@ -808,9 +813,21 @@ DOCUMENTATION <target name="docs" - description="Generated the API for library and compiler sources" + description="Generated the API for library sources" + depends="setup.quick" > - <echo level="error" message="Docs is not available yet."/> + <mkdir dir="${scaladoc.lib.dir}"/> + <quickdoc + srcdir="${src.dir}/${lib.dir.name}" + destdir="${scaladoc.lib.dir}" + sourcepath="" + > + <classpath> + <pathelement location="${quick.lib.dir}"/> + </classpath> + <include name="**/*.scala"/> + <excludesfile name="${nsc.excludes}" if="excludes.avail"/> + </quickdoc> </target> <!-- =========================================================================== @@ -879,7 +896,7 @@ GENERATES A DISTRIBUTION file="${dist.current.dir}/bin/${scaladoc.exec.name}" perm="ugo+rx" /> - <lockertool + <quicktool file="${dist.current.dir}/bin/${scalaint.exec.name}" name="Scala interpreter" class="scala.tools.nsc.MainInterpreter" diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala new file mode 100644 index 0000000000..6fc1c03f62 --- /dev/null +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -0,0 +1,417 @@ +/* __ ______________ *\ +** / |/ / ____/ ____/ ** +** / | | /___ / /___ ** +** /_/|__/_____/_____/ Copyright 2005-2006 LAMP/EPFL ** +** +** $Id: Scalac.scala 5676 2006-02-01 13:51:26Z mcdirmid $ +\* */ + +package scala.tools.ant { + + + import java.io.{File, InputStream, FileWriter} + import java.net.{URL, URLClassLoader} + import java.util.{ArrayList, Vector} + + import org.apache.tools.ant.{AntClassLoader, BuildException, + DirectoryScanner, Project} + import org.apache.tools.ant.taskdefs.MatchingTask + import org.apache.tools.ant.types.Path + import org.apache.tools.ant.util.{FileUtils, GlobPatternMapper, + SourceFileScanner} + import org.apache.tools.ant.types.{EnumeratedAttribute, Reference} + + import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} + import scala.tools.nsc.{Global, FatalError, Settings} + import scala.tools.nsc.doc.DocGenerator + + /** An Ant task to document Scala code. + * This task can take the following parameters as attributes:<ul> + * <li>srcdir (mandatory),</li> + * <li>srcref,</li> + * <li>destdir,</li> + * <li>classpath,</li> + * <li>classpathref,</li> + * <li>sourcepath,</li> + * <li>sourcepathref,</li> + * <li>bootclasspath,</li> + * <li>bootclasspathref,</li> + * <li>extdirs,</li> + * <li>extdirsref,</li> + * <li>encoding.</li> + * </ul> + * It also takes the following parameters as nested elements:<ul> + * <li>src (for srcdir),</li> + * <li>classpath,</li> + * <li>sourcepath,</li> + * <li>bootclasspath,</li> + * <li>extdirs.</li> + * </ul> + * + * @author Gilles Dubochet */ + class Scaladoc extends MatchingTask { + + /** The unique Ant file utilities instance to use in this task. */ + private val fileUtils = FileUtils.newFileUtils() + +/******************************************************************************\ +** Ant user-properties ** +\******************************************************************************/ + + /** The directories that contain source files to compile. */ + private var origin: Option[Path] = None + /** The directory to put the compiled files in. */ + private var destination: Option[File] = None + + /** The class path to use for this compilation. */ + private var classpath: Option[Path] = None + /** The source path to use for this compilation. */ + private var sourcepath: Option[Path] = None + /** The boot class path to use for this compilation. */ + private var bootclasspath: Option[Path] = None + /** The external extensions path to use for this compilation. */ + private var extdirs: Option[Path] = None + + /** The character encoding of the files to compile. */ + private var encoding: Option[String] = None + +/******************************************************************************\ +** Properties setters ** +\******************************************************************************/ + + /** Sets the srcdir attribute. Used by Ant. + * @param input The value of <code>origin</code>. */ + def setSrcdir(input: Path) = + if (origin.isEmpty) origin = Some(input) + else origin.get.append(input) + + /** Sets the <code>origin</code> as a nested src Ant parameter. + * @return An origin path to be configured. */ + def createSrc(): Path = { + if (origin.isEmpty) origin = Some(new Path(getProject())) + origin.get.createPath() + } + + /** Sets the <code>origin</code> as an external reference Ant parameter. + * @param input A reference to an origin path. */ + def setSrcref(input: Reference) = + createSrc().setRefid(input) + + /** Sets the destdir attribute. Used by Ant. + * @param input The value of <code>destination</code>. */ + def setDestdir(input: File) = + destination = Some(input) + + /** Sets the classpath attribute. Used by Ant. + * @param input The value of <code>classpath</code>. */ + def setClasspath(input: Path) = + if (classpath.isEmpty) classpath = Some(input) + else classpath.get.append(input) + + /** Sets the <code>classpath</code> as a nested classpath Ant parameter. + * @return A class path to be configured. */ + def createClasspath(): Path = { + if (classpath.isEmpty) classpath = Some(new Path(getProject())) + classpath.get.createPath() + } + + /** Sets the <code>classpath</code> as an external reference Ant parameter. + * @param input A reference to a class path. */ + def setClasspathref(input: Reference) = + createClasspath().setRefid(input) + + /** Sets the sourcepath attribute. Used by Ant. + * @param input The value of <code>sourcepath</code>. */ + def setSourcepath(input: Path) = + if (sourcepath.isEmpty) sourcepath = Some(input) + else sourcepath.get.append(input) + + /** Sets the <code>sourcepath</code> as a nested sourcepath Ant parameter. + * @return A source path to be configured. */ + def createSourcepath(): Path = { + if (sourcepath.isEmpty) sourcepath = Some(new Path(getProject())) + sourcepath.get.createPath() + } + + /** Sets the <code>sourcepath</code> as an external reference Ant parameter. + * @param input A reference to a source path. */ + def setSourcepathref(input: Reference) = + createSourcepath().setRefid(input) + + /** Sets the boot classpath attribute. Used by Ant. + * @param input The value of <code>bootclasspath</code>. */ + def setBootclasspath(input: Path) = + if (bootclasspath.isEmpty) bootclasspath = Some(input) + else bootclasspath.get.append(input) + + /** Sets the <code>bootclasspath</code> as a nested sourcepath Ant + * parameter. + * @return A source path to be configured. */ + def createBootclasspath(): Path = { + if (bootclasspath.isEmpty) bootclasspath = Some(new Path(getProject())) + bootclasspath.get.createPath() + } + + /** Sets the <code>bootclasspath</code> as an external reference Ant + * parameter. + * @param input A reference to a source path. */ + def setBootclasspathref(input: Reference) = + createBootclasspath().setRefid(input) + + /** Sets the external extensions path attribute. Used by Ant. + * @param input The value of <code>extdirs</code>. */ + def setExtdirs(input: Path) = + if (extdirs.isEmpty) extdirs = Some(input) + else extdirs.get.append(input) + + /** Sets the <code>extdirs</code> as a nested sourcepath Ant parameter. + * @return An extensions path to be configured. */ + def createExtdirs(): Path = { + if (extdirs.isEmpty) extdirs = Some(new Path(getProject())) + extdirs.get.createPath() + } + + /** Sets the <code>extdirs</code> as an external reference Ant parameter. + * @param input A reference to an extensions path. */ + def setExtdirsref(input: Reference) = + createExtdirs().setRefid(input) + + /** Sets the encoding attribute. Used by Ant. + * @param input The value of <code>encoding</code>. */ + def setEncoding(input: String): Unit = + encoding = Some(input) + +/******************************************************************************\ +** Properties getters ** +\******************************************************************************/ + + /** Gets the value of the classpath attribute in a Scala-friendly form. + * @returns The class path as a list of files. */ + private def getClasspath: List[File] = + if (classpath.isEmpty) error("Member 'classpath' is empty.") + else + List.fromArray(classpath.get.list()).map(nameToFile) + + /** Gets the value of the origin attribute in a Scala-friendly form. + * @returns The origin path as a list of files. */ + private def getOrigin: List[File] = + if (origin.isEmpty) error("Member 'origin' is empty.") + else List.fromArray(origin.get.list()).map(nameToFile) + + /** Gets the value of the destination attribute in a Scala-friendly form. + * @returns The destination as a file. */ + private def getDestination: File = + if (destination.isEmpty) error("Member 'destination' is empty.") + else existing(getProject().resolveFile(destination.get.toString())) + + /** Gets the value of the sourcepath attribute in a Scala-friendly form. + * @returns The source path as a list of files. */ + private def getSourcepath: List[File] = + if (sourcepath.isEmpty) error("Member 'sourcepath' is empty.") + else List.fromArray(sourcepath.get.list()).map(nameToFile) + + /** Gets the value of the bootclasspath attribute in a Scala-friendly form. + * @returns The boot class path as a list of files. */ + private def getBootclasspath: List[File] = + if (bootclasspath.isEmpty) error("Member 'bootclasspath' is empty.") + else List.fromArray(bootclasspath.get.list()).map(nameToFile) + + /** Gets the value of the extdirs attribute in a Scala-friendly form. + * @returns The extensions path as a list of files. */ + private def getExtdirs: List[File] = + if (extdirs.isEmpty) error("Member 'extdirs' is empty.") + else List.fromArray(extdirs.get.list()).map(nameToFile) + +/******************************************************************************\ +** Compilation and support methods ** +\******************************************************************************/ + + /** This is forwarding method to circumvent bug #281 in Scala 2. Remove when + * bug has been corrected. */ + override protected def getDirectoryScanner(baseDir: java.io.File) = + super.getDirectoryScanner(baseDir) + + /** Transforms a string name into a file relative to the provided base + * directory. + * @param base A file pointing to the location relative to which the name + * will be resolved. + * @param name A relative or absolute path to the file as a string. + * @return A file created from the name and the base file. */ + private def nameToFile(base: File)(name: String): File = + existing(fileUtils.resolveFile(base, name)) + + /** Transforms a string name into a file relative to the build root + * directory. + * @param name A relative or absolute path to the file as a string. + * @return A file created from the name. */ + private def nameToFile(name: String): File = + existing(getProject().resolveFile(name)) + + /** Tests if a file exists and prints a warning in case it doesn't. Always + * returns the file, even if it doesn't exist. + * @param file A file to test for existance. + * @return The same file. */ + private def existing(file: File): File = { + if (!file.exists()) + log("Element '" + file.toString() + "' does not exist.", + Project.MSG_WARN) + file + } + + /** Transforms a path into a Scalac-readable string. + * @param path A path to convert. + * @return A string-representation of the path like 'a.jar:b.jar'. */ + private def asString(path: List[File]): String = + path.map(asString).mkString("", File.pathSeparator, "") + + /** Transforms a file into a Scalac-readable string. + * @param path A file to convert. + * @return A string-representation of the file like '/x/k/a.scala'. */ + private def asString(file: File): String = + file.getAbsolutePath() + + /** Generates a build error. Error location will be the current task in the + * ant file. + * @param message A message describing the error. + * @throws BuildException A build error exception thrown in every case. */ + private def error(message: String): All = + throw new BuildException(message, getLocation()) + + private def readResource(resource: String): String = { + val chars = new Iterator[Char] { + private val stream = + this.getClass().getClassLoader().getResourceAsStream(resource) + private def readStream(): Char = stream.read().asInstanceOf[Char] + private var buf: Char = readStream() + def hasNext: Boolean = (buf != (-1.).asInstanceOf[Char]) + def next: Char = { + val bufbuf = buf + buf = readStream() + bufbuf + } + } + val builder = new StringBuffer() + while (chars.hasNext) { + builder.append(chars.next) + } + builder.toString() + } + + private def writeFile(file: File, content: String) = + if (file.exists() && !file.canWrite()) + error("File " + file + " is not writable") + else { + val writer = new FileWriter(file, false) + writer.write(content) + writer.close() + } + +/******************************************************************************\ +** The big execute method ** +\******************************************************************************/ + + /** Performs the compilation. */ + override def execute() = { + + // Tests if all mandatory attributes are set and valid. + if (origin.isEmpty) error("Attribute 'srcdir' is not set.") + if (getOrigin.isEmpty) error("Attribute 'srcdir' is not set.") + if (!destination.isEmpty && !destination.get.isDirectory()) + error("Attribute 'destdir' does not refer to an existing directory.") + if (destination.isEmpty) destination = Some(getOrigin.head) + + val mapper = new GlobPatternMapper() + mapper.setTo("*.html") + mapper.setFrom("*.scala") + + // Scans source directories to build up a compile lists. + // If force is false, only files were the .class file in destination is + // older than the .scala file will be used. + val sourceFiles: List[File] = + for { + val originDir <- getOrigin; + val originFile <- { + val includedFiles = + getDirectoryScanner(originDir).getIncludedFiles() + val list = List.fromArray(includedFiles) + if (list.length > 0) + log( + "Documenting " + list.length + " source file" + + (if (list.length > 1) "s" else "") + + (" to " + getDestination.toString()) + ) + else + log("No files selected for documentation", Project.MSG_VERBOSE) + + list + } + } yield { + log(originFile.toString(), Project.MSG_DEBUG) + nameToFile(originDir)(originFile) + } + + // Builds-up the compilation settings for Scalac with the existing Ant + // parameters. + val reporter = new ConsoleReporter() + val settings = new Settings(error) + settings.doc.value = true + settings.outdir.value = asString(destination.get) + if (!classpath.isEmpty) + settings.classpath.value = asString(getClasspath) + if (!sourcepath.isEmpty) + settings.sourcepath.value = asString(getSourcepath) + /*else if (origin.get.size() > 0) + settings.sourcepath.value = origin.get.list()(0)*/ + if (!bootclasspath.isEmpty) + settings.bootclasspath.value = asString(getBootclasspath) + if (!extdirs.isEmpty) settings.extdirs.value = asString(getExtdirs) + if (!encoding.isEmpty) settings.encoding.value = encoding.get + + // Compiles the actual code + object compiler extends Global(settings, reporter) + try { + val run = new compiler.Run + run.compile(sourceFiles.map(f:File=>f.toString())) + object generator extends DocGenerator { + val global = compiler + val outdir = settings.outdir.value + } + generator.process(run.units) + if (reporter.errors > 0) + error ( + "Document failed with " + + reporter.errors + " error" + + (if (reporter.errors > 1) "s" else "") + + "; see the documenter error output for details." + ) + else if (reporter.warnings > 0) + log ( + "Document suceeded with " + + reporter.warnings + " warning" + + (if (reporter.warnings > 1) "s" else "") + + "; see the documenter output for details." + ) + reporter.printSummary() + } catch { + case exception: Throwable if (exception.getMessage != null) => + exception.printStackTrace() + error("Document failed because of an internal documenter error (" + + exception.getMessage + "); see the error output for details.") + case exception => + exception.printStackTrace() + error("Document failed because of an internal documenter error " + + "(no error message provided); see the error output for details.") + } + writeFile( + new File(settings.outdir.value, "script.js"), + readResource("scala/tools/ant/resources/script.js") + ) + writeFile( + new File(settings.outdir.value, "style.css"), + readResource("scala/tools/ant/resources/style.css") + ) + } + + } + +} diff --git a/src/compiler/scala/tools/ant/resources/script.js b/src/compiler/scala/tools/ant/resources/script.js new file mode 100644 index 0000000000..94093ecc50 --- /dev/null +++ b/src/compiler/scala/tools/ant/resources/script.js @@ -0,0 +1,5 @@ +<!-- +function setWindowTitle(title) { + parent.document.title = title; +} +--> diff --git a/src/compiler/scala/tools/ant/resources/style.css b/src/compiler/scala/tools/ant/resources/style.css new file mode 100644 index 0000000000..5fecb9014e --- /dev/null +++ b/src/compiler/scala/tools/ant/resources/style.css @@ -0,0 +1,124 @@ +/* Scaladoc style sheet */ + +a:link { + color: #0000ee; +} + +a:visited { + color: #551a8b; +} + +a:active { + color: #0000ee; +} + +body { + background-color: #ffffff; +} + +div.entity { + margin: 18px 0px 18px 0px; + font-size: x-large; + font-weight: bold; +} + +div.doctitle { + font-weight: bold; + font-style: italic; +} + +div.doctitle-larger { + margin: 0px 0px 10px 0px; + font-size: larger; + font-weight: bold; +} + +div.page-title { + margin: 15px 0px 15px 0px; + font-size: x-large; + font-weight: bold; + text-align: center; +} + +span.entity { + color: #ff6666; +} + +table.member { + border-collapse: collapse; + border: 2px solid #888888; + background-color: #ffffff; + width: 100%; +} + +table.member-detail { + margin: 10px 0px 0px 0px; + border-collapse: collapse; + border: 2px solid #888888; + background-color: #ffffff; + width: 100%; +} + +table.navigation { + border-collapse: collapse; + width: 100%; + font-family: Arial,Helvetica,Sans-serif; +} + +table.list { + border-collapse: collapse; + border-style: none; + width: 100%; +} + +td.inherited-members { + border-top: 2px solid #888888; + border-right: 0px; +} + +td.inherited-owner { + background-color: #eeeeff; + font-weight: bold; +} + +td.member-title { + border: 2px solid #888888; + background-color: #ccccff; + font-size: x-large; + font-weight: bold; +} + +td.modifiers { + border-top: 2px solid #888888; + border-right: 2px solid #888888; + width: 50px; + text-align: right; +} + +td.navigation-enabled { + font-weight: bold; + color: #000000; + background-color: #eeeeff; +} + +td.navigation-links { + width: 100%; + background-color: #eeeeff; +} + +td.navigation-selected { + font-weight: bold; + color: #ffffff; + background-color: #00008b; +} + +td.signature { + border-top: 2px solid #888888; + width: 90%; +} + +td.title { + background-color: #ccccff; + font-size: x-large; + font-weight: bold; +} diff --git a/src/compiler/scala/tools/nsc/doc/DocGenerator.scala b/src/compiler/scala/tools/nsc/doc/DocGenerator.scala index b3c5d56b60..1d32a4ec43 100644 --- a/src/compiler/scala/tools/nsc/doc/DocGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/DocGenerator.scala @@ -42,7 +42,7 @@ abstract class DocGenerator extends Models { def title: String def save(nodes: NodeSeq) = { val path0 = outdir + "/" + path + ".html" - System.err.println("Writing to " + path0) + //System.err.println("Writing to " + path0) val file = new File(path0) val parent = file.getParentFile() if (!parent.exists()) parent.mkdirs() @@ -63,7 +63,7 @@ abstract class DocGenerator extends Models { case msym : ModuleSymbol => "$object"; case csym : ClassSymbol => ""; case _ => - System.err.println("XXX: class or object " + orig + " not found in " + sym); + //System.err.println("XXX: class or object " + orig + " not found in " + sym); "XXXXX"; }) @@ -465,8 +465,8 @@ abstract class DocGenerator extends Models { map = map.update(mmbr.kind, new TreeSet[HasTree]); val sz = map(mmbr.kind).size; map = map.update(mmbr.kind, map(mmbr.kind) + mmbr); - if (map(mmbr.kind).size == sz) - System.err.println(""+mmbr + " not added"); + /*if (map(mmbr.kind).size == sz) + System.err.println(""+mmbr + " not added");*/ map } |