746 lines
21 KiB
Ruby
Raw Normal View History

rubocop: Use `Sorbet/StrictSigil` as it's better than comments - Previously I thought that comments were fine to discourage people from wasting their time trying to bump things that used `undef` that Sorbet didn't support. But RuboCop is better at this since it'll complain if the comments are unnecessary. - Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501. - I've gone for a mixture of `rubocop:disable` for the files that can't be `typed: strict` (use of undef, required before everything else, etc) and `rubocop:todo` for everything else that should be tried to make strictly typed. There's no functional difference between the two as `rubocop:todo` is `rubocop:disable` with a different name. - And I entirely disabled the cop for the docs/ directory since `typed: strict` isn't going to gain us anything for some Markdown linting config files. - This means that now it's easier to track what needs to be done rather than relying on checklists of files in our big Sorbet issue: ```shell $ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l 268 ``` - And this is confirmed working for new files: ```shell $ git status On branch use-rubocop-for-sorbet-strict-sigils Untracked files: (use "git add <file>..." to include in what will be committed) Library/Homebrew/bad.rb Library/Homebrew/good.rb nothing added to commit but untracked files present (use "git add" to track) $ brew style Offenses: bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true. ^^^^^^^^^^^^^ 1340 files inspected, 1 offense detected ```
2024-08-12 10:30:59 +01:00
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "keg_relocate"
require "language/python"
2017-05-22 03:23:50 +02:00
require "lock_file"
require "cachable"
2020-08-17 18:45:48 +02:00
# Installation prefix of a formula.
2014-06-26 19:06:31 -05:00
class Keg
2019-11-05 20:33:32 +00:00
extend Cachable
2020-08-17 18:45:48 +02:00
# Error for when a keg is already linked.
class AlreadyLinkedError < RuntimeError
def initialize(keg)
2017-10-15 02:28:32 +02:00
super <<~EOS
2014-06-24 19:04:52 -05:00
Cannot link #{keg.name}
Another version is already linked: #{keg.linked_keg_record.resolved_path}
2018-06-06 23:34:19 -04:00
EOS
end
end
2020-08-17 18:45:48 +02:00
# Error for when a keg cannot be linked.
class LinkError < RuntimeError
attr_reader :keg, :src, :dst
def initialize(keg, src, dst, cause)
@src = src
@dst = dst
@keg = keg
@cause = cause
super(cause.message)
set_backtrace(cause.backtrace)
end
end
2020-08-17 18:45:48 +02:00
# Error for when a file already exists or belongs to another keg.
class ConflictError < LinkError
2020-10-20 12:03:48 +02:00
sig { returns(String) }
def suggestion
conflict = Keg.for(dst)
rescue NotAKegError, Errno::ENOENT
"already exists. You may want to remove it:\n rm '#{dst}'\n"
else
2017-10-15 02:28:32 +02:00
<<~EOS
is a symlink belonging to #{conflict.name}. You can unlink it:
brew unlink #{conflict.name}
EOS
end
2020-10-20 12:03:48 +02:00
sig { returns(String) }
def to_s
s = []
s << "Could not symlink #{src}"
s << "Target #{dst}" << suggestion
2017-10-15 02:28:32 +02:00
s << <<~EOS
To force the link and overwrite all conflicting files:
2014-06-24 19:04:52 -05:00
brew link --overwrite #{keg.name}
To list all files that would be deleted:
brew link --overwrite #{keg.name} --dry-run
2018-06-06 23:34:19 -04:00
EOS
s.join("\n")
end
end
2020-08-17 18:45:48 +02:00
# Error for when a directory is not writable.
class DirectoryNotWritableError < LinkError
2020-10-20 12:03:48 +02:00
sig { returns(String) }
2018-01-17 10:42:43 +00:00
def to_s
<<~EOS
Could not symlink #{src}
#{dst.dirname} is not writable.
EOS
end
end
# Locale-specific directories have the form `language[_territory][.codeset][@modifier]`
LOCALEDIR_RX = %r{(locale|man)/([a-z]{2}|C|POSIX)(_[A-Z]{2})?(\.[a-zA-Z\-0-9]+(@.+)?)?}
INFOFILE_RX = %r{info/([^.].*?\.info(\.gz)?|dir)$}
2014-03-27 17:13:39 -05:00
# These paths relative to the keg's share directory should always be real
# directories in the prefix, never symlinks.
SHARE_PATHS = %w[
aclocal doc info java locale man
2014-03-27 17:13:39 -05:00
man/man1 man/man2 man/man3 man/man4
man/man5 man/man6 man/man7 man/man8
man/cat1 man/cat2 man/cat3 man/cat4
man/cat5 man/cat6 man/cat7 man/cat8
applications gnome gnome/help icons
2015-11-06 17:32:14 +08:00
mime-info pixmaps sounds postgresql
].freeze
2014-03-27 17:13:39 -05:00
2020-09-11 10:29:21 +01:00
ELISP_EXTENSIONS = %w[.el .elc].freeze
PYC_EXTENSIONS = %w[.pyc .pyo].freeze
LIBTOOL_EXTENSIONS = %w[.la .lai].freeze
KEEPME_FILE = ".keepme"
2020-11-05 15:19:56 -05:00
# @param path if this is a file in a keg, returns the containing {Keg} object.
def self.for(path)
original_path = path
raise Errno::ENOENT, original_path.to_s unless original_path.exist?
if (path = original_path.realpath)
until path.root?
return Keg.new(path) if path.parent.parent == HOMEBREW_CELLAR.realpath
2018-09-17 02:45:00 +02:00
path = path.parent.realpath # realpath() prevents root? failing
end
end
raise NotAKegError, "#{original_path} is not inside a keg"
end
def self.all
Formula.racks.flat_map(&:subdirs).map { |d| new(d) }
end
def self.keg_link_directories
@keg_link_directories ||= %w[
bin etc include lib sbin share var
].freeze
end
def self.must_exist_subdirectories
@must_exist_subdirectories ||= (
keg_link_directories - %w[var] + %w[
opt
var/homebrew/linked
]
).map { |dir| HOMEBREW_PREFIX/dir }.sort.uniq.freeze
end
# Keep relatively in sync with
# {https://github.com/Homebrew/install/blob/HEAD/install.sh}
def self.must_exist_directories
@must_exist_directories ||= (must_exist_subdirectories + [
HOMEBREW_CELLAR,
].sort.uniq).freeze
end
# Keep relatively in sync with
# {https://github.com/Homebrew/install/blob/HEAD/install.sh}
def self.must_be_writable_directories
@must_be_writable_directories ||= (
%w[
etc/bash_completion.d lib/pkgconfig
share/aclocal share/doc share/info share/locale share/man
share/man/man1 share/man/man2 share/man/man3 share/man/man4
share/man/man5 share/man/man6 share/man/man7 share/man/man8
share/zsh share/zsh/site-functions
share/pwsh share/pwsh/completions
var/log
].map { |dir| HOMEBREW_PREFIX/dir } + must_exist_subdirectories + [
HOMEBREW_CACHE,
HOMEBREW_CELLAR,
HOMEBREW_LOCKS,
HOMEBREW_LOGS,
HOMEBREW_REPOSITORY,
Language::Python.homebrew_site_packages,
]
).sort.uniq.freeze
end
attr_reader :path, :name, :linked_keg_record, :opt_record
2014-06-26 19:06:31 -05:00
protected :path
extend Forwardable
def_delegators :path,
2024-04-26 14:04:55 +02:00
:to_path, :hash, :abv, :disk_usage, :file_count, :directory?, :exist?, :/,
2019-04-30 08:44:35 +01:00
:join, :rename, :find
sig { params(path: Pathname).void }
def initialize(path)
path = path.resolved_path if path.to_s.start_with?("#{HOMEBREW_PREFIX}/opt/")
raise "#{path} is not a valid keg" if path.parent.parent.realpath != HOMEBREW_CELLAR.realpath
2014-06-26 19:06:31 -05:00
raise "#{path} is not a directory" unless path.directory?
2018-09-17 02:45:00 +02:00
2014-06-26 19:06:31 -05:00
@path = path
@name = path.parent.basename.to_s
@linked_keg_record = HOMEBREW_LINKED_KEGS/name
@opt_record = HOMEBREW_PREFIX/"opt/#{name}"
2023-04-27 04:09:28 +01:00
@oldname_opt_records = []
@require_relocation = false
end
2015-05-17 20:34:31 +08:00
def rack
path.parent
end
2024-04-26 14:04:55 +02:00
sig { returns(String) }
def to_s = path.to_s
2014-06-26 19:06:31 -05:00
2020-10-20 12:03:48 +02:00
sig { returns(String) }
2014-06-26 19:06:31 -05:00
def inspect
"#<#{self.class.name}:#{path}>"
end
def ==(other)
instance_of?(other.class) && path == other.path
end
2016-09-23 18:13:48 +02:00
alias eql? ==
2014-06-26 19:06:31 -05:00
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
def empty_installation?
Pathname.glob("#{path}/*") do |file|
return false if file.directory? && !file.children.reject(&:ds_store?).empty?
2018-09-17 02:45:00 +02:00
basename = file.basename.to_s
require "metafiles"
next if Metafiles.copy?(basename)
next if %w[.DS_Store INSTALL_RECEIPT.json].include?(basename)
2018-09-17 02:45:00 +02:00
return false
end
true
end
def require_relocation?
@require_relocation
end
def linked?
linked_keg_record.symlink? &&
linked_keg_record.directory? &&
path == linked_keg_record.resolved_path
end
def remove_linked_keg_record
linked_keg_record.unlink
linked_keg_record.parent.rmdir_if_possible
end
def optlinked?
opt_record.symlink? && path == opt_record.resolved_path
end
def remove_old_aliases
opt = opt_record.parent
linkedkegs = linked_keg_record.parent
tap = begin
to_formula.tap
rescue
# If the formula can't be found, just ignore aliases for now.
nil
end
if tap
bad_tap_opt = opt/tap.user
FileUtils.rm_rf bad_tap_opt if !bad_tap_opt.symlink? && bad_tap_opt.directory?
end
aliases.each do |a|
# versioned aliases are handled below
next if a.match?(/.+@./)
2018-09-17 02:45:00 +02:00
remove_alias_symlink(opt/a, opt_record)
remove_alias_symlink(linkedkegs/a, linked_keg_record)
end
Pathname.glob("#{opt_record}@*").each do |a|
a = a.basename.to_s
next if aliases.include?(a)
remove_alias_symlink(opt/a, rack)
remove_alias_symlink(linkedkegs/a, rack)
end
end
def remove_opt_record
opt_record.unlink
opt_record.parent.rmdir_if_possible
end
def uninstall(raise_failures: false)
CacheStoreDatabase.use(:linkage) do |db|
break unless db.created?
2018-09-17 02:45:00 +02:00
LinkageCacheStore.new(path, db).delete!
end
FileUtils.rm_r(path)
2014-06-26 19:06:31 -05:00
path.parent.rmdir_if_possible
remove_opt_record if optlinked?
remove_linked_keg_record if linked?
remove_old_aliases
2023-04-27 04:09:28 +01:00
remove_oldname_opt_records
rescue Errno::EACCES, Errno::ENOTEMPTY
raise if raise_failures
odie <<~EOS
Could not remove #{name} keg! Do so manually:
sudo rm -rf #{path}
EOS
end
def unlink(verbose: false, dry_run: false)
ObserverPathnameExtension.reset_counts!
dirs = []
keg_directories = self.class.keg_link_directories.map { |d| path/d }
.select(&:exist?)
keg_directories.each do |dir|
2013-02-17 22:54:43 -06:00
dir.find do |src|
2014-06-26 19:06:31 -05:00
dst = HOMEBREW_PREFIX + src.relative_path_from(path)
dst.extend(ObserverPathnameExtension)
dirs << dst if dst.directory? && !dst.symlink?
# check whether the file to be unlinked is from the current keg first
next unless dst.symlink?
next if src != dst.resolved_path
2018-09-17 02:45:00 +02:00
if dry_run
puts dst
Find.prune if src.directory?
next
end
dst.uninstall_info if dst.to_s.match?(INFOFILE_RX)
dst.unlink
Find.prune if src.directory?
2012-03-25 13:08:58 +01:00
end
end
unless dry_run
remove_old_aliases
remove_linked_keg_record if linked?
(dirs - self.class.must_exist_subdirectories).reverse_each(&:rmdir_if_possible)
end
ObserverPathnameExtension.n
end
2023-04-27 04:09:28 +01:00
def lock
2015-08-18 19:33:24 +08:00
FormulaLock.new(name).with_lock do
2023-04-27 04:09:28 +01:00
oldname_locks = oldname_opt_records.map do |record|
FormulaLock.new(record.basename.to_s)
2015-08-18 19:33:24 +08:00
end
2023-04-27 04:09:28 +01:00
oldname_locks.each(&:lock)
yield
ensure
oldname_locks&.each(&:unlock)
2015-08-18 19:33:24 +08:00
end
end
def completion_installed?(shell)
dir = case shell
2017-06-01 16:06:51 +02:00
when :bash then path/"etc/bash_completion.d"
2024-02-13 18:46:58 +01:00
when :fish then path/"share/fish/vendor_completions.d"
when :zsh
dir = path/"share/zsh/site-functions"
dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") }
when :pwsh then path/"share/pwsh/completions"
2016-09-21 08:32:57 +02:00
end
2017-09-24 19:24:46 +01:00
dir&.directory? && !dir.children.empty?
end
def functions_installed?(shell)
case shell
when :fish
dir = path/"share/fish/vendor_functions.d"
dir.directory? && !dir.children.empty?
when :zsh
# Check for non completion functions (i.e. files not started with an underscore),
# since those can be checked separately
dir = path/"share/zsh/site-functions"
dir.directory? && dir.children.any? { |f| !f.basename.to_s.start_with?("_") }
end
end
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
def plist_installed?
!Dir["#{path}/*.plist"].empty?
end
2020-10-20 12:03:48 +02:00
sig { returns(T::Array[Pathname]) }
def apps
app_prefix = optlinked? ? opt_record : path
Pathname.glob("#{app_prefix}/{,libexec/}*.app")
end
def elisp_installed?
return false unless (path/"share/emacs/site-lisp"/name).exist?
2018-09-17 02:45:00 +02:00
2020-09-11 10:29:21 +01:00
(path/"share/emacs/site-lisp"/name).children.any? { |f| ELISP_EXTENSIONS.include? f.extname }
end
sig { returns(PkgVersion) }
def version
require "pkg_version"
2014-06-26 19:06:31 -05:00
PkgVersion.parse(path.basename.to_s)
end
2024-03-29 23:07:38 +00:00
def version_scheme
@version_scheme ||= tab.version_scheme
end
# For ordering kegs by version with `.sort_by`, `.max_by`, etc.
# @see Formula.version_scheme
def scheme_and_version
[version_scheme, version]
end
def to_formula
Formulary.from_keg(self)
end
2023-04-27 04:09:28 +01:00
def oldname_opt_records
return @oldname_opt_records unless @oldname_opt_records.empty?
@oldname_opt_records = if (opt_dir = HOMEBREW_PREFIX/"opt").directory?
opt_dir.subdirs.select do |dir|
dir.symlink? && dir != opt_record && path.parent == dir.resolved_path.parent
end
2023-04-27 04:09:28 +01:00
else
[]
end
end
def link(verbose: false, dry_run: false, overwrite: false)
raise AlreadyLinkedError, self if linked_keg_record.directory?
ObserverPathnameExtension.reset_counts!
2024-03-07 16:20:20 +00:00
optlink(verbose:, dry_run:, overwrite:) unless dry_run
# yeah indeed, you have to force anything you need in the main tree into
# these dirs REMEMBER that *NOT* everything needs to be in the main tree
2024-03-07 16:20:20 +00:00
link_dir("etc", verbose:, dry_run:, overwrite:) { :mkpath }
link_dir("bin", verbose:, dry_run:, overwrite:) { :skip_dir }
link_dir("sbin", verbose:, dry_run:, overwrite:) { :skip_dir }
link_dir("include", verbose:, dry_run:, overwrite:) do |relative_path|
case relative_path.to_s
when /^postgresql@\d+/
:mkpath
else
:link
end
end
2024-03-07 16:20:20 +00:00
link_dir("share", verbose:, dry_run:, overwrite:) do |relative_path|
case relative_path.to_s
when INFOFILE_RX then :info
2020-11-13 10:07:02 -05:00
when "locale/locale.alias",
%r{^icons/.*/icon-theme\.cache$}
:skip_file
when LOCALEDIR_RX,
%r{^icons/}, # all icons subfolders should also mkpath
/^zsh/,
/^fish/,
%r{^lua/}, # Lua, Lua51, Lua53 all need the same handling.
%r{^guile/},
/^postgresql@\d+/,
2020-11-13 10:07:02 -05:00
*SHARE_PATHS
:mkpath
2020-11-13 17:21:51 +01:00
else
:link
end
end
2009-07-29 00:56:22 +01:00
2024-03-07 16:20:20 +00:00
link_dir("lib", verbose:, dry_run:, overwrite:) do |relative_path|
case relative_path.to_s
2020-11-13 17:21:51 +01:00
when "charset.alias"
:skip_file
2020-11-13 10:07:02 -05:00
when "pkgconfig", # pkg-config database gets explicitly created
"cmake", # cmake database gets explicitly created
"dtrace", # lib/language folders also get explicitly created
/^gdk-pixbuf/,
"ghc",
/^gio/,
/^lua/,
2020-11-13 10:07:02 -05:00
/^mecab/,
/^node/,
/^ocaml/,
/^perl5/,
"php",
/^postgresql@\d+/,
2021-07-03 23:33:09 +01:00
/^python[23]\.\d+/,
2020-11-13 10:07:02 -05:00
/^R/,
2020-11-13 17:21:51 +01:00
/^ruby/
2020-11-13 10:07:02 -05:00
:mkpath
2020-11-13 17:21:51 +01:00
else
# Everything else is symlinked to the cellar
:link
end
end
2024-03-07 16:20:20 +00:00
link_dir("Frameworks", verbose:, dry_run:, overwrite:) do |relative_path|
# Frameworks contain symlinks pointing into a subdir, so we have to use
# the :link strategy. However, for Foo.framework and
# Foo.framework/Versions we have to use :mkpath so that multiple formulae
# can link their versions into it and `brew [un]link` works.
if relative_path.to_s.match?(%r{[^/]*\.framework(/Versions)?$})
:mkpath
else
:link
end
end
2024-03-07 16:20:20 +00:00
make_relative_symlink(linked_keg_record, path, verbose:, dry_run:, overwrite:) unless dry_run
2014-04-21 09:40:24 -05:00
rescue LinkError
2024-03-07 16:20:20 +00:00
unlink(verbose:)
raise
2014-04-21 09:40:24 -05:00
else
ObserverPathnameExtension.n
end
def prepare_debug_symbols; end
def consistent_reproducible_symlink_permissions!; end
2023-04-27 04:09:28 +01:00
def remove_oldname_opt_records
oldname_opt_records.reject! do |record|
return false if record.resolved_path != path
2018-09-17 02:45:00 +02:00
2023-04-27 04:09:28 +01:00
record.unlink
record.parent.rmdir_if_possible
true
end
end
sig { returns(Tab) }
def tab
Tab.for_keg(self)
end
def runtime_dependencies
2019-11-05 20:33:32 +00:00
Keg.cache[:runtime_dependencies] ||= {}
Keg.cache[:runtime_dependencies][path] ||= tab.runtime_dependencies
end
def aliases
tab.aliases || []
end
def optlink(verbose: false, dry_run: false, overwrite: false)
opt_record.delete if opt_record.symlink? || opt_record.exist?
2024-03-07 16:20:20 +00:00
make_relative_symlink(opt_record, path, verbose:, dry_run:, overwrite:)
aliases.each do |a|
alias_opt_record = opt_record.parent/a
alias_opt_record.delete if alias_opt_record.symlink? || alias_opt_record.exist?
2024-03-07 16:20:20 +00:00
make_relative_symlink(alias_opt_record, path, verbose:, dry_run:, overwrite:)
end
2023-04-27 04:09:28 +01:00
oldname_opt_records.each do |record|
record.delete
2024-03-07 16:20:20 +00:00
make_relative_symlink(record, path, verbose:, dry_run:, overwrite:)
2023-04-27 04:09:28 +01:00
end
end
2014-03-13 09:05:40 +00:00
def delete_pyc_files!
2023-03-21 21:42:51 -07:00
path.find { |pn| pn.delete if PYC_EXTENSIONS.include?(pn.extname) }
path.find { |pn| FileUtils.rm_rf pn if pn.basename.to_s == "__pycache__" }
2014-03-13 09:05:40 +00:00
end
def normalize_pod2man_outputs!
# Only process uncompressed manpages, which end in a digit
manpages = Dir[path/"share/man/*/*.[1-9]"]
generated_regex = /^\.\\"\s*Automatically generated by .*\n/
manpages.each do |f|
manpage = Pathname.new(f)
next unless manpage.file?
content = manpage.read
unless content.valid_encoding?
# Occasionally, a manpage might not be encoded as UTF-8. ISO-8859-1 is a
# common alternative that's worth trying in this case.
content = File.read(manpage, encoding: "ISO-8859-1")
# If the encoding is still invalid, we can't do anything about it.
next unless content.valid_encoding?
end
content = content.gsub(generated_regex, "")
content = content.lines.map do |line|
next line unless line.start_with?(".TH")
# Split the line by spaces, but preserve quoted strings
parts = line.split(/\s(?=(?:[^"]|"[^"]*")*$)/)
next line if parts.length != 6
# pod2man embeds the perl version used into the 5th field of the footer
T.must(parts[4]).gsub!(/^"perl v.*"$/, "\"\"")
"#{parts.join(" ")}\n"
end.join
manpage.atomic_write(content)
end
end
sig { returns(T::Array[String]) }
def keepme_refs
keepme = path/KEEPME_FILE
return [] if !keepme.exist? || !keepme.readable?
keepme.readlines.select { |ref| File.exist?(ref.strip) }
end
def binary_executable_or_library_files
[]
end
def codesign_patched_binary(file); end
private
def resolve_any_conflicts(dst, dry_run: false, verbose: false, overwrite: false)
return unless dst.symlink?
src = dst.resolved_path
2024-04-30 11:10:23 +02:00
# `src` itself may be a symlink, so check lstat to ensure we are dealing with
# a directory and not a symlink pointing to a directory (which needs to be
2014-07-12 20:15:57 -05:00
# treated as a file). In other words, we only want to resolve one symlink.
begin
stat = src.lstat
rescue Errno::ENOENT
# dst is a broken symlink, so remove it.
dst.unlink unless dry_run
return
end
2016-09-23 22:02:23 +02:00
return unless stat.directory?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
begin
keg = Keg.for(src)
rescue NotAKegError
2021-01-26 15:21:24 -05:00
puts "Won't resolve conflicts for symlink #{dst} as it doesn't resolve into the Cellar." if verbose
2016-09-23 22:02:23 +02:00
return
end
2016-09-23 22:02:23 +02:00
dst.unlink unless dry_run
keg.link_dir(src, dry_run: false, verbose: false, overwrite: false) { :mkpath }
2016-09-23 22:02:23 +02:00
true
end
def make_relative_symlink(dst, src, verbose: false, dry_run: false, overwrite: false)
if dst.symlink? && src == dst.resolved_path
puts "Skipping; link already exists: #{dst}" if verbose
return
end
# cf. git-clean -n: list files to delete, don't really link or delete
if dry_run && overwrite
if dst.symlink?
puts "#{dst} -> #{dst.resolved_path}"
elsif dst.exist?
puts dst
end
return
end
# list all link targets
if dry_run
puts dst
return
end
dst.delete if overwrite && (dst.exist? || dst.symlink?)
dst.make_relative_symlink(src)
rescue Errno::EEXIST => e
2016-09-23 22:02:23 +02:00
raise ConflictError.new(self, src.relative_path_from(path), dst, e) if dst.exist?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
if dst.symlink?
dst.unlink
retry
end
rescue Errno::EACCES => e
raise DirectoryNotWritableError.new(self, src.relative_path_from(path), dst, e)
rescue SystemCallError => e
raise LinkError.new(self, src.relative_path_from(path), dst, e)
end
def remove_alias_symlink(alias_symlink, alias_match_path)
if alias_symlink.symlink? && alias_symlink.exist?
alias_symlink.delete if alias_match_path.exist? && alias_symlink.realpath == alias_match_path.realpath
elsif alias_symlink.symlink? || alias_symlink.exist?
alias_symlink.delete
end
end
protected
2014-06-26 19:06:31 -05:00
# symlinks the contents of path+relative_dir recursively into #{HOMEBREW_PREFIX}/relative_dir
def link_dir(relative_dir, verbose: false, dry_run: false, overwrite: false)
2017-06-01 16:06:51 +02:00
root = path/relative_dir
return unless root.exist?
2018-09-17 02:45:00 +02:00
root.find do |src|
next if src == root
2018-09-17 02:45:00 +02:00
2014-06-26 19:06:31 -05:00
dst = HOMEBREW_PREFIX + src.relative_path_from(path)
dst.extend ObserverPathnameExtension
2014-07-12 19:56:58 -05:00
if src.symlink? || src.file?
Find.prune if File.basename(src) == ".DS_Store"
Find.prune if src.resolved_path == dst
# Don't link pyc or pyo files because Python overwrites these
# cached object files and next time brew wants to link, the
# file is in the way.
2020-09-11 10:29:21 +01:00
Find.prune if PYC_EXTENSIONS.include?(src.extname) && src.to_s.include?("/site-packages/")
case yield src.relative_path_from(root)
when :skip_file, nil
Find.prune
when :info
next if File.basename(src) == "dir" # skip historical local 'dir' files
2018-09-17 02:45:00 +02:00
2024-03-07 16:20:20 +00:00
make_relative_symlink(dst, src, verbose:, dry_run:, overwrite:)
dst.install_info
else
2024-03-07 16:20:20 +00:00
make_relative_symlink dst, src, verbose:, dry_run:, overwrite:
end
elsif src.directory?
# if the dst dir already exists, then great! walk the rest of the tree tho
next if dst.directory? && !dst.symlink?
2018-09-17 02:45:00 +02:00
# no need to put .app bundles in the path, the user can just use
# spotlight, or the open command and actual mac apps use an equivalent
Find.prune if src.extname == ".app"
case yield src.relative_path_from(root)
when :skip_dir
Find.prune
when :mkpath
2024-03-07 16:20:20 +00:00
dst.mkpath unless resolve_any_conflicts(dst, verbose:, dry_run:, overwrite:)
else
2024-03-07 16:20:20 +00:00
unless resolve_any_conflicts(dst, verbose:, dry_run:, overwrite:)
make_relative_symlink(dst, src, verbose:, dry_run:, overwrite:)
Find.prune
end
end
end
end
end
end
require "extend/os/keg"