You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by as...@apache.org on 2008/02/28 09:31:39 UTC

svn commit: r631890 - in /incubator/buildr/trunk: lib/core.rb lib/core/addon.rb lib/tasks/zip.rb spec/addon_spec.rb spec/sandbox.rb

Author: assaf
Date: Thu Feb 28 00:31:38 2008
New Revision: 631890

URL: http://svn.apache.org/viewvc?rev=631890&view=rev
Log:
Addons

Added:
    incubator/buildr/trunk/lib/core/addon.rb
    incubator/buildr/trunk/spec/addon_spec.rb
Modified:
    incubator/buildr/trunk/lib/core.rb
    incubator/buildr/trunk/lib/tasks/zip.rb
    incubator/buildr/trunk/spec/sandbox.rb

Modified: incubator/buildr/trunk/lib/core.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/core.rb?rev=631890&r1=631889&r2=631890&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/core.rb (original)
+++ incubator/buildr/trunk/lib/core.rb Thu Feb 28 00:31:38 2008
@@ -24,3 +24,4 @@
 require 'core/test'
 require 'core/checks'
 require 'core/generate'
+require 'core/addon'

Added: incubator/buildr/trunk/lib/core/addon.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/core/addon.rb?rev=631890&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/core/addon.rb (added)
+++ incubator/buildr/trunk/lib/core/addon.rb Thu Feb 28 00:31:38 2008
@@ -0,0 +1,145 @@
+# 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
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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 'java/artifact'
+
+
+module Buildr
+
+  # Addons are a mechanicm for sharing extensions, tasks and common code across builds,
+  # using remote and local repositories.
+  #
+  # An addon is a collection of files, a ZIP archive when distributed and an exploded
+  # directory when used locally.  They are installed and expanded into the local repository.
+  #
+  # Addons provide functionality in three different ways:
+  # * The addon directory is added to the $LOAD_PATH, and its files can be accessed from
+  #    the build tasks, typically using +require+.
+  # * The +init.rb+ file is required by default, if present.  An addon can use this to
+  #   install project extensions, introduce new tasks, install libraries, etc.
+  # * Task files that go in the +tasks+ sub-directory with the extension +.rake+ are
+  #   automatically loaded after the buildfile.  Use these to introduce addition tasks.
+  #
+  # The addon method supports options that are set on the addon on first use.  The +init.rb+
+  # file can access these options through the global variable $ADDON.
+  #
+  # Addons are referenced by a qualified name.  For local and remote repositories, the
+  # last part of the qualified name maps to the artifact identifier, the rest is the group
+  # identifier.  For example, 'org.apache.buildr.openjpa:1.0' becomes
+  # 'org.apache.buildr:openjpa:zip:1.0'.
+  class Addon
+
+    class << self
+
+      # Returns all the loaded addons.
+      def list
+        @addons.values
+      end
+
+      def load(from, options = nil) #:nodoc:
+        options ||= {}
+        case from
+        when Rake::FileTask
+          target = from.to_s
+          name = target.pathmap('%n')
+        when String
+          name, version, *rest = from.split(':')
+          fail "Expecting <name>:<version>, found #{from}." unless name && version && rest.empty?
+          group = name.split('.')
+          id = group.pop
+          fail "Addon name is qualified, like foo.bar or foo.bar.baz, but not foo." if group.empty?
+          artifact = Buildr.artifact("#{group.join('.')}:#{id}:zip:#{version}")
+          target = artifact.to_s.ext
+          Buildr.unzip(target=>artifact)
+        else
+          fail "Can only load addon from repository (name:version) or file task."
+        end
+        if addon = @addons[name]
+          fail "Attempt to load addon #{name} with two different version numbers, first (#{addon.version}) and now (#{version})." unless
+            addon.version == version
+          false
+        else
+          @addons[name] = new(name, version, target, options)
+          true
+        end
+      end
+
+    end
+
+    @addons = {}
+
+    # Addon name.
+    attr_reader :name
+    # Version number (may be nil).
+    attr_reader :version
+    # The path for the addon directory.
+    attr_reader :path
+
+    include Enumerable
+
+    def initialize(name, version, path, options) #:nodoc:
+      @name, @version, @options = name, version, options
+      @path = File.expand_path(path)
+      file(@path).invoke
+      raise "#{@path} is not a directory." unless File.directory?(@path)
+      begin
+        $ADDON = self
+        $LOAD_PATH << @path unless $LOAD_PATH.include?(@path)
+        init_file = File.join(@path, 'init.rb')
+        require init_file if File.exist?(init_file)
+        import *FileList[File.join(@path, 'tasks/*.rake')]
+      rescue
+        $LOAD_PATH.delete @path
+      ensure
+        $ADDON = nil
+      end
+    end
+
+    # Returns the value of the option.
+    def [](name)
+      @options[name]
+    end
+
+    # Sets the value of the option.
+    def []=(name, value)
+      @options[name] = value
+    end
+
+    def each(&block) #:nodoc:
+      @options.each(&block)
+    end
+
+    def to_s #:nodoc:
+      version ? "#{name}:#{version}" : name
+    end
+
+  end
+
+  # :call-seq:
+  #   addon(id, options?)
+  #   addon(task, options?)
+  #
+  # Use this to download and install an addon.  The first form takes the addon identifier,
+  # a string that contains the qualified name, colon and version number.  For example:
+  #   addon 'org.apache.buildr.openjpa:1.0'
+  # Some addons accept options passed as a hash argument.
+  #
+  # The second form takes a file task that points to the directory containing the addon.
+  def addon(from, options = nil)
+    Addon.load(from, options)
+  end
+
+end

