From b7ce6d042f6524f80c83f562462fadee28675ad0 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Sat, 3 Dec 2016 02:45:23 +0100 Subject: [PATCH] Tweak SBT boot jar handling Fixes https://github.com/alexarchambault/coursier/issues/402 --- .../scala-2.10/coursier/SbtBootJars.scala | 18 +++ .../coursier/SbtScalaJarsRepository.scala | 114 ------------------ .../src/main/scala-2.10/coursier/Tasks.scala | 41 ++++--- .../src/main/scala-2.10/coursier/ToSbt.scala | 6 +- .../scala-sources-javadoc-jars/build.sbt | 46 +++++++ .../project/plugins.sbt | 11 ++ .../src/main/scala/Main.scala | 6 + .../scala-sources-javadoc-jars/test | 4 + 8 files changed, 115 insertions(+), 131 deletions(-) create mode 100644 plugin/src/main/scala-2.10/coursier/SbtBootJars.scala delete mode 100644 plugin/src/main/scala-2.10/coursier/SbtScalaJarsRepository.scala create mode 100644 plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/build.sbt create mode 100644 plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/src/main/scala/Main.scala create mode 100644 plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/test diff --git a/plugin/src/main/scala-2.10/coursier/SbtBootJars.scala b/plugin/src/main/scala-2.10/coursier/SbtBootJars.scala new file mode 100644 index 000000000..209ee390f --- /dev/null +++ b/plugin/src/main/scala-2.10/coursier/SbtBootJars.scala @@ -0,0 +1,18 @@ +package coursier + +import java.io.File + +object SbtBootJars { + def apply( + scalaOrg: String, + scalaVersion: String, + jars: Seq[File] + ): Map[(Module, String), File] = + jars.collect { + case jar if jar.getName.endsWith(".jar") => + val name = jar.getName.stripSuffix(".jar") + val mod = Module(scalaOrg, name) + + (mod, scalaVersion) -> jar + }.toMap +} \ No newline at end of file diff --git a/plugin/src/main/scala-2.10/coursier/SbtScalaJarsRepository.scala b/plugin/src/main/scala-2.10/coursier/SbtScalaJarsRepository.scala deleted file mode 100644 index 08f504539..000000000 --- a/plugin/src/main/scala-2.10/coursier/SbtScalaJarsRepository.scala +++ /dev/null @@ -1,114 +0,0 @@ -package coursier - -import java.io.File - -import coursier.core.Publication - -import scalaz.{ EitherT, Monad } -import scalaz.Scalaz.ToEitherOps - -object SbtScalaJarsRepository { - - // Will break in 2.11, where scala-parser-combinators_2.11 and scala-xml_2.11, with different - // org and versions, are thrown into the mix. - // To handle these well, we would need to fetch actual infos about the scala-* dependencies - // from actual repos, and use that from SbtScalaJarsRepository. - - val looseDependencies = Map( - "scala-compiler" -> Set( - "scala-library", - "scala-reflect" - ), - "scala-reflect" -> Set( - "scala-library" - ) - ) - -} - -case class SbtScalaJarsRepository( - scalaOrg: String, - scalaVersion: String, - jars: Seq[File] -) extends Repository { repo => - - val foundNames = jars.collect { - case jar if jar.getName.endsWith(".jar") => - jar.getName.stripSuffix(".jar") - }.toSet - - val dependencies = SbtScalaJarsRepository.looseDependencies - .filterKeys(foundNames) - .mapValues(_.filter(foundNames)) - - val artifacts = jars.collect { - case jar if jar.getName.endsWith(".jar") => - val name = jar.getName.stripSuffix(".jar") - val mod = Module(scalaOrg, name) - - val proj = Project( - mod, - scalaVersion, - dependencies.getOrElse(name, Set.empty[String]).toVector.map { depName => - val dep = Dependency(Module(scalaOrg, depName), scalaVersion) - "compile" -> dep - }, - MavenRepository.defaultConfigurations, - None, - Nil, - Nil, - Nil, - None, - None, - None, - Seq("compile" -> Publication(name, "jar", "jar", "")), - Info("", "", Nil, Nil, None) - ) - - (mod, scalaVersion) -> ((proj, jar)) - }.toMap - - val source: Artifact.Source = new Artifact.Source { - def artifacts( - dependency: Dependency, - project: Project, - overrideClassifiers: Option[Seq[String]] - ) = - if (overrideClassifiers.isEmpty) - repo.artifacts.get(project.moduleVersion) match { - case Some((_, f)) => - Seq( - Artifact( - f.toURI.toString, - Map.empty, - Map.empty, - Attributes("jar", ""), - changing = true, - None - ) - ) - case None => - Nil - } - else - Nil - } - - def find[F[_]]( - module: Module, - version: String, - fetch: Fetch.Content[F] - )(implicit - F: Monad[F] - ): EitherT[F, String, (Artifact.Source, Project)] = { - - val res = artifacts.get((module, version)) match { - case None => - s"not found in internal SBT scala JARs: $module:$version".left - case Some((p, _)) => - (source, p).right - } - - EitherT(F.point(res)) - } -} diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index 0b082a731..8f681ff8e 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -435,17 +435,6 @@ object Tasks { val interProjectRepo = InterProjectRepository(interProjectDependencies) - val internalSbtScalaProvider = appConfiguration.value.provider.scalaProvider - val internalSbtScalaJarsRepo = SbtScalaJarsRepository( - so, // this seems plain wrong - this assumes that the scala org of the project is the same - // as the one that started SBT. This will scrap the scala org specific JARs by the ones - // that booted SBT, even if the latter come from the standard org.scala-lang org. - // But SBT itself does it this way, and not doing so may make two different versions - // of the scala JARs land in the classpath... - internalSbtScalaProvider.version(), - internalSbtScalaProvider.jars() - ) - val ivyHome = sys.props.getOrElse( "ivy.home", new File(sys.props("user.home")).toURI.getPath + ".ivy2" @@ -521,7 +510,7 @@ object Tasks { } } - val internalRepositories = Seq(globalPluginsRepo, interProjectRepo, internalSbtScalaJarsRepo) + val internalRepositories = Seq(globalPluginsRepo, interProjectRepo) val repositories = internalRepositories ++ @@ -674,6 +663,18 @@ object Tasks { // Downloads are already parallel, no need to parallelize further anyway synchronized { + val so = scalaOrganization.value + val internalSbtScalaProvider = appConfiguration.value.provider.scalaProvider + val sbtBootJarOverrides = SbtBootJars( + so, // this seems plain wrong - this assumes that the scala org of the project is the same + // as the one that started SBT. This will scrap the scala org specific JARs by the ones + // that booted SBT, even if the latter come from the standard org.scala-lang org. + // But SBT itself does it this way, and not doing so may make two different versions + // of the scala JARs land in the classpath... + internalSbtScalaProvider.version(), + internalSbtScalaProvider.jars() + ) + lazy val cm = coursierSbtClassifiersModule.value lazy val projectName = thisProjectRef.value.project @@ -862,10 +863,22 @@ object Tasks { artifact }.toSet - def artifactFileOpt(artifact: Artifact) = { + def artifactFileOpt(module: Module, version: String, artifact: Artifact) = { + val artifact0 = artifact .copy(attributes = Attributes()) // temporary hack :-( - val res = artifactFiles.get(artifact0) + + // Under some conditions, SBT puts the scala JARs of its own classpath + // in the application classpath. Ensuring we return SBT's jars rather than + // JARs from the coursier cache, so that a same JAR doesn't land twice in the + // application classpath (once via SBT jars, once via coursier cache). + val fromBootJars = + if (artifact.classifier.isEmpty && artifact.`type` == "jar") + sbtBootJarOverrides.get((module, version)) + else + None + + val res = fromBootJars.orElse(artifactFiles.get(artifact0)) if (res.isEmpty && !erroredArtifacts(artifact0)) log.error(s"${artifact.url} not downloaded (should not happen)") diff --git a/plugin/src/main/scala-2.10/coursier/ToSbt.scala b/plugin/src/main/scala-2.10/coursier/ToSbt.scala index b29eb8156..a4a401e62 100644 --- a/plugin/src/main/scala-2.10/coursier/ToSbt.scala +++ b/plugin/src/main/scala-2.10/coursier/ToSbt.scala @@ -96,7 +96,7 @@ object ToSbt { def moduleReports( res: Resolution, classifiersOpt: Option[Seq[String]], - artifactFileOpt: Artifact => Option[File] + artifactFileOpt: (Module, String, Artifact) => Option[File] ) = { val depArtifacts = classifiersOpt match { @@ -145,7 +145,7 @@ object ToSbt { dep, dependees, proj, - artifacts.map(a => a -> artifactFileOpt(a)) + artifacts.map(a => a -> artifactFileOpt(proj.module, proj.version, a)) ) } } @@ -155,7 +155,7 @@ object ToSbt { resolution: Resolution, configs: Map[String, Set[String]], classifiersOpt: Option[Seq[String]], - artifactFileOpt: Artifact => Option[File] + artifactFileOpt: (Module, String, Artifact) => Option[File] ): sbt.UpdateReport = { val configReports = configs.map { diff --git a/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/build.sbt b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/build.sbt new file mode 100644 index 000000000..bb4dfcae2 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/build.sbt @@ -0,0 +1,46 @@ +scalaVersion := appConfiguration.value.provider.scalaProvider.version + +coursierCachePolicies := { + if (sys.props("os.name").startsWith("Windows")) + coursierCachePolicies.value + else + Seq(coursier.CachePolicy.ForceDownload) +} + +lazy val updateClassifiersCheck = TaskKey[Unit]("updateClassifiersCheck") + +updateClassifiersCheck := { + + val configReport = updateClassifiers.value + .configuration("compile") + .getOrElse { + throw new Exception( + "compile configuration not found in updateClassifiers report" + ) + } + + val scalaLibraryArtifacts = configReport + .modules + .collectFirst { + case moduleReport + if moduleReport.module.organization == "org.scala-lang" && + moduleReport.module.name == "scala-library" => + moduleReport.artifacts + } + .toSeq + .flatten + + def classifierArtifact(classifier: String) = + scalaLibraryArtifacts.collectFirst { + case (a, _) if a.classifier == Some(classifier) => a + } + + def ensureHasClassifierArtifact(classifier: String) = + assert( + classifierArtifact(classifier).nonEmpty, + s"scala-library $classifier not found" + ) + + ensureHasClassifierArtifact("javadoc") + ensureHasClassifierArtifact("sources") +} \ No newline at end of file diff --git a/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/project/plugins.sbt b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/project/plugins.sbt new file mode 100644 index 000000000..8d902e4dc --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/project/plugins.sbt @@ -0,0 +1,11 @@ +{ + val pluginVersion = sys.props.getOrElse( + "plugin.version", + throw new RuntimeException( + """|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin + ) + ) + + addSbtPlugin("io.get-coursier" % "sbt-coursier" % pluginVersion) +} \ No newline at end of file diff --git a/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/src/main/scala/Main.scala b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/src/main/scala/Main.scala new file mode 100644 index 000000000..3eeff697e --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/src/main/scala/Main.scala @@ -0,0 +1,6 @@ +import java.io.File +import java.nio.file.Files + +object Main extends App { + Files.write(new File("output").toPath, "OK".getBytes("UTF-8")) +} \ No newline at end of file diff --git a/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/test b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/test new file mode 100644 index 000000000..e82fd5a76 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/scala-sources-javadoc-jars/test @@ -0,0 +1,4 @@ +$ delete output +> run +$ exists output +> updateClassifiersCheck \ No newline at end of file