From caa9ebc482969a884da5f9c9c246470811b8599d Mon Sep 17 00:00:00 2001
From: Teemu Lehtinen <teemu.t.lehtinen@aalto.fi>
Date: Wed, 20 Aug 2014 13:20:41 +0300
Subject: [PATCH] Add option -port to fsc

Option "port" limits compile server lookup and start to given port.
Normally fsc will start a compile server in a random port if no server
is yet running. This can be problematic with firewalls and/or remote
compile servers. Option "port" should not be confused with option
"server" which looks for a compile server in given host and port and
fails if such server is not found.

Automatic tests for command line user interface do not exist at all.
Thus, adding a test for one new option would require designing a whole
new testing method.

Cherry picked from 7daecd8
---
 .../scala/tools/nsc/CompileClient.scala       |  4 +-
 .../scala/tools/nsc/CompileServer.scala       | 56 ++++++++++++-------
 .../scala/tools/nsc/CompileSocket.scala       | 37 +++++++-----
 .../tools/nsc/settings/FscSettings.scala      |  4 +-
 .../scala/tools/util/SocketServer.scala       |  4 +-
 5 files changed, 65 insertions(+), 40 deletions(-)

diff --git a/src/compiler/scala/tools/nsc/CompileClient.scala b/src/compiler/scala/tools/nsc/CompileClient.scala
index 731f6926f00..842d6ac535b 100644
--- a/src/compiler/scala/tools/nsc/CompileClient.scala
+++ b/src/compiler/scala/tools/nsc/CompileClient.scala
@@ -43,8 +43,8 @@ class StandardCompileClient extends HasCompileSocket with CompileOutputCommon {
     info(vmArgs.mkString("[VM arguments: ", " ", "]"))
 
     val socket =
-      if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown)
-      else Some(compileSocket.getSocket(settings.server.value))
+      if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown, settings.port.value)
+      else compileSocket.getSocket(settings.server.value)
 
     socket match {
       case Some(sock) => compileOnServer(sock, fscArgs)
diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala
index 7a0a072bb8d..6352d75686a 100644
--- a/src/compiler/scala/tools/nsc/CompileServer.scala
+++ b/src/compiler/scala/tools/nsc/CompileServer.scala
@@ -5,11 +5,13 @@
 
 package scala.tools.nsc
 
-import java.io.{ BufferedOutputStream, FileOutputStream, PrintStream }
-import scala.tools.nsc.reporters.{Reporter, ConsoleReporter}
-import scala.reflect.internal.util.FakePos //Position
+import java.io.PrintStream
+
+import scala.reflect.internal.util.FakePos
+import scala.tools.nsc.io.Directory
+import scala.tools.nsc.reporters.{ConsoleReporter, Reporter}
+import scala.tools.nsc.settings.FscSettings
 import scala.tools.util.SocketServer
-import settings.FscSettings
 
 /**
  *  The server part of the fsc offline compiler.  It awaits compilation
@@ -19,7 +21,7 @@ import settings.FscSettings
  *  @author Martin Odersky
  *  @version 1.0
  */
-class StandardCompileServer extends SocketServer {
+class StandardCompileServer(fixPort: Int = 0) extends SocketServer(fixPort) {
   lazy val compileSocket: CompileSocket = CompileSocket
 
   private var compiler: Global = null
@@ -34,7 +36,7 @@ class StandardCompileServer extends SocketServer {
   val MaxCharge = 0.8
 
   private val runtime = Runtime.getRuntime()
-  import runtime.{ totalMemory, freeMemory, maxMemory }
+  import runtime.{freeMemory, maxMemory, totalMemory}
 
   /** Create a new compiler instance */
   def newGlobal(settings: Settings, reporter: Reporter) =
@@ -170,16 +172,16 @@ class StandardCompileServer extends SocketServer {
 }
 
 
-object CompileServer extends StandardCompileServer {
+object CompileServer {
   /** A directory holding redirected output */
-  private lazy val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory()
+  //private lazy val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory()
 
-  private def createRedirect(filename: String) =
-    new PrintStream((redirectDir / filename).createFile().bufferedOutput())
+  private def createRedirect(dir: Directory, filename: String) =
+    new PrintStream((dir / filename).createFile().bufferedOutput())
 
-  def main(args: Array[String]) = 
+  def main(args: Array[String]) =
     execute(() => (), args)
-  
+
   /**
    * Used for internal testing. The callback is called upon
    * server start, notifying the caller that the server is
@@ -191,21 +193,33 @@ object CompileServer extends StandardCompileServer {
    */
   def execute(startupCallback : () => Unit, args: Array[String]) {
     val debug = args contains "-v"
+    var port = 0
+
+    val i = args.indexOf("-p")
+    if (i >= 0 && args.length > i + 1) {
+    	scala.util.control.Exception.ignoring(classOf[NumberFormatException]) {
+    		port = args(i + 1).toInt
+    	}
+    }
+
+    // Create instance rather than extend to pass a port parameter.
+    val server = new StandardCompileServer(port)
+    val redirectDir = (server.compileSocket.tmpDir / "output-redirects").createDirectory()
 
     if (debug) {
-      echo("Starting CompileServer on port " + port)
-      echo("Redirect dir is " + redirectDir)
+      server.echo("Starting CompileServer on port " + server.port)
+      server.echo("Redirect dir is " + redirectDir)
     }
 
-    Console.withErr(createRedirect("scala-compile-server-err.log")) {
-      Console.withOut(createRedirect("scala-compile-server-out.log")) {
-        Console.err.println("...starting server on socket "+port+"...")
+    Console.withErr(createRedirect(redirectDir, "scala-compile-server-err.log")) {
+      Console.withOut(createRedirect(redirectDir, "scala-compile-server-out.log")) {
+        Console.err.println("...starting server on socket "+server.port+"...")
         Console.err.flush()
-        compileSocket setPort port
+        server.compileSocket setPort server.port
         startupCallback()
-        run()
-    
-        compileSocket deletePort port
+        server.run()
+
+        server.compileSocket deletePort server.port
       }
     }
   }
diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala
index 4051bda9144..f5039b8303f 100644
--- a/src/compiler/scala/tools/nsc/CompileSocket.scala
+++ b/src/compiler/scala/tools/nsc/CompileSocket.scala
@@ -5,16 +5,13 @@
 
 package scala.tools.nsc
 
-import java.io.{ IOException, FileNotFoundException, PrintWriter, FileOutputStream }
-import java.io.{ BufferedReader, FileReader }
-import java.util.regex.Pattern
-import java.net._
+import java.io.FileNotFoundException
 import java.security.SecureRandom
-import io.{ File, Path, Directory, Socket }
-import scala.util.control.Exception.catching
-import scala.tools.util.CompileOutputCommon
+
 import scala.reflect.internal.util.StringOps.splitWhere
 import scala.sys.process._
+import scala.tools.nsc.io.{File, Path, Socket}
+import scala.tools.util.CompileOutputCommon
 
 trait HasCompileSocket {
   def compileSocket: CompileSocket
@@ -50,6 +47,9 @@ class CompileSocket extends CompileOutputCommon {
   protected lazy val compileClient: StandardCompileClient = CompileClient
   def verbose = compileClient.verbose
 
+  /* Fixes the port where to start the server, 0 yields some free port */
+  var fixPort = 0
+
   /** The prefix of the port identification file, which is followed
    *  by the port number.
    */
@@ -67,7 +67,7 @@ class CompileSocket extends CompileOutputCommon {
 
   /** The class name of the scala compile server */
   protected val serverClass     = "scala.tools.nsc.CompileServer"
-  protected def serverClassArgs = if (verbose) List("-v") else Nil  // debug
+  protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil)
 
   /** A temporary directory to use */
   val tmpDir = {
@@ -107,9 +107,14 @@ class CompileSocket extends CompileOutputCommon {
   def portFile(port: Int) = portsDir / File(port.toString)
 
   /** Poll for a server port number; return -1 if none exists yet */
-  private def pollPort(): Int = portsDir.list.toList match {
+  private def pollPort(): Int = if (fixPort > 0) {
+  	if (portsDir.list.toList.exists(_.name == fixPort.toString)) fixPort else -1
+  } else portsDir.list.toList match {
     case Nil      => -1
-    case x :: xs  => try x.name.toInt finally xs foreach (_.delete())
+    case x :: xs  => try x.name.toInt catch {
+    	case e: Exception => x.delete()
+    	throw e
+    }
   }
 
   /** Get the port number to which a scala compile server is connected;
@@ -155,7 +160,8 @@ class CompileSocket extends CompileOutputCommon {
     * create a new daemon if necessary.  Returns None if the connection
     * cannot be established.
     */
-  def getOrCreateSocket(vmArgs: String, create: Boolean = true): Option[Socket] = {
+  def getOrCreateSocket(vmArgs: String, create: Boolean = true, fixedPort: Int = 0): Option[Socket] = {
+    fixPort = fixedPort
     val maxMillis = 10 * 1000   // try for 10 seconds
     val retryDelay = 50
     val maxAttempts = maxMillis / retryDelay
@@ -189,13 +195,16 @@ class CompileSocket extends CompileOutputCommon {
     try   { Some(x.toInt) }
     catch { case _: NumberFormatException => None }
 
-  def getSocket(serverAdr: String): Socket = (
+  def getSocket(serverAdr: String): Option[Socket] = (
     for ((name, portStr) <- splitWhere(serverAdr, _ == ':', true) ; port <- parseInt(portStr)) yield
       getSocket(name, port)
   ) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr)
 
-  def getSocket(hostName: String, port: Int): Socket =
-    Socket(hostName, port).opt getOrElse fatal("Unable to establish connection to server %s:%d; exiting".format(hostName, port))
+  def getSocket(hostName: String, port: Int): Option[Socket] = {
+    val sock = Socket(hostName, port).opt
+    if (sock.isEmpty) warn("Unable to establish connection to server %s:%d".format(hostName, port))
+    sock
+  }
 
   def getPassword(port: Int): String = {
     val ff  = portFile(port)
diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
index 5c852ae07c1..f5f971d697e 100644
--- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
@@ -22,13 +22,15 @@ class FscSettings(error: String => Unit) extends Settings(error) {
   val reset        = BooleanSetting("-reset",    "Reset compile server caches")
   val shutdown     = BooleanSetting("-shutdown", "Shutdown compile server")
   val server       = StringSetting ("-server",   "hostname:portnumber", "Specify compile server socket", "")
+  val port         = IntSetting    ("-port",     "Search and start compile server in given port only",
+  		                                      0, Some((0, Int.MaxValue)), (_: String) => None)
   val preferIPv4   = BooleanSetting("-ipv4",     "Use IPv4 rather than IPv6 for the server socket")
   val idleMins     = IntSetting    ("-max-idle", "Set idle timeout in minutes for fsc (use 0 for no timeout)",
                                               30, Some((0, Int.MaxValue)), (_: String) => None)
 
   // For improved help output, separating fsc options from the others.
   def fscSpecific = Set[Settings#Setting](
-    currentDir, reset, shutdown, server, preferIPv4, idleMins
+    currentDir, reset, shutdown, server, port, preferIPv4, idleMins
   )
   val isFscSpecific: String => Boolean = fscSpecific map (_.name)
 
diff --git a/src/compiler/scala/tools/util/SocketServer.scala b/src/compiler/scala/tools/util/SocketServer.scala
index 1b06ce2ff2e..edbc7ecc554 100644
--- a/src/compiler/scala/tools/util/SocketServer.scala
+++ b/src/compiler/scala/tools/util/SocketServer.scala
@@ -27,12 +27,12 @@ trait CompileOutputCommon {
  *  @author  Martin Odersky
  *  @version 1.0
  */
-abstract class SocketServer extends CompileOutputCommon {
+abstract class SocketServer(fixPort: Int = 0) extends CompileOutputCommon {
   def shutdown: Boolean
   def session(): Unit
   def timeout(): Unit = ()  // called after a timeout is detected for subclasses to cleanup
   // a hook for subclasses
-  protected def createServerSocket(): ServerSocket = new ServerSocket(0)
+  protected def createServerSocket(): ServerSocket = new ServerSocket(fixPort)
 
   var in: BufferedReader = _
   var out: PrintWriter   = _
