Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global release switch, tag prefix #37

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
19 changes: 7 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ organization := "com.github.gseitz"

name := "sbt-release"

version := "0.8-SNAPSHOT"

unmanagedSourceDirectories in Compile <+= (sbtVersion, sourceDirectory in Compile) ((sv, sd) => new File(sd, "scala-sbt-" + sv))
version := "0.8-BBSNAPSHOT"

sbtPlugin := true

publishTo <<= (version) { version: String =>
val scalasbt = "http://scalasbt.artifactoryonline.com/scalasbt/"
val (name, url) = if (version.contains("-SNAPSHOT"))
("sbt-plugin-snapshots", scalasbt+"sbt-plugin-snapshots")
else
("sbt-plugin-releases", scalasbt+"sbt-plugin-releases")
Some(Resolver.url(name, new URL(url))(Resolver.ivyStylePatterns))
}
publishTo := Some("backuity" at "http://dev.backuity.com:8081/nexus/content/repositories/releases/")

credentials += Credentials("Sonatype Nexus Repository Manager", "dev.backuity.com", "developer", "b@ckuit1")

crossBuildingSettings

publishMavenStyle := false
CrossBuilding.crossSbtVersions := Seq("0.11.3", "0.12", "0.13")
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.11.3
sbt.version=0.12.4
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("net.virtual-void" % "sbt-cross-building" % "0.8.0")
31 changes: 31 additions & 0 deletions src/main/scala-sbt-0.13/SbtCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package sbtrelease

import sbt._
import sbt.Aggregation.KeyValue
import sbt.std.Transform.DummyTaskMap
import Utilities._

object SbtCompat {
val EmptySetting = List.empty[String]

def runTaskAggregated[T](taskKey: TaskKey[T], state: State) = {
import EvaluateTask._
val extra = DummyTaskMap(Nil)
val extracted = state.extract
val config = extractedConfig(extracted, extracted.structure)

val rkey = Utilities.resolve(taskKey.scopedKey, extracted)
val keys = Aggregation.aggregate(rkey, ScopeMask(), extracted.structure.extra)
val tasks = Act.keyValues(extracted.structure)(keys)
val toRun = tasks map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join;
val roots = tasks map { case KeyValue(k,_) => k }


val (newS, result) = withStreams(extracted.structure, state){ str =>
val transform = nodeView(state, str, roots, extra)
runTask(toRun, state,str, extracted.structure.index.triggers, config)(transform)
}
(newS, result)
}

}
92 changes: 80 additions & 12 deletions src/main/scala/ReleaseExtra.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package sbtrelease

import java.io.File
import sbt._
import Keys._
import annotation.tailrec
Expand Down Expand Up @@ -33,8 +32,31 @@ object ReleaseStateTransformations {
newSt
}

