Merge pull request #15025 from dduugg/enable-unpack-strategy-types

Enable UnpackStrategy types
This commit is contained in:
Douglas Eichelberger 2023-03-22 12:06:12 -07:00 committed by GitHub
commit 65d858da12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 115 additions and 210 deletions

View File

@ -0,0 +1,6 @@
# typed: strict
class Object
sig { returns(T::Boolean) }
def present?; end
end

View File

@ -11,8 +11,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
include SystemCommand::Mixin include SystemCommand::Mixin
using Magic
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) } sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
def extract_to_dir(unpack_dir, basename:, verbose:) def extract_to_dir(unpack_dir, basename:, verbose:)
with_env(TZ: "UTC") do with_env(TZ: "UTC") do

View File

@ -1,9 +1,5 @@
# typed: strict # typed: strict
module UnpackStrategy module UnpackStrategy::Zip::MacOSZipExtension
class Zip include Kernel
module MacOSZipExtension
include Kernel
end
end
end end

View File

@ -466,6 +466,32 @@ class Pathname
def rpaths def rpaths
[] []
end 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 end
require "extend/os/pathname" require "extend/os/pathname"

View File

@ -1,29 +1,8 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "system_command" 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. # Module containing all available strategies for unpacking archives.
# #
# @api private # @api private
@ -33,38 +12,6 @@ module UnpackStrategy
include SystemCommand::Mixin 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 def self.strategies
@strategies ||= [ @strategies ||= [
Tar, # Needs to be before Bzip2/Gzip/Xz/Lzma/Zstd. Tar, # Needs to be before Bzip2/Gzip/Xz/Lzma/Zstd.
@ -195,7 +142,7 @@ module UnpackStrategy
end end
# Ensure all extracted directories are writable. # Ensure all extracted directories are writable.
tmp_unpack_dir.each_directory do |path| each_directory(tmp_unpack_dir) do |path|
next if path.writable? next if path.writable?
FileUtils.chmod "u+w", path, verbose: verbose FileUtils.chmod "u+w", path, verbose: verbose
@ -208,6 +155,19 @@ module UnpackStrategy
def dependencies def dependencies
[] []
end 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 end
require "unpack_strategy/air" require "unpack_strategy/air"

View File

@ -3,14 +3,3 @@
module UnpackStrategy module UnpackStrategy
include Kernel include Kernel
end 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

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".air"] [".air"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Bazaar < Directory class Bazaar < Directory
extend T::Sig extend T::Sig
using Magic
def self.can_extract?(path) def self.can_extract?(path)
super && (path/".bzr").directory? super && (path/".bzr").directory?
end end

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".bz2"] [".bz2"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".cab"] [".cab"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Compress < Tar class Compress < Tar
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".Z"] [".Z"]

View File

@ -6,8 +6,6 @@ require_relative "directory"
module UnpackStrategy module UnpackStrategy
# Strategy for unpacking CVS repositories. # Strategy for unpacking CVS repositories.
class Cvs < Directory class Cvs < Directory
using Magic
def self.can_extract?(path) def self.can_extract?(path)
super && (path/"CVS").directory? super && (path/"CVS").directory?
end end

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[] []

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "tempfile" require "tempfile"
@ -12,6 +12,8 @@ module UnpackStrategy
# Helper module for listing the contents of a volume mounted from a disk image. # Helper module for listing the contents of a volume mounted from a disk image.
module Bom module Bom
extend T::Sig
DMG_METADATA = Set.new(%w[ DMG_METADATA = Set.new(%w[
.background .background
.com.apple.timemachine.donotpresent .com.apple.timemachine.donotpresent
@ -35,106 +37,100 @@ module UnpackStrategy
end end
end end
refine Pathname do # Check if path is considered disk image metadata.
extend T::Sig 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. # Check if path is a symlink to a system directory (commonly to /Applications).
sig { returns(T::Boolean) } sig { params(pathname: Pathname).returns(T::Boolean) }
def dmg_metadata? def self.system_dir_symlink?(pathname)
DMG_METADATA.include?(cleanpath.ascend.to_a.last.to_s) 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 end
# Check if path is a symlink to a system directory (commonly to /Applications). odebug "Command `#{result.command.shelljoin}` in '#{pathname}' took #{tries} tries." if tries > 1
sig { returns(T::Boolean) }
def system_dir_symlink?
symlink? && MacOS.system_dir?(dirname.join(readlink))
end
sig { returns(String) } bom_paths = result.stdout.split("\0")
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
# Spurious bug on CI, which in most cases can be worked around by retrying. raise EmptyError, pathname if bom_paths.empty?
break r unless r.stderr.match?(/Interrupted system call/i)
raise "Command `#{r.command.shelljoin}` was interrupted." if tries >= 3 bom_paths
end .reject { |path| dmg_metadata?(Pathname(path)) }
.reject { |path| system_dir_symlink?(pathname/path) }
odebug "Command `#{result.command.shelljoin}` in '#{self}' took #{tries} tries." if tries > 1 .join("\n")
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
end end
end end
private_constant :Bom
# Strategy for unpacking a volume mounted from a disk image. # Strategy for unpacking a volume mounted from a disk image.
class Mount class Mount
extend T::Sig extend T::Sig
using Bom
include UnpackStrategy include UnpackStrategy
def eject(verbose: false) 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 # For HFS, just use <mount-path>
disk_info = system_command!( # For APFS, find the <physical-store> corresponding to <mount-path>
"diskutil", eject_paths = disk_info.plist
args: ["info", "-plist", path], .fetch("APFSPhysicalStores", [])
print_stderr: false, .map { |store| store["APFSPhysicalStore"] }
verbose: verbose, .compact
) .presence || [path]
# For HFS, just use <mount-path> eject_paths.each do |eject_path|
# For APFS, find the <physical-store> corresponding to <mount-path> system_command! "diskutil",
eject_paths = disk_info.plist args: ["eject", eject_path],
.fetch("APFSPhysicalStores", []) print_stderr: false,
.map { |store| store["APFSPhysicalStore"] } verbose: verbose
.compact end
.presence || [path] else
eject_paths.each do |eject_path|
system_command! "diskutil", system_command! "diskutil",
args: ["eject", eject_path], args: ["unmount", "force", path],
print_stderr: false, print_stderr: false,
verbose: verbose verbose: verbose
end end
else rescue ErrorDuringExecution => e
system_command! "diskutil", raise e if (tries -= 1).zero?
args: ["unmount", "force", path],
print_stderr: false,
verbose: verbose
end
rescue ErrorDuringExecution => e
raise e if (tries -= 1).zero?
sleep 1 sleep 1
retry retry
end
end end
private private
sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) } sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) }
def extract_to_dir(unpack_dir, basename:, verbose:) def extract_to_dir(unpack_dir, basename:, verbose:)
tries = 3
bom = begin bom = begin
tries ||= 3 Bom.bom(path)
path.bom
rescue Bom::EmptyError => e rescue Bom::EmptyError => e
raise e if (tries -= 1).zero? raise e if (tries -= 1).zero?

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Executable < Uncompressed class Executable < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".sh", ".bash"] [".sh", ".bash"]

