Buildr C0 Coverage Information - RCov

lib/buildr/packaging/artifact.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/buildr/packaging/artifact.rb 905 463
26.74%
38.88%

Key

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.

Coverage Details

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

Generated on 2011-07-06 23:35:38 -0700 with rcov 0.9.8