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 # Collection of options for controlling Buildr. For example for running builds without running # test cases, or running builds in parallel. class Options # Runs the build in parallel when true (defaults to false). You can force a parallel build by # setting this option directly, or by running the parallel task ahead of the build task. # # This option only affects recurvise tasks. For example: # rake parallel package # will run all package tasks (from the sub-projects) in parallel, but each sub-project's package # task runs its child tasks (prepare, compile, resources, etc) in sequence. attr_accessor :parallel end class << self # :call-seq: # options() => Options # # Returns the Buildr options. See Options. def options() @options ||= Options.new end end task("parallel") { Buildr.options.parallel = true } class Project # The target directory. By default, it's the target directory inside the project. Various tasks # use it to determine where to place files, e.g. when compiling or packaging. The clean task # nukes it. def target() @target ||= _("target") end def target=(dir) @target = _(dir) end # :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 Project.on_define do |project| project.clean { verbose(false) { rm_rf project.path_to(:target) } } 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 clean deploy DEBUG=no --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 namespace "buildr" do desc "Freezes the Rakefile so it always uses Buildr version #{Buildr::VERSION}" task "freeze" do gem = %Q{gem "buildr", "#{Buildr::VERSION}"} rakefile = read(Rake.application.rakefile) puts "Freezing the Rakefile so it always uses Buildr version #{Buildr::VERSION}" write Rake.application.rakefile, rakefile =~ /gem\s*(["'])buildr\1/ ? rakefile.sub(/gem\s*(["'])buildr\1\s*,\s*(["']).*\2/, gem) : gem + "\n" + rakefile end desc "Unfreezes the Rakefile to use the latest version of Buildr" task "unfreeze" do puts "Unfreezing the Rakefile to use the latest version of Buildr from your Gems repository" write Rake.application.rakefile, read(Rake.application.rakefile).sub(/^\s*gem\s*(["'])buildr\1.*\n/, "") end end end