lib/tasks/zip.rb in buildr-1.2.2 vs lib/tasks/zip.rb in buildr-1.2.3
- old
+ new
@@ -2,304 +2,307 @@
require "zip/zipfilesystem"
module Buildr
- # The ZipTask creates a new ZIP file. You can include any number of files and
- # and directories, use exclusion patterns, and include files into specific
- # directories.
- #
- # For example:
- # zip("test.zip").tap do |task|
- # task.include "srcs"
- # task.include "README", "LICENSE"
- # end
- #
- # See Buildr#zip.
- class ZipTask < Rake::FileTask
+ # Base class for ZipTask, TarTask and other archives.
+ class ArchiveTask < Rake::FileTask
# Which files go where. All the rules for including, excluding and merging files
- # are handled by this object. A zip has at least one path.
+ # are handled by this object.
class Path #:nodoc:
- attr_reader :actions
+ # Returns the archive from this path.
+ attr_reader :root
- def initialize(zip, path)
- @zip = zip
+ def initialize(root, path)
+ @root = root
@path = "#{path}/" if path
- expand_src = proc { (@files || []).map(&:to_s).uniq }
+ @files = FileList[]
+ # Expand source files added to this path.
+ expand_src = proc { @files.map{ |file| file.to_s }.uniq }
@sources = [ expand_src ]
- @actions = [] << proc do |zip|
+ # Add files and directories added to this path.
+ @actions = [] << proc do |file_map|
+ file_map[@path] = nil if @path
expand_src.call.each do |path|
if File.directory?(path)
in_directory(path, @files) do |file, rel_path|
- puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
- zip.add("#{@path}#{rel_path}", file) { true }
+ dest = "#{@path}#{rel_path}"
+ puts "Adding #{dest}" if Rake.application.options.trace
+ file_map[dest] = file
end
else
puts "Adding #{@path}#{File.basename(path)}" if Rake.application.options.trace
- zip.add("#{@path}#{File.basename(path)}", path) { true }
+ file_map["#{@path}#{File.basename(path)}"] = path
end
end
end
end
- # Documented in ZipTask.
- def include(*files)
- if Hash === files.last
- options = files.pop
- else
- options = {}
- end
- files = files.flatten
+ # :call-seq:
+ # include(*files) => self
+ # include(*files, :path=>path) => self
+ # include(file, :as=>name) => self
+ # include(:from=>path) => self
+ # include(*files, :merge=>true) => self
+ def include(*args)
+ options = args.pop if Hash === args.last
+ files = args.flatten
- if options[:path]
- path(options[:path]).include *files +[ options.reject { |k,v| k == :path } ]
+ if options.nil? || options.empty?
+ @files.include *files.map { |file| file.to_s }
+ elsif options[:path]
+ sans_path = options.reject { |k,v| k == :path }
+ path(options[:path]).include *files + [sans_path]
elsif options[:as]
- raise "You can only use the :as option in combination with the :path option" unless options.keys.size == 1
+ raise "You can only use the :as option in combination with the :path option" unless options.size == 1
raise "You can only use one file with the :as option" unless files.size == 1
- include_as(files.first.to_s, options[:as])
+ include_as files.first.to_s, options[:as]
elsif options[:from]
- raise "You can only use the :from option in combination with the :path option" unless options.keys.size == 1
+ raise "You can only use the :from option in combination with the :path option" unless options.size == 1
raise "You canont use the :from option with file names" unless files.empty?
- [options[:from]].flatten.each { |path| include_as(path.to_s, ".") }
+ [options[:from]].flatten.each { |path| include_as path.to_s, "." }
elsif options[:merge]
- raise "You can only use the :merge option in combination with the :path option" unless options.keys.size == 1
+ raise "You can only use the :merge option in combination with the :path option" unless options.size == 1
files.each { |file| merge file }
- elsif options.keys.empty?
- (@files ||= FileList[]).include files.map(&:to_s)
else
raise "Unrecognized option #{options.keys.join(", ")}"
end
self
end
alias :add :include
- # Documented in ZipTask.
+ # :call-seq:
+ # exclude(*files) => self
def exclude(*files)
- (@files ||= FileList[]).exclude *files
+ @files.exclude *files
self
end
- # Documented in ZipTask.
- def merge(*files)
- options = files.pop if Hash === files.last
+ # :call-seq:
+ # merge(*files) => Merge
+ # merge(*files, :path=>name) => Merge
+ def merge(*args)
+ options = args.pop if Hash === args.last
+ files = args.flatten
- if options && options[:path]
- path(options[:path]).merge *files +[ options.reject { |k,v| k == :path } ]
- elsif options.nil? || options.keys.empty?
+ if options.nil? || options.empty?
files.collect do |file|
@sources << proc { file.to_s }
expander = ZipExpander.new(file)
- @actions << proc { |zip| expander.expand(zip, @path) }
+ @actions << proc { |file_map| expander.expand(file_map, @path) }
expander
end.first
+ elsif options[:path]
+ sans_path = options.reject { |k,v| k == :path }
+ path(options[:path]).merge *files + [sans_path]
+ self
else
raise "Unrecognized option #{options.keys.join(", ")}"
end
end
- # Documented in ZipTask.
+ # Returns a Path relative to this one.
def path(path)
- path.blank? ? self : @zip.path("#{@path}#{path}")
+ path.blank? ? self : @root.path("#{@path}#{path}")
end
- # Documented in ZipTask.
- def root()
- @zip
+ # Returns all the source files.
+ def sources() #:nodoc:
+ @sources.map{ |source| source.call }.flatten
end
- # Returns all the source files.
- def sources()
- @sources.map(&:call).flatten
+ def add_files(file_map) #:nodoc:
+ @actions.each { |action| action.call(file_map) }
end
def to_s()
@path || ""
end
protected
def include_as(source, as)
@sources << proc { source }
- @actions << proc do |zip|
+ @actions << proc do |file_map|
file = source.to_s
+ file_map[@path] = nil if @path
if File.directory?(file)
in_directory(file) do |file, rel_path|
- if as == "."
- dest = (@path || "") + rel_path.split("/")[1..-1].join("/")
- else
- dest = "#{@path}#{as}#{rel_path}"
- end
+ dest = as == "." ? (@path || "") + rel_path.split("/")[1..-1].join("/") : "#{@path}#{as}#{rel_path}"
puts "Adding #{dest}" if Rake.application.options.trace
- zip.add(dest, file) { true }
+ file_map[dest] = file
end
else
puts "Adding #{@path}#{as}" if Rake.application.options.trace
- zip.add("#{@path}#{as}", file) { true }
+ file_map["#{@path}#{as}"] = file
end
end
end
def in_directory(dir, excludes = nil)
prefix = Regexp.new("^" + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
- Dir["#{dir}/**/*"].
+ FileList["#{dir}/**/*"].
reject { |file| File.directory?(file) || (excludes && excludes.exclude?(file)) }.
each { |file| yield file, file.sub(prefix, "") }
end
end
+
# Extend one Zip file into another.
class ZipExpander #:nodoc:
def initialize(zip_file)
@zip_file = zip_file.to_s
+ @includes = []
+ @excludes = []
end
def include(*files)
- (@includes ||= [])
@includes |= files
self
end
def exclude(*files)
- (@excludes ||= [])
@excludes |= files
self
end
- def expand(zip, path)
- @includes ||= ["*"]
- @excludes ||= []
+ def expand(file_map, path)
+ @includes = ["*"] if @includes.empty?
Zip::ZipFile.open(@zip_file) do |source|
source.entries.reject { |entry| entry.directory? }.each do |entry|
if @includes.any? { |pattern| File.fnmatch(pattern, entry.name) } &&
!@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
- puts "Adding #{path}#{entry.name}" if Rake.application.options.trace
- zip.get_output_stream("#{path}#{entry.name}") { |output| output.write source.read(entry) }
+ dest = "#{path}#{entry.name}"
+ puts "Adding #{dest}" if Rake.application.options.trace
+ file_map[dest] = lambda { |output| output.write source.read(entry) }
end
end
end
end
end
+
def initialize(*args) #:nodoc:
super
@paths = { nil=>Path.new(self, nil) }
- enhance do |task|
- puts "Creating #{task.name}" if verbose
- # We're here because the Zip file does not exist, or one of the files is
- # newer than the Zip contents; in the later case, opening the Zip file
- # will add to its contents instead of replacing it, so we want the Zip
- # gone before we change it. We also don't want to see any partial updates.
- rm task.name, :verbose=>false rescue nil
- mkpath File.dirname(task.name), :verbose=>false
- begin
- Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) do |zip|
- zip.restore_permissions = true
- create zip
+ @prepares = []
+
+ # Make sure we're the last enhancements, so other enhancements can add content.
+ enhance do
+ @file_map = {}
+ enhance do
+ send "create" if respond_to?(:create)
+ # We're here because the archive file does not exist, or one of the files is newer than the archive contents;
+ # we need to make sure the archive doesn't exist (e.g. opening an existing Zip will add instead of create).
+ # We also want to protect against partial updates.
+ rm name, :verbose=>false rescue nil
+ mkpath File.dirname(name), :verbose=>false
+ begin
+ @paths.each { |name, object| object.add_files(@file_map) }
+ create_from @file_map
+ rescue
+ rm name, :verbose=>false rescue nil
+ raise
end
- rescue
- rm task.name, :verbose=>false rescue nil
- raise
end
end
end
# :call-seq:
# include(*files) => self
# include(*files, :path=>path) => self
# include(file, :as=>name) => self
- # include(*zips, :merge=>true) => self
+ # include(:from=>path) => self
+ # include(*files, :merge=>true) => self
#
- # Include files in the ZIP (or current path) and returns self.
+ # Include files in this archive, or when called on a path, within that path. Returns self.
#
- # This method accepts three options. You can use :path to include files under
- # a specific path, for example:
+ # The first form accepts a list of files, directories and glob patterns and adds them to the archive.
+ # For example, to include the file foo, directory bar (including all files in there) and all files under baz:
+ # zip(..).include("foo", "bar", "baz/*")
+ #
+ # The second form is similar but adds files/directories under the specified path. For example,
+ # to add foo as bar/foo:
# zip(..).include("foo", :path=>"bar")
- # includes the file bar as bar/foo. See also #path.
+ # The :path option is the same as using the path method:
+ # zip(..).path("bar").include("foo")
+ # All other options can be used in combination with the :path option.
#
- # You can use :as to include a file under a different name, for example:
+ # The third form adds a file or directory under a different name. For example, to add the file foo under the
+ # name bar:
# zip(..).include("foo", :as=>"bar")
- # You can use the :as option in combination with the :path option, but only with
- # a single file at a time.
#
- # As a special case, you can include the entire contents of a directory by including
- # the directory and using :as=>".". This:
- # zip(..).include("srcs", :as=>".")
- # will include all the source files, using the directory as a prerequisite. This:
- # zip(..).include("srcs/*")
- # includes all the same source files, using the source files as a prerequisite.
+ # The fourth form adds the contents of a directory using the directory as a prerequisite:
+ # zip(..).include(:from=>"foo")
+ # Unlike <code>include("foo")</code> it includes the contents of the directory, not the directory itself.
+ # Unlike <code>include("foo/*")</code>, it uses the directory timestamp for dependency management.
#
- # You can use :merge option to include the contents of another ZIP file, for example:
- # zip(..).include("foo.zip", :merge=>true)
- # You can use the :merge option in combination with the :path option. See also #merge.
+ # The fifth form includes the contents of another archive by expanding it into this archive. For example:
+ # zip(..).include("foo.zip", :merge=>true).include("bar.zip")
+ # You can also use the method #merge.
def include(*files)
@paths[nil].include *files
self
end
alias :add :include
# :call-seq:
# exclude(*files) => self
#
- # Excludes files and returns self. Can be used in combination with include to
- # prevent some files from being included.
+ # Excludes files and returns self. Can be used in combination with include to prevent some files from being included.
def exclude(*files)
@paths[nil].exclude *files
self
end
# :call-seq:
# merge(*files) => Merge
# merge(*files, :path=>name) => Merge
#
- # Merges ZIP files and returns a merge object. The contents of the merged ZIP file is
- # extracted into this ZIP file (or current path).
+ # Merges another archive into this one by including the individual files from the merged archive.
#
- # The returned object supports two methods: include and exclude. You can use these to
- # merge only specific files from the ZIP. For example:
+ # Returns an object that supports two methods: include and exclude. You can use these methods to merge
+ # only specific files. For example:
# zip(..).merge("src.zip").include("module1/*")
- #
- # This differs from include with the :merge option, which returns self.
def merge(*files)
@paths[nil].merge *files
end
# :call-seq:
# path(name) => Path
#
- # Returns a path object. You can use the path object to include files in a given
- # path inside the ZIP file. The path object implements the include, exclude, merge,
- # path and root methods.
- #
- # For example:
+ # Returns a path object. Use the path object to include files under a path, for example, to include
+ # the file "foo" as "bar/foo":
# zip(..).path("bar").include("foo")
- # Will add the file foo under the name bar/foo.
#
- # As a shorthand, you can also use the :path option:
- # zip(..).include("foo", :path=>"bar")
+ # Returns a Path object. The Path object implements all the same methods, like include, exclude, merge
+ # and so forth. It also implements path and root, so that:
+ # path("foo").path("bar") == path("foo/bar")
+ # path("foo").root == root
def path(name)
- name.blank? ? @paths[nil] : (@paths[name] ||= Path.new(self, name))
+ return @paths[nil] if name.blank?
+ @paths[name] ||= Path.new(self, name)
end
# :call-seq:
- # root() => ZipTask
+ # root() => ArchiveTask
#
- # Returns the root path, essentially the ZipTask object itself. In case you are wondering
- # down paths and want to go back.
+ # Call this on an archive to return itself, and on a path to return the archive.
def root()
self
end
# :call-seq:
# with(options) => self
#
- # Pass options to the task. Returns self. ZipTask itself does not support any options,
- # but other tasks (e.g. JarTask, WarTask) do.
+ # Passes options to the task and returns self. Some tasks support additional options, for example,
+ # the WarTask supports options like :manifest, :libs and :classes.
#
# For example:
# package(:jar).with(:manifest=>"MANIFEST_MF")
def with(options)
options.each do |key, value|
@@ -316,11 +319,13 @@
end
self
end
def invoke_prerequisites() #:nodoc:
- prerequisites.concat @paths.collect { |name, path| path.sources }.flatten
+ @prepares.each { |prepare| prepare.call(self) }
+ @prepares.clear
+ @prerequisites |= @paths.collect { |name, path| path.sources }.flatten
super
end
def needed?() #:nodoc:
return true unless File.exist?(name)
@@ -329,36 +334,68 @@
# include("foo/bar", :path=>"foo/bar")
# This will play havoc if we handled all the prerequisites together
# under the task, so instead we handle them individually for each path.
#
# We need to check that any file we include is not newer than the
- # contents of the ZIP. The file itself but also the directory it's
+ # contents of the Zip. The file itself but also the directory it's
# coming from, since some tasks touch the directory, e.g. when the
# content of target/classes is included into a WAR.
most_recent = @paths.collect { |name, path| path.sources }.flatten.
each { |src| File.directory?(src) ? FileList["#{src}/**/*"] | [src] : src }.flatten.
select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
File.stat(name).mtime < (most_recent || Rake::EARLY) || super
end
protected
- # Sub-classes override this method to perform additional creation tasks,
- # e.g. creating a manifest file in a JAR.
- def create(zip)
- @paths.each { |name, obj| obj.actions.each { |action| action[zip] } }
+ # Adds a prepare block. These blocks are called early on for adding more content to
+ # the archive, before invoking prerequsities. Anything you add here will be invoked
+ # as a prerequisite and used to determine whether or not to generate this archive.
+ # In contrast, enhance blocks are evaluated after it was decided to create this archive.
+ def prepare(&block)
+ @prepares << block
end
def []=(key, value) #:nodoc:
raise ArgumentError, "This task does not support the option #{key}."
end
end
+
+
+ # The ZipTask creates a new Zip file. You can include any number of files and and directories,
+ # use exclusion patterns, and include files into specific directories.
+ #
+ # For example:
+ # zip("test.zip").tap do |task|
+ # task.include "srcs"
+ # task.include "README", "LICENSE"
+ # end
+ #
+ # See Buildr#zip and ArchiveTask.
+ class ZipTask < ArchiveTask
+
+ private
+
+ def create_from(file_map)
+ Zip::ZipFile.open(name, Zip::ZipFile::CREATE) do |zip|
+ zip.restore_permissions = true
+ file_map.each do |path, content|
+ zip.mkdir path unless content || zip.find_entry(path)
+ zip.add path, content if String === content
+ zip.get_output_stream(path) { |output| content.call(output) } if content.respond_to?(:call)
+ end
+ end
+ end
+
+ end
+
+
# :call-seq:
# zip(file) => ZipTask
#
- # The ZipTask creates a new ZIP file. You can include any number of files and
+ # The ZipTask creates a new Zip file. You can include any number of files and
# and directories, use exclusion patterns, and include files into specific
# directories.
#
# For example:
# zip("test.zip").tap do |task|