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

                          
       
 

                       



                                                                 
                              

                 


                                                                                     
 


                                                                   
                                                                       
 



                                                                       

                                
               
                     
                                                               
                          

                       
                 
     

                                                   
                                                             
 
                                                                     
                                                              

                                                                 
                                                
 
                                                            
 
                                      
              


           

                                                      
 

                                     
                     


                                                   

                                                                        

                                                               
                                                 
                         



                                                    
                                            



                   



                                                         
                                 
     


                    
                      
                                    

                                      


                        
                           
                                                             
          
                             
              
                                         

       

   



                                                     





                                                      
                                                                      



                                                                                   

                                                     

                                                         
                                              

                                           
         


                                             
             
                             
                                                  
                                        
     
   
 
                                     
                                                               

                                                                    
                                 
                                   


                            
                                                       
                             
               
                                         



                                                         



                                                                       
                                                   
     
                                      
                    
                         
 

                            
                                                
                   


                             
                                       
                 
                                                       



                                                                         
                          
         
                                                                   
                                                                  

               

                                                    
                                               
     
   

                                                                             
                                                       
 



                                                                       
                                                                    
                                                  
                                        





                                                                     
                                                
             
                                                

                                                                        
                 
                                             









                                                                             

         
                      

   



                                                 

                                              


                                                                   

                                                 
                                         

                             
                                                                       




                               
                                                      


                                

                                                           
                                                  
     
 
                                        
                                                              



                             
 


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

package scala.tools.nsc

import java.lang.{Thread, System, Runtime}
import java.lang.NumberFormatException
import java.io.{File, IOException, PrintWriter, FileOutputStream}
import java.io.{BufferedReader, FileReader}
import java.util.regex.Pattern
import java.net._

/** This class manages sockets for the fsc offline compiler.  */
class CompileSocket {
  protected def compileClient: StandardCompileClient = CompileClient //todo: lazy val

  /** The prefix of the port identification file, which is followed
   *  by the port number.
   */
  protected def dirName = "scalac-compile-server-port" //todo: lazy val

  protected def cmdName = Properties.cmdName //todo: lazy val

  /** The vm part of the command to start a new scala compile server */
  protected val vmCommand =
    Properties.scalaHome match {
      case null =>
        cmdName
      case dirname =>
        val trial = new File(new File(dirname, "bin"), cmdName)
        if (trial.canRead)
          trial.getPath
        else
          cmdName
    }

  /** The class name of the scala compile server */
  protected val serverClass = "scala.tools.nsc.CompileServer"

  /** A regular expression for checking compiler output for errors */
  val errorRegex = ".*(errors? found|don't know|bad option).*"

  /** A Pattern object for checking compiler output for errors */
  val errorPattern = Pattern.compile(errorRegex)

  protected def error(msg: String) = System.err.println(msg)

  protected def fatal(msg: String) = {
    error(msg)
    exit(1)
  }

  protected def info(msg: String) =
    if (compileClient.verbose) System.out.println(msg)

  /** A temporary directory to use */
  val tmpDir = {
    val totry = List(
        ("scala.home", List("var", "scala-devel")),
        ("user.home", List("tmp")),
        ("java.io.tmpdir", Nil))

    /** Expand a property-extensions pair into a complete File object */
    def expand(trial: (String, List[String])): Option[File] = {
      val (topdirProp, extensions) = trial
      val topdir = System.getProperty(topdirProp)
      if (topdir eq null)
        return None

      val fulldir =
        extensions.foldLeft[File](new File(topdir))(
            (dir,ext) => new File(dir, ext))

      Some(fulldir)
    }

    /** Try to create directory f, and then see if it can
     *  be written into. */
    def isDirWritable(f: File): Boolean = {
      f.mkdirs()
      f.isDirectory && f.canWrite
    }

    val potentials =
      for {
        trial <- totry
        val expanded = expand(trial)
        if !expanded.isEmpty
        if isDirWritable(expanded.get)
      }
      yield expanded.get

    if (potentials.isEmpty)
      fatal("Could not find a directory for temporary files")
    else {
      val d = potentials.head
      d.mkdirs
      info("[Temp directory: " + d + "]")
      d
    }
  }

  /* A directory holding port identification files */
  val portsDir =  new File(tmpDir, dirName)
  portsDir.mkdirs

  /** Maximum number of polls for an available port */
  private val MaxAttempts = 100

  /** Time (in ms) to sleep between two polls */
  private val sleepTime = 20

