# 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.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers')) describe Buildr::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 describe '.root' do it 'should return the top level namespace' do Buildr::ArtifactNamespace.root.should be_root end it 'should yield the namespace if a block is given' do flag = false Buildr::ArtifactNamespace.root { |ns| flag = true; ns.should be_root } flag.should == true end it 'should return the root when used outside of a project definition' do artifact_ns.should be_root end it 'should yield to a block when used outside of a project definition' do flag = false artifact_ns {|ns| flag = true; ns.should be_root} flag.should == true end end describe '.instance' do 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' task :doit do artifact_ns.should_not be_root artifact_ns.name.should == 'foo' end.invoke end end it 'should return the root namespace if given :root' do artifact_ns(:root).should be_root 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' artifact_ns(['foo', 'bar', 'baz']).name.should == 'foo:bar:baz' abc_module do 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' define 'baz' do artifact_ns(:current).name.should == 'foo:baz' end end end end describe '#parent' do it 'should be nil for root namespace' do artifact_ns(:root).parent.should be_nil end it 'should be the parent namespace for nested modules' do abc_module do artifact_ns(A::B::C).parent.should == artifact_ns(A::B) artifact_ns(A::B).parent.should == artifact_ns(A) artifact_ns(A).parent.should == artifact_ns(:root) end end it 'should be the parent namespace for nested projects' do define 'a' do define 'b' do define 'c' do artifact_ns.parent.should == artifact_ns(parent) end artifact_ns.parent.should == artifact_ns(parent) end artifact_ns.parent.should == artifact_ns(:root) end end end describe '#parent=' do it 'should reject to set parent for root namespace' do lambda { artifact_ns(:root).parent = :foo }.should raise_error(Exception, /cannot set parent/i) end it 'should allow to set parent' do artifact_ns(:bar).parent = :foo artifact_ns(:bar).parent.should == artifact_ns(:foo) artifact_ns(:bar).parent = artifact_ns(:baz) artifact_ns(:bar).parent.should == artifact_ns(:baz) end it 'should allow to set parent to :current' do abc_module do mod = A::B artifact_ns(mod).parent = :current def mod.stuff Buildr::artifact_ns(self) end define 'a' do define 'b' do mod.stuff.parent.should == artifact_ns end mod.stuff.parent.should == artifact_ns end end end end describe '#need' do it 'should accept an artifact spec' do define 'one' do artifact_ns.need 'a:b:c:1' # referenced by spec artifact_ns['a:b:c'].should_not be_selected # referenced by name artifact_ns[:b].should_not be_selected artifact_ns[:b].should be_satisfied_by('a:b:c:1') artifact_ns[:b].should_not be_satisfied_by('a:b:c:2') artifact_ns[:b].should_not be_satisfied_by('d:b:c:1') artifact_ns[:b].version.should == '1' end end it 'should accept an artifact spec with classifier' do define 'one' do artifact_ns.need 'a:b:c:d:1' # referenced by spec artifact_ns['a:b:c:d:'].should_not be_selected # referenced by name artifact_ns[:b].should_not be_selected artifact_ns[:b].should be_satisfied_by('a:b:c:d:1') artifact_ns[:b].should_not be_satisfied_by('a:b:c:d:2') artifact_ns[:b].should_not be_satisfied_by('d:b:c:d:1') artifact_ns[:b].version.should == '1' end end it 'should accept a requirement_spec' do define 'one' do artifact_ns.need 'thing -> a:b:c:2.1 -> ~>2.0' # referenced by spec artifact_ns['a:b:c'].should_not be_selected # referenced by name artifact_ns.key?(:b).should be_false artifact_ns[:thing].should_not be_selected artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') artifact_ns[:thing].version.should == '2.1' end end it 'should accept a hash :name -> requirement_spec' do define 'one' do artifact_ns.need :thing => 'a:b:c:2.1 -> ~>2.0' artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') artifact_ns[:thing].version.should == '2.1' end define 'two' do artifact_ns.need :thing => 'a:b:c:(~>2.0 | 2.1)' artifact_ns[:thing].should be_satisfied_by('a:b:c:2.5') artifact_ns[:thing].should_not be_satisfied_by('a:b:c:3') artifact_ns[:thing].version.should == '2.1' end end it 'should take a hash :name -> specs_array' do define 'one' do artifact_ns.need :things => ['foo:bar:jar:1.0', 'foo:baz:jar:2.0',] artifact_ns['foo:bar:jar'].should_not be_selected artifact_ns['foo:baz:jar'].should_not be_selected artifact_ns[:bar, :baz].should == [nil, nil] artifact_ns[:things].map(&:unversioned_spec).should include('foo:bar:jar', 'foo:baz:jar') artifact_ns.alias :baz, 'foo:baz:jar' artifact_ns[:baz].should == artifact_ns['foo:baz:jar'] end end it 'should select best matching version if defined' do define 'one' do artifact_ns.use :a => 'foo:bar:jar:1.5' artifact_ns.use :b => 'foo:baz:jar:2.0' define 'two' do artifact_ns[:a].requirement.should be_nil artifact_ns[:a].should be_selected artifact_ns.need :c => 'foo:bat:jar:3.0' artifact_ns['foo:bat:jar'].should_not be_selected artifact_ns[:c].should_not be_selected artifact_ns.need :one => 'foo:bar:jar:>=1.0' artifact_ns[:one].version.should == '1.5' artifact_ns[:one].should be_selected artifact_ns[:a].requirement.should be_nil artifact_ns.need :two => 'foo:baz:jar:>2' artifact_ns[:two].version.should be_nil artifact_ns[:two].should_not be_selected artifact_ns[:b].requirement.should be_nil end end end end describe '#use' do it 'should register the artifact on namespace' do define 'one' do artifact_ns.use :thing => 'a:b:c:1' artifact_ns[:thing].requirement.should be_nil artifact_ns[:thing].version.should == '1' artifact_ns[:thing].id.should == 'b' define 'one' do artifact_ns.use :thing => 'a:d:c:2' artifact_ns[:thing].requirement.should be_nil artifact_ns[:thing].version.should == '2' artifact_ns[:thing].id.should == 'd' artifact_ns.use :copied => artifact_ns.parent[:thing] artifact_ns[:copied].should_not == artifact_ns.parent[:thing] artifact_ns[:copied].requirement.should be_nil artifact_ns[:copied].version.should == '1' artifact_ns[:copied].id.should == 'b' artifact_ns.use :aliased => :copied artifact_ns[:aliased].should == artifact_ns[:copied] lambda { artifact_ns.use :invalid => :unknown }.should raise_error(NameError, /undefined/i) end artifact_ns[:copied].should be_nil end end it 'should register two artifacts with different version on namespace' do define 'one' do artifact_ns.use :foo => 'a:b:c:1' artifact_ns.use :bar => 'a:b:c:2' artifact_ns[:foo].version.should == '1' artifact_ns[:bar].version.should == '2' # unversioned references the last version set. artifact_ns['a:b:c'].version.should == '2' end end it 'should complain if namespace requirement is not satisfied' do define 'one' do artifact_ns.need :bar => 'foo:bar:baz:~>1.5' lambda { artifact_ns.use :bar => '1.4' }.should raise_error(Exception, /unsatisfied/i) end end it 'should be able to register a group' do specs = ['its:me:here:1', 'its:you:there:2'] artifact_ns.use :them => specs artifact_ns[:them].map(&:to_spec).should == specs artifact_ns['its:me:here'].should_not be_nil artifact_ns[:you].should be_nil end it 'should be able to assign sub namespaces' do artifact_ns(:foo).bar = "foo:bar:baz:0" artifact_ns(:moo).foo = artifact_ns(:foo) artifact_ns(:moo).foo.should == artifact_ns(:foo) artifact_ns(:moo).foo_bar.should == artifact_ns(:foo).bar end it 'should handle symbols with dashes and periods' do [:'a-b', :'a.b'].each do |symbol| artifact_ns.use symbol => 'a:b:c:1' artifact_ns[symbol].version.should == '1' artifact_ns[symbol].id.should == 'b' end end it 'should handle version string' do foo = artifact_ns do |ns| ns.bar = 'a:b:c:1' end foo.use :bar => '2.0' foo.bar.version.should == '2.0' end end describe '#values' do it 'returns the artifacts defined on namespace' do define 'foo' do artifact_ns.use 'foo:one:baz:1.0' define 'bar' do artifact_ns.use 'foo:two:baz:1.0' specs = artifact_ns.values.map(&:to_spec) specs.should include('foo:two:baz:1.0') specs.should_not include('foo:one:baz:1.0') specs = artifact_ns.values(true).map(&:to_spec) specs.should include('foo:two:baz:1.0', 'foo:one:baz:1.0') end 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 '#artifacts' do it 'returns artifacts in namespace' do define 'one' do artifact_ns[:foo] = 'group:foo:jar:1' artifact_ns[:bar] = 'group:bar:jar:1' artifact_ns.artifacts.map{|a| a.to_spec}.should include('group:foo:jar:1', 'group:bar:jar:1') end end end describe '#keys' do it 'returns names in namespace' do define 'one' do artifact_ns[:foo] = 'group:foo:jar:1' artifact_ns[:bar] = 'group:bar:jar:1' artifact_ns.keys.should include('foo', 'bar') end end end describe '#delete' do it 'deletes corresponding artifact requirement' do define 'one' do artifact_ns[:foo] = 'group:foo:jar:1' artifact_ns[:bar] = 'group:bar:jar:1' artifact_ns.delete :bar artifact_ns.artifacts.map{|a| a.to_spec}.should include('group:foo:jar:1') artifact_ns[:foo].to_spec.should eql('group:foo:jar:1') end end end describe '#clear' do it 'clears all artifact requirements in namespace' do define 'one' do artifact_ns[:foo] = 'group:foo:jar:1' artifact_ns[:bar] = 'group:bar:jar:1' artifact_ns.clear artifact_ns.artifacts.should be_empty end end end describe '#method_missing' do it 'should use cool_aid! to create a requirement' do define 'foo' do artifact_ns.cool_aid!('cool:aid:jar:2').should be_kind_of(ArtifactNamespace::ArtifactRequirement) artifact_ns[:cool_aid].version.should == '2' artifact_ns[:cool_aid].should_not be_selected define 'bar' do artifact_ns.cool_aid! 'cool:aid:man:3', '>2' artifact_ns[:cool_aid].version.should == '3' artifact_ns[:cool_aid].requirement.should be_satisfied_by('2.5') artifact_ns[:cool_aid].should_not be_selected end end end it 'should use cool_aid= as shorhand for [:cool_aid]=' do artifact_ns.cool_aid = 'cool:aid:jar:1' artifact_ns[:cool_aid].should be_selected end it 'should use cool_aid as shorthand for [:cool_aid]' do artifact_ns.need :cool_aid => 'cool:aid:jar:1' artifact_ns.cool_aid.should_not be_selected end it 'should use cool_aid? to test if artifact has been defined and selected' do artifact_ns.need :cool_aid => 'cool:aid:jar:>1' artifact_ns.should_not have_cool_aid artifact_ns.should_not have_unknown artifact_ns.cool_aid = '2' artifact_ns.should have_cool_aid end end describe '#ns' do it 'should create a sub namespace' do artifact_ns.ns :foo artifact_ns[:foo].should be_kind_of(ArtifactNamespace) artifact_ns(:foo).should_not === artifact_ns.foo artifact_ns.foo.parent.should == artifact_ns end it 'should take any use arguments' do artifact_ns.ns :foo, :bar => 'foo:bar:jar:0', :baz => 'foo:baz:jar:0' artifact_ns.foo.bar.should be_selected artifact_ns.foo[:baz].should be_selected end it 'should access sub artifacts using with foo_bar like syntax' do artifact_ns.ns :foo, :bar => 'foo:bar:jar:0', :baz => 'foo:baz:jar:0' artifact_ns[:foo_baz].should be_selected artifact_ns.foo_bar.should be_selected artifact_ns.foo.ns :bat, 'bat:man:jar:>1' batman = artifact_ns.foo.bat.man batman.should be_selected artifact_ns[:foo_bat_man] = '3' artifact_ns[:foo_bat_man].should == batman artifact_ns[:foo_bat_man].version.should == '3' end it 'should include sub artifacts when calling #values' do artifact_ns.ns :bat, 'bat:man:jar:>1' artifact_ns.values.should_not be_empty artifact_ns.values.first.unversioned_spec.should == 'bat:man:jar' end it 'should reopen a sub-namespace' do artifact_ns.ns :bat, 'bat:man:jar:>1' bat = artifact_ns[:bat] bat.should == artifact_ns.ns(:bat) end it 'should fail reopening if not a sub-namespace' do artifact_ns.foo = 'foo:bar:baz:0' lambda { artifact_ns.ns(:foo) }.should raise_error(TypeError, /not a sub/i) end it 'should clone artifacts when assigned' do artifact_ns(:foo).bar = "foo:bar:jar:0" artifact_ns(:moo).ns :muu, :miu => artifact_ns(:foo).bar artifact_ns(:moo).muu.miu.should_not == artifact_ns(:foo).bar artifact_ns(:moo).muu.miu.to_spec.should == artifact_ns(:foo).bar.to_spec end it 'should clone parent artifacts by name' do define 'foo' do artifact_ns.bar = "foo:bar:jar:0" define 'moo' do artifact_ns.ns(:muu).use :bar artifact_ns.muu_bar.should be_selected artifact_ns.muu.bar.should_not == artifact_ns.bar end end end end it 'should be an Enumerable' do artifact_ns.should be_kind_of(Enumerable) artifact_ns.use 'foo:bar:baz:1.0' artifact_ns.map(&:artifact).should include(artifact('foo:bar:baz:1.0')) end end # ArtifactNamespace describe Buildr::ArtifactNamespace::ArtifactRequirement do before(:each) { Buildr::ArtifactNamespace.clear } it 'should be created from artifact_ns' do foo = artifact_ns do |ns| ns.bar = 'a:b:c:1.0' end foo.bar.should be_kind_of(ArtifactNamespace::ArtifactRequirement) end it 'should handle version as string' do foo = artifact_ns do |ns| ns.bar = 'a:b:c:1.0' end foo.bar.version = '2.0' foo.bar.version.should == '2.0' end it 'should handle version string directly' do foo = artifact_ns do |ns| ns.bar = 'a:b:c:1.0' end foo.bar = '2.0' foo.bar.version.should == '2.0' end end # ArtifactRequirement describe Buildr do before(:each) { Buildr::ArtifactNamespace.clear } describe '.artifacts' do it 'should take ruby symbols and ask the current namespace for them' do define 'foo' do artifact_ns.cool = 'cool:aid:jar:1.0' artifact_ns.use 'some:other:jar:1.0' artifact_ns.use 'bat:man:jar:1.0' compile.with :cool, :other, :'bat:man:jar' compile.dependencies.map(&:to_spec).should include('cool:aid:jar:1.0', 'some:other:jar:1.0', 'bat:man:jar:1.0') end end it 'should take a namespace' do artifact_ns(:moo).muu = 'moo:muu:jar:1.0' define 'foo' do compile.with artifact_ns(:moo) compile.dependencies.map(&:to_spec).should include('moo:muu:jar:1.0') end end end describe '.artifact' do it 'should search current namespace if given a symbol' do define 'foo' do artifact_ns.use :cool => 'cool:aid:jar:1.0' define 'bar' do artifact(:cool).should == artifact_ns[:cool].artifact end end end it 'should search current namespace if given a symbol spec' do define 'foo' do artifact_ns.use 'cool:aid:jar:1.0' define 'bar' do artifact(:'cool:aid:jar').should == artifact_ns[:aid].artifact end end end it 'should fail when no artifact by that name is found' do define 'foo' do artifact_ns.use 'cool:aid:jar:1.0' define 'bar' do 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 RSpec::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