You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by la...@apache.org on 2008/08/28 00:04:19 UTC

svn commit: r689640 - in /incubator/buildr/trunk: CHANGELOG lib/buildr/core/build.rb spec/build_spec.rb

Author: lacton
Date: Wed Aug 27 15:04:19 2008
New Revision: 689640

URL: http://svn.apache.org/viewvc?rev=689640&view=rev
Log:
Release task: added support for alternative SVN layout, fixed regexp for THIS_VERSION, fixed calls to Buildr.application.buildfile.

Added tests for Release task.  Extracted an Svn class to make testing easier.

SVN has two official layouts.  Reference: http://svnbook.red-bean.com/en/1.4/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout
Added support for the other one.

Buildr.application.buildfile is no longer a file path, but a FileTask.  This change requires to convert the Task to a file path.

Fixed a few typos and extracted a few methods.


Modified:
    incubator/buildr/trunk/CHANGELOG
    incubator/buildr/trunk/lib/buildr/core/build.rb
    incubator/buildr/trunk/spec/build_spec.rb

Modified: incubator/buildr/trunk/CHANGELOG
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/CHANGELOG?rev=689640&r1=689639&r2=689640&view=diff
==============================================================================
--- incubator/buildr/trunk/CHANGELOG (original)
+++ incubator/buildr/trunk/CHANGELOG Wed Aug 27 15:04:19 2008
@@ -2,6 +2,8 @@
 * Added:  Growl notifications (OS X only).
 * Added:  error, info and trace methods.
 * Added:  BUILDR-128 Emma support
