| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| lib/buildr/packaging/artifact_namespace.rb | 984 | 537 | 23.88%
|
36.69%
|
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 pollution 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 different artifact combinations and/or versions. Assigning |
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 below 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 ArtifactRequirement objects. |
140 # In fact the only difference 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:jar: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.settings.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 if name.size == 0 |
278 instance = ROOT |
279 else |
280 name = name.to_s |
281 @instances ||= Hash.new { |h, k| h[k] = new(k) } |
282 instance = @instances[name] |
283 end |
284 yield(instance) if block_given? |
285 instance |
286 end |
287 |
288 alias_method :[], :instance |
289 alias_method :for, :instance |
290 |
291 # :call-seq: |
292 # ArtifactNamespace.root { |ns| ... } -> ns |
293 # |
294 # Obtain the root namespace, returns the ROOT constant |
295 def root |
296 yield ROOT if block_given? |
297 ROOT |
298 end |
299 end |
300 |
301 module DClone #:nodoc: |
302 def dclone |
303 clone = self.clone |
304 clone.instance_variables.each do |i| |
305 value = clone.instance_variable_get(i) |
306 value = value.dclone rescue |
307 clone.instance_variable_set(i, value) |
308 end |
309 clone |
310 end |
311 end |
312 |
313 class Registry < Hash #:nodoc: |
314 include DClone |
315 |
316 attr_accessor :parent |
317 def alias(new_name, old_name) |
318 new_name = new_name.to_sym |
319 old_name = old_name.to_sym |
320 if obj = get(old_name, true) |
321 self[new_name] = obj |
322 @aliases ||= [] |
323 group = @aliases.find { |a| a.include?(new_name) } |
324 group.delete(new_name) if group |
325 group = @aliases.find { |a| a.include?(old_name) } |
326 @aliases << (group = [old_name]) unless group |
327 group << new_name unless group.include?(new_name) |
328 end |
329 obj |
330 end |
331 |
332 def aliases(name) |
333 return [] unless name |
334 name = name.to_sym |
335 ((@aliases ||= []).find { |a| a.include?(name) } || [name]).dup |
336 end |
337 |
338 def []=(key, value) |
339 return unless key |
340 super(key.to_sym, value) |
341 end |
342 |
343 def get(key, include_parent = nil) |
344 [].tap { |a| aliases(key).select { |n| a[0] = self[n] } }.first || |
345 (include_parent && parent && parent.get(key, include_parent)) |
346 end |
347 |
348 def keys(include_parent = nil) |
349 (super() | (include_parent && parent && parent.keys(include_parent) || [])).uniq |
350 end |
351 |
352 def values(include_parent = nil) |
353 (super() | (include_parent && parent && parent.values(include_parent) || [])).uniq |
354 end |
355 |
356 def key?(key, include_parent = nil) |
357 return false unless key |
358 super(key.to_sym) || (include_parent && parent && parent.key?(key, include_parent)) |
359 end |
360 |
361 def delete(key, include_parent = nil) |
362 aliases(key).map {|n| super(n) } && include_parent && parent && parent.delete(key, include_parent) |
363 end |
364 end |
365 |
366 # An artifact requirement is an object that ActsAsArtifact and has |
367 # an associated VersionRequirement. It also knows the name (some times equal to the |
368 # artifact id) that is used to store it in an ArtifactNamespace. |
369 class ArtifactRequirement |
370 attr_accessor :version |
371 attr_reader :name, :requirement |
372 |
373 include DClone |
374 |
375 # Create a requirement from an `artifact requirement spec`. |
376 # This spec has three parts, separated by -> |
377 # |
378 # some_name -> ar:ti:fact:3.2.5 -> ( >2 & <4) |
379 # |
380 # As you can see it's just an artifact spec, prefixed with |
381 # some_name -> |
382 # the :some_name symbol becomes this object's name and |
383 # is used to store it on an ArtifactNamespace. |
384 # |
385 # ar:ti:fact:3.2.5 |
386 # |
387 # The second part is an artifact spec by itself, and specifies |
388 # all remaining attributes, the version of this spec becomes |
389 # the default version of this requirement. |
390 # |
391 # The last part consist of a VersionRequirement. |
392 # -> ( >2 & <4) |
393 # |
394 # VersionRequirement supports RubyGem's comparision operators |
395 # in adition to parens, logical and, logical or and negation. |
396 # See the docs for VersionRequirement for more info on operators. |
397 def initialize(spec) |
398 self.class.send :include, ActsAsArtifact unless ActsAsArtifact === self |
399 if ArtifactRequirement === spec |
400 copy_attrs(spec) |
401 else |
402 spec = requirement_hash(spec) |
403 apply_spec(spec[:spec]) |
404 self.name = spec[:name] |
405 @requirement = spec[:requirement] |
406 @version = @requirement.default if VersionRequirement.requirement?(@version) |
407 end |
408 end |
409 |
410 # Copy attributes from other to this object |
411 def copy_attrs(other) |
412 (ActsAsArtifact::ARTIFACT_ATTRIBUTES + [:name, :requirement]).each do |attr| |
413 value = other.instance_variable_get("@#{attr}") |
414 value = value.dup if value && !value.kind_of?(Numeric) && !value.kind_of?(Symbol) |
415 instance_variable_set("@#{attr}", value) |
416 end |
417 end |
418 |
419 def name=(name) |
420 @name = name.to_s |
421 end |
422 |
423 # Set a the requirement, this must be an string formatted for |
424 # VersionRequirement#create to parse. |
425 def requirement=(version_requirement) |
426 @requirement = VersionRequirement.create(version_requirement.to_s) |
427 end |
428 |
429 # Return a hash consisting of :name, :spec, :requirement |
430 def requirement_hash(spec = self) |
431 result = {} |
432 if String === spec |
433 parts = spec.split(/\s*->\s*/, 3).map(&:strip) |
434 case parts.size |
435 when 1 |
436 result[:spec] = Artifact.to_hash(parts.first) |
437 when 2 |
438 if /^\w+$/ === parts.first |
439 result[:name] = parts.first |
440 result[:spec] = Artifact.to_hash(parts.last) |
441 else |
442 result[:spec] = Artifact.to_hash(parts.first) |
443 result[:requirement] = VersionRequirement.create(parts.last) |
444 end |
445 when 3 |
446 result[:name] = parts.first |
447 result[:spec] = Artifact.to_hash(parts[1]) |
448 result[:requirement] = VersionRequirement.create(parts.last) |
449 end |
450 else |
451 result[:spec] = Artifact.to_hash(spec) |
452 end |
453 result[:name] ||= result[:spec][:id].to_s.to_sym |
454 result[:requirement] ||= VersionRequirement.create(result[:spec][:version]) |
455 result |
456 end |
457 |
458 # Test if this requirement is satisfied by an artifact spec. |
459 def satisfied_by?(spec) |
460 return false unless requirement |
461 spec = Artifact.to_hash(spec) |
462 hash = to_spec_hash |
463 hash.delete(:version) |
464 version = spec.delete(:version) |
465 hash == spec && requirement.satisfied_by?(version) |
466 end |
467 |
468 # Has user selected a version for this requirement? |
469 def selected? |
470 @selected |
471 end |
472 |
473 def selected! #:nodoc: |
474 @selected = true |
475 @listeners.each { |l| l.call(self) } if @listeners |
476 self |
477 end |
478 |
479 def add_listener(&callback) |
480 (@listeners ||= []) << callback |
481 end |
482 |
483 # Return the Artifact object for the currently selected version |
484 def artifact |
485 ::Buildr.artifact(self) |
486 end |
487 |
488 # Format this requirement as an `artifact requirement spec` |
489 def to_requirement_spec |
490 result = to_spec |
491 result = "#{name} -> #{result}" if name |
492 result = "#{result} -> #{requirement}" if requirement |
493 result |
494 end |
495 |
496 def to_s #:nodoc: |
497 id ? to_requirement_spec : version |
498 end |
499 |
500 # Return an artifact spec without the version part. |
501 def unversioned_spec |
502 str = to_spec |
503 return nil if str =~ /^:+/ |
504 ary = str.split(':') |
505 ary = ary[0...-1] if ary.size > 3 |
506 ary.join(':') |
507 end |
508 |
509 class << self |
510 # Return an artifact spec without the version part. |
511 def unversioned_spec(spec) |
512 str = spec.to_s |
513 return nil if str =~ /^:+/ |
514 ary = str.split(':') |
515 ary = ary[0...-1] if ary.size > 3 |
516 if ary.size > 2 |
517 ary.join(':') |
518 else |
519 new(spec).unversioned_spec |
520 end |
521 end |
522 end |
523 end |
524 |
525 include DClone |
526 include Enumerable |
527 attr_reader :name |
528 |
529 def initialize(name = nil) #:nodoc: |
530 @name = name.to_s if name |
531 end |
532 clear |
533 |
534 def root |
535 yield ROOT if block_given? |
536 ROOT |
537 end |
538 |
539 # ROOT namespace has no parent |
540 def parent |
541 if root? |
542 nil |
543 elsif @parent.kind_of?(ArtifactNamespace) |
544 @parent |
545 elsif @parent |
546 ArtifactNamespace.instance(@parent) |
547 elsif name |
548 parent_name = name.gsub(/::?[^:]+$/, '') |
549 parent_name == name ? root : ArtifactNamespace.instance(parent_name) |
550 else |
551 root |
552 end |
553 end |
554 |
555 # Set the parent for the current namespace, except if it is ROOT |
556 def parent=(other) |
557 raise 'Cannot set parent of root namespace' if root? |
558 @parent = other |
559 @registry = nil |
560 end |
561 |
562 # Is this the ROOT namespace? |
563 def root? |
564 ROOT == self |
565 end |
566 |
567 # Create a named sub-namespace, sub-namespaces are themselves |
568 # ArtifactNamespace instances but cannot be referenced by |
569 # the Buildr.artifact_ns, ArtifactNamespace.instance methods. |
570 # Reference needs to be through this object using the given +name+ |
571 # |
572 # artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0' |
573 # artifact_ns('foo').bar # => the sub-namespace 'foo.bar' |
574 # artifact_ns('foo').bar.thing # => the some thing artifact |
575 # |
576 # See the top level ArtifactNamespace documentation for examples |
577 def ns(name, *uses, &block) |
578 name = name.to_sym |
579 sub = registry[name] |
580 if sub |
581 raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace) |
582 else |
583 sub = ArtifactNamespace.new("#{self.name}.#{name}") |
584 sub.parent = self |
585 registry[name] = sub |
586 end |
587 sub.use(*uses) |
588 yield sub if block_given? |
589 sub |
590 end |
591 |
592 # Test if a sub-namespace by the given name exists |
593 def ns?(name) |
594 sub = registry[name.to_sym] |
595 ArtifactNamespace === sub |
596 end |
597 |
598 # :call-seq: |
599 # artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5' |
600 # artifact_ns.need :name => 'org.foo:bar:jar:1.0' |
601 # |
602 # Create a new ArtifactRequirement on this namespace. |
603 # ArtifactNamespace#method_missing provides syntactic sugar for this. |
604 def need(*specs) |
605 named = specs.flatten.inject({}) do |seen, spec| |
606 if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty? |
607 spec.each_pair do |name, spec| |
608 if Array === spec # a group |
609 seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) } |
610 else |
611 artifact = ArtifactRequirement.new(spec) |
612 artifact.name = name |
613 seen[artifact.name] ||= artifact |
614 end |
615 end |
616 else |
617 artifact = ArtifactRequirement.new(spec) |
618 seen[artifact.name] ||= artifact |
619 end |
620 seen |
621 end |
622 named.each_pair do |name, artifact| |
623 if Array === artifact # a group |
624 artifact.each do |a| |
625 unvers = a.unversioned_spec |
626 previous = registry[unvers] |
627 if previous && previous.selected? && a.satisfied_by?(previous) |
628 a.version = previous.version |
629 end |
630 registry[unvers] = a |
631 end |
632 group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}])) |
633 else |
634 unvers = artifact.unversioned_spec |
635 previous = registry.get(unvers, true) |
636 if previous && previous.selected? && artifact.satisfied_by?(previous) |
637 artifact.version = previous.version |
638 artifact.selected! |
639 end |
640 registry[unvers] = artifact |
641 registry.alias name, unvers unless name.to_s[/^\s*$/] |
642 end |
643 end |
644 self |
645 end |
646 |
647 # :call-seq: |
648 # artifact_ns.use 'name -> org:foo:bar:jar:1.2.3' |
649 # artifact_ns.use :name => 'org:foo:bar:jar:1.2.3' |
650 # artifact_ns.use :name => '2.5.6' |
651 # |
652 # First and second form are equivalent, the third is used when an |
653 # ArtifactRequirement has been previously defined with :name, so it |
654 # just selects the version. |
655 # |
656 # ArtifactNamespace#method_missing provides syntactic sugar for this. |
657 def use(*specs) |
658 named = specs.flatten.inject({}) do |seen, spec| |
659 if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty? |
660 spec.each_pair do |name, spec| |
661 if ArtifactNamespace === spec # create as subnamespace |
662 raise ArgumentError.new("Circular reference") if self == spec |
663 registry[name.to_sym] = spec |
664 elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec)) |
665 artifact = ArtifactRequirement.allocate |
666 artifact.name = name |
667 artifact.version = spec.to_s |
668 seen[artifact.name] ||= artifact |
669 elsif Symbol === spec |
670 self.alias name, spec |
671 elsif Array === spec # a group |
672 seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) } |
673 else |
674 artifact = ArtifactRequirement.new(spec) |
675 artifact.name = name |
676 seen[artifact.name] ||= artifact |
677 end |
678 end |
679 else |
680 if Symbol === spec |
681 artifact = get(spec).dclone |
682 else |
683 artifact = ArtifactRequirement.new(spec) |
684 end |
685 seen[artifact.name] ||= artifact |
686 end |
687 seen |
688 end |
689 named.each_pair do |name, artifact| |
690 is_group = Array === artifact |
691 artifact = [artifact].flatten.map do |artifact| |
692 unvers = artifact.unversioned_spec |
693 previous = get(unvers, false) || get(name, false) |
694 if previous # have previous on current namespace |
695 if previous.requirement # we must satisfy the requirement |
696 unless unvers # we only have the version |
697 satisfied = previous.requirement.satisfied_by?(artifact.version) |
698 else |
699 satisfied = previous.satisfied_by?(artifact) |
700 end |
701 raise "Unsatisfied dependency #{previous} " + |
702 "not satisfied by #{artifact}" unless satisfied |
703 previous.version = artifact.version # OK, set new version |
704 artifact = previous # use the same object for aliases |
705 else # not a requirement, set the new values |
706 unless artifact.id == previous.id && name != previous.name |
707 previous.copy_attrs(artifact) |
708 artifact = previous |
709 end |
710 end |
711 else |
712 if unvers.nil? && # we only have the version |
713 (previous = get(unvers, true, false, false)) |
714 version = artifact.version |
715 artifact.copy_attrs(previous) |
716 artifact.version = version |
717 end |
718 artifact.requirement = nil |
719 end |
720 artifact.selected! |
721 end |
722 artifact = artifact.first unless is_group |
723 if is_group |
724 names = artifact.map do |art| |
725 unv = art.unversioned_spec |
726 registry[unv] = art |
727 unv |
728 end |
729 group(name, *(names + [{:namespace => self}])) |
730 elsif artifact.id |
731 unvers = artifact.unversioned_spec |
732 registry[name] = artifact |
733 registry.alias unvers, name |
734 else |
735 registry[name] = artifact |
736 end |
737 end |
738 self |
739 end |
740 |
741 # Like Hash#fetch |
742 def fetch(name, default = nil, &block) |
743 block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") } |
744 real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name) |
745 get(real_name.to_sym) || default || block.call(name) |
746 end |
747 |
748 # :call-seq: |
749 # artifact_ns[:name] -> ArtifactRequirement |
750 # artifact_ns[:many, :names] -> [ArtifactRequirement] |
751 def [](*names) |
752 ary = values_at(*names) |
753 names.size == 1 ? ary.first : ary |
754 end |
755 |
756 # :call-seq: |
757 # artifact_ns[:name] = 'some:cool:jar:1.0.2' |
758 # artifact_ns[:name] = '1.0.2' |
759 # |
760 # Just like the use method |
761 def []=(*names) |
762 values = names.pop |
763 values = [values] unless Array === values |
764 names.each_with_index do |name, i| |
765 use name => (values[i] || values.last) |
766 end |
767 end |
768 |
769 # yield each ArtifactRequirement |
770 def each(&block) |
771 values.each(&block) |
772 end |
773 |
774 # return Artifact objects for each requirement |
775 def artifacts(*names) |
776 (names.empty? && values || values_at(*names)).map(&:artifact) |
777 end |
778 |
779 # Return all requirements for this namespace |
780 def values(include_parents = false, include_groups = true) |
781 seen, dict = {}, registry |
782 while dict |
783 dict.each do |k, v| |
784 v = v.call if v.respond_to?(:call) |
785 v = v.values if v.kind_of?(ArtifactNamespace) |
786 if Array === v && include_groups |
787 v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) } |
788 else |
789 seen[v.name] = v unless seen.key?(v.name) |
790 end |
791 end |
792 dict = include_parents ? dict.parent : nil |
793 end |
794 seen.values |
795 end |
796 |
797 # Return only the named requirements |
798 def values_at(*names) |
799 names.map do |name| |
800 catch :artifact do |
801 unless name.to_s[/^[\w\-\.]+$/] |
802 unvers = ArtifactRequirement.unversioned_spec(name) |
803 unless unvers.to_s == name.to_s |
804 req = ArtifactRequirement.new(name) |
805 reg = self |
806 while reg |
807 candidate = reg.send(:get, unvers, false, false, true) |
808 throw :artifact, candidate if req.satisfied_by?(candidate) |
809 reg = reg.parent |
810 end |
811 end |
812 end |
813 get(name.to_sym) |
814 end |
815 end |
816 end |
817 |
818 def key?(name, include_parents = false) |
819 name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/] |
820 registry.key?(name, include_parents) |
821 end |
822 |
823 def keys |
824 values.map(&:name) |
825 end |
826 |
827 def delete(name, include_parents = false) |
828 registry.delete(name, include_parents) |
829 self |
830 end |
831 |
832 def clear |
833 keys.each { |k| delete(k) } |
834 end |
835 |
836 # :call-seq: |
837 # group :who, :me, :you |
838 # group :them, :me, :you, :namespace => ns |
839 # |
840 # Create a virtual group on this namespace. When the namespace |
841 # is asked for the +who+ artifact, it's value is an array made from |
842 # the remaining names. These names are searched by default from the current |
843 # namespace. |
844 # Second form specified the starting namespace to search from. |
845 def group(group_name, *members) |
846 namespace = (Hash === members.last && members.pop[:namespace]) || :current |
847 registry[group_name] = lambda do |
848 artifacts = self.class[namespace].values_at(*members) |
849 artifacts = artifacts.first if members.size == 1 |
850 artifacts |
851 end |
852 self |
853 end |
854 |
855 alias_method :virtual, :group |
856 |
857 # Create an alias for a named requirement. |
858 def alias(new_name, old_name) |
859 registry.alias(new_name, old_name) or |
860 raise NameError.new("Undefined artifact name: #{old_name}") |
861 end |
862 |
863 def to_s #:nodoc: |
864 name.to_s |
865 end |
866 |
867 # :call-seq: |
868 # artifact_ns.cool_aid!('cool:aid:jar:2.3.4', '~>2.3') -> artifact_requirement |
869 # artifact_ns.cool_aid = '2.3.5' |
870 # artifact_ns.cool_aid -> artifact_requirement |
871 # artifact_ns.cool_aid? -> true | false |
872 # |
873 # First form creates an ArtifactRequirement on the namespace. |
874 # It is equivalent to providing a requirement_spec to the #need method: |
875 # artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3" |
876 # the second argument is optional. |
877 # |
878 # Second form can be used to select an artifact version |
879 # and is equivalent to: |
880 # artifact_ns.use :cool_aid => '1.0' |
881 # |
882 # Third form obtains the named ArtifactRequirement, can be |
883 # used to test if a named requirement has been defined. |
884 # It is equivalent to: |
885 # artifact_ns.fetch(:cool_aid) { nil } |
886 # |
887 # Last form tests if the ArtifactRequirement has been defined |
888 # and a version has been selected for use. |
889 # It is equivalent to: |
890 # |
891 # artifact_ns.has_cool_aid? |
892 # artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? } |
893 # |
894 def method_missing(name, *args, &block) |
895 case name.to_s |
896 when /!$/ then |
897 name = $`.intern |
898 if args.size < 1 || args.size > 2 |
899 raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)") |
900 end |
901 need name => args.first |
902 get(name).tap { |r| r.requirement = args.last if args.size == 2 } |
903 when /=$/ then use $` => args.first |
904 when /\?$/ then |
905 name = $`.gsub(/^(has|have)_/, '').intern |
906 [get(name)].flatten.all? { |a| a && a.selected? } |
907 else |
908 if block || args.size > 0 |
909 raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given") |
910 end |
911 get(name) |
912 end |
913 end |
914 |
915 # Return an anonymous module |
916 # # first create a requirement |
917 # artifact_ns.cool_aid! 'cool:aid:jar:>=1.0' |
918 # |
919 # # extend an object as a cool_aid delegator |
920 # jars = Object.new.extend(artifact_ns.accessor(:cool_aid)) |
921 # jars.cool_aid = '2.0' |
922 # |
923 # artifact_ns.cool_aid.version # -> '2.0' |
924 def accessor(*names) |
925 ns = self |
926 Module.new do |
927 names.each do |name| |
928 define_method("#{name}") { ns.send("#{name}") } |
929 define_method("#{name}?") { ns.send("#{name}?") } |
930 define_method("#{name}=") { |vers| ns.send("#{name}=", vers) } |
931 end |
932 end |
933 end |
934 |
935 private |
936 def get(name, include_parents = true, include_subs = true, include_self = true) #:nodoc: |
937 if include_subs && name.to_s[/_/] # try sub namespaces first |
938 sub, parts = self, name.to_s.split('_') |
939 sub_name = parts.shift.to_sym |
940 until sub != self || parts.empty? |
941 if registry[sub_name].kind_of?(ArtifactNamespace) |
942 sub = registry[sub_name] |
943 artifact = sub[parts.join('_')] |
944 else |
945 sub_name = [sub_name, parts.shift].join('_').to_sym |
946 end |
947 end |
948 end |
949 unless artifact |
950 if include_self |
951 artifact = registry.get(name, include_parents) |
952 elsif include_parents && registry.parent |
953 artifact = registry.parent.get(name, true) |
954 end |
955 end |
956 artifact = artifact.call if artifact.respond_to?(:call) |
957 artifact |
958 end |
959 |
960 def registry |
961 @registry ||= Registry.new.tap do |m| |
962 m.parent = parent.send(:registry) unless root? |
963 end |
964 end |
965 |
966 end # ArtifactNamespace |
967 |
968 # :call-seq: |
969 # project.artifact_ns -> ArtifactNamespace |
970 # Buildr.artifact_ns(name) -> ArtifactNamespace |
971 # Buildr.artifact_ns -> ArtifactNamespace for the currently running Project |
972 # |
973 # Open an ArtifactNamespace. |
974 # If a block is provided, the namespace is yielded to it. |
975 # |
976 # See also ArtifactNamespace.instance |
977 def artifact_ns(name = nil, &block) |
978 name = self if name.nil? && self.kind_of?(Project) |
979 ArtifactNamespace.instance(name, &block) |
980 end |
981 |
982 end |
983 |
984 |
Generated on 2011-07-06 23:35:38 -0700 with rcov 0.9.8