realityforge-buildr 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,1113 @@
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
+ desc 'Download all artifacts'
19
+ task 'artifacts'
20
+
21
+ desc "Download all artifacts' sources"
22
+ task 'artifacts:sources'
23
+
24
+ desc "Download all artifacts' external annotations"
25
+ task 'artifacts:annotations'
26
+
27
+ desc "Download all artifacts' javadoc"
28
+ task 'artifacts:javadoc'
29
+
30
+ # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
31
+ #
32
+ # An artifact has an identifier, group identifier, type, version number and
33
+ # optional classifier. All can be used to locate it in the local repository,
34
+ # download from or upload to a remote repository.
35
+ #
36
+ # The #to_spec and #to_hash methods allow it to be used everywhere an artifact is
37
+ # accepted.
38
+ module ActsAsArtifact
39
+
40
+ ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version]
41
+ MAVEN_METADATA = 'maven-metadata.xml'
42
+
43
+ class << self
44
+ private
45
+
46
+ # :stopdoc:
47
+ def included(mod)
48
+ mod.extend self
49
+ end
50
+
51
+ def extend_object(base)
52
+ base.instance_eval { alias :install_old :install } if base.respond_to? :install
53
+ base.instance_eval { alias :uninstall_old :uninstall } if base.respond_to? :uninstall
54
+ base.instance_eval { alias :upload_old :upload } if base.respond_to? :upload
55
+ super
56
+ end
57
+
58
+ def extended(base)
59
+ #We try to keep the previous instance methods defined on the base instance if there were ones.
60
+ base.instance_eval { alias :install :install_old } if base.respond_to? :install_old
61
+ base.instance_eval { alias :uninstall :uninstall_old } if base.respond_to? :uninstall_old
62
+ base.instance_eval { alias :upload :upload_old } if base.respond_to? :upload_old
63
+ end
64
+
65
+ # :startdoc:
66
+ end
67
+
68
+ # The artifact identifier.
69
+ attr_reader :id
70
+ # The group identifier.
71
+ attr_reader :group
72
+ # The file type. (Symbol)
73
+ attr_reader :type
74
+ # The version number.
75
+ attr_reader :version
76
+ # Optional artifact classifier.
77
+ attr_reader :classifier
78
+
79
+ attr_accessor :buildr_project
80
+
81
+ def snapshot?
82
+ version =~ /-SNAPSHOT$/
83
+ end
84
+
85
+ def final_version
86
+ return version unless snapshot?
87
+ Time.now.strftime("%Y%m%d.%H%M%S")
88
+ end
89
+
90
+ # :call-seq:
91
+ # to_spec_hash => Hash
92
+ #
93
+ # Returns the artifact specification as a hash. For example:
94
+ # com.example:app:jar:1.2
95
+ # becomes:
96
+ # { :group=>'com.example',
97
+ # :id=>'app',
98
+ # :type=>:jar,
99
+ # :version=>'1.2' }
100
+ def to_spec_hash
101
+ base = { :group=>group, :id=>id, :type=>type, :version=>version }
102
+ classifier ? base.merge(:classifier=>classifier) : base
103
+ end
104
+ alias_method :to_hash, :to_spec_hash
105
+
106
+ # :call-seq:
107
+ # to_spec => String
108
+ #
109
+ # Returns the artifact specification, in the structure:
110
+ # <group>:<artifact>:<type>:<version>
111
+ # or
112
+ # <group>:<artifact>:<type>:<classifier>:<version>
113
+ def to_spec
114
+ classifier ? "#{group}:#{id}:#{type}:#{classifier}:#{version}" : "#{group}:#{id}:#{type}:#{version}"
115
+ end
116
+
117
+ # :call-seq:
118
+ # pom => Artifact
119
+ #
120
+ # Convenience method that returns a POM artifact.
121
+ def pom
122
+ return self if type == :pom
123
+ Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom)
124
+ end
125
+
126
+ # :call-seq:
127
+ # sources_artifact => Artifact
128
+ #
129
+ # Convenience method that returns a sources artifact.
130
+ def sources_artifact
131
+ sources_spec = to_spec_hash.merge(:classifier=>'sources')
132
+ sources_task = OptionalArtifact.define_task(Buildr.repositories.locate(sources_spec))
133
+ sources_task.send :apply_spec, sources_spec
134
+ sources_task
135
+ end
136
+
137
+ # :call-seq:
138
+ # javadoc_artifact => Artifact
139
+ #
140
+ # Convenience method that returns the associated javadoc artifact
141
+ def javadoc_artifact
142
+ javadoc_spec = to_spec_hash.merge(:classifier=>'javadoc')
143
+ javadoc_task = OptionalArtifact.define_task(Buildr.repositories.locate(javadoc_spec))
144
+ javadoc_task.send :apply_spec, javadoc_spec
145
+ javadoc_task
146
+ end
147
+
148
+ # :call-seq:
149
+ # annotations_artifact => Artifact
150
+ #
151
+ # Convenience method that returns an annotations artifact. The annotations artifact is used by
152
+ # Intellij IDEA as a source of external annotations.
153
+ def annotations_artifact
154
+ annotations_spec = to_spec_hash.merge(:classifier=>'annotations')
155
+ annotations_task = OptionalArtifact.define_task(Buildr.repositories.locate(annotations_spec))
156
+ annotations_task.send :apply_spec, annotations_spec
157
+ annotations_task
158
+ end
159
+
160
+ # :call-seq:
161
+ # pom_xml => string
162
+ #
163
+ # Creates POM XML for this artifact.
164
+ def pom_xml
165
+ if self.buildr_project
166
+ Buildr::CustomPom.pom_xml(self.buildr_project, self)
167
+ else
168
+ Proc.new do
169
+ xml = Builder::XmlMarkup.new(:indent => 2)
170
+ xml.instruct!
171
+ xml.project do
172
+ xml.modelVersion '4.0.0'
173
+ xml.groupId group
174
+ xml.artifactId id
175
+ xml.version version
176
+ xml.classifier classifier if classifier
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ # :call-seq:
183
+ # maven_metadata_xml => string
184
+ #
185
+ # Creates Maven Metadata XML content for this artifact.
186
+ def maven_metadata_xml
187
+ xml = Builder::XmlMarkup.new(:indent=>2)
188
+ xml.instruct!
189
+ xml.metadata do
190
+ xml.groupId group
191
+ xml.artifactId id
192
+ xml.version version
193
+ xml.versioning do
194
+ xml.snapshot do
195
+ xml.timestamp final_version
196
+ xml.buildNumber 1
197
+ end
198
+ xml.lastupdated Time.now.strftime("%Y%m%d%H%M%S")
199
+ end
200
+ end
201
+ end
202
+
203
+ def install
204
+ invoke
205
+ in_local_repository = Buildr.repositories.locate(self)
206
+ if pom && pom != self && classifier.nil?
207
+ pom.invoke
208
+ pom.install
209
+ end
210
+ if name != in_local_repository
211
+ mkpath File.dirname(in_local_repository)
212
+ cp name, in_local_repository, :preserve => false
213
+ info "Installed #{name} to #{in_local_repository}"
214
+ end
215
+ end
216
+
217
+ def uninstall
218
+ installed = Buildr.repositories.locate(self)
219
+ rm installed if File.exist?(installed)
220
+ pom.uninstall if pom && pom != self && classifier.nil?
221
+ end
222
+
223
+ # :call-seq:
224
+ # upload
225
+ # upload(url)
226
+ # upload(options)
227
+ #
228
+ # Uploads the artifact, its POM and digital signatures to remote server.
229
+ #
230
+ # In the first form, uses the upload options specified by repositories.release_to
231
+ # or repositories.snapshot_to if the subject is a snapshot artifact.
232
+ # In the second form, uses a URL that includes all the relevant information.
233
+ # In the third form, uses a hash with the options :url, :username, :password,
234
+ # and :permissions. All but :url are optional.
235
+ def upload(upload_to = nil)
236
+ upload_task(upload_to).invoke
237
+ end
238
+
239
+ def upload_task(upload_to = nil)
240
+ upload_to ||= Buildr.repositories.snapshot_to if snapshot? && Buildr.repositories.snapshot_to != nil && Buildr.repositories.snapshot_to[:url] != nil
241
+ upload_to ||= Buildr.repositories.release_to
242
+ upload_to = { :url=>upload_to } unless Hash === upload_to
243
+ raise ArgumentError, 'Don\'t know where to upload, perhaps you forgot to set repositories.release_to' unless upload_to[:url]
244
+
245
+ # Set the upload URI, including mandatory slash (we expect it to be the base directory).
246
+ # Username/password may be part of URI, or separate entities.
247
+ uri = URI.parse(upload_to[:url].clone)
248
+ uri.path = uri.path + '/' unless uri.path[-1] == '/'
249
+ to_escape = "!\"\#$%&'()*+,-./:;<=>?@{}|~`'"
250
+ uri.user = URI.encode(upload_to[:username], to_escape) if upload_to[:username]
251
+ uri.password = URI.encode(upload_to[:password], to_escape) if upload_to[:password]
252
+
253
+ path = group.gsub('.', '/') + "/#{id}/#{version}/#{upload_name}"
254
+
255
+ unless task = Buildr.application.lookup(uri+path)
256
+ deps = [self]
257
+ deps << pom.upload_task( upload_to ) if pom && pom != self && classifier.nil?
258
+
259
+ task = Rake::Task.define_task uri + path => deps do
260
+ # Upload artifact relative to base URL, need to create path before uploading.
261
+ options = upload_to[:options] || {:permissions => upload_to[:permissions]}
262
+ info "Deploying #{to_spec}"
263
+ URI.upload uri + path, name, options
264
+ if snapshot? && pom != self
265
+ maven_metadata = group.gsub('.', '/') + "/#{id}/#{version}/#{MAVEN_METADATA}"
266
+ URI.write uri + maven_metadata, maven_metadata_xml, :permissions => upload_to[:permissions]
267
+ end
268
+ end
269
+ end
270
+ task
271
+ end
272
+
273
+ protected
274
+
275
+ # Apply specification to this artifact.
276
+ def apply_spec(spec)
277
+ spec = Artifact.to_hash(spec)
278
+ ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) }
279
+ self
280
+ end
281
+
282
+ def group_path
283
+ group.gsub('.', '/')
284
+ end
285
+
286
+ def upload_name
287
+ return File.basename(name) unless snapshot?
288
+ return File.basename(name).gsub(/SNAPSHOT/, "#{final_version}-1")
289
+ end
290
+
291
+ def extract_type(type)
292
+ return :jar if type == :bundle
293
+ type
294
+ end
295
+
296
+ end
297
+
298
+
299
+ # A file task referencing an artifact in the local repository.
300
+ #
301
+ # This task includes all the artifact attributes (group, id, version, etc). It points
302
+ # to the artifact's path in the local repository. When invoked, it will download the
303
+ # artifact into the local repository if the artifact does not already exist.
304
+ #
305
+ # Note: You can enhance this task to create the artifact yourself, e.g. download it from
306
+ # a site that doesn't have a remote repository structure, copy it from a different disk, etc.
307
+ class Artifact < Rake::FileTask
308
+
309
+ # The default artifact type.
310
+ DEFAULT_TYPE = :jar
311
+
312
+ include ActsAsArtifact, Buildr
313
+
314
+ class << self
315
+
316
+ # :call-seq:
317
+ # lookup(spec) => Artifact
318
+ #
319
+ # Lookup a previously registered artifact task based on its specification (String or Hash).
320
+ def lookup(spec)
321
+ @artifacts ||= {}
322
+ @artifacts[to_spec(spec)]
323
+ end
324
+
325
+ # :call-seq:
326
+ # list => specs
327
+ #
328
+ # Returns an array of specs for all the registered artifacts. (Anything created from artifact, or package).
329
+ def list
330
+ @artifacts ||= {}
331
+ @artifacts.keys
332
+ end
333
+
334
+ # :call-seq:
335
+ # register(artifacts) => artifacts
336
+ #
337
+ # Register an artifact task(s) for later lookup (see #lookup).
338
+ def register(*tasks)
339
+ @artifacts ||= {}
340
+ fail 'You can only register an artifact task, one of the arguments is not a Task that responds to to_spec' unless
341
+ tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) }
342
+ tasks.each { |task| @artifacts[task.to_spec] = task }
343
+ tasks
344
+ end
345
+
346
+ # :call-seq:
347
+ # to_hash(spec_hash) => spec_hash
348
+ # to_hash(spec_string) => spec_hash
349
+ # to_hash(artifact) => spec_hash
350
+ #
351
+ # Turn a spec into a hash. This method accepts a String, Hash or any object that responds to
352
+ # the method to_spec. There are several reasons to use this method:
353
+ # * You can pass anything that could possibly be a spec, and get a hash.
354
+ # * It will check that the spec includes the group identifier, artifact
355
+ # identifier and version number and set the file type, if missing.
356
+ # * It will always return a new specs hash.
357
+ def to_hash(spec)
358
+ if spec.respond_to?(:to_spec)
359
+ to_hash spec.to_spec
360
+ elsif Hash === spec
361
+ rake_check_options spec, :id, :group, :type, :classifier, :version, :scope
362
+ # Sanitize the hash and check it's valid.
363
+ spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k].to_s if spec[k] ; h }
364
+ fail "Missing group identifier for #{spec.inspect}" unless spec[:group]
365
+ fail "Missing artifact identifier for #{spec.inspect}" unless spec[:id]
366
+ fail "Missing version for #{spec.inspect}" unless spec[:version]
367
+ spec[:type] = (spec[:type] || DEFAULT_TYPE).to_sym
368
+ spec
369
+ elsif String === spec
370
+ group, id, type, version, *rest = spec.split(':').map { |part| part.empty? ? nil : part }
371
+ unless rest.empty?
372
+ # Optional classifier comes before version.
373
+ classifier, version = version, rest.shift
374
+ fail "Expecting <group:id:type:version> or <group:id:type:classifier:version>, found <#{spec}>" unless rest.empty?
375
+ end
376
+ to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier
377
+ else
378
+ fail 'Expecting a String, Hash or object that responds to to_spec'
379
+ end
380
+ end
381
+
382
+ # :call-seq:
383
+ # to_spec(spec_hash) => spec_string
384
+ #
385
+ # Convert a hash back to a spec string. This method accepts
386
+ # a string, hash or any object that responds to to_spec.
387
+ def to_spec(hash)
388
+ hash = to_hash(hash) unless Hash === hash
389
+ version = ":#{hash[:version]}" if hash[:version]
390
+ classifier = ":#{hash[:classifier]}" if hash[:classifier]
391
+ "#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_TYPE}#{classifier}#{version}"
392
+ end
393
+
394
+ # :call-seq:
395
+ # hash_to_file_name(spec_hash) => file_name
396
+ #
397
+ # Convert a hash spec to a file name.
398
+ def hash_to_file_name(hash)
399
+ version = "-#{hash[:version]}" if hash[:version]
400
+ classifier = "-#{hash[:classifier]}" if hash[:classifier]
401
+ "#{hash[:id]}#{version}#{classifier}.#{extract_type(hash[:type]) || DEFAULT_TYPE}"
402
+ end
403
+
404
+ end
405
+
406
+ def initialize(*args) #:nodoc:
407
+ super
408
+ enhance do |task|
409
+ # Default behavior: download the artifact from one of the remote repositories
410
+ # if the file does not exist. But this default behavior is counter productive
411
+ # if the artifact knows how to build itself (e.g. download from a different location),
412
+ # so don't perform it if the task found a different way to create the artifact.
413
+ task.enhance do
414
+ if download_needed? task
415
+ info "Downloading #{to_spec}"
416
+ download
417
+ pom.invoke rescue nil if pom && pom != self && classifier.nil?
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ # :call-seq:
424
+ # from(path) => self
425
+ #
426
+ # Use this when you want to install or upload an artifact from a given file, for example:
427
+ # test = artifact('group:id:jar:1.0').from('test.jar')
428
+ # install test
429
+ # See also Buildr#install and Buildr#upload.
430
+ def from(path)
431
+ @from = path.is_a?(Rake::Task) ? path : File.expand_path(path.to_s)
432
+ enhance [@from] do
433
+ mkpath File.dirname(name)
434
+ cp @from.to_s, name
435
+ end
436
+ pom.content pom_xml unless pom == self || pom.has_content?
437
+ self
438
+ end
439
+
440
+ # :call-seq:
441
+ # content(string) => self
442
+ # content(Proc) => self
443
+ #
444
+ # Use this when you want to install or upload an artifact from a given content, for example:
445
+ # readme = artifact('com.example:readme:txt:1.0').content(<<-EOF
446
+ # Please visit our website at http://example.com/readme
447
+ # <<EOF
448
+ # install readme
449
+ #
450
+ # If the argument is a Proc the it will be called when the artifact is written out. If the result is not a proc
451
+ # and not a string, it will be converted to a string using to_s
452
+ def content(string = nil)
453
+ unless string
454
+ @content = @content.call if @content.is_a?(Proc)
455
+ return @content
456
+ end
457
+
458
+ unless @content
459
+ enhance do
460
+ write name, self.content
461
+ end
462
+
463
+ class << self
464
+ # Force overwriting target since we don't have source file
465
+ # to check for timestamp modification
466
+ def needed?
467
+ true
468
+ end
469
+ end
470
+ end
471
+ @content = string
472
+ pom.content pom_xml unless pom == self || pom.has_content?
473
+ self
474
+ end
475
+
476
+ protected
477
+
478
+ def has_content?
479
+ @from || @content
480
+ end
481
+
482
+ # :call-seq:
483
+ # download
484
+ #
485
+ # Downloads an artifact from one of the remote repositories, and stores it in the local
486
+ # repository. Raises an exception if the artifact is not found.
487
+ #
488
+ # This method attempts to download the artifact from each repository in the order in
489
+ # which they are returned from #remote, until successful.
490
+ def download
491
+ trace "Downloading #{to_spec}"
492
+ remote = Buildr.repositories.remote_uri
493
+ remote = remote.each { |repo_url| repo_url.path += '/' unless repo_url.path[-1] == '/' }
494
+ fail "Unable to download #{to_spec}. No remote repositories defined." if remote.empty?
495
+ exact_success = remote.find do |repo_url|
496
+ begin
497
+ path = "#{group_path}/#{id}/#{version}/#{File.basename(name)}"
498
+ download_artifact(repo_url + path)
499
+ true
500
+ rescue URI::NotFoundError
501
+ false
502
+ rescue Exception=>error
503
+ info error
504
+ trace error.backtrace.join("\n")
505
+ false
506
+ end
507
+ end
508
+
509
+ if exact_success
510
+ return
511
+ elsif snapshot?
512
+ download_m2_snapshot(remote)
513
+ else
514
+ fail_download(remote)
515
+ end
516
+ end
517
+
518
+ def download_m2_snapshot(remote_uris)
519
+ remote_uris.find do |repo_url|
520
+ snapshot_url = current_snapshot_repo_url(repo_url)
521
+ if snapshot_url
522
+ begin
523
+ download_artifact snapshot_url
524
+ true
525
+ rescue URI::NotFoundError
526
+ false
527
+ end
528
+ else
529
+ false
530
+ end
531
+ end or fail_download(remote_uris)
532
+ end
533
+
534
+ def current_snapshot_repo_url(repo_url)
535
+ begin
536
+ metadata_path = "#{group_path}/#{id}/#{version}/maven-metadata.xml"
537
+ metadata_xml = StringIO.new
538
+ URI.download repo_url + metadata_path, metadata_xml
539
+ metadata = REXML::Document.new(metadata_xml.string).root
540
+ timestamp = REXML::XPath.first(metadata, '//timestamp')
541
+ build_number = REXML::XPath.first(metadata, '//buildNumber')
542
+ error "No timestamp provided for the snapshot #{to_spec}" if timestamp.nil?
543
+ error "No build number provided for the snapshot #{to_spec}" if build_number.nil?
544
+ return nil if timestamp.nil? || build_number.nil?
545
+ snapshot_of = version[0, version.size - 9]
546
+ classifier_snippet = (classifier != nil) ? "-#{classifier}" : ""
547
+ repo_url + "#{group_path}/#{id}/#{version}/#{id}-#{snapshot_of}-#{timestamp.text}-#{build_number.text}#{classifier_snippet}.#{type}"
548
+ rescue URI::NotFoundError
549
+ nil
550
+ end
551
+ end
552
+
553
+ def fail_download(remote_uris)
554
+ fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}"
555
+ end
556
+
557
+ protected
558
+
559
+ # :call-seq:
560
+ # needed?
561
+ #
562
+ # Validates whether artifact is required to be downloaded from repository
563
+ def needed?
564
+ return true if snapshot? && File.exist?(name) && (update_snapshot? || old?)
565
+ super
566
+ end
567
+
568
+ private
569
+
570
+ # :call-seq:
571
+ # download_artifact
572
+ #
573
+ # Downloads artifact from given repository,
574
+ # supports downloading snapshot artifact with relocation on succeed to local repository
575
+ def download_artifact(path)
576
+ download_file = "#{name}.#{Time.new.to_i}"
577
+ begin
578
+ URI.download path, download_file
579
+ if File.exist?(download_file)
580
+ FileUtils.mkdir_p(File.dirname(name))
581
+ FileUtils.mv(download_file, name)
582
+ end
583
+ ensure
584
+ File.delete(download_file) if File.exist?(download_file)
585
+ end
586
+ end
587
+
588
+ # :call-seq:
589
+ # :download_needed?
590
+ #
591
+ # Validates whether artifact is required to be downloaded from repository
592
+ def download_needed?(task)
593
+ return true if !File.exist?(name)
594
+
595
+ if snapshot?
596
+ return false if offline? && File.exist?(name)
597
+ return true if update_snapshot? || old?
598
+ end
599
+
600
+ return false
601
+ end
602
+
603
+ def update_snapshot?
604
+ Buildr.application.options.update_snapshots
605
+ end
606
+
607
+ def offline?
608
+ Buildr.application.options.work_offline
609
+ end
610
+
611
+ # :call-seq:
612
+ # old?
613
+ #
614
+ # Checks whether existing artifact is older than period from build settings or one day
615
+ def old?
616
+ settings = Buildr.application.settings
617
+ time_to_be_old = settings.user[:expire_time] || settings.build[:expire_time] || 60 * 60 * 24
618
+ File.mtime(name).to_i < (Time.new.to_i - time_to_be_old)
619
+ end
620
+
621
+ end
622
+
623
+
624
+ # An artifact that is optional.
625
+ # If downloading fails, the user will be informed but it will not raise an exception.
626
+ class OptionalArtifact < Artifact
627
+
628
+ protected
629
+
630
+ # If downloading fails, the user will be informed but it will not raise an exception.
631
+ def download
632
+ super
633
+ rescue
634
+ info "Failed to download #{to_spec}. Skipping it."
635
+ end
636
+
637
+ end
638
+
639
+
640
+ # Holds the path to the local repository, URLs for remote repositories, and settings for release server.
641
+ #
642
+ # You can access this object from the #repositories method. For example:
643
+ # repositories.remote << 'http://example.com/repo'
644
+ class Repositories
645
+ include Singleton
646
+
647
+ # :call-seq:
648
+ # local => path
649
+ #
650
+ # Returns the path to the local repository.
651
+ #
652
+ # The default path is .m2/repository relative to the home directory.
653
+ # You can set this using the M2_REPO environment variable or the repositories/local
654
+ # value in your settings.yaml file.
655
+ def local
656
+ @local ||= File.expand_path(ENV['M2_REPO'] || ENV['local_repo'] ||
657
+ (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['local']) ||
658
+ File.join(ENV['HOME'], '.m2/repository'))
659
+ end
660
+
661
+ # :call-seq:
662
+ # local = path
663
+ #
664
+ # Sets the path to the local repository.
665
+ #
666
+ # The best place to set the local repository path is from a buildr.rb file
667
+ # located in the .buildr directory under your home directory. That way all
668
+ # your projects will share the same path, without affecting other developers
669
+ # collaborating on these projects.
670
+ def local=(dir)
671
+ @local = dir ? File.expand_path(dir) : nil
672
+ end
673
+
674
+ # :call-seq:
675
+ # locate(spec) => path
676
+ #
677
+ # Locates an artifact in the local repository based on its specification, and returns
678
+ # a file path.
679
+ #
680
+ # For example:
681
+ # locate :group=>'log4j', :id=>'log4j', :version=>'1.1'
682
+ # => ~/.m2/repository/log4j/log4j/1.1/log4j-1.1.jar
683
+ def locate(spec)
684
+ spec = Artifact.to_hash(spec)
685
+ File.join(local, spec[:group].split('.'), spec[:id], spec[:version], Artifact.hash_to_file_name(spec))
686
+ end
687
+
688
+ # :call-seq:
689
+ # mirrors => Array
690
+ #
691
+ # Returns an array of all the mirror repository URLs.
692
+ #
693
+ # Mirrors override remote repositories defined in the project.
694
+ # The best way is to add repositories to the user settings file under '$HOME/.buildr/settings.yaml'.
695
+ # For example:
696
+ # repositories:
697
+ # mirrors:
698
+ # - http://example.com/repository
699
+ def mirrors
700
+ unless @mirrors
701
+ @mirrors = [Buildr.settings.user, Buildr.settings.build].inject([]) { |repos, hash|
702
+ repos | Array(hash['repositories'] && hash['repositories']['mirrors'])
703
+ }
704
+ end
705
+ @mirrors
706
+ end
707
+
708
+ # :call-seq:
709
+ # remote = Array
710
+ # remote = url
711
+ # remote = nil
712
+ #
713
+ # With a String argument, clears the array and set it to that single URL.
714
+ #
715
+ # With an Array argument, clears the array and set it to these specific URLs.
716
+ #
717
+ # With nil, clears the array.
718
+ def mirrors=(urls)
719
+ case urls
720
+ when nil then @mirrors = nil
721
+ when Array then @mirrors = urls.dup
722
+ else @mirrors = [urls.to_s]
723
+ end
724
+ end
725
+
726
+ # :call-seq:
727
+ # remote => Array
728
+ #
729
+ # Returns an array of all the remote repository URLs.
730
+ #
731
+ # When downloading artifacts, repositories are accessed in the order in which they appear here.
732
+ # The best way is to add repositories individually, for example:
733
+ # repositories.remote << 'http://example.com/repo'
734
+ #
735
+ # You can also specify remote repositories in the settings.yaml (per user) and build.yaml (per build)
736
+ # files. Both sets of URLs are loaded by default into this array, URLs from the personal setting
737
+ # showing first.
738
+ #
739
+ # For example:
740
+ # repositories:
741
+ # remote:
742
+ # - http://example.com/repo
743
+ # - http://elsewhere.com/repo
744
+ def remote
745
+ unless mirrors.empty?
746
+ info "Remote repositories overridden by mirrors #{mirrors.map(&:to_s).join(", ")}"
747
+ mirrors
748
+ end
749
+ unless @remote
750
+ @remote = [Buildr.settings.user, Buildr.settings.build].inject([]) { |repos, hash|
751
+ repos | Array(hash['repositories'] && hash['repositories']['remote'])
752
+ }
753
+ end
754
+ @remote
755
+ end
756
+
757
+ # :call-seq:
758
+ # remote_uri => Array
759
+ #
760
+ # Returns an array of all the remote repositories as instances of URI
761
+ #
762
+ # Supports
763
+ # * String urls: "http://example.com/repo"
764
+ # * URI: URI.parse( "http://example.com/repo" )
765
+ # * Hash: { :url => "http://example.com/repo", :user => "user", :pass => "pass" }
766
+ #
767
+ def remote_uri
768
+ remote
769
+
770
+ uris = []
771
+ @remote.each do |repo|
772
+ case repo
773
+ when nil then
774
+ # ignore nil
775
+ when URI then
776
+ uris << repo
777
+ when Hash then
778
+ url = (repo[:url] || repo['url'] )
779
+ if url
780
+ uri = URI.parse(url)
781
+ if ( username = (repo[:username] || repo['username'] || repo[:user] || repo['user']) )
782
+ uri.user = username
783
+ end
784
+
785
+ if ( password = (repo[:password] || repo['password'] || repo[:pass] || repo['pass']) )
786
+ uri.password = password
787
+ end
788
+ uris << uri
789
+ else
790
+ fail( "Repository Hash format missing url: #{repo}" )
791
+ end
792
+
793
+ when String then
794
+ uris << URI.parse(repo)
795
+ else
796
+ fail( "Unsupported Repository format: #{repo}" )
797
+ end
798
+ end
799
+
800
+ uris
801
+ end
802
+
803
+ # :call-seq:
804
+ # remote = Array
805
+ # remote = url
806
+ # remote = nil
807
+ #
808
+ # With a String argument, clears the array and set it to that single URL.
809
+ #
810
+ # With an Array argument, clears the array and set it to these specific URLs.
811
+ #
812
+ # With nil, clears the array.
813
+ def remote=(urls)
814
+ case urls
815
+ when nil then @remote = nil
816
+ when Array then @remote = urls.dup
817
+ else @remote = [urls.to_s]
818
+ end
819
+ end
820
+
821
+ # :call-seq:
822
+ # release_to = url
823
+ # release_to = hash
824
+ #
825
+ # Specifies the release server. Accepts a Hash with different repository settings
826
+ # (e.g. url, username, password), or a String to only set the repository URL.
827
+ #
828
+ # Besides the URL, all other settings depend on the transport protocol in use.
829
+ #
830
+ # For example:
831
+ # repositories.release_to = { :url=>'https://example.com/var/www/repo/',
832
+ # :username='john', :password=>'secret' }
833
+ # Or in the settings.yaml file:
834
+ # repositories:
835
+ # release_to: https://john:secret@example.com/var/www/repo/
836
+ #
837
+ # repositories:
838
+ # release_to:
839
+ # url: https://example.com/var/www/repo/
840
+ # username: john
841
+ # password: secret
842
+ def release_to=(options)
843
+ options = { :url=>options } unless Hash === options
844
+ @release_to = options
845
+ end
846
+
847
+ # :call-seq:
848
+ # release_to => hash
849
+ #
850
+ # Returns the current release server setting as a Hash. This is a more convenient way to
851
+ # configure the settings, as it allows you to specify the settings progressively.
852
+ #
853
+ # For example, the Buildfile will contain the repository URL used by all developers:
854
+ # repositories.release_to[:url] ||= 'https://example.com/var/www/repo'
855
+ # Your private buildr.rb will contain your credentials:
856
+ # repositories.release_to[:username] = 'john'
857
+ # repositories.release_to[:password] = 'secret'
858
+ def release_to
859
+ unless @release_to
860
+ value = (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['release_to']) \
861
+ || (Buildr.settings.build['repositories'] && Buildr.settings.build['repositories']['release_to'])
862
+ @release_to = Hash === value ? value.inject({}) { |hash, (key, value)| hash.update(key.to_sym=>value) } : { :url=>Array(value).first }
863
+ end
864
+ @release_to
865
+ end
866
+
867
+ # :call-seq:
868
+ # snapshot_to = url
869
+ # snapshot_to = hash
870
+ #
871
+ # Specifies the release server. Accepts a Hash with different repository settings
872
+ # (e.g. url, username, password), or a String to only set the repository URL.
873
+ #
874
+ # Besides the URL, all other settings depend on the transport protocol in use.
875
+ #
876
+ # For example:
877
+ # repositories.snapshot_to = 'https://john:secret@example.com/var/www/repo/'
878
+ #
879
+ # repositories.snapshot_to = { :url=>'https://example.com/var/www/repo/',
880
+ # :username='john', :password=>'secret' }
881
+ # Or in the settings.yaml file:
882
+ # repositories:
883
+ # snapshot_to: https://john:secret@example.com/var/www/repo/
884
+ #
885
+ # repositories:
886
+ # snapshot_to:
887
+ # url: https://example.com/var/www/repo/
888
+ # username: john
889
+ # password: secret
890
+ def snapshot_to=(options)
891
+ options = { :url=>options } unless Hash === options
892
+ @snapshot_to = options
893
+ end
894
+
895
+ # :call-seq:
896
+ # snapshot_to => hash
897
+ #
898
+ # Returns the current snapshot release server setting as a Hash. This is a more convenient way to
899
+ # configure the settings, as it allows you to specify the settings progressively.
900
+ #
901
+ # For example, the Buildfile will contain the repository URL used by all developers:
902
+ # repositories.snapshot_to[:url] ||= 'https://example.com/var/www/repo'
903
+ # Your private buildr.rb will contain your credentials:
904
+ # repositories.snapshot_to[:username] = 'john'
905
+ # repositories.snapshot_to[:password] = 'secret'
906
+ def snapshot_to
907
+ unless @snapshot_to
908
+ value = (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['snapshot_to']) \
909
+ || (Buildr.settings.build['repositories'] && Buildr.settings.build['repositories']['snapshot_to'])
910
+ @snapshot_to = Hash === value ? value.inject({}) { |hash, (key, value)| hash.update(key.to_sym=>value) } : { :url=>Array(value).first }
911
+ end
912
+ @snapshot_to
913
+ end
914
+
915
+ end
916
+
917
+ # :call-seq:
918
+ # repositories => Repositories
919
+ #
920
+ # Returns an object you can use for setting the local repository path, remote repositories
921
+ # URL and release server settings.
922
+ #
923
+ # See Repositories.
924
+ def repositories
925
+ Repositories.instance
926
+ end
927
+
928
+ # :call-seq:
929
+ # artifact(spec) => Artifact
930
+ # artifact(spec) { |task| ... } => Artifact
931
+ #
932
+ # Creates a file task to download and install the specified artifact in the local repository.
933
+ #
934
+ # You can use a String or a Hash for the artifact specification. The file task will point at
935
+ # the artifact's path inside the local repository. You can then use this tasks as a prerequisite
936
+ # for other tasks.
937
+ #
938
+ # This task will download and install the artifact only once. In fact, it will download and
939
+ # install the artifact if the artifact does not already exist. You can enhance it if you have
940
+ # a different way of creating the artifact in the local repository. See Artifact for more details.
941
+ #
942
+ # For example, to specify an artifact:
943
+ # artifact('log4j:log4j:jar:1.1')
944
+ #
945
+ # To use the artifact in a task:
946
+ # compile.with artifact('log4j:log4j:jar:1.1')
947
+ #
948
+ # To specify an artifact and the means for creating it:
949
+ # download(artifact('dojo:dojo-widget:zip:2.0')=>
950
+ # 'http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip')
951
+ def artifact(spec, path = nil, &block) #:yields:task
952
+ spec = artifact_ns.fetch(spec) if spec.kind_of?(Symbol)
953
+ spec = Artifact.to_hash(spec)
954
+ unless task = Artifact.lookup(spec)
955
+ task = Artifact.define_task(path || repositories.locate(spec))
956
+ task.send :apply_spec, spec
957
+ Rake::Task['rake:artifacts'].enhance [task]
958
+ Artifact.register(task)
959
+ unless spec[:type] == :pom
960
+ Rake::Task['artifacts:sources'].enhance [task.sources_artifact]
961
+ Rake::Task['artifacts:javadoc'].enhance [task.javadoc_artifact]
962
+ Rake::Task['artifacts:annotations'].enhance [task.annotations_artifact]
963
+ end
964
+ end
965
+ task.enhance &block
966
+ end
967
+
968
+ # :call-seq:
969
+ # artifacts(*spec) => artifacts
970
+ #
971
+ # Handles multiple artifacts at a time. This method is the plural equivalent of
972
+ # #artifact, but can do more things.
973
+ #
974
+ # Returns an array of artifacts built using the supplied
975
+ # specifications, each of which can be:
976
+ # * An artifact specification (String or Hash). Returns the appropriate Artifact task.
977
+ # * An artifact of any other task. Returns the task as is.
978
+ # * A project. Returns all artifacts created (packaged) by that project.
979
+ # * A string. Returns that string, assumed to be a file name.
980
+ # * An array of artifacts or a Struct.
981
+ # * A symbol. Returns the named artifact from the current ArtifactNamespace
982
+ #
983
+ # For example, handling a collection of artifacts:
984
+ # xml = [ xerces, xalan, jaxp ]
985
+ # ws = [ axis, jax-ws, jaxb ]
986
+ # db = [ jpa, mysql, sqltools ]
987
+ # artifacts(xml, ws, db)
988
+ #
989
+ # Using artifacts created by a project:
990
+ # artifacts project('my-app') # All packages
991
+ # artifacts project('my-app').package(:war) # Only the WAR
992
+ def artifacts(*specs, &block)
993
+ specs.flatten.inject([]) do |set, spec|
994
+ case spec
995
+ when ArtifactNamespace
996
+ set |= spec.artifacts
997
+ when Symbol, Hash
998
+ set |= [artifact(spec)]
999
+ when /([^:]+:){2,4}/ # A spec as opposed to a file name.
1000
+ set |= [artifact(spec)]
1001
+ when String # Must always expand path.
1002
+ set |= [File.expand_path(spec)]
1003
+ when Project
1004
+ set |= artifacts(spec.packages)
1005
+ when Rake::Task
1006
+ set |= [spec]
1007
+ when Struct
1008
+ set |= artifacts(spec.values)
1009
+ else
1010
+ if spec.respond_to? :to_spec
1011
+ set |= artifacts(spec.to_spec)
1012
+ else
1013
+ fail "Invalid artifact specification in #{specs.inspect}"
1014
+ end
1015
+ end
1016
+ end
1017
+ end
1018
+
1019
+ def transitive(*args)
1020
+ options = Hash === args.last ? args.pop : {}
1021
+ dep_opts = {
1022
+ :scopes => options[:scopes] || [nil, "compile", "runtime"],
1023
+ :optional => options[:optional]
1024
+ }
1025
+ specs = args.flatten
1026
+ specs.inject([]) do |set, spec|
1027
+ case spec
1028
+ when /([^:]+:){2,4}/ # A spec as opposed to a file name.
1029
+ artifact = artifact(spec)
1030
+ set |= [artifact] unless artifact.type == :pom
1031
+ set |= POM.load(artifact.pom).dependencies(dep_opts).map { |spec| artifact(spec) }
1032
+ when Hash
1033
+ set |= [transitive(spec, options)]
1034
+ when String # Must always expand path.
1035
+ set |= transitive(file(File.expand_path(spec)), options)
1036
+ when Project
1037
+ set |= transitive(spec.packages, options)
1038
+ when Rake::Task
1039
+ set |= spec.respond_to?(:to_spec) ? transitive(spec.to_spec, options) : [spec]
1040
+ when Struct
1041
+ set |= transitive(spec.values, options)
1042
+ else
1043
+ fail "Invalid artifact specification in: #{specs.to_s}"
1044
+ end
1045
+ end
1046
+ end
1047
+
1048
+ # :call-seq:
1049
+ # group(ids, :under=>group_name, :version=>number) => artifacts
1050
+ #
1051
+ # Convenience method for defining multiple artifacts that belong to the same group, type and version.
1052
+ # Accepts multiple artifact identifiers followed by two or three hash values:
1053
+ # * :under -- The group identifier
1054
+ # * :version -- The version number
1055
+ # * :type -- The artifact type (optional)
1056
+ # * :classifier -- The artifact classifier (optional)
1057
+ #
1058
+ # For example:
1059
+ # group 'xbean', 'xbean_xpath', 'xmlpublic', :under=>'xmlbeans', :version=>'2.1.0'
1060
+ # Or:
1061
+ # group %w{xbean xbean_xpath xmlpublic}, :under=>'xmlbeans', :version=>'2.1.0'
1062
+ def group(*args)
1063
+ hash = args.pop
1064
+ args.flatten.map do |id|
1065
+ artifact :group => hash[:under],
1066
+ :type => hash[:type],
1067
+ :version => hash[:version],
1068
+ :classifier => hash[:classifier],
1069
+ :id => id
1070
+ end
1071
+ end
1072
+
1073
+ # :call-seq:
1074
+ # install(artifacts) => install_task
1075
+ #
1076
+ # Installs the specified artifacts in the local repository as part of the install task.
1077
+ #
1078
+ # You can use this to install various files in the local repository, for example:
1079
+ # install artifact('group:id:jar:1.0').from('some_jar.jar')
1080
+ # $ buildr install
1081
+ def install(*args, &block)
1082
+ artifacts = artifacts(args).uniq
1083
+ raise ArgumentError, 'This method can only install artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
1084
+ task('install').tap do |install|
1085
+ install.enhance(artifacts) do
1086
+ artifacts.each(&:install)
1087
+ end
1088
+ install.enhance &block if block
1089
+ task('uninstall') do
1090
+ artifacts.map(&:to_s ).each { |file| rm file if File.exist?(file) }
1091
+ end
1092
+ end
1093
+ end
1094
+
1095
+ # :call-seq:
1096
+ # upload(artifacts)
1097
+ #
1098
+ # Uploads the specified artifacts to the release server as part of the upload task.
1099
+ #
1100
+ # You can use this to upload various files to the release server, for example:
1101
+ # upload artifact('group:id:jar:1.0').from('some_jar.jar')
1102
+ # $ buildr upload
1103
+ def upload(*args, &block)
1104
+ artifacts = artifacts(args)
1105
+ raise ArgumentError, 'This method can only upload artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
1106
+ upload_artifacts_tasks = artifacts.map { |artifact| artifact.upload_task }
1107
+ task('upload').tap do |task|
1108
+ task.enhance &block if block
1109
+ task.enhance upload_artifacts_tasks
1110
+ end
1111
+ end
1112
+
1113
+ end