require "open3" module Buildr BUILD_TASKS = { :build =>"Build the project", :clean =>"Clean files generated during a build", :package =>"Create packages", :install =>"Install packages created by the project", :uninstall=>"Remove previously installed packages", :deploy =>"Deploy packages created by the project" } # Handles the build and clean tasks. BUILD_TASKS.each { |name, comment| Project.local_task(task(name)).add_comment(comment) } Project.on_define do |project| BUILD_TASKS.each { |name, comment| project.recursive_task name } end class Project def build(*args, &block) task("build").enhance args, &block end def clean(*args, &block) task("clean").enhance args, &block end end Project.on_define do |project| # Make sure these tasks are defined in the project. project.build project.clean end class Release VERSION_NUMBER_PATTERN = /VERSION_NUMBER\s*=\s*(["'])(.*)\1/ NEXT_VERSION_PATTERN = /NEXT_VERSION\s*=\s*(["'])(.*)\1/ class << self def svn_ignores() @ignores = (@ignores || []).map { |pat| pat.is_a?(Regexp) ? pat : Regexp.new("^.*\s+#{Regexp.escape pat}$") } end end def initialize(rakefile = nil) @rakefile = rakefile || File.read(Rake.application.rakefile) end attr_reader :rakefile def invoke() # Make sure we don't have anything uncommitted in SVN. check_status # Update current version to next version before deploying. next_ver = update_version # Run the deployment externally using the new version number # (from the modified Rakefile). sh "rake deploy" # Update the next version as well to the next increment and commit. update_next_version next_ver # Tag the repository for this release. tag_repository next_ver # Update the next version to end with -SNAPSHOT. update_version_to_snapshot next_ver end def check_status() ignores = Release.svn_ignores status = svn("status", "--ignore-externals", :verbose=>false). reject { |line| line =~ /^X\s/ || ignores.any? { |pat| line =~ pat } } fail "Uncommitted SVN files violate the First Principle Of Release!\n#{status}" unless status.empty? end # Change the Rakefile and update the current version number to the # next version number (VERSION_NUMBER = NEXT_VERSION). We need this # before making a release with the next version. Return the next version. def update_version() rakefile = read_rakefile version = rakefile.scan(VERSION_NUMBER_PATTERN)[0][1] or fail "Looking for VERSION_NUMBER = \"...\" in your Rakefile, none found" next_ver = rakefile.scan(NEXT_VERSION_PATTERN)[0][1] or fail "Looking for NEXT_VERSION = \"...\" in your Rakefile, none found" if verbose puts "Current version: #{version}" puts "Next version: #{next_ver}" end # Switch version numbers. write_rakefile rakefile.gsub(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) } next_ver end # Change the Rakefile and update the next version number to one after # (NEXT_VERSION = NEXT_VERSION + 1). We do this to automatically increment # future version number after each successful release. def update_next_version(version) # Update to new version number. nums = version.split(".") nums[-1] = nums[-1].to_i + 1 next_ver = nums.join(".") write_rakefile read_rakefile.gsub(NEXT_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) } # Commit new version number. svn "commit", "-m", "Changed version number to #{version}", rakefile end # Create a tag in the SVN repository. def tag_repository(version) # Copy to tag. cur_url = svn("info").scan(/URL: (.*)/)[0][0] new_url = cur_url.sub(/trunk$/, "tags/#{version}") svn "remove", new_url, "-m", "Removing old copy" rescue nil svn "copy", cur_url, new_url, "-m", "Release #{version}" end def update_version_to_snapshot(version) version += "-SNAPSHOT" write_rakefile read_rakefile.gsub(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{version}"}) } # Commit new version number. svn "commit", "-m", "Changed version number to #{version}", rakefile end protected def read_rakefile() File.read(rakefile) end def write_rakefile(contents) File.open(rakefile, "w") { |file| file.write contents } end def svn(*args) if Hash === args.last options = args.pop else options = { :verbose=>verbose } end puts ["svn", *args].join(" ") if options[:verbose] Open3.popen3("svn", *args) do |stdin, stdout, stderr| stdin.close error = stderr.read fail error unless error.empty? returning(stdout.read) { |output| puts output if Rake.application.options.trace } end end end desc "Make a release" task("release").tap do |release| class << release def release() @release ||= Release.new end end release.enhance { release.invoke } end end