Tweak SBT boot jar handling

Fixes https://github.com/alexarchambault/coursier/issues/402
This commit is contained in:
Alexandre Archambault 2016-12-03 02:45:23 +01:00
parent ea5169cc0a
commit b7ce6d042f
No known key found for this signature in database
GPG Key ID: 14640A6839C263A9
8 changed files with 115 additions and 131 deletions

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -435,17 +435,6 @@ object Tasks {
val interProjectRepo = InterProjectRepository(interProjectDependencies) 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( val ivyHome = sys.props.getOrElse(
"ivy.home", "ivy.home",
new File(sys.props("user.home")).toURI.getPath + ".ivy2" 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 = val repositories =
internalRepositories ++ internalRepositories ++
@ -674,6 +663,18 @@ object Tasks {
// Downloads are already parallel, no need to parallelize further anyway // Downloads are already parallel, no need to parallelize further anyway
synchronized { 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 cm = coursierSbtClassifiersModule.value
lazy val projectName = thisProjectRef.value.project lazy val projectName = thisProjectRef.value.project
@ -862,10 +863,22 @@ object Tasks {
artifact artifact
}.toSet }.toSet
def artifactFileOpt(artifact: Artifact) = { def artifactFileOpt(module: Module, version: String, artifact: Artifact) = {
val artifact0 = artifact val artifact0 = artifact
.copy(attributes = Attributes()) // temporary hack :-( .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)) if (res.isEmpty && !erroredArtifacts(artifact0))
log.error(s"${artifact.url} not downloaded (should not happen)") log.error(s"${artifact.url} not downloaded (should not happen)")

View File

@ -96,7 +96,7 @@ object ToSbt {
def moduleReports( def moduleReports(
res: Resolution, res: Resolution,
classifiersOpt: Option[Seq[String]], classifiersOpt: Option[Seq[String]],
artifactFileOpt: Artifact => Option[File] artifactFileOpt: (Module, String, Artifact) => Option[File]
) = { ) = {
val depArtifacts = val depArtifacts =
classifiersOpt match { classifiersOpt match {
@ -145,7 +145,7 @@ object ToSbt {
dep, dep,
dependees, dependees,
proj, 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, resolution: Resolution,
configs: Map[String, Set[String]], configs: Map[String, Set[String]],
classifiersOpt: Option[Seq[String]], classifiersOpt: Option[Seq[String]],
artifactFileOpt: Artifact => Option[File] artifactFileOpt: (Module, String, Artifact) => Option[File]
): sbt.UpdateReport = { ): sbt.UpdateReport = {
val configReports = configs.map { val configReports = configs.map {

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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"))
}

View File

@ -0,0 +1,4 @@
$ delete output
> run
$ exists output
> updateClassifiersCheck