From d27cec6839015565c73ec017273459fe6040f081 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Mon, 24 Apr 2017 20:46:23 +0200 Subject: [PATCH] Check Travis status --- project/Release.scala | 26 ++++++++ project/Travis.scala | 134 +++++++++++++++++++++++++++++++++++++++ project/build.properties | 2 +- project/plugins.sbt | 6 +- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 project/Travis.scala diff --git a/project/Release.scala b/project/Release.scala index 3a700c131..1d470aec5 100644 --- a/project/Release.scala +++ b/project/Release.scala @@ -19,6 +19,31 @@ object Release { } } + 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 previousReleaseVersion = AttributeKey[String]("previousReleaseVersion") val initialVersion = AttributeKey[String]("initialVersion") @@ -216,6 +241,7 @@ object Release { val settings = Seq( releaseProcess := Seq[ReleaseStep]( + checkTravisStatus, savePreviousReleaseVersion, checkSnapshotDependencies, inquireVersions, diff --git a/project/Travis.scala b/project/Travis.scala new file mode 100644 index 000000000..f02386a2b --- /dev/null +++ b/project/Travis.scala @@ -0,0 +1,134 @@ +import java.io.{ByteArrayOutputStream, InputStream} +import java.net.{HttpURLConnection, URL, URLConnection} +import java.nio.charset.StandardCharsets + +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 + ) + + + 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 + } + + private 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) + } + + def builds(repo: String, log: Logger): List[Build] = { + + val url = s"https://api.travis-ci.org/repos/$repo/builds" + val resp = 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 = fetch(url, log) + + resp.decodeEither[Job] match { + case Left(err) => + sys.error(s"Error decoding response from $url: $err") + case Right(job) => + job + } + } + +} \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index a6e117b61..64317fdae 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index cbb164012..ffa8bf444 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,7 +13,11 @@ plugins_( "org.tpolecat" % "tut-plugin" % "0.4.8" ) -libs += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value +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"