Add scripted release

This commit is contained in:
Peter Vlugter 2011-12-09 21:55:49 +13:00
parent 7db3f62ff5
commit ceb888b9a7
11 changed files with 412 additions and 179 deletions

View file

@ -6,6 +6,7 @@
{% extends "basic/layout.html" %}
{% set script_files = script_files + ['_static/theme_extras.js'] %}
{% set css_files = css_files + ['_static/print.css'] %}
{% set is_snapshot = version.endswith("-SNAPSHOT") %}
{# do not display relbars #}
{% block relbar1 %}{% endblock %}
@ -37,7 +38,11 @@
{%- endif -%}
<h1 class="heading"><a href="{{ pathto('index') }}"><span>{{ shorttitle|e }}</span></a></h1>
<h2 class="heading"><a href="{{ pathto('index') }}"><span>Version {{ version|e }}</span></a></h2>
{%- if is_snapshot -%}
<h2 class="rightheading"><span><a href="http://akka.io/docs/akka/snapshot/Akka.pdf">PDF</a></span></h2>
{%- else -%}
<h2 class="rightheading"><span><a href="http://akka.io/docs/akka/{{ version|e }}/Akka.pdf">PDF</a></span></h2>
{%- endif -%}
{%- endblock %}
</div>
<div class="topnav">

View file

@ -24,10 +24,10 @@ object AkkaBuild extends Build {
lazy val akka = Project(
id = "akka",
base = file("."),
settings = parentSettings ++ Unidoc.settings ++ rstdocSettings ++ Seq(
settings = parentSettings ++ Release.settings ++ Unidoc.settings ++ Rstdoc.settings ++ Publish.versionSettings ++ Seq(
parallelExecution in GlobalScope := false,
Unidoc.unidocExclude := Seq(samples.id, tutorials.id),
rstdocDirectory <<= baseDirectory / "akka-docs"
Publish.defaultPublishTo in ThisBuild <<= crossTarget / "repository",
Unidoc.unidocExclude := Seq(samples.id, tutorials.id)
),
aggregate = Seq(actor, testkit, actorTests, stm, remote, slf4j, amqp, mailboxes, akkaSbtPlugin, samples, tutorials, docs)
)
@ -266,7 +266,7 @@ object AkkaBuild extends Build {
// Settings
override lazy val settings = super.settings ++ buildSettings ++ Publish.versionSettings
override lazy val settings = super.settings ++ buildSettings
lazy val baseSettings = Defaults.defaultSettings ++ Publish.settings
@ -349,23 +349,6 @@ object AkkaBuild extends Build {
compileInputs in MultiJvm <<= (compileInputs in MultiJvm) dependsOn (ScalariformKeys.format in MultiJvm),
ScalariformKeys.preferences in MultiJvm := formattingPreferences
)
// reStructuredText docs
val rstdocDirectory = SettingKey[File]("rstdoc-directory")
val rstdoc = TaskKey[File]("rstdoc", "Build the reStructuredText documentation.")
lazy val rstdocSettings = Seq(rstdoc <<= rstdocTask)
def rstdocTask = (rstdocDirectory, streams) map {
(dir, s) => {
s.log.info("Building reStructuredText documentation...")
val exitCode = Process(List("make", "clean", "html", "pdf"), dir) ! s.log
if (exitCode != 0) sys.error("Failed to build docs.")
s.log.info("Done building docs.")
dir
}
}
}
// Dependencies

View file

@ -1,16 +1,19 @@
package akka
import sbt._
import Keys._
import sbt.Keys._
import sbt.Project.Initialize
import java.io.File
object Publish {
final val Snapshot = "-SNAPSHOT"
val defaultPublishTo = SettingKey[File]("default-publish-to")
lazy val settings = Seq(
crossPaths := false,
pomExtra := akkaPomExtra,
publishTo := akkaPublishTo,
publishTo <<= akkaPublishTo,
credentials ++= akkaCredentials,
organizationName := "Typesafe Inc.",
organizationHomepage := Some(url("http://www.typesafe.com"))
@ -32,11 +35,12 @@ object Publish {
</licenses>
}
def akkaPublishTo: Option[Resolver] = {
def akkaPublishTo: Initialize[Option[Resolver]] = {
defaultPublishTo { default =>
val property = Option(System.getProperty("akka.publish.repository"))
val repo = property map { "Akka Publish Repository" at _ }
val m2repo = Path.userHome / ".m2" /"repository"
repo orElse Some(Resolver.file("Local Maven Repository", m2repo))
repo orElse Some(Resolver.file("Default Local Repository", default))
}
}
def akkaCredentials: Seq[Credentials] = {
@ -44,17 +48,11 @@ object Publish {
property map (f => Credentials(new File(f))) toSeq
}
def stampVersion = Command.command("stamp-version") { state =>
append((version in ThisBuild ~= stamp) :: Nil, state)
}
// timestamped versions
// TODO: replace with extracted.append when updated to sbt 0.10.1
def append(settings: Seq[Setting[_]], state: State): State = {
def stampVersion = Command.command("stamp-version") { state =>
val extracted = Project.extract(state)
import extracted._
val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
val newStructure = Load.reapply(session.original ++ append, structure)
Project.setProject(session, newStructure, state)
extracted.append(List(version in ThisBuild ~= stamp), state)
}
def stamp(version: String): String = {

34
project/Release.scala Normal file
View file

@ -0,0 +1,34 @@
package akka
import sbt._
import sbt.Keys._
import java.io.File
object Release {
val releaseDirectory = SettingKey[File]("release-directory")
lazy val settings: Seq[Setting[_]] = commandSettings ++ Seq(
releaseDirectory <<= crossTarget / "release"
)
lazy val commandSettings = Seq(
commands += buildReleaseCommand
)
def buildReleaseCommand = Command.command("build-release") { state =>
val extracted = Project.extract(state)
val release = extracted.get(releaseDirectory)
val releaseVersion = extracted.get(version)
val projectRef = extracted.get(thisProjectRef)
val repo = extracted.get(Publish.defaultPublishTo)
val state1 = extracted.runAggregated(publish in projectRef, state)
val (state2, api) = extracted.runTask(Unidoc.unidoc, state1)
val (state3, docs) = extracted.runTask(Rstdoc.rstdoc, state2)
IO.delete(release)
IO.createDirectory(release)
IO.copyDirectory(repo, release / "releases")
IO.copyDirectory(api, release / "api" / "akka" / releaseVersion)
IO.copyDirectory(docs, release / "docs" / "akka" / releaseVersion)
state3
}
}

34
project/Rstdoc.scala Normal file
View file

@ -0,0 +1,34 @@
package akka
import sbt._
import sbt.Keys._
import java.io.File
object Rstdoc {
val rstdocDirectory = SettingKey[File]("rstdoc-directory")
val rstdocTarget = SettingKey[File]("rstdoc-target")
val rstdoc = TaskKey[File]("rstdoc", "Build the reStructuredText documentation.")
lazy val settings = Seq(
rstdocDirectory <<= baseDirectory / "akka-docs",
rstdocTarget <<= crossTarget / "rstdoc",
rstdoc <<= rstdocTask
)
def rstdocTask = (rstdocDirectory, rstdocTarget, streams) map {
(dir, target, s) => {
s.log.info("Building reStructuredText documentation...")
val logger = new ProcessLogger {
def info(o: => String): Unit = s.log.debug(o)
def error(e: => String): Unit = s.log.debug(e)
def buffer[T](f: => T): T = f
}
val exitCode = Process(List("make", "clean", "html", "pdf"), dir) ! logger
if (exitCode != 0) sys.error("Failed to build docs.")
s.log.info("Creating reStructuredText documentation successful.")
IO.copyDirectory(dir / "_build" / "html", target)
IO.copyFile(dir / "_build" / "latex" / "Akka.pdf", target / "Akka.pdf")
target
}
}
}

View file

@ -1,8 +1,8 @@
package akka
import sbt._
import Keys._
import Project.Initialize
import sbt.Keys._
import sbt.Project.Initialize
object Unidoc {
val unidocDirectory = SettingKey[File]("unidoc-directory")

85
project/scripts/find-replace Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env bash
#
# Find and replace across all source files.
# This script will be called as part of the release script.
# get the source location for this script; handles symlinks
function get_script_path {
local source="${BASH_SOURCE[0]}"
while [ -h "${source}" ] ; do
source="$(readlink "${source}")";
done
echo ${source}
}
# path, name, and dir for this script
declare -r script_path=$(get_script_path)
declare -r script_name=$(basename "${script_path}")
declare -r script_dir="$(cd -P "$(dirname "${script_path}")" && pwd)"
# print usage info
function usage {
echo "Usage: ${script_name} find_expr replace_expr"
}
function echolog {
echo "[${script_name}] $@"
}
declare -r find_expr=$1
declare -r replace_expr=$2
if [ -z "$find_expr" ]; then
usage
exit 1
fi
echolog "$find_expr --> $replace_expr"
# exclude directories from search
declare exclude_dirs=".git dist deploy embedded-repo lib_managed project/boot project/scripts src_managed target"
echolog "excluding directories: $exclude_dirs"
exclude_opts="\("
op="-path"
for dir in $exclude_dirs; do
exclude_opts="${exclude_opts} ${op} '*/${dir}/*'"
op="-or -path"
done
exclude_opts="${exclude_opts} \) -prune -o"
# replace in files
search="find . ${exclude_opts} -type f -print0 | xargs -0 grep -Il \"$find_expr\""
files=$(eval "$search")
simple_diff="diff --old-line-format='[$script_name] - %l
' --new-line-format='[$script_name] + %l
' --changed-group-format='%<%>' --unchanged-group-format=''"
for file in $files; do
echolog $file
# escape / for sed
sedfind=$(echo $find_expr | sed 's/\//\\\//g')
sedreplace=$(echo $replace_expr | sed 's/\//\\\//g')
sed -i '.sed' "s/${sedfind}/${sedreplace}/g" $file
eval "$simple_diff $file.sed $file"
rm -f $file.sed
done
# replace in file names
search="find . ${exclude_opts} -type f -name \"*${find_expr}*\" -print"
files=$(eval "$search")
for file in $files; do
dir=$(dirname $file)
name=$(basename $file)
newname=$(echo $name | sed "s/${find_expr}/${replace_expr}/g")
echolog "$file --> $newname"
mv $file $dir/$newname
done

View file

@ -1,82 +0,0 @@
#!/bin/bash
# Find and replace across all source files.
#
# Example usage:
#
# sh project/scripts/find-replace.sh 1.1-SNAPSHOT 1.1-RC1
#
# This script will be called as part of the sbt release script.
FIND=$1
REPLACE=$2
if [ -z "$FIND" ]; then
echo "Usage: find-replace.sh FIND REPLACE"
exit 1
fi
echo
echo "Find and replace: $FIND --> $REPLACE"
# Exclude directories from search
excludedirs=".git dist deploy embedded-repo lib_managed project/boot project/scripts src_managed target"
echo "Excluding directories: $excludedirs"
excludeopts="\("
op="-path"
for dir in $excludedirs; do
excludeopts="${excludeopts} ${op} '*/${dir}/*'"
op="-or -path"
done
excludeopts="${excludeopts} \) -prune -o"
# Replace in files
search="find . ${excludeopts} -type f -print0 | xargs -0 grep -Il \"${FIND}\""
echo $search
echo
files=$(eval "$search")
simplediff="diff --old-line-format='- %l
' --new-line-format='+ %l
' --changed-group-format='%<%>' --unchanged-group-format=''"
for file in $files; do
echo
echo $file
# escape / for sed
sedfind=$(echo $FIND | sed 's/\//\\\//g')
sedreplace=$(echo $REPLACE | sed 's/\//\\\//g')
sed -i '.sed' "s/${sedfind}/${sedreplace}/g" $file
eval "$simplediff $file.sed $file"
rm -f $file.sed
done
echo
# Replace in file names
search="find . ${excludeopts} -type f -name \"*${FIND}*\" -print"
echo $search
echo
files=$(eval "$search")
for file in $files; do
dir=$(dirname $file)
name=$(basename $file)
newname=$(echo $name | sed "s/${FIND}/${REPLACE}/g")
echo "$file --> $newname"
mv $file $dir/$newname
done
echo

View file

@ -1,35 +0,0 @@
#!/bin/bash
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Usage: push-release.sh VERSION"
exit 1
fi
source ~/.akka-release
if [ -z "$AKKA_RELEASE_SERVER" ]; then
echo "Need AKKA_RELEASE_SERVER to be specified"
exit 1
fi
if [ -z "$AKKA_RELEASE_PATH" ]; then
echo "Need AKKA_RELEASE_PATH to be specified"
exit 1
fi
ref=$(git symbolic-ref HEAD 2> /dev/null)
branch=${ref#refs/heads/}
git push origin $branch
git push origin --tags
release="target/release/${VERSION}"
tmp="/tmp/akka-release-${VERSION}"
rsync -avz ${release}/ ${AKKA_RELEASE_SERVER}:${tmp}/
echo "Verify sudo on $AKKA_RELEASE_SERVER"
ssh -t ${AKKA_RELEASE_SERVER} sudo -v
ssh -t ${AKKA_RELEASE_SERVER} sudo rsync -rpt ${tmp}/ ${AKKA_RELEASE_PATH}
ssh -t ${AKKA_RELEASE_SERVER} rm -rf ${tmp}

244
project/scripts/release Normal file → Executable file
View file

@ -1,10 +1,234 @@
sh git checkout -b releasing-{{release.arg1}}
set akka.release true
clean
script find-replace.sh {{project.version}} {{release.arg1}}
script find-replace.sh //[[:space:]]*release:[[:space:]]*
reload
build-release
sh git add .
sh git commit -am 'Update version for release {{project.version}}'
sh git tag -m 'Version {{project.version}}' v{{project.version}}
#!/usr/bin/env bash
#
# Release script for Akka.
# defaults
declare -r default_server="akka.io"
declare -r default_path="/akka/www"
# settings
declare -r release_dir="target/release"
declare release_server=${default_server}
declare release_path=${default_path}
# flags
unset run_tests
# get the source location for this script; handles symlinks
function get_script_path {
local source="${BASH_SOURCE[0]}"
while [ -h "${source}" ] ; do
source="$(readlink "${source}")";
done
echo ${source}
}
# path, name, and dir for this script
declare -r script_path=$(get_script_path)
declare -r script_name=$(basename "${script_path}")
declare -r script_dir="$(cd -P "$(dirname "${script_path}")" && pwd)"
# print usage info
function usage {
cat <<EOM
Usage: ${script_name} [options] VERSION
-h | --help Print this usage message
-t | --run-tests Run all tests before releasing
-s | --server SERVER Set the release server (default ${default_server})
-p | --path PATH Set the path on the release server (default ${default_path})
EOM
}
# echo a log message
function echolog {
echo "[${script_name}] $@"
}
# echo an error message
function echoerr {
echo "[${script_name}] $@" 1>&2
}
# fail the script with an error message
function fail {
echoerr "$@"
exit 1
}
# process options and set flags
while true; do
case "$1" in
-h | --help ) usage; exit 1 ;;
-t | --run-tests ) run_tests=true; shift ;;
-s | --server ) release_server=$2; shift 2 ;;
-p | --path ) release_path=$2; shift 2 ;;
* ) break ;;
esac
done
if [ $# != "1" ]; then
usage
fail "A release version must be specified"
fi
declare -r version=$1
declare -r publish_path="${release_server}:${release_path}"
# check for a git command
type -P git &> /dev/null || fail "git command not found"
# check for an sbt command
type -P sbt &> /dev/null || fail "sbt command not found"
# get the current git branch
function get_current_branch {
local ref=$(git symbolic-ref HEAD 2> /dev/null)
local branch=${ref#refs/heads/}
echo "${branch}"
}
# get the current project version from sbt
# a little messy as the ansi escape codes are included
function get_current_version {
local result=$(sbt version | tail -1 | cut -f2)
# remove ansi escape code from end
local code0=$(echo -e "\033[0m")
echo ${result%$code0}
}
# store the current git branch for cleaning up
declare -r initial_branch=$(get_current_branch)
# check we have an initial branch
[[ "${initial_branch}" ]] || fail "Not on a git branch"
# check that we have a clean status
[[ -z "$(git status --porcelain)" ]] || {
git status
fail "There are uncommitted changes - please commit before releasing"
}
# the branch we'll release on
declare -r release_branch="releasing-${version}"
# try to run a cleanup command - these shouldn't actually fail
function safely {
"$@" || fail "Failed to clean up release - please check current state"
}
# perform a clean up when a failure has occurred
function git_cleanup {
echoerr "Cleaning up..."
local branch=$(get_current_branch)
safely git reset --hard
safely git clean -f
if [ "${branch}" == "${release_branch}" ]; then
safely git checkout ${initial_branch}
safely git branch -d ${release_branch}
local tags=$(git tag -l)
[[ "${tags}" == *v${version}* ]] && safely git tag -d v${version}
fi
echoerr "Cleaned up failed release"
}
# clean up and fail the script with an error message
function bail_out {
echoerr "Bailing out!"
git_cleanup
fail "$@"
}
# bail out for signals
function signal_bail_out {
echoerr "Interrupted by signal"
bail_out "Received signal to stop release"
}
# bail out on signals
trap signal_bail_out SIGHUP SIGINT SIGTERM
# try to run a command or otherwise bail out
function try {
"$@" || bail_out "Failed to create release"
}
echolog "Creating release ${version} ..."
echolog "Publishing to ${publish_path}"
[[ $run_tests ]] && echolog "All tests will be run"
# try ssh'ing to the release server
echolog "Checking ssh connection to ${release_server}"
try ssh -t ${release_server} echo "Successfully contacted release server."
echolog "Getting current project version from sbt..."
declare -r current_version=$(get_current_version)
echolog "Current version is ${current_version}"
# check out a release branch
try git checkout -b ${release_branch}
# find and replace the version
try ${script_dir}/find-replace ${current_version} ${version}
# start clean
try sbt clean
# run the tests if specified
if [ $run_tests ]; then
echolog "Running all tests..."
try sbt test
echolog "All tests are green"
fi
# build the release
echolog "Building the release..."
try sbt build-release
echolog "Successfully created local release"
# commit and tag this release
echolog "Committing and tagging..."
try git add .
try git commit -am "Update version for release ${version}"
try git tag -m "Version ${version}" v${version}
# the point of no return... we're now pushing out to servers
# use a special failure from now on
function arrgh {
cat 1>&2 <<EOM
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Release failed while pushing to servers!
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
EOM
fail "Could not complete release - please check current state"
}
# try running a pushing command or otherwise fail
function important {
"$@" || arrgh
}
# new interrupted bail out for signals
function arrgh_interrupt {
echoerr "Interrupted by signal"
arrgh
}
# new exit on signals
trap arrgh_interrupt SIGHUP SIGINT SIGTERM
# push the commits and tags to git origin
echolog "Pushing to git origin..."
important git push origin ${release_branch}
important git push origin --tags
# push the release to the server
echolog "Pushing ${release_dir} to ${publish_path} ..."
important rsync -rlpvz --chmod=Dg+ws,Fg+w ${release_dir}/ ${publish_path}/
echolog "Switching back to initial branch"
git checkout ${initial_branch}
echolog "Successfully created release ${version}"

View file

@ -1,13 +0,0 @@
set akka.release true
clean
clean-lib
script find-replace.sh {{project.version}} {{test-release.arg1}}
script find-replace.sh //[[:space:]]*release:[[:space:]]*
reload
update
test-compile
test
build-release
sh git reset --hard
sh git clean -f
reload