  /** The command which starts the compile server, given vm arguments.
    *
    *  @param vmArgs  the argument string to be passed to the java or scala command
    *                 the string must be either empty or start with a ' '.
    */
  private def serverCommand(vmArgs: String): String =
    vmCommand + vmArgs + " " + serverClass

  /** Start a new server; returns true iff it succeeds */
  private def startNewServer(vmArgs: String) {
    val cmd = serverCommand(vmArgs)
    info("[Executed command: " + cmd + "]")
    try {
      Runtime.getRuntime().exec(cmd)
//      val exitVal = proc.waitFor()
//      info("[Exit value: " + exitVal + "]")
    } catch {
      case ex: IOException =>
        fatal("Cannot start compilation daemon." +
              "\ntried command: " + cmd)
    }
  }

  /** The port identification file */
  def portFile(port: Int) = new File(portsDir, port.toString())

  /** Poll for a server port number; return -1 if none exists yet */
  private def pollPort(): Int = {
    val hits = portsDir.listFiles()
    if (hits.length == 0) -1
    else
      try {
        for (i <- 1 until hits.length) hits(i).delete()
        hits(0).getName.toInt
      } catch {
        case ex: NumberFormatException =>
          fatal(ex.toString() +
                "\nbad file in temp directory: " +
                hits(0).getAbsolutePath() +
                "\nplease remove the file and try again")
      }
  }

  /** Get the port number to which a scala compile server is connected;
   *  If no server is running yet, then create one.
   */
  def getPort(vmArgs: String): Int = {
    var attempts = 0
    var port = pollPort()

    if (port < 0)
      startNewServer(vmArgs)
    while (port < 0 && attempts < MaxAttempts) {
      attempts += 1
      Thread.sleep(sleepTime)
      port = pollPort()
    }
    info("[Port number: " + port + "]")
    if (port < 0)
      fatal("Could not connect to compilation daemon.")
    port
  }

  /** Set the port number to which a scala compile server is connected */
  def setPort(port: Int) {
    try {
      val f = new PrintWriter(new FileOutputStream(portFile(port)))
      f.println(new java.security.SecureRandom().nextInt.toString)
      f.close()
    } catch {
      case ex: /*FileNotFound+Security*/Exception =>
        fatal("Cannot create file: " +
              portFile(port).getAbsolutePath())
    }
  }

  /** Delete the port number to which a scala compile server was connected */
  def deletePort(port: Int) { portFile(port).delete() }

  /** Get a socket connected to a daemon.  If create is true, then
    * create a new daemon if necessary.  Returns null if the connection
    * cannot be established.
    */
  def getOrCreateSocket(vmArgs: String, create: Boolean): Socket = {
    val nAttempts = 49  // try for about 5 seconds
    def getsock(attempts: Int): Socket =
      if (attempts == 0) {
        error("Unable to establish connection to compilation daemon")
        null
      } else {
        val port = if(create) getPort(vmArgs) else pollPort()
        if(port < 0) return null
        val hostAdr = InetAddress.getLocalHost()
        try {
          val result = new Socket(hostAdr, port)
          info("[Connected to compilation daemon at port " + port + "]")
          result
        } catch {
          case e: /*IO+Security*/Exception =>
            info(e.toString)
            info("[Connecting to compilation daemon at port "  +
                 port + " failed; re-trying...]")

            if (attempts % 2 == 0)
              portFile(port).delete // 50% chance to stop trying on this port

            Thread.sleep(100) // delay before retrying

            getsock(attempts - 1)
        }
      }
    getsock(nAttempts)
  }

  /** Same as getOrCreateSocket(vmArgs, true). */
  def getOrCreateSocket(vmArgs: String): Socket =
    getOrCreateSocket(vmArgs, true)

  def getSocket(serverAdr: String): Socket = {
    val cpos = serverAdr indexOf ':'
    if (cpos < 0)
      fatal("Malformed server address: " + serverAdr + "; exiting")
    else {
      val hostName = serverAdr.substring(0, cpos)
      val port = try {
        serverAdr.substring(cpos+1).toInt
      } catch {
        case ex: Throwable =>
          fatal("Malformed server address: " + serverAdr + "; exiting")
      }
      getSocket(hostName, port)
    }
  }

  def getSocket(hostName: String, port: Int): Socket =
    try {
      new Socket(hostName, port)
    } catch {
      case e: /*IO+Security*/Exception =>
        fatal("Unable to establish connection to server " +
              hostName + ":" + port + "; exiting")
    }

  def getPassword(port: Int): String = {
    val f = new BufferedReader(new FileReader(portFile(port)))
    val result = f.readLine()
    f.close()
    result
  }
}


object CompileSocket extends CompileSocket