| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| lib/buildr/packaging/archive.rb | 535 | 312 | 14.02%
|
18.59%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 # Licensed to the Apache Software Foundation (ASF) under one or more |
2 # contributor license agreements. See the NOTICE file distributed with this |
3 # work for additional information regarding copyright ownership. The ASF |
4 # licenses this file to you under the Apache License, Version 2.0 (the |
5 # "License"); you may not use this file except in compliance with the License. |
6 # You may obtain a copy of the License at |
7 # |
8 # http://www.apache.org/licenses/LICENSE-2.0 |
9 # |
10 # Unless required by applicable law or agreed to in writing, software |
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
13 # License for the specific language governing permissions and limitations under |
14 # the License. |
15 |
16 |
17 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