diff options
author | Paul Phillips <paulp@improving.org> | 2011-11-21 20:38:58 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-11-21 20:38:58 +0000 |
commit | eb0643210f2007775df3d116f237056ec5916874 (patch) | |
tree | b568cfb05b6a881dc32d67fbff54497fbbcb7a0c | |
parent | f9278123ebc33bbd4833e431575c4d1b22404d57 (diff) | |
download | scala-eb0643210f2007775df3d116f237056ec5916874.tar.gz scala-eb0643210f2007775df3d116f237056ec5916874.tar.bz2 scala-eb0643210f2007775df3d116f237056ec5916874.zip |
Implemented manifest-based class-paths.
If you run a jar directly, like
scala foo.jar
Then if a Class-Path attribute is present in the jar manifest, the
classpath will be constructed from that instead of the arguments. Some
things remain to be determined, like whether it's supposed to replace
a classpath given on the command line or supplement it, and whether
the master jar should be on the classpath or only and exactly the jars
listed in the manifest.
There's a really nice test case, which won't be run of course, but I
can't stand going any further without tests for these hard to test on
all platforms things. The faux .check file shows what I see.
Closes SI-4355, review by harrah.
-rw-r--r-- | src/compiler/scala/tools/nsc/MainGenericRunner.scala | 23 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/ObjectRunner.scala | 14 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/io/Jar.scala | 8 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/util/ClassPath.scala | 14 | ||||
-rwxr-xr-x | test/script-tests/README | 8 | ||||
-rw-r--r-- | test/script-tests/jar-manifest/resources/MANIFEST.MF | 3 | ||||
-rwxr-xr-x | test/script-tests/jar-manifest/run-test | 41 | ||||
-rw-r--r-- | test/script-tests/jar-manifest/run-test.check | 30 | ||||
-rw-r--r-- | test/script-tests/jar-manifest/src/jar-test.scala | 34 |
9 files changed, 162 insertions, 13 deletions
diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala index cef13843dc..cc1139f8a7 100644 --- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala +++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala @@ -7,13 +7,28 @@ package scala.tools.nsc import java.net.URL import scala.tools.util.PathResolver - import io.{ File } import util.{ ClassPath, ScalaClassLoader } import Properties.{ versionString, copyrightString } import interpreter.{ ILoop } import GenericRunnerCommand._ +object JarRunner extends CommonRunner { + def runJar(settings: GenericRunnerSettings, jarPath: String, arguments: Seq[String]): Either[Throwable, Boolean] = { + val jar = new io.Jar(jarPath) + val mainClass = jar.mainClass getOrElse sys.error("Cannot find main class for jar: " + jarPath) + val jarURLs = ClassPath expandManifestPath jarPath + val urls = if (jarURLs.isEmpty) File(jarPath).toURL +: settings.classpathURLs else jarURLs + + if (settings.Ylogcp.value) { + Console.err.println("Running jar with these URLs as the classpath:") + urls foreach println + } + + runAndCatch(urls, mainClass, arguments) + } +} + /** An object that runs Scala code. It has three possible * sources for the code to run: pre-compiled code, a script file, * or interactive entry. @@ -56,11 +71,7 @@ class MainGenericRunner { case AsScript => ScriptRunner.runScriptAndCatch(settings, thingToRun, command.arguments) case AsJar => - ObjectRunner.runAndCatch( - File(thingToRun).toURL +: settings.classpathURLs, - new io.Jar(thingToRun).mainClass getOrElse sys.error("Cannot find main class for jar: " + thingToRun), - command.arguments - ) + JarRunner.runJar(settings, thingToRun, command.arguments) case Error => Right(false) case _ => diff --git a/src/compiler/scala/tools/nsc/ObjectRunner.scala b/src/compiler/scala/tools/nsc/ObjectRunner.scala index 6ff0718de7..110de7aad5 100644 --- a/src/compiler/scala/tools/nsc/ObjectRunner.scala +++ b/src/compiler/scala/tools/nsc/ObjectRunner.scala @@ -11,12 +11,7 @@ import util.ScalaClassLoader import java.lang.reflect.InvocationTargetException import util.Exceptional.unwrap -/** An object that runs another object specified by name. - * - * @author Lex Spoon - * @version 1.1, 2007/7/13 - */ -object ObjectRunner { +trait CommonRunner { /** Check whether a class with the specified name * exists on the specified class path. */ def classExists(urls: List[URL], objectName: String): Boolean = @@ -41,3 +36,10 @@ object ObjectRunner { catch { case e => Left(unwrap(e)) } } } + +/** An object that runs another object specified by name. + * + * @author Lex Spoon + * @version 1.1, 2007/7/13 + */ +object ObjectRunner extends CommonRunner { } diff --git a/src/compiler/scala/tools/nsc/io/Jar.scala b/src/compiler/scala/tools/nsc/io/Jar.scala index ad1598a85d..bbed5a9e20 100644 --- a/src/compiler/scala/tools/nsc/io/Jar.scala +++ b/src/compiler/scala/tools/nsc/io/Jar.scala @@ -40,7 +40,15 @@ class Jar(file: File) extends Iterable[JarEntry] { lazy val jarFile = new JarFile(file.jfile) lazy val manifest = withJarInput(s => Option(s.getManifest)) + def mainClass = manifest map (f => f(Name.MAIN_CLASS)) + /** The manifest-defined classpath String if available. */ + def classPathString: Option[String] = + for (m <- manifest ; cp <- m.attrs get Name.CLASS_PATH) yield cp + def classPathElements: List[String] = classPathString match { + case Some(s) => s split "\\s+" toList + case _ => Nil + } def withJarInput[T](f: JarInputStream => T): T = { val in = new JarInputStream(file.inputStream()) diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 484f809e6f..622b4db2a2 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -13,6 +13,7 @@ import io.{ File, Directory, Path, Jar, AbstractFile, ClassAndJarInfo } import scala.tools.util.StringOps.splitWhere import Jar.isJarOrZip import File.pathSeparator +import java.net.MalformedURLException /** <p> * This module provides star expansion of '-classpath' option arguments, behaves the same as @@ -110,11 +111,22 @@ object ClassPath { case dir => dir filter (_.isClassContainer) map (x => new java.io.File(dir.file, x.name) getPath) toList } } + /** Expand manifest jar classpath entries: these are either urls, or paths + * relative to the location of the jar. + */ + def expandManifestPath(jarPath: String): List[URL] = { + val file = File(jarPath) + if (!file.isFile) return Nil + + val baseDir = file.parent + new Jar(file).classPathElements map (elem => + specToURL(elem) getOrElse (baseDir / elem).toURL + ) + } /** A useful name filter. */ def isTraitImplementation(name: String) = name endsWith "$class.class" - import java.net.MalformedURLException def specToURL(spec: String): Option[URL] = try Some(new URL(spec)) catch { case _: MalformedURLException => None } diff --git a/test/script-tests/README b/test/script-tests/README new file mode 100755 index 0000000000..3f5c2ce19c --- /dev/null +++ b/test/script-tests/README @@ -0,0 +1,8 @@ +This is a fresh start for script tests. The fact that windows exists can +no longer be allowed to stand in the way of testing the wide range of +functionality which currently goes completely untested. So I'll just be +putting self-contained script tests in here to run some way that doesn't +depend on all the platform stars aligning all the time. Feel free to +join me. + +-- extempore, Nov 21 2011
\ No newline at end of file diff --git a/test/script-tests/jar-manifest/resources/MANIFEST.MF b/test/script-tests/jar-manifest/resources/MANIFEST.MF new file mode 100644 index 0000000000..93c54c3c6f --- /dev/null +++ b/test/script-tests/jar-manifest/resources/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: bippy.Runner +Class-Path: bippy.jar dingus.jar http://mirrors.ibiblio.org/pub/mirrors/maven2/com/thoughtworks/paranamer/paranamer/2.4/paranamer-2.4.jar diff --git a/test/script-tests/jar-manifest/run-test b/test/script-tests/jar-manifest/run-test new file mode 100755 index 0000000000..2c6d5876b8 --- /dev/null +++ b/test/script-tests/jar-manifest/run-test @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# + +set -e + +paranamerjar="http://mirrors.ibiblio.org/pub/mirrors/maven2/com/thoughtworks/paranamer/paranamer/2.4/paranamer-2.4.jar" +build=$(dirname $0)/../../../build/pack + +if [[ -n "$SCALA_HOME" ]]; then + scala="$SCALA_HOME/bin/scala" +elif [[ -d $build ]]; then + scala=$(cd $build && pwd -P)/bin/scala +else + scala="scala" +fi + +echo "$($scala -version 2>&1)" +scalac="${scala}c" + +[[ -d lib ]] || mkdir lib +[[ -f lib/paranamer-2.4.jar ]] || ( printf >&2 "Grabbing paranamer jar\n\n" && cd lib && wget --quiet "$paranamerjar" ) + +rm -rf target && mkdir target +"$scalac" -d target -cp lib/'*' src/*.scala +cd target + +jar cmf ../resources/MANIFEST.MF bippy.jar bippy +jar cf dingus.jar dingus + +run () { + echo "" + echo "% $@" + "$@" +} + +cat <<EOM +$(run pwd) +$(run jar tf bippy.jar) +$(run jar tf dingus.jar) +$(run $scala $@ bippy.jar) +EOM diff --git a/test/script-tests/jar-manifest/run-test.check b/test/script-tests/jar-manifest/run-test.check new file mode 100644 index 0000000000..ef59a6cbac --- /dev/null +++ b/test/script-tests/jar-manifest/run-test.check @@ -0,0 +1,30 @@ +Scala code runner version 2.10.0.r26038-b20111121102734 -- Copyright 2002-2011, LAMP/EPFL + +% pwd +/scala/trunk/test/script-tests/jar-manifest/target + +% jar tf bippy.jar +META-INF/ +META-INF/MANIFEST.MF +bippy/ +bippy/Runner$$anonfun$main$1.class +bippy/Runner$$anonfun$main$2.class +bippy/Runner$$anonfun$main$3.class +bippy/Runner$.class +bippy/Runner.class + +% jar tf dingus.jar +META-INF/ +META-INF/MANIFEST.MF +dingus/ +dingus/Printable.class + +% /scala/trunk/build/pack/bin/scala bippy.jar +1 "Greetings from dingus.jar!" +2 bippyBingle has parameters: imparametorium, antidisestablish, x +3 bippyBoo has parameters: quuxParameter +4 +5 Urls exposed through the classloader: +6 file:/scala/trunk/test/script-tests/jar-manifest/target/./bippy.jar +7 file:/scala/trunk/test/script-tests/jar-manifest/target/./dingus.jar +8 http://mirrors.ibiblio.org/pub/mirrors/maven2/com/thoughtworks/paranamer/paranamer/2.4/paranamer-2.4.jar diff --git a/test/script-tests/jar-manifest/src/jar-test.scala b/test/script-tests/jar-manifest/src/jar-test.scala new file mode 100644 index 0000000000..80e3aafff0 --- /dev/null +++ b/test/script-tests/jar-manifest/src/jar-test.scala @@ -0,0 +1,34 @@ +import scala.tools.nsc.util.HasClassPath + +package bippy { + object Runner { + var line = 0 + def echo(msgs: Any*) = { + line += 1 + Console.println("%-2s %s".format(line, msgs mkString " ")) + } + + def bippyBoo(quuxParameter: Int) = 5 + def bippyBingle(imparametorium: String, antidisestablish: Int, x: Float) = () + + def main(args: Array[String]): Unit = { + echo(new dingus.Printable) + val namer = new com.thoughtworks.paranamer.BytecodeReadingParanamer + getClass.getMethods filter (_.getName startsWith "bippy") foreach { m => + echo(m.getName, "has parameters:", namer.lookupParameterNames(m).mkString(", ")) + } + echo("") + echo("Urls exposed through the classloader:") + getClass.getClassLoader match { + case x: HasClassPath => x.classPathURLs foreach (x => echo(x)) + case _ => echo("None! Seems unlikely we'd get this far then.") + } + } + } +} + +package dingus { + class Printable { + override def toString = "\"Greetings from dingus.jar!\"" + } +}
\ No newline at end of file |