Merge pull request #512 from coursier/topic/sbt-release

Use sbt-release
This commit is contained in:
Alexandre Archambault 2017-04-24 22:40:22 +02:00 committed by GitHub
commit 916488393a
12 changed files with 500 additions and 21 deletions

View File

@ -5,7 +5,7 @@
A Scala library to fetch dependencies from Maven / Ivy repositories
[![Build Status](https://travis-ci.org/coursier/coursier.svg?branch=master)](https://travis-ci.org/coursier/coursier)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/trtum5b7washfbj9?svg=true)](https://ci.appveyor.com/project/coursier/coursier)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/yy3svc6ukqpykw5s?svg=true)](https://ci.appveyor.com/project/alexarchambault/coursier-a7n6k)
[![Join the chat at https://gitter.im/coursier/coursier](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/coursier/coursier?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Maven Central](https://img.shields.io/maven-central/v/io.get-coursier/coursier_2.11.svg)](https://maven-badges.herokuapp.com/maven-central/io.get-coursier/coursier_2.11)
[![Scaladoc](http://javadoc-badge.appspot.com/io.get-coursier/coursier_2.11.svg?label=scaladoc)](http://javadoc-badge.appspot.com/io.get-coursier/coursier_2.11)

View File

@ -5,7 +5,7 @@
A Scala library to fetch dependencies from Maven / Ivy repositories
[![Build Status](https://travis-ci.org/coursier/coursier.svg?branch=master)](https://travis-ci.org/coursier/coursier)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/trtum5b7washfbj9?svg=true)](https://ci.appveyor.com/project/coursier/coursier)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/yy3svc6ukqpykw5s?svg=true)](https://ci.appveyor.com/project/alexarchambault/coursier-a7n6k)
[![Join the chat at https://gitter.im/coursier/coursier](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/coursier/coursier?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Maven Central](https://img.shields.io/maven-central/v/io.get-coursier/coursier_2.11.svg)](https://maven-badges.herokuapp.com/maven-central/io.get-coursier/coursier_2.11)
[![Scaladoc](http://javadoc-badge.appspot.com/io.get-coursier/coursier_2.11.svg?label=scaladoc)](http://javadoc-badge.appspot.com/io.get-coursier/coursier_2.11)

31
project/Appveyor.scala Normal file
View File

@ -0,0 +1,31 @@
import argonaut._
import argonaut.Argonaut._
import argonaut.ArgonautShapeless._
import sbt.Logger
object Appveyor {
final case class Build(
buildId: Long,
branch: String,
commitId: String,
status: String
)
def branchLastBuild(repo: String, branch: String, log: Logger): Build = {
final case class Response(build: Build)
val url = s"https://ci.appveyor.com/api/projects/$repo/branch/$branch"
val rawResp = HttpUtil.fetch(url, log)
rawResp.decodeEither[Response] match {
case Left(err) =>
sys.error(s"Error decoding response from $url: $err")
case Right(resp) =>
resp.build
}
}
}

57
project/HttpUtil.scala Normal file
View File

@ -0,0 +1,57 @@
import java.io.{ByteArrayOutputStream, InputStream}
import java.net.{HttpURLConnection, URL, URLConnection}
import java.nio.charset.StandardCharsets
import sbt.Logger
object HttpUtil {
private def readFully(is: InputStream): Array[Byte] = {
val buffer = new ByteArrayOutputStream
val data = Array.ofDim[Byte](16384)
var nRead = 0
while ({
nRead = is.read(data, 0, data.length)
nRead != -1
})
buffer.write(data, 0, nRead)
buffer.flush()
buffer.toByteArray
}
def fetch(url: String, log: Logger): String = {
val url0 = new URL(url)
log.info(s"Fetching $url")
val (rawResp, code) = {
var conn: URLConnection = null
var httpConn: HttpURLConnection = null
var is: InputStream = null
try {
conn = url0.openConnection()
httpConn = conn.asInstanceOf[HttpURLConnection]
httpConn.setRequestProperty("Accept", "application/vnd.travis-ci.2+json")
is = conn.getInputStream
(readFully(is), httpConn.getResponseCode)
} finally {
if (is != null)
is.close()
if (httpConn != null)
httpConn.disconnect()
}
}
if (code / 100 != 2)
sys.error(s"Unexpected response code when getting $url: $code")
new String(rawResp, StandardCharsets.UTF_8)
}
}

View File

@ -62,6 +62,6 @@ object Publish {
}
)
lazy val released = pomStuff ++ pushToSonatypeStuff
lazy val released = pomStuff ++ pushToSonatypeStuff ++ Release.settings
}

287
project/Release.scala Normal file
View File

@ -0,0 +1,287 @@
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.regex.Pattern
import com.typesafe.sbt.pgp.PgpKeys
import sbt._
import sbt.Keys._
import sbtrelease.ReleasePlugin.autoImport._
import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
import scala.io._
object Release {
implicit final class StateOps(val state: State) extends AnyVal {
def vcs: sbtrelease.Vcs =
Project.extract(state).get(releaseVcs).getOrElse {
sys.error("VCS not set")
}
}
val checkTravisStatus = ReleaseStep { state =>
val currentHash = state.vcs.currentHash
val build = Travis.builds("coursier/coursier", state.log)
.find { build =>
build.job_ids.headOption.exists { id =>
Travis.job(id, state.log).commit.sha == currentHash
}
}
.getOrElse {
sys.error(s"Status for commit $currentHash not found on Travis")
}
state.log.info(s"Found build ${build.id.value} for commit $currentHash, state: ${build.state}")
build.state match {
case "passed" =>
case _ =>
sys.error(s"Build for $currentHash in state ${build.state}")
}
state
}
val checkAppveyorStatus = ReleaseStep { state =>
val currentHash = state.vcs.currentHash
val build = Appveyor.branchLastBuild("alexarchambault/coursier-a7n6k", "master", state.log)
state.log.info(s"Found last build ${build.buildId} for branch master, status: ${build.status}")
if (build.commitId != currentHash)
sys.error(s"Last master Appveyor build corresponds to commit ${build.commitId}, expected $currentHash")
if (build.status != "success")
sys.error(s"Last master Appveyor build status: ${build.status}")
state
}
val previousReleaseVersion = AttributeKey[String]("previousReleaseVersion")
val initialVersion = AttributeKey[String]("initialVersion")
val saveInitialVersion = ReleaseStep { state =>
val currentVer = Project.extract(state).get(version)
state.put(initialVersion, currentVer)
}
def versionChanges(state: State): Boolean = {
val initialVer = state.get(initialVersion).getOrElse {
sys.error(s"${initialVersion.label} key not set")
}
val (_, nextVer) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
initialVer == nextVer
}
val updateVersionPattern = "(?m)^VERSION=.*$".r
def updateVersionInScript(file: File, newVersion: String): Unit = {
val content = Source.fromFile(file)(Codec.UTF8).mkString
updateVersionPattern.findAllIn(content).toVector match {
case Seq() => sys.error(s"Found no matches in $file")
case Seq(_) =>
case _ => sys.error(s"Found too many matches in $file")
}
val newContent = updateVersionPattern.replaceAllIn(content, "VERSION=" + newVersion)
Files.write(file.toPath, newContent.getBytes(StandardCharsets.UTF_8))
}
val updateScripts = ReleaseStep { state =>
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
val scriptsDir = Project.extract(state).get(baseDirectory.in(ThisBuild)) / "scripts"
val scriptFiles = Seq(
scriptsDir / "generate-launcher.sh",
scriptsDir / "generate-sbt-launcher.sh"
)
val vcs = state.vcs
for (f <- scriptFiles) {
updateVersionInScript(f, releaseVer)
vcs.add(f.getAbsolutePath).!!(state.log)
}
state
}
val updateLaunchers = ReleaseStep { state =>
val baseDir = Project.extract(state).get(baseDirectory.in(ThisBuild))
val scriptsDir = baseDir / "scripts"
val scriptFiles = Seq(
(scriptsDir / "generate-launcher.sh") -> (baseDir / "coursier"),
(scriptsDir / "generate-sbt-launcher.sh") -> (baseDir / "csbt")
)
val vcs = state.vcs
for ((f, output) <- scriptFiles) {
sbt.Process(Seq(f.getAbsolutePath, "-f")).!!(state.log)
vcs.add(output.getAbsolutePath).!!(state.log)
}
state
}
val savePreviousReleaseVersion = ReleaseStep { state =>
val cmd = Seq(state.vcs.commandName, "tag", "-l")
val tag = scala.sys.process.Process(cmd)
.!!
.linesIterator
.toVector
.lastOption
.getOrElse {
sys.error(s"Found no tags when running ${cmd.mkString(" ")}")
}
val ver =
if (tag.startsWith("v"))
tag.stripPrefix("v")
else
sys.error(s"Last tag '$tag' doesn't start with 'v'")
state.put(previousReleaseVersion, ver)
}
val updateTutReadme = ReleaseStep { state =>
val previousVer = state.get(previousReleaseVersion).getOrElse {
sys.error(s"${previousReleaseVersion.label} key not set")
}
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
val readmeFile = Project.extract(state).get(baseDirectory.in(ThisBuild)) / "doc" / "README.md"
val pattern = Pattern.quote(previousVer).r
val content = Source.fromFile(readmeFile)(Codec.UTF8).mkString
val newContent = pattern.replaceAllIn(content, releaseVer)
Files.write(readmeFile.toPath, newContent.getBytes(StandardCharsets.UTF_8))
state.vcs.add(readmeFile.getAbsolutePath).!!(state.log)
state
}
val stageReadme = ReleaseStep { state =>
val baseDir = Project.extract(state).get(baseDirectory.in(ThisBuild))
val processedReadmeFile = baseDir / "README.md"
state.vcs.add(processedReadmeFile.getAbsolutePath).!!(state.log)
state
}
val coursierVersionPattern = s"(?m)^${Pattern.quote("def coursierVersion = \"")}[^${'"'}]*${Pattern.quote("\"")}$$".r
val updatePluginsSbt = ReleaseStep { state =>
val vcs = state.vcs
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
val baseDir = Project.extract(state).get(baseDirectory.in(ThisBuild))
val pluginsSbtFile = baseDir / "project" / "plugins.sbt"
val projectPluginsSbtFile = baseDir / "project" / "project" / "plugins.sbt"
val files = Seq(
pluginsSbtFile,
projectPluginsSbtFile
)
for (f <- files) {
val content = Source.fromFile(f)(Codec.UTF8).mkString
coursierVersionPattern.findAllIn(content).toVector match {
case Seq() => sys.error(s"Found no matches in $f")
case Seq(_) =>
case _ => sys.error(s"Found too many matches in $f")
}
val newContent = coursierVersionPattern.replaceAllIn(content, "def coursierVersion = \"" + releaseVer + "\"")
Files.write(f.toPath, newContent.getBytes(StandardCharsets.UTF_8))
vcs.add(f.getAbsolutePath).!!(state.log)
}
state
}
val commitUpdates = ReleaseStep(
action = { state =>
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
state.vcs.commit(s"Updates for $releaseVer", sign = true).!(state.log)
state
},
check = { state =>
val vcs = state.vcs
if (vcs.hasModifiedFiles)
sys.error("Aborting release: unstaged modified files")
if (vcs.hasUntrackedFiles && !Project.extract(state).get(releaseIgnoreUntrackedFiles))
sys.error(
"Aborting release: untracked files. Remove them or specify 'releaseIgnoreUntrackedFiles := true' in settings"
)
state
}
)
val settings = Seq(
releaseProcess := Seq[ReleaseStep](
checkTravisStatus,
checkAppveyorStatus,
savePreviousReleaseVersion,
checkSnapshotDependencies,
inquireVersions,
saveInitialVersion,
setReleaseVersion,
commitReleaseVersion,
publishArtifacts,
releaseStepCommand("sonatypeRelease"),
updateScripts,
updateLaunchers,
updateTutReadme,
releaseStepCommand("tut"),
stageReadme,
updatePluginsSbt,
commitUpdates,
tagRelease,
setNextVersion,
commitNextVersion,
ReleaseStep(_.reload),
pushChanges
),
releasePublishArtifactsAction := PgpKeys.publishSigned.value
)
}

82
project/Travis.scala Normal file
View File

@ -0,0 +1,82 @@
import argonaut._
import argonaut.Argonaut._
import argonaut.ArgonautShapeless._
import sbt.Logger
object Travis {
final case class BuildId(value: Long) extends AnyVal
object BuildId {
implicit val decode: DecodeJson[BuildId] =
DecodeJson.LongDecodeJson.map(BuildId(_))
}
final case class JobId(value: Long) extends AnyVal
object JobId {
implicit val decode: DecodeJson[JobId] =
DecodeJson.LongDecodeJson.map(JobId(_))
}
final case class CommitId(value: Long) extends AnyVal
object CommitId {
implicit val decode: DecodeJson[CommitId] =
DecodeJson.LongDecodeJson.map(CommitId(_))
}
final case class Build(
id: BuildId,
job_ids: List[JobId],
pull_request: Boolean,
state: String,
commit_id: CommitId
)
final case class Builds(
builds: List[Build]
)
final case class Commit(
id: CommitId,
sha: String,
branch: String
)
final case class JobDetails(
state: String
)
final case class Job(
commit: Commit,
job: JobDetails
)
def builds(repo: String, log: Logger): List[Build] = {
val url = s"https://api.travis-ci.org/repos/$repo/builds"
val resp = HttpUtil.fetch(url, log)
resp.decodeEither[Builds] match {
case Left(err) =>
sys.error(s"Error decoding response from $url: $err")
case Right(builds) =>
log.info(s"Got ${builds.builds.length} builds")
builds.builds
}
}
def job(id: JobId, log: Logger): Job = {
val url = s"https://api.travis-ci.org/jobs/${id.value}"
val resp = HttpUtil.fetch(url, log)
resp.decodeEither[Job] match {
case Left(err) =>
sys.error(s"Error decoding response from $url: $err")
case Right(job) =>
job
}
}
}

View File

@ -1 +1 @@
sbt.version=0.13.8
sbt.version=0.13.15

View File

@ -1,10 +1,27 @@
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.8.2")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.15")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.4.0")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.8")
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC1")
addSbtPlugin("io.get-coursier" % "sbt-shading" % "1.0.0-RC1")
addSbtPlugin("com.typesafe.sbt" % "sbt-proguard" % "0.2.2")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.13")
libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
plugins_(
"io.get-coursier" % "sbt-coursier" % coursierVersion,
"com.typesafe" % "sbt-mima-plugin" % "0.1.13",
"org.xerial.sbt" % "sbt-pack" % "0.8.2",
"com.jsuereth" % "sbt-pgp" % "1.0.0",
"com.typesafe.sbt" % "sbt-proguard" % "0.2.2",
"com.github.gseitz" % "sbt-release" % "1.0.4",
"org.scala-js" % "sbt-scalajs" % "0.6.15",
"org.scoverage" % "sbt-scoverage" % "1.4.0",
"io.get-coursier" % "sbt-shading" % coursierVersion,
"org.xerial.sbt" % "sbt-sonatype" % "1.1",
"org.tpolecat" % "tut-plugin" % "0.4.8"
)
libs ++= Seq(
"org.scala-sbt" % "scripted-plugin" % sbtVersion.value,
compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full), // for shapeless / auto type class derivations
"com.github.alexarchambault" %% "argonaut-shapeless_6.2" % "1.2.0-M5"
)
// important: this line is matched / substituted during releases (via sbt-release)
def coursierVersion = "1.0.0-RC1"
def plugins_(modules: ModuleID*) = modules.map(addSbtPlugin)
def libs = libraryDependencies

View File

@ -1 +1,4 @@
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC1")
addSbtPlugin("io.get-coursier" % "sbt-coursier" % coursierVersion)
// important: this line is matched / substituted during releases (via sbt-release)
def coursierVersion = "1.0.0-RC1"

View File

@ -1,12 +1,14 @@
#!/usr/bin/env bash
set -e
COURSIER_VERSION=1.0.0-M15-2
VERSION=1.0.0-M15-2
"$(dirname "$0")/../coursier" bootstrap \
"io.get-coursier:sbt-launcher_2.12:$COURSIER_VERSION" \
"io.get-coursier:sbt-launcher_2.12:$VERSION" \
-r sonatype:releases \
--no-default \
-i launcher \
-I launcher:org.scala-sbt:launcher-interface:1.0.0 \
-o csbt \
-J -Djline.shutdownhook=false
-J -Djline.shutdownhook=false \
"$@"

View File

@ -151,7 +151,7 @@ object CentralTests extends TestSuite {
): Future[T] = async {
val res = await(resolve(deps, extraRepo = extraRepo))
assert(res.errors.isEmpty)
assert(res.metadataErrors.isEmpty)
assert(res.conflicts.isEmpty)
assert(res.isDone)
@ -444,7 +444,7 @@ object CentralTests extends TestSuite {
val res = await(resolve(deps))
assert(res.errors.isEmpty)
assert(res.metadataErrors.isEmpty)
assert(res.conflicts.isEmpty)
assert(res.isDone)
@ -479,7 +479,7 @@ object CentralTests extends TestSuite {
val res = await(resolve(deps))
assert(res.errors.isEmpty)
assert(res.metadataErrors.isEmpty)
assert(res.conflicts.isEmpty)
assert(res.isDone)