realityforge-buildr 1.5.9

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