From d2b86f5cc58e0c80aee7a3fecf0cb54a48209882 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Aug 2014 23:37:45 -0400 Subject: [PATCH 01/11] notes --- notes/0.13.6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/notes/0.13.6.md b/notes/0.13.6.md index cfd21d5d6..ea3078a09 100644 --- a/notes/0.13.6.md +++ b/notes/0.13.6.md @@ -48,6 +48,7 @@ [1494]: https://github.com/sbt/sbt/pull/1494 [1516]: https://github.com/sbt/sbt/pull/1516 [1465]: https://github.com/sbt/sbt/issues/1465 + [1514]: https://github.com/sbt/sbt/issues/1514 [1524]: https://github.com/sbt/sbt/issues/1524 [1530]: https://github.com/sbt/sbt/issues/1530 [1536]: https://github.com/sbt/sbt/pull/1536 From a5a44a88c27059a00f6b2c5b83cef19f2771db1a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Aug 2014 20:36:28 -0400 Subject: [PATCH 02/11] #1541. Use HTTPS for sbt plugin repository --- ivy/src/main/scala/sbt/Resolver.scala | 6 ++++-- notes/0.13.6.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ivy/src/main/scala/sbt/Resolver.scala b/ivy/src/main/scala/sbt/Resolver.scala index 4ff1f10f0..87257fc87 100644 --- a/ivy/src/main/scala/sbt/Resolver.scala +++ b/ivy/src/main/scala/sbt/Resolver.scala @@ -149,7 +149,7 @@ object Resolver { private[sbt] def useSecureResolvers = sys.props.get("sbt.repository.secure") map { _.toLowerCase == "true" } getOrElse true val TypesafeRepositoryRoot = typesafeRepositoryRoot(useSecureResolvers) - val SbtPluginRepositoryRoot = "http://repo.scala-sbt.org/scalasbt" + val SbtPluginRepositoryRoot = sbtPluginRepositoryRoot(useSecureResolvers) val SonatypeRepositoryRoot = "https://oss.sonatype.org/content/repositories" val JavaNet2RepositoryName = "java.net Maven2 Repository" val JavaNet2RepositoryRoot = javanet2RepositoryRoot(useSecureResolvers) @@ -164,7 +164,9 @@ object Resolver { else "http://download.java.net/maven/2" // TODO: This switch is only kept for backward compatibility. Hardcode to HTTPS in the future. private[sbt] def typesafeRepositoryRoot(secure: Boolean) = (if (secure) "https" else "http") + "://repo.typesafe.com/typesafe" - + // TODO: This switch is only kept for backward compatibility. Hardcode to HTTPS in the future. + private[sbt] def sbtPluginRepositoryRoot(secure: Boolean) = (if (secure) "https" else "http") + "://repo.scala-sbt.org/scalasbt" + // obsolete: kept only for launcher compatibility private[sbt] val ScalaToolsReleasesName = "Sonatype OSS Releases" private[sbt] val ScalaToolsSnapshotsName = "Sonatype OSS Snapshots" diff --git a/notes/0.13.6.md b/notes/0.13.6.md index ea3078a09..845216584 100644 --- a/notes/0.13.6.md +++ b/notes/0.13.6.md @@ -79,7 +79,7 @@ ### Fixes with compatibility implications -- Maven Central Repository, Java.net Maven 2 Repository, and Typesafe Repository now defaults to HTTPS. (See below) +- Maven Central Repository, Java.net Maven 2 Repository, Typesafe Repository, and sbt Plugin repository now defaults to HTTPS. (See below) - `ThisProject` used to resolve to the root project in a build even when it's place in `subproj/build.sbt`. sbt 0.13.6 fixes it to resolve to the sub project. [#1194][1194]/[#1358][1358] by [@dansanduleac][@dansanduleac] - Global plugins classpath used to be injected into every build. This will no longer be the case. [#1347][1347]/[#1352][1352] by [@dansanduleac][@dansanduleac] - Fixes `newer` command in scripted. [#1419][1419] by [@jroper][@jroper] @@ -128,7 +128,7 @@ Thanks to Sonatype, HTTPS access to Maven Central Repository is available to pub -Dsbt.repository.secure=false -Java.net Maven 2 repository and Typesafe repository also defaults to HTTPS. +Java.net Maven 2 repository, Typesafe repository, and sbt Plugin repository also defaults to HTTPS. [#1494][1494] by [@rtyley][@rtyley], [#1536][1536] by [@benmccann][@benmccann], and [#1541][1541] by [@eed3si9n][@eed3si9n]. From cfd9da578939b8d0d3f27ec825bec5c0c8ca2d5a Mon Sep 17 00:00:00 2001 From: James Ward Date: Sun, 31 Aug 2014 09:51:58 -0600 Subject: [PATCH 03/11] Fix url to sbt-dev group --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4455751f3..afa6d730e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [CONTRIBUTING]: CONTRIBUTING.md [Setup]: http://www.scala-sbt.org/release/docs/Getting-Started/Setup [FAQ]: http://www.scala-sbt.org/release/docs/faq - [sbt-dev]: https://groups.google.com/d/forum/sbt-dev‎ + [sbt-dev]: https://groups.google.com/d/forum/sbt-dev [StackOverflow]: http://stackoverflow.com/tags/sbt [LICENSE]: LICENSE From a6d85ae2cbaf459411ba6ba8dfc0881e6e07725f Mon Sep 17 00:00:00 2001 From: Antonio Cunei Date: Thu, 4 Sep 2014 01:15:58 +0200 Subject: [PATCH 04/11] Allow the "-bin" Scala version suffix to specify a bincompat version There is sometimes the need to use a test version of Scala that is intended to be binary compatible with a standard release version. At this time, due to the particular logic implemented within sbt, a non-numeric suffix will never have the same binaryScalaVersion of a release version ending in ".0", like for instance "2.11.0". This commit allows developers to use as suffix any string that begins with "-bin", for instance "2.11.0-bin-compat-test-1". Such a suffix will have a binaryScalaVersion of "2.11", being therefore considered binary compatible with release versions. --- util/cross/src/main/input_sources/CrossVersionUtil.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/cross/src/main/input_sources/CrossVersionUtil.scala b/util/cross/src/main/input_sources/CrossVersionUtil.scala index 24310b39c..6a63c9139 100644 --- a/util/cross/src/main/input_sources/CrossVersionUtil.scala +++ b/util/cross/src/main/input_sources/CrossVersionUtil.scala @@ -39,9 +39,11 @@ object CrossVersionUtil private[${{cross.package0}}] def scalaApiVersion(v: String): Option[(Int, Int)] = { val ReleaseV = """(\d+)\.(\d+)\.(\d+)(-\d+)?""".r + val BinCompatV = """(\d+)\.(\d+)\.(\d+)-bin(-.*)?""".r val NonReleaseV = """(\d+)\.(\d+)\.(\d+)(-\w+)""".r v match { case ReleaseV(x, y, z, ht) => Some((x.toInt, y.toInt)) + case BinCompatV(x, y, z, ht) => Some((x.toInt, y.toInt)) case NonReleaseV(x, y, z, ht) if z.toInt > 0 => Some((x.toInt, y.toInt)) case _ => None } From dbf69b11cdeef0edb5db40bb8ba39f4a10fe14eb Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 5 Sep 2014 09:21:22 -0400 Subject: [PATCH 05/11] Split out inter-project resolver into its own Chain. Fixes #1565 * Create two chains if we have inter-project resolver - One which ensures inter-project deps are always taken - One which will look in all "other" repositories for dependencies and will use the "most up-to-date" -SNAPSHOT strategy. * No additional tests, as this would simple break every multi-project test if it were wrong. --- ivy/src/main/scala/sbt/ConvertResolver.scala | 2 +- ivy/src/main/scala/sbt/Ivy.scala | 230 ++---------------- ivy/src/main/scala/sbt/Resolver.scala | 2 +- .../scala/sbt/ivyint/SbtChainResolver.scala | 230 ++++++++++++++++++ 4 files changed, 249 insertions(+), 215 deletions(-) create mode 100644 ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala diff --git a/ivy/src/main/scala/sbt/ConvertResolver.scala b/ivy/src/main/scala/sbt/ConvertResolver.scala index 8097ae5b9..70a0530e1 100644 --- a/ivy/src/main/scala/sbt/ConvertResolver.scala +++ b/ivy/src/main/scala/sbt/ConvertResolver.scala @@ -17,7 +17,7 @@ import java.io.File import org.apache.ivy.util.ChecksumHelper import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact } -private object ConvertResolver { +private[sbt] object ConvertResolver { /** * This class contains all the reflective lookups used in the * checksum-friendly URL publishing shim. diff --git a/ivy/src/main/scala/sbt/Ivy.scala b/ivy/src/main/scala/sbt/Ivy.scala index b8908200f..55a596bbc 100644 --- a/ivy/src/main/scala/sbt/Ivy.scala +++ b/ivy/src/main/scala/sbt/Ivy.scala @@ -236,7 +236,7 @@ final class IvySbt(val configuration: IvyConfiguration) { } } -private object IvySbt { +private[sbt] object IvySbt { val DefaultIvyConfigFilename = "ivysettings.xml" val DefaultIvyFilename = "ivy.xml" val DefaultMavenFilename = "pom.xml" @@ -273,219 +273,23 @@ private object IvySbt { mrid.getRevision endsWith "-SNAPSHOT" def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, log: Logger): DependencyResolver = resolverChain(name, resolvers, localOnly, settings, UpdateOptions(), log) - def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, updateOptions: UpdateOptions, log: Logger): DependencyResolver = - { - val newDefault = new ChainResolver { - // Technically, this should be applied to module configurations. - // That would require custom subclasses of all resolver types in ConvertResolver (a delegation approach does not work). - // It would be better to get proper support into Ivy. - // A workaround is to configure the ModuleConfiguration resolver to be a ChainResolver. - // - // This method is only used by the pom parsing code in Ivy to find artifacts it doesn't know about. - // In particular, a) it looks up source and javadoc classifiers b) it looks up a main artifact for packaging="pom" - // sbt now provides the update-classifiers or requires explicitly specifying classifiers explicitly - // Providing a main artifact for packaging="pom" does not seem to be correct and the lookup can be expensive. - // - // Ideally this could just skip the lookup, but unfortunately several artifacts in practice do not follow the - // correct behavior for packaging="pom" and so it is only skipped for source/javadoc classifiers. - override def locate(artifact: IArtifact) = if (hasImplicitClassifier(artifact)) null else super.locate(artifact) + def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, settings: IvySettings, updateOptions: UpdateOptions, log: Logger): DependencyResolver = { + def mapResolvers(rs: Seq[Resolver]) = rs.map(r => ConvertResolver(r, settings, log)) + val (projectResolvers, rest) = resolvers.partition(_.name == "inter-project") + if (projectResolvers.isEmpty) new ivyint.SbtChainResolver(name, mapResolvers(rest), settings, updateOptions, log) + else { + // Here we set up a "first repo wins" chain resolver + val delegate = new ivyint.SbtChainResolver(name + "-delegate", mapResolvers(rest), settings, updateOptions, log) + val prs = mapResolvers(projectResolvers) + // Here we construct a chain resolver which will FORCE looking at the project resolver first. + new ivyint.SbtChainResolver(name, + prs :+ delegate, + settings, + UpdateOptions().withLatestSnapshots(false), + log) - override def getDependency(dd: DependencyDescriptor, data: ResolveData) = - { - if (data.getOptions.getLog != LogOptions.LOG_QUIET) - Message.info("Resolving " + dd.getDependencyRevisionId + " ...") - val gd = doGetDependency(dd, data) - val mod = resetArtifactResolver(gd) - mod - } - // Modified implementation of ChainResolver#getDependency. - // When the dependency is changing, it will check all resolvers on the chain - // regardless of what the "latest strategy" is set, and look for the published date - // or the module descriptor to sort them. - // This implementation also skips resolution if "return first" is set to true, - // and if a previously resolved or cached revision has been found. - def doGetDependency(dd: DependencyDescriptor, data0: ResolveData): ResolvedModuleRevision = - { - val useLatest = (dd.isChanging || (isChanging(dd.getDependencyRevisionId))) && updateOptions.latestSnapshots - if (useLatest) { - Message.verbose(s"${getName} is changing. Checking all resolvers on the chain") - } - val data = new ResolveData(data0, doValidate(data0)) - val resolved = Option(data.getCurrentResolvedModuleRevision) - val resolvedOrCached = - resolved orElse { - Message.verbose(getName + ": Checking cache for: " + dd) - Option(findModuleInCache(dd, data, true)) map { mr => - Message.verbose(getName + ": module revision found in cache: " + mr.getId) - forcedRevision(mr) - } - } - var temp: Option[ResolvedModuleRevision] = - if (useLatest) None - else resolvedOrCached - val resolvers = getResolvers.toArray.toVector collect { case x: DependencyResolver => x } - val results = resolvers map { x => - // if the revision is cached and isReturnFirst is set, don't bother hitting any resolvers - if (isReturnFirst && temp.isDefined && !useLatest) Right(None) - else { - val resolver = x - val oldLatest: Option[LatestStrategy] = setLatestIfRequired(resolver, Option(getLatestStrategy)) - try { - val previouslyResolved = temp - // if the module qualifies as changing, then resolve all resolvers - if (useLatest) data.setCurrentResolvedModuleRevision(None.orNull) - else data.setCurrentResolvedModuleRevision(temp.orNull) - temp = Option(resolver.getDependency(dd, data)) - val retval = Right( - if (temp eq previouslyResolved) None - else if (useLatest) temp map { x => - (reparseModuleDescriptor(dd, data, resolver, x), resolver) - } - else temp map { x => (forcedRevision(x), resolver) } - ) - retval - } catch { - case ex: Exception => - Message.verbose("problem occurred while resolving " + dd + " with " + resolver - + ": " + IvyStringUtils.getStackTrace(ex)) - Left(ex) - } finally { - oldLatest map { _ => doSetLatestStrategy(resolver, oldLatest) } - checkInterrupted - } - } - } - val errors = results collect { case Left(e) => e } - val foundRevisions: Vector[(ResolvedModuleRevision, DependencyResolver)] = results collect { case Right(Some(x)) => x } - val sorted = - if (useLatest) (foundRevisions.sortBy { - case (rmr, _) => - rmr.getDescriptor.getPublicationDate.getTime - }).reverse.headOption map { - case (rmr, resolver) => - // Now that we know the real latest revision, let's force Ivy to use it - val artifactOpt = findFirstArtifactRef(rmr.getDescriptor, dd, data, resolver) - artifactOpt match { - case None if resolver.getName == "inter-project" => // do nothing - case None => throw new RuntimeException("\t" + resolver.getName - + ": no ivy file nor artifact found for " + rmr) - case Some(artifactRef) => - val systemMd = toSystem(rmr.getDescriptor) - getRepositoryCacheManager.cacheModuleDescriptor(resolver, artifactRef, - toSystem(dd), systemMd.getAllArtifacts().head, None.orNull, getCacheOptions(data)) - } - rmr - } - else foundRevisions.reverse.headOption map { _._1 } - val mrOpt: Option[ResolvedModuleRevision] = sorted orElse resolvedOrCached - mrOpt match { - case None if errors.size == 1 => - errors.head match { - case e: RuntimeException => throw e - case e: ParseException => throw e - case e: Throwable => throw new RuntimeException(e.toString, e) - } - case None if errors.size > 1 => - val err = (errors.toList map { IvyStringUtils.getErrorMessage }).mkString("\n\t", "\n\t", "\n") - throw new RuntimeException(s"several problems occurred while resolving $dd:$err") - case _ => - if (resolved == mrOpt) resolved.orNull - else (mrOpt map { resolvedRevision }).orNull - } - } - // Ivy seem to not want to use the module descriptor found at the latest resolver - private[this] def reparseModuleDescriptor(dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver, rmr: ResolvedModuleRevision): ResolvedModuleRevision = - Option(resolver.findIvyFileRef(dd, data)) flatMap { ivyFile => - ivyFile.getResource match { - case r: FileResource => - try { - val parser = rmr.getDescriptor.getParser - val md = parser.parseDescriptor(settings, r.getFile.toURL, r, false) - Some(new ResolvedModuleRevision(resolver, resolver, md, rmr.getReport, true)) - } catch { - case _: ParseException => None - } - case _ => None - } - } getOrElse rmr - /** Ported from BasicResolver#findFirstAirfactRef. */ - private[this] def findFirstArtifactRef(md: ModuleDescriptor, dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver): Option[ResolvedResource] = - { - def artifactRef(artifact: IArtifact, date: Date): Option[ResolvedResource] = - resolver match { - case resolver: BasicResolver => - IvyContext.getContext.set(resolver.getName + ".artifact", artifact) - try { - Option(resolver.doFindArtifactRef(artifact, date)) orElse { - Option(artifact.getUrl) map { url => - Message.verbose("\tusing url for " + artifact + ": " + url) - val resource = - if ("file" == url.getProtocol) new FileResource(new IFileRepository(), new File(url.getPath())) - else new URLResource(url) - new ResolvedResource(resource, artifact.getModuleRevisionId.getRevision) - } - } - } finally { - IvyContext.getContext.set(resolver.getName + ".artifact", null) - } - case _ => - None - } - val artifactRefs = md.getConfigurations.toIterator flatMap { conf => - md.getArtifacts(conf.getName).toIterator flatMap { af => - artifactRef(af, data.getDate).toIterator - } - } - if (artifactRefs.hasNext) Some(artifactRefs.next) - else None - } - /** Ported from ChainResolver#forcedRevision. */ - private[this] def forcedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision = - new ResolvedModuleRevision(rmr.getResolver, rmr.getArtifactResolver, rmr.getDescriptor, rmr.getReport, true) - /** Ported from ChainResolver#resolvedRevision. */ - private[this] def resolvedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision = - if (isDual) new ResolvedModuleRevision(rmr.getResolver, this, rmr.getDescriptor, rmr.getReport, rmr.isForce) - else rmr - /** Ported from ChainResolver#setLatestIfRequired. */ - private[this] def setLatestIfRequired(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] = - latestStrategyName(resolver) match { - case Some(latestName) if latestName != "default" => - val oldLatest = latestStrategy(resolver) - doSetLatestStrategy(resolver, latest) - oldLatest - case _ => None - } - /** Ported from ChainResolver#getLatestStrategyName. */ - private[this] def latestStrategyName(resolver: DependencyResolver): Option[String] = - resolver match { - case r: HasLatestStrategy => Some(r.getLatest) - case _ => None - } - /** Ported from ChainResolver#getLatest. */ - private[this] def latestStrategy(resolver: DependencyResolver): Option[LatestStrategy] = - resolver match { - case r: HasLatestStrategy => Some(r.getLatestStrategy) - case _ => None - } - /** Ported from ChainResolver#setLatest. */ - private[this] def doSetLatestStrategy(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] = - resolver match { - case r: HasLatestStrategy => - val oldLatest = latestStrategy(resolver) - r.setLatestStrategy(latest.orNull) - oldLatest - case _ => None - } - } - newDefault.setName(name) - newDefault.setReturnFirst(true) - newDefault.setCheckmodified(false) - for (sbtResolver <- resolvers) { - log.debug("\t" + sbtResolver) - newDefault.add(ConvertResolver(sbtResolver, settings, log)) - } - newDefault } + } def addResolvers(resolvers: Seq[Resolver], settings: IvySettings, log: Logger) { for (r <- resolvers) { log.debug("\t" + r) @@ -528,7 +332,7 @@ private object IvySbt { // if there are problems with this, a less aggressive fix might be to only reset the artifact resolver when it is a ProjectResolver // a possible problem is that fetching artifacts is slower, due to the full chain being the artifact resolver instead of the specific resolver // This also fixes #760, which occurs when metadata exists in a repository, but the artifact doesn't. - private[this] def resetArtifactResolver(resolved: ResolvedModuleRevision): ResolvedModuleRevision = + private[sbt] def resetArtifactResolver(resolved: ResolvedModuleRevision): ResolvedModuleRevision = if (resolved eq null) null else { diff --git a/ivy/src/main/scala/sbt/Resolver.scala b/ivy/src/main/scala/sbt/Resolver.scala index 87257fc87..3678adadb 100644 --- a/ivy/src/main/scala/sbt/Resolver.scala +++ b/ivy/src/main/scala/sbt/Resolver.scala @@ -166,7 +166,7 @@ object Resolver { private[sbt] def typesafeRepositoryRoot(secure: Boolean) = (if (secure) "https" else "http") + "://repo.typesafe.com/typesafe" // TODO: This switch is only kept for backward compatibility. Hardcode to HTTPS in the future. private[sbt] def sbtPluginRepositoryRoot(secure: Boolean) = (if (secure) "https" else "http") + "://repo.scala-sbt.org/scalasbt" - + // obsolete: kept only for launcher compatibility private[sbt] val ScalaToolsReleasesName = "Sonatype OSS Releases" private[sbt] val ScalaToolsSnapshotsName = "Sonatype OSS Snapshots" diff --git a/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala b/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala new file mode 100644 index 000000000..685431124 --- /dev/null +++ b/ivy/src/main/scala/sbt/ivyint/SbtChainResolver.scala @@ -0,0 +1,230 @@ +package sbt +package ivyint + +import java.io.File +import java.text.ParseException +import java.util.Date + +import org.apache.ivy.core.settings.IvySettings +import org.apache.ivy.core.{ IvyContext, LogOptions } +import org.apache.ivy.core.module.descriptor.{ ModuleDescriptor, DependencyDescriptor, Artifact => IArtifact } +import org.apache.ivy.core.resolve.{ ResolvedModuleRevision, ResolveData } +import org.apache.ivy.plugins.latest.LatestStrategy +import org.apache.ivy.plugins.repository.file.{ FileRepository => IFileRepository, FileResource } +import org.apache.ivy.plugins.repository.url.URLResource +import org.apache.ivy.plugins.resolver.{ ChainResolver, BasicResolver, DependencyResolver } +import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource } +import org.apache.ivy.util.{ Message, MessageLogger, StringUtils => IvyStringUtils } + +class SbtChainResolver(name: String, resolvers: Seq[DependencyResolver], settings: IvySettings, updateOptions: UpdateOptions, log: Logger) extends ChainResolver { + // TODO - We need to special case the project resolver so it always "wins" when resolving with inter-project dependencies. + + // Initialize ourselves. + setName(name) + setReturnFirst(true) + setCheckmodified(false) + // Here we append all the resolvers we were passed *AND* look for + // a project resolver, which we will special-case. + resolvers.foreach(add) + + // Technically, this should be applied to module configurations. + // That would require custom subclasses of all resolver types in ConvertResolver (a delegation approach does not work). + // It would be better to get proper support into Ivy. + // A workaround is to configure the ModuleConfiguration resolver to be a ChainResolver. + // + // This method is only used by the pom parsing code in Ivy to find artifacts it doesn't know about. + // In particular, a) it looks up source and javadoc classifiers b) it looks up a main artifact for packaging="pom" + // sbt now provides the update-classifiers or requires explicitly specifying classifiers explicitly + // Providing a main artifact for packaging="pom" does not seem to be correct and the lookup can be expensive. + // + // Ideally this could just skip the lookup, but unfortunately several artifacts in practice do not follow the + // correct behavior for packaging="pom" and so it is only skipped for source/javadoc classifiers. + override def locate(artifact: IArtifact) = if (IvySbt.hasImplicitClassifier(artifact)) null else super.locate(artifact) + + override def getDependency(dd: DependencyDescriptor, data: ResolveData) = + { + if (data.getOptions.getLog != LogOptions.LOG_QUIET) + Message.info("Resolving " + dd.getDependencyRevisionId + " ...") + val gd = doGetDependency(dd, data) + val mod = IvySbt.resetArtifactResolver(gd) + mod + } + // Modified implementation of ChainResolver#getDependency. + // When the dependency is changing, it will check all resolvers on the chain + // regardless of what the "latest strategy" is set, and look for the published date + // or the module descriptor to sort them. + // This implementation also skips resolution if "return first" is set to true, + // and if a previously resolved or cached revision has been found. + def doGetDependency(dd: DependencyDescriptor, data0: ResolveData): ResolvedModuleRevision = + { + val useLatest = (dd.isChanging || (IvySbt.isChanging(dd.getDependencyRevisionId))) && updateOptions.latestSnapshots + if (useLatest) { + Message.verbose(s"${getName} is changing. Checking all resolvers on the chain") + } + val data = new ResolveData(data0, doValidate(data0)) + val resolved = Option(data.getCurrentResolvedModuleRevision) + val resolvedOrCached = + resolved orElse { + Message.verbose(getName + ": Checking cache for: " + dd) + Option(findModuleInCache(dd, data, true)) map { mr => + Message.verbose(getName + ": module revision found in cache: " + mr.getId) + forcedRevision(mr) + } + } + var temp: Option[ResolvedModuleRevision] = + if (useLatest) None + else resolvedOrCached + val resolvers = getResolvers.toArray.toVector collect { case x: DependencyResolver => x } + val results = resolvers map { x => + // if the revision is cached and isReturnFirst is set, don't bother hitting any resolvers + if (isReturnFirst && temp.isDefined && !useLatest) Right(None) + else { + val resolver = x + val oldLatest: Option[LatestStrategy] = setLatestIfRequired(resolver, Option(getLatestStrategy)) + try { + val previouslyResolved = temp + // if the module qualifies as changing, then resolve all resolvers + if (useLatest) data.setCurrentResolvedModuleRevision(None.orNull) + else data.setCurrentResolvedModuleRevision(temp.orNull) + temp = Option(resolver.getDependency(dd, data)) + val retval = Right( + if (temp eq previouslyResolved) None + else if (useLatest) temp map { x => + (reparseModuleDescriptor(dd, data, resolver, x), resolver) + } + else temp map { x => (forcedRevision(x), resolver) } + ) + retval + } catch { + case ex: Exception => + Message.verbose("problem occurred while resolving " + dd + " with " + resolver + + ": " + IvyStringUtils.getStackTrace(ex)) + Left(ex) + } finally { + oldLatest map { _ => doSetLatestStrategy(resolver, oldLatest) } + checkInterrupted + } + } + } + val errors = results collect { case Left(e) => e } + val foundRevisions: Vector[(ResolvedModuleRevision, DependencyResolver)] = results collect { case Right(Some(x)) => x } + val sorted = + if (useLatest) (foundRevisions.sortBy { + case (rmr, _) => + rmr.getDescriptor.getPublicationDate.getTime + }).reverse.headOption map { + case (rmr, resolver) => + // Now that we know the real latest revision, let's force Ivy to use it + val artifactOpt = findFirstArtifactRef(rmr.getDescriptor, dd, data, resolver) + artifactOpt match { + case None if resolver.getName == "inter-project" => // do nothing + case None => throw new RuntimeException("\t" + resolver.getName + + ": no ivy file nor artifact found for " + rmr) + case Some(artifactRef) => + val systemMd = toSystem(rmr.getDescriptor) + getRepositoryCacheManager.cacheModuleDescriptor(resolver, artifactRef, + toSystem(dd), systemMd.getAllArtifacts().head, None.orNull, getCacheOptions(data)) + } + rmr + } + else foundRevisions.reverse.headOption map { _._1 } + val mrOpt: Option[ResolvedModuleRevision] = sorted orElse resolvedOrCached + mrOpt match { + case None if errors.size == 1 => + errors.head match { + case e: RuntimeException => throw e + case e: ParseException => throw e + case e: Throwable => throw new RuntimeException(e.toString, e) + } + case None if errors.size > 1 => + val err = (errors.toList map { IvyStringUtils.getErrorMessage }).mkString("\n\t", "\n\t", "\n") + throw new RuntimeException(s"several problems occurred while resolving $dd:$err") + case _ => + if (resolved == mrOpt) resolved.orNull + else (mrOpt map { resolvedRevision }).orNull + } + } + // Ivy seem to not want to use the module descriptor found at the latest resolver + private[this] def reparseModuleDescriptor(dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver, rmr: ResolvedModuleRevision): ResolvedModuleRevision = + Option(resolver.findIvyFileRef(dd, data)) flatMap { ivyFile => + ivyFile.getResource match { + case r: FileResource => + try { + val parser = rmr.getDescriptor.getParser + val md = parser.parseDescriptor(settings, r.getFile.toURL, r, false) + Some(new ResolvedModuleRevision(resolver, resolver, md, rmr.getReport, true)) + } catch { + case _: ParseException => None + } + case _ => None + } + } getOrElse rmr + /** Ported from BasicResolver#findFirstAirfactRef. */ + private[this] def findFirstArtifactRef(md: ModuleDescriptor, dd: DependencyDescriptor, data: ResolveData, resolver: DependencyResolver): Option[ResolvedResource] = + { + def artifactRef(artifact: IArtifact, date: Date): Option[ResolvedResource] = + resolver match { + case resolver: BasicResolver => + IvyContext.getContext.set(resolver.getName + ".artifact", artifact) + try { + Option(resolver.doFindArtifactRef(artifact, date)) orElse { + Option(artifact.getUrl) map { url => + Message.verbose("\tusing url for " + artifact + ": " + url) + val resource = + if ("file" == url.getProtocol) new FileResource(new IFileRepository(), new File(url.getPath())) + else new URLResource(url) + new ResolvedResource(resource, artifact.getModuleRevisionId.getRevision) + } + } + } finally { + IvyContext.getContext.set(resolver.getName + ".artifact", null) + } + case _ => + None + } + val artifactRefs = md.getConfigurations.toIterator flatMap { conf => + md.getArtifacts(conf.getName).toIterator flatMap { af => + artifactRef(af, data.getDate).toIterator + } + } + if (artifactRefs.hasNext) Some(artifactRefs.next) + else None + } + /** Ported from ChainResolver#forcedRevision. */ + private[this] def forcedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision = + new ResolvedModuleRevision(rmr.getResolver, rmr.getArtifactResolver, rmr.getDescriptor, rmr.getReport, true) + /** Ported from ChainResolver#resolvedRevision. */ + private[this] def resolvedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision = + if (isDual) new ResolvedModuleRevision(rmr.getResolver, this, rmr.getDescriptor, rmr.getReport, rmr.isForce) + else rmr + /** Ported from ChainResolver#setLatestIfRequired. */ + private[this] def setLatestIfRequired(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] = + latestStrategyName(resolver) match { + case Some(latestName) if latestName != "default" => + val oldLatest = latestStrategy(resolver) + doSetLatestStrategy(resolver, latest) + oldLatest + case _ => None + } + /** Ported from ChainResolver#getLatestStrategyName. */ + private[this] def latestStrategyName(resolver: DependencyResolver): Option[String] = + resolver match { + case r: HasLatestStrategy => Some(r.getLatest) + case _ => None + } + /** Ported from ChainResolver#getLatest. */ + private[this] def latestStrategy(resolver: DependencyResolver): Option[LatestStrategy] = + resolver match { + case r: HasLatestStrategy => Some(r.getLatestStrategy) + case _ => None + } + /** Ported from ChainResolver#setLatest. */ + private[this] def doSetLatestStrategy(resolver: DependencyResolver, latest: Option[LatestStrategy]): Option[LatestStrategy] = + resolver match { + case r: HasLatestStrategy => + val oldLatest = latestStrategy(resolver) + r.setLatestStrategy(latest.orNull) + oldLatest + case _ => None + } +} \ No newline at end of file From bc421f0ac9e04902140b9a35b0f017ad6d17e775 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sun, 7 Sep 2014 12:01:50 -0400 Subject: [PATCH 06/11] Fix issue where generated config-classes was not propogated. Fixes #1568. This is the fallout of attempting not to leak config-file classes. Since we DO NOT have valid incremental compiler for config-classes, we've instituted workaround to ensure that regular incremental compilation *AND* our own sbt loader do not hose each other. A full solution will eventually be to find a way for .sbt files to participate in regular compilation of a project. For now, we fix the tracking of generated.class files throughout an sbt "loadProjects" call, and then clean any .class files that were not generated for a full reload. This commit just fixes a minor tracking issue. --- .travis.yml | 1 + main/src/main/scala/sbt/Load.scala | 9 +++++---- sbt/src/sbt-test/project-load/sha-conflict/build.sbt | 7 +++++++ sbt/src/sbt-test/project-load/sha-conflict/js/build.sbt | 1 + .../project-load/sha-conflict/project/plugins.sbt | 1 + sbt/src/sbt-test/project-load/sha-conflict/test | 1 + 6 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 sbt/src/sbt-test/project-load/sha-conflict/build.sbt create mode 100644 sbt/src/sbt-test/project-load/sha-conflict/js/build.sbt create mode 100644 sbt/src/sbt-test/project-load/sha-conflict/project/plugins.sbt create mode 100644 sbt/src/sbt-test/project-load/sha-conflict/test diff --git a/.travis.yml b/.travis.yml index 397db5cfc..be24bf0a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ env: - SCRIPTED_TEST="scripted source-dependencies/*2of3" - SCRIPTED_TEST="scripted source-dependencies/*3of3" - SCRIPTED_TEST="scripted tests/*" + - SCRIPTED_TEST="scripted project-load/*" - SCRIPTED_TEST="safeUnitTests" - SCRIPTED_TEST="checkBuildScala211" diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 9cac96cde..d137d1415 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -439,7 +439,8 @@ object Load { val memoSettings = new mutable.HashMap[File, LoadedSbtFile] def loadProjects(ps: Seq[Project], createRoot: Boolean) = { - loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context, Nil) + val result = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context, Nil) + result } val loadedProjectsRaw = loadProjects(initialProjects, !hasRootAlreadyDefined) @@ -567,14 +568,14 @@ object Load { case Seq(next, rest @ _*) => log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}") val (finished, discovered, generated) = discoverAndLoad(next) - loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context, generated) + loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles) case Nil if makeOrDiscoverRoot => log.debug(s"[Loading] Scanning directory ${buildBase}") discover(AddSettings.defaultSbtFiles, buildBase) match { case DiscoveredProjects(Some(root), discovered, files, generated) => log.debug(s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}") val finalRoot = finalizeProject(root, files) - loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated) + loadTransitive(discovered, buildBase, plugins, eval, injectSettings, finalRoot +: acc, memoSettings, log, false, buildUri, context, generated ++ generatedConfigClassFiles) // Here we need to create a root project... case DiscoveredProjects(None, discovered, files, generated) => log.debug(s"[Loading] Found non-root projects ${discovered.map(_.id).mkString(",")}") @@ -587,7 +588,7 @@ object Load { val root = finalizeProject(Build.defaultAggregatedProject(defaultID, buildBase, refs), files) val result = root +: (acc ++ otherProjects.projects) log.debug(s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}") - LoadedProjects(result, generated ++ otherGenerated) + LoadedProjects(result, generated ++ otherGenerated ++ generatedConfigClassFiles) } case Nil => log.debug(s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}") diff --git a/sbt/src/sbt-test/project-load/sha-conflict/build.sbt b/sbt/src/sbt-test/project-load/sha-conflict/build.sbt new file mode 100644 index 000000000..8231138b6 --- /dev/null +++ b/sbt/src/sbt-test/project-load/sha-conflict/build.sbt @@ -0,0 +1,7 @@ +lazy val root = project in file(".") + +lazy val js = project + +def bigDataSesameVersion = "2.6.10" + +libraryDependencies += "foo" % "bar" % bigDataSesameVersion \ No newline at end of file diff --git a/sbt/src/sbt-test/project-load/sha-conflict/js/build.sbt b/sbt/src/sbt-test/project-load/sha-conflict/js/build.sbt new file mode 100644 index 000000000..007767b67 --- /dev/null +++ b/sbt/src/sbt-test/project-load/sha-conflict/js/build.sbt @@ -0,0 +1 @@ +bintraySettings \ No newline at end of file diff --git a/sbt/src/sbt-test/project-load/sha-conflict/project/plugins.sbt b/sbt/src/sbt-test/project-load/sha-conflict/project/plugins.sbt new file mode 100644 index 000000000..972538b79 --- /dev/null +++ b/sbt/src/sbt-test/project-load/sha-conflict/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") \ No newline at end of file diff --git a/sbt/src/sbt-test/project-load/sha-conflict/test b/sbt/src/sbt-test/project-load/sha-conflict/test new file mode 100644 index 000000000..477407e68 --- /dev/null +++ b/sbt/src/sbt-test/project-load/sha-conflict/test @@ -0,0 +1 @@ +> name From a857ae275905c5d75a4abae03b901a9981f6aa2c Mon Sep 17 00:00:00 2001 From: Daniel Peebles Date: Thu, 4 Sep 2014 02:20:36 -0400 Subject: [PATCH 07/11] Fixes #1570 --- main/src/main/scala/sbt/Defaults.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 8d933dc02..845069277 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1655,7 +1655,15 @@ object Classpaths { import xsbti.Predefined repo match { case m: xsbti.MavenRepository => MavenRepository(m.id, m.url.toString) - case i: xsbti.IvyRepository => Resolver.url(i.id, i.url)(Patterns(i.ivyPattern :: Nil, i.artifactPattern :: Nil, mavenCompatible(i), descriptorOptional(i), skipConsistencyCheck(i))) + case i: xsbti.IvyRepository => + val patterns = Patterns(i.ivyPattern :: Nil, i.artifactPattern :: Nil, mavenCompatible(i), descriptorOptional(i), skipConsistencyCheck(i)) + i.url.getProtocol match { + case "file" => + // This hackery is to deal suitably with UNC paths on Windows. Once we can assume Java7, Paths should save us from this. + val file = try { new File(i.url.toURI) } catch { case e: java.net.URISyntaxException => new File(i.url.getPath) } + Resolver.file(i.id, file)(patterns) + case _ => Resolver.url(i.id, i.url)(patterns) + } case p: xsbti.PredefinedRepository => p.id match { case Predefined.Local => Resolver.defaultLocal case Predefined.MavenLocal => Resolver.mavenLocal From fa36d0e2908f6787a2ea3db5e9dbf86ba6624459 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 9 Sep 2014 08:54:43 -0400 Subject: [PATCH 08/11] Add missing `configs` method from Project to the build.sbt DSL. * Create `configs` method for the sbt DSL * Add ProjectManipulation for this method to implement. --- main/src/main/scala/sbt/dsl/package.scala | 4 +++- main/src/main/scala/sbt/internals/DslAst.scala | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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: _*) +} From 50460fad285e459da095de967f70d5b638c76f69 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 9 Sep 2014 08:56:20 -0400 Subject: [PATCH 09/11] Add toString methods to classloaders, for debugging. * Add toString methods to all classloader classes. --- .../main/scala/sbt/classpath/ClassLoaders.scala | 14 +++++++++++++- .../scala/sbt/classpath/ClasspathUtilities.scala | 7 +++++++ .../src/main/scala/sbt/classpath/DualLoader.scala | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala index b50d84883..2c15bdcc8 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala @@ -46,14 +46,26 @@ 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[_] = { val c = super.loadClass(className, resolve) if (includeLoader(c.getClassLoader, root) || fromClasspath(c)) c - else + else { + System.err.println(s"DEBUGME: Failing to load class $className because it was not in expected parent chain.") + System.err.println(s"DEBUGME: Found: ${c.getClassLoader}") + System.err.println(s"DEBUGME: Expected: ${root}") + System.err.println(s"DEBUGME: onClasspath: ${fromClasspath(c)}") throw new ClassNotFoundException(className) + } } private[this] def fromClasspath(c: Class[_]): Boolean = { 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`.*/ From d2950da9dd59b0eaaedc7c10f31f2cad3f3cd207 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 9 Sep 2014 08:56:51 -0400 Subject: [PATCH 10/11] Fix issue where ScalaInstance broke the thread-context-classloader for all scala classes. The issue is that when you manually set a ScalaInstance, i.e. not one from Ivy, the classpath which is returned for any given configuration ONLY uses Ivy. This means that the legitimate Scala JAR files that need to be on the classpath are missing from the list. For some reason, the way we instantiate tests uses an unfiltered classloader against the ScalaInstance, *BUT* the thread-context-classloader DOES use a filtered instance by classpath. This add the hook into the TestFramework runner creation so that the classpath accurately reflects the jars needed. cc @rkuhn --- testing/src/main/scala/sbt/TestFramework.scala | 7 ++++++- util/classpath/src/main/scala/sbt/ScalaInstance.scala | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) 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 From da1fc33b521d64cd2448d42a2ea682ce3bd69585 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 9 Sep 2014 09:16:10 -0400 Subject: [PATCH 11/11] Removing printlns and adding the test, DOH. --- .../scala-instance-classloader/build.sbt | 29 +++++++++++++++++++ .../src/test/scala/Test.scala | 21 ++++++++++++++ .../tests/scala-instance-classloader/test | 1 + .../scala/sbt/classpath/ClassLoaders.scala | 7 +---- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 sbt/src/sbt-test/tests/scala-instance-classloader/build.sbt create mode 100644 sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala create mode 100644 sbt/src/sbt-test/tests/scala-instance-classloader/test 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/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala index 2c15bdcc8..2a1362f7e 100644 --- a/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala +++ b/util/classpath/src/main/scala/sbt/classpath/ClassLoaders.scala @@ -59,13 +59,8 @@ final class ClasspathFilter(parent: ClassLoader, root: ClassLoader, classpath: S val c = super.loadClass(className, resolve) if (includeLoader(c.getClassLoader, root) || fromClasspath(c)) c - else { - System.err.println(s"DEBUGME: Failing to load class $className because it was not in expected parent chain.") - System.err.println(s"DEBUGME: Found: ${c.getClassLoader}") - System.err.println(s"DEBUGME: Expected: ${root}") - System.err.println(s"DEBUGME: onClasspath: ${fromClasspath(c)}") + else throw new ClassNotFoundException(className) - } } private[this] def fromClasspath(c: Class[_]): Boolean = {