summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/ScriptRunner.scala
blob: dad15d49602cb5d23ae4afb00859aa34af6dde51 (plain) (tree)
1
2
3
4
5
6
7
8



                                
       
 
                       
 


                                                  

                             
                                                
 
                                                   
  
                                                         

               
                          

                                      
                                         
  
                                                  


               
                      



                                         



                                                                  
                     




                                                        
                                                
















                                                             

                                          


















                                                                  
                                                                 


     






                                                          
                 

                               
     




                                                               
                                                           
      
                                                     



                                               
                                                                            




                                                                       
                       
                                                                              
 
                      

   

                                                     
      
                                                     

                                         
                                     
                             

                                                         
 

                                
                             
                                           
                                 

                                     




                                                                       
                                                          

                                      
                                      
   











                                                               
























                                                                               
                                                                 










                                



                                                              
                                                             

                                 
   












                                                                                
                                        
                                          
                                                     
                                 
                                                          




                                                            

     
                                      




                                                                   
                  




                                                              
                                                
             
                       
                                             
                        
                                             



                                                     
                                           
             
           
                   
                                         



                                                                                 
                                              
           
                   
                                       
                 
                                       
       


     



                                                                    
                                      


                                       

                                     
                                                          
            

     










                                                                      
                              





                                           

   
/* NSC -- new Scala compiler
 * Copyright 2005-2006 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$

package scala.tools.nsc

import java.io._
import java.util.jar._
import java.lang.reflect.InvocationTargetException
import scala.tools.nsc.util._
import scala.tools.nsc.io._
import scala.tools.nsc.reporters.ConsoleReporter

/** An object that runs Scala code in script files.
 *
 *  For example, here is a complete Scala script on Unix:
 *
 *    #!/bin/sh
 *    exec scala "$0" "$@"
 *    !#
 *    Console.println("Hello, world!")
 *    argv.toList foreach Console.println
 *
 * And here is a batch file example on Windows XP:
 *
 *    ::#!
 *    @echo off
 *    call scala %0 %*
 *    goto :eof
 *    ::!#
 *    Console.println("Hello, world!")
 *    argv.toList foreach Console.println
 *
 * TODO: It would be better if error output went to stderr instead
 * of stdout....
 */
object ScriptRunner {
  /** Choose a jar filename to hold the compiled version
    * of a script
    */
  private def jarFileFor(scriptFile: String): File = {
    val filename =
      if (scriptFile.matches(".*\\.[^.\\\\/]*"))
        scriptFile.replaceFirst("\\.[^.\\\\/]*$", ".jar")
      else
        scriptFile + ".jar"

    new File(filename)
  }

  /** Try to create a jar out of all the contents
    * of a directory.
    */
  private def tryMakeJar(jarFile: File, sourcePath: File) = {
    try {
      val jarFileStream = new FileOutputStream(jarFile)
      val jar = new JarOutputStream(jarFileStream)
      val buf = new Array[byte](10240)

      def addFromDir(dir: File, prefix: String): Unit = {
        for (val entry <- dir.listFiles) {
          if (entry.isFile) {
            jar.putNextEntry(new JarEntry(prefix + entry.getName))

            val input = new FileInputStream(entry)
            var n = input.read(buf, 0, buf.length)
            while (n >= 0) {
              jar.write (buf, 0, n)
              n = input.read(buf, 0, buf.length)
            }
            jar.closeEntry
            input.close
          } else {
            addFromDir(entry, prefix + entry.getName + "/")
          }
        }
      }

      addFromDir(sourcePath, "")
      jar.close
    } catch {
      case _:Error => jarFile.delete // XXX what errors to catch?
    }
  }

  /** Read the entire contents of a file as a String. */
  private def contentsOfFile(filename: String): String = {
    val strbuf = new StringBuffer
    val reader = new FileReader(filename)
    val cbuf = new Array[Char](1024)
    while(true) {
      val n = reader.read(cbuf)
      if (n <= 0)
        return strbuf.toString
      strbuf.append(cbuf, 0, n)
    }
    throw new Error("impossible")
  }

  /** Find the length of the header in the specified file, if
    * there is one.  The header part starts with "#!" or "::#!"
    * and ends with a line that begins with "!#" or "::!#".
    */
  private def headerLength(filename: String): Int = {
    import java.util.regex._

    val fileContents = contentsOfFile(filename)

    if (!(fileContents.startsWith("#!") || fileContents.startsWith("::#!")))
      return 0

    val matcher =
      (Pattern.compile("^(::)?!#.*(\\r|\\n|\\r\\n)", Pattern.MULTILINE)
              .matcher(fileContents))
    if (! matcher.find)
      throw new Error("script file does not close its header with !# or ::!#")

    return matcher.end
  }

