require "open3" require "core/project" module Buildr desc "Build the project" Project.local_task("build") { |name| "Building #{name}" } desc "Clean files generated during a build" Project.local_task("clean") { |name| "Cleaning #{name}" } desc "Create packages" Project.local_task("package"=>"build") { |name| "Packaging #{name}" } desc "Install packages created by the project" Project.local_task("install"=>"package") { |name| "Installing packages from #{name}" } desc "Remove previously installed packages" Project.local_task("uninstall") { |name| "Uninstalling packages from #{name}" } desc "Deploy packages created by the project" Project.local_task("deploy"=>"package") { |name| "Deploying packages from #{name}" } [ :build, :clean, :package, :install, :uninstall, :deploy ].each do |name| Project.on_define { |project| project.recursive_task name } end class Project # :call-seq: # build(*prereqs) => task # build { |task| .. } => task # # Returns the project's build task. With arguments or block, also enhances that task. def build(*prereqs, &block) task("build").enhance prereqs, &block end # :call-seq: # build(*prereqs) => task # build { |task| .. } => task # # Returns the project's clean task. With arguments or block, also enhances that task. def clean(*prereqs, &block) task("clean").enhance prereqs, &block end end desc "The default task it build" task "default"=>"build" class Release THIS_VERSION_PATTERN = /THIS_VERSION|VERSION_NUMBER\s*=\s*(["'])(.*)\1/ NEXT_VERSION_PATTERN = /NEXT_VERSION\s*=\s*(["'])(.*)\1/ class << self # :call-seq: # make() # # Make a release. def make() check version = with_next_version { |filename, version| sh "rake deploy --rakefile #{filename}" } tag version commit version + "-SNAPSHOT" end protected # :call-seq: # check() # # Check that we don't have any local changes in the working copy. Fails if it finds anything # in the working copy that is not checked into source control. def check() # Status check reveals modified file, but also SVN externals which we can safely ignore. status = svn("status", "--ignore-externals").reject { |line| line =~ /^X\s/ } fail "Uncommitted SVN files violate the First Principle Of Release!\n#{status}" unless status.empty? end # :call-seq: # with_next_version() { |filename| ... } => version # # Yields to block with upgraded version number, before committing to use it. Returns the *new* # current version number. # # We need a Rakefile with upgraded version numbers to run the build, but we don't want the # Rakefile modified unless the build succeeds. So this method updates the version numbers in # a separate (Rakefile.next) file, yields to the block with that filename, and if successful # copies the new file over the existing one. # # Version numbers are updated as follows. The next release version becomes the current one, # and the next version is upgraded by one to become the new next version. So: # THIS_VERSION = 1.1.0 # NEXT_VERSION = 1.2.0 # becomes: # THIS_VERSION = 1.2.0 # NEXT_VERSION = 1.2.1 # and the method will return 1.2.0. def with_next_version() new_filename = Rake.application.rakefile + ".next" modified = change_version do |this_version, next_version| one_after = next_version.split(".") one_after[-1] = one_after[-1].to_i + 1 [ next_version, one_after.join(".") ] end File.open(new_filename, "w") { |file| file.write modified } begin yield new_filename mv new_filename, Rake.application.rakefile ensure rm new_filename rescue nil end File.read(Rake.application.rakefile).scan(THIS_VERSION_PATTERN)[0][1] end # :call-seq: # change_version() { |this, next| ... } => rakefile # # Change version numbers in the current Rakefile, but without writing a new file (yet). # Returns the contents of the Rakefile with the modified version numbers. # # This method yields to the block with the current (this) and next version numbers and expects # an array with the new this and next version numbers. def change_version() rakefile = File.read(Rake.application.rakefile) this_version = rakefile.scan(THIS_VERSION_PATTERN)[0][1] or fail "Looking for THIS_VERSION = \"...\" in your Rakefile, none found" next_version = rakefile.scan(NEXT_VERSION_PATTERN)[0][1] or fail "Looking for NEXT_VERSION = \"...\" in your Rakefile, none found" this_version, next_version = yield(this_version, next_version) if verbose puts "Upgrading version numbers:" puts " This: #{this_version}" puts " Next: #{next_version}" end rakefile.gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{this_version}"}) }. gsub(NEXT_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_version}"}) } end # :call-seq: # tag(version) # # Tags the current working copy with the release version number. def tag(version) url = svn("info").scan(/URL: (.*)/)[0][0].sub(/trunk$/, "tags/#{version}") svn "remove", url, "-m", "Removing old copy" rescue nil svn "copy", Dir.pwd, url, "-m", "Release #{version}" end # :call-seq: # commit(version) # # Last, we commit what we currently have in the working copy. def commit(version) rakefile = File.read(Rake.application.rakefile). gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{version}"}) } File.open(Rake.application.rakefile, "w") { |file| file.write rakefile } svn "commit", "-m", "Changed version number to #{version}", Rake.application.rakefile end # :call-seq: # svn(*args) # # Executes SVN command and returns the output. def svn(*args) cmd = "svn " + args.map { |arg| arg[" "] ? %Q{"#{arg}"} : arg }.join(" ") puts cmd if verbose `#{cmd}`.tap { fail "SVN command failed" unless $?.exitstatus == 0 } end end end desc "Make a release" task "release" do |task| Release.make end end