Buildr C0 Coverage Information - RCov

lib/buildr/packaging/archive.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/buildr/packaging/archive.rb 535 312
14.02%
18.59%

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 module Buildr
18 
19   # Base class for ZipTask, TarTask and other archives.
20   class ArchiveTask < Rake::FileTask
21 
22     # Which files go where. All the rules for including, excluding and merging files
23     # are handled by this object.
24     class Path #:nodoc:
25 
26       # Returns the archive from this path.
27       attr_reader :root
28 
29       def initialize(root, path)
30         @root = root
31         @path = path.empty? ? path : "#{path}/"
32         @includes = FileList[]
33         @excludes = []
34         # Expand source files added to this path.
35         expand_src = proc { @includes.map{ |file| file.to_s }.uniq }
36         @sources = [ expand_src ]
37         # Add files and directories added to this path.
38         @actions = [] << proc do |file_map|
39           expand_src.call.each do |path|
40             unless excluded?(path)
41               if File.directory?(path)
42                 in_directory path do |file, rel_path|
43                   dest = "#{@path}#{rel_path}"
44                   unless excluded?(dest)
45                     trace "Adding #{dest}"
46                     file_map[dest] = file
47                   end
48                 end
49               end
50               unless File.basename(path) == "."
51                 trace "Adding #{@path}#{File.basename(path)}"
52                 file_map["#{@path}#{File.basename(path)}"] = path
53               end
54             end
55           end
56         end
57       end
58 
59       # :call-seq:
60       #   include(*files) => self
61       #   include(*files, :path=>path) => self
62       #   include(file, :as=>name) => self
63       #   include(:from=>path) => self
64       #   include(*files, :merge=>true) => self
65       def include(*args)
66         options = args.pop if Hash === args.last
67         files = to_artifacts(args)
68         raise 'AchiveTask.include() values should not include nil' if files.include? nil
69 
70         if options.nil? || options.empty?
71           @includes.include *files.flatten
72         elsif options[:path]
73           sans_path = options.reject { |k,v| k == :path }
74           path(options[:path]).include *files + [sans_path]
75         elsif options[:as]
76           raise 'You can only use the :as option in combination with the :path option' unless options.size == 1
77           raise 'You can only use one file with the :as option' unless files.size == 1
78           include_as files.first.to_s, options[:as]
79         elsif options[:from]
80           raise 'You can only use the :from option in combination with the :path option' unless options.size == 1
81           raise 'You cannot use the :from option with file names' unless files.empty?
82           fail 'AchiveTask.include() :from value should not be nil' if [options[:from]].flatten.include? nil
83           [options[:from]].flatten.each { |path| include_as path.to_s, '.' }
84         elsif options[:merge]
85           raise 'You can only use the :merge option in combination with the :path option' unless options.size == 1
86           files.each { |file| merge file }
87         else
88           raise "Unrecognized option #{options.keys.join(', ')}"
89         end
90         self
91       end
92       alias :add :include
93       alias :<< :include
94 
95       # :call-seq:
96       #   exclude(*files) => self
97       def exclude(*files)
98         files = to_artifacts(files)
99         @excludes |= files
100         @excludes |= files.reject { |f| f =~ /\*$/ }.map { |f| "#{f}/*" }
101         self
102       end
103 
104       # :call-seq:
105       #   merge(*files) => Merge
106       #   merge(*files, :path=>name) => Merge
107       def merge(*args)
108         options = Hash === args.last ? args.pop : {}
109         files = to_artifacts(args)
110         rake_check_options options, :path
111         raise ArgumentError, "Expected at least one file to merge" if files.empty?
112         path = options[:path] || @path
113         expanders = files.collect do |file|
114           @sources << proc { file.to_s }
115           expander = ZipExpander.new(file)
116           @actions << proc do |file_map|
117             file.invoke() if file.is_a?(Rake::Task)
118             expander.expand(file_map, path)
119           end
120           expander
121         end
122         Merge.new(expanders)
123       end
124 
125       # Returns a Path relative to this one.
126       def path(path)
127         return self if path.nil?
128         return root.path(path[1..-1]) if path[0] == ?/
129         root.path("#{@path}#{path}")
130       end
131 
132       # Returns all the source files.
133       def sources #:nodoc:
134         @sources.map{ |source| source.call }.flatten
135       end
136 
137       def add_files(file_map) #:nodoc:
138         @actions.each { |action| action.call(file_map) }
139       end
140 
141       # :call-seq:
142       #   exist => boolean
143       #
144       # Returns true if this path exists. This only works if the path has any entries in it,
145       # so exist on path happens to be the opposite of empty.
146       def exist?
147         !entries.empty?
148       end
149 
150       # :call-seq:
151       #   empty? => boolean
152       #
153       # Returns true if this path is empty (has no other entries inside).
154       def empty?
155         entries.all? { |entry| entry.empty? }
156       end
157 
158       # :call-seq:
159       #   contain(file*) => boolean
160       #
161       # Returns true if this ZIP file path contains all the specified files. You can use relative
162       # file names and glob patterns (using *, **, etc).
163       def contain?(*files)
164         files.all? { |file| entries.detect { |entry| File.fnmatch(file, entry.to_s) } }
165       end
166 
167       # :call-seq:
168       #   entry(name) => ZipEntry
169       #
170       # Returns a ZIP file entry. You can use this to check if the entry exists and its contents,
171       # for example:
172       #   package(:jar).path("META-INF").entry("LICENSE").should contain(/Apache Software License/)
173       def entry(name)
174         root.entry("#{@path}#{name}")
175       end
176 
177       def to_s
178         @path
179       end
180 
181     protected
182 
183     # Convert objects to artifacts, where applicable
184     def to_artifacts(files)
185       files.flatten.inject([]) do |set, file|
186         case file
187         when ArtifactNamespace
188           set |= file.artifacts
189         when Symbol, Hash
190           set |= [artifact(file)]
191         when /([^:]+:){2,4}/ # A spec as opposed to a file name.
192           set |= [Buildr.artifact(file)]
193         when Project
194           set |= Buildr.artifacts(file.packages)
195         when Rake::Task
196           set |= [file]
197         when Struct
198           set |= Buildr.artifacts(file.values)
199         else
200           # non-artifacts passed as-is; in particular, String paths are
201           # unmodified since Rake FileTasks don't use absolute paths
202           set |= [file]
203         end
204       end
205     end
206 
207     def include_as(source, as)
208         @sources << proc { source }
209         @actions << proc do |file_map|
210           file = source.to_s
211           unless excluded?(file)
212             if File.directory?(file)
213               in_directory file do |file, rel_path|
214                 path = rel_path.split('/')[1..-1]
215                 path.unshift as unless as == '.'
216                 dest = "#{@path}#{path.join('/')}"
217                 unless excluded?(dest)
218                   trace "Adding #{dest}"
219                   file_map[dest] = file
220                 end
221               end
222               unless as == "."
223                 trace "Adding #{@path}#{as}/"
224                 file_map["#{@path}#{as}/"] = nil # :as is a folder, so the trailing / is required.
225               end
226             else
227               file_map["#{@path}#{as}"] = file
228             end
229 
230           end
231         end
232       end
233 
234       def in_directory(dir)
235         prefix = Regexp.new('^' + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
236         Util.recursive_with_dot_files(dir).reject { |file| excluded?(file) }.
237           each { |file| yield file, file.sub(prefix, '') }
238       end
239 
240       def excluded?(file)
241         @excludes.any? { |exclude| File.fnmatch(exclude, file) }
242       end
243 
244       def entries #:nodoc:
245         return root.entries unless @path
246         @entries ||= root.entries.inject([]) { |selected, entry|
247           selected << entry.name.sub(@path, "") if entry.name.index(@path) == 0
248           selected
249         }
250       end
251 
252     end
253 
254 
255     class Merge
256       def initialize(expanders)
257         @expanders = expanders
258       end
259 
260       def include(*files)
261         @expanders.each { |expander| expander.include(*files) }
262         self
263       end
264       alias :<< :include
265 
266       def exclude(*files)
267         @expanders.each { |expander| expander.exclude(*files) }
268         self
269       end
270     end
271 
272 
273     # Extend one Zip file into another.
274     class ZipExpander #:nodoc:
275 
276       def initialize(zip_file)
277         @zip_file = zip_file.to_s
278         @includes = []
279         @excludes = []
280       end
281 
282       def include(*files)
283         @includes |= files
284         self
285       end
286       alias :<< :include
287 
288       def exclude(*files)
289         @excludes |= files
290         self
291       end
292 
293       def expand(file_map, path)
294         @includes = ['*'] if @includes.empty?
295         Zip::ZipFile.open(@zip_file) do |source|
296           source.entries.reject { |entry| entry.directory? }.each do |entry|
297             if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
298                !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
299               dest = path =~ /^\/?$/ ? entry.name : Util.relative_path(path + "/" + entry.name)
300               trace "Adding #{dest}"
301               file_map[dest] = lambda { |output| output.write source.read(entry) }
302             end
303           end
304         end
305       end
306 
307     end
308 
309 
310     def initialize(*args) #:nodoc:
311       super
312       clean
313 
314       # Make sure we're the last enhancements, so other enhancements can add content.
315       enhance do
316         @file_map = {}
317         enhance do
318           send 'create' if respond_to?(:create)
319           # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
320           # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
321           # We also want to protect against partial updates.
322           rm name rescue nil
323           mkpath File.dirname(name)
324           begin
325             @paths.each do |name, object|
326               @file_map[name] = nil unless name.empty?
327               object.add_files(@file_map)
328             end
329             create_from @file_map
330           rescue
331             rm name rescue nil
332             raise
333           end
334         end
335       end
336     end
337 
338     # :call-seq:
339     #   clean => self
340     #
341     # Removes all previously added content from this archive.
342     # Use this method if you want to remove default content from a package.
343     # For example, package(:jar) by default includes compiled classes and resources,
344     # using this method, you can create an empty jar and afterwards add the
345     # desired content to it.
346     #
347     #    package(:jar).clean.include path_to('desired/content')
348     def clean
349       @paths = { '' => Path.new(self, '') }
350       @prepares = []
351       self
352     end
353 
354     # :call-seq:
355     #   include(*files) => self
356     #   include(*files, :path=>path) => self
357     #   include(file, :as=>name) => self
358     #   include(:from=>path) => self
359     #   include(*files, :merge=>true) => self
360     #
361     # Include files in this archive, or when called on a path, within that path. Returns self.
362     #
363     # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
364     # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
365     #   zip(..).include('foo', 'bar', 'baz/*')
366     #
367     # The second form is similar but adds files/directories under the specified path. For example,
368     # to add foo as bar/foo:
369     #   zip(..).include('foo', :path=>'bar')
370     # The :path option is the same as using the path method:
371     #   zip(..).path('bar').include('foo')
372     # All other options can be used in combination with the :path option.
373     #
374     # The third form adds a file or directory under a different name. For example, to add the file foo under the
375     # name bar:
376     #   zip(..).include('foo', :as=>'bar')
377     #
378     # The fourth form adds the contents of a directory using the directory as a prerequisite:
379     #   zip(..).include(:from=>'foo')
380     # Unlike <code>include('foo')</code> it includes the contents of the directory, not the directory itself.
381     # Unlike <code>include('foo/*')</code>, it uses the directory timestamp for dependency management.
382     #
383     # The fifth form includes the contents of another archive by expanding it into this archive. For example:
384     #   zip(..).include('foo.zip', :merge=>true).include('bar.zip')
385     # You can also use the method #merge.
386     def include(*files)
387       fail "AchiveTask.include() called with nil values" if files.include? nil
388       @paths[''].include *files if files.compact.size > 0
389       self
390     end
391     alias :add :include
392     alias :<< :include
393 
394     # :call-seq:
395     #   exclude(*files) => self
396     #
397     # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
398     def exclude(*files)
399       @paths[''].exclude *files
400       self
401     end
402 
403     # :call-seq:
404     #   merge(*files) => Merge
405     #   merge(*files, :path=>name) => Merge
406     #
407     # Merges another archive into this one by including the individual files from the merged archive.
408     #
409     # Returns an object that supports two methods: include and exclude. You can use these methods to merge
410     # only specific files. For example:
411     #   zip(..).merge('src.zip').include('module1/*')
412     def merge(*files)
413       @paths[''].merge *files
414     end
415 
416     # :call-seq:
417     #   path(name) => Path
418     #
419     # Returns a path object. Use the path object to include files under a path, for example, to include
420     # the file 'foo' as 'bar/foo':
421     #   zip(..).path('bar').include('foo')
422     #
423     # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
424     # and so forth. It also implements path and root, so that:
425     #   path('foo').path('bar') == path('foo/bar')
426     #   path('foo').root == root
427     def path(name)
428       return @paths[''] if name.nil?
429       normalized = name.split('/').inject([]) do |path, part|
430         case part
431         when '.', nil, ''
432           path
433         when '..'
434           path[0...-1]
435         else
436           path << part
437         end
438       end.join('/')
439       @paths[normalized] ||= Path.new(self, normalized)
440     end
441 
442     # :call-seq:
443     #   root => ArchiveTask
444     #
445     # Call this on an archive to return itself, and on a path to return the archive.
446     def root
447       self
448     end
449 
450     # :call-seq:
451     #   with(options) => self
452     #
453     # Passes options to the task and returns self. Some tasks support additional options, for example,
454     # the WarTask supports options like :manifest, :libs and :classes.
455     #
456     # For example:
457     #   package(:jar).with(:manifest=>'MANIFEST_MF')
458     def with(options)
459       options.each do |key, value|
460         begin
461           send "#{key}=", value
462         rescue NoMethodError
463           raise ArgumentError, "#{self.class.name} does not support the option #{key}"
464         end
465       end
466       self
467     end
468 
469     def invoke_prerequisites(args, chain) #:nodoc:
470       @prepares.each { |prepare| prepare.call(self) }
471       @prepares.clear
472 
473       file_map = {}
474       @paths.each do |name, path|
475         path.add_files(file_map)
476       end
477 
478       # filter out Procs (dynamic content), nils and others
479       @prerequisites |= file_map.values.select { |src| src.is_a?(String) || src.is_a?(Rake::Task) }
480 
481       super
482     end
483 
484     def needed? #:nodoc:
485       return true unless File.exist?(name)
486       # You can do something like:
487       #   include('foo', :path=>'foo').exclude('foo/bar', path=>'foo').
488       #     include('foo/bar', :path=>'foo/bar')
489       # This will play havoc if we handled all the prerequisites together
490       # under the task, so instead we handle them individually for each path.
491       #
492       # We need to check that any file we include is not newer than the
493       # contents of the Zip. The file itself but also the directory it's
494       # coming from, since some tasks touch the directory, e.g. when the
495       # content of target/classes is included into a WAR.
496       most_recent = @paths.collect { |name, path| path.sources }.flatten.
497         select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
498       File.stat(name).mtime < (most_recent || Rake::EARLY) || super
499     end
500 
501     # :call-seq:
502     #   empty? => boolean
503     #
504     # Returns true if this ZIP file is empty (has no other entries inside).
505     def empty?
506       path("").empty
507     end
508 
509     # :call-seq:
510     #   contain(file*) => boolean
511     #
512     # Returns true if this ZIP file contains all the specified files. You can use absolute
513     # file names and glob patterns (using *, **, etc).
514     def contain?(*files)
515       path("").contain?(*files)
516     end
517 
518   protected
519 
520     # Adds a prepare block. These blocks are called early on for adding more content to
521     # the archive, before invoking prerequsities. Anything you add here will be invoked
522     # as a prerequisite and used to determine whether or not to generate this archive.
523     # In contrast, enhance blocks are evaluated after it was decided to create this archive.
524     def prepare(&block)
525       @prepares << block
526     end
527 
528     def []=(key, value) #:nodoc:
529       raise ArgumentError, "This task does not support the option #{key}."
530     end
531 
532   end
533 
534 
535 end

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