  /** Wrap a script file into a runnable object named
    * scala.scripting.Main .
    */
  def wrappedScript(filename: String): SourceFile = {
    val preamble =
      new SourceFile("<script preamble>",
          ("package $scalascript\n" +
          "object Main {\n" +
          "  def main(argv: Array[String]): Unit = {\n" +
          "  val args = argv;\n").toCharArray)

    val middle = {
      val f = new File(filename)
      new SourceFileFragment(
          new SourceFile(new PlainFile(f)),
          headerLength(filename),
          f.length.asInstanceOf[Int])
    }
    val end = new SourceFile("<script trailer>", "\n} }\n".toCharArray)

    new CompoundSourceFile(preamble, middle, end)
  }

  /** Compile a script using the fsc compilation deamon */
  private def compileWithDaemon(
      settings: GenericRunnerSettings,
      scriptFileIn: String): Boolean =
  {
    val scriptFile = CompileClient.absFileName(scriptFileIn)
    for {
      val setting:settings.StringSetting <- List(
            settings.classpath,
            settings.sourcepath,
            settings.bootclasspath,
            settings.extdirs,
            settings.outdir)
    } {
      setting.value = CompileClient.absFileNames(setting.value)
    }

    val compSettingNames =
      (new Settings(error)).allSettings.map(.name)

    val compSettings =
      settings.allSettings.filter(stg =>
        compSettingNames.contains(stg.name))

    val coreCompArgs =
      compSettings.foldLeft[List[String]](Nil)((args, stg) =>
        stg.unparse ::: args)

    val compArgs = coreCompArgs ::: List("-Xscript", scriptFile)

    val socket = CompileSocket.getOrCreateSocket("")
    val out = new PrintWriter(socket.getOutputStream(), true)
    val in = new BufferedReader(new InputStreamReader(socket.getInputStream()))

    out.println(CompileSocket.getPassword(socket.getPort))
    out.println(compArgs.mkString("", "\0", ""))

    var compok = true

    var fromServer = in.readLine()
    while (fromServer != null) {
      System.out.println(fromServer)
      if (CompileSocket.errorPattern.matcher(fromServer).matches)
        compok = false

      fromServer = in.readLine()
    }
    in.close()
    out.close()
    socket.close()

    compok
  }

  /** Compile a script and then run the specified closure with
    * a classpath for the compiled script.
    */
  private def withCompiledScript
        (settings: GenericRunnerSettings, scriptFile: String)
        (handler: String => Unit)
        : Unit =
  {
    import Interpreter.deleteRecursively

    /** Compiles the script file, and returns two things:
      * the directory with the compiled class files,
      * and a flag for whether the compilation succeeded.
      */
    def compile: Pair[File, Boolean] = {
      val compiledPath = File.createTempFile("scalascript", "")
      compiledPath.delete  // the file is created as a file; make it a directory
      compiledPath.mkdirs

      settings.outdir.value = compiledPath.getPath

      if (settings.nocompdaemon.value) {
        val reporter = new ConsoleReporter
        val compiler = new Global(settings, reporter)
        val cr = new compiler.Run
        cr.compileSources(List(wrappedScript(scriptFile)))
        Pair(compiledPath, reporter.errors == 0)
      } else {
        val compok = compileWithDaemon(settings, scriptFile)
        Pair(compiledPath, compok)
      }
    }

    if (settings.savecompiled.value) {
      val jarFile = jarFileFor(scriptFile)

      def jarOK = (jarFile.canRead &&
        (jarFile.lastModified > new File(scriptFile).lastModified))

      if (jarOK) {
        // pre-compiled jar is current
        handler(jarFile.getAbsolutePath)
      } else {
        // The pre-compiled jar is old.  Recompile the script.
        jarFile.delete
        val Pair(compiledPath, compok) = compile
        try {
          if (compok) {
            tryMakeJar(jarFile, compiledPath)
            if (jarOK) {
              deleteRecursively(compiledPath)
              handler(jarFile.getAbsolutePath)
            } else {
              // run from the interpreter's temporary
              // directory
              handler(compiledPath.getPath)
            }
          }
        } finally {
          deleteRecursively(compiledPath)
        }
      }
    } else {
      // don't use the cache; just run from the interpreter's temporary directory
      val Pair(compiledPath, compok) = compile
      try {
        if (compok)
          handler(compiledPath.getPath)
      } finally {
        deleteRecursively(compiledPath)
      }
    }
  }

  /** Run a script file with the specified arguments and compilation
    * settings.
    */
  def runScript(
      settings: GenericRunnerSettings,
      scriptFile: String,
      scriptArgs: List[String]): Unit =
  {
    val f = new File(scriptFile)
    if (!f.exists || f.isDirectory) {
      scala.Console.println("no such file: " + scriptFile)
      return
    }

    withCompiledScript(settings, scriptFile)(compiledLocation => {
      def pparts(path: String) = path.split(File.pathSeparator).toList

      val classpath =
        pparts(settings.bootclasspath.value) :::
        List(compiledLocation) :::
        pparts(settings.classpath.value)

      try {
        ObjectRunner.run(
          classpath,
          "$scalascript.Main",
          scriptArgs.toArray)
      } catch {
        case e:InvocationTargetException =>
          e.getCause.printStackTrace
      }
    })
  }
}