View File

@ -11,8 +11,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
extend SystemCommand::Mixin extend SystemCommand::Mixin
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[] []

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[] []

View File

@ -6,8 +6,6 @@ require_relative "directory"
module UnpackStrategy module UnpackStrategy
# Strategy for unpacking Git repositories. # Strategy for unpacking Git repositories.
class Git < Directory class Git < Directory
using Magic
def self.can_extract?(path) def self.can_extract?(path)
super && (path/".git").directory? super && (path/".git").directory?
end end

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".gz"] [".gz"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Jar < Uncompressed class Jar < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".apk", ".jar"] [".apk", ".jar"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".lha", ".lzh"] [".lha", ".lzh"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class LuaRock < Uncompressed class LuaRock < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".rock"] [".rock"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".lz"] [".lz"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".lzma"] [".lzma"]

View File

@ -6,8 +6,6 @@ require_relative "directory"
module UnpackStrategy module UnpackStrategy
# Strategy for unpacking Mercurial repositories. # Strategy for unpacking Mercurial repositories.
class Mercurial < Directory class Mercurial < Directory
using Magic
def self.can_extract?(path) def self.can_extract?(path)
super && (path/".hg").directory? super && (path/".hg").directory?
end end

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class MicrosoftOfficeXml < Uncompressed class MicrosoftOfficeXml < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[ [

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Otf < Uncompressed class Otf < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".otf"] [".otf"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".7z"] [".7z"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".pax"] [".pax"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Pkg < Uncompressed class Pkg < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".pkg", ".mkpg"] [".pkg", ".mkpg"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".rar"] [".rar"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class SelfExtractingExecutable < GenericUnar class SelfExtractingExecutable < GenericUnar
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[] []

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Sit < GenericUnar class Sit < GenericUnar
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".sit"] [".sit"]

View File

@ -6,8 +6,6 @@ require_relative "directory"
module UnpackStrategy module UnpackStrategy
# Strategy for unpacking Subversion repositories. # Strategy for unpacking Subversion repositories.
class Subversion < Directory class Subversion < Directory
using Magic
def self.can_extract?(path) def self.can_extract?(path)
super && (path/".svn").directory? super && (path/".svn").directory?
end end

View File

@ -11,8 +11,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
extend SystemCommand::Mixin extend SystemCommand::Mixin
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[ [

View File

@ -8,8 +8,6 @@ module UnpackStrategy
class Ttf < Uncompressed class Ttf < Uncompressed
extend T::Sig extend T::Sig
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".ttc", ".ttf"] [".ttc", ".ttf"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".xar"] [".xar"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".xz"] [".xz"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".zip"] [".zip"]

View File

@ -8,8 +8,6 @@ module UnpackStrategy
include UnpackStrategy include UnpackStrategy
using Magic
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def self.extensions def self.extensions
[".zst"] [".zst"]