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/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 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 4ff1f10f0..3678adadb 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,6 +164,8 @@ 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" 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 diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 7cf91d619..9321c5e35 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 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/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/notes/0.13.6.md b/notes/0.13.6.md index 7b126fb5c..fa4923d1c 100644 --- a/notes/0.13.6.md +++ b/notes/0.13.6.md @@ -80,7 +80,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] @@ -130,7 +130,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]. 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 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`.*/ 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 }