C0 code coverage information
Generated on Wed Oct 07 08:34:05 -0700 2009 with rcov 0.8.2.1
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with this
3 # work for additional information regarding copyright ownership. The ASF
4 # licenses this file to you under the Apache License, Version 2.0 (the
5 # "License"); you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
14 # the License.
15
16
17 require 'buildr/packaging/version_requirement'
18
19
20 module Buildr
21
22 # An ArtifactNamespace is a hierarchical dictionary used to manage ArtifactRequirements.
23 # It can be used to have different artifact versions per project
24 # or to allow users to select a version for addons or modules.
25 #
26 # Namespaces are opened using the Buildr.artifact_ns method, most important methods are:
27 # [need] Used to create a requirement on the namespace.
28 # [use] Set the artifact version to use for a requirement.
29 # [values_at] Reference requirements by name.
30 # [each] Return each ArtifactRequirement in the namespace.
31 # The method_missing method for instances provides some syntactic sugar to these.
32 # See the following examples, and the methods for ArtifactRequirement.
33 #
34 # = Avoiding constant polution on buildfile
35 #
36 # Each project has its own ArtifactNamespace inheriting the one from the
37 # parent project up to the root namespace.
38 #
39 # Consider the following snippet, as project grows, each subproject
40 # may need diferent artifact combinations and/or versions. Asigning
41 # artifact specifications to constants can make it painful to maintain
42 # their references even if using structs/hashes.
43 #
44 # -- buildfile --
45 # SPRING = 'org.springframework:spring:jar:2.5'
46 # SPRING_OLD = 'org.springframework:spring:jar:1.0'
47 # LOGGING = ['comons-logging:commons-logging:jar:1.1.1',
48 # 'log4j:log4j:jar:1.2.15']
49 # WL_LOGGING = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
50 # LOGGING_WEBLOGIC = ['comons-logging:commons-logging:jar:1.1.1',
51 # WL_LOGGING]
52 # COMMONS = struct :collections => 'commons-collection:commons-collection:jar:3.1',
53 # :net => 'commons-net:commons-net:jar:1.4.0'
54 #
55 # define 'example1' do
56 # define 'one' do
57 # compile.with SPRING, LOGGING_WEBLOGIC, COMMONS
58 # end
59 # define 'two' do
60 # compile.with SPRING_OLD, LOGGING, COMMONS
61 # end
62 # define 'three' do
63 # compile.with "commons-collections:commons-collections:jar:2.2"
64 # end
65 # end
66 #
67 #
68 # With ArtifactNamespace you can do some more advanced stuff, the following
69 # annotated snipped could still be reduced if default artifact definitions were
70 # loaded from yaml file (see section bellow and ArtifactNamespace.load).
71 #
72 # -- buildfile --
73 # artifact_ns do |ns| # the current namespace (root if called outside a project)
74 # # default artifacts
75 # ns.spring = 'org.springframework:spring:jar:2.5'
76 # # default logger is log4j
77 # ns.logger = 'log4j:log4j:jar:1.2.15'
78 #
79 # # create a sub namespace by calling the #ns method,
80 # # artifacts defined on the sub-namespace can be referenced by
81 # # name :commons_net or by calling commons.net
82 # ns.ns :commons, :net => 'commons-net:commons-net:jar:1.4.0',
83 # :logging => 'comons-logging:commons-logging:jar:1.1.1'
84 #
85 #
86 # # When a child namespace asks for the :log artifact,
87 # # these artifacts will be searched starting from the :current namespace.
88 # ns.virtual :log, :logger, :commons_logging
89 # end
90 #
91 # artifact_ns('example2:one') do |ns| # namespace for the one subproject
92 # ns.logger = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
93 # end
94 # artifact_ns('example2:two') do |ns|
95 # ns.spring = '1.0' # for project two use an older spring version (just for an example)
96 # end
97 # artifact_ns('example2:three').commons_collections = 2.2'
98 # artifact_ns('example2:four') do |ns|
99 # ns.beanutils = 'commons-beanutils:commons-beanutils:jar:1.5' # just for this project
100 # ns.ns(:compilation).use :commons_logging, :beanutils, :spring # compile time dependencies
101 # ns.ns(:testing).use :log, :beanutils, 'cglib:cglib-nodep:jar:2.1.3' # run time dependencies
102 # end
103 #
104 # define 'example2' do
105 # define 'one' do
106 # compile.with :spring, :log, :commons # uses weblogic logging
107 # end
108 # define 'two' do
109 # compile.with :spring, :log, :commons # will take old spring
110 # end
111 # define 'three' do
112 # compile.with :commons_collections
113 # test.with artifact_ns('example2:two').spring # use spring from project two
114 # end
115 # define 'four' do
116 # compile.with artifact_ns.compilation
117 # test.with artifact_ns.testing
118 # end
119 # task(:down_them_all) do # again, just to fill this space with something ;)
120 # parent.projects.map(&method(:artifact_ns)).map(&:artifacts).map(&:invoke)
121 # end
122 # end
123 #
124 # = Loading from a yaml file (e. profiles.yaml)
125 #
126 # If your projects use lots of jars (after all we are using java ;) you may prefer
127 # to have constant artifact definitions on an external file.
128 # Doing so would allow an external tool (or future Buildr feature) to maintain
129 # an artifacts.yaml for you.
130 # An example usage is documented on the ArtifactNamespace.load method.
131 #
132 # = For addon/plugin writers & Customizing artifact versions
133 #
134 # Sometimes users would need to change the default artifact versions used by some
135 # module, for example, the XMLBeans compiler needs this, because of compatibility
136 # issues. Another example would be to select the groovy version to use on all our
137 # projects so that Buildr modules requiring groovy jars can use user prefered versions.
138 #
139 # To meet this goal, an ArtifactNamespace allows to specify ArgumentRequirement objects.
140 # In fact the only diference with the examples you have already seen is that requirements
141 # have an associated VersionRequirement, so that each time a user tries to select a version,
142 # buildr checks if it satisfies the requirements.
143 #
144 # Requirements are declared using the ArtifactNamespace#need method, but again,
145 # syntactic sugar is provided by ArtifactNamespace#method_missing.
146 #
147 # The following example is taken from the XMLBeans compiler module.
148 # And illustrates how addon authors should specify their requirements,
149 # provide default versions, and document the namespace for users to customize.
150 #
151 # module Buildr::XMLBeans
152 #
153 # # You need to document this constant, giving users some hints
154 # # about when are (maybe some of) these artifacts used. I mean,
155 # # some modules, add jars to the Buildr classpath when its file
156 # # is required, you would need to tell your users, so that they
157 # # can open the namespace and specify their defaults. Of course
158 # # when the requirements are defined, buildr checks if any compatible
159 # # version has been already defined, if so, uses it.
160 # #
161 # # Some things here have been changed to illustrate their meaning.
162 # REQUIRES = ArtifactNamespace.for(self).tap do |ns|
163 #
164 # # This jar requires a >2.0 version, default being 2.3.0
165 # ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2'
166 #
167 # # Users can customize with Buildr::XMLBeans::REQUIRES.stax_api = '1.2'
168 # # This is a non-flexible requirement, only satisfied by version 1.0.1
169 # ns.stax_api! 'stax:stax-api:jar:1.0.1'
170 #
171 # # This one is not part of XMLBeans, but is just another example
172 # # illustrating an `artifact requirement spec`.
173 #
174 # ns.need " some_name -> ar:ti:fact:3.2.5 -> ( >2 & <4)"
175 #
176 # # As you can see it's just an artifact spec, prefixed with
177 # # ' some_name -> ', this means users can use that name to
178 # # reference the requirement, also this string has a VersionRequirement
179 # # just after another ->.
180 # end
181 #
182 # # The REQUIRES constant is an ArtifactNamespace instance,
183 # # that means we can use it directly. Note that calling
184 # # Buildr.artifact_ns would lead to the currently executing context,
185 # # not the one for this module.
186 # def use
187 # # test if user specified his own version, if so, we could perform some
188 # # functionallity based on this.
189 # REQUIRES.some_name.selected? # => false
190 #
191 # REQUIRES.some_name.satisfied_by?('1.5') # => false
192 # puts REQUIRES.some_name.requirement # => ( >2 & <4 )
193 #
194 # REQUIRES.artifacts # get the Artifact tasks
195 # end
196 #
197 # end
198 #
199 # A more advanced example using ArtifactRequirement listeners is included
200 # in the artifact_namespace_spec.rb description for 'Extension using ArtifactNamespace'
201 # That's it for addon writers, now, users can select their prefered version with
202 # something like:
203 #
204 # require 'buildr/xmlbeans'
205 # Buildr::XMLBeans::REQUIRES.xmlbeans = '2.2.0'
206 #
207 # More advanced stuff, if users really need to select an xmlbeans version
208 # per project, they can do so letting :current (that is, the currently running
209 # namespace) be parent of the REQUIRES namespace:
210 #
211 # Buildr::XMLBeans::REQUIRES.parent = :current
212 #
213 # Now, provided that the compiler does not caches its artifacts, it will
214 # select the correct version. (See the first section for how to select per project
215 # artifacts).
216 #
217 #
218 class ArtifactNamespace
219 class << self
220 # Forget all namespaces, create a new ROOT
221 def clear
222 @instances = nil
223 remove_const(:ROOT) rescue nil
224 const_set(:ROOT, new('root'))
225 end
226
227 # Populate namespaces from a hash of hashes.
228 # The following example uses the profiles yaml to achieve this.
229 #
230 # -- profiles.yaml --
231 # development:
232 # artifacts:
233 # root: # root namespace
234 # spring: org.springframework:spring:jar:2.5
235 # groovy: org.codehaus.groovy:groovy:jar:1.5.4
236 # logging: # define a named group
237 # - log4j:log4j:jar:1.2.15
238 # - commons-logging:commons-logging:1.1.1
239 #
240 # # open Buildr::XMLBeans namespace
241 # Buildr::XMLBeans:
242 # xmlbeans: 2.2
243 #
244 # # for subproject one:oldie
245 # one:oldie:
246 # spring: org.springframework:spring:jar:1.0
247 #
248 # -- buildfile --
249 # ArtifactNamespace.load(Buildr.profile['artifacts'])
250 def load(namespaces = {})
251 namespaces.each_pair { |name, uses| instance(name).use(uses) }
252 end
253
254 # :call-seq:
255 # ArtifactNamespace.instance { |current_ns| ... } -> current_ns
256 # ArtifactNamespace.instance(name) { |ns| ... } -> ns
257 # ArtifactNamespace.instance(:current) { |current_ns| ... } -> current_ns
258 # ArtifactNamespace.instance(:root) { |root_ns| ... } -> root_ns
259 #
260 # Obtain an instance for the given name
261 def instance(name = nil)
262 case name
263 when :root, 'root' then return ROOT
264 when ArtifactNamespace then return name
265 when Array then name = name.join(':')
266 when Module, Project then name = name.name
267 when :current, 'current', nil then
268 task = Thread.current[:rake_chain]
269 task = task.instance_variable_get(:@value) if task
270 name = case task
271 when Project then task.name
272 when Rake::Task then task.scope.join(':')
273 when nil then Buildr.application.current_scope.join(':')
274 end
275 end
276 name = name.to_s
277 return ROOT if name.size == 0
278 name = name.to_s
279 @instances ||= Hash.new { |h, k| h[k] = new(k) }
280 instance = @instances[name]
281 yield(instance) if block_given?
282 instance
283 end
284
285 alias_method :[], :instance
286 alias_method :for, :instance
287
288 # :call-seq:
289 # ArtifactNamespace.root { |ns| ... } -> ns
290 #
291 # Obtain the root namespace, returns the ROOT constant
292 def root
293 yield ROOT if block_given?
294 ROOT
295 end
296 end
297
298 module DClone #:nodoc:
299 def dclone
300 clone = self.clone
301 clone.instance_variables.each do |i|
302 value = clone.instance_variable_get(i)
303 value = value.dclone rescue
304 clone.instance_variable_set(i, value)
305 end
306 clone
307 end
308 end
309
310 class Registry < Hash #:nodoc:
311 include DClone
312
313 attr_accessor :parent
314 def alias(new_name, old_name)
315 new_name = new_name.to_sym
316 old_name = old_name.to_sym
317 if obj = get(old_name, true)
318 self[new_name] = obj
319 @aliases ||= []
320 group = @aliases.find { |a| a.include?(new_name) }
321 group.delete(new_name) if group
322 group = @aliases.find { |a| a.include?(old_name) }
323 @aliases << (group = [old_name]) unless group
324 group << new_name unless group.include?(new_name)
325 end
326 obj
327 end
328
329 def aliases(name)
330 return [] unless name
331 name = name.to_sym
332 ((@aliases ||= []).find { |a| a.include?(name) } || [name]).dup
333 end
334
335 def []=(key, value)
336 return unless key
337 super(key.to_sym, value)
338 end
339
340 def get(key, include_parent = nil)
341 [].tap { |a| aliases(key).select { |n| a[0] = self[n] } }.first ||
342 (include_parent && parent && parent.get(key, include_parent))
343 end
344
345 def keys(include_parent = nil)
346 (super() | (include_parent && parent && parent.keys(include_parent) || [])).uniq
347 end
348
349 def values(include_parent = nil)
350 (super() | (include_parent && parent && parent.values(include_parent) || [])).uniq
351 end
352
353 def key?(key, include_parent = nil)
354 return false unless key
355 super(key.to_sym) || (include_parent && parent && parent.key?(key, include_parent))
356 end
357
358 def delete(key, include_parent = nil)
359 aliases(key).map {|n| super(n) } && include_parent && parent && parent.delete(key, include_parent)
360 end
361 end
362
363 # An artifact requirement is an object that ActsAsArtifact and has
364 # an associated VersionRequirement. It also knows the name (some times equal to the
365 # artifact id) that is used to store it in an ArtifactNamespace.
366 class ArtifactRequirement
367 attr_accessor :version
368 attr_reader :name, :requirement
369
370 include DClone
371
372 # Create a requirement from an `artifact requirement spec`.
373 # This spec has three parts, separated by ->
374 #
375 # some_name -> ar:ti:fact:3.2.5 -> ( >2 & <4)
376 #
377 # As you can see it's just an artifact spec, prefixed with
378 # some_name ->
379 # the :some_name symbol becomes this object's name and
380 # is used to store it on an ArtifactNamespace.
381 #
382 # ar:ti:fact:3.2.5
383 #
384 # The second part is an artifact spec by itself, and specifies
385 # all remaining attributes, the version of this spec becomes
386 # the default version of this requirement.
387 #
388 # The last part consist of a VersionRequirement.
389 # -> ( >2 & <4)
390 #
391 # VersionRequirement supports RubyGem's comparision operators
392 # in adition to parens, logical and, logical or and negation.
393 # See the docs for VersionRequirement for more info on operators.
394 def initialize(spec)
395 self.class.send :include, ActsAsArtifact unless ActsAsArtifact === self
396 if ArtifactRequirement === spec
397 copy_attrs(spec)
398 else
399 spec = requirement_hash(spec)
400 apply_spec(spec[:spec])
401 self.name = spec[:name]
402 @requirement = spec[:requirement]
403 @version = @requirement.default if VersionRequirement.requirement?(@version)
404 end
405 end
406
407 # Copy attributes from other to this object
408 def copy_attrs(other)
409 (ActsAsArtifact::ARTIFACT_ATTRIBUTES + [:name, :requirement]).each do |attr|
410 value = other.instance_variable_get("@#{attr}")
411 value = value.dup if value && !value.kind_of?(Numeric) && !value.kind_of?(Symbol)
412 instance_variable_set("@#{attr}", value)
413 end
414 end
415
416 def name=(name)
417 @name = name.to_s
418 end
419
420 # Set a the requirement, this must be an string formatted for
421 # VersionRequirement#create to parse.
422 def requirement=(version_requirement)
423 @requirement = VersionRequirement.create(version_requirement.to_s)
424 end
425
426 # Return a hash consisting of :name, :spec, :requirement
427 def requirement_hash(spec = self)
428 result = {}
429 if String === spec
430 parts = spec.split(/\s*->\s*/, 3).map(&:strip)
431 case parts.size
432 when 1
433 result[:spec] = Artifact.to_hash(parts.first)
434 when 2
435 if /^\w+$/ === parts.first
436 result[:name] = parts.first
437 result[:spec] = Artifact.to_hash(parts.last)
438 else
439 result[:spec] = Artifact.to_hash(parts.first)
440 result[:requirement] = VersionRequirement.create(parts.last)
441 end
442 when 3
443 result[:name] = parts.first
444 result[:spec] = Artifact.to_hash(parts[1])
445 result[:requirement] = VersionRequirement.create(parts.last)
446 end
447 else
448 result[:spec] = Artifact.to_hash(spec)
449 end
450 result[:name] ||= result[:spec][:id].to_s.to_sym
451 result[:requirement] ||= VersionRequirement.create(result[:spec][:version])
452 result
453 end
454
455 # Test if this requirement is satisfied by an artifact spec.
456 def satisfied_by?(spec)
457 return false unless requirement
458 spec = Artifact.to_hash(spec)
459 hash = to_spec_hash
460 hash.delete(:version)
461 version = spec.delete(:version)
462 hash == spec && requirement.satisfied_by?(version)
463 end
464
465 # Has user selected a version for this requirement?
466 def selected?
467 @selected
468 end
469
470 def selected! #:nodoc:
471 @selected = true
472 @listeners.each { |l| l.call(self) } if @listeners
473 self
474 end
475
476 def add_listener(&callback)
477 (@listeners ||= []) << callback
478 end
479
480 # Return the Artifact object for the currently selected version
481 def artifact
482 ::Buildr.artifact(self)
483 end
484
485 # Format this requirement as an `artifact requirement spec`
486 def to_requirement_spec
487 result = to_spec
488 result = "#{name} -> #{result}" if name
489 result = "#{result} -> #{requirement}" if requirement
490 result
491 end
492
493 def to_s #:nodoc:
494 id ? to_requirement_spec : version
495 end
496
497 # Return an artifact spec without the version part.
498 def unversioned_spec
499 str = to_spec
500 return nil if str =~ /^:+/
501 ary = str.split(':')
502 ary = ary[0...-1] if ary.size > 3
503 ary.join(':')
504 end
505
506 class << self
507 # Return an artifact spec without the version part.
508 def unversioned_spec(spec)
509 str = spec.to_s
510 return nil if str =~ /^:+/
511 ary = str.split(':')
512 ary = ary[0...-1] if ary.size > 3
513 if ary.size > 2
514 ary.join(':')
515 else
516 new(spec).unversioned_spec
517 end
518 end
519 end
520 end
521
522 include DClone
523 include Enumerable
524 attr_reader :name
525
526 def initialize(name = nil) #:nodoc:
527 @name = name.to_s if name
528 end
529 clear
530
531 def root
532 ROOT
533 end
534
535 # ROOT namespace has no parent
536 def parent
537 if root?
538 nil
539 elsif @parent.kind_of?(ArtifactNamespace)
540 @parent
541 elsif @parent
542 ArtifactNamespace.instance(@parent)
543 elsif name
544 parent_name = name.gsub(/::?[^:]+$/, '')
545 parent_name == name ? root : ArtifactNamespace.instance(parent_name)
546 else
547 root
548 end
549 end
550
551 # Set the parent for the current namespace, except if it is ROOT
552 def parent=(other)
553 raise 'Cannot set parent of root namespace' if root?
554 @parent = other
555 @registry = nil
556 end
557
558 # Is this the ROOT namespace?
559 def root?
560 ROOT == self
561 end
562
563 # Create a named sub-namespace, sub-namespaces are themselves
564 # ArtifactNamespace instances but cannot be referenced by
565 # the Buildr.artifact_ns, ArtifactNamespace.instance methods.
566 # Reference needs to be through this object using the given +name+
567 #
568 # artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0'
569 # artifact_ns('foo').bar # => the sub-namespace 'foo.bar'
570 # artifact_ns('foo').bar.thing # => the some thing artifact
571 #
572 # See the top level ArtifactNamespace documentation for examples
573 def ns(name, *uses, &block)
574 name = name.to_sym
575 sub = registry[name]
576 if sub
577 raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace)
578 else
579 sub = ArtifactNamespace.new("#{self.name}.#{name}")
580 sub.parent = self
581 registry[name] = sub
582 end
583 sub.use(*uses)
584 yield sub if block_given?
585 sub
586 end
587
588 # Test if a sub-namespace by the given name exists
589 def ns?(name)
590 sub = registry[name.to_sym]
591 ArtifactNamespace === sub
592 end
593
594 # :call-seq:
595 # artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5'
596 # artifact_ns.need :name => 'org.foo:bar:jar:1.0'
597 #
598 # Create a new ArtifactRequirement on this namespace.
599 # ArtifactNamespace#method_missing provides syntactic sugar for this.
600 def need(*specs)
601 named = specs.flatten.inject({}) do |seen, spec|
602 if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
603 spec.each_pair do |name, spec|
604 if Array === spec # a group
605 seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
606 else
607 artifact = ArtifactRequirement.new(spec)
608 artifact.name = name
609 seen[artifact.name] ||= artifact
610 end
611 end
612 else
613 artifact = ArtifactRequirement.new(spec)
614 seen[artifact.name] ||= artifact
615 end
616 seen
617 end
618 named.each_pair do |name, artifact|
619 if Array === artifact # a group
620 artifact.each do |a|
621 unvers = a.unversioned_spec
622 previous = registry[unvers]
623 if previous && previous.selected? && a.satisfied_by?(previous)
624 a.version = previous.version
625 end
626 registry[unvers] = a
627 end
628 group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}]))
629 else
630 unvers = artifact.unversioned_spec
631 previous = registry.get(unvers, true)
632 if previous && previous.selected? && artifact.satisfied_by?(previous)
633 artifact.version = previous.version
634 artifact.selected!
635 end
636 registry[unvers] = artifact
637 registry.alias name, unvers unless name.to_s[/^\s*$/]
638 end
639 end
640 self
641 end
642
643 # :call-seq:
644 # artifact_ns.use 'name -> org:foo:bar:jar:1.2.3'
645 # artifact_ns.use :name => 'org:foo:bar:jar:1.2.3'
646 # artifact_ns.use :name => '2.5.6'
647 #
648 # First and second form are equivalent, the third is used when an
649 # ArtifactRequirement has been previously defined with :name, so it
650 # just selects the version.
651 #
652 # ArtifactNamespace#method_missing provides syntactic sugar for this.
653 def use(*specs)
654 named = specs.flatten.inject({}) do |seen, spec|
655 if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
656 spec.each_pair do |name, spec|
657 if ArtifactNamespace === spec # create as subnamespace
658 raise ArgumentError.new("Circular reference") if self == spec
659 registry[name.to_sym] = spec
660 elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec))
661 artifact = ArtifactRequirement.allocate
662 artifact.name = name
663 artifact.version = spec.to_s
664 seen[artifact.name] ||= artifact
665 elsif Symbol === spec
666 self.alias name, spec
667 elsif Array === spec # a group
668 seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
669 else
670 artifact = ArtifactRequirement.new(spec)
671 artifact.name = name
672 seen[artifact.name] ||= artifact
673 end
674 end
675 else
676 if Symbol === spec
677 artifact = get(spec).dclone
678 else
679 artifact = ArtifactRequirement.new(spec)
680 end
681 seen[artifact.name] ||= artifact
682 end
683 seen
684 end
685 named.each_pair do |name, artifact|
686 is_group = Array === artifact
687 artifact = [artifact].flatten.map do |artifact|
688 unvers = artifact.unversioned_spec
689 previous = get(unvers, false) || get(name, false)
690 if previous # have previous on current namespace
691 if previous.requirement # we must satisfy the requirement
692 unless unvers # we only have the version
693 satisfied = previous.requirement.satisfied_by?(artifact.version)
694 else
695 satisfied = previous.satisfied_by?(artifact)
696 end
697 raise "Unsatisfied dependency #{previous} " +
698 "not satisfied by #{artifact}" unless satisfied
699 previous.version = artifact.version # OK, set new version
700 artifact = previous # use the same object for aliases
701 else # not a requirement, set the new values
702 unless artifact.id == previous.id && name != previous.name
703 previous.copy_attrs(artifact)
704 artifact = previous
705 end
706 end
707 else
708 if unvers.nil? && # we only have the version
709 (previous = get(unvers, true, false, false))
710 version = artifact.version
711 artifact.copy_attrs(previous)
712 artifact.version = version
713 end
714 artifact.requirement = nil
715 end
716 artifact.selected!
717 end
718 artifact = artifact.first unless is_group
719 if is_group
720 names = artifact.map do |art|
721 unv = art.unversioned_spec
722 registry[unv] = art
723 unv
724 end
725 group(name, *(names + [{:namespace => self}]))
726 elsif artifact.id
727 unvers = artifact.unversioned_spec
728 registry[name] = artifact
729 registry.alias unvers, name
730 else
731 registry[name] = artifact
732 end
733 end
734 self
735 end
736
737 # Like Hash#fetch
738 def fetch(name, default = nil, &block)
739 block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") }
740 real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name)
741 get(real_name.to_sym) || default || block.call(name)
742 end
743
744 # :call-seq:
745 # artifact_ns[:name] -> ArtifactRequirement
746 # artifact_ns[:many, :names] -> [ArtifactRequirement]
747 def [](*names)
748 ary = values_at(*names)
749 names.size == 1 ? ary.first : ary
750 end
751
752 # :call-seq:
753 # artifact_ns[:name] = 'some:cool:jar:1.0.2'
754 # artifact_ns[:name] = '1.0.2'
755 #
756 # Just like the use method
757 def []=(*names)
758 values = names.pop
759 values = [values] unless Array === values
760 names.each_with_index do |name, i|
761 use name => (values[i] || values.last)
762 end
763 end
764
765 # yield each ArtifactRequirement
766 def each(&block)
767 values.each(&block)
768 end
769
770 # return Artifact objects for each requirement
771 def artifacts(*names)
772 (names.empty? && values || values_at(*names)).map(&:artifact)
773 end
774
775 # Return all requirements for this namespace
776 def values(include_parents = false, include_groups = true)
777 seen, dict = {}, registry
778 while dict
779 dict.each do |k, v|
780 v = v.call if v.respond_to?(:call)
781 v = v.values if v.kind_of?(ArtifactNamespace)
782 if Array === v && include_groups
783 v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) }
784 else
785 seen[v.name] = v unless seen.key?(v.name)
786 end
787 end
788 dict = include_parents ? dict.parent : nil
789 end
790 seen.values
791 end
792
793 # Return only the named requirements
794 def values_at(*names)
795 names.map do |name|
796 catch :artifact do
797 unless name.to_s[/^[\w\-\.]+$/]
798 unvers = ArtifactRequirement.unversioned_spec(name)
799 unless unvers.to_s == name.to_s
800 req = ArtifactRequirement.new(name)
801 reg = self
802 while reg
803 candidate = reg.send(:get, unvers, false, false, true)
804 throw :artifact, candidate if req.satisfied_by?(candidate)
805 reg = reg.parent
806 end
807 end
808 end
809 get(name.to_sym)
810 end
811 end
812 end
813
814 def key?(name, include_parents = false)
815 name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/]
816 registry.key?(name, include_parents)
817 end
818
819 def delete(name, include_parents = false)
820 registry.delete(name, include_parents)
821 self
822 end
823
824 # :call-seq:
825 # group :who, :me, :you
826 # group :them, :me, :you, :namespace => ns
827 #
828 # Create a virtual group on this namespace. When the namespace
829 # is asked for the +who+ artifact, it's value is an array made from
830 # the remaining names. These names are searched by default from the current
831 # namespace.
832 # Second form specified the starting namespace to search from.
833 def group(group_name, *members)
834 namespace = (Hash === members.last && members.pop[:namespace]) || :current
835 registry[group_name] = lambda do
836 artifacts = self.class[namespace].values_at(*members)
837 artifacts = artifacts.first if members.size == 1
838 artifacts
839 end
840 self
841 end
842
843 alias_method :virtual, :group
844
845 # Create an alias for a named requirement.
846 def alias(new_name, old_name)
847 registry.alias(new_name, old_name) or
848 raise NameError.new("Undefined artifact name: #{old_name}")
849 end
850
851 def to_s #:nodoc:
852 name.to_s
853 end
854
855 # :call-seq:
856 # artifact_ns.cool_aid!('cool:aid:jar:2.3.4', '~>2.3') -> artifact_requirement
857 # artifact_ns.cool_aid = '2.3.5'
858 # artifact_ns.cool_aid -> artifact_requirement
859 # artifact_ns.cool_aid? -> true | false
860 #
861 # First form creates an ArtifactRequirement on the namespace.
862 # It is equivalent to providing a requirement_spec to the #need method:
863 # artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3"
864 # the second argument is optional.
865 #
866 # Second form can be used to select an artifact version
867 # and is equivalent to:
868 # artifact_ns.use :cool_aid => '1.0'
869 #
870 # Third form obtains the named ArtifactRequirement, can be
871 # used to test if a named requirement has been defined.
872 # It is equivalent to:
873 # artifact_ns.fetch(:cool_aid) { nil }
874 #
875 # Last form tests if the ArtifactRequirement has been defined
876 # and a version has been selected for use.
877 # It is equivalent to:
878 #
879 # artifact_ns.has_cool_aid?
880 # artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? }
881 #
882 def method_missing(name, *args, &block)
883 case name.to_s
884 when /!$/ then
885 name = $`.intern
886 if args.size < 1 || args.size > 2
887 raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
888 end
889 need name => args.first
890 get(name).tap { |r| r.requirement = args.last if args.size == 2 }
891 when /=$/ then use $` => args.first
892 when /\?$/ then
893 name = $`.gsub(/^(has|have)_/, '').intern
894 [get(name)].flatten.all? { |a| a && a.selected? }
895 else
896 if block || args.size > 0
897 raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given")
898 end
899 get(name)
900 end
901 end
902
903 # Return an anonymous module
904 # # first create a requirement
905 # artifact_ns.cool_aid! 'cool:aid:jar:>=1.0'
906 #
907 # # extend an object as a cool_aid delegator
908 # jars = Object.new.extend(artifact_ns.accessor(:cool_aid))
909 # jars.cool_aid = '2.0'
910 #
911 # artifact_ns.cool_aid.version # -> '2.0'
912 def accessor(*names)
913 ns = self
914 Module.new do
915 names.each do |name|
916 define_method("#{name}") { ns.send("#{name}") }
917 define_method("#{name}?") { ns.send("#{name}?") }
918 define_method("#{name}=") { |vers| ns.send("#{name}=", vers) }
919 end
920 end
921 end
922
923 private
924 def get(name, include_parents = true, include_subs = true, include_self = true) #:nodoc:
925 if include_subs && name.to_s[/_/] # try sub namespaces first
926 sub, parts = self, name.to_s.split('_')
927 sub_name = parts.shift.to_sym
928 until sub != self || parts.empty?
929 if registry[sub_name].kind_of?(ArtifactNamespace)
930 sub = registry[sub_name]
931 artifact = sub[parts.join('_')]
932 else
933 sub_name = [sub_name, parts.shift].join('_').to_sym
934 end
935 end
936 end
937 unless artifact
938 if include_self
939 artifact = registry.get(name, include_parents)
940 elsif include_parents && registry.parent
941 artifact = registry.parent.get(name, true)
942 end
943 end
944 artifact = artifact.call if artifact.respond_to?(:call)
945 artifact
946 end
947
948 def registry
949 @registry ||= Registry.new.tap do |m|
950 m.parent = parent.send(:registry) unless root?
951 end
952 end
953
954 end # ArtifactNamespace
955
956 # :call-seq:
957 # project.artifact_ns -> ArtifactNamespace
958 # Buildr.artifact_ns(name) -> ArtifactNamespace
959 # Buildr.artifact_ns -> ArtifactNamespace for the currently running Project
960 #
961 # Open an ArtifactNamespace.
962 # If a block is provided, the namespace is yielded to it.
963 #
964 # See also ArtifactNamespace.instance
965 def artifact_ns(name = nil, &block)
966 name = self if name.nil? && self.kind_of?(Project)
967 ArtifactNamespace.instance(name, &block)
968 end
969
970 end
971
972
Generated using the rcov code coverage analysis tool for Ruby
version 0.8.2.1.