brew/Library/Homebrew/extend/pathname.rb

488 lines
12 KiB
Ruby
Raw Normal View History

require 'pathname'
require 'mach'
require 'resource'
2014-06-07 17:49:07 -05:00
require 'metafiles'
# we enhance pathname to make our code more readable
class Pathname
include MachO
BOTTLE_EXTNAME_RX = /(\.[a-z0-9_]+\.bottle\.(\d+\.)?tar\.gz)$/
2012-02-09 18:43:47 -08:00
def install *sources
sources.each do |src|
case src
when Resource
src.stage(self)
when Resource::Partial
src.resource.stage { install(*src.files) }
2012-02-09 18:43:47 -08:00
when Array
if src.empty?
2012-03-30 23:02:32 -07:00
opoo "tried to install empty array to #{self}"
return
end
src.each { |s| install_p(s, File.basename(s)) }
2012-02-09 18:43:47 -08:00
when Hash
if src.empty?
2012-03-30 23:02:32 -07:00
opoo "tried to install empty hash to #{self}"
return
end
src.each { |s, new_basename| install_p(s, new_basename) }
2012-02-09 18:43:47 -08:00
else
install_p(src, File.basename(src))
2012-02-09 18:43:47 -08:00
end
end
end
def install_p(src, new_basename)
raise Errno::ENOENT, src.to_s unless File.symlink?(src) || File.exist?(src)
2015-03-26 22:22:45 -04:00
src = Pathname(src)
dst = join(new_basename)
dst = yield(src, dst) if block_given?
mkpath
# Use FileUtils.mv over File.rename to handle filesystem boundaries. If src
# is a symlink, and its target is moved first, FileUtils.mv will fail:
# https://bugs.ruby-lang.org/issues/7707
# In that case, use the system "mv" command.
2015-03-26 22:22:45 -04:00
if src.symlink?
raise unless Kernel.system 'mv', src, dst
else
FileUtils.mv src, dst
end
end
private :install_p
2012-02-12 10:36:16 -08:00
# 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, File.basename(s)) }
2012-02-12 10:36:16 -08:00
when Hash
src.each { |s, new_basename| install_symlink_p(s, new_basename) }
2012-02-12 10:36:16 -08:00
else
install_symlink_p(src, File.basename(src))
2012-02-12 10:36:16 -08:00
end
end
end
def install_symlink_p(src, new_basename)
src = Pathname(src).expand_path(self)
dst = join(new_basename)
2012-02-12 10:36:16 -08:00
mkpath
FileUtils.ln_sf(src.relative_path_from(dst.parent), dst)
2012-02-12 10:36:16 -08:00
end
private :install_symlink_p
2012-02-12 10:36:16 -08:00
# we assume this pathname object is a file obviously
2014-05-03 14:53:08 -05:00
alias_method :old_write, :write if method_defined?(:write)
def write(content, *open_args)
raise "Will not overwrite #{to_s}" if exist?
dirname.mkpath
open("w", *open_args) { |f| f.write(content) }
end
2014-07-27 16:21:35 -07:00
def binwrite(contents, *open_args)
open("wb", *open_args) { |f| f.write(contents) }
end unless method_defined?(:binwrite)
2014-07-27 10:34:14 -07:00
2014-07-28 21:11:35 -07:00
def binread(*open_args)
open("rb", *open_args) { |f| f.read }
end unless method_defined?(:binread)
2012-03-16 12:58:39 +00:00
# NOTE always overwrites
def atomic_write content
require "tempfile"
tf = Tempfile.new(basename.to_s, dirname)
begin
tf.binmode
tf.write(content)
begin
old_stat = stat
rescue Errno::ENOENT
old_stat = default_stat
end
uid = Process.uid
gid = Process.groups.delete(old_stat.gid) { Process.gid }
begin
tf.chown(uid, gid)
tf.chmod(old_stat.mode)
rescue Errno::EPERM
end
File.rename(tf.path, self)
ensure
tf.close!
end
end
def default_stat
sentinel = parent.join(".brew.#{Process.pid}.#{rand(Time.now.to_i)}")
sentinel.open("w") { }
sentinel.stat
ensure
sentinel.unlink
2012-03-16 12:58:39 +00:00
end
private :default_stat
2012-03-16 12:58:39 +00:00
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?
dst = sub(pattern, replacement)
raise "#{self} is the same file as #{dst}" if self == dst
if directory?
dst.mkpath
else
dst.dirname.mkpath
dst = yield(self, dst) if block_given?
FileUtils.cp(self, dst)
end
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
2013-04-27 15:21:05 -05:00
rescue Errno::ENOTEMPTY
if (ds_store = self+'.DS_Store').exist? && children.length == 1
ds_store.unlink
retry
2013-04-27 15:21:05 -05:00
else
false
end
2013-04-27 15:21:05 -05:00
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
2014-04-06 12:34:42 -05:00
when /^xar!/n then :xar
2014-12-09 01:22:55 -05:00
when /^\xed\xab\xee\xdb/n then :rpm
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
2014-06-11 12:05:19 -05:00
def incremental_hash(klass)
digest = klass.new
if digest.respond_to?(:file)
digest.file(self)
else
buf = ""
2015-01-05 10:45:05 -05:00
open("rb") { |f| digest << buf while f.read(16384, buf) }
2014-06-11 12:05:19 -05:00
end
digest.hexdigest
end
def sha1
require 'digest/sha1'
incremental_hash(Digest::SHA1)
end
2013-10-14 22:05:30 -05:00
def sha256
require 'digest/sha2'
incremental_hash(Digest::SHA2)
2009-12-30 18:56:46 +00:00
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
2009-12-30 18:56:46 +00:00
# FIXME eliminate the places where we rely on this method
alias_method :to_str, :to_s unless method_defined?(:to_str)
2010-02-27 12:29:45 +00:00
def cd
Dir.chdir(self){ yield }
end
def subdirs
children.select{ |child| child.directory? }
end
2010-07-25 12:07:35 -07:00
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)
2010-08-15 17:17:26 -07:00
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
2011-06-16 17:38:52 +01:00
saved_perms = nil
unless writable_real?
2011-06-16 17:38:52 +01:00
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
2012-03-02 20:28:54 +00:00
def find_formula
[join("Formula"), join("HomebrewFormula"), self].each do |d|
if d.exist?
2014-04-25 18:58:16 -05:00
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
2013-06-20 16:18:01 -05:00
targets.flatten!
if targets.empty?
opoo "tried to write exec scripts to #{self} for an empty list of targets"
return
end
mkpath
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}\" "}
dirname.mkpath
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|
next if file.directory?
dst.install(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=""
mkpath
(self+script_name).write <<-EOS.undent
#!/bin/bash
exec java #{java_opts} -jar #{target_jar} "$@"
EOS
end
2014-06-07 17:52:11 -05:00
def install_metafiles from=Pathname.pwd
Pathname(from).children.each do |p|
next if p.directory?
2014-06-07 23:40:26 -05:00
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
2014-06-07 17:52:11 -05:00
install(filename)
end
end
def abv
out = ""
n = Utils.popen_read("find", expand_path.to_s, "-type", "f", "!", "-name", ".DS_Store").split("\n").size
out << "#{n} files, " if n > 1
out << Utils.popen_read("/usr/bin/du", "-hs", expand_path.to_s).split("\t")[0].strip
out
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
2014-06-08 20:04:16 -05:00
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
2010-08-15 17:17:26 -07:00
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