blob: 06cd526546a9b96bdfdc123a08e68dda7286fb61 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
require 'digest/md5'
require 'digest/sha1'
gpg_cmd = 'gpg2'
STAGE_DATE = ENV['STAGE_DATE'] ||'%Y-%m-%d') unless defined?(STAGE_DATE)
RC_VERSION = ENV['RC_VERSION'] || '' unless defined?(RC_VERSION)
task 'prepare' do
# Update source files to next release number.
lambda do
current_version = spec.version.to_s.split('.').map { |v| v.to_i }.
zip([0, 0, 0]).map { |a| a.inject(0) { |t,i| i.nil? ? nil : t + i } }.compact.join('.')
ver_file = "lib/#{}/version.rb"
if File.exist?(ver_file)
modified =\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{current_version}#{$2}" } ver_file, 'w' do |file|
file.write modified
puts "[X] Removed dev suffix from version in #{ver_file}"
# Make sure we're doing a release from checked code.
lambda do
puts 'Checking there are no local changes ... '
git = `git status -s`
fail "Cannot release unless all local changes are in Git:\n#{git}" unless git.empty?
puts '[X] There are no local changes, everything is in source control'
# Make sure we have a valid CHANGELOG entry for this release.
lambda do
puts 'Checking that CHANGELOG indicates most recent version and today''s date ... '
expecting = "#{spec.version} (#{STAGE_DATE})"
header = File.readlines('CHANGELOG').first.chomp
fail "Expecting CHANGELOG to start with #{expecting}, but found #{header} instead" unless expecting == header
puts '[x] CHANGELOG indicates most recent version and today''s date'
# Make sure we have a valid CHANGELOG entry for this release.
lambda do
puts 'Checking that doc/index.textile indicates most recent version and today''s date ... '
expecting = "Highlights from Buildr #{spec.version} (#{STAGE_DATE})"
content ='doc/index.textile')
fail "Expecting doc/index.textile to contain #{expecting}" unless content.include?(expecting)
puts '[x] doc/index.textile indicates most recent version and today''s date'
# Need GPG to sign the packages.
lambda do
raise "ENV['GPG_USER'] not specified" unless ENV['GPG_USER']
gpg_arg = ENV['GPG_USER']
gpg_arg or fail 'Please run with GPG_USER=<argument for gpg --local-user>'
gpg_ok = `gpg2 --list-keys #{gpg_arg}` rescue nil
unless $?.success?
gpg_ok = `gpg --list-keys #{gpg_arg}`
gpg_cmd = 'gpg'
fail "No GPG user #{gpg_arg}" if gpg_ok.empty?
# Need Prince to generate PDF
lambda do
puts 'Checking that we have prince available ... '
sh 'prince --version'
puts '[X] We have prince available'
raise 'Can not run stage process under jruby' if RUBY_PLATFORM[/java/]
raise 'Can not run staging process under older rubies' unless RUBY_VERSION >= '1.9'
task 'stage' => %w(clobber prepare) do
mkpath '_staged'
lambda do
puts 'Ensuring all files have appropriate group and other permissions...'
sh 'find . -type f | xargs chmod go+r'
sh 'find . -type d | xargs chmod go+rx'
puts '[X] File permissions updated/validated.'
# Start by figuring out what has changed.
lambda do
puts 'Looking for changes between this release and previous one ...'
pattern = /(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/
changes ='CHANGELOG').scan(pattern).inject({}) { |hash, set| hash[set[1]] = set[2] ; hash }
current = changes[spec.version.to_s]
fail "No changeset found for version #{spec.version}" unless current '_staged/CHANGES', 'w' do |file|
file.write "#{spec.version} (#{STAGE_DATE})\n"
file.write current
puts '[X] Listed most recent changed in _staged/CHANGES'
# Create the packages (gem, tarball) and sign them. This requires user
# intervention so the earlier we do it the better.
lambda do
puts 'Creating and signing release packages ...'
mkpath '_staged/dist'
FileList['pkg/*.{gem,zip,tgz}'].each do |source|
pkg = source.pathmap('_staged/dist/%n%x')
cp source, pkg
bytes =, 'rb') { |file| } + '.md5', 'w') { |file| file.write Digest::MD5.hexdigest(bytes) << ' ' << File.basename(pkg) } + '.sha1', 'w') { |file| file.write Digest::SHA1.hexdigest(bytes) << ' ' << File.basename(pkg) }
cmd = []
cmd << gpg_cmd
cmd << '--local-user'
cmd << ENV['GPG_USER']
cmd << '--armor'
cmd << '--batch'
cmd << '--passphrase'
cmd << ENV['GPG_PASS']
cmd << '--output'
cmd << pkg + '.asc'
cmd << '--detach-sig'
cmd << pkg
sh *cmd, :verbose=>true
cp 'etc/KEYS', '_staged/dist'
puts '[X] Created and signed release packages in _staged/dist'
# The download page should link to the new binaries/sources, and we
# want to do that before generating the site/documentation.
lambda do
puts 'Updating download page with links to release packages ... '
mirror = "{}/#{spec.version}"
official = "{}/#{spec.version}"
rows = FileList['_staged/dist/*.{gem,tgz,zip}'].map { |pkg|
name, md5 = File.basename(pkg), Digest::MD5.file(pkg).to_s
%{| "#{name}":#{mirror}/#{name} | "#{md5}":#{official}/#{name}.md5 | "Sig":#{official}/#{name}.asc |}
textile = <<-TEXTILE
h3. #{} #{spec.version} (#{STAGE_DATE})
|_. Package |_. MD5 Checksum |_. PGP |
p>. ("Release signing keys":#{official}/KEYS)
file_name = 'doc/download.textile'
print "Adding download links to #{file_name} ... "
modified =
sub(/^h2\(#dist\).*$/) { |header| "#{header}\n\n#{textile}" } file_name, 'w' do |file|
file.write modified
puts "[X] Updated #{file_name}"
# Now we can create the Web site, this includes running specs, coverage report, etc.
# This will take a while, so we want to do it as last step before upload.
lambda do
puts 'Creating new Web site'
cp_r '_site', '_staged/site'
puts '[X] Created new Web site in _staged/site'
# Move everything over to so we can vote on it.
lambda do
puts "Uploading _staged directory ..."
sh "svn mkdir{spec.version}#{RC_VERSION} -m 'Creating Buildr release candidate #{spec.version}#{RC_VERSION}'"
sh "cd _staged; svn checkout{spec.version}#{RC_VERSION} ."
sh "cd _staged; svn add *"
sh "cd _staged; svn commit -m 'Uploading Buildr RC #{spec.version}#{RC_VERSION}'"
puts "[X] Uploaded _staged directory"
# Prepare a release vote email. In the distant future this will also send the
# email for you and vote on it.
lambda do
# Need to know who you are on Apache, local user may be different (see .ssh/config).
base_url = "{spec.version}#{RC_VERSION}"
# Need changes for this release only.
changelog ='CHANGELOG').scan(/(^(\d+\.\d+(?:\.\d+)?)\s+\(\d{4}-\d{2}-\d{2}\)\s*((:?^[^\n]+\n)*))/)
changes = changelog[0][2]
previous_version = changelog[1][1]
email = <<-EMAIL
Subject: [VOTE] Buildr #{spec.version} release
We're voting on the source distributions available here:
The documentation generated for this release is available here:
The following changes were made since #{previous_version}:
#{changes.gsub(/^/, ' ')}
EMAIL 'vote-email.txt', 'w' do |file|
file.write email
puts '[X] Created release vote email template in ''vote-email.txt'''
puts email
task('clobber') { rm_rf '_staged' }
task('clobber') { rm_rf 'vote-email.txt' }