sbt/project/Release.scala

409 lines
12 KiB
Scala
Raw Normal View History

2017-04-24 20:46:23 +02:00
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}
2017-04-24 20:46:23 +02:00
import java.util.regex.Pattern
import com.typesafe.sbt.pgp.PgpKeys
2017-10-26 14:53:18 +02:00
import sbt._
2017-04-24 20:46:23 +02:00
import sbt.Keys._
2017-05-15 15:32:51 +02:00
import sbt.Package.ManifestAttributes
2017-04-24 20:46:23 +02:00
import sbtrelease.ReleasePlugin.autoImport._
import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
import scala.io._
2017-10-20 02:48:12 +02:00
import scala.sys.process.ProcessLogger
2017-04-24 20:46:23 +02:00
object Release {
2017-10-20 02:48:12 +02:00
// adapted from https://github.com/sbt/sbt-release/blob/eccd4cb7b9818b2a731380fe31c399dc9cb7375b/src/main/scala/ReleaseExtra.scala#L239-L243
private def toProcessLogger(st: State): ProcessLogger =
new ProcessLogger {
def err(s: => String) = st.log.info(s)
def out(s: => String) = st.log.info(s)
def buffer[T](f: => T) = st.log.buffer(f)
}
2017-04-24 20:46:23 +02:00
implicit final class StateOps(val state: State) extends AnyVal {
def vcs: sbtrelease.Vcs =
Project.extract(state).get(releaseVcs).getOrElse {
sys.error("VCS not set")
}
}
2017-04-24 20:46:23 +02:00
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
}
2017-04-24 20:46:23 +02:00
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
}
2017-04-24 20:46:23 +02:00
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(
2018-02-01 23:59:46 +01:00
scriptsDir / "generate-launcher.sh"
2017-04-24 20:46:23 +02:00
)
val vcs = state.vcs
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
for (f <- scriptFiles) {
updateVersionInScript(f, releaseVer)
2017-10-20 02:48:12 +02:00
vcs.add(f.getAbsolutePath).!!(log)
2017-04-24 20:46:23 +02:00
}
state
}
val updateLaunchers = ReleaseStep { state =>
val baseDir = Project.extract(state).get(baseDirectory.in(ThisBuild))
val scriptsDir = baseDir / "scripts"
val scriptFiles = Seq(
2017-12-28 11:47:04 +01:00
(scriptsDir / "generate-launcher.sh") -> (baseDir / "coursier")
2017-04-24 20:46:23 +02:00
)
val vcs = state.vcs
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
for ((f, output) <- scriptFiles) {
2017-10-24 17:47:29 +02:00
sys.process.Process(Seq(f.getAbsolutePath, "-f")).!!(log)
2017-10-20 02:48:12 +02:00
vcs.add(output.getAbsolutePath).!!(log)
2017-04-24 20:46:23 +02:00
}
state
}
val savePreviousReleaseVersion = ReleaseStep { state =>
2017-09-20 16:57:31 +02:00
val cmd = Seq(state.vcs.commandName, "tag", "--sort", "version:refname")
2017-04-24 20:46:23 +02:00
val tag = scala.sys.process.Process(cmd)
.!!
2017-10-26 14:53:18 +02:00
.lines
2017-04-24 20:46:23 +02:00
.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 =>
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
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")
}
2018-02-01 23:59:46 +01:00
val readmeFile = Project.extract(state).get(baseDirectory.in(ThisBuild)) / "doc" / "readme" / "README.md"
2017-04-24 20:46:23 +02:00
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))
2017-10-20 02:48:12 +02:00
state.vcs.add(readmeFile.getAbsolutePath).!!(log)
2017-04-24 20:46:23 +02:00
state
}
val stageReadme = ReleaseStep { state =>
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
val baseDir = Project.extract(state).get(baseDirectory.in(ThisBuild))
val processedReadmeFile = baseDir / "README.md"
2017-10-20 02:48:12 +02:00
state.vcs.add(processedReadmeFile.getAbsolutePath).!!(log)
2017-04-24 20:46:23 +02:00
state
}
2017-09-20 16:55:15 +02:00
val coursierVersionPattern = s"(?m)^${Pattern.quote("def coursierVersion0 = \"")}[^${'"'}]*${Pattern.quote("\"")}$$".r
2017-04-24 20:46:23 +02:00
val updatePluginsSbt = ReleaseStep { state =>
val vcs = state.vcs
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
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))
2017-05-15 15:32:50 +02:00
val projectProjectPluginsSbtFile = baseDir / "project" / "project" / "project" / "plugins.sbt"
2017-04-24 20:46:23 +02:00
val files = Seq(
2017-05-15 15:32:50 +02:00
projectProjectPluginsSbtFile
2017-04-24 20:46:23 +02:00
)
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")
}
2017-09-20 16:55:15 +02:00
val newContent = coursierVersionPattern.replaceAllIn(content, "def coursierVersion0 = \"" + releaseVer + "\"")
2017-04-24 20:46:23 +02:00
Files.write(f.toPath, newContent.getBytes(StandardCharsets.UTF_8))
2017-10-20 02:48:12 +02:00
vcs.add(f.getAbsolutePath).!!(log)
2017-04-24 20:46:23 +02:00
}
state
}
val mimaVersionsPattern = s"(?m)^(\\s+)${Pattern.quote("\"\" // binary compatibility versions")}$$".r
val updateMimaVersions = ReleaseStep { state =>
val vcs = state.vcs
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
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 mimaScalaFile = baseDir / "project" / "Mima.scala"
val content = Source.fromFile(mimaScalaFile)(Codec.UTF8).mkString
mimaVersionsPattern.findAllIn(content).toVector match {
case Seq() => sys.error(s"Found no matches in $mimaScalaFile")
case Seq(_) =>
case _ => sys.error(s"Found too many matches in $mimaScalaFile")
}
val newContent = mimaVersionsPattern.replaceAllIn(
content,
m => {
val indent = m.group(1)
indent + "\"" + releaseVer + "\",\n" +
indent + "\"\" // binary compatibility versions"
}
)
Files.write(mimaScalaFile.toPath, newContent.getBytes(StandardCharsets.UTF_8))
2017-10-20 02:48:12 +02:00
vcs.add(mimaScalaFile.getAbsolutePath).!!(log)
state
}
val updateTestFixture = ReleaseStep(
action = { state =>
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")
}
if (initialVer == nextVer)
state
else {
val vcs = state.vcs
val log = toProcessLogger(state)
val originalFile = Paths.get(s"tests/shared/src/test/resources/resolutions/io.get-coursier/coursier_2.11/$initialVer")
val originalContent = new String(Files.readAllBytes(originalFile), StandardCharsets.UTF_8)
val destFile = Paths.get(s"tests/shared/src/test/resources/resolutions/io.get-coursier/coursier_2.11/$nextVer")
val destContent = originalContent.replace(initialVer, nextVer)
log.out(s"Writing $destFile")
Files.write(destFile, destContent.getBytes(StandardCharsets.UTF_8))
vcs.add(destFile.toAbsolutePath.toString).!!(log)
vcs.cmd("rm", originalFile.toAbsolutePath.toString).!!(log)
state
}
}
)
2017-04-24 20:46:23 +02:00
val commitUpdates = ReleaseStep(
action = { state =>
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-04-24 20:46:23 +02:00
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
2017-10-20 02:48:12 +02:00
state.vcs.commit(s"Updates for $releaseVer", sign = true).!(log)
2017-04-24 20:46:23 +02:00
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
}
)
2017-05-15 15:32:51 +02:00
val addReleaseToManifest = ReleaseStep { state =>
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
val tag = "v" + releaseVer
reapply(
Seq(
// Tag will be one commit after the one with which the publish was really made, because of the commit
// updating scripts / plugins.
packageOptions += ManifestAttributes("Vcs-Release-Tag" -> tag)
),
state
)
}
// tagRelease from sbt-release seem to use the next version (snapshot one typically) rather than the released one :/
val reallyTagRelease = ReleaseStep { state =>
2017-10-20 02:48:12 +02:00
val log = toProcessLogger(state)
2017-05-15 15:32:51 +02:00
val (releaseVer, _) = state.get(ReleaseKeys.versions).getOrElse {
sys.error(s"${ReleaseKeys.versions.label} key not set")
}
val sign = Project.extract(state).get(releaseVcsSign)
val tag = "v" + releaseVer
2017-10-20 02:48:12 +02:00
state.vcs.tag(tag, s"Releasing $tag", sign).!(log)
2017-05-15 15:32:51 +02:00
state
}
2017-04-24 20:46:23 +02:00
val settings = Seq(
releaseProcess := Seq[ReleaseStep](
2017-04-24 20:46:23 +02:00
checkTravisStatus,
2017-04-24 20:46:23 +02:00
checkAppveyorStatus,
2017-04-24 20:46:23 +02:00
savePreviousReleaseVersion,
checkSnapshotDependencies,
inquireVersions,
saveInitialVersion,
setReleaseVersion,
commitReleaseVersion,
2017-05-15 15:32:51 +02:00
addReleaseToManifest,
2017-04-24 20:46:23 +02:00
publishArtifacts,
releaseStepCommand("sonatypeRelease"),
updateScripts,
updateLaunchers,
updateTutReadme,
releaseStepCommand("tut"),
stageReadme,
updatePluginsSbt,
updateMimaVersions,
updateTestFixture,
2017-04-24 20:46:23 +02:00
commitUpdates,
2017-05-15 15:32:51 +02:00
reallyTagRelease,
2017-04-24 20:46:23 +02:00
setNextVersion,
commitNextVersion,
ReleaseStep(_.reload),
pushChanges
),
releasePublishArtifactsAction := PgpKeys.publishSigned.value
)
}