diff --git a/cache/src/main/scala/coursier/TermDisplay.scala b/cache/src/main/scala/coursier/TermDisplay.scala index b241c6c4a..7ea1b7f25 100644 --- a/cache/src/main/scala/coursier/TermDisplay.scala +++ b/cache/src/main/scala/coursier/TermDisplay.scala @@ -74,8 +74,7 @@ object TermDisplay { val start = actualFraction match { case None => - val elem = if (watching) "." else "?" - s" [ $elem ] " + " [ ] " case Some(frac) => val elem = if (watching) "." else "#" diff --git a/core/shared/src/main/scala/coursier/core/Definitions.scala b/core/shared/src/main/scala/coursier/core/Definitions.scala index dde61eac1..aa37c8f55 100644 --- a/core/shared/src/main/scala/coursier/core/Definitions.scala +++ b/core/shared/src/main/scala/coursier/core/Definitions.scala @@ -64,6 +64,9 @@ final case class Attributes( ) { def publication(name: String, ext: String): Publication = Publication(name, `type`, ext, classifier) + + def isEmpty: Boolean = + `type`.isEmpty && classifier.isEmpty } final case class Project( diff --git a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala index 7d4452f98..0f3f5ac75 100644 --- a/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala +++ b/core/shared/src/main/scala/coursier/core/ResolutionProcess.scala @@ -3,9 +3,8 @@ package core import scala.annotation.tailrec import scala.language.higherKinds - -import scalaz.{Monad, -\/, \/-} -import scalaz.Scalaz.{ToFunctorOps, ToTraverseOps, vectorInstance} +import scalaz.{-\/, Monad, \/, \/-} +import scalaz.Scalaz.{ToFunctorOps, ToBindOps, ToTraverseOps, vectorInstance} sealed abstract class ResolutionProcess { @@ -166,7 +165,10 @@ object ResolutionProcess { Missing(resolution0.missingFromCache.toSeq, resolution0, apply) } - private def fetchAll[F[_]](modVers: Seq[(Module, String)], fetch: Fetch.Metadata[F])(implicit F: Monad[F]) = { + private[coursier] def fetchAll[F[_]]( + modVers: Seq[(Module, String)], + fetch: Fetch.Metadata[F] + )(implicit F: Monad[F]): F[Vector[((Module, String), Seq[String] \/ (Artifact.Source, Project))]] = { def uniqueModules(modVers: Seq[(Module, String)]): Stream[Seq[(Module, String)]] = { @@ -191,8 +193,11 @@ object ResolutionProcess { uniqueModules(modVers) .toVector - .traverse(fetch) - .map(_.flatten) + .foldLeft(F.point(Vector.empty[((Module, String), Seq[String] \/ (Artifact.Source, Project))])) { + (acc, l) => + for (v <- acc; e <- fetch(l)) + yield v ++ e + } } } diff --git a/core/shared/src/main/scala/coursier/maven/MavenSource.scala b/core/shared/src/main/scala/coursier/maven/MavenSource.scala index 20c64dad0..df1b09dfe 100644 --- a/core/shared/src/main/scala/coursier/maven/MavenSource.scala +++ b/core/shared/src/main/scala/coursier/maven/MavenSource.scala @@ -19,8 +19,9 @@ final case class MavenSource( overrideClassifiers: Option[Seq[String]] ): Seq[Artifact] = { - val packagingTpeMap = project.packagingOpt - .filter(_ != Pom.relocatedPackaging) + val packagingOpt = project.packagingOpt.filter(_ != Pom.relocatedPackaging) + + val packagingTpeMap = packagingOpt .map { packaging => (MavenSource.typeDefaultClassifier(packaging), MavenSource.typeExtension(packaging)) -> packaging } @@ -67,7 +68,18 @@ final case class MavenSource( ) } - lazy val defaultPublication = { + lazy val defaultPublications = { + + val packagingPublicationOpt = packagingOpt + .filter(_ => dependency.attributes.isEmpty) + .map { packaging => + Publication( + dependency.module.name, + packaging, + MavenSource.typeExtension(packaging), + MavenSource.typeDefaultClassifier(packaging) + ) + } val type0 = if (dependency.attributes.`type`.isEmpty) "jar" else dependency.attributes.`type` @@ -84,41 +96,41 @@ final case class MavenSource( MavenSource.classifierExtensionDefaultTypeOpt(classifier, ext).getOrElse(ext) ) - Publication( - dependency.module.name, - tpe, - ext, - classifier - ) + val pubs = packagingPublicationOpt.toSeq :+ + Publication( + dependency.module.name, + tpe, + ext, + classifier + ) + + pubs.distinct } - overrideClassifiers match { - case Some(classifiers) => - - classifiers - .map { classifier => - if (classifier == dependency.attributes.classifier) - defaultPublication - else { - val ext = "jar" - val tpe = packagingTpeMap.getOrElse( - (classifier, ext), - MavenSource.classifierExtensionDefaultTypeOpt(classifier, ext).getOrElse(ext) - ) + overrideClassifiers + .fold(defaultPublications) { classifiers => + classifiers.flatMap { classifier => + if (classifier == dependency.attributes.classifier) + defaultPublications + else { + val ext = "jar" + val tpe = packagingTpeMap.getOrElse( + (classifier, ext), + MavenSource.classifierExtensionDefaultTypeOpt(classifier, ext).getOrElse(ext) + ) + Seq( Publication( dependency.module.name, tpe, ext, classifier ) - } + ) } - .map(artifactWithExtra) - - case None => - Seq(defaultPublication).map(artifactWithExtra) - } + } + } + .map(artifactWithExtra) } private val types = Map("sha1" -> "SHA-1", "md5" -> "MD5", "asc" -> "sig") @@ -239,7 +251,7 @@ final case class MavenSource( else if (dependency.attributes.`type`.nonEmpty) enrichedPublications.collect { case p - if p.publication.classifier.isEmpty && ( + if (p.publication.classifier.isEmpty || p.publication.classifier == MavenSource.typeDefaultClassifier(dependency.attributes.`type`)) && ( p.publication.`type` == dependency.attributes.`type` || (p.publication.ext == dependency.attributes.`type` && project.packagingOpt.toSeq.contains(p.publication.`type`)) // wow ) => @@ -290,14 +302,14 @@ final case class MavenSource( } val defaultPublications = artifactsUnknownPublications(dependency, project, overrideClassifiers) + .map(makeOptional) if (project.publications.isEmpty) defaultPublications else { val listedPublications = artifactsKnownPublications(dependency, project, overrideClassifiers) val listedUrls = listedPublications.map(_.url).toSet - val defaultPublications0 = defaultPublications.map(makeOptional) - val defaultPublicationsMap = defaultPublications0 + val defaultPublicationsMap = defaultPublications .map(a => a.url -> a) .toMap val listedPublications0 = listedPublications.map { a => @@ -305,7 +317,7 @@ final case class MavenSource( .get(a.url) .fold(a)(merge(a, _)) } - val extraPublications = defaultPublications0 + val extraPublications = defaultPublications .filter(a => !listedUrls(a.url)) listedPublications0 ++ extraPublications diff --git a/core/shared/src/main/scala/coursier/util/Parse.scala b/core/shared/src/main/scala/coursier/util/Parse.scala index b8c28731d..270a427e3 100644 --- a/core/shared/src/main/scala/coursier/util/Parse.scala +++ b/core/shared/src/main/scala/coursier/util/Parse.scala @@ -207,6 +207,8 @@ object Parse { ).right else if (s.startsWith("ivy:")) IvyRepository.parse(s.stripPrefix("ivy:")) + else if (s == "jitpack") + MavenRepository("https://jitpack.io").right else MavenRepository(s).right diff --git a/sbt-coursier/src/main/scala/coursier/FromSbt.scala b/sbt-coursier/src/main/scala/coursier/FromSbt.scala index 9c50d9aa9..c603f106b 100644 --- a/sbt-coursier/src/main/scala/coursier/FromSbt.scala +++ b/sbt-coursier/src/main/scala/coursier/FromSbt.scala @@ -103,6 +103,34 @@ object FromSbt { (module0, version, url, module.isChanging) } + def sbtClassifiersProject( + cm: GetClassifiersModule, + scalaVersion: String, + scalaBinaryVersion: String + ) = { + + val p = FromSbt.project( + cm.id, + cm.dependencies, + cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap, + scalaVersion, + scalaBinaryVersion + ) + + // for w/e reasons, the dependencies sometimes don't land in the right config above + // this is a loose attempt at fixing that + cm.configurations match { + case Seq(cfg) => + p.copy( + dependencies = p.dependencies.map { + case (_, d) => (cfg.name, d) + } + ) + case _ => + p + } + } + def project( projectID: ModuleID, allDependencies: Seq[ModuleID], diff --git a/sbt-coursier/src/main/scala/coursier/Tasks.scala b/sbt-coursier/src/main/scala/coursier/Tasks.scala index fafe42b19..5d4551e97 100644 --- a/sbt-coursier/src/main/scala/coursier/Tasks.scala +++ b/sbt-coursier/src/main/scala/coursier/Tasks.scala @@ -550,13 +550,7 @@ object Tasks { val (currentProject, fallbackDependencies, configGraphs) = if (sbtClassifiers) { - val proj = FromSbt.project( - cm.id, - cm.dependencies, - cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap, - sv, - sbv - ) + val proj = FromSbt.sbtClassifiersProject(cm, sv, sbv) val fallbackDeps = FromSbt.fallbackDependencies( cm.dependencies, @@ -1112,13 +1106,7 @@ object Tasks { val currentProject = if (sbtClassifiers) - FromSbt.project( - cm.id, - cm.dependencies, - cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap, - sv, - sbv - ) + FromSbt.sbtClassifiersProject(cm, sv, sbv) else proj.copy(publications = publications) @@ -1155,7 +1143,7 @@ object Tasks { val configs = if (withClassifiers && sbtClassifiers) - cm.configurations.map(c => c.name -> Set.empty[String]).toMap + cm.configurations.map(c => c.name -> Set(c.name)).toMap else shadedConfigOpt.fold(configs0) { case (baseConfig, shadedConfig) => @@ -1277,13 +1265,7 @@ object Tasks { val currentProject = if (sbtClassifiers) - FromSbt.project( - cm.id, - cm.dependencies, - cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap, - sv, - sbv - ) + FromSbt.sbtClassifiersProject(cm, sv, sbv) else proj.copy(publications = publications) diff --git a/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/build.sbt b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/build.sbt new file mode 100644 index 000000000..77feb59e6 --- /dev/null +++ b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/build.sbt @@ -0,0 +1,39 @@ + +import Compatibility._ + +scalaVersion := appConfiguration.value.provider.scalaProvider.version + +lazy val updateSbtClassifiersCheck = TaskKey[Unit]("updateSbtClassifiersCheck") + +updateSbtClassifiersCheck := { + + val configReport = updateSbtClassifiers + .value + .configuration(Default) + .getOrElse { + throw new Exception( + "default configuration not found in updateSbtClassifiers report" + ) + } + + def artifacts(org: String, name: String) = configReport + .modules + .collect { + case moduleReport + if moduleReport.module.organization == org && + moduleReport.module.name == name => + moduleReport.artifacts + } + .toSeq + .flatten + + def ensureHasArtifact(org: String, name: String) = + assert( + artifacts(org, name).exists(_._2.getName.endsWith("-sources.jar")), + s"$org:$name not found" + ) + + ensureHasArtifact("org.scala-lang", "scala-library") + ensureHasArtifact("io.get-coursier", "coursier_" + scalaBinaryVersion.value) + ensureHasArtifact("io.get-coursier", "sbt-coursier") +} diff --git a/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/plugins.sbt b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/plugins.sbt new file mode 100644 index 000000000..8d902e4dc --- /dev/null +++ b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/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/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.10/Compatibility.scala b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.10/Compatibility.scala new file mode 100644 index 000000000..8824f1209 --- /dev/null +++ b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.10/Compatibility.scala @@ -0,0 +1,8 @@ +object Compatibility { + + implicit class UpdateReportOps(val rep: sbt.UpdateReport) extends AnyVal { + def configuration(conf: sbt.Configuration) = + rep.configuration(conf.name) + } + +} diff --git a/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.12/Compatibility.scala b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.12/Compatibility.scala new file mode 100644 index 000000000..19835e1be --- /dev/null +++ b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/project/src/main/scala-2.12/Compatibility.scala @@ -0,0 +1 @@ +object Compatibility diff --git a/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/test b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/test new file mode 100644 index 000000000..f6351a4d9 --- /dev/null +++ b/sbt-coursier/src/sbt-test/sbt-coursier/update-sbt-classifiers/test @@ -0,0 +1 @@ +> updateSbtClassifiersCheck diff --git a/tests/jvm/src/test/scala/coursier/test/ResolutionProcessTests.scala b/tests/jvm/src/test/scala/coursier/test/ResolutionProcessTests.scala new file mode 100644 index 000000000..6fa1039cc --- /dev/null +++ b/tests/jvm/src/test/scala/coursier/test/ResolutionProcessTests.scala @@ -0,0 +1,72 @@ +package coursier.test + +import java.util.concurrent.ConcurrentHashMap + +import coursier.{Fetch, Module} +import coursier.core.ResolutionProcess +import utest._ + +import scala.collection.JavaConverters._ +import scala.concurrent.duration.DurationInt +import scalaz.{-\/, \/-} +import scalaz.concurrent.Task + +object ResolutionProcessTests extends TestSuite { + + val tests = TestSuite { + + 'fetchAll - { + + // check that tasks fetching different versions of the same module are spawned sequentially + // rather than all at once + def check(extra: Int): Unit = { + + val mod = Module("org", "name") + val modVers = (1 to (9 + extra)) + .map(_.toString) + .map((mod, _)) + + val called = new ConcurrentHashMap[String, Unit] + + val fetch: Fetch.Metadata[Task] = { + + case Seq((`mod`, "9")) => + // never calls the callback + Task.async { _ => + called.put("9", ()) + () + } + + case Seq(mv @ (`mod`, v)) => + Task.async { cb => + called.put(v, ()) + cb(\/-(Seq((mv, -\/(Seq("w/e")))))) + } + + case _ => sys.error(s"Cannot happen ($modVers)") + } + + val res = ResolutionProcess.fetchAll(modVers, fetch) + .timed(1.second) + .attempt + .unsafePerformSync + + // must have timed out + assert(res.swap.exists[Throwable] { case _: java.util.concurrent.TimeoutException => true; case _ => false }) + + val called0 = called.asScala.iterator.map(_._1).toSet + val expectedCalled = (0 to extra) + .map(9 + _) + .map(_.toString) + .toSet + assert(called0 == expectedCalled) + } + + * - check(0) + * - check(1) + * - check(3) + } + + } + +} diff --git a/tests/metadata b/tests/metadata index 93eccec4f..6b2578a25 160000 --- a/tests/metadata +++ b/tests/metadata @@ -1 +1 @@ -Subproject commit 93eccec4ffd1719586b6f1e1dddefcbea6722e7d +Subproject commit 6b2578a25220930e60b505b90e636092757d6397 diff --git a/tests/shared/src/test/resources/resolutions/android.arch.lifecycle/extensions/1.0.0-alpha3 b/tests/shared/src/test/resources/resolutions/android.arch.lifecycle/extensions/1.0.0-alpha3 new file mode 100644 index 000000000..effcc9c3c --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/android.arch.lifecycle/extensions/1.0.0-alpha3 @@ -0,0 +1,10 @@ +android.arch.core:core:1.0.0-alpha3:compile +android.arch.lifecycle:common:1.0.0-alpha3:compile +android.arch.lifecycle:extensions:1.0.0-alpha3:compile +android.arch.lifecycle:runtime:1.0.0-alpha3:compile +com.android.support:support-annotations:25.3.1:compile +com.android.support:support-compat:25.3.1:compile +com.android.support:support-core-ui:25.3.1:compile +com.android.support:support-core-utils:25.3.1:compile +com.android.support:support-fragment:25.3.1:compile +com.android.support:support-media-compat:25.3.1:compile \ No newline at end of file diff --git a/tests/shared/src/test/scala/coursier/test/CentralTests.scala b/tests/shared/src/test/scala/coursier/test/CentralTests.scala index bf91d54ea..392d36e61 100644 --- a/tests/shared/src/test/scala/coursier/test/CentralTests.scala +++ b/tests/shared/src/test/scala/coursier/test/CentralTests.scala @@ -569,6 +569,7 @@ abstract class CentralTests extends TestSuite { val zookeeperTestArtifact = zookeeperTestArtifacts.head + assert(!isActualCentral || !zookeeperTestArtifact.isOptional) assert(zookeeperTestArtifact.attributes.`type` == "test-jar") assert(zookeeperTestArtifact.attributes.classifier == "tests") zookeeperTestArtifact.url.endsWith("-tests.jar") @@ -822,6 +823,33 @@ abstract class CentralTests extends TestSuite { } } } + + 'packagingTpe - { + val mod = Module("android.arch.lifecycle", "extensions") + val ver = "1.0.0-alpha3" + + val extraRepo = MavenRepository("https://maven.google.com") + + * - resolutionCheck(mod, ver, extraRepos = Seq(extraRepo)) + + * - withArtifacts(mod, ver, "*", extraRepos = Seq(extraRepo), transitive = true) { artifacts => + val urls = artifacts.map(_.url).toSet + val expectedUrls = Set( + "https://maven.google.com/com/android/support/support-fragment/25.3.1/support-fragment-25.3.1.aar", + "https://maven.google.com/android/arch/core/core/1.0.0-alpha3/core-1.0.0-alpha3.aar", + "https://maven.google.com/android/arch/lifecycle/runtime/1.0.0-alpha3/runtime-1.0.0-alpha3.aar", + "https://maven.google.com/android/arch/lifecycle/extensions/1.0.0-alpha3/extensions-1.0.0-alpha3.aar", + "https://maven.google.com/com/android/support/support-compat/25.3.1/support-compat-25.3.1.aar", + "https://maven.google.com/com/android/support/support-media-compat/25.3.1/support-media-compat-25.3.1.aar", + "https://maven.google.com/com/android/support/support-core-ui/25.3.1/support-core-ui-25.3.1.aar", + "https://maven.google.com/com/android/support/support-core-utils/25.3.1/support-core-utils-25.3.1.aar", + "https://maven.google.com/com/android/support/support-annotations/25.3.1/support-annotations-25.3.1.jar", + "https://maven.google.com/android/arch/lifecycle/common/1.0.0-alpha3/common-1.0.0-alpha3.jar" + ) + + assert(expectedUrls.forall(urls)) + } + } } } diff --git a/tests/shared/src/test/scala/coursier/test/ParseTests.scala b/tests/shared/src/test/scala/coursier/test/ParseTests.scala index aa625a13d..510304771 100644 --- a/tests/shared/src/test/scala/coursier/test/ParseTests.scala +++ b/tests/shared/src/test/scala/coursier/test/ParseTests.scala @@ -42,5 +42,10 @@ object ParseTests extends TestSuite { val res = Parse.repository("typesafe:releases") assert(res.exists(isMavenRepo)) } + + "jitpack" - { + val res = Parse.repository("jitpack") + assert(res.exists(isMavenRepo)) + } } }