lazy val inquireVersions = ReleaseStep(inquireVersionsAction, homogeneousVersionsCheck)
private lazy val homogeneousVersionsCheck = { st: State =>
val extracted = st.extract

lazy val inquireVersions: ReleaseStep = { st: State =>
if( st.get(globalRelease) ) {
// as we set one global version for all projects (version in ThisBuild)
// we have to make sure versions are homogeneous across aggregated projects
// so that we don't publish aggregates with incorrect versions (for instance a SNAPSHOT)
val rootVersion = extracted.get(version)
for(aggregate <- extracted.currentProject.aggregate) {
val aggregateVersion = extracted.get(version.in(aggregate))
if( aggregateVersion != rootVersion ) {
sys.error("Aggregated project '%s' has version '%s' which differs from its root : '%s'" format (aggregate.project, aggregateVersion, rootVersion))
sys.error("You probably have multiple 'version.sbt' files. sbt-release only support one identical version for all aggregated projects.")
}
}
} else {
// make sure we have no aggregated projects
if( !extracted.currentProject.aggregate.isEmpty ) {
sys.error("Non global release cannot work on aggregated projects.")
}
}
st
}
private lazy val inquireVersionsAction = { st: State =>
val extracted = Project.extract(st)

val useDefs = st.get(useDefaults).getOrElse(false)
Expand Down Expand Up @@ -68,16 +90,45 @@ object ReleaseStateTransformations {
lazy val setNextVersion: ReleaseStep = setVersion(_._2)
private[sbtrelease] def setVersion(selectVersion: Versions => String): ReleaseStep = { st: State =>
val vs = st.get(versions).getOrElse(sys.error("No versions are set! Was this release part executed before inquireVersions?"))
val selected = selectVersion(vs)
val newVersion = selectVersion(vs)

val currentVersion = st.extract.get(version)
if (newVersion == currentVersion) {
st.log.info("No version update needed, version remains '%s'" format currentVersion)
st
} else {
st.log.info("Setting version to '%s'." format newVersion)

st.log.info("Setting version to '%s'." format selected)
writeVersionToFile(newVersion, st)

// ideally we'd like to reload the project with the updated file...

val newVersionSettings = if( st.get(globalRelease) ) {
Seq(version in ThisBuild := newVersion)
} else {
Nil
} ++ Seq(version := newVersion) // always set version scoped to the current project as it precedes version scoped to ThisBuild (see below)
//
// under global release, if the previous version.sbt file had its version defined as `version := "xxx"`
// it would override 'version in ThisBuild' so we could be releasing a SNAPSHOT instead

val versionString = "%sversion in ThisBuild := \"%s\"%s" format (lineSep, selected, lineSep)
IO.write(new File("version.sbt"), versionString)
val newSt = reapply(newVersionSettings, st)

reapply(Seq(
version in ThisBuild := selected
), st)
homogeneousVersionsCheck(newSt)
}
}

private def writeVersionToFile(version: String, st: State) {
val scope = if( st.get(globalRelease) ) "in ThisBuild" else "" // scope to the current project
val versionString = "%sversion %s := \"%s\"%s" format (lineSep, scope, version, lineSep)
val file = versionFile(st)
st.log.info("Updating " + file.getAbsolutePath)
IO.write(file, versionString)
}


private def versionFile(st: State): File = {
new File(st.extract.get(baseDirectory), "version.sbt")
}

private def vcs(st: State): Vcs = {
Expand All @@ -87,7 +138,21 @@ object ReleaseStateTransformations {
private[sbtrelease] lazy val initialVcsChecks = { st: State =>
val status = (vcs(st).status !!).trim
if (status.nonEmpty) {
sys.error("Aborting release. Working directory is dirty.")
if( st.get(interactiveCommit) ) {
st.log.info("Working directory is dirty:")
st.log.info("\n\t" + status.replaceAll("\\n","\n\t"))

SimpleReader.readLine("\nEnter a message to add everything and commit: ") match {
case Some(message) =>
vcs(st).addAll !! st.log
vcs(st).commit(message) !! st.log

case None =>
sys.error("No message entered. Aborting release!")
}
} else {
sys.error("Aborting release. Working directory is dirty.")
}
}

st.log.info("Starting release process off commit: " + vcs(st).currentHash)
Expand All @@ -106,7 +171,7 @@ object ReleaseStateTransformations {

lazy val commitNextVersion = ReleaseStep(commitVersion)
private[sbtrelease] def commitVersion = { st: State =>
vcs(st).add("version.sbt") !! st.log
vcs(st).add(versionFile(st).getAbsolutePath) !! st.log
val status = (vcs(st).status !!) trim

val newState = if (status.nonEmpty) {
Expand All @@ -128,7 +193,7 @@ object ReleaseStateTransformations {
if (vcs(st).existsTag(tag)) {
defaultChoice orElse SimpleReader.readLine("Tag [%s] exists! Overwrite, keep or abort or enter a new tag (o/k/a)? [a] " format tag) match {
case Some("" | "a" | "A") =>
sys.error("Aborting release!")
sys.error("Tag [%s] already exist. Aborting release!" format tag)

case Some("k" | "K") =>
st.log.warn("The current tag [%s] does not point to the commit for this release!" format tag)
Expand Down Expand Up @@ -298,10 +363,13 @@ object ExtraReleaseCommands {


object Utilities {

val lineSep = sys.props.get("line.separator").getOrElse(sys.error("No line separator? Really?"))

class StateW(st: State) {
def extract = Project.extract(st)
def get[T](a: AttributeKey[T]) : Option[T] = st.attributes.get(a)
def get[T](s: SettingKey[T]) : T = extract.get(s)
}
implicit def stateW(st: State): StateW = new StateW(st)

Expand Down
17 changes: 14 additions & 3 deletions src/main/scala/ReleasePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ object ReleasePlugin extends Plugin {
lazy val releaseProcess = SettingKey[Seq[ReleaseStep]]("release-process")
lazy val releaseVersion = SettingKey[String => String]("release-release-version")
lazy val nextVersion = SettingKey[String => String]("release-next-version")
lazy val tagPrefix = SettingKey[String]("tag-prefix", "Allow to prefix tag-name, tag-comment and commit-message at once")
lazy val tagName = TaskKey[String]("release-tag-name")
lazy val tagComment = TaskKey[String]("release-tag-comment")
lazy val commitMessage = TaskKey[String]("release-commit-message")

// a non-global release will generate version.sbt file containing a version scoped to the current project, not to the build
lazy val globalRelease = SettingKey[Boolean]("global-release", "Release the current project and all its aggregated projects as one global project under " +
"a common version (i.e a version scoped to the build), otherwise release only the current project allowing a different version scheme than its parent " +
"(i.e a version scoped to the project only)")
lazy val interactiveCommit = SettingKey[Boolean]("release-interactive-commit", "If the repository is dirty, allow the user to commit within the SBT shell")

lazy val versionControlSystem = SettingKey[Option[Vcs]]("release-vcs")

lazy val versions = AttributeKey[Versions]("release-versions")
Expand Down Expand Up @@ -58,12 +65,16 @@ object ReleasePlugin extends Plugin {
snapshots
},

interactiveCommit := true,
globalRelease := true,

releaseVersion := { ver => Version(ver).map(_.withoutQualifier.string).getOrElse(versionFormatError) },
nextVersion := { ver => Version(ver).map(_.bumpMinor.asSnapshot.string).getOrElse(versionFormatError) },

tagName <<= (version in ThisBuild) map (v => "v" + v),
tagComment <<= (version in ThisBuild) map (v => "Releasing %s" format v),
commitMessage <<= (version in ThisBuild) map (v => "Setting version to %s" format v),
tagPrefix := "",
tagName <<= (version, tagPrefix) map {(v,pref) => pref + "v" + v},
tagComment <<= (version, tagPrefix) map ((v,pref) => "Releasing %s%s" format (pref,v)),
commitMessage <<= (version, tagPrefix) map ((v,pref) => "Setting version to %s%s" format (pref,v)),

versionControlSystem <<= (baseDirectory)(Vcs.detect(_)),

Expand Down
5 changes: 5 additions & 0 deletions src/main/scala/Vcs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trait Vcs {
def status: ProcessBuilder
def currentHash: String
def add(files: String*): ProcessBuilder
def addAll: ProcessBuilder
def commit(message: String): ProcessBuilder
def existsTag(name: String): Boolean
def checkRemote(remote: String): ProcessBuilder
Expand Down Expand Up @@ -60,6 +61,8 @@ object Mercurial extends Vcs with GitLike {

protected val markerDirectory = ".hg"

def addAll = cmd("add")

def status = cmd("status")

def currentHash = (cmd("identify", "-i") !!) trim
Expand Down Expand Up @@ -93,6 +96,8 @@ object Git extends Vcs with GitLike {
private lazy val trackingRemoteCmd: ProcessBuilder = cmd("config", "branch.%s.remote" format currentBranch)
def trackingRemote: String = (trackingRemoteCmd !!) trim

def addAll = cmd("add","-A",".")

def hasUpstream = trackingRemoteCmd ! devnull == 0 && trackingBranchCmd ! devnull == 0

def currentBranch = (cmd("symbolic-ref", "HEAD") !!).trim.stripPrefix("refs/heads/")
Expand Down