From 8c4ebabe193ffd9dab68e0c5fda7d118d09aed0b Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 26 Feb 2013 08:26:04 -0500 Subject: [PATCH] remove JLine from the launcher It is no longer necessary for it to be loaded in a stable class loader and line reading in the launcher does not require anything more advanced than java.io.Console.readLine(String). Scala versions 2.8 and later use the version that goes through JAnsi and for that it is sufficient to have JNA in a stable loader. --- launch/NOTICE | 7 +-- launch/src/main/scala/xsbt/boot/Boot.scala | 20 +++++---- .../scala/xsbt/boot/BootConfiguration.scala | 2 - launch/src/main/scala/xsbt/boot/Create.scala | 4 +- .../main/scala/xsbt/boot/FilteredLoader.scala | 12 +++--- launch/src/main/scala/xsbt/boot/Pre.scala | 4 ++ .../main/scala/xsbt/boot/SimpleReader.scala | 43 ------------------- project/Proguard.scala | 8 +--- project/Sbt.scala | 9 ++-- project/Util.scala | 3 +- 10 files changed, 35 insertions(+), 77 deletions(-) delete mode 100644 launch/src/main/scala/xsbt/boot/SimpleReader.scala diff --git a/launch/NOTICE b/launch/NOTICE index a55686429..d0158f707 100644 --- a/launch/NOTICE +++ b/launch/NOTICE @@ -3,12 +3,9 @@ Copyright 2008, 2009, 2010 Mark Harrah, David MacIver Licensed under BSD-style license (see LICENSE) Classes from the Scala library are distributed with the launcher. -Copyright 2002-2008 EPFL, Lausanne +Copyright 2002-2013 EPFL, Lausanne Licensed under BSD-style license (see licenses/LICENSE_Scala) -JLine is distributed with the launcher. -It is licensed under a BSD-style license (see licenses/LICENSE_JLine) - Classes from Apache Ivy, licensed under the Apache License, Version 2.0 (see licenses/LICENSE_Apache) are distributed with the launcher. It requires the following notice: @@ -32,4 +29,4 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - \ No newline at end of file + diff --git a/launch/src/main/scala/xsbt/boot/Boot.scala b/launch/src/main/scala/xsbt/boot/Boot.scala index f70d041ad..1e43269f6 100644 --- a/launch/src/main/scala/xsbt/boot/Boot.scala +++ b/launch/src/main/scala/xsbt/boot/Boot.scala @@ -15,12 +15,14 @@ object Boot println("sbt launcher version " + Package.getPackage("xsbt.boot").getImplementationVersion) case _ => System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM + System.setProperty("jline.shutdownhook", "false") CheckProxy() initJansi() + setLogFormat() run(args) } } - // this arrangement is because Scala 2.7.7 does not properly optimize away + // this arrangement is because Scala does not always properly optimize away // the tail recursion in a catch statement final def run(args: Array[String]): Unit = runImpl(args) match { case Some(newArgs) => run(newArgs) @@ -47,19 +49,21 @@ object Boot private def exit(code: Int): Nothing = System.exit(code).asInstanceOf[Nothing] + private[this] def setLogFormat() { + if(System.getProperty("sbt.log.format") eq null) + System.setProperty("sbt.log.format", "true") + } private def initJansi() { try { val c = Class.forName("org.fusesource.jansi.AnsiConsole") c.getMethod("systemInstall").invoke(null) - if (System.getProperty("sbt.log.format") eq null) - System.setProperty("sbt.log.format", "true") } catch { case ignore: ClassNotFoundException => - /* The below code intentionally traps everything. It technically shouldn't trap the - * non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen - * mitigation code that should not render sbt completely unusable if jansi initialization fails. - * [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578]. - */ + /* The below code intentionally traps everything. It technically shouldn't trap the + * non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen + * mitigation code that should not render sbt completely unusable if jansi initialization fails. + * [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578]. + */ case ex: Throwable => println("Jansi found on class path but initialization failed: " + ex) } } diff --git a/launch/src/main/scala/xsbt/boot/BootConfiguration.scala b/launch/src/main/scala/xsbt/boot/BootConfiguration.scala index 699bf0a8a..e64af227c 100644 --- a/launch/src/main/scala/xsbt/boot/BootConfiguration.scala +++ b/launch/src/main/scala/xsbt/boot/BootConfiguration.scala @@ -42,8 +42,6 @@ private object BootConfiguration /** The class name prefix used to hide the launcher classes from the application. * Note that access to xsbti classes are allowed.*/ final val SbtBootPackage = "xsbt.boot." - /** The prefix for JLine resources.*/ - final val JLinePackagePath = "jline/" /** The loader will check that these classes can be loaded and will assume that their presence indicates * the Scala compiler and library have been downloaded.*/ val TestLoadScalaClasses = "scala.Option" :: "scala.tools.nsc.Global" :: Nil diff --git a/launch/src/main/scala/xsbt/boot/Create.scala b/launch/src/main/scala/xsbt/boot/Create.scala index 36a0f3dc9..384afbd5e 100644 --- a/launch/src/main/scala/xsbt/boot/Create.scala +++ b/launch/src/main/scala/xsbt/boot/Create.scala @@ -15,7 +15,7 @@ object Initialize lazy val selectFill = (_: AppProperty).fill def create(file: File, promptCreate: String, enableQuick: Boolean, spec: List[AppProperty]) { - SimpleReader.readLine(promptCreate + " (y/N" + (if(enableQuick) "/s" else "") + ") ") match + readLine(promptCreate + " (y/N" + (if(enableQuick) "/s" else "") + ") ") match { case None => declined("") case Some(line) => @@ -52,7 +52,7 @@ object Initialize case set: SetProperty => properties.setProperty(name, set.value) case prompt: PromptProperty => def noValue = declined("No value provided for " + prompt.label) - SimpleReader.readLine(prompt.label + prompt.default.toList.map(" [" + _ + "]").mkString + ": ") match + readLine(prompt.label + prompt.default.toList.map(" [" + _ + "]").mkString + ": ") match { case None => noValue case Some(line) => diff --git a/launch/src/main/scala/xsbt/boot/FilteredLoader.scala b/launch/src/main/scala/xsbt/boot/FilteredLoader.scala index 1e35cb6d6..fbc46bb54 100644 --- a/launch/src/main/scala/xsbt/boot/FilteredLoader.scala +++ b/launch/src/main/scala/xsbt/boot/FilteredLoader.scala @@ -3,7 +3,7 @@ */ package xsbt.boot -import BootConfiguration.{FjbgPackage, IvyPackage, JLinePackagePath, SbtBootPackage, ScalaPackage} +import BootConfiguration.{FjbgPackage, IvyPackage, SbtBootPackage, ScalaPackage} import scala.collection.immutable.Stream /** A custom class loader to ensure the main part of sbt doesn't load any Scala or @@ -13,17 +13,17 @@ private[boot] final class BootFilteredLoader(parent: ClassLoader) extends ClassL @throws(classOf[ClassNotFoundException]) override final def loadClass(className: String, resolve: Boolean): Class[_] = { - // note that we allow xsbti.* and jline.* + // note that we allow xsbti.* if(className.startsWith(ScalaPackage) || className.startsWith(IvyPackage) || className.startsWith(SbtBootPackage) || className.startsWith(FjbgPackage)) throw new ClassNotFoundException(className) else super.loadClass(className, resolve) } - override def getResources(name: String) = if(includeResource(name)) super.getResources(name) else excludedLoader.getResources(name) - override def getResource(name: String) = if(includeResource(name)) super.getResource(name) else excludedLoader.getResource(name) - def includeResource(name: String) = name.startsWith(JLinePackagePath) + override def getResources(name: String) = excludedLoader.getResources(name) + override def getResource(name: String) = excludedLoader.getResource(name) + // the loader to use when a resource is excluded. This needs to be at least parent.getParent so that it skips parent. parent contains - // resources included in the launcher, which need to be ignored. Now that launcher can be unrooted (not the application entry point), + // resources included in the launcher, which need to be ignored. Now that the launcher can be unrooted (not the application entry point), // this needs to be the Java extension loader (the loader with getParent == null) private val excludedLoader = Loaders(parent.getParent).head } diff --git a/launch/src/main/scala/xsbt/boot/Pre.scala b/launch/src/main/scala/xsbt/boot/Pre.scala index da4bc5a71..30f091e48 100644 --- a/launch/src/main/scala/xsbt/boot/Pre.scala +++ b/launch/src/main/scala/xsbt/boot/Pre.scala @@ -9,6 +9,10 @@ package xsbt.boot object Pre { + def readLine(prompt: String): Option[String] = { + val c = System.console() + if(c eq null) None else Option(c.readLine(prompt)) + } def trimLeading(line: String) = { def newStart(i: Int): Int = if(i >= line.length || !Character.isWhitespace(line.charAt(i))) i else newStart(i+1) diff --git a/launch/src/main/scala/xsbt/boot/SimpleReader.scala b/launch/src/main/scala/xsbt/boot/SimpleReader.scala deleted file mode 100644 index e76bbb2bc..000000000 --- a/launch/src/main/scala/xsbt/boot/SimpleReader.scala +++ /dev/null @@ -1,43 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah - */ -package xsbt.boot - -import jline.console.ConsoleReader -abstract class JLine -{ - protected[this] val reader: ConsoleReader - def readLine(prompt: String) = JLine.withJLine { unsynchronizedReadLine(prompt) } - private[this] def unsynchronizedReadLine(prompt: String) = - reader.readLine(prompt) match - { - case null => None - case x => Some(x.trim) - } -} -private object JLine -{ - def terminal = jline.TerminalFactory.get - def createReader() = - terminal.synchronized - { - val cr = new ConsoleReader - terminal.setEchoEnabled(true) - cr.setBellEnabled(false) - cr - } - def withJLine[T](action: => T): T = - { - val t = terminal - t.synchronized - { - t.setEchoEnabled(false) - try { action } - finally { t.setEchoEnabled(true) } - } - } -} -object SimpleReader extends JLine -{ - protected[this] val reader = JLine.createReader() -} \ No newline at end of file diff --git a/project/Proguard.scala b/project/Proguard.scala index 2eb10ea75..4f01ce128 100644 --- a/project/Proguard.scala +++ b/project/Proguard.scala @@ -36,7 +36,7 @@ object LaunchProguard ) def specific(launchSub: Reference): Seq[Setting[_]] = inConfig(Proguard)(Seq( - keepFullClasses ++= "xsbti.**" :: "jline.**" :: Nil, + keepFullClasses ++= "xsbti.**" :: Nil, keepClasses ++= "org.apache.ivy.plugins.resolver.URLResolver" :: "org.apache.ivy.plugins.resolver.IBiblioResolver" :: Nil, artifactPath <<= (target, version) { (out,v) => out / ("sbt-launch-" + v + ".jar") }, options <++= (dependencyClasspath in (launchSub, Compile), compile in (launchSub,Compile), streams) map { (cp, analysis, s) => mapJars(cp.files, analysis.relations.allBinaryDeps.toSeq, s.log) }, @@ -95,18 +95,15 @@ object LaunchProguard def mapInJars(inJars: Seq[File], log: Logger): Seq[String] = { - val (jlineJars, notJLine) = inJars partition isJarX("jline") - val (ivyJars, notIvy) = notJLine partition isJarX("ivy") + val (ivyJars, notIvy) = inJars partition isJarX("ivy") val (libraryJar, remaining) = notIvy partition isJarX("scala-library") val (compilerJar, otherJars) = remaining partition isJarX("scala-compiler") log.debug("proguard configuration:") - log.debug("\tJLline jar location: " + jlineJars.mkString(", ")) log.debug("\tIvy jar location: " + ivyJars.mkString(", ")) log.debug("\tOther jars:\n\t" + otherJars.mkString("\n\t")) ((withJar(ivyJars.toSeq, "Ivy") + excludeString(excludeIvyResources)) :: - (withJar(jlineJars, "JLine") + jlineFilter ) :: (withJar(libraryJar, "Scala library") + libraryFilter) :: otherJars.map(jar => mkpath(jar) + generalFilter).toList) map { "-injars " + _ } } @@ -125,7 +122,6 @@ object LaunchProguard Nil private def libraryFilter = "(!META-INF/**,!*.properties,!scala/actors/**,!scala/util/parsing/*.class,**.class)" - private def jlineFilter = "(!META-INF/**)" private def generalFilter = "(!META-INF/**,!*.properties)" private def withJar[T](files: Seq[File], name: String) = mkpath(files.headOption getOrElse error(name + " not present") ) diff --git a/project/Sbt.scala b/project/Sbt.scala index b6cfd5be3..19e148c30 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -62,9 +62,9 @@ object Sbt extends Build // Utilities related to reflection, managing Scala versions, and custom class loaders lazy val classpathSub = baseProject(utilPath / "classpath", "Classpath") dependsOn(launchInterfaceSub, interfaceSub, ioSub) settings(scalaCompiler) // Command line-related utilities. - lazy val completeSub = testedBaseProject(utilPath / "complete", "Completion") dependsOn(collectionSub, controlSub, ioSub) settings(jline) + lazy val completeSub = testedBaseProject(utilPath / "complete", "Completion") dependsOn(collectionSub, controlSub, ioSub) settings(jline : _*) // logging - lazy val logSub = testedBaseProject(utilPath / "log", "Logging") dependsOn(interfaceSub, processSub) settings(libraryDependencies += jlineDep % "optional") + lazy val logSub = testedBaseProject(utilPath / "log", "Logging") dependsOn(interfaceSub, processSub) settings(jline : _*) // Relation lazy val relationSub = testedBaseProject(utilPath / "relation", "Relation") dependsOn(interfaceSub, processSub) // class file reader and analyzer @@ -155,7 +155,8 @@ object Sbt extends Build def scriptedTask: Initialize[InputTask[Unit]] = InputTask(scriptedSource(dir => (s: State) => scriptedParser(dir))) { result => (proguard in Proguard, fullClasspath in scriptedSbtSub in Test, scalaInstance in scriptedSbtSub, publishAll, scriptedSource, result) map { (launcher, scriptedSbtClasspath, scriptedSbtInstance, _, sourcePath, args) => - val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, scriptedSbtInstance.loader) + val noJLine = new classpath.FilteredLoader(scriptedSbtInstance.loader, "jline." :: Nil) + val loader = classpath.ClasspathUtilities.toLoader(scriptedSbtClasspath.files, noJLine) val m = ModuleUtilities.getObject("sbt.test.ScriptedTests", loader) val r = m.getClass.getMethod("run", classOf[File], classOf[Boolean], classOf[Array[String]], classOf[File], classOf[Array[String]]) val launcherVmOptions = Array("-XX:MaxPermSize=256M") // increased after a failure in scripted source-dependencies/macro @@ -190,7 +191,7 @@ object Sbt extends Build Util.inAllProjects(projects filterNot Set(root, sbtSub, scriptedBaseSub, scriptedSbtSub, scriptedPluginSub) map { p => LocalProject(p.id) }, scoped) def launchSettings = - Seq(jline, ivy, crossPaths := false, + Seq(ivy, crossPaths := false, compile in Test <<= compile in Test dependsOn(publishLocal in interfaceSub, publishLocal in testSamples, publishLocal in launchInterfaceSub) // mappings in (Compile, packageBin) <++= (mappings in (launchInterfaceSub, Compile, packageBin) ).identity ) ++ diff --git a/project/Util.scala b/project/Util.scala index 6a3d3f4ea..4b62e266d 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -156,7 +156,8 @@ object Common { def lib(m: ModuleID) = libraryDependencies += m lazy val jlineDep = "jline" % "jline" % "2.10" - lazy val jline = lib(jlineDep) + lazy val jansiDep = "org.fusesource.jansi" % "jansi" % "1.9" // jline pom doesn't explicitly declare it? + lazy val jline = Seq(lib(jlineDep), lib(jansiDep)) lazy val ivy = lib("org.apache.ivy" % "ivy" % "2.3.0-rc1") lazy val httpclient = lib("commons-httpclient" % "commons-httpclient" % "3.1") lazy val jsch = lib("com.jcraft" % "jsch" % "0.1.46" intransitive() )