
As far as I can tell these methods have only ever been used in the test suite. Since Formula includes FileUtils, it is generally simpler (and in the case of cp, more readable) to use the FileUtils methods directly. Closes Homebrew/homebrew#30081.
488 lines
12 KiB
Ruby
488 lines
12 KiB
Ruby
require 'pathname'
|
|
require 'mach'
|
|
require 'resource'
|
|
require 'metafiles'
|
|
|
|
# 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=src
|
|
src = Pathname(src).expand_path(self)
|
|
dst = join File.basename(new_basename)
|
|
mkpath
|
|
FileUtils.ln_sf src.relative_path_from(dst.parent), dst
|
|
end
|
|
protected :install_symlink_p
|
|
|
|
# we assume this pathname object is a file obviously
|
|
alias_method :old_write, :write if method_defined?(:write)
|
|
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(basename.to_s)
|
|
tf.binmode
|
|
tf.write(content)
|
|
tf.close
|
|
|
|
begin
|
|
old_stat = stat
|
|
rescue Errno::ENOENT
|
|
old_stat = default_stat
|
|
end
|
|
|
|
FileUtils.mv tf.path, self
|
|
|
|
uid = Process.uid
|
|
gid = Process.groups.delete(old_stat.gid) { Process.gid }
|
|
|
|
begin
|
|
chown(uid, gid)
|
|
chmod(old_stat.mode)
|
|
rescue Errno::EPERM
|
|
end
|
|
end
|
|
|
|
def default_stat
|
|
sentinel = parent.join(".brew.#{Process.pid}.#{rand(Time.now.to_i)}")
|
|
sentinel.open("w") { }
|
|
sentinel.stat
|
|
ensure
|
|
sentinel.unlink
|
|
end
|
|
private :default_stat
|
|
|
|
def cp dst
|
|
opoo "Pathname#cp is deprecated, use FileUtils.cp"
|
|
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
|
|
opoo "Pathname#chmod_R is deprecated, use FileUtils.chmod_R"
|
|
require 'fileutils'
|
|
FileUtils.chmod_R perms, to_s
|
|
end
|
|
|
|
def version
|
|
require 'version'
|
|
Version.parse(self)
|
|
end
|
|
|
|
def compression_type
|
|
case extname
|
|
when ".jar", ".war"
|
|
# Don't treat jars or wars as compressed
|
|
return
|
|
when ".gz"
|
|
# If the filename ends with .gz not preceded by .tar
|
|
# then we want to gunzip but not tar
|
|
return :gzip_only
|
|
when ".bz2"
|
|
return :bzip2_only
|
|
end
|
|
|
|
# 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
|
|
when /^xar!/n then :xar
|
|
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(klass)
|
|
digest = klass.new
|
|
if digest.respond_to?(:file)
|
|
digest.file(self)
|
|
else
|
|
buf = ""
|
|
open("rb") { |f| digest << buf while f.read(1024, buf) }
|
|
end
|
|
digest.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(self, 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
|
|
|
|
def make_relative_symlink(src)
|
|
dirname.mkpath
|
|
File.symlink(src.relative_path_from(dirname), self)
|
|
end
|
|
|
|
def /(other)
|
|
unless other.respond_to?(:to_str) || other.respond_to?(:to_path)
|
|
opoo "Pathname#/ called on #{inspect} with #{other.inspect} as an argument"
|
|
puts "This behavior is deprecated, please pass either a String or a Pathname"
|
|
end
|
|
self + other.to_s
|
|
end unless method_defined?(:/)
|
|
|
|
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
|
|
quiet_system "/usr/bin/install-info", "--quiet", to_s, "#{dirname}/dir"
|
|
end
|
|
|
|
def uninstall_info
|
|
quiet_system "/usr/bin/install-info", "--delete", "--quiet", to_s, "#{dirname}/dir"
|
|
end
|
|
|
|
def find_formula
|
|
[join("Formula"), join("HomebrewFormula"), self].each do |d|
|
|
if d.exist?
|
|
d.children.each do |pn|
|
|
yield pn if pn.extname == ".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
|
|
end
|
|
end
|
|
|
|
# Writes an exec script that sets environment variables
|
|
def write_env_script target, env
|
|
env_export = ''
|
|
env.each {|key, value| env_export += "#{key}=\"#{value}\" "}
|
|
self.write <<-EOS.undent
|
|
#!/bin/bash
|
|
#{env_export}exec "#{target}" "$@"
|
|
EOS
|
|
end
|
|
|
|
# Writes a wrapper env script and moves all files to the dst
|
|
def env_script_all_files dst, env
|
|
dst.mkpath
|
|
Pathname.glob("#{self}/*") do |file|
|
|
dst.install_p file
|
|
new_file = dst+file.basename
|
|
file.write_env_script(new_file, env)
|
|
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
|
|
end
|
|
|
|
def install_metafiles from=Pathname.pwd
|
|
Pathname(from).children.each do |p|
|
|
next if p.directory?
|
|
next unless Metafiles.copy?(p.basename.to_s)
|
|
# 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
|
|
install(filename)
|
|
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
|
|
elsif RUBY_VERSION == "2.0.0"
|
|
# https://bugs.ruby-lang.org/issues/9915
|
|
prepend Module.new {
|
|
def inspect
|
|
super.force_encoding(@path.encoding)
|
|
end
|
|
}
|
|
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
|
|
puts "ln -s #{src.relative_path_from(dirname)} #{basename}" if ARGV.verbose?
|
|
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
|