import java.io.{FileInputStream, File, InputStream, FileWriter} import sbt._ import scala.io._ /** * Create the scala binaries * Based on scala.tools.ant.ScalaTool * @author Grégory Moix (for the sbt adaptation) */ trait ScalaTools { self: BasicLayer => lazy val templatesLocation = compilerConfig.srcDir/ "scala" / "tools" / "ant" / "templates" lazy val unixTemplate = templatesLocation / "tool-unix.tmpl" lazy val winTemplate = templatesLocation / "tool-windows.tmpl" // XXX encoding and generalize private def getResourceAsCharStream(resource: Path): Stream[Char] = { val stream = new FileInputStream(resource.asFile) def streamReader(): Stream[Char] = stream.read match { case -1 => Stream.empty case value => Stream.cons(value.asInstanceOf[Char], streamReader()) } if (stream == null) { log.debug("Stream was null") Stream.empty } //else Stream continually stream.read() takeWhile (_ != -1) map (_.asInstanceOf[Char]) // Does not work in scala 2.7.7 else streamReader } // Converts a variable like @SCALA_HOME@ to ${SCALA_HOME} when pre = "${" and post = "}" private def transposeVariableMarkup(text: String, pre: String, post: String) : String = { val chars = Source.fromString(text) val builder = new StringBuilder() while (chars.hasNext) { val char = chars.next if (char == '@') { var char = chars.next val token = new StringBuilder() while (chars.hasNext && char != '@') { token.append(char) char = chars.next } if (token.toString == "") builder.append('@') else builder.append(pre + token.toString + post) } else builder.append(char) } builder.toString } private def readAndPatchResource(resource: Path, tokens: Map[String, String]): String = { val chars = getResourceAsCharStream(resource).elements val builder = new StringBuilder() while (chars.hasNext) { val char = chars.next if (char == '@') { var char = chars.next val token = new StringBuilder() while (chars.hasNext && char != '@') { token.append(char) char = chars.next } if (tokens.contains(token.toString)) builder.append(tokens(token.toString)) else if (token.toString == "") builder.append('@') else builder.append("@" + token.toString + "@") } else builder.append(char) } builder.toString } private def writeFile(file: File, content: String, makeExecutable: Boolean): Option[String] = if (file.exists() && !file.canWrite()) Some("File " + file + " is not writable") else { val writer = new FileWriter(file, false) writer.write(content) writer.close() file.setExecutable(makeExecutable) None } /** Gets the value of the classpath attribute in a Scala-friendly form. * @return The class path as a list of files. */ private def getUnixclasspath(classpath: List[String]): String = transposeVariableMarkup(classpath.mkString("", ":", "").replace('\\', '/'), "${", "}") /** Gets the value of the classpath attribute in a Scala-friendly form. * @return The class path as a list of files. */ private def getWinclasspath(classpath: List[String]): String = transposeVariableMarkup(classpath.mkString("", ";", "").replace('/', '\\'), "%", "%") /** Performs the tool creation of a tool with for a given os * @param file * @param mainClas * @param properties * @param javaFlags * @param toolFlags * @param classPath * @param template * @param classpathFormater */ private def tool(template: Path, classpathFormater: List[String] => String, file: Path, mainClass: String, properties: String, javaFlags: String, toolFlags: String, classPath: List[Path], makeExecutable: Boolean): Option[String] = { val patches = Map ( ("class", mainClass), ("properties", properties), ("javaflags", javaFlags), ("toolflags", toolFlags), ("classpath", classpathFormater(classPath.map(_.absolutePath))) ) val result = readAndPatchResource(template, patches) writeFile(file.asFile, result, makeExecutable) } private def generateTool(config: ToolConfiguration): Option[String] = generateTool(config.toolName, config.destination, config.mainClass, config.properties, config.javaFlags, config.toolFlags, config.classPath) private def generateTool(toolName: String, destination: Path, mainClass: String, properties: String, javaFlags: String, toolFlags: String, classPath: List[Path]): Option[String] ={ val unixFile = destination / toolName val winFile = destination /(toolName + ".bat") tool(unixTemplate, getUnixclasspath, unixFile, mainClass, properties, javaFlags, toolFlags, classPath, true) orElse tool(winTemplate, getWinclasspath, winFile, mainClass, properties, javaFlags, toolFlags, classPath, false) } /*============================================================================*\ ** Definition of the different tools ** \*============================================================================*/ private val defaultJavaFlags = "-Xmx256M -Xms32M" /** * A class that holds the different parameters of a tool */ class ToolConfiguration(val toolName: String, val destination: Path, val mainClass: String, val properties: String, val javaFlags: String, val toolFlags: String, val classPath: List[Path]) /** * Generate all tools * @param destination Root folder where all the binaries will be written * @param classpath Should be specified when you want to use a specific classpath, could be Nil if you want * to make the bin use what is in the lib folder of the distribution. */ def tools(destination: Path, classpath: List[Path]) = task { val scala = new ToolConfiguration("scala", destination, "scala.tools.nsc.MainGenericRunner", "",defaultJavaFlags, "", classpath) val scalac = new ToolConfiguration("scalac", destination, "scala.tools.nsc.Main", "",defaultJavaFlags, "", classpath) val scaladoc = new ToolConfiguration("scaladoc",destination,"scala.tools.nsc.ScalaDoc", "",defaultJavaFlags,"", classpath) val fsc = new ToolConfiguration("fsc", destination,"scala.tools.nsc.CompileClient", "",defaultJavaFlags, "", classpath) val scalap = new ToolConfiguration("scalap",destination, "scala.tools.scalap.Main", "",defaultJavaFlags, "", classpath) val toolList = scala :: scalac :: scaladoc :: fsc :: scalap :: Nil def process(list: List[ToolConfiguration]): Option[String] = list match { case x :: xs => { log.debug("Generating "+x.toolName+" bin") generateTool(x) orElse process(xs) } case Nil => None } FileUtilities.createDirectory(destination, log) process(toolList) } }