diff --git a/build.sbt b/build.sbt index 0c17ebb11..f20018ab2 100644 --- a/build.sbt +++ b/build.sbt @@ -188,6 +188,19 @@ lazy val `sbt-coursier` = project .dependsOn(coreJvm, cache, extra) .settings(plugin) +lazy val `sbt-pgp-coursier` = project + .dependsOn(`sbt-coursier`) + .settings( + plugin, + libs ++= { + scalaBinaryVersion.value match { + case "2.10" | "2.12" => + Seq(Deps.sbtPgp.value) + case _ => Nil + } + } + ) + lazy val `sbt-shading` = project .enablePlugins(ShadingPlugin) .dependsOn(`sbt-coursier`) @@ -258,6 +271,7 @@ lazy val jvm = project extra, cli, `sbt-coursier`, + `sbt-pgp-coursier`, `sbt-shading`, `sbt-launcher`, doc, @@ -293,6 +307,7 @@ lazy val `sbt-plugins` = project cache, extra, `sbt-coursier`, + `sbt-pgp-coursier`, `sbt-shading` ) .settings( @@ -314,6 +329,7 @@ lazy val coursier = project extra, cli, `sbt-coursier`, + `sbt-pgp-coursier`, `sbt-shading`, `sbt-launcher`, web, diff --git a/project/Deps.scala b/project/Deps.scala index 24281954e..628e082cc 100644 --- a/project/Deps.scala +++ b/project/Deps.scala @@ -1,5 +1,6 @@ import sbt._ +import sbt.Defaults.sbtPluginExtra import sbt.Keys._ object Deps { @@ -19,6 +20,17 @@ object Deps { def typesafeConfig = "com.typesafe" % "config" % "1.3.1" def argonautShapeless = "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M5" + def sbtPgp = Def.setting { + val sbtv = CrossVersion.binarySbtVersion(sbtVersion.value) + val sv = scalaBinaryVersion.value + val ver = sv match { + case "2.10" => "1.0.1" + case "2.12" => "1.1.0-M1" + case _ => "foo" // unused + } + sbtPluginExtra("com.jsuereth" % "sbt-pgp" % ver, sbtv, sv) + } + def scalaAsync = Def.setting { val version = diff --git a/sbt-coursier/src/main/scala/coursier/CoursierPlugin.scala b/sbt-coursier/src/main/scala/coursier/CoursierPlugin.scala index 9b2c9b4db..2ba611d94 100644 --- a/sbt-coursier/src/main/scala/coursier/CoursierPlugin.scala +++ b/sbt-coursier/src/main/scala/coursier/CoursierPlugin.scala @@ -47,6 +47,7 @@ object CoursierPlugin extends AutoPlugin { val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree val coursierArtifacts = Keys.coursierArtifacts + val coursierSignedArtifacts = Keys.coursierSignedArtifacts val coursierClassifiersArtifacts = Keys.coursierClassifiersArtifacts val coursierSbtClassifiersArtifacts = Keys.coursierSbtClassifiersArtifacts } @@ -97,6 +98,7 @@ object CoursierPlugin extends AutoPlugin { coursierFallbackDependencies := Tasks.coursierFallbackDependenciesTask.value, coursierCache := Cache.default, coursierArtifacts := Tasks.artifactFilesOrErrors(withClassifiers = false).value, + coursierSignedArtifacts := Tasks.artifactFilesOrErrors(withClassifiers = false, includeSignatures = true).value, coursierClassifiersArtifacts := Tasks.artifactFilesOrErrors( withClassifiers = true ).value, diff --git a/sbt-coursier/src/main/scala/coursier/Keys.scala b/sbt-coursier/src/main/scala/coursier/Keys.scala index 526d92df5..c421d89d3 100644 --- a/sbt-coursier/src/main/scala/coursier/Keys.scala +++ b/sbt-coursier/src/main/scala/coursier/Keys.scala @@ -62,6 +62,7 @@ object Keys { ) val coursierArtifacts = TaskKey[Map[Artifact, FileError \/ File]]("coursier-artifacts") + val coursierSignedArtifacts = TaskKey[Map[Artifact, FileError \/ File]]("coursier-signed-artifacts") val coursierClassifiersArtifacts = TaskKey[Map[Artifact, FileError \/ File]]("coursier-classifiers-artifacts") val coursierSbtClassifiersArtifacts = TaskKey[Map[Artifact, FileError \/ File]]("coursier-sbt-classifiers-artifacts") } diff --git a/sbt-coursier/src/main/scala/coursier/Tasks.scala b/sbt-coursier/src/main/scala/coursier/Tasks.scala index 56fd43808..591dad1f1 100644 --- a/sbt-coursier/src/main/scala/coursier/Tasks.scala +++ b/sbt-coursier/src/main/scala/coursier/Tasks.scala @@ -840,7 +840,8 @@ object Tasks { def artifactFilesOrErrors( withClassifiers: Boolean, sbtClassifiers: Boolean = false, - ignoreArtifactErrors: Boolean = false + ignoreArtifactErrors: Boolean = false, + includeSignatures: Boolean = false ) = Def.task { // let's update only one module at once, for a better output @@ -878,12 +879,21 @@ object Tasks { else None - val allArtifacts = + val allArtifacts0 = classifiers match { case None => res.flatMap(_.artifacts) case Some(cl) => res.flatMap(_.classifiersArtifacts(cl)) } + val allArtifacts = + if (includeSignatures) + allArtifacts0.flatMap { a => + val sigOpt = a.extra.get("sig").map(_.copy(attributes = Attributes())) + Seq(a) ++ sigOpt.toSeq + } + else + allArtifacts0 + var pool: ExecutorService = null var artifactsLogger: TermDisplay = null @@ -1022,7 +1032,8 @@ object Tasks { shadedConfigOpt: Option[(String, String)], withClassifiers: Boolean, sbtClassifiers: Boolean = false, - ignoreArtifactErrors: Boolean = false + ignoreArtifactErrors: Boolean = false, + includeSignatures: Boolean = false ) = Def.task { def grouped[K, V](map: Seq[(K, V)])(mapKey: K => K): Map[K, Seq[V]] = @@ -1134,7 +1145,9 @@ object Tasks { Keys.coursierSbtClassifiersArtifacts else Keys.coursierClassifiersArtifacts - } else + } else if (includeSignatures) + Keys.coursierSignedArtifacts + else Keys.coursierArtifacts ).value @@ -1178,7 +1191,9 @@ object Tasks { _, _, _ - ) + ), + log, + includeSignatures = includeSignatures ) } diff --git a/sbt-coursier/src/main/scala/coursier/ToSbt.scala b/sbt-coursier/src/main/scala/coursier/ToSbt.scala index 5073f19e1..c56bf69be 100644 --- a/sbt-coursier/src/main/scala/coursier/ToSbt.scala +++ b/sbt-coursier/src/main/scala/coursier/ToSbt.scala @@ -116,22 +116,42 @@ object ToSbt { res: Resolution, classifiersOpt: Option[Seq[String]], artifactFileOpt: (Module, String, Artifact) => Option[File], - keepPomArtifact: Boolean = false + log: sbt.Logger, + keepPomArtifact: Boolean = false, + includeSignatures: Boolean = false ) = { - val depArtifacts0 = + val depArtifacts1 = classifiersOpt match { case None => res.dependencyArtifacts case Some(cl) => res.dependencyClassifiersArtifacts(cl) } - val depArtifacts = + val depArtifacts0 = if (keepPomArtifact) - depArtifacts0 + depArtifacts1 else - depArtifacts0.filter { + depArtifacts1.filter { case (_, a) => a.attributes != Attributes("pom", "") } + val depArtifacts = + if (includeSignatures) { + + val notFound = depArtifacts0.filter(!_._2.extra.contains("sig")) + + if (notFound.isEmpty) + depArtifacts0.flatMap { + case (dep, a) => + Seq(dep -> a) ++ a.extra.get("sig").toSeq.map(dep -> _) + } + else { + for ((_, a) <- notFound) + log.error(s"No signature found for ${a.url}") + sys.error(s"${notFound.length} signature(s) not found") + } + } else + depArtifacts0 + val groupedDepArtifacts = grouped(depArtifacts) val versions = res.dependencies.toVector.map { dep => @@ -184,7 +204,9 @@ object ToSbt { configs: Map[String, Set[String]], classifiersOpt: Option[Seq[String]], artifactFileOpt: (Module, String, Artifact) => Option[File], - keepPomArtifact: Boolean = false + log: sbt.Logger, + keepPomArtifact: Boolean = false, + includeSignatures: Boolean = false ): sbt.UpdateReport = { val configReports = configs.map { @@ -192,7 +214,14 @@ object ToSbt { val configDeps = extends0.flatMap(configDependencies.getOrElse(_, Nil)) val subRes = resolutions(config).subset(configDeps) - val reports = ToSbt.moduleReports(subRes, classifiersOpt, artifactFileOpt, keepPomArtifact) + val reports = ToSbt.moduleReports( + subRes, + classifiersOpt, + artifactFileOpt, + log, + keepPomArtifact = keepPomArtifact, + includeSignatures = includeSignatures + ) ConfigurationReport( config, diff --git a/sbt-pgp-coursier/src/main/scala/coursier/CoursierSbtPgpPlugin.scala b/sbt-pgp-coursier/src/main/scala/coursier/CoursierSbtPgpPlugin.scala new file mode 100644 index 000000000..c5fa119d0 --- /dev/null +++ b/sbt-pgp-coursier/src/main/scala/coursier/CoursierSbtPgpPlugin.scala @@ -0,0 +1,22 @@ +package coursier + +import com.typesafe.sbt.pgp.PgpKeys.updatePgpSignatures +import sbt.AutoPlugin + +object CoursierSbtPgpPlugin extends AutoPlugin { + + override def trigger = allRequirements + + override def requires = com.typesafe.sbt.SbtPgp && coursier.CoursierPlugin + + override val projectSettings = Seq( + updatePgpSignatures := { + Tasks.updateTask( + None, + withClassifiers = false, + includeSignatures = true + ).value + } + ) + +} diff --git a/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/build.sbt b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/build.sbt new file mode 100644 index 000000000..1fd35c3b4 --- /dev/null +++ b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/build.sbt @@ -0,0 +1,31 @@ +scalaVersion := "2.11.8" + +libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M5" + +lazy val check = TaskKey[Unit]("check") + +check := { + val report = com.typesafe.sbt.pgp.PgpKeys.updatePgpSignatures.value + val configReport = report + .configurations + .find { confRep => + confRep.configuration == "compile" + } + .getOrElse { + sys.error("No configuration report found for configuration 'compile'") + } + val moduleReports = configReport.modules + val artifacts = moduleReports.flatMap(_.artifacts.map(_._1)) + val signatures = moduleReports + .flatMap(_.artifacts) + .filter(_._1.extension == "jar.asc") + .map(_._2) + assert( + signatures.nonEmpty, + "No signatures found" + ) + assert( + signatures.forall(_.getAbsolutePath.contains("/.coursier/cache/")), + s"Found signatures not provided by coursier:\n${signatures.map(" " + _).mkString("\n")}" + ) +} diff --git a/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/project/plugins.sbt b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/project/plugins.sbt new file mode 100644 index 000000000..d59204c4b --- /dev/null +++ b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-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-pgp-coursier" % pluginVersion) +} diff --git a/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/test b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/test new file mode 100644 index 000000000..b09384dc8 --- /dev/null +++ b/sbt-pgp-coursier/src/sbt-test/sbt-pgp-coursier/simple/test @@ -0,0 +1,2 @@ +> checkPgpSignatures +> check diff --git a/scripts/travis.sh b/scripts/travis.sh index 405875409..af69270c7 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -83,7 +83,8 @@ is212() { } runSbtCoursierTests() { - sbt ++$SCALA_VERSION coreJVM/publishLocal cache/publishLocal extra/publishLocal "sbt-coursier/scripted sbt-coursier/*" + addPgpKeys + sbt ++$SCALA_VERSION sbt-plugins/publishLocal "sbt-coursier/scripted sbt-coursier/*" sbt-pgp-coursier/scripted if [ "$SCALA_VERSION" = "2.10" ]; then sbt ++$SCALA_VERSION "sbt-coursier/scripted sbt-coursier-0.13/*" fi @@ -212,6 +213,12 @@ testBootstrap() { fi } +addPgpKeys() { + for key in b41f2bce 9fa47a44 ae548ced b4493b94 53a97466 36ee59d9 dc426429 3b80305d 69e0a56c fdd5c0cd 35543c27 70173ee5 111557de 39c263a9; do + gpg --keyserver keyserver.ubuntu.com --recv "$key" + done +} + # TODO Add coverage once https://github.com/scoverage/sbt-scoverage/issues/111 is fixed