diff --git a/build.sbt b/build.sbt index 309d42081..dc7b5a0ef 100644 --- a/build.sbt +++ b/build.sbt @@ -400,9 +400,23 @@ lazy val plugin = project sbtPlugin := { scalaVersion.value.startsWith("2.10.") }, - // added so that 2.10 artifacts of the other modules can be found by - // the too-naive-for-now inter-project resolver of the coursier SBT plugin - resolvers += Resolver.sonatypeRepo("snapshots") + resolvers ++= Seq( + // added so that 2.10 artifacts of the other modules can be found by + // the too-naive-for-now inter-project resolver of the coursier SBT plugin + Resolver.sonatypeRepo("snapshots"), + // added for sbt-scripted to be fine even with ++2.11.x + Resolver.typesafeIvyRepo("releases") + ) + ) + .settings(ScriptedPlugin.scriptedSettings) + .settings( + scriptedLaunchOpts ++= Seq( + "-Xmx1024M", + "-XX:MaxPermSize=256M", + "-Dplugin.version=" + version.value, + "-Dsbttest.base=" + (sourceDirectory.value / "sbt-test").getAbsolutePath + ), + scriptedBufferLog := false ) lazy val `coursier` = project.in(file(".")) diff --git a/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala b/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala index 01db819d8..308295082 100644 --- a/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala +++ b/plugin/src/main/scala-2.10/coursier/CoursierPlugin.scala @@ -20,6 +20,7 @@ object CoursierPlugin extends AutoPlugin { val coursierVerbosity = Keys.coursierVerbosity val coursierResolvers = Keys.coursierResolvers val coursierSbtResolvers = Keys.coursierSbtResolvers + val coursierFallbackDependencies = Keys.coursierFallbackDependencies val coursierCache = Keys.coursierCache val coursierProject = Keys.coursierProject val coursierProjects = Keys.coursierProjects @@ -39,6 +40,7 @@ object CoursierPlugin extends AutoPlugin { coursierVerbosity := Settings.defaultVerbosityLevel, coursierResolvers <<= Tasks.coursierResolversTask, coursierSbtResolvers <<= externalResolvers in updateSbtClassifiers, + coursierFallbackDependencies <<= Tasks.coursierFallbackDependenciesTask, coursierCache := Cache.default, update <<= Tasks.updateTask(withClassifiers = false), updateClassifiers <<= Tasks.updateTask(withClassifiers = true), diff --git a/plugin/src/main/scala-2.10/coursier/FallbackDependenciesRepository.scala b/plugin/src/main/scala-2.10/coursier/FallbackDependenciesRepository.scala new file mode 100644 index 000000000..3c196e9d6 --- /dev/null +++ b/plugin/src/main/scala-2.10/coursier/FallbackDependenciesRepository.scala @@ -0,0 +1,55 @@ +package coursier + +import java.net.URL + +import scalaz.{ EitherT, Monad } + +case class FallbackDependenciesRepository( + fallbacks: Map[(Module, String), (URL, Boolean)] +) extends Repository { + + private val source = new Artifact.Source { + def artifacts( + dependency: Dependency, + project: Project, + overrideClassifiers: Option[Seq[String]] + ) = + fallbacks.get(dependency.moduleVersion) match { + case None => Nil + case Some((url, changing)) => + Seq( + Artifact(url.toString, Map.empty, Map.empty, Attributes("jar", ""), changing) + ) + } + } + + def find[F[_]]( + module: Module, + version: String, + fetch: Fetch.Content[F] + )(implicit + F: Monad[F] + ): EitherT[F, String, (Artifact.Source, Project)] = + fallbacks.get((module, version)) match { + case None => + EitherT.left(F.point("No fallback URL found")) + + case Some((url, _)) => + val proj = Project( + module, + version, + Nil, + Map.empty, + None, + Nil, + Nil, + Nil, + None, + None, + Nil, + Info.empty + ) + + EitherT.right(F.point((source, proj))) + } +} diff --git a/plugin/src/main/scala-2.10/coursier/FromSbt.scala b/plugin/src/main/scala-2.10/coursier/FromSbt.scala index 0152cd1a3..367c090f7 100644 --- a/plugin/src/main/scala-2.10/coursier/FromSbt.scala +++ b/plugin/src/main/scala-2.10/coursier/FromSbt.scala @@ -2,7 +2,7 @@ package coursier import coursier.ivy.{ IvyXml, IvyRepository } -import java.net.MalformedURLException +import java.net.{ MalformedURLException, URL } import sbt.{ Resolver, CrossVersion, ModuleID } import sbt.mavenint.SbtPomExtraProperties @@ -80,11 +80,25 @@ object FromSbt { } for { - (from, to) <- allMappings.toSeq + (from, to) <- allMappings attr <- attributes } yield from -> dep.copy(configuration = to, attributes = attr) } + def fallbackDependencies( + allDependencies: Seq[ModuleID], + scalaVersion: String, + scalaBinaryVersion: String + ): Seq[(Module, String, URL, Boolean)] = + for { + module <- allDependencies + artifact <- module.explicitArtifacts + url <- artifact.url.toSeq + } yield { + val (module0, version) = moduleVersion(module, scalaVersion, scalaBinaryVersion) + (module0, version, url, module.isChanging) + } + def project( projectID: ModuleID, allDependencies: Seq[ModuleID], diff --git a/plugin/src/main/scala-2.10/coursier/Keys.scala b/plugin/src/main/scala-2.10/coursier/Keys.scala index 0af9153d9..796031c87 100644 --- a/plugin/src/main/scala-2.10/coursier/Keys.scala +++ b/plugin/src/main/scala-2.10/coursier/Keys.scala @@ -1,6 +1,8 @@ package coursier import java.io.File +import java.net.URL + import coursier.core.Publication import sbt.{ GetClassifiersModule, Resolver, SettingKey, TaskKey } @@ -18,6 +20,8 @@ object Keys { val coursierCache = SettingKey[File]("coursier-cache", "") + val coursierFallbackDependencies = TaskKey[Seq[(Module, String, URL, Boolean)]]("coursier-fallback-dependencies", "") + val coursierProject = TaskKey[Project]("coursier-project", "") val coursierProjects = TaskKey[Seq[Project]]("coursier-projects", "") val coursierPublications = TaskKey[Seq[(String, Publication)]]("coursier-publications", "") diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index fa56b03c5..3583c0d8c 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -1,6 +1,7 @@ package coursier import java.io.{ OutputStreamWriter, File } +import java.net.URL import java.nio.file.Files import java.util.concurrent.Executors @@ -42,6 +43,26 @@ object Tasks { } } + def coursierFallbackDependenciesTask: Def.Initialize[sbt.Task[Seq[(Module, String, URL, Boolean)]]] = + ( + sbt.Keys.state, + sbt.Keys.thisProjectRef + ).flatMap { (state, projectRef) => + + val allDependenciesTask = allDependencies.in(projectRef).get(state) + + for { + allDependencies <- allDependenciesTask + } yield { + + FromSbt.fallbackDependencies( + allDependencies, + scalaVersion.in(projectRef).get(state), + scalaBinaryVersion.in(projectRef).get(state) + ) + } + } + def coursierProjectTask: Def.Initialize[sbt.Task[Project]] = ( sbt.Keys.state, @@ -181,19 +202,31 @@ object Tasks { lazy val cm = coursierSbtClassifiersModule.value - val currentProject = - if (sbtClassifiers) - FromSbt.project( + val (currentProject, fallbackDependencies) = + if (sbtClassifiers) { + val sv = scalaVersion.value + val sbv = scalaBinaryVersion.value + + val proj = FromSbt.project( cm.id, cm.modules, cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap, - scalaVersion.value, - scalaBinaryVersion.value + sv, + sbv ) - else { + + val fallbackDeps = FromSbt.fallbackDependencies( + cm.modules, + sv, + sbv + ) + + (proj, fallbackDeps) + } else { val proj = coursierProject.value val publications = coursierPublications.value - proj.copy(publications = publications) + val fallbackDeps = coursierFallbackDependencies.value + (proj.copy(publications = publications), fallbackDeps) } val ivySbt0 = ivySbt.value @@ -226,6 +259,24 @@ object Tasks { FromSbt.moduleVersion(_, sv, sbv) ).toMap + var anyNonSupportedExclusionRule = false + val exclusions = excludeDependencies.value.flatMap { + rule => + if ( + rule.artifact != "*" || + rule.configurations.nonEmpty || + rule.crossVersion != sbt.CrossVersion.Disabled + ) { + Console.err.println(s"Warning: unsupported exclusion rule $rule") + anyNonSupportedExclusionRule = true + Nil + } else + Seq((rule.organization, rule.name)) + }.toSet + + if (anyNonSupportedExclusionRule) + Console.err.println(s"Only supported exclusion rule fields: organization, name") + val resolvers = if (sbtClassifiers) coursierSbtResolvers.value @@ -236,7 +287,10 @@ object Tasks { val startRes = Resolution( - currentProject.dependencies.map { case (_, dep) => dep }.toSet, + currentProject.dependencies.map { + case (_, dep) => + dep.copy(exclusions = dep.exclusions ++ exclusions) + }.toSet, filter = Some(dep => !dep.optional), forceVersions = userForceVersions ++ forcedScalaModules(sv) ++ projects.map(_.moduleVersion) ) @@ -277,7 +331,25 @@ object Tasks { "ivy.home" -> (new File(sys.props("user.home")).toURI.getPath + ".ivy2") ) ++ sys.props - val repositories = Seq(globalPluginsRepo, interProjectRepo) ++ resolvers.flatMap(FromSbt.repository(_, ivyProperties)) + val repositories = Seq( + globalPluginsRepo, + interProjectRepo + ) ++ resolvers.flatMap( + FromSbt.repository(_, ivyProperties) + ) ++ { + if (fallbackDependencies.isEmpty) + Nil + else { + val map = fallbackDependencies.map { + case (mod, ver, url, changing) => + (mod, ver) -> ((url, changing)) + }.toMap + + Seq( + FallbackDependenciesRepository(map) + ) + } + } def report = { val pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory) diff --git a/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/build.sbt b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/build.sbt new file mode 100644 index 000000000..17832bca7 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/build.sbt @@ -0,0 +1,5 @@ +scalaVersion := "2.11.8" + +libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.1" % "1.0.0-RC1" + +excludeDependencies += SbtExclusionRule("com.chuusai", "shapeless_2.11") diff --git a/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/project/plugins.sbt b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/project/plugins.sbt new file mode 100644 index 000000000..152225a9e --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/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) +} diff --git a/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/src/main/scala/Main.scala b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/src/main/scala/Main.scala new file mode 100644 index 000000000..c8d199037 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/src/main/scala/Main.scala @@ -0,0 +1,21 @@ +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 argonautShapelessFound = classFound("argonaut.derive.MkEncodeJson") + + assert(argonautShapelessFound) + assert(!shapelessFound) + + Files.write(new File("output").toPath, "OK".getBytes("UTF-8")) +} diff --git a/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/test b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/test new file mode 100644 index 000000000..2182f57b0 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/exclude-dependencies/test @@ -0,0 +1,3 @@ +$ delete output +> run +$ exists output diff --git a/plugin/src/sbt-test/sbt-coursier/from/build.sbt b/plugin/src/sbt-test/sbt-coursier/from/build.sbt new file mode 100644 index 000000000..0f861ab23 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/from/build.sbt @@ -0,0 +1,31 @@ +scalaVersion := "2.11.8" + +libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.41" from { + + val f = file(sys.props("sbttest.base")) / "sbt-coursier" / "from" / "shapeless_2.11-2.3.0.jar" + + if (!f.exists()) { + val url0 = "https://repo1.maven.org/maven2/com/chuusai/shapeless_2.11/2.3.0/shapeless_2.11-2.3.0.jar" + + scala.Console.err.println(s"Fetching $url0") + + val url = new java.net.URL(url0) + + val is = url.openStream() + val os = new java.io.FileOutputStream(f) + + var read = -1 + val b = Array.fill[Byte](16*1024)(0) + while ({ + read = is.read(b) + read >= 0 + }) { + os.write(b, 0, read) + } + + is.close() + os.close() + } + + f.toURI.toString +} \ No newline at end of file diff --git a/plugin/src/sbt-test/sbt-coursier/from/project/plugins.sbt b/plugin/src/sbt-test/sbt-coursier/from/project/plugins.sbt new file mode 100644 index 000000000..152225a9e --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/from/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) +} diff --git a/plugin/src/sbt-test/sbt-coursier/from/src/main/scala/Main.scala b/plugin/src/sbt-test/sbt-coursier/from/src/main/scala/Main.scala new file mode 100644 index 000000000..c9401ff3e --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/from/src/main/scala/Main.scala @@ -0,0 +1,13 @@ +import java.io.File +import java.nio.file.Files + +import shapeless._ + +object Main extends App { + case class CC(s: String) + val cc = CC("OK") + val l = Generic[CC].to(cc) + val msg = l.head + + Files.write(new File("output").toPath, msg.getBytes("UTF-8")) +} diff --git a/plugin/src/sbt-test/sbt-coursier/from/test b/plugin/src/sbt-test/sbt-coursier/from/test new file mode 100644 index 000000000..2182f57b0 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/from/test @@ -0,0 +1,3 @@ +$ delete output +> run +$ exists output diff --git a/plugin/src/sbt-test/sbt-coursier/simple/build.sbt b/plugin/src/sbt-test/sbt-coursier/simple/build.sbt new file mode 100644 index 000000000..c03b2c8be --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/simple/build.sbt @@ -0,0 +1 @@ +scalaVersion := "2.11.8" diff --git a/plugin/src/sbt-test/sbt-coursier/simple/project/plugins.sbt b/plugin/src/sbt-test/sbt-coursier/simple/project/plugins.sbt new file mode 100644 index 000000000..152225a9e --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/simple/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) +} diff --git a/plugin/src/sbt-test/sbt-coursier/simple/src/main/scala/Main.scala b/plugin/src/sbt-test/sbt-coursier/simple/src/main/scala/Main.scala new file mode 100644 index 000000000..61295349d --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/simple/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")) +} diff --git a/plugin/src/sbt-test/sbt-coursier/simple/test b/plugin/src/sbt-test/sbt-coursier/simple/test new file mode 100644 index 000000000..2182f57b0 --- /dev/null +++ b/plugin/src/sbt-test/sbt-coursier/simple/test @@ -0,0 +1,3 @@ +$ delete output +> run +$ exists output diff --git a/project/plugins.sbt b/project/plugins.sbt index a0cfaba4e..a8d35b748 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,3 +6,4 @@ addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") addSbtPlugin("com.github.alexarchambault" % "coursier-sbt-plugin" % "1.0.0-M8") addSbtPlugin("com.typesafe.sbt" % "sbt-proguard" % "0.2.2") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8") +libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value