
The abv method has a construct that causes emacs to not highlight the rest of pathname.rb, so move abv lower in the file. Sorry.
503 lines
13 KiB
Ruby
503 lines
13 KiB
Ruby
require 'pathname'
|
|
require 'mach'
|
|
require 'resource'
|
|
|
|
# we enhance pathname to make our code more readable
|
|
class Pathname
|
|
include MachO
|
|
|
|
BOTTLE_EXTNAME_RX = /(\.[a-z_]+(32)?\.bottle\.(\d+\.)?tar\.gz)$/
|
|
|
|
def install *sources
|
|
sources.each do |src|
|
|
case src
|
|
when Resource
|
|
src.stage(self)
|
|
when Resource::Partial
|
|
src.resource.stage { install(*src.files) }
|
|
when Array
|
|
if src.empty?
|
|
opoo "tried to install empty array to #{self}"
|
|
return
|
|
end
|
|
src.each {|s| install_p(s) }
|
|
when Hash
|
|
if src.empty?
|
|
opoo "tried to install empty hash to #{self}"
|
|
return
|
|
end
|
|
src.each {|s, new_basename| install_p(s, new_basename) }
|
|
else
|
|
install_p(src)
|
|
end
|
|
end
|
|
end
|
|
|
|
def install_p src, new_basename = nil
|
|
if new_basename
|
|
new_basename = File.basename(new_basename) # rationale: see Pathname.+
|
|
dst = self+new_basename
|
|
else
|
|
dst = self
|
|
end
|
|
|
|
src = src.to_s
|
|
dst = dst.to_s
|
|
|
|
# if it's a symlink, don't resolve it to a file because if we are moving
|
|
# files one by one, it's likely we will break the symlink by moving what
|
|
# it points to before we move it
|
|
# and also broken symlinks are not the end of the world
|
|
raise "#{src} does not exist" unless File.symlink? src or File.exist? src
|
|
|
|
dst = yield(src, dst) if block_given?
|
|
|
|
mkpath
|
|
if File.symlink? src
|
|
# we use the BSD mv command because FileUtils copies the target and
|
|
# not the link! I'm beginning to wish I'd used Python quite honestly!
|
|
raise unless Kernel.system 'mv', src, dst
|
|
else
|
|
# we mv when possible as it is faster and you should only be using
|
|
# this function when installing from the temporary build directory
|
|
FileUtils.mv src, dst
|
|
end
|
|
end
|
|
protected :install_p
|
|
|
|
# Creates symlinks to sources in this folder.
|
|
def install_symlink *sources
|
|
sources.each do |src|
|
|
case src
|
|
when Array
|
|
src.each {|s| install_symlink_p(s) }
|
|
when Hash
|
|
src.each {|s, new_basename| install_symlink_p(s, new_basename) }
|
|
else
|
|
install_symlink_p(src)
|
|
end
|
|
end
|
|
end
|
|
|
|
def install_symlink_p src, new_basename = nil
|
|
if new_basename.nil?
|
|
dst = self+File.basename(src)
|
|
else
|
|
dst = self+File.basename(new_basename)
|
|
end
|
|
mkpath
|
|
FileUtils.ln_s src.to_s, dst.to_s
|
|
end
|
|
protected :install_symlink_p
|
|
|
|
# we assume this pathname object is a file obviously
|
|
def write content
|
|
raise "Will not overwrite #{to_s}" if exist?
|
|
dirname.mkpath
|
|
File.open(self, 'w') {|f| f.write content }
|
|
end
|
|
|
|
# NOTE always overwrites
|
|
def atomic_write content
|
|
require 'tempfile'
|
|
tf = Tempfile.new(self.basename.to_s)
|
|
tf.write(content)
|
|
tf.close
|
|
FileUtils.mv tf.path, self.to_s
|
|
end
|
|
|
|
def cp dst
|
|
if file?
|
|
FileUtils.cp to_s, dst
|
|
else
|
|
FileUtils.cp_r to_s, dst
|
|
end
|
|
return dst
|
|
end
|
|
|
|
def cp_path_sub pattern, replacement
|
|
raise "#{self} does not exist" unless self.exist?
|
|
|
|
src = self.to_s
|
|
dst = src.sub(pattern, replacement)
|
|
raise "#{src} is the same file as #{dst}" if src == dst
|
|
|
|
dst_path = Pathname.new dst
|
|
|
|
if self.directory?
|
|
dst_path.mkpath
|
|
return
|
|
end
|
|
|
|
dst_path.dirname.mkpath
|
|
|
|
dst = yield(src, dst) if block_given?
|
|
|
|
FileUtils.cp(src, dst)
|
|
end
|
|
|
|
# extended to support common double extensions
|
|
alias extname_old extname
|
|
def extname(path=to_s)
|
|
BOTTLE_EXTNAME_RX.match(path)
|
|
return $1 if $1
|
|
/(\.(tar|cpio|pax)\.(gz|bz2|lz|xz|Z))$/.match(path)
|
|
return $1 if $1
|
|
return File.extname(path)
|
|
end
|
|
|
|
# for filetypes we support, basename without extension
|
|
def stem
|
|
File.basename((path = to_s), extname(path))
|
|
end
|
|
|
|
# I don't trust the children.length == 0 check particularly, not to mention
|
|
# it is slow to enumerate the whole directory just to see if it is empty,
|
|
# instead rely on good ol' libc and the filesystem
|
|
def rmdir_if_possible
|
|
rmdir
|
|
true
|
|
rescue Errno::ENOTEMPTY
|
|
if (ds_store = self+'.DS_Store').exist? && children.length == 1
|
|
ds_store.unlink
|
|
retry
|
|
else
|
|
false
|
|
end
|
|
rescue Errno::EACCES, Errno::ENOENT
|
|
false
|
|
end
|
|
|
|
def chmod_R perms
|
|
require 'fileutils'
|
|
FileUtils.chmod_R perms, to_s
|
|
end
|
|
|
|
def version
|
|
require 'version'
|
|
Version.parse(self)
|
|
end
|
|
|
|
def compression_type
|
|
# Don't treat jars or wars as compressed
|
|
return nil if self.extname == '.jar'
|
|
return nil if self.extname == '.war'
|
|
|
|
# OS X installer package
|
|
return :pkg if self.extname == '.pkg'
|
|
|
|
# If the filename ends with .gz not preceded by .tar
|
|
# then we want to gunzip but not tar
|
|
return :gzip_only if self.extname == '.gz'
|
|
|
|
# Get enough of the file to detect common file types
|
|
# POSIX tar magic has a 257 byte offset
|
|
# magic numbers stolen from /usr/share/file/magic/
|
|
case open('rb') { |f| f.read(262) }
|
|
when /^PK\003\004/n then :zip
|
|
when /^\037\213/n then :gzip
|
|
when /^BZh/n then :bzip2
|
|
when /^\037\235/n then :compress
|
|
when /^.{257}ustar/n then :tar
|
|
when /^\xFD7zXZ\x00/n then :xz
|
|
when /^LZIP/n then :lzip
|
|
when /^Rar!/n then :rar
|
|
when /^7z\xBC\xAF\x27\x1C/n then :p7zip
|
|
else
|
|
# This code so that bad-tarballs and zips produce good error messages
|
|
# when they don't unarchive properly.
|
|
case extname
|
|
when ".tar.gz", ".tgz", ".tar.bz2", ".tbz" then :tar
|
|
when ".zip" then :zip
|
|
end
|
|
end
|
|
end
|
|
|
|
def text_executable?
|
|
%r[^#!\s*\S+] === open('r') { |f| f.read(1024) }
|
|
end
|
|
|
|
def incremental_hash(hasher)
|
|
incr_hash = hasher.new
|
|
buf = ""
|
|
open('rb') { |f| incr_hash << buf while f.read(1024, buf) }
|
|
incr_hash.hexdigest
|
|
end
|
|
|
|
def sha1
|
|
require 'digest/sha1'
|
|
incremental_hash(Digest::SHA1)
|
|
end
|
|
|
|
def sha256
|
|
require 'digest/sha2'
|
|
incremental_hash(Digest::SHA2)
|
|
end
|
|
|
|
def verify_checksum expected
|
|
raise ChecksumMissingError if expected.nil? or expected.empty?
|
|
actual = Checksum.new(expected.hash_type, send(expected.hash_type).downcase)
|
|
raise ChecksumMismatchError.new(expected, actual) unless expected == actual
|
|
end
|
|
|
|
if '1.9' <= RUBY_VERSION
|
|
alias_method :to_str, :to_s
|
|
end
|
|
|
|
def cd
|
|
Dir.chdir(self){ yield }
|
|
end
|
|
|
|
def subdirs
|
|
children.select{ |child| child.directory? }
|
|
end
|
|
|
|
def resolved_path
|
|
self.symlink? ? dirname+readlink : self
|
|
end
|
|
|
|
def resolved_path_exists?
|
|
link = readlink
|
|
rescue ArgumentError
|
|
# The link target contains NUL bytes
|
|
false
|
|
else
|
|
(dirname+link).exist?
|
|
end
|
|
|
|
# perhaps confusingly, this Pathname object becomes the symlink pointing to
|
|
# the src paramter.
|
|
def make_relative_symlink src
|
|
src = Pathname.new(src) unless src.kind_of? Pathname
|
|
|
|
self.dirname.mkpath
|
|
Dir.chdir self.dirname do
|
|
# NOTE only system ln -s will create RELATIVE symlinks
|
|
quiet_system 'ln', '-s', src.relative_path_from(self.dirname), self.basename
|
|
if not $?.success?
|
|
if self.exist?
|
|
raise <<-EOS.undent
|
|
Could not symlink file: #{src.expand_path}
|
|
Target #{self} already exists. You may need to delete it.
|
|
To force the link and overwrite all other conflicting files, do:
|
|
brew link --overwrite formula_name
|
|
|
|
To list all files that would be deleted:
|
|
brew link --overwrite --dry-run formula_name
|
|
EOS
|
|
# #exist? will return false for symlinks whose target doesn't exist
|
|
elsif self.symlink?
|
|
raise <<-EOS.undent
|
|
Could not symlink file: #{src.expand_path}
|
|
Target #{self} already exists as a symlink to #{readlink}.
|
|
If this file is from another formula, you may need to
|
|
`brew unlink` it. Otherwise, you may want to delete it.
|
|
To force the link and overwrite all other conflicting files, do:
|
|
brew link --overwrite formula_name
|
|
|
|
To list all files that would be deleted:
|
|
brew link --overwrite --dry-run formula_name
|
|
EOS
|
|
elsif !dirname.writable_real?
|
|
raise <<-EOS.undent
|
|
Could not symlink file: #{src.expand_path}
|
|
#{dirname} is not writable. You should change its permissions.
|
|
EOS
|
|
else
|
|
raise <<-EOS.undent
|
|
Could not symlink file: #{src.expand_path}
|
|
#{self} may already exist.
|
|
#{dirname} may not be writable.
|
|
EOS
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def / that
|
|
join that.to_s
|
|
end
|
|
|
|
def ensure_writable
|
|
saved_perms = nil
|
|
unless writable_real?
|
|
saved_perms = stat.mode
|
|
chmod 0644
|
|
end
|
|
yield
|
|
ensure
|
|
chmod saved_perms if saved_perms
|
|
end
|
|
|
|
def install_info
|
|
unless self.symlink?
|
|
raise "Cannot install info entry for unbrewed info file '#{self}'"
|
|
end
|
|
system '/usr/bin/install-info', '--quiet', self.to_s, (self.dirname+'dir').to_s
|
|
end
|
|
|
|
def uninstall_info
|
|
unless self.symlink?
|
|
raise "Cannot uninstall info entry for unbrewed info file '#{self}'"
|
|
end
|
|
system '/usr/bin/install-info', '--delete', '--quiet', self.to_s, (self.dirname+'dir').to_s
|
|
end
|
|
|
|
def all_formula pwd = self
|
|
children.map{ |child| child.relative_path_from(pwd) }.each do |pn|
|
|
yield pn if pn.to_s =~ /.rb$/
|
|
end
|
|
children.each do |child|
|
|
child.all_formula(pwd) do |pn|
|
|
yield pn
|
|
end if child.directory?
|
|
end
|
|
end
|
|
|
|
def find_formula
|
|
[self/:Formula, self/:HomebrewFormula, self].each do |d|
|
|
if d.exist?
|
|
d.children.map{ |child| child.relative_path_from(self) }.each do |pn|
|
|
yield pn if pn.to_s =~ /.rb$/
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
# Writes an exec script in this folder for each target pathname
|
|
def write_exec_script *targets
|
|
targets.flatten!
|
|
if targets.empty?
|
|
opoo "tried to write exec scripts to #{self} for an empty list of targets"
|
|
return
|
|
end
|
|
targets.each do |target|
|
|
target = Pathname.new(target) # allow pathnames or strings
|
|
(self+target.basename()).write <<-EOS.undent
|
|
#!/bin/bash
|
|
exec "#{target}" "$@"
|
|
EOS
|
|
# +x here so this will work during post-install as well
|
|
(self+target.basename()).chmod 0644
|
|
end
|
|
end
|
|
|
|
# Writes an exec script that invokes a java jar
|
|
def write_jar_script target_jar, script_name, java_opts=""
|
|
(self+script_name).write <<-EOS.undent
|
|
#!/bin/bash
|
|
exec java #{java_opts} -jar #{target_jar} "$@"
|
|
EOS
|
|
# +x here so this will work during post-install as well
|
|
(self+script_name).chmod 0644
|
|
end
|
|
|
|
def install_metafiles from=nil
|
|
# Default to current path, and make sure we have a pathname, not a string
|
|
from = "." if from.nil?
|
|
from = Pathname.new(from.to_s)
|
|
|
|
from.children.each do |p|
|
|
next if p.directory?
|
|
next unless FORMULA_META_FILES.should_copy? p
|
|
# Some software symlinks these files (see help2man.rb)
|
|
filename = p.resolved_path
|
|
# Some software links metafiles together, so by the time we iterate to one of them
|
|
# we may have already moved it. libxml2's COPYING and Copyright are affected by this.
|
|
next unless filename.exist?
|
|
filename.chmod 0644
|
|
self.install filename
|
|
end
|
|
end
|
|
|
|
# Returns an array containing all dynamically-linked libraries, based on the
|
|
# output of otool. This returns the install names, so these are not guaranteed
|
|
# to be absolute paths.
|
|
# Returns an empty array both for software that links against no libraries,
|
|
# and for non-mach objects.
|
|
def dynamically_linked_libraries
|
|
`#{MacOS.locate("otool")} -L "#{expand_path}"`.chomp.split("\n")[1..-1].map do |line|
|
|
line[/\t(.+) \([^(]+\)/, 1]
|
|
end
|
|
end
|
|
|
|
def abv
|
|
out=''
|
|
n=`find #{to_s} -type f ! -name .DS_Store | wc -l`.to_i
|
|
out<<"#{n} files, " if n > 1
|
|
out<<`/usr/bin/du -hs #{to_s} | cut -d"\t" -f1`.strip
|
|
end
|
|
|
|
# We redefine these private methods in order to add the /o modifier to
|
|
# the Regexp literals, which forces string interpolation to happen only
|
|
# once instead of each time the method is called. This is fixed in 1.9+.
|
|
if RUBY_VERSION <= "1.8.7"
|
|
alias_method :old_chop_basename, :chop_basename
|
|
def chop_basename(path)
|
|
base = File.basename(path)
|
|
if /\A#{Pathname::SEPARATOR_PAT}?\z/o =~ base
|
|
return nil
|
|
else
|
|
return path[0, path.rindex(base)], base
|
|
end
|
|
end
|
|
private :chop_basename
|
|
|
|
alias_method :old_prepend_prefix, :prepend_prefix
|
|
def prepend_prefix(prefix, relpath)
|
|
if relpath.empty?
|
|
File.dirname(prefix)
|
|
elsif /#{SEPARATOR_PAT}/o =~ prefix
|
|
prefix = File.dirname(prefix)
|
|
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
|
|
prefix + relpath
|
|
else
|
|
prefix + relpath
|
|
end
|
|
end
|
|
private :prepend_prefix
|
|
end
|
|
end
|
|
|
|
module ObserverPathnameExtension
|
|
class << self
|
|
attr_accessor :n, :d
|
|
|
|
def reset_counts!
|
|
@n = @d = 0
|
|
end
|
|
|
|
def total
|
|
n + d
|
|
end
|
|
|
|
def counts
|
|
[n, d]
|
|
end
|
|
end
|
|
|
|
def unlink
|
|
super
|
|
puts "rm #{to_s}" if ARGV.verbose?
|
|
ObserverPathnameExtension.n += 1
|
|
end
|
|
def rmdir
|
|
super
|
|
puts "rmdir #{to_s}" if ARGV.verbose?
|
|
ObserverPathnameExtension.d += 1
|
|
end
|
|
def make_relative_symlink src
|
|
super
|
|
ObserverPathnameExtension.n += 1
|
|
end
|
|
def install_info
|
|
super
|
|
puts "info #{to_s}" if ARGV.verbose?
|
|
end
|
|
def uninstall_info
|
|
super
|
|
puts "uninfo #{to_s}" if ARGV.verbose?
|
|
end
|
|
end
|