From 627877fcc74934128d7ea43ba137c537e193d795 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 13:12:50 +0200 Subject: [PATCH 1/9] Update coursier to 2.0.0-RC6-18 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b153b9aea..3a8a6a881 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ inThisBuild(List( ) )) -val coursierVersion0 = "2.0.0-RC6-16" +val coursierVersion0 = "2.0.0-RC6-18" lazy val `lm-coursier` = project .in(file("modules/lm-coursier")) From 3cd521c43c13d44570edefd52a4ed2353dcfd00f Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 12:57:28 +0200 Subject: [PATCH 2/9] Run resolution for each configuration Rather than for each configuration sub-graph. This runs a resolution for Compile, one for Runtime, one for Test, etc. rather than one for all 3 of them, for example. We re-use the Resolution instance from the first extended configuration, so that the performance penalty is really low. --- .../CoursierDependencyResolution.scala | 16 +++++---- .../src/main/scala/lmcoursier/Inputs.scala | 30 +++++++++++++++- .../internal/ResolutionParams.scala | 16 ++++++++- .../lmcoursier/internal/ResolutionRun.scala | 30 ++++++++++++---- .../internal/SbtCoursierCache.scala | 8 ++--- .../lmcoursier/internal/SbtUpdateReport.scala | 15 ++------ .../lmcoursier/internal/UpdateParams.scala | 2 +- .../scala/lmcoursier/internal/UpdateRun.scala | 10 ++---- .../sbtcoursiershared/InputsTasks.scala | 2 +- .../coursier/sbtcoursier/CoursierPlugin.scala | 9 ++--- .../coursier/sbtcoursier/DisplayTasks.scala | 36 +++++++++++-------- .../coursier/sbtcoursier/InputsTasks.scala | 9 +++-- .../scala/coursier/sbtcoursier/Keys.scala | 4 +-- .../sbtcoursier/ResolutionTasks.scala | 10 +++--- .../coursier/sbtcoursier/UpdateTasks.scala | 8 +++-- .../sbt-coursier/dependency-graph/build.sbt | 3 +- .../dependency-graph/whatDependsOnResult.log | 2 +- .../shared-2/per-config-resolution/build.sbt | 8 +++++ .../per-config-resolution/project/plugins.sbt | 13 +++++++ .../src/main/scala/Main.scala | 7 ++++ .../src/test/scala/Test.scala | 7 ++++ .../shared-2/per-config-resolution/test | 2 ++ 22 files changed, 171 insertions(+), 76 deletions(-) create mode 100644 modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/build.sbt create mode 100644 modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/project/plugins.sbt create mode 100644 modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/main/scala/Main.scala create mode 100644 modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/test/scala/Test.scala create mode 100644 modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/test diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala b/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala index addf9b9ce..3d9d2cf6f 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala @@ -129,9 +129,11 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen (ToCoursier.configuration(config), ToCoursier.dependency(dep0)) } - val configGraphs = Inputs.ivyGraphs( - Inputs.configExtends(module0.configurations) - ).map(_.map(ToCoursier.configuration)) + val orderedConfigs = Inputs.orderedConfigurations(Inputs.configExtendsSeq(module0.configurations)) + .map { + case (config, extends0) => + (ToCoursier.configuration(config), extends0.map(ToCoursier.configuration)) + } val typelevel = so == Typelevel.typelevelOrg @@ -146,7 +148,7 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen val resolutionParams = ResolutionParams( dependencies = dependencies, fallbackDependencies = conf.fallbackDependencies, - configGraphs = configGraphs, + orderedConfigs = orderedConfigs, autoScalaLibOpt = if (conf.autoScalaLibrary) Some((so, sv)) else None, mainRepositories = mainRepositories, parentProjectCache = Map.empty, @@ -167,10 +169,10 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen missingOk = conf.missingOk, ) - def artifactsParams(resolutions: Map[Set[Configuration], Resolution]): ArtifactsParams = + def artifactsParams(resolutions: Map[Configuration, Resolution]): ArtifactsParams = ArtifactsParams( classifiers = classifiers, - resolutions = resolutions.values.toSeq, + resolutions = resolutions.values.toSeq.distinct, includeSignatures = false, loggerOpt = loggerOpt, projectName = projectName, @@ -193,7 +195,7 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen } def updateParams( - resolutions: Map[Set[Configuration], Resolution], + resolutions: Map[Configuration, Resolution], artifacts: Seq[(Dependency, Publication, Artifact, Option[File])] ) = UpdateParams( diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala b/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala index 23e1a0d86..a88fae0bf 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala @@ -15,6 +15,11 @@ object Inputs { Configuration(from.value) -> Configuration(to.value) } + def configExtendsSeq(configurations: Seq[sbt.librarymanagement.Configuration]): Seq[(Configuration, Seq[Configuration])] = + configurations + .map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name))) + + @deprecated("Now unused internally, to be removed in the future", "2.0.0-RC6-5") def configExtends(configurations: Seq[sbt.librarymanagement.Configuration]): Map[Configuration, Seq[Configuration]] = configurations .map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name))) @@ -25,7 +30,7 @@ object Inputs { shadedConfig: Option[(String, Configuration)] = None ): Map[Configuration, Set[Configuration]] = { - val configs0 = configExtends(configurations) + val configs0 = configExtendsSeq(configurations).toMap def allExtends(c: Configuration) = { // possibly bad complexity @@ -55,6 +60,29 @@ object Inputs { } } + def orderedConfigurations( + configurations: Seq[(Configuration, Seq[Configuration])] + ): Seq[(Configuration, Seq[Configuration])] = { + + val map = configurations.toMap + + def helper(done: Set[Configuration], toAdd: List[Configuration]): Stream[(Configuration, Seq[Configuration])] = + toAdd match { + case Nil => Stream.empty + case config :: rest => + val extends0 = map.getOrElse(config, Nil) + val missingExtends = extends0.filterNot(done) + if (missingExtends.isEmpty) + (config, extends0) #:: helper(done + config, rest) + else + helper(done, missingExtends.toList ::: toAdd) + } + + helper(Set.empty, configurations.map(_._1).toList) + .toVector + } + + @deprecated("Now unused internally, to be removed in the future", "2.0.0-RC6-5") def ivyGraphs(configurations: Map[Configuration, Seq[Configuration]]): Seq[Set[Configuration]] = { // probably bad complexity, but that shouldn't matter given the size of the graphs involved... diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala index ced79bf3c..56a99067a 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionParams.scala @@ -10,11 +10,13 @@ import lmcoursier.FallbackDependency import lmcoursier.definitions.ToCoursier import coursier.util.Task +import scala.collection.mutable + // private[coursier] final case class ResolutionParams( dependencies: Seq[(Configuration, Dependency)], fallbackDependencies: Seq[FallbackDependency], - configGraphs: Seq[Set[Configuration]], + orderedConfigs: Seq[(Configuration, Seq[Configuration])], autoScalaLibOpt: Option[(Organization, String)], mainRepositories: Seq[Repository], parentProjectCache: ProjectCache, @@ -30,6 +32,18 @@ final case class ResolutionParams( missingOk: Boolean, ) { + lazy val allConfigExtends: Map[Configuration, Set[Configuration]] = { + val map = new mutable.HashMap[Configuration, Set[Configuration]] + for ((config, extends0) <- orderedConfigs) { + val allExtends = extends0 + .iterator + // the else of the getOrElse shouldn't be hit (because of the ordering of the configurations) + .foldLeft(Set(config))((acc, ext) => acc ++ map.getOrElse(ext, Set(ext))) + map += config -> allExtends + } + map.toMap + } + val fallbackDependenciesRepositories = if (fallbackDependencies.isEmpty) Nil diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionRun.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionRun.scala index dc34688e8..f853386af 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionRun.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/ResolutionRun.scala @@ -9,6 +9,8 @@ import coursier.maven.MavenRepository import coursier.params.rule.RuleResolution import sbt.util.Logger +import scala.collection.mutable + // private[coursier] object ResolutionRun { @@ -16,7 +18,8 @@ object ResolutionRun { params: ResolutionParams, verbosityLevel: Int, log: Logger, - configs: Set[Configuration] + configs: Set[Configuration], + startingResolutionOpt: Option[Resolution] ): Either[coursier.error.ResolutionError, Resolution] = { val isScalaToolConfig = configs(Configuration("scala-tool")) @@ -80,6 +83,8 @@ object ResolutionRun { ThreadUtil.withFixedThreadPool(params.parallel) { pool => Resolve() + // re-using various caches from a resolution of a configuration we extend + .withInitialResolution(startingResolutionOpt) .withDependencies( params.dependencies.collect { case (config, dep) if configs(config) => @@ -126,7 +131,7 @@ object ResolutionRun { params: ResolutionParams, verbosityLevel: Int, log: Logger - ): Either[coursier.error.ResolutionError, Map[Set[Configuration], Resolution]] = { + ): Either[coursier.error.ResolutionError, Map[Configuration, Resolution]] = { // TODO Warn about possible duplicated modules from source repositories? @@ -141,13 +146,24 @@ object ResolutionRun { // Downloads are already parallel, no need to parallelize further, anyway. val resOrError = Lock.lock.synchronized { - params.configGraphs.foldLeft[Either[coursier.error.ResolutionError, Map[Set[Configuration], Resolution]]](Right(Map())) { - case (acc, config) => + var map = new mutable.HashMap[Configuration, Resolution] + val either = params.orderedConfigs.foldLeft[Either[coursier.error.ResolutionError, Unit]](Right(())) { + case (acc, (config, extends0)) => for { - m <- acc - res <- resolution(params, verbosityLevel, log, config) - } yield m + (config -> res) + _ <- acc + initRes = { + val it = extends0.iterator.flatMap(map.get(_).iterator) + if (it.hasNext) Some(it.next()) + else None + } + allExtends = params.allConfigExtends.getOrElse(config, Set.empty) + res <- resolution(params, verbosityLevel, log, allExtends, initRes) + } yield { + map += config -> res + () + } } + either.map(_ => map.toMap) } for (res <- resOrError) SbtCoursierCache.default.putResolution(params.resolutionKey, res) diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtCoursierCache.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtCoursierCache.scala index 23f8367c6..a018a565b 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtCoursierCache.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtCoursierCache.scala @@ -12,15 +12,15 @@ class SbtCoursierCache { import SbtCoursierCache._ - private val resolutionsCache = new ConcurrentHashMap[ResolutionKey, Map[Set[Configuration], Resolution]] + private val resolutionsCache = new ConcurrentHashMap[ResolutionKey, Map[Configuration, Resolution]] // these may actually not need to be cached any more, now that the resolutions // are cached private val reportsCache = new ConcurrentHashMap[ReportKey, UpdateReport] - def resolutionOpt(key: ResolutionKey): Option[Map[Set[Configuration], Resolution]] = + def resolutionOpt(key: ResolutionKey): Option[Map[Configuration, Resolution]] = Option(resolutionsCache.get(key)) - def putResolution(key: ResolutionKey, res: Map[Set[Configuration], Resolution]): Unit = + def putResolution(key: ResolutionKey, res: Map[Configuration, Resolution]): Unit = resolutionsCache.put(key, res) def reportOpt(key: ReportKey): Option[UpdateReport] = @@ -53,7 +53,7 @@ object SbtCoursierCache { final case class ReportKey( dependencies: Seq[(Configuration, Dependency)], - resolution: Map[Set[Configuration], Resolution], + resolution: Map[Configuration, Resolution], withClassifiers: Boolean, sbtClassifiers: Boolean, includeSignatures: Boolean diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtUpdateReport.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtUpdateReport.scala index 420b1fb34..17a6e52d8 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtUpdateReport.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/SbtUpdateReport.scala @@ -132,7 +132,6 @@ private[internal] object SbtUpdateReport { private def moduleReports( thisModule: (Module, String), - config: Configuration, res: Resolution, interProjectDependencies: Seq[Project], classifiersOpt: Option[Seq[Classifier]], @@ -293,9 +292,8 @@ private[internal] object SbtUpdateReport { def apply( thisModule: (Module, String), configDependencies: Map[Configuration, Seq[Dependency]], - resolutions: Map[Configuration, Resolution], + resolutions: Seq[(Configuration, Resolution)], interProjectDependencies: Vector[Project], - configs: Map[Configuration, Set[Configuration]], classifiersOpt: Option[Seq[Classifier]], artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File], fullArtifactsOpt: Option[Map[(Dependency, Publication, Artifact), Option[File]]], @@ -306,18 +304,11 @@ private[internal] object SbtUpdateReport { missingOk: Boolean ): UpdateReport = { - val configReports = configs.map { - case (config, extends0) => - val configDeps = extends0 - .toSeq - .sortBy(_.value) - .flatMap(configDependencies.getOrElse(_, Nil)) - .distinct - val subRes = resolutions(config).subset(configDeps) + val configReports = resolutions.map { + case (config, subRes) => val reports = moduleReports( thisModule, - config, subRes, interProjectDependencies, classifiersOpt, diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateParams.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateParams.scala index 5a5e641e9..6c3cfe93d 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateParams.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateParams.scala @@ -15,7 +15,7 @@ final case class UpdateParams( configs: Map[Configuration, Set[Configuration]], dependencies: Seq[(Configuration, Dependency)], interProjectDependencies: Seq[Project], - res: Map[Set[Configuration], Resolution], + res: Map[Configuration, Resolution], includeSignatures: Boolean, sbtBootJarOverrides: Map[(Module, String), File], classpathOrder: Boolean, diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateRun.scala b/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateRun.scala index cdd548b8a..7744dc3e8 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateRun.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/internal/UpdateRun.scala @@ -57,11 +57,6 @@ object UpdateRun { log: Logger ): UpdateReport = Lock.lock.synchronized { - val configResolutions = params.res.flatMap { - case (configs, r) => - configs.iterator.map((_, r)) - } - val depsByConfig = grouped(params.dependencies)( config => params.shadedConfigOpt match { @@ -74,7 +69,7 @@ object UpdateRun { if (verbosityLevel >= 2) { val finalDeps = dependenciesWithConfig( - configResolutions, + params.res, depsByConfig, params.configs ) @@ -87,9 +82,8 @@ object UpdateRun { SbtUpdateReport( params.thisModule, depsByConfig, - configResolutions, + params.res.toVector.sortBy(_._1.value), // FIXME Order by config topologically? params.interProjectDependencies.toVector, - params.configs, params.classifiers, params.artifactFileOpt, params.fullArtifacts, diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala index 3a687c483..0561db1cc 100644 --- a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala +++ b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala @@ -37,7 +37,7 @@ object InputsTasks { val exclusions0 = Inputs.exclusions(excludeDeps, sv, sbv, log) - val configMap = Inputs.configExtends(configurations) + val configMap = Inputs.configExtendsSeq(configurations).toMap val proj = FromSbt.project( projId, diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala index 7ccb8b98e..e03780feb 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala @@ -176,13 +176,10 @@ object CoursierPlugin extends AutoPlugin { coursierResolutions .value - .collectFirst { - case (configs, res) if configs(config) => - res - } - .getOrElse { + .getOrElse( + config, sys.error(s"Resolution for configuration $config not found") - } + ) }, coursierSbtClassifiersResolution := (Def.taskDyn { val missingOk = (updateConfiguration in updateSbtClassifiers).value.missingOk diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala index d708f2bd8..1af43bd39 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala @@ -13,7 +13,7 @@ import scala.collection.mutable object DisplayTasks { - private case class ResolutionResult(configs: Set[Configuration], resolution: Resolution, dependencies: Seq[Dependency]) + private case class ResolutionResult(config: Configuration, resolution: Resolution, dependencies: Seq[Dependency]) private def coursierResolutionTask( sbtClassifiers: Boolean = false, @@ -40,7 +40,11 @@ object DisplayTasks { Def.task { val currentProject = currentProjectTask.value val classifiersRes = coursierSbtClassifiersResolution.value - Map(currentProject.configurations.keySet.map(ToCoursier.configuration) -> classifiersRes) + currentProject + .configurations + .keysIterator + .map(config => ToCoursier.configuration(config) -> classifiersRes) + .toMap } else Def.task(coursierResolutions.value) @@ -57,19 +61,23 @@ object DisplayTasks { val resolutions = resolutionsTask.value for { - (subGraphConfigs, res) <- resolutions.toSeq - if subGraphConfigs.exists(includedConfigs) + (subGraphConfig, res) <- resolutions.toSeq + if includedConfigs(subGraphConfig) } yield { - val dependencies0 = currentProject.dependencies.collect { - case (cfg, dep) if includedConfigs(cfg) && subGraphConfigs(cfg) => dep - }.sortBy { dep => - (dep.module.organization, dep.module.name, dep.version) - } + val dependencies0 = currentProject + .dependencies + .collect { + case (`subGraphConfig`, dep) => + dep + } + .sortBy { dep => + (dep.module.organization, dep.module.name, dep.version) + } val subRes = res.subset(dependencies0) - ResolutionResult(subGraphConfigs, subRes, dependencies0) + ResolutionResult(subGraphConfig, subRes, dependencies0) } } } @@ -82,9 +90,9 @@ object DisplayTasks { val projectName = thisProjectRef.value.project val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value - for (ResolutionResult(subGraphConfigs, resolution, dependencies) <- resolutions) { + for (ResolutionResult(subGraphConfig, resolution, dependencies) <- resolutions) { streams.value.log.info( - s"$projectName (configurations ${subGraphConfigs.toVector.sorted.mkString(", ")})" + "\n" + + s"$projectName (configuration ${subGraphConfig.value})" + "\n" + Print.dependencyTree( resolution, dependencies, @@ -110,13 +118,13 @@ object DisplayTasks { val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value val result = new mutable.StringBuilder - for (ResolutionResult(subGraphConfigs, resolution, _) <- resolutions) { + for (ResolutionResult(subGraphConfig, resolution, _) <- resolutions) { val roots = resolution .minDependencies .filter(f => f.module == module) .toVector .sortBy(_.toString) // elements already have the same module, there's not much left for sorting… - val strToPrint = s"$projectName (configurations ${subGraphConfigs.toVector.sorted.map(_.value).mkString(", ")})" + "\n" + + val strToPrint = s"$projectName (configurations ${subGraphConfig.value})" + "\n" + Print.dependencyTree( resolution, roots, diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/InputsTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/InputsTasks.scala index 8f743a4fc..af1900ab3 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/InputsTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/InputsTasks.scala @@ -26,10 +26,13 @@ object InputsTasks { } } - def ivyGraphsTask: Def.Initialize[sbt.Task[Seq[Set[Configuration]]]] = + def ivyGraphsTask: Def.Initialize[sbt.Task[Seq[(Configuration, Seq[Configuration])]]] = Def.task { val p = coursierProject.value - Inputs.ivyGraphs(p.configurations).map(_.map(ToCoursier.configuration)) + Inputs.orderedConfigurations(p.configurations.toSeq).map { + case (config, extends0) => + (ToCoursier.configuration(config), extends0.map(ToCoursier.configuration)) + } } def parentProjectCacheTask: Def.Initialize[sbt.Task[Map[Seq[sbt.librarymanagement.Resolver], Seq[coursier.ProjectCache]]]] = @@ -53,7 +56,7 @@ object InputsTasks { n.foldLeft(Map.empty[Seq[Resolver], Seq[ProjectCache]]) { case (caches, (ref, resolutions)) => val mainResOpt = resolutions.collectFirst { - case (k, v) if k(Configuration.compile) => v + case (Configuration.compile, v) => v } val r = for { diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala index 4e4df6ad1..4599d2cf1 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala @@ -21,7 +21,7 @@ object Keys { val coursierVerbosity = SettingKey[Int]("coursier-verbosity") - val coursierConfigGraphs = TaskKey[Seq[Set[Configuration]]]("coursier-config-graphs") + val coursierConfigGraphs = TaskKey[Seq[(Configuration, Seq[Configuration])]]("coursier-config-graphs") val coursierSbtClassifiersModule = TaskKey[GetClassifiersModule]("coursier-sbt-classifiers-module") @@ -29,7 +29,7 @@ object Keys { val coursierParentProjectCache = TaskKey[Map[Seq[Resolver], Seq[ProjectCache]]]("coursier-parent-project-cache") - val coursierResolutions = TaskKey[Map[Set[Configuration], Resolution]]("coursier-resolutions") + val coursierResolutions = TaskKey[Map[Configuration, Resolution]]("coursier-resolutions") private[coursier] val actualCoursierResolution = TaskKey[Resolution]("coursier-resolution") diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala index ef7e923ba..463bea98d 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala @@ -19,9 +19,9 @@ object ResolutionTasks { def resolutionsTask( sbtClassifiers: Boolean = false, missingOk: Boolean = false, - ): Def.Initialize[sbt.Task[Map[Set[Configuration], coursier.Resolution]]] = { + ): Def.Initialize[sbt.Task[Map[Configuration, coursier.Resolution]]] = { - val currentProjectTask: sbt.Def.Initialize[sbt.Task[(Project, Seq[FallbackDependency], Seq[Set[Configuration]])]] = + val currentProjectTask: sbt.Def.Initialize[sbt.Task[(Project, Seq[FallbackDependency], Seq[(Configuration, Seq[Configuration])])]] = if (sbtClassifiers) Def.task { val sv = scalaVersion.value @@ -35,7 +35,7 @@ object ResolutionTasks { sbv ) - (proj, fallbackDeps, Vector(cm.configurations.map(c => Configuration(c.name)).toSet)) + (proj, fallbackDeps, cm.configurations.map(c => Configuration(c.name) -> Nil)) } else Def.task { @@ -97,7 +97,7 @@ object ResolutionTasks { val authenticationByRepositoryId = coursierCredentials.value.mapValues(_.authentication) - val (currentProject, fallbackDependencies, configGraphs) = currentProjectTask.value + val (currentProject, fallbackDependencies, orderedConfigs) = currentProjectTask.value val autoScalaLib = autoScalaLibrary.value && scalaModuleInfo.value.forall(_.overrideScalaVersion) @@ -132,7 +132,7 @@ object ResolutionTasks { ResolutionParams( dependencies = currentProject.dependencies, fallbackDependencies = fallbackDependencies, - configGraphs = configGraphs, + orderedConfigs = orderedConfigs, autoScalaLibOpt = if (autoScalaLib) Some((so, sv)) else None, mainRepositories = mainRepositories, parentProjectCache = parentProjectCache, diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala index 693cc9df3..2d312f242 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala @@ -35,9 +35,13 @@ object UpdateTasks { val resTask = if (withClassifiers && sbtClassifiers) Def.task { - val cm = coursierSbtClassifiersModule.value + val mod = coursierSbtClassifiersModule.value val classifiersRes = coursierSbtClassifiersResolution.value - Map(cm.configurations.map(c => Configuration(c.name)).toSet -> classifiersRes) + mod + .configurations + .iterator + .map(c => Configuration(c.name) -> classifiersRes) + .toMap } else Def.task(coursierResolutions.value) diff --git a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/build.sbt b/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/build.sbt index 671c71f89..54a8ba1e9 100644 --- a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/build.sbt +++ b/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/build.sbt @@ -12,5 +12,6 @@ import CoursierPlugin.autoImport._ whatDependsOnCheck := { val result = (coursierWhatDependsOn in Compile).toTask(" log4j:log4j").value val file = new File("whatDependsOnResult.log") - assert(IO.read(file).toString == result) + val expected = IO.read(file).toString + assert(expected == result, s"Expected '$expected', got '$result'") } diff --git a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/whatDependsOnResult.log b/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/whatDependsOnResult.log index e0e5a5377..64d70c9bb 100644 --- a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/whatDependsOnResult.log +++ b/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/whatDependsOnResult.log @@ -1,4 +1,4 @@ -dependency-graph (configurations compile, compile-internal, optional, provided, runtime, runtime-internal, test, test-internal) +dependency-graph (configurations compile) └─ log4j:log4j:1.2.17 ├─ org.apache.zookeeper:zookeeper:3.5.0-alpha log4j:log4j:1.2.16 -> 1.2.17 └─ org.slf4j:slf4j-log4j12:1.7.5 diff --git a/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/build.sbt b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/build.sbt new file mode 100644 index 000000000..f2f2507cf --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/build.sbt @@ -0,0 +1,8 @@ +scalaVersion := "2.13.2" +libraryDependencies ++= Seq( + "io.get-coursier" %% "coursier-core" % "2.0.0-RC6", + // depends on coursier-core 2.0.0-RC6-16 + "io.get-coursier" %% "coursier" % "2.0.0-RC6-16" % Test +) +mainClass.in(Compile) := Some("Main") +mainClass.in(Test) := Some("Test") diff --git a/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/project/plugins.sbt b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/project/plugins.sbt new file mode 100644 index 000000000..71a44ffd3 --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/project/plugins.sbt @@ -0,0 +1,13 @@ +addSbtPlugin { + + val name = sys.props.getOrElse( + "plugin.name", + sys.error("plugin.name Java property not set") + ) + val version = sys.props.getOrElse( + "plugin.version", + sys.error("plugin.version Java property not set") + ) + + "io.get-coursier" % name % version +} \ No newline at end of file diff --git a/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/main/scala/Main.scala new file mode 100644 index 000000000..370b4e4bf --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/main/scala/Main.scala @@ -0,0 +1,7 @@ +object Main { + def main(args: Array[String]): Unit = { + val version = coursier.util.Properties.version + val expected = "2.0.0-RC6" + assert(version == expected, s"version: $version, expected: $expected") + } +} diff --git a/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/test/scala/Test.scala b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/test/scala/Test.scala new file mode 100644 index 000000000..9d91a9318 --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/src/test/scala/Test.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + val version = coursier.util.Properties.version + val expected = "2.0.0-RC6-16" + assert(version == expected, s"version: $version, expected: $expected") + } +} diff --git a/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/test b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/test new file mode 100644 index 000000000..e7abb0441 --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-2/per-config-resolution/test @@ -0,0 +1,2 @@ +> run +> test:run From 808f9a0d0f5dd3095d8883257a67d78b80501fb9 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 23:03:37 +0200 Subject: [PATCH 3/9] Tweak updateSbtClassifiers handling in sbt-coursier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running one resolution per config messed things up a bit… --- .../scala/coursier/sbtcoursier/ArtifactsTasks.scala | 2 +- .../scala/coursier/sbtcoursier/CoursierPlugin.scala | 6 +++--- .../scala/coursier/sbtcoursier/DisplayTasks.scala | 12 ++---------- .../src/main/scala/coursier/sbtcoursier/Keys.scala | 2 +- .../scala/coursier/sbtcoursier/UpdateTasks.scala | 12 ++---------- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ArtifactsTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ArtifactsTasks.scala index bf2a41121..39e3a24db 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ArtifactsTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ArtifactsTasks.scala @@ -24,7 +24,7 @@ object ArtifactsTasks { val resTask: sbt.Def.Initialize[sbt.Task[Seq[Resolution]]] = if (withClassifiers && sbtClassifiers) - Def.task(Seq(coursierSbtClassifiersResolution.value)) + Def.task(coursierSbtClassifiersResolutions.value.values.toVector) else Def.task(coursierResolutions.value.values.toVector) diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala index e03780feb..8cfdfbb14 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/CoursierPlugin.scala @@ -30,7 +30,7 @@ object CoursierPlugin extends AutoPlugin { val coursierParentProjectCache = Keys.coursierParentProjectCache val coursierResolutions = Keys.coursierResolutions - val coursierSbtClassifiersResolution = Keys.coursierSbtClassifiersResolution + val coursierSbtClassifiersResolutions = Keys.coursierSbtClassifiersResolutions val coursierDependencyTree = Keys.coursierDependencyTree val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree @@ -181,13 +181,13 @@ object CoursierPlugin extends AutoPlugin { sys.error(s"Resolution for configuration $config not found") ) }, - coursierSbtClassifiersResolution := (Def.taskDyn { + coursierSbtClassifiersResolutions := (Def.taskDyn { val missingOk = (updateConfiguration in updateSbtClassifiers).value.missingOk ResolutionTasks.resolutionsTask( sbtClassifiers = true, missingOk = missingOk, ) - }).value.head._2 + }).value ) override lazy val buildSettings = super.buildSettings ++ Seq( diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala index 1af43bd39..8f56a12fc 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/DisplayTasks.scala @@ -37,17 +37,9 @@ object DisplayTasks { val resolutionsTask = if (sbtClassifiers) - Def.task { - val currentProject = currentProjectTask.value - val classifiersRes = coursierSbtClassifiersResolution.value - currentProject - .configurations - .keysIterator - .map(config => ToCoursier.configuration(config) -> classifiersRes) - .toMap - } + coursierSbtClassifiersResolutions else - Def.task(coursierResolutions.value) + coursierResolutions Def.task { diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala index 4599d2cf1..6fe4a8d28 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/Keys.scala @@ -33,7 +33,7 @@ object Keys { private[coursier] val actualCoursierResolution = TaskKey[Resolution]("coursier-resolution") - val coursierSbtClassifiersResolution = TaskKey[Resolution]("coursier-sbt-classifiers-resolution") + val coursierSbtClassifiersResolutions = TaskKey[Map[Configuration, Resolution]]("coursier-sbt-classifiers-resolution") val coursierDependencyTree = TaskKey[Unit]( "coursier-dependency-tree", diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala index 2d312f242..0cc24a47a 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/UpdateTasks.scala @@ -34,17 +34,9 @@ object UpdateTasks { val resTask = if (withClassifiers && sbtClassifiers) - Def.task { - val mod = coursierSbtClassifiersModule.value - val classifiersRes = coursierSbtClassifiersResolution.value - mod - .configurations - .iterator - .map(c => Configuration(c.name) -> classifiersRes) - .toMap - } + coursierSbtClassifiersResolutions else - Def.task(coursierResolutions.value) + coursierResolutions // we should be able to call .value on that one here, its conditions don't originate from other tasks val artifactFilesOrErrors0Task = From fa7279782304744839bdd704ceb3cbbe5a337de7 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 15:59:09 +0200 Subject: [PATCH 4/9] Clean-up scripted test --- .../shared-1/exclude-dependencies/src/main/scala/Main.scala | 2 -- .../src/sbt-test/shared-1/exclude-dependencies/test | 2 -- 2 files changed, 4 deletions(-) diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala index 1bc056610..89960e16b 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala +++ b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala @@ -27,6 +27,4 @@ object Main extends App { !argonautFound, "Expected not to find classes from argonaut" ) - - Files.write(new File("output").toPath, "OK".getBytes("UTF-8")) } diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test index fa5d6a196..e3e7dcee8 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test +++ b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test @@ -1,5 +1,3 @@ -$ delete output > run -$ exists output > publishLocal $ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-exclude-dependencies_2.12:0.1.0-SNAPSHOT From 3d374247cac8403c301ca2ddf56ea2be4da07e7d Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 16:54:19 +0200 Subject: [PATCH 5/9] Rely on coursier.Resolve to handle exclusions It applies it to root dependencies too, in particular. --- .../CoursierDependencyResolution.scala | 22 ++++++------- .../sbtcoursiershared/InputsTasks.scala | 14 +------- .../sbtcoursier/ResolutionTasks.scala | 13 +++++++- .../{ => a}/src/main/scala/Main.scala | 0 .../b/src/main/scala/Main.scala | 30 +++++++++++++++++ .../shared-1/exclude-dependencies/build.sbt | 33 ++++++++++++++----- .../shared-1/exclude-dependencies/test | 8 +++-- 7 files changed, 84 insertions(+), 36 deletions(-) rename modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/{ => a}/src/main/scala/Main.scala (100%) create mode 100644 modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/b/src/main/scala/Main.scala diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala b/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala index 3d9d2cf6f..82b00d189 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/CoursierDependencyResolution.scala @@ -25,14 +25,6 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen * sbt-coursier, that was moved to this module. */ - private lazy val excludeDependencies = conf - .excludeDependencies - .map { - case (strOrg, strName) => - (lmcoursier.definitions.Organization(strOrg), lmcoursier.definitions.ModuleName(strName)) - } - .toSet - def moduleDescriptor(moduleSetting: ModuleDescriptorConfiguration): ModuleDescriptor = CoursierModuleDescriptor(moduleSetting, conf) @@ -125,8 +117,7 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen } .map { case (config, dep) => - val dep0 = dep.withExclusions(dep.exclusions ++ excludeDependencies) - (ToCoursier.configuration(config), ToCoursier.dependency(dep0)) + (ToCoursier.configuration(config), ToCoursier.dependency(dep)) } val orderedConfigs = Inputs.orderedConfigurations(Inputs.configExtendsSeq(module0.configurations)) @@ -145,6 +136,14 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen .withCredentials(conf.credentials.map(ToCoursier.credentials)) .withFollowHttpToHttpsRedirections(conf.followHttpToHttpsRedirections.getOrElse(true)) + val excludeDependencies = conf + .excludeDependencies + .map { + case (strOrg, strName) => + (coursier.Organization(strOrg), coursier.ModuleName(strName)) + } + .toSet + val resolutionParams = ResolutionParams( dependencies = dependencies, fallbackDependencies = conf.fallbackDependencies, @@ -164,7 +163,8 @@ class CoursierDependencyResolution(conf: CoursierConfiguration) extends Dependen .withProfiles(conf.mavenProfiles.toSet) .withForceVersion(conf.forceVersions.map { case (k, v) => (ToCoursier.module(k), v) }.toMap) .withTypelevel(typelevel) - .withReconciliation(ToCoursier.reconciliation(conf.reconciliation)), + .withReconciliation(ToCoursier.reconciliation(conf.reconciliation)) + .withExclusions(excludeDependencies), strictOpt = conf.strict.map(ToCoursier.strict), missingOk = conf.missingOk, ) diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala index 0561db1cc..97d77d458 100644 --- a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala +++ b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/InputsTasks.scala @@ -28,32 +28,21 @@ object InputsTasks { private def coursierProject0( projId: ModuleID, dependencies: Seq[ModuleID], - excludeDeps: Seq[InclExclRule], configurations: Seq[sbt.librarymanagement.Configuration], sv: String, sbv: String, log: Logger ): Project = { - val exclusions0 = Inputs.exclusions(excludeDeps, sv, sbv, log) - val configMap = Inputs.configExtendsSeq(configurations).toMap - val proj = FromSbt.project( + FromSbt.project( projId, dependencies, configMap, sv, sbv ) - - proj.withDependencies( - proj.dependencies.map { - case (config, dep) => - val dep0 = dep.withExclusions(dep.exclusions ++ exclusions0) - (config, dep0) - } - ) } private[sbtcoursiershared] def coursierProjectTask: Def.Initialize[sbt.Task[Project]] = @@ -68,7 +57,6 @@ object InputsTasks { coursierProject0( projectID.in(projectRef).get(state), allDependenciesTask.value, - actualExcludeDependencies.in(projectRef).get(state), // should projectID.configurations be used instead? ivyConfigurations.in(projectRef).get(state), scalaVersion.in(projectRef).get(state), diff --git a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala index 463bea98d..ee3166fe0 100644 --- a/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala +++ b/modules/sbt-coursier/src/main/scala/coursier/sbtcoursier/ResolutionTasks.scala @@ -114,6 +114,16 @@ object ResolutionTasks { .map(_.foldLeft[ProjectCache](Map.empty)(_ ++ _)) .getOrElse(Map.empty) + val excludeDeps = Inputs.exclusions( + coursier.sbtcoursiershared.InputsTasks.actualExcludeDependencies.value, + sv, + sbv, + log + ).map { + case (org, name) => + (Organization(org.value), ModuleName(name.value)) + } + val mainRepositories = resolvers .flatMap { resolver => Resolvers.repository( @@ -154,7 +164,8 @@ object ResolutionTasks { .withProfiles(userEnabledProfiles) .withForceVersion(userForceVersions.map { case (k, v) => (ToCoursier.module(k), v) }.toMap) .withTypelevel(typelevel) - .addReconciliation(versionReconciliations0: _*), + .addReconciliation(versionReconciliations0: _*) + .withExclusions(excludeDeps), strictOpt = strictOpt, missingOk = missingOk, ), diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/a/src/main/scala/Main.scala similarity index 100% rename from modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/src/main/scala/Main.scala rename to modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/a/src/main/scala/Main.scala diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/b/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/b/src/main/scala/Main.scala new file mode 100644 index 000000000..89960e16b --- /dev/null +++ b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/b/src/main/scala/Main.scala @@ -0,0 +1,30 @@ +import java.io.File +import java.nio.file.Files + +import scala.util.Try + +object Main extends App { + + def classFound(clsName: String) = Try( + Thread.currentThread() + .getContextClassLoader() + .loadClass(clsName) + ).toOption.nonEmpty + + val shapelessFound = classFound("shapeless.HList") + val argonautFound = classFound("argonaut.Json") + val argonautShapelessFound = classFound("argonaut.derive.MkEncodeJson") + + assert( + argonautShapelessFound, + "Expected to find class from argonaut-shapeless" + ) + assert( + !shapelessFound, + "Expected not to find classes from shapeless" + ) + assert( + !argonautFound, + "Expected not to find classes from argonaut" + ) +} diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/build.sbt b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/build.sbt index 0ac1493d5..7888e23ca 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/build.sbt +++ b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/build.sbt @@ -1,11 +1,26 @@ -scalaVersion := "2.12.8" +lazy val a = project + .settings( + organization := "io.get-coursier.test", + name := "sbt-coursier-exclude-dependencies", + version := "0.1.0-SNAPSHOT", + scalaVersion := "2.12.8", + libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M11", + excludeDependencies += sbt.ExclusionRule("com.chuusai", "shapeless_2.12"), + excludeDependencies += "io.argonaut" %% "argonaut" + ) -organization := "io.get-coursier.test" -name := "sbt-coursier-exclude-dependencies" -version := "0.1.0-SNAPSHOT" - -libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M11" - -excludeDependencies += sbt.ExclusionRule("com.chuusai", "shapeless_2.12") -excludeDependencies += "io.argonaut" %% "argonaut" +lazy val b = project + .settings( + organization := "io.get-coursier.test", + name := "sbt-coursier-exclude-dependencies-2", + version := "0.1.0-SNAPSHOT", + scalaVersion := "2.12.8", + libraryDependencies ++= Seq( + "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M11", + "com.chuusai" %% "shapeless" % "2.3.3", + "io.argonaut" %% "argonaut" % "6.2.3" + ), + excludeDependencies += sbt.ExclusionRule("com.chuusai", "shapeless_2.12"), + excludeDependencies += "io.argonaut" %% "argonaut" + ) diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test index e3e7dcee8..9eee79a6c 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test +++ b/modules/sbt-coursier/src/sbt-test/shared-1/exclude-dependencies/test @@ -1,3 +1,7 @@ -> run -> publishLocal +> a/run +> a/publishLocal $ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-exclude-dependencies_2.12:0.1.0-SNAPSHOT +> b/run +> b/publishLocal +# doesn't pass yet (missing exclusions of root deps in ivy.xml?) +# $ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-exclude-dependencies-2_2.12:0.1.0-SNAPSHOT From 6cebc8fe7a5763ee75dd17385fa3a6fdb00100b1 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 19:27:41 +0200 Subject: [PATCH 6/9] Keep global exclusions as such in generated ivy.xml --- .../src/main/scala/lmcoursier/Inputs.scala | 13 +++++-- .../coursier/sbtcoursiershared/IvyXml.scala | 32 ++++++++++++++---- .../sbtcoursiershared/IvyXmlTests.scala | 2 +- .../all-exclude-dependencies/coursier | Bin 11811 -> 28197 bytes .../shared-1/exclude-dependencies/coursier | Bin 11811 -> 28197 bytes .../shared-1/exclude-dependencies/test | 3 +- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala b/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala index a88fae0bf..da55d13b8 100644 --- a/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/Inputs.scala @@ -123,12 +123,12 @@ object Inputs { sets.values.toVector.distinct.map(_.set.toSet) } - def exclusions( + def exclusionsSeq( excludeDeps: Seq[InclExclRule], sv: String, sbv: String, log: Logger - ): Set[(Organization, ModuleName)] = { + ): Seq[(Organization, ModuleName)] = { var anyNonSupportedExclusionRule = false @@ -144,7 +144,6 @@ object Inputs { Seq((Organization(rule.organization), ModuleName(name))) } } - .toSet if (anyNonSupportedExclusionRule) log.warn("Only supported exclusion rule fields: organization, name") @@ -152,6 +151,14 @@ object Inputs { res } + def exclusions( + excludeDeps: Seq[InclExclRule], + sv: String, + sbv: String, + log: Logger + ): Set[(Organization, ModuleName)] = + exclusionsSeq(excludeDeps, sv, sbv, log).toSet + def forceVersions(depOverrides: Seq[ModuleID], sv: String, sbv: String): Seq[(Module, String)] = depOverrides.map(FromSbt.moduleVersion(_, sv, sbv)) diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala index 5b9d614dd..9369a42fd 100644 --- a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala +++ b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala @@ -3,11 +3,12 @@ package coursier.sbtcoursiershared import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Files +import lmcoursier.Inputs import lmcoursier.definitions.{Configuration, Project} import org.apache.ivy.core.module.id.ModuleRevisionId import sbt.{Def, Setting, Task, TaskKey} import sbt.internal.librarymanagement.IvySbt -import sbt.librarymanagement.PublishConfiguration +import sbt.librarymanagement.{CrossVersion, PublishConfiguration} import scala.collection.JavaConverters._ import scala.xml.{Node, PrefixedAttribute} @@ -16,6 +17,7 @@ object IvyXml { private[sbtcoursiershared] def rawContent( currentProject: Project, + exclusions: Seq[(String, String)], shadedConfigOpt: Option[Configuration] ): String = { @@ -28,12 +30,13 @@ object IvyXml { val printer = new scala.xml.PrettyPrinter(Int.MaxValue, 2) """""" + '\n' + - printer.format(content(currentProject, shadedConfigOpt)) + printer.format(content(currentProject, exclusions, shadedConfigOpt)) } // These are required for publish to be fine, later on. private def writeFiles( currentProject: Project, + exclusions: Seq[(String, String)], shadedConfigOpt: Option[Configuration], ivySbt: IvySbt, log: sbt.util.Logger @@ -53,7 +56,7 @@ object IvyXml { val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule) val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule) - val content0 = rawContent(currentProject, shadedConfigOpt) + val content0 = rawContent(currentProject, exclusions, shadedConfigOpt) cacheIvyFile.getParentFile.mkdirs() log.info(s"Writing Ivy file $cacheIvyFile") Files.write(cacheIvyFile.toPath, content0.getBytes(UTF_8)) @@ -63,7 +66,11 @@ object IvyXml { Files.write(cacheIvyPropertiesFile.toPath, Array.emptyByteArray) } - private def content(project0: Project, shadedConfigOpt: Option[Configuration]): Node = { + private def content( + project0: Project, + exclusions: Seq[(String, String)], + shadedConfigOpt: Option[Configuration] + ): Node = { val filterOutDependencies = shadedConfigOpt.toSet[Configuration].flatMap { shadedConfig => @@ -148,11 +155,16 @@ object IvyXml { n % moduleAttrs } + val excludeElems = exclusions.toVector.map { + case (org, name) => + + } + {infoElem} {confElems} {publicationElems} - {dependencyElems} + {dependencyElems}{excludeElems} } @@ -166,12 +178,20 @@ object IvyXml { val doGen = coursierGenerateIvyXml.value if (doGen) Def.task { + val sv = sbt.Keys.scalaVersion.value + val sbv = sbt.Keys.scalaBinaryVersion.value + val log = sbt.Keys.streams.value.log val currentProject = { val proj = coursierProject.value val publications = coursierPublications.value proj.withPublications(publications) } - writeFiles(currentProject, shadedConfigOpt, sbt.Keys.ivySbt.value, sbt.Keys.streams.value.log) + val excludeDeps = Inputs.exclusionsSeq(InputsTasks.actualExcludeDependencies.value, sv, sbv, log) + .map { + case (org, name) => + (org.value, name.value) + } + writeFiles(currentProject, excludeDeps, shadedConfigOpt, sbt.Keys.ivySbt.value, log) } else Def.task(()) diff --git a/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala b/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala index 6a460e0be..3085d849a 100644 --- a/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala +++ b/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala @@ -21,7 +21,7 @@ object IvyXmlTests extends TestSuite { Info("", "", Nil, Nil, None) ) - val content = IvyXml.rawContent(project, None) + val content = IvyXml.rawContent(project, Nil, None) assert(!content.contains("")) } diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/coursier b/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/coursier index 6cada6f8bfeee885fead8a7cbfbeab130ad6b10f..1153c17975ba0deff835940340867c1dd774da93 100755 GIT binary patch literal 28197 zcmce-1xzGgpEk-cxVyW%ySux)H}38X?(XjH?hMelyIaE`1A{X-!=3;8?w9?Nd$XJD zCbyEVRG&KMq@JgL_46htW%h7$Wj3>MWCl2TlDJvJIhwj!xd~E`!@=1IvhtI7S=-nH zNc2g_C~U|`82?^U{A&V3h=M8gHx4&!t-XXDZ z{Lj&UmNCjP($SK*0o+L#8UJoCLiYFM_je*Wqg((P2|xdT&IY)dn*ZlF8$k*h8XFRN z5>{H;e~(%?IRfC|{_mX_ZB1SOZpT9Q{}YP;`)U8d_WuI*7uWv?leLYd`(Ggc;`}cd zfH%OL#Mac)l!QV~R7=!YSw%zrU);f|$bW^v0E2;n0s9eBssi?Z9przn&7C}4-E07V ze`qHscQ%T*eOy>5cZf^D3n_lPuj8DZJ0N$3G7;33xh)N4**iwM1 z&2m_Mxiu=HmT3x-14({#Ds0T#fD=s#$(8gQ3G#V!n{>i-Dzebql`trjDAV_PrVN(V zHeoz(?z;EM>w3rUw0EC-|KUw|;2lo{TL^=d@=yuYE1HMwunI<2LQX<-L>Bcas!Mx> z7WFB*%Xs7~84=oTRn)ko0O=tus&cHi)JQ8?0s3uU)RTn&!7x2jo+n5Xd(RaV0`Js& zlK_9cR*Gqkz|Foh%rfbm1X2-fFu(BNIiT`biARj;mW^kp!0}}LWw`-t$}}YghT-G0 z4m;=K3++rfZ^SFkI2_XCvdlFVf{?fy#xD_Mxw**KzCpXhkxmzJ9zf2p&9KrS@J96F z-XZV-%@Ms(*%7GmZqO3g1!hDdGI5Q}d4M;3Y=}J5OZZuigSo%_6F>q>h~C64U@%QD zyWo27j)lyOYA*FLYscc*hM_!5|PL#J=kFummrD;6gdiXS4 zYPIp>Hlc}5&0}Qv4ZLT&nwm{=PToI?q>`UgVle`kOWNU$&28JZJ?(l;ZF&oLxVo_w z8k+MLIcemPA!6{Nv2++d*Fgyeb^tqY?mWIc51VNT;rw z3VKWIHHWzjKU`}nQu}>IiH@9oO4jOCpgCT$>L%s34m#b(qS85=48Ap$qQz^S`3!mxAYs<5HIH%kQ)svcgtD-0@IUnfDHY#{j zWdsk)0`pVIugF}=kQkRaP1qZ_l2JCZ;8{wKq9pq$-Q7*zOCIocDk<&t@(YRtQ*N(@ zBVw_t&PZd-xVFx=6A?KoV=7mUlkW~yqmw`8*cIxzkg@0K;19HT8u2z5v<=PLDBNhB zHp9!tB^2VZ|2aon$_#FxZmA8yQ}H}2GcTK|K6PvX$VtO6w6R?fb_51B*M6GKt0K)w z9`hXc!mDv9PqFF=vbQ+)IYZWwB~y`8+r84>ktu2^rHt356>|D58N6Keg|Jl`559i) z!8iKpPuDo@2L4WYt_3LPMAryw7CL&WI3|Dx_pI59E3>#_U#~Aycq?o1V6b5i=ha=j zcmGDabCM>RIxxGzIYuC(!dLt%qR+&uQ2 z!n*Y=z&Xag=DmhPNU>O~czQ}+_B-jMgcUrR#HD!@0kA_}N9=(>Z!5m|mW_*AaGk;IId>lQPuNv9OUqZ< z9=@|Fy(0ocara}!527BlOQNBHu(^Qy4JH_j=FqZw2778#Ai!S6Xsn{F(g!uPklY=X z+h908Hp(A;iyDEaF6<%Mxrf0jGre_s>ZRac=hV|J${mgq8{OeK6lvoTkt{O5^VyqX z%3nB7b+v6h@@pRjwRH8ha%(>}zU7ZyeHrgtusET6vZrVwvgpfmqPu;Yeu+SvPd>dZ zKcat5@hDhLmF=~f{ejk_b+~>>GJaAmk5e;a(%6Q;!>G0JOuQ`xBQhYMijvGq0xc8a1L?TGmca=_66F%G{KG%%{FL+_Iat zt>c;1QV6J|h1T)Rc1;50zW5j7O*sML6&dU0hl&mO<4{0l(+u6ZJ$jfpr`>c{3}@(9^ua@W^WCXmZ zV5#p1LXnCPc_`m-+CtyqQb@>qNy-YwXUIy)NXpE+zyZ$p`>6`Ye5(maMA@t zkrcuSbnuvvkAVR}bMVw)j{59G=e?R|o$j<+UoUa4xkFkpT`Q5_H zicNQ-rAdUxA}SjlO7{mLTi zVK}4JMz^%|{r2no+uI}i^|x!RUjtfOC!w&8wM{;+`>%cXpZcE$o+7A?0&h&grX}j* z#My)xj^Npd(r+bkjZ<#B;;1eBrN_Jg{>o#>7VjEkPJnl%v1`D)?wDJPiiIj;ENx2` zUF^@6EYeurmMlil8(JU@XdSH}<;Ds2Gx^3Cc3$3FbT}4GNyb}tcp7bt;)^VdtcFa9 z8XA$THzg=Fi~_bB&T7cAD+lx&!O66%1MYN4gN4`+6VDR2!K?|#6#*Kj!PL7o1hOjy zL;_@l)96zk5-Y=80foZNjcw!Xl^+oFsSlCu)S0k8((qtktDubO<2tISAculJ;K(Le z9_XzH@p1MhZm1$Ia2l+DJYx;~98bJ~jhJWInZE%8u75nU3Jp6W15t$o zLlR$ZDI#zO4#(%6iGl0y&%E=G@ct`_#%2?V#-Qaufp|aM0noTp)*Hh1+t=JrawK+w z-$%LvKgJ84dx?XF90K1ly;wdZ6^v~lA5w2 z_}|H7q$H!LsJ%CsEf1A~r+R!PH4Hj*`lWt4a>nOnFM4ruJI!rx_Vw?{mjhx})u^wS&L`igcJ4d`YXTs~&Pf<8Y8aGh|Dj*ZNE=-+p`NnqFjN zU#`>#{46W5Q*C)EP~fYkV%KA;Y>?Og5nJQgIBl`@+FAjKyl@ThrE>eTwlpJ`CHjgROyxM5X7U zDW#>mcGEuq-cKWH$b-g8pTHYe+W<*f<9ejJz@mP%xZH2mc~b=;V9y=T5KP-A&q}HidjEL3Xgr0@j;PhQ>yOOhFc8n|Z5lgY$?t>A0IW{lO z@|TVMv7pGx0>wGjbXolH18(XWWRQNIYzKdrx`|QOZ;kBs7mms#2|*B-r!^60Bbg*t zKZr}ZhjT{@g=^x|g1B6~6JHzS88yrrV(r$D>Na$!$K}x8(pB4mjHdNGMb0TeM~o$^9&GePH7^y{(;y zx8nAc3PB$lQ)V`HWK=?*d|Y0z!DONl7Y6Du36ig}b9lad+WbP8HTIS|@yb|@?`v_? zE(9Q13*zoh-k-A#?^gcGTJmA&=N9j6Y8LN&o57gn=J(gTunIQ@5qLJ{Z$$a<=iAtG zr{d9Pz}s4O8Auk<4g+uj^5IpL#tKDLCk7q9HLJfWW9cPVq`thASWr2XU8aK%#O}=1 zwd*N4=~bMM%J*u^TRp6ZEm@cQuE|M3Z0XJVQuLRi41?$x`=)T^3?p=B-E*iH5m>UG2#mUaE? zM&)m;4iMC|yfPAj?Xlk-)wbX5sIILy|G>Q1>VVU@F`K=t@Pg=9p4ut$&`_T~H7Iqr z-K?9lX`prIanM)rMMmk3bj&IsU=~d|QMQ~R=+?+*kgFrju0A4{pw21pwZ@S- ze-|2!HOXNe4)JnI&p^|yb%w$0;~GCaeOWY0(b`|pE_-o`aJ_ih4s9=}ea0s(CsOQa zN~~uQ1x2~89lR~N&fZ@GQIq$jk?}a<)!oC&W11>4fMY2>U_}=H?Tk@!xx($@t#t-I zuW$1V1JkL${bkQ+cS${bI}yw29ci~OFi35F%Vrs+iqWlAX$|K)LVMZlN=_=>I7K(w zv^sSaA)eJm9qe$0Kh+UKr(?P`gpv>r__C7Ej0!7X_=%B{yBU?vRc&|Q%c0&J-z+D$ z?uJib<~ZIvh%Ym)(QvtY zz-IFq>o*#(3_*L_l7D`w=2kWR^0uEDQRO!~w2TBn+5BMZSuDnl8CTGZsuNdGhidJO zbnf!fY4)ZdB|WVVe28}GhzIjVZ2*4 zAU)5x zG1sy?r>~x{quQbl3_5}_8YR2YGxSL6TK$NUv52GDWYoF!VxzDtGS)YTzEfM|4XfKU zndvJ?O~E!@1!lI52CVab+{dhEr{D0%?lbrtRGi%$!g28Yrt+^~a-z6cy^!NW+~Cv` z%>c#AAVj*-v)_VY5fT7rORHh@2T!WL#W__29?(7ll3z?%6;7yJu-g2t=_rK2{It^L zWwHt8CjQuCymZsMi$&;=sy&QStv`$v@{yV7WjAmEUAoyG2S4-+@}kXQcm9Y#%(1x6 zDpB4j9%rR9mV-cqOvn-J6#0fEs^KuICKh;foXNCH&K*of@=15}6b$WoViBmR6f2G3 zi<}i0eRIU)hn_)%qb>3w)sBJ#>C*iHE_p{1OC~6fbt{bEn)k}uG&r1f3YGxR@!(UJ zwoH_vlH)nbWtVk@x`cdVMO5F>Z5*Y--i+Cd-23vDH_Z-@u}B4rDr$F;CB%bVXV+-+ z2dvM@PCQC@N5>{UypL^v{<^QVY+*d7pG-?Wj$MRGy7ePcY5SU6*CtTQPSD=-DC}FF zHFe+*o^G}9>MSX{4nqFs*e$?Dj8SL1w;c$tFL;$4}Adb#9Lk1>M z^@q_pbJ0MUPslP$?A#BYa9H)TtR-Ti<_@p8JMEt_JZbT0hvro$fjgw$d1|bOS7oOJ zp@T(kMbgbNaKM^f)rcw8wnf~Hk?f?uGr4!x*!%8xR~@StP(f0PGz}Z`~Yd22YPDA>ta5f7^yrO8fQwpuAS&% zpr&UDSi&1|_%(#a`ls#qy)K8#qJyU@2wX$pO8LlWU3|44qNaOLbLa2OL-4Aw8{S)i zgMt{A(Nbp@nl)=93D-`Qg2=_qAC%Xg7)M=E*mKw=Y@OU_C3xRC7bzfD_?siXZt{7d zPzF zD89Vm@M-wH_|gNo>$y6;{0<`|2jR3p!%RXQ*}JO{tXUD=3L!miSE`5_@`b)tcL5Wxj8xnnjVDv?N zfouqusd%5%J{rQ#aQK_J4koOU3#=d#DG}_PCoFFy%O}nWw(W&RO}%ten~J8i^N@>Z zdrqK+?mRNWPV~76xbS@X35Bl9j$$(xpfneD}4IZCK}w)bjh_8?eZ6W*0ZbL>7rR_oGRXJV_kL`s}QKjakyi9_8HiX zb3H~G)0~+jkkf?q0xKv+uP}3J1;5-H{<3Y}9c`aOLlGIF=Uw4uezf`UodC-w zEHkeFaBa&fn5EJxChZ0%kuWkDSH83mZb^aeQ@|T+e|YV!qXA`G zAvGAR!>xqW@Q^pz+k(Ft63-tIbW4q45NExeZ|v9QolYH;q(2(;AKtSr4r7!4*0Gx? zJGWTe(yL)dNO8GedIfe;9~r)*N=jUXDP>;{;V)1h<;GRFOd>vZjS0lYQ(ejO5bqas zQl|g-bls8h>q-}H7F=HO)8LBTfKZUAJLApC!rwQ+aNxbrr&?{NEG56~zRgFdcaw*O zeM^ad-K71lR(zFGMCB$#I02p?Xg&8rw96cqo8erLB-L%!4SF_8UGORIwpKK9g`k-mE6s<3ka1GeUlaA) zda4dS?Q-WWhzeYDjhsGZ92uotS6PS9_;chIBX^3_pz^$x4|jb zu_Df`a__bUnpB#S!$w%?wFlhKYQM*S^_x#cz#nKj1fj?5&*GLKNdk+b>a zq_wk7{#c&Is*T_-4*S3_pU5O)lb7LXauA~+*tW6lAgN2{?-3Vw^e}>jD6P-m)$*dN zvdZ0YTocxT-{uIki_=s1fptA*s=D}5v|nnMPuIOj{9&JsLHOV`ZnNDRUWlkjD^~g% z^IL6NzJlEGRltC^;F1{{^UO=>Yvmvv~yQIa~_kYx||A(6# z7bF;%58VGo4V(U34aaN4>fn!K{(+{LwbjK(Np?`h*Vx`TL@BdTK;w0BsIrj(LpRT* zd(ayJ9~P^v)8e`cpb%VoWAaSou#sr5*O9Ol#xz~0mfUx=xaHD<-<2L;iTu{kwl6>hFkRL zLb-Q^!&9{HNzdgYjTC{5xA@Wfa_>rqPiR3&&!zbKH)PzNLmD7a2}wlv2vtn&K^4#_ z3ot825l6KdcV1OB7Q5CPoB>ncMQoHS&%(PHPFRi3^B@}`mCo~oq@g4jfar|{$~c~ zMR!z#x%w1<2uAbH5umSlmfifZ-0XefAOv79&ffIj{LULk-Y7oq-juT*u#%-76xb&P zw7TU&EClj%`V&3bK7;R-fkb!8LGlc5Y(}*p(w%A$y?1*=qQUuaoh&*kOA>MRPKu z3p2_pX5$spy`&qG}oF z=7l?A^00av!~ENEkkegnw%QTv+B-fdBakp5Yn`hAabq+K4*d~=j;=#j2n>x={>f6a z9gEg8))(uSE+r1eMdiP&>*}d<3}*IHL`YC*(vQ*Dl-#0q(pb8yDH4~OE7MA-%_{0F zR+_dV{;IdFi~^DXIwD*vRZjzRlTk~3bK$Or#Ie#xp)ZTFehte6tW_PzdKoq^sSys# zaep+1g>m8&v#3)8x0|R+T?D11V~>RwxEC(DQ&0s>pUF8`aPQ#lClU}}$OVvpdoeyx z|F+%{&YqoE+WbMSTW~hZE6e+{(qpSlPrR_~%pRxd_r$G{pi3AsZg5Ttq?WFGA51=0 zB{dopaWH3Wvn@cnMU-SYIjdOzBO{xI;Vn(XP^hh5{vi!h9;)>AK!Sp_sTTrd!Negd zSP<8dAd$+;yuD_=a@6EqXkp=2fNva9#MlzN+fQZ>#a~Yo*({CypvkpRMrn97h0DN%7TH^#^(7 z^w9Xcb5k8>NQ&h)OmotFV|v8$l>|pAn7L=W927Pob1BBJIa6c<;$*JuBWroquhutN zgW(*Rv6jTtXo8IwC@m5ZW@!GvOTe{XG?5a)4*+p2CsBxaF)%X$xvg|zmzw8 z6ANyy(-`K0V_auOeVqx`un@rB2A5i$3iZgQ>HPeeZjs*W$orzP#6^|)H`dT>RY>zI zM73rbQ177)M$I?mFVRn12UqT_^Za_8+Uv}9+;4csBp*@vY&ON;sUY*R)AdeNd>Dg! z%X-plZs9?Zly%4t2VF@#qdg*i+>BgA1KVgZV3lv$Ut*GoP9^Pt$^k&rxk#5|A zz0yuIUBg{4nwql&qiHS_T0o#oy7%TLAPq^d@T~Eh8v9V)o;$h++AI^jaZ2s*igw-$=DN?X(qYM*C~tc>(q>LSe=3$~FN zM<6)9L_&4-C0y=WC!dV;)V}|)mL(@h`87dMfTt#a&whp&nTK37T-o#+A3A`Iz}473 z^Z2qx`}b5Y>!F+9YVB}w3`c@5SJ@(KJXK+e)Ds6sp(~bl#UiInsjP6;Q2i+j#bAVG z)AB`*)Cb*;`AoMVCi}gW0DiWcI#eW7E?XB>mT2#|WJ0(B8HJ>b&13h{26hVj23J;m zaj3aW3%(!#VzXGAm&wGgYe$_E#r3VMa_Lm*=rxHgl(Cz1?mda1H+r@q@Zn-+ z)F0aNuSgZ7baXLo5Qrm1uzzByqAeSrSgY=xvGC2LCuU0P8kn`Lg7ou?JoB9jgg)e1 zCnV6>JMO{d=N3j1pg>bsTXpsYT#d2Yu^ix^0_SU#oSw1}8<=o_qHlD%vLWNoUC+Y* z_!#*Y`LyUTU|@o%{~I5};y-*0bvtErQ4GQ52K(N+yCxXABC}BkdEI^F7#x2}MHO^T zs^8o0UJUXbt+Sq555_iR`spLY2Z?NZHuYkI6M;NlL{@WxUXOE$h5G|vyMo_%4LDt9 zM-#zzd%>3u9%N6GGS!;hSsSo@(OCu;R95t^!2}q0BTFSy|RT@_k zA&Hm}572Pv>asY3%dj^Xm&|oFZc+sT@M~!{yDEL`bWA4r%K5~HXdUcty><}0?Yo&Z z2`04SOZjWtAk=wvEvBBc?l`@=4ed^+89V)4!W(s6*Hw< z|2AY-iuSG~FH=sX0`_53x^SbowBt(F)SB&8zbEH%nAV>z&mu>9$KKAmL*EiOF>H+T{IkSHi2*Wcgzfhf~jrkid) z-^^$TUpjE^)a;R4PkY4#4@(GTp;E`@y>tJlDoK7lTFJvb_t+}UT2z!*rYP0#ATI9B z@ucUVTOv2Xu$mH#%kWCJ!x2=tA~F6G5>2R6So9rscmKO`U6O0Y;T==l7{$d9Q;?L< z=p@FtdY-o&d+i-&I07ENJnb00R7~^K_yAJimw!aeoEyap4IB)t@#}vjV(kA-pOU(@ zsVl%j{GVPW1t(Jrfa`yVT+(+XAPdTvFtyEBQW5$v6M!AKCRT?ALw*vQs);Ed9z9}= zy+yA8wv2`hf&dtDxNkL|#o6HD_0@Z?@N3}bKoFRLInE)?hzT+H2Mm!fE7`A69tj3# zKDmMzY~Rv4QcVE6m96G6_@uSI))D~OOL8x`0#R4rny!2@RXTW5c#|-8>+w&SR(mfh zOhr!`I5j^q8U;(iMR{{O$7bu=9G`fj0^_RxQ2Ay^90UKGw;JVc{VHO&UacQno?dY4 zRXrgQ_OGPzjCST+VDh+2*jo43xj7KIN&ND3Ei=SVZ^+ND8}0<;J8I%uLtFB~pzlrt z%~1c)dO4Ai^Z1vvxG(<|t#tnltqT9q2lj6^|Fc4_>IwD56H3$*iqsRz^q)HU-(tBX z6cQ5@78DW@lo1pY5mX=)krb3A;y*=l)_CjxclA6ejFtW59L$=WEDBl3MNAW#EQ(dwA_0CU8;1Z6^Yls$D5uHWS8feGxT-u@nfpsilZ2hQ!IRSR z{?K5|l7uh+DzHaec!_}i(&GGI68GQr%~}1MF+h^2JWvEBe4oO`JauJ6ua-yv&Ot3I z1iJWew6=ug%m-h5S1l#U{xGl05YMy&xT~K$s@E^*z%uajTuP+^i7HZBIRX?__awBLR7x# zMZHN~f6AETHM@dIg&MaIk7VI8h)-kWU^%PVY{)zpF7?sqQjOMO@EnE3 z^UZrREvHGDmUq3x14XC-!#J*erofQ^97OD4o3h>?kH0g%pt_f+#n zD3Jt07z!uKSX2lr*{YBk>7A1i?rWV2c_IJRoQ5hj~zE>@0H)Vi+-*nz9 z8l$mf*D(h#L1&(U6?@^diba`#ELYRnboxQ9gs-nMj;PCYo3OR^Y8=u&BDZ zX$YyM52vuRy>S1L4_neem0CLI`WCruCb%bkyGf#Hm;H36r-<3K5p2$K$yqCI1Cfm1 z=+o$srDB0{Y6;&=mTB2h5)RbX`^nsZ!{lex$p%gZQ=7bC4<#s5&jFgFH0CmcGM%s@ zj`EzWN{gVULEHB<9qnz%t1Mo3%Mj_ebz4?jgh^I0bL=Cx!s#LFa5#C^MdofabTA%;!Z3p~YD8n`NwedAE{y>FLD@1??t1&o8)^Wnh zplol!Y0)|`0*YW5IqN&cdBTECsq%TNNA*|N)4NGY&_Xz~2Hx`#jd#O59>$S1N;M%9(1}Xo6T3aJDdrTnGSaK zmh;U>CGDz);1{D}R!-0mqY{OdOJZK)ollvbH1*O_=;mc+l-boRF1Rs}J=JS)8#6r2 z5SMbGEX`~C^&<*{D6>I2=J8vIK9sJt5B0eUfS_ad;b5sDUTnEOR1g8IZ z)sPYAcu2h(sHE8e67Oge+*?4azv#FE&~m=mey%IbWzCx*P)=Hg*H~;SclSWXYk0`c zHOZ*?&S9M=;KN0qr#r==cyy>ge4+Vuak_t|mBa4K4fqoW0fXoy?t1#bgLlU8N#28i zC`Qw@>MOL0)8kOP7c8ykqLt_3Hs0`X@*Z0}wV}4byi4vJ!mMr$3nDHl@fF-pSt877 zJhG82$DCTi#X*^z1J{8wmS)DMj1q#4ijq#T-8MUH2dZ#m9_(D+$4s`vMbi;FQ2RVHFHZBx(Wom!vx(g0)5_hkh!(S6S5jM= zNcq@>XK}G{#f!rMaNlTLBt+0E@kHY88dZF2ykZwrTAQS0OJBd8rWN(as)W=HQJMN| zM9Izlt3$P>d!j>;&}wT;!U_*aFuH(~KqkI{yxNrT15XP5qA1{dB>BjL?K9#fts7xL zjNLDzaW_b*^0t3|nfRYj{MPphIpTLFN@bjd-uT;YgjSBH3lblG1& zOpx1!Oe-r7>FxS}2#VgXyd97Qx5qwG&&*jLFd(Z3WD|eu!cfM&FNHBsNftZZ|fMfm~1+t4sMX-rL0P+AK{-!}? zBb;!Q*z-)EGeG~0o-!MG7bVUNm35ZT_Fd{24 z`yw(i3C4B_u8y6PfTT!?F5W3Xdbqv854gRNQnZZ+tJhXB=euX9#B6XACde zX9Q>rei z|L)VYBI7hu&ul>K)uO4sWx=yLfL!selpkreeaOfj_%#ErHDk}O zY4$0JTd?jpIrG>}=8<8kLZM-Y8%X8zIwh&EvQ@3>$3{f$5N9!*Y!%(v76^drvCLVN zJa3!W?qn*YiY6pnQHWh~m>10d(YH6i=0OjH)wyC!HO#ighxZKsEu5;u@v#-##>mZ% z*a))};Ze_t7O^YWTi5TW-&NU4-dAaE<<#Y%tZl`!LwHq8=+kgSK-ocU&KG~lGFygQ zc37yNqCffeLaEE$>i@&b_s-4Np3zr2Gvj>q^@t<5t=u_X^874--1nI?M`6b zj1w-kft7flsUfF~LjJIvw)i8;2Dhi%b>pB|Kh2Z2U2Wef_s6*L8O#H9YKNpj%n7lq z1a$OyF~fsS@)-E}=X}8l(z{bo@Ex*EMbJo`e&Cl{W=Dw~*ulbu2#-(5;V7e`plit2 zc=!k8OV7>Fe}= z2-S4zc5_M~MXjR0K{ScZZi%%Qa|6$Dlx|5K$v@3WL}-QDCUvvfTcQ~CD%yOfuW~}# zfnL87&uIgHE#T4Z?x#%-+k|<`}^*?@YER$r_&R`xI4EatV>RTasc)0)atg z&yw}621*q}eC^Ndv8|$7U5{gXWVvCTN9%hC>s6vLOQ9)pfO9}p)hu{O37t}r_#h45 z^ET9%eNjX?p+u1e&u5Kj;jxyW{6W_gCkMRR>IUxTL{%egGeNE;NP)^7qb3H!?}iCr zt+Md1FyKky#P)QP6A30m`!|+0vr!4|27=DPl(wtKS(JnOI?3fhuog#W@Y1VqcQsr| z6>(ih15C}Uva^cStCgr_D%Dd1MQ2T|F&jnnG}TN6>zc_$?pO*q;`Ma#rL;7PV)0c= zuyqqAh`P~MQq85Zn9Da(A!(lE)sp1r)XgjOl&{=arYwh`BbQE+X;gBA|0-DNW#Gk?%%P5Y2_>)B@fsqT*X zS#BVmVvkB(Sc=UT((78=oUwECuQcJt9{^w4-#+|f#Q%+vGyk`d``eW6t)Y$i2RVBt zzt2Ym2L_-6J%!ei#3W*>qlYF2s{jLl|LX3O?IR0Cbzi~mq@!1_-+pChSY&J0F6E#p z$4OyQ?C4PMVQhDy*KWJ3S8FJ7$m?V|Opy)o@Rstq>(}9T&3jtlmO4IHzyoQ*N-5yI z6YAu1$=*S6ijJ$S0{yuh&>5y9gzT zj~R}^NxxNACAnxRxlXfHVqII}5Ys`wRa~XHs3Zwu(>MqPC#D7Z@2E#i-yp?TUZZ+{ zAB5&m7<0g58h+zY7~+TAW4tj6-i8#a8%p%D$%^TO*`d6l4VLCq0PcblV?UP-(grue z`5OY=`*aVOg6FNXqEo?*mssEnmr({`5wVZz!JU>_unQMhlpooYt?!+64x$DL!Ht)n zVRpf#IW-Pr@vsg}5jDGvi3*uo-<4xasBGNnW z#LD0LFlQWOi8Nx%-;xGHLky7IBw=eGOoJQWl0_{W)s0jJ zYs6GS{-Lq`#34wu1aacB35}1lz~poDn>B&Vf7s(;YRC z`o=35+dYS@N8P$lDeztQ05iX0>af*Cs*>02k6HE{=B|%M!?J%d4Shb-sIYsFLPcimm02+5bGuUSO^Q{F}hOh*{ zR8yK$vp;I_1Mm!I2CBa8rqTRRm~uOmDk*I!cVKpM!O1T75vGUJQp)n&ePmPw%92tk z5{lV2p}}(Dx1!DD2@d|~m*7LR99^C(z;8j%$mT6kn27mh9y}Qiept~n8?mc9F|pC? zZJ6p%fv_#dYQ9DyH6ka$R-KmJbg>5br66Z8F|Z3dU~;iqzPVqwGP|v!9W$7*+&r18 zIOOW|Xz?n&h=XYiupWhh=O*GH$Hm&Pt#}+Kt1$NrWe{q0b#{-0^cz)J^KeIL|rPmF4^5{3)IW2SP=M3xp>+!UD z&ST)?Qb3@-u_8H*xDG#<<{>#ETUkDUOPFm(dDUgHCqgdaUPiMb^|0MtYjWINH3N@2 zO-2Va;Swpz8ZvrM6Peb?%K6m&^NoMF&m0WYogII)o?dsa(SW%fcymG+B`!|XW*Doe zV?jZBvNr>2t2X)$z1!F890$f(MZ|UP&R^C)n6#X9J-3u_C+(TayYaYnR8x=F^q|pP zrla`c{VdJt0OjDQOl%ztsH|l9mLz!c*-G`)&n@7Kc_|Yp6&BIZe7p$aIwRpD+i3?< z=~EOhE`v^?#n>HY-BOqkgqJ{%xdcyUab3b3LzgEHL-b-#Dd7*KLpvI%^>R^oEG z3YF)P^w8p=DYJJ@;&I96z$posvWb3RWNNSycE}G8&e~In*DE zgR>nZ8I0sS@O~~5df zDzB@Yj^ABk2U1~UBiQvHbE|pwt%aIMW3MEUqO2{fs zjbzqms9$Bwex2Hsw#f{<7=~w`oVPr(FJ`hJ*ToWYlx}{P;?h^cG=Qz7h&#$jrcr2h zZP>SQ^CO#`U5?|Db)i+9DnGsh`5!TqsTm+5R=*uBi`%79?`AibKi=d0+)!jVQnK3W z$!oF3f3tAZ-+Z{)(E4+izLnvIpT}(cbubgypoygGtXK?f~lql^J8QU4nCLG6R7?DPB4c!@8tX9G%GH< zZn!m>Id4^ArFjtw1TDiis?UXqoXS>b(Sgo_kvr$#E*4_c%~|Wsxo-Q4ngQOJq-S@% zug*!%IQeXc)&gNQ&P{F5en8RA`Ss9?M3cJx%iM`lAdc}7^CeKxd|_+%v#PNCaJ(r; z&UX<5sfNg&H!cQG7`fw#BqOu8_AzhnvRynAF;AaMNAHqZFnIlIK-Va0Pz^RdP~I?- zb?y`{<5e!?%F#PuL>#Wi?)ApjGd|Soi0X00ZlmR{R;Z83ZeEjo3~*ehp+(-a;(?%D zFe2~>M0Aa)nx9s=V4*iyBx-vR_zIC756a%3UHIAx4Z8ZXtw&w-9s6?kqwz}0hd%c{ z{(6WBJ5}exav0S+n`R8iDa|W0zszMj$|f*5pGISOYSx8*^y;Hl)1+bAJ#2Pr1?vnI zi&4`DZxr!_fRSo6^d4=+y8`F-aw@q~*HlLgaQ>>x2?F~K*ZMnC#fYi<#+3_OE~6F}_rYfd|m017{@Nj~9dYgu4t9CrEZ1sr9=xt<3xBi4 z;KW8AYN14NiGqJUfpKAq>D-ao-u3wqXCJFq6T?|WktfIb}l4$`C9%UhT6@gLb3&!@6(j1RV|mJcIL#&@MjAYW1t) zu$o~a&6$opWBP{MO~ytwcktVLCP%s1SgYNXo+LwG!$=`#y|IGYP zmOUE$H`zQq7?>mO|0X`L{y#~uH#hvh@j*lTUBfy)9eq0?zh_b^Uoz#U?J`GO!o9Pi zd>kXC15pDSk*@rOck1tk9=&p#;x-ZDH11_22360 z^j8CMGavcV8wmVZ!|0WKURYueI0n>}|iCV%$Cp%rV4-a4@Tx5aBa0!3>NId~1h1#8$T*8oH8cVA< z5>hdA-oV_{c zD@RqCp)=roQS;&Qz1}z!vtDzmf8Sx?saHGjzJnche+;GF+b>=AybI%{d+9nUlWOHH zm8O34oF8k?KP9uTknLv;e6g2;p}{r0Ym-kqYBpcI(ZgO&SW`q@AfL}!3My)2AV1ka zDk5*@nt6ZjDJa+gY|0vt?db(ZJ-X*nY|%nU+ZSL!BV?f-?)r4*S=@tpwH~)JnW{aq zHE6#^fK^?XnVvW)vq7PQFB)=&Tvsg5_DigVZPf7Ur6=XOR&KuO6bFOCklKB3&_oSB zH|PTpuzQm2?a%e7`cqob!?C1~E%nOL#Vqn~Hx#O<_fl`Xn#$i)hKQ}uXL z$8vD9wKzSAQ#nB^&)xwvamG|TAC@Es2 zwS|YIm29soDv^TO$|B@Sjhgq=hp_@;9eWyfwuo6GSwBpmS_k3RaJl)wzD*?X<4)2jBTK_9OGqyOSOX|FGCS|IwWY5Na{s zvn>8t<2N?!#_h{cm}1mk%eov@sN(U$kA6uKaSR2~EG!I=casp+_k7L`-649HDc{T$ z8;6y~hnMKxP?5Q41#4YJ{naTx|!q#1X%GD6}Xh{SNA#we0>9420X6c;35_ewwxHim&7H#37Kr zorB1asZC{?;S;wlFnc=cp^L4p?&)OZ_qb5Pj;3-B>E=}n#|v{6mNitp=vlt1r%G`! zXNO^9botrwcU<4J(zdJz&X`>I5`X4PP?$?_0<4QA?vK*!R{OmvSlUXV0sPH+XMigr!7yY>)jdqTt-a-_WEs z`qY(XH9|UDgd}aSEvI84QgtPBH&HZbUA4dwgcXR8UR3hRN6aHBPfTuN{YT0PSG&E$ zkDPswPN6)yk8aR#5{6TRsH9#d4g`>9aXlFehgNu8Uj!jG zq-Zr$HG+B67zJ$ySa{TN(N#jOBE^nXCzl>?ddC-o-pTJu1M}h%{Z5|QzgT2E`B#rD z`=h5r9lS?I@jvR3J^!5pb*^5J%MsmF=6|T%RM{&(q_-1}ot%p)r*rfbkbq1T9$A_- zqedX}sFWg*mQ;Ia{VJaE|o zrS8AXg=f9X2B_G8w^SD zk7GG3NT{$0{Nqhop?o4;9Av%3P!4~3XoY-4?wSBhj+Dxr#5&DHpwl^VjZiAqlmY5# zG&A~j!3DYo!@A-Sa^``8Qasm@OzlHSWy1#Ui}s!fgm!We^;Y!rtq_E;R zq1?)Rrf3XA{%8QODu>9xi4VAlv?G7D?~)7VgTlV9i4hpW&UDEvI&eaYA_mJ0NhKK+ zC|ljJ-_yM;55X+|PVS$v#*D(g0S$>}V)72RekY^esXd2L$$1fE>AsuJkqC|h-^Cj3r0cK<}qF9Nw<)-OVJ&-%P0GdpL`&*FPNr|Gfr5LI1;ePGnw zE4zcltdTF+^WcKTLYWYiGGueqMH)0YN`EbJ8lh=nrq?mxUe5?JOj`BSSG?b-hPR~B6xcgXt`0AdebSU|l9}yWTH1bNB_UpS;s`SR;y6ek~J$_uzQHU z+NW>q_?tQ%FRCuR~foNV?m; zlSX4Hpo_~Id1)P*&}J(KN-OwIPnqzsalloFo;=_@p<*@IUet~{zmD4mg#XT*>4of= zB_mxJAauqcA%uLG^-7(5YpG4N%S6adzfdN)IjI=IN2@8SKBzoQR+rV0r0xqu)3)6? z%u7{V36_oT9%Y=9&&r1zn~Z@n#6*mp7wVg9;_TZc5J1qKmNru%#b+s$z#VPjvKXdM zI|ly1@0j5^(Rhxi&%VW22=O?*=~q=>wdn9>wNp)U*l<@EBSHylev6dJYB|GWIoAMX z+OTaGFw1;{V3DEHs-0ZhgJy-nno_sy!607pP7T?(|O~c{GH4Mb- z;0$_hXg&KtKSgwCe*DVG-I>YU<3~8F$;Mtx5oWtO=2T%tOTAsLP`#xx>CUyR$1CV| zNmmh6Uk;GQeX41ACO$fC>(@j211f7ewMiRre>;3M$%J91RWzv zlF%Y{XX#1PbI18bJgfl_ypm&OgLV@TEvXu}L9^0fbTtLAfqfen(h=BzHwxOIyCEc& zt%u!Qw*~4z7i}}dn)l?5V`g869?Ef<$qQGib|pykW@&iHA}9`>s&&ADJ@IYU?%}S> zuAj3QRbCS`Xa|xJYzR;f3SFj^j2~AA3>H961XS*G1QW$#i4o+Zv|+zUKDeT{PucYe zVX0BD-R;UDs1baESiU>x@C&2Vpd#%xHdm0}! z+|O&{W}|8pUZ_ZPkPYhC3%pc-RGIg=1~}zjx3Ea)T2f5_#CS7rxY|}1KS-B$8%8{;*BwV+5*|p>`gk9L;#pu z&jG|0aX^sG{<=i z1>IUf*LA!Evxpf2af@xcj)wWmA+y}kYCn;*C5yr;H-%5MYZ4ae5L0`;y`YdVlOH!s z;hxYN4;~kiz#Kuj=lu{ZG7v47rJ<%?HYTPn7$_Iw+nP;}-3*LeFzB=EJ`~pojWh0W zM5EpI6l&S!VK8f}`Rqf{2K57)TC=*Kf2`RAoVOwVT|Q+g%1RX zNt{P!vM8ieRY5G{f@t$%YO(^A*ZN{d?l_dK{2QfXrf*%wrLm`XsZxnEgEQ<40{G~h zkrwIerw+%zRMyFLHtqIq3ZF$ARo~>9gz|`zyLAasoOStfZHIrzQ0SB6`jj8fP%e{$ zVRKKFbA+4y9;d!Ai~r=6Ky%-ZO2LD3dUXEae7E;3Zx^+5%bE-W^!xLxq?8R=b#o%C zENam3jon16gk6wY>Fb4_&3y#%iuLRa1KeJ zmB7FWCaenU9cQK)iIu0Z87oh{Xh z#$6485#w0!xXx{NQcU}u;F1Y+i=;&}JWzF+JM0v(->eRWf!M9gA4l-9Q%Pe+6bXvJ zOj94?n74+d#;?XFIwLzNct0Fd^u}>g^rmjd7ZraOhBEJ!zO?VdLYD(}gVG@mFN;pw(=8AimNHg_Mg30==~G`z^dC<$SQmsJ z$_E^`-1++H<@5W-a7uDi!-Hc(GM=O(t?XG%=I& z1UR`3PA0ZERBmMxC+LD_vKyzzg~%#L<4*4pvJ#O@)rPV69e*4ycKE*RrvW!kPxl< zl5AnqT6EHn*`~N&5;mvIT%n@_m@41qvZS==a2Goyp#oCvJ#4II2%1W*Q6dh5w0+k?*@u~R|m)bW<4gKU@hHL%g{4rYXagDn=!wd~M1U%gD z1o5QVE)*Y)G2|zBw*PodR8l*lk`)jE(^RJ7U~AA7fBp9JL8G!7NCvNi(Q(&@YQ`w$;0s6CVlL}N(Q`2A1P&wIp zXgr$J9r@iF@GC4j>_J1-el2kqo^FikV&lZcb3XAxH#8~ZUhjFX5tw1Df%A#=3C0cQ z?s=I!XrC(mvVd(bY!3>oXMKikD=2!%f{#YFVr1ShBnp4x+z>LU79I7_vUhckKe4o;i7?qiu%9=P_?qh?W(YK7~oS&@qPQVa_s7rl`^ zj}9#sjk&574qcdW_CbA&##vxsRqIDsMmZk-v z&MFp*ijMKhO-7~p;A?c{lb^1OJ>b&8?+Y{4J7glAgILeIsM0OA3A(b+(w2RRKh!R+ zJ%r?R($2#=4;gnLtxTb6b5D2z>lHmey0$j#5njsmE`|l9@qjVFf?u7!l4NwQ7OlX` zP~=Rq))b9Zy-{UbWR?^unjYLLoEB&O51ppr8~Mq(*>J`)Eg5_w>`K9*uHGDK6@mb z>3hIKYzRxhV=a^x1UvWZiiQAm9p+D(od&Z+6E7Z;M${X+AZc-)^H)4+BnO@g^$w)ub%C?$F3%D>xnS0~#Mz~~LlC?MMTWa~{P%|u?f#vf{!xP%d7?UcqEh|Rf#~3N_QKu|ynjF|Ktd|b z>tBmMg#X12|7`M0s8STy*<=MTjD)nlI+6|&W%E+*lA3{_&Z?SEeGRMhPEwEFR z&&Acxu#-wmWL-%7iE(Qul`xeMK#b6kybZ8M2(Kk24Z}li)}b#Verd72!%4!$fk=sl zs4PxA9xk4U3HMAql~}hoSXd-Y;}6je+7x_!+J!YN-v9o6WkE$|w#m9s90$BXZ|p8xgMn>08^eFs;IW|7DD?}m z0Mu2Xj)aqhFd1|u{39iAnn#Zw0Zg^Kbn!cgb0K;J3B;z5en_>0&ppX=@&`w6gOs$*0v*ksyIR^mR~MHWHvG^pRgS;9 z&^&+r+>cVgW#?)WoKkDoulP74#h$l8u*@p;g9UgUgQcL{sxG5OQ8KZE@QR(0W7RNT zqYA&Urm%>vObFn+b#ppBHeo$FLH-h>s9UgdB9(Ym)$Xf2m$lVtWA8(;5$8T+LmI12 zuDT4%Q3?<30*(x$+e$)>O_vBI~G6)p`*<@`)*2`P! zP<<)|POTtgLUu@Po2H0}qNrhVC*#y7*7pwOF580|yN8`de?}l(;s=9+NaA;_2tLzZ z6MUwV{~;5$u}0@Vv`1U{6FT(GJ0PXtXTPWrffTmiF4gmc6&U$S=hQ@h?^L;{xSJVu zZcwVoK`y_VTy=z~P*$1;wAGGn51qCR&rs8Ii!P3mFs~@m&Qz#1qVie&1*K>(;ju8* zjb!spq)^83yY=`ZkI0=-mQ`*^q;*bO8{D;rE_mNRDBK^v;k;fenWXhEdO1Y|c&;rv zova1eLuoUo1-8Fd91=PHsHl0k&&>Y1DrknzooIYrE90w52<{2t=LMjF>hhZp?S0zr zlO-T`;W9rQvAvrvON^%vHVgXHo-=narX!6I;)-H5!Rkgx*=WdQscNJFTC#$e- z!9$XYjFtqO0!WtK(bV16(~`~B!PMH4-PQ?cZR>91VFq+_GH182b+t5icXGA0bYpk5 zw6cf)!e;K|Xk}{+eD3Dt7`?rvp;nBFhk>E!18FW%#L&S|)Q4C?d3R5gLi&pti zn?#m)1tC2>CEgSvEI5gkA07=JvsC&FTrro(z__uzo6UTh|0Sif5q{he2>&oBaDmM~ zG4}F~1wG;IkEu=CdZ9ngmU199gE5sndm-M#A)`$KR0`E#3_s2O;5l7 zZI+658l#}eGVRK<5Kjk(1z$uvazTF-q-XKrASNRjAW??ur6TJxBPf>;a$?eLww2$t z{8nOA&*YwiPxus|)d%q9wdLvDGRkN@OdU_YsXU$N%ctv&EpjyNn`rMP2RXlbUTcDc zIFT_qm8r>Gcem`cMHj=3Rw=sr@X1ILK_yzpGLDKap%K4-4f>0PL2S$5A0^=rd0U=Y z-5-AYK=@z$&2Rrd7W}_%Qo!cnYVTH|_c3vu1-E0x>mGDe$nC3V)rFPuDj|an3A%-i z^SQ_m+eirgPc5F3TIDm|uX0I+tu^kIU}LDhI{6-`0ilD4ho8s}O_>VRjR6q2Z&IAA zc%;~b*J6^#m2?f%H}zvsjZ#_l&jFmQ2qQ@yel>a)!VSG1FgsoJs3Jr!RUzw9@+ z&wP|9N!sFvZB7#B`LIKzscRPNW6ylqSyF`!IOi;q=gmWV#VLPF9>1a?rX*P^+j5Mb zuY;uxCq1~j9Vm$;E@59cy(G^TPR^&AL}h9DCJ#UYO94(27()iZnEcfhNOrn~o8K`JjPNY0WYR1$j6Lb}H=#jUWeED>*%O9zcgyvhQ8lLWx zNNF2-Y6fGYu;ov*rjl;s#AfBqWlvcqiwmpWNp~^|l=zbL-xDc19-Yt+GNy&}@5i4n z?tRea^q=8lMff(>Lqygxss2U8RKO8=c7)&aYH^&@MB&9To!-n!-@=H>BgLb86*S@J9@lTvSI}$u(2P6|r(-S5 zv6w;qNdn=96A_Ryl@qc7v67lWB(Btorjn-RnYoElU%%fr+(22K7A2nZ=B1^|SO1}d`>NN#C8n5JBzu6{yI#cty|_V=vgzjXM%xbD*8e9X$Y8 zVKi%9NkduzbJ(sH9q4+ya0g9^hs8z-Tjy@wPi?i6aW}0e7(Eu8O{Bb$3Z=e$pA#3m zda?t-N6S57iH;TsMw(>3)L;%=p6QYN?1rHpBkB}W;i&ngo}mBr?BoU}MG)twnAo%6 zPZdPZ4u7HF`A@jdK4wg5MfHhVYp*t75-D48mf@Hl5qEDmLl?06Vwq1o@&{a=h)jGtirx(@|2KjBpMk~I(hYvo4|7X5q<;l!cu+!s z$LN2pe**R0EjZw>|CeGH(Am|=+0xY=o|u#d$Jm*H%!;hzLn_0=ZlemUz($o(b~ct# zWwuGB(Sd0gaF4-(K-|fwwIY{{+s$yMccL{&r%&3%qUa^(W8(o-h9f{OzlF7kJyr<4@ok zd?@E{z~2sGcY(Kk8~y~!(Ec6x+YaO|@U~;YuW$1|tC;@p!2g5!!`%&TEBk*LAN;d! z;NQHz9fCh?5bkns3)}xZ5(12W=l*sHxC^{3O8yg=!1QjQUzw_6&20txm04*cEGxeL5quKWq4*8e;3cVFZ# z@OD8U_0PW_{MpNA|6gjJ+Z+C>Gj5-h|Ee?oS)oR^HoR4J+$Q`=fVZ>nuaozGmb3BS zkHMcA_t&ZXUG^{5|4zGqM!W&@{}=XuviaQ&ZYR}W+54ZxYVlv(;8reIQ$m4fM+Ah2 P@Sjw8TC0LH5D@+sLxn<5 literal 11811 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 P@Sjw8TC0LH5D@+sL_R_v literal 11811 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 b/run > b/publishLocal -# doesn't pass yet (missing exclusions of root deps in ivy.xml?) -# $ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-exclude-dependencies-2_2.12:0.1.0-SNAPSHOT +$ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-exclude-dependencies-2_2.12:0.1.0-SNAPSHOT From 8aa40d1f21d3e47ee917d45f02e35a88c47ab3ed Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 18 May 2020 19:42:37 +0200 Subject: [PATCH 7/9] Move ivy.xml generation stuff to lm-coursier So that it can be re-used from sbt --- .../src/main/scala/lmcoursier}/IvyXml.scala | 93 +---------------- .../test/scala/lmcoursier/IvyXmlTests.scala | 27 +++++ .../sbtcoursiershared/IvyXmlGeneration.scala | 99 +++++++++++++++++++ .../sbtcoursiershared/SbtCoursierShared.scala | 2 +- .../sbtcoursiershared/IvyXmlTests.scala | 30 ------ 5 files changed, 129 insertions(+), 122 deletions(-) rename modules/{sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared => lm-coursier/src/main/scala/lmcoursier}/IvyXml.scala (55%) create mode 100644 modules/lm-coursier/src/test/scala/lmcoursier/IvyXmlTests.scala create mode 100644 modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXmlGeneration.scala delete mode 100644 modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala b/modules/lm-coursier/src/main/scala/lmcoursier/IvyXml.scala similarity index 55% rename from modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala rename to modules/lm-coursier/src/main/scala/lmcoursier/IvyXml.scala index 9369a42fd..b55dc1d16 100644 --- a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXml.scala +++ b/modules/lm-coursier/src/main/scala/lmcoursier/IvyXml.scala @@ -1,21 +1,13 @@ -package coursier.sbtcoursiershared - -import java.nio.charset.StandardCharsets.UTF_8 -import java.nio.file.Files +package lmcoursier import lmcoursier.Inputs import lmcoursier.definitions.{Configuration, Project} -import org.apache.ivy.core.module.id.ModuleRevisionId -import sbt.{Def, Setting, Task, TaskKey} -import sbt.internal.librarymanagement.IvySbt -import sbt.librarymanagement.{CrossVersion, PublishConfiguration} -import scala.collection.JavaConverters._ import scala.xml.{Node, PrefixedAttribute} object IvyXml { - private[sbtcoursiershared] def rawContent( + def apply( currentProject: Project, exclusions: Seq[(String, String)], shadedConfigOpt: Option[Configuration] @@ -34,38 +26,6 @@ object IvyXml { } // These are required for publish to be fine, later on. - private def writeFiles( - currentProject: Project, - exclusions: Seq[(String, String)], - 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, exclusions, 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) - } - private def content( project0: Project, exclusions: Seq[(String, String)], @@ -168,53 +128,4 @@ object IvyXml { } - private def makeIvyXmlBefore[T]( - task: TaskKey[T], - shadedConfigOpt: Option[Configuration] - ): Setting[Task[T]] = - task := task.dependsOn { - Def.taskDyn { - import SbtCoursierShared.autoImport._ - val doGen = coursierGenerateIvyXml.value - if (doGen) - Def.task { - val sv = sbt.Keys.scalaVersion.value - val sbv = sbt.Keys.scalaBinaryVersion.value - val log = sbt.Keys.streams.value.log - val currentProject = { - val proj = coursierProject.value - val publications = coursierPublications.value - proj.withPublications(publications) - } - val excludeDeps = Inputs.exclusionsSeq(InputsTasks.actualExcludeDependencies.value, sv, sbv, log) - .map { - case (org, name) => - (org.value, name.value) - } - writeFiles(currentProject, excludeDeps, shadedConfigOpt, sbt.Keys.ivySbt.value, log) - } - else - Def.task(()) - } - }.value - - private lazy val needsIvyXmlLocal = Seq(sbt.Keys.publishLocalConfiguration) ++ getPubConf("makeIvyXmlLocalConfiguration") - private lazy val needsIvyXml = Seq(sbt.Keys.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(makeIvyXmlBefore(_, shadedConfigOpt)) - } diff --git a/modules/lm-coursier/src/test/scala/lmcoursier/IvyXmlTests.scala b/modules/lm-coursier/src/test/scala/lmcoursier/IvyXmlTests.scala new file mode 100644 index 000000000..ad0ae6818 --- /dev/null +++ b/modules/lm-coursier/src/test/scala/lmcoursier/IvyXmlTests.scala @@ -0,0 +1,27 @@ +package lmcoursier + +import lmcoursier.definitions.{Configuration, Info, Module, ModuleName, Organization, Project} +import org.scalatest.{Matchers, PropSpec} + +object IvyXmlTests extends PropSpec with Matchers { + + property("no truncation") { + val project = Project( + Module(Organization("org"), ModuleName("name"), Map()), + "ver", + Nil, + Map( + Configuration("foo") -> (1 to 80).map(n => Configuration("bar" + n)) // long list of configurations -> no truncation any way + ), + Nil, + None, + Nil, + Info("", "", Nil, Nil, None) + ) + + val content = IvyXml(project, Nil, None) + + assert(!content.contains("")) + } + +} diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXmlGeneration.scala b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXmlGeneration.scala new file mode 100644 index 000000000..35c681662 --- /dev/null +++ b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/IvyXmlGeneration.scala @@ -0,0 +1,99 @@ +package coursier.sbtcoursiershared + +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.Files + +import lmcoursier.{Inputs, IvyXml} +import lmcoursier.definitions.{Configuration, Project} +import org.apache.ivy.core.module.id.ModuleRevisionId +import sbt.{Def, Setting, Task, TaskKey} +import sbt.internal.librarymanagement.IvySbt +import sbt.librarymanagement.{CrossVersion, PublishConfiguration} + +import scala.collection.JavaConverters._ + +object IvyXmlGeneration { + + // These are required for publish to be fine, later on. + private def writeFiles( + currentProject: Project, + exclusions: Seq[(String, String)], + 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 = IvyXml(currentProject, exclusions, 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) + } + + private def makeIvyXmlBefore[T]( + task: TaskKey[T], + shadedConfigOpt: Option[Configuration] + ): Setting[Task[T]] = + task := task.dependsOn { + Def.taskDyn { + import SbtCoursierShared.autoImport._ + val doGen = coursierGenerateIvyXml.value + if (doGen) + Def.task { + val sv = sbt.Keys.scalaVersion.value + val sbv = sbt.Keys.scalaBinaryVersion.value + val log = sbt.Keys.streams.value.log + val currentProject = { + val proj = coursierProject.value + val publications = coursierPublications.value + proj.withPublications(publications) + } + val excludeDeps = Inputs.exclusionsSeq(InputsTasks.actualExcludeDependencies.value, sv, sbv, log) + .map { + case (org, name) => + (org.value, name.value) + } + writeFiles(currentProject, excludeDeps, shadedConfigOpt, sbt.Keys.ivySbt.value, log) + } + else + Def.task(()) + } + }.value + + private lazy val needsIvyXmlLocal = Seq(sbt.Keys.publishLocalConfiguration) ++ getPubConf("makeIvyXmlLocalConfiguration") + private lazy val needsIvyXml = Seq(sbt.Keys.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(makeIvyXmlBefore(_, shadedConfigOpt)) + +} diff --git a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/SbtCoursierShared.scala b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/SbtCoursierShared.scala index 1f896c1b5..174042d62 100644 --- a/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/SbtCoursierShared.scala +++ b/modules/sbt-coursier-shared/src/main/scala/coursier/sbtcoursiershared/SbtCoursierShared.scala @@ -174,7 +174,7 @@ object SbtCoursierShared extends AutoPlugin { versionReconciliation := Seq.empty ) ++ { if (pubSettings) - IvyXml.generateIvyXmlSettings() + IvyXmlGeneration.generateIvyXmlSettings() else Nil } diff --git a/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala b/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala deleted file mode 100644 index 3085d849a..000000000 --- a/modules/sbt-coursier-shared/src/test/scala/coursier/sbtcoursiershared/IvyXmlTests.scala +++ /dev/null @@ -1,30 +0,0 @@ -package coursier.sbtcoursiershared - -import lmcoursier.definitions.{Configuration, Info, Module, ModuleName, Organization, Project} -import utest._ - -object IvyXmlTests extends TestSuite { - - val tests = Tests { - "no truncation" - { - - val project = Project( - Module(Organization("org"), ModuleName("name"), Map()), - "ver", - Nil, - Map( - Configuration("foo") -> (1 to 80).map(n => Configuration("bar" + n)) // long list of configurations -> no truncation any way - ), - Nil, - None, - Nil, - Info("", "", Nil, Nil, None) - ) - - val content = IvyXml.rawContent(project, Nil, None) - - assert(!content.contains("")) - } - } - -} From 123f40723d3866b7f29d4077eb125780f4441a50 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 19 May 2020 02:18:15 +0200 Subject: [PATCH 8/9] Clean-up scripted test Actually unused file --- .../dependency-graph/src/main/scala/Main.scala | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/src/main/scala/Main.scala diff --git a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/src/main/scala/Main.scala deleted file mode 100644 index 032874759..000000000 --- a/modules/sbt-coursier/src/sbt-test/sbt-coursier/dependency-graph/src/main/scala/Main.scala +++ /dev/null @@ -1,8 +0,0 @@ -import java.io.File -import java.nio.file.Files - -import org.apache.zookeeper.ZooKeeper - -object Main extends App { - Files.write(new File("output").toPath, classOf[ZooKeeper].getSimpleName.getBytes("UTF-8")) -} From 2c2c98669db698707fecc4e0a9aa1a970b5b57f8 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 19 May 2020 02:39:57 +0200 Subject: [PATCH 9/9] Clean-up scripted test --- .../shared-1/all-exclude-dependencies/src/main/scala/Main.scala | 2 -- .../src/sbt-test/shared-1/all-exclude-dependencies/test | 2 -- 2 files changed, 4 deletions(-) diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/src/main/scala/Main.scala b/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/src/main/scala/Main.scala index 1bc056610..89960e16b 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/src/main/scala/Main.scala +++ b/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/src/main/scala/Main.scala @@ -27,6 +27,4 @@ object Main extends App { !argonautFound, "Expected not to find classes from argonaut" ) - - Files.write(new File("output").toPath, "OK".getBytes("UTF-8")) } diff --git a/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/test b/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/test index 27ecd9d91..731ff86e2 100644 --- a/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/test +++ b/modules/sbt-coursier/src/sbt-test/shared-1/all-exclude-dependencies/test @@ -1,5 +1,3 @@ -$ delete output > run -$ exists output > publishLocal $ exec java -jar coursier launch io.get-coursier.test:sbt-coursier-all-exclude-dependencies_2.12:0.1.0-SNAPSHOT