Modified: incubator/buildr/trunk/lib/tasks/zip.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/tasks/zip.rb?rev=631890&r1=631889&r2=631890&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/tasks/zip.rb (original)
+++ incubator/buildr/trunk/lib/tasks/zip.rb Thu Feb 28 00:31:38 2008
@@ -537,7 +537,6 @@
       # specified. Nothing will happen unless we include all files.
       if @paths.empty?
         @paths[nil] = FromPath.new(self, nil)
-        @paths[nil].include '*'
       end
 
       # Otherwise, empty unzip creates target as a file when touching.
@@ -652,7 +651,7 @@
       end
 
       def map(entries)
-        includes = @include || ['*']
+        includes = @include || ['**/*']
         excludes = @exclude || []
         entries.inject({}) do |map, entry|
           short = entry.name.sub(@path, '')

Added: incubator/buildr/trunk/spec/addon_spec.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/addon_spec.rb?rev=631890&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/addon_spec.rb (added)
+++ incubator/buildr/trunk/spec/addon_spec.rb Thu Feb 28 00:31:38 2008
@@ -0,0 +1,170 @@
+# 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
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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 File.join(File.dirname(__FILE__), 'spec_helpers')
+
+
+describe Addon do
+  before { $loaded = false }
+
+  it 'should list all loaded addons' do
+    write 'foobar/init.rb'
+    addon file('foobar')
+    Addon.list.each { |addon| addon.should be_kind_of(Addon) }.
+      map(&:name).should include('foobar')
+  end
+
+  it 'should return true when loading addon for first time' do
+    write 'foobar/init.rb'
+    addon(file('foobar')).should be(true)
+  end
+
+  it 'should allow loading same addon twice' do
+    write 'foobar/init.rb'
+    addon file('foobar')
+    lambda { addon file('foobar') }.should_not raise_error
+  end
+
+  it 'should return false when loading addon second time' do
+    write 'foobar/init.rb'
+    addon(file('foobar'))
+    addon(file('foobar')).should be(false)
+  end
+
+  it 'should instantiate addon once even if loaded twice' do
+    write 'foobar/init.rb', <<-RUBY
+      $loaded = !$loaded
+    RUBY
+    lambda { addon file('foobar') }.should change { $loaded }
+    lambda { addon file('foobar') }.should_not change { $loaded }
+  end
+end
+
+
+describe Addon, 'from directory' do
+  before { $loaded = false }
+
+  it 'should have no version number' do
+  end
+
+  it 'should add directory to LOAD_PATH' do
+    mkpath 'foobar'
+    lambda { addon file('foobar') }.should change { $LOAD_PATH.clone }
+    $LOAD_PATH.should include(File.expand_path('foobar'))
+  end
+
+  it 'should load init.rb file if found' do
+    write 'foobar/init.rb', '$loaded = true'
+    lambda { addon file('foobar') }.should change { $loaded }.to(true)
+  end
+
+  it 'should add init.rb to LOADED_FEATURES' do
+    write 'foobar/init.rb'
+    lambda { addon file('foobar') }.should change { $LOADED_FEATURES.clone }
+    $LOADED_FEATURES.should include(File.expand_path('foobar/init.rb'))
+  end
+
+  it 'should pass options to addon' do
+    write 'foobar/init.rb', '$loaded = $ADDON[:loaded]'
+    lambda { addon file('foobar'), :loaded=>5 }.should change { $loaded }.to(5)
+  end
+
+  it 'should import any tasks present in tasks sub-directory' do
+    write 'foobar/tasks/foo.rake', "$loaded = 'foo'"
+    addon file('foobar')
+    lambda { Rake.application.load_imports }.should change { $loaded }.to('foo')
+  end
+
+  it 'should fail if directory doesn\'t exist' do
+    lambda { addon file('missing') }.should raise_error(RuntimeError, /not a directory/i)
+  end
+
+  it 'should fail if path is not a directory' do
+    write 'wrong'
+    lambda { addon file('wrong') }.should raise_error(RuntimeError, /not a directory/i)
+  end
+end
+
+
+describe Addon, 'from artifact' do
+  before { $loaded = false }
+
+  def load_addon(options = nil)
+    write 'source/init.rb', "require 'extra'"
+    write 'source/extra.rb', '$loaded = $ADDON[:loaded] || true'
+    zip('repository/org/apache/buildr/foobar/1.0/foobar-1.0.zip').include(:from=>'source').invoke
+    addon 'org.apache.buildr.foobar:1.0', options
+  end
+
+  it 'should figure out addon group from name:version' do
+    artifact('fizz.buzz:foobar:zip:1.0').should_receive(:execute)
+    addon 'fizz.buzz.foobar:1.0' rescue nil
+  end
+
+  it 'should pick name from prefix' do
+    load_addon
+    Addon.list.map(&:name).should include('org.apache.buildr.foobar')
+  end
+
+  it 'should pick version from suffix' do
+    load_addon
+    Addon.list.map(&:version).should include('1.0')
+  end
+
+  it 'should download artifact from remote repository' do
+    lambda { addon 'org.apache.buildr.foobar:1.0' }.should raise_error(Exception, /no remote repositories/i)
+  end
+
+  it 'should install artifact in local repository' do
+    load_addon
+    file('repository/org/apache/buildr/foobar/1.0/foobar-1.0.zip').should exist
+  end
+
+  it 'should expand ZIP addon into local repository' do
+    load_addon
+    file('repository/org/apache/buildr/foobar/1.0/foobar-1.0').should exist
+    file('repository/org/apache/buildr/foobar/1.0/foobar-1.0').should contain('init.rb', 'extra.rb')
+  end
+
+  it 'should add directory to LOAD_PATH' do
+    lambda { load_addon  }.should change { $LOAD_PATH.clone }
+    $LOAD_PATH.should include(File.expand_path('repository/org/apache/buildr/foobar/1.0/foobar-1.0'))
+  end
+
+  it 'should load init.rb file if found' do
+    lambda { load_addon }.should change { $loaded }.to(true)
+  end
+
+  it 'should add init.rb to LOADED_FEATURES' do
+    lambda { load_addon }.should change { $LOADED_FEATURES.clone }
+    $LOADED_FEATURES.should include(File.expand_path('repository/org/apache/buildr/foobar/1.0/foobar-1.0/init.rb'))
+  end
+
+  it 'should pass options to addon' do
+    lambda { load_addon :loaded=>5 }.should change { $loaded }.to(5)
+  end
+
+  it 'should fail if loading same addon with two different versions' do
+    load_addon
+    lambda { addon 'org.apache.buildr.foobar:2.0' }.should raise_error(RuntimeError, /two different version numbers/)
+  end
+
+  it 'should import any tasks present in tasks sub-directory' do
+    write 'source/tasks/foo.rake', "$loaded = 'foo'"
+    load_addon
+    lambda { Rake.application.load_imports }.should change { $loaded }.to('foo')
+  end
+end

