| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with this |
| # work for additional information regarding copyright ownership. The ASF |
| # licenses this file to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| |
| module Buildr #:nodoc: |
| |
| # 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 |
| |
| # Compression level for this Zip. |
| attr_accessor :compression_level |
| |
| def initialize(*args) #:nodoc: |
| self.compression_level = Zlib::DEFAULT_COMPRESSION |
| super |
| end |
| |
| # :call-seq: |
| # entry(name) => Entry |
| # |
| # Returns a ZIP file entry. You can use this to check if the entry exists and its contents, |
| # for example: |
| # package(:jar).entry("META-INF/LICENSE").should contain(/Apache Software License/) |
| def entry(entry_name) |
| ::Zip::Entry.new(name, entry_name) |
| end |
| |
| def entries #:nodoc: |
| @entries ||= Zip::File.open(name) { |zip| zip.entries } |
| end |
| |
| private |
| |
| def create_from(file_map, transform_map) |
| Zip::OutputStream.open name do |zip| |
| seen = {} |
| mkpath = lambda do |dir| |
| dirname = (dir[-1..-1] =~ /\/$/) ? dir : dir + '/' |
| unless dir == '.' || seen[dirname] |
| mkpath.call File.dirname(dirname) |
| zip.put_next_entry(dirname, compression_level) |
| seen[dirname] = true |
| end |
| end |
| |
| paths = file_map.keys.sort |
| paths.each do |path| |
| contents = file_map[path] |
| warn "Warning: Path in zipfile #{name} contains backslash: #{path}" if path =~ /\\/ |
| |
| # Must ensure that the directory entry is created for intermediate paths, otherwise |
| # zips can be created without entries for directories which can break some tools |
| mkpath.call File.dirname(path) |
| |
| entry_created = false |
| to_transform = [] |
| transform = transform_map.key?(path) |
| [contents].flatten.each do |content| |
| if content.respond_to?(:call) |
| unless entry_created |
| entry = zip.put_next_entry(path, compression_level) |
| entry.unix_perms = content.mode & 07777 if content.respond_to?(:mode) |
| entry_created = true |
| end |
| if transform |
| output = StringIO.new |
| content.call output |
| to_transform << output.string |
| else |
| content.call zip |
| end |
| elsif content.nil? || File.directory?(content.to_s) |
| mkpath.call path |
| else |
| File.open content.to_s, 'rb' do |is| |
| unless entry_created |
| entry = zip.put_next_entry(path, compression_level) |
| entry.unix_perms = is.stat.mode & 07777 |
| entry_created = true |
| end |
| if transform |
| output = StringIO.new |
| while data = is.read(4096) |
| output << data |
| end |
| to_transform << output.string |
| else |
| while data = is.read(4096) |
| zip << data |
| end |
| end |
| end |
| end |
| end |
| if transform_map.key?(path) |
| zip << transform_map[path].call(to_transform) |
| end |
| end |
| end |
| end |
| |
| end |
| |
| |
| # :call-seq: |
| # zip(file) => ZipTask |
| # |
| # 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 |
| def zip(file) |
| ZipTask.define_task(file) |
| end |
| |
| |
| # An object for unzipping/untarring a file into a target directory. You can tell it to include |
| # or exclude only specific files and directories, and also to map files from particular |
| # paths inside the zip file into the target directory. Once ready, call #extract. |
| # |
| # Usually it is more convenient to create a file task for extracting the zip file |
| # (see #unzip) and pass this object as a prerequisite to other tasks. |
| # |
| # See Buildr#unzip. |
| class Unzip |
| |
| # The zip file to extract. |
| attr_accessor :zip_file |
| # The target directory to extract to. |
| attr_accessor :target |
| |
| # Initialize with hash argument of the form target=>zip_file. |
| def initialize(args) |
| @target, arg_names, zip_file = Buildr.application.resolve_args([args]) |
| @zip_file = zip_file.first |
| @paths = {} |
| end |
| |
| # :call-seq: |
| # extract |
| # |
| # Extract the zip/tgz file into the target directory. |
| # |
| # You can call this method directly. However, if you are using the #unzip method, |
| # it creates a file task for the target directory: use that task instead as a |
| # prerequisite. For example: |
| # build unzip(dir=>zip_file) |
| # Or: |
| # unzip(dir=>zip_file).target.invoke |
| def extract |
| # If no paths specified, then no include/exclude patterns |
| # specified. Nothing will happen unless we include all files. |
| if @paths.empty? |
| @paths[nil] = FromPath.new(self, nil) |
| end |
| |
| # Otherwise, empty unzip creates target as a file when touching. |
| mkpath target.to_s |
| if zip_file.to_s.match /\.t?gz$/ |
| #un-tar.gz |
| Zlib::GzipReader.open(zip_file.to_s) { |tar| |
| Archive::Tar::Minitar::Input.open(tar) do |inp| |
| inp.each do |tar_entry| |
| @paths.each do |path, patterns| |
| patterns.map([tar_entry]).each do |dest, entry| |
| next if entry.directory? |
| dest = File.expand_path(dest, target.to_s) |
| trace "Extracting #{dest}" |
| mkpath File.dirname(dest) rescue nil |
| File.open(dest, 'wb', entry.mode) {|f| f.write entry.read} |
| File.chmod(entry.mode, dest) |
| end |
| end |
| end |
| end |
| } |
| else |
| Zip::File.open(zip_file.to_s) do |zip| |
| entries = zip.collect |
| @paths.each do |path, patterns| |
| patterns.map(entries).each do |dest, entry| |
| next if entry.directory? |
| dest = File.expand_path(dest, target.to_s) |
| trace "Extracting #{dest}" |
| mkpath File.dirname(dest) rescue nil |
| entry.restore_permissions = true |
| entry.extract(dest) { true } |
| end |
| end |
| end |
| end |
| # Let other tasks know we updated the target directory. |
| touch target.to_s |
| end |
| |
| #reads the includes/excludes and apply them to the entry_name |
| def included?(entry_name) |
| @paths.each do |path, patterns| |
| return true if path.nil? |
| if entry_name =~ /^#{path}/ |
| short = entry_name.sub(path, '') |
| if patterns.include.any? { |pattern| File.fnmatch(pattern, entry_name) } && |
| !patterns.exclude.any? { |pattern| File.fnmatch(pattern, entry_name) } |
| # trace "tar_entry.full_name " + entry_name + " is included" |
| return true |
| end |
| end |
| end |
| # trace "tar_entry.full_name " + entry_name + " is excluded" |
| return false |
| end |
| |
| |
| # :call-seq: |
| # include(*files) => self |
| # include(*files, :path=>name) => self |
| # |
| # Include all files that match the patterns and returns self. |
| # |
| # Use include if you only want to unzip some of the files, by specifying |
| # them instead of using exclusion. You can use #include in combination |
| # with #exclude. |
| def include(*files) |
| if Hash === files.last |
| from_path(files.pop[:path]).include *files |
| else |
| from_path(nil).include *files |
| end |
| self |
| end |
| alias :add :include |
| |
| # :call-seq: |
| # exclude(*files) => self |
| # |
| # Exclude all files that match the patterns and return self. |
| # |
| # Use exclude to unzip all files except those that match the pattern. |
| # You can use #exclude in combination with #include. |
| def exclude(*files) |
| if Hash === files.last |
| from_path(files.pop[:path]).exclude *files |
| else |
| from_path(nil).exclude *files |
| end |
| self |
| end |
| |
| # :call-seq: |
| # from_path(name) => Path |
| # |
| # Allows you to unzip from a path. Returns an object you can use to |
| # specify which files to include/exclude relative to that path. |
| # Expands the file relative to that path. |
| # |
| # For example: |
| # unzip(Dir.pwd=>'test.jar').from_path('etc').include('LICENSE') |
| # will unzip etc/LICENSE into ./LICENSE. |
| # |
| # This is different from: |
| # unzip(Dir.pwd=>'test.jar').include('etc/LICENSE') |
| # which unzips etc/LICENSE into ./etc/LICENSE. |
| def from_path(name) |
| @paths[name] ||= FromPath.new(self, name) |
| end |
| alias :path :from_path |
| |
| # :call-seq: |
| # root => Unzip |
| # |
| # Returns the root path, essentially the Unzip object itself. In case you are wondering |
| # down paths and want to go back. |
| def root |
| self |
| end |
| |
| # Returns the path to the target directory. |
| def to_s |
| target.to_s |
| end |
| |
| class FromPath #:nodoc: |
| |
| def initialize(unzip, path) |
| @unzip = unzip |
| if path |
| @path = path[-1] == ?/ ? path : path + '/' |
| else |
| @path = '' |
| end |
| end |
| |
| # See UnzipTask#include |
| def include(*files) #:doc: |
| @include ||= [] |
| @include |= files |
| self |
| end |
| |
| # See UnzipTask#exclude |
| def exclude(*files) #:doc: |
| @exclude ||= [] |
| @exclude |= files |
| self |
| end |
| |
| def map(entries) |
| includes = @include || ['*'] |
| excludes = @exclude || [] |
| entries.inject({}) do |map, entry| |
| if entry.name =~ /^#{@path}/ |
| short = entry.name.sub(@path, '') |
| if includes.any? { |pat| File.fnmatch(pat, short) } && |
| !excludes.any? { |pat| File.fnmatch(pat, short) } |
| map[short] = entry |
| end |
| end |
| map |
| end |
| end |
| |
| # Documented in Unzip. |
| def root |
| @unzip |
| end |
| |
| # The target directory to extract to. |
| def target |
| @unzip.target |
| end |
| |
| end |
| |
| end |
| |
| # :call-seq: |
| # unzip(to_dir=>zip_file) => Zip |
| # |
| # Creates a task that will unzip a file into the target directory. The task name |
| # is the target directory, the prerequisite is the file to unzip. |
| # |
| # This method creates a file task to expand the zip file. It returns an Unzip object |
| # that specifies how the file will be extracted. You can include or exclude specific |
| # files from within the zip, and map to different paths. |
| # |
| # The Unzip object's to_s method return the path to the target directory, so you can |
| # use it as a prerequisite. By keeping the Unzip object separate from the file task, |
| # you overlay additional work on top of the file task. |
| # |
| # For example: |
| # unzip('all'=>'test.zip') |
| # unzip('src'=>'test.zip').include('README', 'LICENSE') |
| # unzip('libs'=>'test.zip').from_path('libs') |
| def unzip(args) |
| target, arg_names, zip_file = Buildr.application.resolve_args([args]) |
| task = file(File.expand_path(target.to_s)=>zip_file) |
| Unzip.new(task=>zip_file).tap do |setup| |
| task.enhance { setup.extract } |
| end |
| end |
| |
| end |