mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Merge pull request #15025 from dduugg/enable-unpack-strategy-types
Enable UnpackStrategy types
This commit is contained in:
commit
65d858da12
6
Library/Homebrew/extend/object.rbi
Normal file
6
Library/Homebrew/extend/object.rbi
Normal file
@ -0,0 +1,6 @@
|
||||
# typed: strict
|
||||
|
||||
class Object
|
||||
sig { returns(T::Boolean) }
|
||||
def present?; end
|
||||
end
|
@ -11,8 +11,6 @@ module UnpackStrategy
|
||||
include UnpackStrategy
|
||||
include SystemCommand::Mixin
|
||||
|
||||
using Magic
|
||||
|
||||
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
|
||||
def extract_to_dir(unpack_dir, basename:, verbose:)
|
||||
with_env(TZ: "UTC") do
|
||||
|
@ -1,9 +1,5 @@
|
||||
# typed: strict
|
||||
|
||||
module UnpackStrategy
|
||||
class Zip
|
||||
module MacOSZipExtension
|
||||
include Kernel
|
||||
end
|
||||
end
|
||||
module UnpackStrategy::Zip::MacOSZipExtension
|
||||
include Kernel
|
||||
end
|
||||
|
@ -466,6 +466,32 @@ class Pathname
|
||||
def rpaths
|
||||
[]
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def magic_number
|
||||
@magic_number ||= if directory?
|
||||
""
|
||||
else
|
||||
# Length of the longest regex (currently Tar).
|
||||
max_magic_number_length = 262
|
||||
# FIXME: The `T.let` is a workaround until we have https://github.com/sorbet/sorbet/pull/6865
|
||||
T.let(binread(max_magic_number_length), T.nilable(String)) || ""
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def file_type
|
||||
@file_type ||= system_command("file", args: ["-b", self], print_stderr: false)
|
||||
.stdout.chomp
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def zipinfo
|
||||
@zipinfo ||= system_command("zipinfo", args: ["-1", self], print_stderr: false)
|
||||
.stdout
|
||||
.encode(Encoding::UTF_8, invalid: :replace)
|
||||
.split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
require "extend/os/pathname"
|
||||
|
@ -1,29 +1,8 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "system_command"
|
||||
|
||||
# Helper module for iterating over directory trees.
|
||||
#
|
||||
# @api private
|
||||
module PathnameEachDirectory
|
||||
refine Pathname do
|
||||
extend T::Sig
|
||||
|
||||
sig {
|
||||
type_parameters(:T)
|
||||
.params(
|
||||
_block: T.proc.params(path: Pathname).returns(T.type_parameter(:T)),
|
||||
).returns(T.type_parameter(:T))
|
||||
}
|
||||
def each_directory(&_block)
|
||||
find do |path|
|
||||
yield path if path.directory?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Module containing all available strategies for unpacking archives.
|
||||
#
|
||||
# @api private
|
||||
@ -33,38 +12,6 @@ module UnpackStrategy
|
||||
|
||||
include SystemCommand::Mixin
|
||||
|
||||
using PathnameEachDirectory
|
||||
|
||||
# Helper module for identifying the file type.
|
||||
module Magic
|
||||
# Length of the longest regex (currently Tar).
|
||||
MAX_MAGIC_NUMBER_LENGTH = 262
|
||||
private_constant :MAX_MAGIC_NUMBER_LENGTH
|
||||
|
||||
refine Pathname do
|
||||
def magic_number
|
||||
@magic_number ||= if directory?
|
||||
""
|
||||
else
|
||||
binread(MAX_MAGIC_NUMBER_LENGTH) || ""
|
||||
end
|
||||
end
|
||||
|
||||
def file_type
|
||||
@file_type ||= system_command("file", args: ["-b", self], print_stderr: false)
|
||||
.stdout.chomp
|
||||
end
|
||||
|
||||
def zipinfo
|
||||
@zipinfo ||= system_command("zipinfo", args: ["-1", self], print_stderr: false)
|
||||
.stdout
|
||||
.encode(Encoding::UTF_8, invalid: :replace)
|
||||
.split("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
private_constant :Magic
|
||||
|
||||
def self.strategies
|
||||
@strategies ||= [
|
||||
Tar, # Needs to be before Bzip2/Gzip/Xz/Lzma/Zstd.
|
||||
@ -195,7 +142,7 @@ module UnpackStrategy
|
||||
end
|
||||
|
||||
# Ensure all extracted directories are writable.
|
||||
tmp_unpack_dir.each_directory do |path|
|
||||
each_directory(tmp_unpack_dir) do |path|
|
||||
next if path.writable?
|
||||
|
||||
FileUtils.chmod "u+w", path, verbose: verbose
|
||||
@ -208,6 +155,19 @@ module UnpackStrategy
|
||||
def dependencies
|
||||
[]
|
||||
end
|
||||
|
||||
# Helper method for iterating over directory trees.
|
||||
sig {
|
||||
params(
|
||||
pathname: Pathname,
|
||||
_block: T.proc.params(path: Pathname).void,
|
||||
).returns(T.nilable(Pathname))
|
||||
}
|
||||
def each_directory(pathname, &_block)
|
||||
pathname.find do |path|
|
||||
yield path if path.directory?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "unpack_strategy/air"
|
||||
|
@ -3,14 +3,3 @@
|
||||
module UnpackStrategy
|
||||
include Kernel
|
||||
end
|
||||
|
||||
class Pathname
|
||||
sig { returns(String) }
|
||||
def magic_number; end
|
||||
|
||||
sig { returns(String) }
|
||||
def file_type; end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def zipinfo; end
|
||||
end
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".air"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Bazaar < Directory
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
def self.can_extract?(path)
|
||||
super && (path/".bzr").directory?
|
||||
end
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".bz2"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".cab"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Compress < Tar
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".Z"]
|
||||
|
@ -6,8 +6,6 @@ require_relative "directory"
|
||||
module UnpackStrategy
|
||||
# Strategy for unpacking CVS repositories.
|
||||
class Cvs < Directory
|
||||
using Magic
|
||||
|
||||
def self.can_extract?(path)
|
||||
super && (path/"CVS").directory?
|
||||
end
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# typed: false
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "tempfile"
|
||||
@ -12,6 +12,8 @@ module UnpackStrategy
|
||||
|
||||
# Helper module for listing the contents of a volume mounted from a disk image.
|
||||
module Bom
|
||||
extend T::Sig
|
||||
|
||||
DMG_METADATA = Set.new(%w[
|
||||
.background
|
||||
.com.apple.timemachine.donotpresent
|
||||
@ -35,106 +37,100 @@ module UnpackStrategy
|
||||
end
|
||||
end
|
||||
|
||||
refine Pathname do
|
||||
extend T::Sig
|
||||
# Check if path is considered disk image metadata.
|
||||
sig { params(pathname: Pathname).returns(T::Boolean) }
|
||||
def self.dmg_metadata?(pathname)
|
||||
DMG_METADATA.include?(pathname.cleanpath.ascend.to_a.last.to_s)
|
||||
end
|
||||
|
||||
# Check if path is considered disk image metadata.
|
||||
sig { returns(T::Boolean) }
|
||||
def dmg_metadata?
|
||||
DMG_METADATA.include?(cleanpath.ascend.to_a.last.to_s)
|
||||
# Check if path is a symlink to a system directory (commonly to /Applications).
|
||||
sig { params(pathname: Pathname).returns(T::Boolean) }
|
||||
def self.system_dir_symlink?(pathname)
|
||||
pathname.symlink? && MacOS.system_dir?(pathname.dirname.join(pathname.readlink))
|
||||
end
|
||||
|
||||
sig { params(pathname: Pathname).returns(String) }
|
||||
def self.bom(pathname)
|
||||
tries = 0
|
||||
result = loop do
|
||||
# We need to use `find` here instead of Ruby in order to properly handle
|
||||
# file names containing special characters, such as “e” + “´” vs. “é”.
|
||||
r = system_command("find", args: [".", "-print0"], chdir: pathname, print_stderr: false)
|
||||
tries += 1
|
||||
|
||||
# Spurious bug on CI, which in most cases can be worked around by retrying.
|
||||
break r unless r.stderr.match?(/Interrupted system call/i)
|
||||
|
||||
raise "Command `#{r.command.shelljoin}` was interrupted." if tries >= 3
|
||||
end
|
||||
|
||||
# Check if path is a symlink to a system directory (commonly to /Applications).
|
||||
sig { returns(T::Boolean) }
|
||||
def system_dir_symlink?
|
||||
symlink? && MacOS.system_dir?(dirname.join(readlink))
|
||||
end
|
||||
odebug "Command `#{result.command.shelljoin}` in '#{pathname}' took #{tries} tries." if tries > 1
|
||||
|
||||
sig { returns(String) }
|
||||
def bom
|
||||
tries = 0
|
||||
result = loop do
|
||||
# We need to use `find` here instead of Ruby in order to properly handle
|
||||
# file names containing special characters, such as “e” + “´” vs. “é”.
|
||||
r = system_command("find", args: [".", "-print0"], chdir: self, print_stderr: false)
|
||||
tries += 1
|
||||
bom_paths = result.stdout.split("\0")
|
||||
|
||||
# Spurious bug on CI, which in most cases can be worked around by retrying.
|
||||
break r unless r.stderr.match?(/Interrupted system call/i)
|
||||
raise EmptyError, pathname if bom_paths.empty?
|
||||
|
||||
raise "Command `#{r.command.shelljoin}` was interrupted." if tries >= 3
|
||||
end
|
||||
|
||||
odebug "Command `#{result.command.shelljoin}` in '#{self}' took #{tries} tries." if tries > 1
|
||||
|
||||
bom_paths = result.stdout.split("\0")
|
||||
|
||||
raise EmptyError, self if bom_paths.empty?
|
||||
|
||||
bom_paths
|
||||
.reject { |path| Pathname(path).dmg_metadata? }
|
||||
.reject { |path| (self/path).system_dir_symlink? }
|
||||
.join("\n")
|
||||
end
|
||||
bom_paths
|
||||
.reject { |path| dmg_metadata?(Pathname(path)) }
|
||||
.reject { |path| system_dir_symlink?(pathname/path) }
|
||||
.join("\n")
|
||||
end
|
||||
end
|
||||
private_constant :Bom
|
||||
|
||||
# Strategy for unpacking a volume mounted from a disk image.
|
||||
class Mount
|
||||
extend T::Sig
|
||||
|
||||
using Bom
|
||||
include UnpackStrategy
|
||||
|
||||
def eject(verbose: false)
|
||||
tries ||= 3
|
||||
tries = 3
|
||||
begin
|
||||
return unless path.exist?
|
||||
|
||||
return unless path.exist?
|
||||
if tries > 1
|
||||
disk_info = system_command!(
|
||||
"diskutil",
|
||||
args: ["info", "-plist", path],
|
||||
print_stderr: false,
|
||||
verbose: verbose,
|
||||
)
|
||||
|
||||
if tries > 1
|
||||
disk_info = system_command!(
|
||||
"diskutil",
|
||||
args: ["info", "-plist", path],
|
||||
print_stderr: false,
|
||||
verbose: verbose,
|
||||
)
|
||||
# For HFS, just use <mount-path>
|
||||
# For APFS, find the <physical-store> corresponding to <mount-path>
|
||||
eject_paths = disk_info.plist
|
||||
.fetch("APFSPhysicalStores", [])
|
||||
.map { |store| store["APFSPhysicalStore"] }
|
||||
.compact
|
||||
.presence || [path]
|
||||
|
||||
# For HFS, just use <mount-path>
|
||||
# For APFS, find the <physical-store> corresponding to <mount-path>
|
||||
eject_paths = disk_info.plist
|
||||
.fetch("APFSPhysicalStores", [])
|
||||
.map { |store| store["APFSPhysicalStore"] }
|
||||
.compact
|
||||
.presence || [path]
|
||||
|
||||
eject_paths.each do |eject_path|
|
||||
eject_paths.each do |eject_path|
|
||||
system_command! "diskutil",
|
||||
args: ["eject", eject_path],
|
||||
print_stderr: false,
|
||||
verbose: verbose
|
||||
end
|
||||
else
|
||||
system_command! "diskutil",
|
||||
args: ["eject", eject_path],
|
||||
args: ["unmount", "force", path],
|
||||
print_stderr: false,
|
||||
verbose: verbose
|
||||
end
|
||||
else
|
||||
system_command! "diskutil",
|
||||
args: ["unmount", "force", path],
|
||||
print_stderr: false,
|
||||
verbose: verbose
|
||||
end
|
||||
rescue ErrorDuringExecution => e
|
||||
raise e if (tries -= 1).zero?
|
||||
rescue ErrorDuringExecution => e
|
||||
raise e if (tries -= 1).zero?
|
||||
|
||||
sleep 1
|
||||
retry
|
||||
sleep 1
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
|
||||
def extract_to_dir(unpack_dir, basename:, verbose:)
|
||||
tries = 3
|
||||
bom = begin
|
||||
tries ||= 3
|
||||
|
||||
path.bom
|
||||
Bom.bom(path)
|
||||
rescue Bom::EmptyError => e
|
||||
raise e if (tries -= 1).zero?
|
||||
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Executable < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".sh", ".bash"]
|
||||
|
@ -11,8 +11,6 @@ module UnpackStrategy
|
||||
include UnpackStrategy
|
||||
extend SystemCommand::Mixin
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[]
|
||||
|
@ -6,8 +6,6 @@ require_relative "directory"
|
||||
module UnpackStrategy
|
||||
# Strategy for unpacking Git repositories.
|
||||
class Git < Directory
|
||||
using Magic
|
||||
|
||||
def self.can_extract?(path)
|
||||
super && (path/".git").directory?
|
||||
end
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".gz"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Jar < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".apk", ".jar"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".lha", ".lzh"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class LuaRock < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".rock"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".lz"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".lzma"]
|
||||
|
@ -6,8 +6,6 @@ require_relative "directory"
|
||||
module UnpackStrategy
|
||||
# Strategy for unpacking Mercurial repositories.
|
||||
class Mercurial < Directory
|
||||
using Magic
|
||||
|
||||
def self.can_extract?(path)
|
||||
super && (path/".hg").directory?
|
||||
end
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class MicrosoftOfficeXml < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Otf < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".otf"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".7z"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".pax"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Pkg < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".pkg", ".mkpg"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".rar"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class SelfExtractingExecutable < GenericUnar
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Sit < GenericUnar
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".sit"]
|
||||
|
@ -6,8 +6,6 @@ require_relative "directory"
|
||||
module UnpackStrategy
|
||||
# Strategy for unpacking Subversion repositories.
|
||||
class Subversion < Directory
|
||||
using Magic
|
||||
|
||||
def self.can_extract?(path)
|
||||
super && (path/".svn").directory?
|
||||
end
|
||||
|
@ -11,8 +11,6 @@ module UnpackStrategy
|
||||
include UnpackStrategy
|
||||
extend SystemCommand::Mixin
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
class Ttf < Uncompressed
|
||||
extend T::Sig
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".ttc", ".ttf"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".xar"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".xz"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".zip"]
|
||||
|
@ -8,8 +8,6 @@ module UnpackStrategy
|
||||
|
||||
include UnpackStrategy
|
||||
|
||||
using Magic
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.extensions
|
||||
[".zst"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user