summaryrefslogblamecommitdiff
path: root/project/build/ScalaTools.scala
blob: a18249846735621bc882a3858ebf934d5bf98624 (plain) (tree)


















































































































































































                                                                                                                                                                                     
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)

  }
}