You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by vb...@apache.org on 2008/04/01 23:02:12 UTC
svn commit: r643563 - in /incubator/buildr/trunk: lib/buildr/core/project.rb
lib/buildr/java/artifact.rb lib/buildr/java/artifact_namespace.rb
spec/artifact_namespace_spec.rb
Author: vborja
Date: Tue Apr 1 14:02:10 2008
New Revision: 643563
URL: http://svn.apache.org/viewvc?rev=643563&view=rev
Log:
BUILDR-4
Added an example for extension authors using ArtifactNamespace and ArtifactRequirement.add_listener
More documentation, specs.
Modified:
incubator/buildr/trunk/lib/buildr/core/project.rb
incubator/buildr/trunk/lib/buildr/java/artifact.rb
incubator/buildr/trunk/lib/buildr/java/artifact_namespace.rb
incubator/buildr/trunk/spec/artifact_namespace_spec.rb
Modified: incubator/buildr/trunk/lib/buildr/core/project.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/core/project.rb?rev=643563&r1=643562&r2=643563&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/core/project.rb (original)
+++ incubator/buildr/trunk/lib/buildr/core/project.rb Tue Apr 1 14:02:10 2008
@@ -684,7 +684,7 @@
#
# before_define do |project|
# # Define the loc task for this particular project.
- # define_task 'loc' do |task|
+ # Rake::Task.define_task 'loc' do |task|
# lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq.
# inject(0) { |total, file| total + File.readlines(file).count }
# puts "Project #{project.name} has #{lines} lines of code"
Modified: incubator/buildr/trunk/lib/buildr/java/artifact.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/java/artifact.rb?rev=643563&r1=643562&r2=643563&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/java/artifact.rb (original)
+++ incubator/buildr/trunk/lib/buildr/java/artifact.rb Tue Apr 1 14:02:10 2008
@@ -570,20 +570,18 @@
# :call-seq:
# artifacts(*spec) => artifacts
- # artifacts { |namespace| ... } => namespace
- # artifacts(scope) { |namespace| ... } => namespace
- # artifacts[scope] => namespace
#
# Handles multiple artifacts at a time. This method is the plural equivalent of
# #artifacts, but can do more things.
#
- # The first form returns an array of artifacts built using the supplied
+ # Returns an array of artifacts built using the supplied
# specifications, each of which can be:
# * An artifact specification (String or Hash). Returns the appropriate Artifact task.
# * An artifact of any other task. Returns the task as is.
# * A project. Returns all artifacts created (packaged) by that project.
# * A string. Returns that string, assumed to be a file name.
# * An array of artifacts or a Struct.
+ # * A symbol. Returns the named artifact from the current ArtifactNamespace
#
# For example, handling a collection of artifacts:
# xml = [ xerces, xalan, jaxp ]
@@ -594,10 +592,6 @@
# Using artifacts created by a project:
# artifacts project('my-app') # All packages
# artifacts project('my-app').package(:war) # Only the WAR
- #
- # Last three forms return an ArtifactNamespace instead of an array
- # and are only useful to work with the resulting namespace. See the
- # documentation for ArtifactNamespace for more info.
def artifacts(*specs, &block)
specs.flatten.inject([]) do |set, spec|
case spec
@@ -645,7 +639,7 @@
end
# :call-seq:
- # groups(ids, :under=>group_name, :version=>number) => artifacts
+ # group(ids, :under=>group_name, :version=>number) => artifacts
#
# Convenience method for defining multiple artifacts that belong to the same group and version.
# Accepts multiple artifact identifiers follows by two hash values:
Modified: incubator/buildr/trunk/lib/buildr/java/artifact_namespace.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/java/artifact_namespace.rb?rev=643563&r1=643562&r2=643563&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/java/artifact_namespace.rb (original)
+++ incubator/buildr/trunk/lib/buildr/java/artifact_namespace.rb Tue Apr 1 14:02:10 2008
@@ -85,7 +85,7 @@
#
# # When a child namespace asks for the :log artifact,
# # these artifacts will be searched starting from the :current namespace.
- # ns.dynamic :log, :logger, :commons_logging
+ # ns.virtual :log, :logger, :commons_logging
# end
#
# artifact_ns('example2:one') do |ns| # namespace for the one subproject
@@ -195,7 +195,9 @@
# end
#
# end
- #
+ #
+ # A more advanced example using ArtifactRequirement listeners is included
+ # in the artifact_namespace_spec.rb description for 'Extension using ArtifactNamespace'
# That's it for addon writers, now, users can select their prefered version with
# something like:
#
@@ -219,7 +221,7 @@
def clear
@instances = nil
remove_const(:ROOT) if const_defined?(:ROOT)
- const_set(:ROOT, new(:root))
+ const_set(:ROOT, new('root'))
end
# Populate namespaces from a hash of hashes.
@@ -227,8 +229,8 @@
#
# -- profiles.yaml --
# development:
- # :artifacts:
- # :root: # root namespace
+ # artifacts:
+ # root: # root namespace
# spring: org.springframework:spring:jar:2.5
# groovy: org.codehaus.groovy:groovy:jar:1.5.4
# logging: # define a named group
@@ -244,7 +246,7 @@
# spring: org.springframework:spring:jar:1.0
#
# -- buildfile --
- # ArtifactNamespace.load(Buildr.profile[:artifacts])
+ # ArtifactNamespace.load(Buildr.profile['artifacts'])
def load(namespaces = {})
namespaces.each_pair { |name, uses| instance(name).use(uses) }
end
@@ -271,9 +273,9 @@
when nil then Rake.application.current_scope.join(':')
end
end
- name = name.to_s.split(/:{2,}/).join(':')
+ name = name.to_s
return ROOT if name.size == 0
- name = name.intern
+ name = name.to_s
@instances ||= Hash.new { |h, k| h[k] = new(k) }
instance = @instances[name]
yield(instance) if block_given?
@@ -412,7 +414,7 @@
end
def name=(name)
- @name = name.to_s.to_sym
+ @name = name.to_s
end
# Set a the requirement, this must be an string formatted for
@@ -467,9 +469,14 @@
def selected! #:nodoc:
@selected = true
+ @listeners.each { |l| l.call(self) } if @listeners
self
end
+ def add_listener(&callback)
+ (@listeners ||= []) << callback
+ end
+
# Return the Artifact object for the currently selected version
def artifact
::Buildr.artifact(self)
@@ -504,8 +511,8 @@
include Enumerable
attr_reader :name
- def initialize(name) #:nodoc:
- @name = name.to_sym
+ def initialize(name = nil) #:nodoc:
+ @name = name.to_s if name
end
clear
@@ -515,7 +522,18 @@
# ROOT namespace has no parent
def parent
- root? ? nil : ArtifactNamespace.instance(@parent || @name.to_s.split(':')[0...-1].join(':'))
+ if root?
+ nil
+ elsif @parent.kind_of?(ArtifactNamespace)
+ @parent
+ elsif @parent
+ ArtifactNamespace.instance(@parent)
+ elsif name
+ parent_name = name.gsub(/::?[^:]+$/, '')
+ parent_name == name ? root : ArtifactNamespace.instance(parent_name)
+ else
+ root
+ end
end
# Set the parent for the current namespace, except if it is ROOT
@@ -536,7 +554,7 @@
# Reference needs to be through this object using the given +name+
#
# artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0'
- # artifact_ns('foo').bar # => the sub-namespace
+ # artifact_ns('foo').bar # => the sub-namespace 'foo.bar'
# artifact_ns('foo').bar.thing # => the some thing artifact
#
# See the top level ArtifactNamespace documentation for examples
@@ -555,6 +573,12 @@
sub
end
+ # Test if a sub-namespace by the given name exists
+ def ns?(name)
+ sub = registry[name.to_sym]
+ ArtifactNamespace === sub
+ end
+
# :call-seq:
# artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5'
# artifact_ns.need :name => 'org.foo:bar:jar:1.0'
@@ -754,12 +778,24 @@
# Return only the named requirements
def values_at(*names)
names.map do |name|
- name = name.to_s[/^\w+$/] ? name : ArtifactRequirement.unversioned_spec(name)
- get(name.to_sym)
+ catch :artifact do
+ unless name.to_s[/^\w+$/]
+ unvers = ArtifactRequirement.unversioned_spec(name)
+ unless unvers.to_s == name.to_s
+ req = ArtifactRequirement.new(name)
+ reg = self
+ while reg
+ candidate = reg.send(:get, unvers, false, false, true)
+ throw :artifact, candidate if req.satisfied_by?(candidate)
+ reg = reg.parent
+ end
+ end
+ end
+ get(name.to_sym)
+ end
end
end
-
def key?(name, include_parents = false)
name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^\w+$/]
registry.key?(name, include_parents)
@@ -789,7 +825,7 @@
self
end
- alias_method :dynamic, :group
+ alias_method :virtual, :group
# Create an alias for a named requirement.
def alias(new_name, old_name)
@@ -841,7 +877,31 @@
when /\?$/ then
name = $`.gsub(/^(has|have)_/, '').intern
[get(name)].flatten.all? { |a| a && a.selected? }
- else get(name)
+ else
+ if block || args.size > 0
+ raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given")
+ end
+ get(name)
+ end
+ end
+
+ # Return an anonymous module
+ # # first create a requirement
+ # artifact_ns.cool_aid! 'cool:aid:jar:>=1.0'
+ #
+ # # extend an object as a cool_aid delegator
+ # jars = Object.new.extend(artifact_ns.accessor(:cool_aid))
+ # jars.cool_aid = '2.0'
+ #
+ # artifact_ns.cool_aid.version # -> '2.0'
+ def accessor(*names)
+ ns = self
+ Module.new do
+ names.each do |name|
+ define_method("#{name}") { ns.send("#{name}") }
+ define_method("#{name}?") { ns.send("#{name}?") }
+ define_method("#{name}=") { |vers| ns.send("#{name}=", vers) }
+ end
end
end
@@ -878,13 +938,17 @@
end # ArtifactNamespace
+ # :call-seq:
+ # project.artifact_ns -> ArtifactNamespace
+ # Buildr.artifact_ns(name) -> ArtifactNamespace
+ # Buildr.artifact_ns -> ArtifactNamespace for the currently running Project
+ #
# Open an ArtifactNamespace.
- # If no name is given, the namespace for the currently running
- # project is returned. If a block is provided, the namespace
- # is yielded to it.
+ # If a block is provided, the namespace is yielded to it.
#
# See also ArtifactNamespace.instance
def artifact_ns(name = nil, &block)
+ name = self if name.nil? && self.kind_of?(Project)
ArtifactNamespace.instance(name, &block)
end
Modified: incubator/buildr/trunk/spec/artifact_namespace_spec.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/artifact_namespace_spec.rb?rev=643563&r1=643562&r2=643563&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/artifact_namespace_spec.rb (original)
+++ incubator/buildr/trunk/spec/artifact_namespace_spec.rb Tue Apr 1 14:02:10 2008
@@ -13,11 +13,8 @@
# License for the specific language governing permissions and limitations under
# the License.
-
require File.join(File.dirname(__FILE__), 'spec_helpers')
-require 'buildr/java/artifact_namespace'
-
describe Buildr::ArtifactNamespace do
before(:each) { Buildr::ArtifactNamespace.clear }
@@ -43,14 +40,19 @@
it 'should return the top level namespace when invoked outside a project definition' do
artifact_ns.should be_root
end
+
+ it 'should return the namespace for the receiving project' do
+ define('foo') { }
+ project('foo').artifact_ns.name.should == 'foo'
+ end
it 'should return the current project namespace when invoked inside a project' do
define 'foo' do
artifact_ns.should_not be_root
- artifact_ns.name.should == :foo
+ artifact_ns.name.should == 'foo'
task :doit do
artifact_ns.should_not be_root
- artifact_ns.name.should == :foo
+ artifact_ns.name.should == 'foo'
end.invoke
end
end
@@ -60,18 +62,18 @@
end
it 'should return the namespace for the given name' do
- artifact_ns(:foo).name.should == :foo
- artifact_ns('foo:bar').name.should == 'foo:bar'.intern
- artifact_ns(['foo', 'bar', 'baz']).name.should == 'foo:bar:baz'.intern
+ artifact_ns(:foo).name.should == 'foo'
+ artifact_ns('foo:bar').name.should == 'foo:bar'
+ artifact_ns(['foo', 'bar', 'baz']).name.should == 'foo:bar:baz'
abc_module do
- artifact_ns(A::B::C).name.should == 'A:B:C'.intern
+ artifact_ns(A::B::C).name.should == 'A::B::C'
end
artifact_ns(:root).should be_root
artifact_ns(:current).should be_root
define 'foo' do
- artifact_ns(:current).name.should == :foo
+ artifact_ns(:current).name.should == 'foo'
define 'baz' do
- artifact_ns(:current).name.should == 'foo:baz'.intern
+ artifact_ns(:current).name.should == 'foo:baz'
end
end
end
@@ -287,6 +289,54 @@
end
end
end
+
+ describe '#values_at' do
+ it 'returns the named artifacts' do
+ define 'foo' do
+ artifact_ns.use 'foo:one:baz:1.0'
+ define 'bar' do
+ artifact_ns.use :foo_baz => 'foo:two:baz:1.0'
+
+ specs = artifact_ns.values_at('one').map(&:to_spec)
+ specs.should include('foo:one:baz:1.0')
+ specs.should_not include('foo:two:baz:1.0')
+
+ specs = artifact_ns.values_at('foo_baz').map(&:to_spec)
+ specs.should include('foo:two:baz:1.0')
+ specs.should_not include('foo:one:baz:1.0')
+ end
+ end
+ end
+
+ it 'returns first artifacts by their unversioned spec' do
+ define 'foo' do
+ artifact_ns.use 'foo:one:baz:2.0'
+ define 'bar' do
+ artifact_ns.use :older => 'foo:one:baz:1.0'
+
+ specs = artifact_ns.values_at('foo:one:baz').map(&:to_spec)
+ specs.should include('foo:one:baz:1.0')
+ specs.should_not include('foo:one:baz:2.0')
+ end
+ specs = artifact_ns.values_at('foo:one:baz').map(&:to_spec)
+ specs.should include('foo:one:baz:2.0')
+ specs.should_not include('foo:one:baz:1.0')
+ end
+ end
+
+ it 'return first artifact satisfying a dependency' do
+ define 'foo' do
+ artifact_ns.use 'foo:one:baz:2.0'
+ define 'bar' do
+ artifact_ns.use :older => 'foo:one:baz:1.0'
+
+ specs = artifact_ns.values_at('foo:one:baz:>1.0').map(&:to_spec)
+ specs.should include('foo:one:baz:2.0')
+ specs.should_not include('foo:one:baz:1.0')
+ end
+ end
+ end
+ end
describe '#method_missing' do
it 'should use cool_aid! to create a requirement' do
@@ -442,6 +492,144 @@
lambda { artifact(:cool) }.should raise_error(IndexError, /artifact/)
end
end
+ end
+ end
+end
+
+describe "Extension using ArtifactNamespace" do
+ before(:each) { Buildr::ArtifactNamespace.clear }
+
+ def abc_module
+ Object.module_eval 'module A; module B; module C; end; end; end'
+ yield
+ ensure
+ Object.send :remove_const, :A
+ end
+
+ it 'can register namespace listeners' do
+ abc_module do
+ # An example extension to illustrate namespace listeners and method forwarding
+ class A::Example
+
+ module Ext
+ include Buildr::Extension
+ def example; @example ||= A::Example.new; end
+ before_define do |p|
+ Rake::Task.define_task('example') { p.example.doit }
+ end
+ end
+
+ REQUIRES = ArtifactNamespace.for(self) do |ns|
+ ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2'
+ ns.stax_api! 'stax:stax-api:jar:>=1.0.1'
+ end
+
+ attr_reader :options, :requires
+
+ def initialize
+ # We could actually use the REQUIRES namespace, but to make things
+ # a bit more interesting, suppose each Example instance can have its
+ # own artifact requirements in adition to those specified on REQUIRES.
+ # To achieve this we create an anonymous namespace.
+ @requires = ArtifactNamespace.new # a namespace per instance
+ REQUIRES.each { |requirement| @requires.need requirement }
+
+ # For user convenience, we make the options object respond to
+ # :xmlbeans, :xmlbeans=, :xmlbeans?
+ # forwarding them to the namespace.
+ @options = OpenObject.new.extend(@requires.accessor(:xmlbeans, :stax_api))
+ # Register callbacks so we can perform some logic when an artifact
+ # is selected by the user.
+ options.xmlbeans.add_listener &method(:selected_xmlbeans)
+ options.stax_api.add_listener do |stax|
+ # Now using a proc
+ stax.should be_selected
+ stax.version.should == '1.6180'
+ options[:math] = :golden # customize our options for this version
+ # In this example we set the stax version when running outside
+ # a project definition. This means we have no access to the project
+ # namespace unless we had a reference to the project or knew it's name
+ Buildr.artifact_ns(:current).name.should == 'root'
+ end
+ end
+
+ include Spec::Matchers # for assertions
+
+ # Called with the ArtifactRequirement that has just been selected
+ # by a user. This allows extension author to selectively perform
+ # some action by inspecting the requirement state.
+ def selected_xmlbeans(xmlbeans)
+ xmlbeans.should be_selected
+ xmlbeans.version.should == '3.1415'
+ options[:math] = :pi
+ # This example just sets xmlbeans for foo:bar project
+ # So the currently running namespace should have the foo:bar name
+ Buildr.artifact_ns(:current).name.should == 'foo:bar'
+ end
+
+ # Suppose we invoke an ant task here or something else.
+ def doit
+ # Now call ant task with our selected artifact and options
+ classpath = requires.map(&:artifact).map(&:to_s).join(File::PATH_SEPARATOR)
+ lambda { ant('thing') { |ant| ant.classpath classpath, :math => options[:math] } }
+
+ # We are not a Project instance, hence we have no artifact_ns
+ lambda { artifact_ns }.should raise_error(NameError)
+
+ # Extension authors may NOT rely project's namespaces.
+ # However the ruby-way gives you power and at the same time
+ # makes you dangerous, (think open-modules, monkey-patching)
+ # Given that buildr is pure ruby, consider it a sharp-edged sword.
+ # Having said that, you may actually inspect a project's
+ # namespace, but don't write on it without letting your users
+ # know you will.
+ # This example obtains the current project namespace to make
+ # some assertions.
+
+ # To obtain a project's namespace we need either
+ # 1) a reference to the project, and call artifact_ns on it
+ # project.artifact_ns # the namespace for project
+ # 2) know the project name
+ # Buildr.artifact_ns('the:project')
+ # 3) Use :current to reference the currently running project
+ # Buildr.artifact_ns(:current)
+ name = Buildr.artifact_ns(:current).name
+ case name
+ when 'foo:bar'
+ options[:math].should == :pi
+ requires.xmlbeans.version.should == '3.1415'
+ requires.stax_api.version.should == '1.0.1'
+ when 'foo:baz'
+ options[:math].should == :golden
+ requires.xmlbeans.version.should == '2.3.0'
+ requires.stax_api.version.should == '1.6180'
+ else
+ fail "This example expects foo:bar or foo:baz projects not #{name.inspect}"
+ end
+ end
+ end
+
+ define 'foo' do
+ define 'bar' do
+ extend A::Example::Ext
+ task('setup') do
+ example.options.xmlbeans = '3.1415'
+ end
+ task('run' => [:setup, :example])
+ end
+ define 'baz' do
+ extend A::Example::Ext
+ end
+ end
+
+ project('foo:bar').example.requires.should_not == project('foo:baz').example.requires
+ project('foo:bar').example.requires.xmlbeans.should_not == project('foo:baz').example.requires.xmlbeans
+
+ # current namespace outside a project is :root, see the stax callback
+ project('foo:baz').example.options.stax_api = '1.6180'
+ # we call the task outside the project, see #doit
+ lambda { task('foo:bar:run').invoke }.should run_task('foo:bar:example')
+ lambda { task('foo:baz:example').invoke }.should run_task('foo:baz:example')
end
end
end