diff --git a/main/src/main/scala/sbt/dsl/package.scala b/main/src/main/scala/sbt/dsl/package.scala index 4ee17980e..d3b553373 100644 --- a/main/src/main/scala/sbt/dsl/package.scala +++ b/main/src/main/scala/sbt/dsl/package.scala @@ -2,7 +2,7 @@ package sbt import internals.{ DslEntry, - DslSetting, + DslConfigs, DslEnablePlugins, DslDisablePlugins } @@ -10,4 +10,6 @@ import internals.{ package object dsl { def enablePlugins(ps: AutoPlugin*): DslEntry = DslEnablePlugins(ps) def disablePlugins(ps: AutoPlugin*): DslEntry = DslDisablePlugins(ps) + def configs(cs: Configuration*): DslEntry = DslConfigs(cs) + } \ No newline at end of file diff --git a/main/src/main/scala/sbt/internals/DslAst.scala b/main/src/main/scala/sbt/internals/DslAst.scala index 2476cf7a5..9cb5cfea7 100644 --- a/main/src/main/scala/sbt/internals/DslAst.scala +++ b/main/src/main/scala/sbt/internals/DslAst.scala @@ -54,4 +54,8 @@ case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulatio case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation { override val toFunction: Project => Project = _.disablePlugins(plugins: _*) } +/** Represents registering a set of configurations with the current project. */ +case class DslConfigs(cs: Seq[Configuration]) extends ProjectManipulation { + override val toFunction: Project => Project = _.configs(cs: _*) +} diff --git a/sbt/src/sbt-test/tests/scala-instance-classloader/build.sbt b/sbt/src/sbt-test/tests/scala-instance-classloader/build.sbt new file mode 100644 index 000000000..65dabf892 --- /dev/null +++ b/sbt/src/sbt-test/tests/scala-instance-classloader/build.sbt @@ -0,0 +1,29 @@ + +lazy val OtherScala = config("other-scala").hide + +configs(OtherScala) + +libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.1" % OtherScala.name + +managedClasspath in OtherScala := Classpaths.managedJars(OtherScala, classpathTypes.value, update.value) + +// Hack in the scala instance +scalaInstance := { + val rawJars = (managedClasspath in OtherScala).value.map(_.data) + val scalaHome = (target.value / "scala-home") + def removeVersion(name: String): String = + name.replaceAll("\\-2.11.1", "") + for(jar <- rawJars) { + val tjar = scalaHome / s"lib/${removeVersion(jar.getName)}" + IO.copyFile(jar, tjar) + } + IO.listFiles(scalaHome).foreach(f => System.err.println(s" * $f}")) + ScalaInstance(scalaHome, appConfiguration.value.provider.scalaProvider.launcher) +} + + +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" + +libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.3" % "test" + +scalaVersion := "2.11.0" diff --git a/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala b/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala new file mode 100644 index 000000000..6006e8257 --- /dev/null +++ b/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala @@ -0,0 +1,21 @@ +package akka.actor + +import org.junit._ + +class BadTest { + + @Test + def testCpIssue(): Unit = { + // TODO - This is merely the laziest way to run the test. What we want to do: + // * Load something from our own classloader that's INSIDE the scala library + // * Try to load that same something from the THREAD CONTEXT classloader. + // * Ensure we can do both, i.e. the second used to be filtered and broken. + val current = Thread.currentThread.getContextClassLoader + val mine = this.getClass.getClassLoader + val system = ActorSystem() + def evilGetThreadExectionContextName = + system.asInstanceOf[ActorSystemImpl].internalCallingThreadExecutionContext.getClass.getName + val expected = "scala.concurrent.Future$InternalCallbackExecutor$" + Assert.assertEquals("Failed to grab appropriate Akka name", expected, evilGetThreadExectionContextName) + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/scala-instance-classloader/test b/sbt/src/sbt-test/tests/scala-instance-classloader/test new file mode 100644 index 000000000..a270b7b48 --- /dev/null +++ b/sbt/src/sbt-test/tests/scala-instance-classloader/test @@ -0,0 +1 @@ +> test \ No newline at end of file diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 6595fd472..0bbab2058 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -191,7 +191,12 @@ object TestFramework { val notInterfaceFilter = (name: String) => !interfaceFilter(name) val dual = new DualLoader(scalaInstance.loader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false) val main = ClasspathUtilities.makeLoader(classpath, dual, scalaInstance, tempDir) - ClasspathUtilities.filterByClasspath(interfaceJar +: classpath, main) + // TODO - There's actually an issue with the classpath facility such that unmanagedScalaInstances are not added + // to the classpath correctly. We have a temporary workaround here. + val cp: Seq[File] = + if (scalaInstance.isManagedVersion) interfaceJar +: classpath + else scalaInstance.allJars ++ (interfaceJar +: classpath) + ClasspathUtilities.filterByClasspath(cp, main) } def createTestFunction(loader: ClassLoader, taskDef: TaskDef, runner: TestRunner, testTask: TestTask): TestFunction = new TestFunction(taskDef, runner, (r: TestRunner) => withContextLoader(loader) { r.run(taskDef, testTask) }) { def tags = testTask.tags } diff --git a/util/classpath/src/main/scala/sbt/ScalaInstance.scala b/util/classpath/src/main/scala/sbt/ScalaInstance.scala index c0a207887..b2bf5bc3d 100644 --- a/util/classpath/src/main/scala/sbt/ScalaInstance.scala +++ b/util/classpath/src/main/scala/sbt/ScalaInstance.scala @@ -19,6 +19,12 @@ final class ScalaInstance(val version: String, val loader: ClassLoader, val libr @deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0") val compilerJar: File, @deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0") val extraJars: Seq[File], val explicitActual: Option[String]) extends xsbti.compile.ScalaInstance { + /** + * This tells us if the scalaInstance is from a managed (i.e. ivy-resolved) scala *or* + * if it's a free-floating ScalaInstance, in which case we need to do tricks to the classpaths we find + * because it won't be on them. + */ + final def isManagedVersion = explicitActual.isDefined // These are to implement xsbti.ScalaInstance @deprecated("Only `allJars` and `jars` can be reliably provided for modularized Scala.", "0.13.0") def otherJars: Array[File] = extraJars.toArray diff --git a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala index b50d84883..2a1362f7e 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala @@ -46,6 +46,13 @@ final class SelfFirstLoader(classpath: Seq[URL], parent: ClassLoader) extends Lo /** Doesn't load any classes itself, but instead verifies that all classes loaded through `parent` either come from `root` or `classpath`.*/ final class ClasspathFilter(parent: ClassLoader, root: ClassLoader, classpath: Set[File]) extends ClassLoader(parent) { + override def toString = + s"""|ClasspathFilter( + | parent = $parent + | root = $root + | cp = $classpath + |)""".stripMargin + private[this] val directories: Seq[File] = classpath.toSeq.filter(_.isDirectory) override def loadClass(className: String, resolve: Boolean): Class[_] = { diff --git a/util/classpath/src/main/scala/sbt/classpath/ClasspathUtilities.scala b/util/classpath/src/main/scala/sbt/classpath/ClasspathUtilities.scala index 2f7fa1d35..dd29d7f77 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClasspathUtilities.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClasspathUtilities.scala @@ -26,6 +26,13 @@ object ClasspathUtilities { new URLClassLoader(Path.toURLs(paths), parent) with RawResources with NativeCopyLoader { override def resources = resourceMap override val config = new NativeCopyConfig(nativeTemp, paths, javaLibraryPaths) + override def toString = + s"""|URLClassLoader with NativeCopyLoader with RawResources( + | urls = $paths, + | parent = $parent, + | resourceMap = ${resourceMap.keySet}, + | nativeTemp = $nativeTemp + |)""".stripMargin } def javaLibraryPaths: Seq[File] = IO.parseClasspath(System.getProperty("java.library.path")) diff --git a/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala b/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala index b51a403ea..e1d7ff67e 100644 --- a/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala +++ b/util/classpath/src/main/scala/sbt/classpath/DualLoader.scala @@ -12,6 +12,7 @@ final class NullLoader extends ClassLoader { override final def loadClass(className: String, resolve: Boolean): Class[_] = throw new ClassNotFoundException("No classes can be loaded from the null loader") override def getResource(name: String): URL = null override def getResources(name: String): Enumeration[URL] = null + override def toString = "NullLoader" } /** Exception thrown when `loaderA` and `loaderB` load a different Class for the same name. */ @@ -84,6 +85,8 @@ class DualLoader(parentA: ClassLoader, aOnlyClasses: String => Boolean, aOnlyRes new DualEnumeration(urlsA, urlsB) } } + + override def toString = s"DualLoader(a = $parentA, b = $parentB)" } /** Concatenates `a` and `b` into a single `Enumeration`.*/