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.
This commit is contained in:
Mark Harrah 2013-02-26 08:26:04 -05:00
parent 54c08115f6
commit 8c4ebabe19
10 changed files with 35 additions and 77 deletions

View File

@ -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.

View File

@ -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)
}
}

View File

@ -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

View File

@ -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) =>

View File

@ -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
}

View File

@ -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)

View File

@ -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()
}

View File

@ -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") )

View File

@ -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
) ++

View File

@ -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() )