From 38f94a6e314f58022b7ea3618580a55d9a316249 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 8 Mar 2019 20:15:04 -0500 Subject: [PATCH] Coursier dependency resolution integration This adds dependency to LM implemented using Coursier. I had to copy paste a bunch of code from sbt-coursier-shared to break the dependency to sbt. `Global / useCoursier := false` or `-Dsbt.coursier=false` be used to opt-out of using Coursier for the dependency resolution. --- build.sbt | 9 +- main/src/main/scala/sbt/Defaults.scala | 73 ++- main/src/main/scala/sbt/Keys.scala | 16 + .../main/scala/sbt/internal/LMCoursier.scala | 524 ++++++++++++++++++ .../sbt/internal/LibraryManagement.scala | 44 +- .../internal/librarymanagement/IvyXml.scala | 224 ++++++++ project/Dependencies.scala | 20 +- .../dependency-management/artifact/build.sbt | 6 +- 8 files changed, 888 insertions(+), 28 deletions(-) create mode 100644 main/src/main/scala/sbt/internal/LMCoursier.scala create mode 100644 main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala diff --git a/build.sbt b/build.sbt index 0d223c21b..f517ad661 100644 --- a/build.sbt +++ b/build.sbt @@ -590,7 +590,12 @@ lazy val mainProj = (project in file("main")) if (xs exists { s => s.contains(s""""$sv"""") }) () else sys.error("PluginCross.scala does not match up with the scalaVersion " + sv) }, - libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface) ++ log4jDependencies ++ Seq(scalaCacheCaffeine), + libraryDependencies ++= { + scalaXml.value ++ + Seq(launcherInterface) ++ + log4jDependencies ++ + Seq(scalaCacheCaffeine, lmCousier) + }, Compile / scalacOptions -= "-Xfatal-warnings", managedSourceDirectories in Compile += baseDirectory.value / "src" / "main" / "contraband-scala", @@ -650,6 +655,7 @@ lazy val sbtProj = (project in file("sbt")) ) .configure(addSbtIO, addSbtCompilerBridge) +/* lazy val sbtBig = (project in file(".big")) .dependsOn(sbtProj) .settings( @@ -685,6 +691,7 @@ lazy val sbtBig = (project in file(".big")) }).transform(node).head }, ) +*/ lazy val sbtIgnoredProblems = { Vector( diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 00d53e26e..d52b6605e 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -12,6 +12,7 @@ import java.net.{ URI, URL, URLClassLoader } import java.util.Optional import java.util.concurrent.{ Callable, TimeUnit } +import coursier.core.{ Configuration => CConfiguration } import org.apache.ivy.core.module.descriptor.ModuleDescriptor import org.apache.ivy.core.module.id.ModuleRevisionId import sbt.Def.{ Initialize, ScopedKey, Setting, SettingsDefinition } @@ -200,6 +201,7 @@ object Defaults extends BuildCommon { exportJars :== false, trackInternalDependencies :== TrackLevel.TrackAlways, exportToInternal :== TrackLevel.TrackAlways, + useCoursier :== LibraryManagement.defaultUseCoursier, retrieveManaged :== false, retrieveManagedSync :== false, configurationsToRetrieve :== None, @@ -224,7 +226,12 @@ object Defaults extends BuildCommon { pomAllRepositories :== false, pomIncludeRepository :== Classpaths.defaultRepositoryFilter, updateOptions := UpdateOptions(), - forceUpdatePeriod :== None + forceUpdatePeriod :== None, + // coursier settings + csrExtraCredentials :== Nil, + csrLogger :== None, + csrCachePath :== coursier.cache.CacheDefaults.location, + csrMavenProfiles :== Set.empty, ) /** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */ @@ -2125,6 +2132,14 @@ object Classpaths { }).value, moduleName := normalizedName.value, ivyPaths := IvyPaths(baseDirectory.value, bootIvyHome(appConfiguration.value)), + csrCachePath := { + val old = csrCachePath.value + val ip = ivyPaths.value + val defaultIvyCache = bootIvyHome(appConfiguration.value) + if (old != coursier.cache.CacheDefaults.location) old + else if (ip.ivyHome == defaultIvyCache) old + else ip.ivyHome.getOrElse(old) + }, dependencyCacheDirectory := { val st = state.value BuildPaths.getDependencyDirectory(st, BuildPaths.getGlobalBase(st)) @@ -2181,10 +2196,7 @@ object Classpaths { ) else None }, - dependencyResolution := IvyDependencyResolution( - ivyConfiguration.value, - CustomHttp.okhttpClient.value - ), + dependencyResolution := LibraryManagement.dependencyResolutionTask.value, publisher := IvyPublisher(ivyConfiguration.value, CustomHttp.okhttpClient.value), ivyConfiguration := mkIvyConfiguration.value, ivyConfigurations := { @@ -2198,6 +2210,44 @@ object Classpaths { if (managedScalaInstance.value && scalaHome.value.isEmpty) Configurations.ScalaTool :: Nil else Nil }, + // Coursier needs these + ivyConfigurations := { + val confs = ivyConfigurations.value + val names = confs.map(_.name).toSet + val extraSources = + if (names("sources")) + None + else + Some( + Configuration.of( + id = "Sources", + name = "sources", + description = "", + isPublic = true, + extendsConfigs = Vector.empty, + transitive = false + ) + ) + + val extraDocs = + if (names("docs")) + None + else + Some( + Configuration.of( + id = "Docs", + name = "docs", + description = "", + isPublic = true, + extendsConfigs = Vector.empty, + transitive = false + ) + ) + + val use = useCoursier.value + if (use) confs ++ extraSources.toSeq ++ extraDocs.toSeq + else confs + }, moduleSettings := moduleSettings0.value, makePomConfiguration := MakePomConfiguration() .withFile((artifactPath in makePom).value) @@ -2342,8 +2392,17 @@ object Classpaths { case Right(ur) => ur } } - } tag (Tags.Update, Tags.Network)).value - ) + } tag (Tags.Update, Tags.Network)).value, + csrProject := LMCoursier.coursierProjectTask.value, + csrConfiguration := LMCoursier.coursierConfigurationTask(false, false).value, + csrResolvers := LMCoursier.coursierResolversTask.value, + csrRecursiveResolvers := LMCoursier.coursierRecursiveResolversTask.value, + csrSbtResolvers := LMCoursier.coursierSbtResolversTask.value, + csrInterProjectDependencies := LMCoursier.coursierInterProjectDependenciesTask.value, + csrFallbackDependencies := LMCoursier.coursierFallbackDependenciesTask.value, + ) ++ + IvyXml.generateIvyXmlSettings() ++ + LMCoursier.publicationsSetting(Seq(Compile, Test).map(c => c -> CConfiguration(c.name))) val jvmBaseSettings: Seq[Setting[_]] = Seq( libraryDependencies ++= autoLibraryDependency( diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index ac2711453..52eff84cc 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -32,6 +32,8 @@ import sbt.librarymanagement.ivy.{ Credentials, IvyConfiguration, IvyPaths, Upda import sbt.testing.Framework import sbt.util.{ Level, Logger } import xsbti.compile._ +import coursier.cache.CacheLogger +import coursier.lmcoursier.{ CoursierConfiguration, FallbackDependency } import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.xml.{ NodeSeq, Node => XNode } @@ -351,6 +353,20 @@ object Keys { val fullClasspathAsJars = taskKey[Classpath]("The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies, all as JARs.") val internalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[String])]]("The project configurations that this configuration depends on") + val useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting) + val csrCachePath = settingKey[File]("Coursier cache path").withRank(CSetting) + private[sbt] val csrConfiguration = taskKey[CoursierConfiguration]("General dependency management (Coursier) settings, such as the resolvers and options to use.").withRank(DTask) + private[sbt] val csrProject = taskKey[coursier.core.Project]("") + private[sbt] val csrResolvers = taskKey[Seq[Resolver]]("") + private[sbt] val csrRecursiveResolvers = taskKey[Seq[Resolver]]("Resolvers of the current project, plus those of all from its inter-dependency projects") + private[sbt] val csrSbtResolvers = taskKey[Seq[Resolver]]("Resolvers used for sbt artifacts.") + private[sbt] val csrInterProjectDependencies = taskKey[Seq[coursier.core.Project]]("Projects the current project depends on, possibly transitively") + private[sbt] val csrFallbackDependencies = taskKey[Seq[FallbackDependency]]("") + private[sbt] val csrMavenProfiles = settingKey[Set[String]]("") + private[sbt] val csrLogger = taskKey[Option[CacheLogger]]("") + private[sbt] val csrExtraCredentials = taskKey[Seq[coursier.credentials.Credentials]]("") + private[sbt] val csrPublications = taskKey[Seq[(coursier.core.Configuration, coursier.core.Publication)]]("") + val internalConfigurationMap = settingKey[Configuration => Configuration]("Maps configurations to the actual configuration used to define the classpath.").withRank(CSetting) val classpathConfiguration = taskKey[Configuration]("The configuration used to define the classpath.").withRank(CTask) val ivyConfiguration = taskKey[IvyConfiguration]("General dependency management (Ivy) settings, such as the resolvers and paths to use.").withRank(DTask) diff --git a/main/src/main/scala/sbt/internal/LMCoursier.scala b/main/src/main/scala/sbt/internal/LMCoursier.scala new file mode 100644 index 000000000..cf1a65088 --- /dev/null +++ b/main/src/main/scala/sbt/internal/LMCoursier.scala @@ -0,0 +1,524 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import coursier.core.{ + Attributes => CAttributes, + Classifier, + Configuration => CConfiguration, + Dependency => CDependency, + Extension => CExtension, + Info => CInfo, + Module, + ModuleName, + Organization => COrganization, + Project => CProject, + Publication => CPublication, + Type => CType +} +import coursier.credentials.DirectCredentials +import coursier.lmcoursier._ +import sbt.librarymanagement._ +import Keys._ +import sbt.librarymanagement.ivy.{ + FileCredentials, + Credentials, + DirectCredentials => IvyDirectCredentials +} +import sbt.ScopeFilter.Make._ +import scala.collection.JavaConverters._ + +private[sbt] object LMCoursier { + + def coursierProjectTask: Def.Initialize[sbt.Task[CProject]] = + Def.task { + Inputs.coursierProject( + projectID.value, + allDependencies.value, + excludeDependencies.value, + // should projectID.configurations be used instead? + ivyConfigurations.value, + scalaVersion.value, + scalaBinaryVersion.value, + streams.value.log + ) + } + + def coursierConfigurationTask( + withClassifiers: Boolean, + sbtClassifiers: Boolean + ): Def.Initialize[Task[CoursierConfiguration]] = + Def.taskDyn { + val resolversTask = + if (sbtClassifiers) + csrSbtResolvers + else + csrRecursiveResolvers + val classifiersTask: sbt.Def.Initialize[sbt.Task[Option[Seq[Classifier]]]] = + if (withClassifiers && !sbtClassifiers) + Def.task(Some(sbt.Keys.transitiveClassifiers.value.map(Classifier(_)))) + else + Def.task(None) + Def.task { + val rs = resolversTask.value + val scalaOrg = scalaOrganization.value + val scalaVer = scalaVersion.value + val interProjectDependencies = csrInterProjectDependencies.value + val excludeDeps = Inputs.exclusions( + excludeDependencies.value, + scalaVer, + scalaBinaryVersion.value, + streams.value.log + ) + val fallbackDeps = csrFallbackDependencies.value + val autoScalaLib = autoScalaLibrary.value && scalaModuleInfo.value.forall( + _.overrideScalaVersion + ) + val profiles = csrMavenProfiles.value + val credentials = credentialsTask.value + + val createLogger = csrLogger.value + + val cache = csrCachePath.value + + val internalSbtScalaProvider = appConfiguration.value.provider.scalaProvider + val sbtBootJars = internalSbtScalaProvider.jars() + val sbtScalaVersion = internalSbtScalaProvider.version() + val sbtScalaOrganization = "org.scala-lang" // always assuming sbt uses mainline scala + val classifiers = classifiersTask.value + val s = streams.value + Classpaths.warnResolversConflict(rs, s.log) + CoursierConfiguration() + .withResolvers(rs.toVector) + .withInterProjectDependencies(interProjectDependencies.toVector) + .withFallbackDependencies(fallbackDeps.toVector) + .withExcludeDependencies( + excludeDeps.toVector.sorted + .map { + case (o, n) => + (o.value, n.value) + } + ) + .withAutoScalaLibrary(autoScalaLib) + .withSbtScalaJars(sbtBootJars.toVector) + .withSbtScalaVersion(sbtScalaVersion) + .withSbtScalaOrganization(sbtScalaOrganization) + .withClassifiers(classifiers.toVector.flatten.map(_.value)) + .withHasClassifiers(classifiers.nonEmpty) + .withMavenProfiles(profiles.toVector.sorted) + .withScalaOrganization(scalaOrg) + .withScalaVersion(scalaVer) + .withCredentials(credentials) + .withLogger(createLogger) + .withCache(cache) + .withLog(s.log) + } + } + + val credentialsTask = Def.task { + val log = streams.value.log + + val creds = sbt.Keys.credentials.value + .flatMap { + case dc: IvyDirectCredentials => List(dc) + case fc: FileCredentials => + Credentials.loadCredentials(fc.path) match { + case Left(err) => + log.warn(s"$err, ignoring it") + Nil + case Right(dc) => List(dc) + } + } + .map { c => + DirectCredentials() + .withHost(c.host) + .withUsername(c.userName) + .withPassword(c.passwd) + .withRealm(Some(c.realm).filter(_.nonEmpty)) + } + creds ++ csrExtraCredentials.value + } + + def coursierRecursiveResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] = + Def.taskDyn { + val state = sbt.Keys.state.value + val projectRef = sbt.Keys.thisProjectRef.value + + val projects = allRecursiveInterDependencies(state, projectRef) + Def.task { + csrResolvers.all(ScopeFilter(inProjects(projectRef +: projects: _*))).value.flatten + } + } + + def coursierResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] = + Def.taskDyn { + val bootResOpt = bootResolvers.value + val overrideFlag = overrideBuildResolvers.value + Def.task { + val result = resultTask(bootResOpt, overrideFlag).value + val reorderResolvers = true // coursierReorderResolvers.value + val keepPreloaded = false // coursierKeepPreloaded.value + + val result0 = + if (reorderResolvers) + ResolutionParams.reorderResolvers(result) + else + result + + if (keepPreloaded) + result0 + else + result0.filter { r => + !r.name.startsWith("local-preloaded") + } + } + } + + private val pluginIvySnapshotsBase = Resolver.SbtRepositoryRoot.stripSuffix("/") + "/ivy-snapshots" + + def coursierSbtResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] = Def.task { + val resolvers = + sbt.Classpaths + .bootRepositories(appConfiguration.value) + .toSeq + .flatten ++ // required because of the hack above it seems + externalResolvers.in(updateSbtClassifiers).value + + val pluginIvySnapshotsFound = resolvers.exists { + case repo: URLRepository => + repo.patterns.artifactPatterns.headOption + .exists(_.startsWith(pluginIvySnapshotsBase)) + case _ => false + } + + val resolvers0 = + if (pluginIvySnapshotsFound && !resolvers.contains(Classpaths.sbtPluginReleases)) + resolvers :+ Classpaths.sbtPluginReleases + else + resolvers + val keepPreloaded = true // coursierKeepPreloaded.value + if (keepPreloaded) + resolvers0 + else + resolvers0.filter { r => + !r.name.startsWith("local-preloaded") + } + } + + def coursierInterProjectDependenciesTask: Def.Initialize[sbt.Task[Seq[CProject]]] = + Def.taskDyn { + + val state = sbt.Keys.state.value + val projectRef = sbt.Keys.thisProjectRef.value + + val projectRefs = allRecursiveInterDependencies(state, projectRef) + + Def.task { + val projects = csrProject.all(ScopeFilter(inProjects(projectRefs: _*))).value + val projectModules = projects.map(_.module).toSet + + // this includes org.scala-sbt:global-plugins referenced from meta-builds in particular + val extraProjects = sbt.Keys.projectDescriptors.value + .map { + case (k, v) => + moduleFromIvy(k) -> v + } + .filter { + case (module, _) => + !projectModules(module) + } + .toVector + .map { + case (module, v) => + val configurations = v.getConfigurations.map { c => + CConfiguration(c.getName) -> c.getExtends.map(CConfiguration(_)).toSeq + }.toMap + val deps = v.getDependencies.flatMap(dependencyFromIvy) + CProject( + module, + v.getModuleRevisionId.getRevision, + deps, + configurations, + None, + Nil, + Nil, + Nil, + None, + None, + None, + relocated = false, + None, + Nil, + CInfo.empty + ) + } + + projects ++ extraProjects + } + } + + def coursierFallbackDependenciesTask: Def.Initialize[sbt.Task[Seq[FallbackDependency]]] = + Def.taskDyn { + val s = state.value + val projectRef = sbt.Keys.thisProjectRef.value + + val projects = allRecursiveInterDependencies(s, projectRef) + Def.task { + val allDeps = + allDependencies.all(ScopeFilter(inProjects(projectRef +: projects: _*))).value.flatten + + FromSbt.fallbackDependencies( + allDeps, + scalaVersion.in(projectRef).value, + scalaBinaryVersion.in(projectRef).value + ) + } + } + + def publicationsSetting(packageConfigs: Seq[(Configuration, CConfiguration)]): Def.Setting[_] = { + csrPublications := coursierPublicationsTask(packageConfigs: _*).value + } + + def coursierPublicationsTask( + configsMap: (Configuration, CConfiguration)* + ): Def.Initialize[sbt.Task[Seq[(CConfiguration, CPublication)]]] = + Def.task { + val s = sbt.Keys.state.value + val projectRef = sbt.Keys.thisProjectRef.value + val projId = sbt.Keys.projectID.value + val sv = sbt.Keys.scalaVersion.value + val sbv = sbt.Keys.scalaBinaryVersion.value + val ivyConfs = sbt.Keys.ivyConfigurations.value + val extracted = Project.extract(s) + import extracted._ + + val sourcesConfigOpt = + if (ivyConfigurations.value.exists(_.name == "sources")) + Some(CConfiguration("sources")) + else + None + + val docsConfigOpt = + if (ivyConfigurations.value.exists(_.name == "docs")) + Some(CConfiguration("docs")) + else + None + + val sbtBinArtifacts = + for ((config, targetConfig) <- configsMap) yield { + + val publish = getOpt( + publishArtifact + .in(projectRef) + .in(packageBin) + .in(config) + ).getOrElse(false) + + if (publish) + getOpt( + artifact + .in(projectRef) + .in(packageBin) + .in(config) + ).map(targetConfig -> _) + else + None + } + + val sbtSourceArtifacts = + for ((config, targetConfig) <- configsMap) yield { + + val publish = getOpt( + publishArtifact + .in(projectRef) + .in(packageSrc) + .in(config) + ).getOrElse(false) + + if (publish) + getOpt( + artifact + .in(projectRef) + .in(packageSrc) + .in(config) + ).map(sourcesConfigOpt.getOrElse(targetConfig) -> _) + else + None + } + + val sbtDocArtifacts = + for ((config, targetConfig) <- configsMap) yield { + + val publish = + getOpt( + publishArtifact + .in(projectRef) + .in(packageDoc) + .in(config) + ).getOrElse(false) + + if (publish) + getOpt( + artifact + .in(projectRef) + .in(packageDoc) + .in(config) + ).map(docsConfigOpt.getOrElse(targetConfig) -> _) + else + None + } + + val sbtArtifacts = sbtBinArtifacts ++ sbtSourceArtifacts ++ sbtDocArtifacts + + def artifactPublication(artifact: Artifact) = { + + val name = FromSbt.sbtCrossVersionName( + artifact.name, + projId.crossVersion, + sv, + sbv + ) + + CPublication( + name, + CType(artifact.`type`), + CExtension(artifact.extension), + artifact.classifier.fold(Classifier.empty)(Classifier(_)) + ) + } + + val sbtArtifactsPublication = sbtArtifacts.collect { + case Some((config, artifact)) => + config -> artifactPublication(artifact) + } + + val stdArtifactsSet = sbtArtifacts.flatMap(_.map { case (_, a) => a }.toSeq).toSet + + // Second-way of getting artifacts from SBT + // No obvious way of getting the corresponding publishArtifact value for the ones + // only here, it seems. + val extraSbtArtifacts = getOpt( + sbt.Keys.artifacts + .in(projectRef) + ).getOrElse(Nil) + .filterNot(stdArtifactsSet) + + // Seems that SBT does that - if an artifact has no configs, + // it puts it in all of them. See for example what happens to + // the standalone JAR artifact of the coursier cli module. + def allConfigsIfEmpty(configs: Iterable[ConfigRef]): Iterable[ConfigRef] = + if (configs.isEmpty) ivyConfs.filter(_.isPublic).map(c => ConfigRef(c.name)) else configs + + val extraSbtArtifactsPublication = for { + artifact <- extraSbtArtifacts + config <- allConfigsIfEmpty(artifact.configurations.map(x => ConfigRef(x.name))) + // FIXME If some configurations from artifact.configurations are not public, they may leak here :\ + } yield CConfiguration(config.name) -> artifactPublication(artifact) + + sbtArtifactsPublication ++ extraSbtArtifactsPublication + } + + private def moduleFromIvy(id: org.apache.ivy.core.module.id.ModuleRevisionId): Module = + Module( + COrganization(id.getOrganisation), + ModuleName(id.getName), + id.getExtraAttributes.asScala.map { + case (k0, v0) => k0.asInstanceOf[String] -> v0.asInstanceOf[String] + }.toMap + ) + + private def dependencyFromIvy( + desc: org.apache.ivy.core.module.descriptor.DependencyDescriptor + ): Seq[(CConfiguration, CDependency)] = { + + val id = desc.getDependencyRevisionId + val module = moduleFromIvy(id) + val exclusions = desc.getAllExcludeRules.map { rule => + // we're ignoring rule.getConfigurations and rule.getMatcher here + val modId = rule.getId.getModuleId + // we're ignoring modId.getAttributes here + (COrganization(modId.getOrganisation), ModuleName(modId.getName)) + }.toSet + + val configurations = desc.getModuleConfigurations.toVector + .flatMap(s => coursier.ivy.IvyXml.mappings(s)) + + def dependency(conf: CConfiguration, attr: CAttributes) = CDependency( + module, + id.getRevision, + conf, + exclusions, + attr, + optional = false, + desc.isTransitive + ) + + val attributes: CConfiguration => CAttributes = { + + val artifacts = desc.getAllDependencyArtifacts + + val m = artifacts.toVector.flatMap { art => + val attr = CAttributes(CType(art.getType), Classifier.empty) + art.getConfigurations.map(CConfiguration(_)).toVector.map { conf => + conf -> attr + } + }.toMap + + c => m.getOrElse(c, CAttributes.empty) + } + + configurations.map { + case (from, to) => + from -> dependency(to, attributes(to)) + } + } + + private def resultTask( + bootResOpt: Option[Seq[Resolver]], + overrideFlag: Boolean + ): Def.Initialize[sbt.Task[Seq[Resolver]]] = + bootResOpt.filter(_ => overrideFlag) match { + case Some(r) => Def.task(r) + case None => + Def.taskDyn { + val extRes = externalResolvers.value + val isSbtPlugin = sbtPlugin.value + if (isSbtPlugin) + Def.task { + Seq( + sbtResolver.value, + Classpaths.sbtPluginReleases + ) ++ extRes + } else + Def.task(extRes) + } + } + + def allRecursiveInterDependencies(state: sbt.State, projectRef: sbt.ProjectRef) = { + def dependencies(map: Map[String, Seq[String]], id: String): Set[String] = { + + def helper(map: Map[String, Seq[String]], acc: Set[String]): Set[String] = + if (acc.exists(map.contains)) { + val (kept, rem) = map.partition { case (k, _) => acc(k) } + helper(rem, acc ++ kept.valuesIterator.flatten) + } else + acc + + helper(map - id, map.getOrElse(id, Nil).toSet) + } + + val allProjectsDeps = + for (p <- Project.structure(state).allProjects) + yield p.id -> p.dependencies.map(_.project.project) + + val deps = dependencies(allProjectsDeps.toMap, projectRef.project) + + Project.structure(state).allProjectRefs.filter(p => deps(p.project)) + } +} diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index e6366446d..eaed30432 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -5,12 +5,15 @@ * Licensed under Apache License 2.0 (see LICENSE) */ -package sbt.internal +package sbt +package internal +import coursier.lmcoursier.CoursierDependencyResolution import java.io.File - import sbt.internal.librarymanagement._ +import sbt.internal.util.{ ConsoleAppender, LogOption } import sbt.librarymanagement._ +import sbt.librarymanagement.ivy.IvyDependencyResolution import sbt.librarymanagement.syntax._ import sbt.util.{ CacheStore, CacheStoreFactory, Logger, Tracked } import sbt.io.IO @@ -19,6 +22,43 @@ private[sbt] object LibraryManagement { private type UpdateInputs = (Long, ModuleSettings, UpdateConfiguration) + def defaultUseCoursier: Boolean = { + val coursierOpt = sys.props + .get("sbt.coursier") + .flatMap( + str => + ConsoleAppender.parseLogOption(str) match { + case LogOption.Always => Some(true) + case LogOption.Never => Some(false) + case _ => None + } + ) + val ivyOpt = sys.props + .get("sbt.ivy") + .flatMap( + str => + ConsoleAppender.parseLogOption(str) match { + case LogOption.Always => Some(true) + case LogOption.Never => Some(false) + case _ => None + } + ) + val notIvyOpt = ivyOpt map { !_ } + coursierOpt.orElse(notIvyOpt).getOrElse(true) + } + + def dependencyResolutionTask: Def.Initialize[Task[DependencyResolution]] = Def.taskDyn { + if (Keys.useCoursier.value) { + Def.task { CoursierDependencyResolution(Keys.csrConfiguration.value) } + } else + Def.task { + IvyDependencyResolution( + Keys.ivyConfiguration.value, + CustomHttp.okhttpClient.value + ) + } + } + def cachedUpdate( lm: DependencyResolution, module: ModuleDescriptor, diff --git a/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala b/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala new file mode 100644 index 000000000..99db26f58 --- /dev/null +++ b/main/src/main/scala/sbt/internal/librarymanagement/IvyXml.scala @@ -0,0 +1,224 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal +package librarymanagement + +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.Files + +import coursier.core.{ Configuration, Project } +import org.apache.ivy.core.module.id.ModuleRevisionId +import Def.Setting +import sbt.Keys.{ + csrProject, + csrPublications, + publishLocalConfiguration, + publishConfiguration, + useCoursier +} +import sbt.librarymanagement.PublishConfiguration +import scala.collection.JavaConverters._ +import scala.xml.{ Node, PrefixedAttribute } + +object IvyXml { + import sbt.Project._ + + def rawContent( + currentProject: Project, + shadedConfigOpt: Option[Configuration] + ): String = { + + // Important: width = Int.MaxValue, so that no tag gets truncated. + // In particular, that prevents things like to be split to + // + // + // by the pretty-printer. + // See https://github.com/sbt/sbt/issues/3412. + val printer = new scala.xml.PrettyPrinter(Int.MaxValue, 2) + + """""" + '\n' + + printer.format(content(currentProject, shadedConfigOpt)) + } + + // These are required for publish to be fine, later on. + def writeFiles( + currentProject: Project, + shadedConfigOpt: Option[Configuration], + ivySbt: IvySbt, + log: sbt.util.Logger + ): Unit = { + + val ivyCacheManager = ivySbt.withIvy(log)(ivy => ivy.getResolutionCacheManager) + + val ivyModule = ModuleRevisionId.newInstance( + currentProject.module.organization.value, + currentProject.module.name.value, + currentProject.version, + currentProject.module.attributes.asJava + ) + + val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule) + val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule) + + val content0 = rawContent(currentProject, shadedConfigOpt) + cacheIvyFile.getParentFile.mkdirs() + log.info(s"Writing Ivy file $cacheIvyFile") + Files.write(cacheIvyFile.toPath, content0.getBytes(UTF_8)) + + // Just writing an empty file here... Are these only used? + cacheIvyPropertiesFile.getParentFile.mkdirs() + Files.write(cacheIvyPropertiesFile.toPath, Array.emptyByteArray) + () + } + + def content(project0: Project, shadedConfigOpt: Option[Configuration]): Node = { + + val filterOutDependencies = + shadedConfigOpt.toSet[Configuration].flatMap { shadedConfig => + project0.dependencies + .collect { case (`shadedConfig`, dep) => dep } + } + + val project: Project = project0.copy( + dependencies = project0.dependencies.collect { + case p @ (_, dep) if !filterOutDependencies(dep) => p + } + ) + + val infoAttrs = project.module.attributes.foldLeft[xml.MetaData](xml.Null) { + case (acc, (k, v)) => + new PrefixedAttribute("e", k, v, acc) + } + + val licenseElems = project.info.licenses.map { + case (name, urlOpt) => + val n = + + urlOpt.fold(n) { url => + n % .attributes + } + } + + val infoElem = { + + {licenseElems} + {project.info.description} + + } % infoAttrs + + val confElems = project.configurations.toVector.collect { + case (name, extends0) if !shadedConfigOpt.contains(name) => + val extends1 = shadedConfigOpt.fold(extends0)(c => extends0.filter(_ != c)) + val n = + if (extends1.nonEmpty) + n % .attributes + else + n + } + + val publications = project.publications + .groupBy { case (_, p) => p } + .mapValues { _.map { case (cfg, _) => cfg } } + + val publicationElems = publications.map { + case (pub, configs) => + val n = + + + if (pub.classifier.nonEmpty) + n % .attributes + else + n + } + + val dependencyElems = project.dependencies.toVector.map { + case (conf, dep) => + val excludes = dep.exclusions.toSeq.map { + case (org, name) => + + } + + val n = + ${dep.configuration.value}"}> + {excludes} + + + val moduleAttrs = dep.module.attributes.foldLeft[xml.MetaData](xml.Null) { + case (acc, (k, v)) => + new PrefixedAttribute("e", k, v, acc) + } + + n % moduleAttrs + } + + + {infoElem} + {confElems} + {publicationElems} + {dependencyElems} + + } + + def makeIvyXmlBefore[T]( + task: TaskKey[T], + shadedConfigOpt: Option[Configuration] + ): Setting[Task[T]] = + task := task.dependsOn { + Def.taskDyn { + val doGen = useCoursier.value + if (doGen) + Def.task { + val currentProject = { + val proj = csrProject.value + val publications = csrPublications.value + proj.copy(publications = publications) + } + IvyXml.writeFiles( + currentProject, + shadedConfigOpt, + sbt.Keys.ivySbt.value, + sbt.Keys.streams.value.log + ) + } else + Def.task(()) + } + }.value + + private lazy val needsIvyXmlLocal = Seq(publishLocalConfiguration) ++ getPubConf( + "makeIvyXmlLocalConfiguration" + ) + private lazy val needsIvyXml = Seq(publishConfiguration) ++ getPubConf( + "makeIvyXmlConfiguration" + ) + + private[this] def getPubConf(method: String): List[TaskKey[PublishConfiguration]] = + try { + val cls = sbt.Keys.getClass + val m = cls.getMethod(method) + val task = m.invoke(sbt.Keys).asInstanceOf[TaskKey[PublishConfiguration]] + List(task) + } catch { + case _: Throwable => // FIXME Too wide + Nil + } + + def generateIvyXmlSettings( + shadedConfigOpt: Option[Configuration] = None + ): Seq[Setting[_]] = + (needsIvyXml ++ needsIvyXmlLocal).map(IvyXml.makeIvyXmlBefore(_, shadedConfigOpt)) + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 226b6567f..64c9a8f26 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -29,22 +29,7 @@ object Dependencies { private val utilScripted = "org.scala-sbt" %% "util-scripted" % utilVersion private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion - - private val libraryManagementImpl = { - val lmOrganization = - sys.props.get("sbt.build.lm.organization") match { - case Some(impl) => impl - case _ => "org.scala-sbt" - } - - val lmModuleName = - sys.props.get("sbt.build.lm.moduleName") match { - case Some(impl) => impl - case _ => "librarymanagement-ivy" - } - - lmOrganization %% lmModuleName % lmVersion - } + private val libraryManagementImpl = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion val launcherVersion = "1.0.4" val launcherInterface = "org.scala-sbt" % "launcher-interface" % launcherVersion @@ -115,6 +100,9 @@ object Dependencies { def addSbtZincCompile(p: Project): Project = addSbtModule(p, sbtZincPath, "zincCompile", zincCompile) + val lmCousierVersion = "1.1.0-M14" + val lmCousier = "io.get-coursier" %% "lm-coursier" % lmCousierVersion + val sjsonNewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value } diff --git a/sbt/src/sbt-test/dependency-management/artifact/build.sbt b/sbt/src/sbt-test/dependency-management/artifact/build.sbt index 7a737bbec..5ba1238bf 100644 --- a/sbt/src/sbt-test/dependency-management/artifact/build.sbt +++ b/sbt/src/sbt-test/dependency-management/artifact/build.sbt @@ -35,7 +35,9 @@ def retrieveID = org % "test-retrieve" % "2.0" // check that the test class is on the compile classpath, either because it was compiled or because it was properly retrieved def checkTask(classpath: TaskKey[Classpath]) = Def task { - val loader = ClasspathUtilities.toLoader((classpath in Compile).value.files, scalaInstance.value.loader) + val deps = libraryDependencies.value + val cp = (classpath in Compile).value.files + val loader = ClasspathUtilities.toLoader(cp, scalaInstance.value.loader) try { Class.forName("test.Test", false, loader); () } - catch { case _: ClassNotFoundException | _: NoClassDefFoundError => sys.error("Dependency not retrieved properly") } + catch { case _: ClassNotFoundException | _: NoClassDefFoundError => sys.error(s"Dependency not retrieved properly: $deps, $cp") } }