+* Added:  Release task support for alternative SVN repository layout
+          (e.g., http://my.repo.org/trunk/foo).
 * Change: Error reporting now shows 'buildr aborted!' (used to say rake),
           more of the stack trace without running --trace, and when running
           with supported terminal, error message is red.
@@ -34,6 +36,7 @@
 * Fixed:  BUILDR-126  Tests options are shared between unrelated projects when
           using #options instead of #using (Lacton).
 * Fixed:  Should not display "(in `pwd`, development)" when using --quiet.
+* Fixed:  Release task's regexp to find either THIS_VERSION and VERSION_NUMBER.
 * Docs:   BUILDR-111 Troubleshoot tip when Buildr's bin directory shows up in
           RUBYLIB (Geoffrey Ruscoe).
 

Modified: incubator/buildr/trunk/lib/buildr/core/build.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/core/build.rb?rev=689640&r1=689639&r2=689640&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/core/build.rb (original)
+++ incubator/buildr/trunk/lib/buildr/core/build.rb Wed Aug 27 15:04:19 2008
@@ -27,7 +27,7 @@
     # 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:
+    # This option only affects recursive tasks. For example:
     #   buildr 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.
@@ -48,7 +48,7 @@
       desc 'Clean files generated during a build'
       Project.local_task('clean') { |name| "Cleaning #{name}" }
 
-      desc 'The default task it build'
+      desc 'The default task is build'
       task 'default'=>'build'
     end
 
@@ -109,9 +109,47 @@
   end
 
 
+  class Svn
+
+    class << self
+      def commit file, message
+        svn 'commit', '-m', message, file
+      end
+      
+      def copy dir, url, message
+        svn 'copy', dir, url, '-m', message
+      end
+      
+      # Return the current SVN URL
+      def repo_url
+        url = svn('info').scan(/URL: (.*)/)[0][0]
+      end
+      
+      def remove url, message
+        svn 'remove', url, '-m', message
+      end
+      
+      # Status check reveals modified files, but also SVN externals which we can safely ignore.
+      def uncommitted_files
+        svn('status', '--ignore-externals').reject { |line| line =~ /^X\s/ }
+      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(' ')
+        info cmd
+        `#{cmd}`.tap { fail 'SVN command failed' unless $?.exitstatus == 0 }
+      end
+    end
+  end
+  
+  
   class Release
 
-    THIS_VERSION_PATTERN  = /THIS_VERSION|VERSION_NUMBER\s*=\s*(["'])(.*)\1/
+    THIS_VERSION_PATTERN  = /(THIS_VERSION|VERSION_NUMBER)\s*=\s*(["'])(.*)\2/
     NEXT_VERSION_PATTERN  = /NEXT_VERSION\s*=\s*(["'])(.*)\1/
 
     class << self
@@ -122,7 +160,7 @@
       # Make a release.
       def make()
         check
-        version = with_next_version do |filename, version| 
+        version = with_next_version do |filename| 
           options = ['--buildfile', filename, 'DEBUG=no']
           options << '--environment' << Buildr.environment unless Buildr.environment.to_s.empty?
           sh "#{command} _#{Buildr::VERSION}_ clean upload #{options.join(' ')}"
@@ -131,23 +169,55 @@
         commit version + '-SNAPSHOT'
       end
 
-    protected
-
-      def command() #:nodoc:
-        Config::CONFIG['arch'] =~ /dos|win32/i ? $PROGRAM_NAME.ext('cmd') : $PROGRAM_NAME
+      # :call-seq:
+      #   extract_versions(buildfile) => this_version, next_version
+      #
+      # Extract the current and next version numbers from a buildfile.
+      # Raise an error if not found.
+      def extract_versions buildfile
+        begin
+          this_version = buildfile.scan(THIS_VERSION_PATTERN)[0][2]
+        rescue
+          fail 'Looking for THIS_VERSION = "..." in your Buildfile, none found'
+        end
+        begin
+          next_version = buildfile.scan(NEXT_VERSION_PATTERN)[0][1]
+        rescue
+          fail 'Looking for NEXT_VERSION = "..." in your Buildfile, none found'
+        end
+        [this_version, next_version]
       end
-
+      
+      # :call-seq:
+      #   tag_url(svn_url, version) => tag_url
+      #
+      # Returns the SVN url for the tag.
+      # Can tag from the trunk or from branches.
+      # Can handle the two standard repository layouts.
+      #   - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0
+      #   - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0
+      def tag_url svn_url, version
+        trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$})
+        match = trunk_or_branches.match(svn_url)
+        prefix = match[1] || match[3]
+        suffix = match[2] || match[4]
+        prefix + '/tags' + suffix + '/' + version
+      end
+      
       # :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()
-        fail "SVN URL must end with 'trunk' or 'branches/...'" unless svn_url =~ /(trunk)|(branches.*)$/
-        # 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?
+        fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/
+        fail "Uncommitted SVN files violate the First Principle Of Release!\n#{Svn.uncommitted_files}" unless Svn.uncommitted_files.empty?
+      end
+
+    protected
+
+      def command() #:nodoc:
+        Config::CONFIG['arch'] =~ /dos|win32/i ? $PROGRAM_NAME.ext('cmd') : $PROGRAM_NAME
       end
 
       # :call-seq:
@@ -170,7 +240,7 @@
       #   NEXT_VERSION = 1.2.1
       # and the method will return 1.2.0.
       def with_next_version()
-        new_filename = Buildr.application.buildfile + '.next'
+        new_filename = Buildr.application.buildfile.to_s + '.next'
         modified = change_version do |this_version, next_version|
           one_after = next_version.split('.')
           one_after[-1] = one_after[-1].to_i + 1
@@ -179,11 +249,11 @@
         File.open(new_filename, 'w') { |file| file.write modified }
         begin
           yield new_filename
-          mv new_filename, Buildr.application.buildfile
+          mv new_filename, Buildr.application.buildfile.to_s
         ensure
           rm new_filename rescue nil
         end
-        File.read(Buildr.application.buildfile).scan(THIS_VERSION_PATTERN)[0][1]
+        extract_versions(File.read(Buildr.application.buildfile.to_s))[0]
       end
 
       # :call-seq:
@@ -195,11 +265,8 @@
       # 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()
-        buildfile = File.read(Buildr.application.buildfile)
-        this_version = buildfile.scan(THIS_VERSION_PATTERN)[0][1] or
-          fail "Looking for THIS_VERSION = \"...\" in your Buildfile, none found"
-        next_version = buildfile.scan(NEXT_VERSION_PATTERN)[0][1] or
-          fail "Looking for NEXT_VERSION = \"...\" in your Buildfile, none found"
+        buildfile = File.read(Buildr.application.buildfile.to_s)
+        this_version, next_version = extract_versions buildfile
         this_version, next_version = yield(this_version, next_version)
         if verbose
           puts 'Upgrading version numbers:'
@@ -215,9 +282,9 @@
       #
       # Tags the current working copy with the release version number.
       def tag(version)
-        url = svn_url.sub(/(trunk$)|(branches.*)$/, "tags/#{version}")
-        svn 'remove', url, '-m', 'Removing old copy' rescue nil
-        svn 'copy', Dir.pwd, url, '-m', "Release #{version}"
+        url = tag_url Svn.repo_url, version
+        Svn.remove url, 'Removing old copy' rescue nil
+        Svn.copy Dir.pwd, url, "Release #{version}"
       end
 
       # :call-seq:
@@ -225,30 +292,15 @@
       #
       # Last, we commit what we currently have in the working copy.
       def commit(version)
-        buildfile = File.read(Buildr.application.buildfile).
+        buildfile = File.read(Buildr.application.buildfile.to_s).
           gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{version}"}) }
-        File.open(Buildr.application.buildfile, 'w') { |file| file.write buildfile }
-        svn 'commit', '-m', "Changed version number to #{version}", Buildr.application.buildfile
-      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(' ')
-        info cmd
-        `#{cmd}`.tap { fail 'SVN command failed' unless $?.exitstatus == 0 }
-      end
-
-      # Return the current SVN URL
-      def svn_url
-        url = svn('info').scan(/URL: (.*)/)[0][0]
+        File.open(Buildr.application.buildfile.to_s, 'w') { |file| file.write buildfile }
+        Svn.commit Buildr.application.buildfile.to_s, "Changed version number to #{version}"
       end
     end
-
   end
 
+  
   desc 'Make a release'
   task 'release' do |task|
     Release.make

Modified: incubator/buildr/trunk/spec/build_spec.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/build_spec.rb?rev=689640&r1=689639&r2=689640&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/build_spec.rb (original)
+++ incubator/buildr/trunk/spec/build_spec.rb Wed Aug 27 15:04:19 2008
@@ -191,3 +191,184 @@
     @project.reports.should eql('baz')
   end
 end
+
+
+describe Buildr::Release, '#check' do
+  before do
+    Buildr::Svn.stub!(:uncommitted_files).and_return('')
+  end
+  
+  it 'should accept to release from the trunk' do
+    Buildr::Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
+    lambda { Release.check }.should_not raise_error
+  end
+  
+  it 'should accept to release from a branch' do
+    Buildr::Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/branches/1.0')
+    lambda { Release.check }.should_not raise_error
+  end
+  
+  it 'should reject to release from a tag' do
+    Buildr::Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/tags/1.0.0')
+    lambda { Release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'")
+  end
+  
+  it 'should reject a non standard repository layout' do
+    Buildr::Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/bar')
+    lambda { Release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'")
+  end
+  
+  it 'should reject an uncommitted file' do
+    Buildr::Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
+    Buildr::Svn.stub!(:uncommitted_files).and_return('M      foo.rb')
+    lambda { Release.check }.should raise_error(RuntimeError,
+      "Uncommitted SVN files violate the First Principle Of Release!\n" +
+      "M      foo.rb")
+  end
+end
+  
+
+describe Buildr::Release, '#extract_versions' do
+  
+  it 'should extract VERSION_NUMBER and NEXT_VERSION with single quotes' do
+    buildfile = ["VERSION_NUMBER = '1.0.0-SNAPSHOT'", "NEXT_VERSION = '1.0.1'"].join("\n")
+    Release.extract_versions(buildfile).should == ['1.0.0-SNAPSHOT', '1.0.1']
+  end
+  
+  it 'should extract VERSION_NUMBER and NEXT_VERSION with double quotes' do
+    buildfile = [%{VERSION_NUMBER = "1.0.1-SNAPSHOT"}, %{NEXT_VERSION = "1.0.2"}].join("\n")
+    Release.extract_versions(buildfile).should == ['1.0.1-SNAPSHOT', '1.0.2']
+  end
+  
+  it 'should extract VERSION_NUMBER and NEXT_VERSION without any spaces' do
+    buildfile = ["VERSION_NUMBER='1.0.2-SNAPSHOT'", "NEXT_VERSION='1.0.3'"].join("\n")
+    Release.extract_versions(buildfile).should == ['1.0.2-SNAPSHOT', '1.0.3']
+  end
+  
+  it 'should extract THIS_VERSION as an alternative to VERSION_NUMBER' do
+    buildfile = ["THIS_VERSION = '1.0.3-SNAPSHOT'", "NEXT_VERSION = '1.0.4'"].join("\n")
+    Release.extract_versions(buildfile).should == ['1.0.3-SNAPSHOT', '1.0.4']
+  end
+  
+  it 'should complain if no current version number' do
+    buildfile = "NEXT_VERSION = '1.0.1'"
+    lambda { Release.extract_versions(buildfile) }.should raise_error('Looking for THIS_VERSION = "..." in your Buildfile, none found')
+  end
+  
+  it 'should complain if no next version number' do
+    buildfile = "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
+    lambda { Release.extract_versions(buildfile) }.should raise_error('Looking for NEXT_VERSION = "..." in your Buildfile, none found')
+  end
+end
+
+
+describe Buildr::Svn, '#repo_url' do
+  it 'should extract the SVN URL from svn info' do
+    Svn.stub!(:svn, 'info').and_return(<<EOF)
+Path: .
+URL: http://my.repo.org/foo/trunk
+Repository Root: http://my.repo.org
+Repository UUID: 12345678-9abc-def0-1234-56789abcdef0
+Revision: 112
+Node Kind: directory
+Schedule: normal
+Last Changed Author: Lacton
+Last Changed Rev: 110
+Last Changed Date: 2008-08-19 12:00:00 +0200 (Tue, 19 Aug 2008)
+EOF
+    Svn.repo_url.should == 'http://my.repo.org/foo/trunk'
+  end
+end
+
+
+# Reference: http://svnbook.red-bean.com/en/1.4/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout
+describe Buildr::Release, '#tag url' do
+  it 'should accept to tag foo/trunk' do
+    Release.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/1.0.0'
+  end
+  
+  it 'should accept to tag foo/branches/1.0' do
+    Release.tag_url('http://my.repo.org/foo/branches/1.0', '1.0.1').should == 'http://my.repo.org/foo/tags/1.0.1'
+  end
+  
+  it 'should accept to tag trunk/foo' do
+    Release.tag_url('http://my.repo.org/trunk/foo', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
+  end
+  
+  it 'should accept to tag branches/foo/1.0' do
+    Release.tag_url('http://my.repo.org/branches/foo/1.0', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
+  end
+end
+
+
+describe Buildr::Release, '#with_next_version' do
+  before do
+    Buildr.application.stub!(:buildfile).and_return(file('buildfile'))
+    write 'buildfile', <<-EOF
+      THIS_VERSION = '1.1.0'
+      NEXT_VERSION = '1.2.0'
+      EOF
+  end
+  
+  it 'should yield the name of an updated buildfile' do
+    Release.send :with_next_version do |new_filename|
+      File.read(new_filename).should == <<-EOF
+      THIS_VERSION = "1.2.0"
+      NEXT_VERSION = "1.2.1"
+      EOF
+    end
+  end
+  
+  it 'should yield a name different from the original buildfile' do
+    Release.send :with_next_version do |new_filename|
+      new_filename.should_not point_to_path('buildfile')
+    end
+  end
+  
+  it 'should return the new version number' do
+    new_version = Release.send(:with_next_version) {}
+    new_version.should == '1.2.0'
+  end
+end
+
+
+describe Buildr::Release, '#tag' do
+  before do
+    Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
+    Svn.stub!(:copy)
+  end
+  
+  it 'should tag the working copy' do
+    Svn.stub!(:remove)
+    Svn.should_receive(:copy).with(Dir.pwd, 'http://my.repo.org/foo/tags/1.0.1', 'Release 1.0.1')
+    Release.send :tag, '1.0.1'
+  end
+  
+  it 'should remove the tag if it already exists' do
+    Svn.should_receive(:remove).with('http://my.repo.org/foo/tags/1.0.1', 'Removing old copy')
+    Release.send :tag, '1.0.1'
+  end
+  
+  it 'should accept that the tag does not exist' do
+    Svn.stub!(:remove).and_raise(RuntimeError)
+    Release.send :tag, '1.0.1'
+  end
+end
+
+
+describe Buildr::Release, '#commit' do
+  before do
+    write 'buildfile', 'THIS_VERSION = "1.0.0"'
+  end
+  
+  it 'should update the buildfile with the given version number' do
+    Svn.stub!(:commit)
+    Release.send :commit, '1.0.1-SNAPSHOT'
+    file('buildfile').should contain('THIS_VERSION = "1.0.1-SNAPSHOT"')
+  end
+  
+    it 'should commit the new buildfile on the trunk' do
+      Svn.should_receive(:commit).with(File.expand_path('buildfile'), 'Changed version number to 1.0.1-SNAPSHOT')
+      Release.send :commit, '1.0.1-SNAPSHOT'
+    end
+end
\ No newline at end of file