diff --git a/build.sbt b/build.sbt index 0dbe9d0..d103cd0 100644 --- a/build.sbt +++ b/build.sbt @@ -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") diff --git a/project/build.properties b/project/build.properties index d428711..5e96e96 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.11.3 +sbt.version=0.12.4 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..cb2afac --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("net.virtual-void" % "sbt-cross-building" % "0.8.0") \ No newline at end of file diff --git a/src/main/scala-sbt-0.12.2/SbtCompat.scala b/src/main/scala-sbt-0.12/SbtCompat.scala similarity index 100% rename from src/main/scala-sbt-0.12.2/SbtCompat.scala rename to src/main/scala-sbt-0.12/SbtCompat.scala diff --git a/src/main/scala-sbt-0.13/SbtCompat.scala b/src/main/scala-sbt-0.13/SbtCompat.scala new file mode 100644 index 0000000..57b6288 --- /dev/null +++ b/src/main/scala-sbt-0.13/SbtCompat.scala @@ -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) + } + +} diff --git a/src/main/scala/ReleaseExtra.scala b/src/main/scala/ReleaseExtra.scala index 7bb0a5a..1e343c3 100644 --- a/src/main/scala/ReleaseExtra.scala +++ b/src/main/scala/ReleaseExtra.scala @@ -1,6 +1,5 @@ package sbtrelease -import java.io.File import sbt._ import Keys._ import annotation.tailrec @@ -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) @@ -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 = { @@ -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) @@ -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) { @@ -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) @@ -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) diff --git a/src/main/scala/ReleasePlugin.scala b/src/main/scala/ReleasePlugin.scala index b220b46..2953d75 100644 --- a/src/main/scala/ReleasePlugin.scala +++ b/src/main/scala/ReleasePlugin.scala @@ -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") @@ -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(_)), diff --git a/src/main/scala/Vcs.scala b/src/main/scala/Vcs.scala index a384324..d709374 100644 --- a/src/main/scala/Vcs.scala +++ b/src/main/scala/Vcs.scala @@ -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 @@ -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 @@ -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/")