Modified: incubator/buildr/trunk/spec/sandbox.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/sandbox.rb?rev=631890&r1=631889&r2=631890&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/sandbox.rb (original)
+++ incubator/buildr/trunk/spec/sandbox.rb Thu Feb 28 00:31:38 2008
@@ -56,6 +56,8 @@
     # Move to the work directory and make sure Rake thinks of it as the Rakefile directory.
     @sandbox[:pwd] = Dir.pwd
     Dir.chdir @test_dir
+    @sandbox[:load_path] = $LOAD_PATH.clone
+    @sandbox[:loaded_features] = $LOADED_FEATURES.clone
     @sandbox[:original_dir] = Rake.application.original_dir 
     Rake.application.instance_eval { @original_dir = Dir.pwd }
     Rake.application.instance_eval { @rakefile = File.expand_path('buildfile') }
@@ -96,6 +98,8 @@
     # Switch back Rake directory.
     Dir.chdir @sandbox[:pwd]
     original_dir = @sandbox[:original_dir]
+    $LOAD_PATH.replace @sandbox[:load_path]
+    $LOADED_FEATURES.replace @sandbox[:loaded_features]
     Rake.application.instance_eval { @original_dir = original_dir }
     FileUtils.rm_rf @test_dir
 
@@ -104,8 +108,9 @@
     @sandbox[:tasks].each { |block| block.call }
     Rake.application.instance_variable_set :@rules, @sandbox[:rules]
 
-    # Get rid of all artifacts and out test directory.
+    # Get rid of all artifacts and addons.
     @sandbox[:artifacts].tap { |artifacts| Artifact.class_eval { @artifacts = artifacts } }
+    Addon.instance_eval { @addons.clear }
 
     # Restore options.
     Buildr.options.test = nil