Merge pull request #4474 from reitermarkus/refactor-containers

Refactor containers and automatically determine dependencies.
This commit is contained in:
Markus Reiter 2018-07-16 08:40:38 +02:00 committed by GitHub
commit f92e3086ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 181 additions and 155 deletions

View File

@ -6,7 +6,6 @@ require "hbc/container/bzip2"
require "hbc/container/cab"
require "hbc/container/criteria"
require "hbc/container/dmg"
require "hbc/container/directory"
require "hbc/container/executable"
require "hbc/container/generic_unar"
require "hbc/container/gpg"

View File

@ -3,34 +3,21 @@ require "hbc/container/base"
module Hbc
class Container
class Air < Base
INSTALLER_PATHNAME =
Pathname("/Applications/Utilities/Adobe AIR Application Installer.app" \
"/Contents/MacOS/Adobe AIR Application Installer")
def self.me?(criteria)
%w[.air].include?(criteria.path.extname)
end
def self.installer_cmd
return @installer_cmd ||= INSTALLER_PATHNAME if installer_exist?
raise CaskError, <<~EOS
Adobe AIR runtime not present, try installing it via
brew cask install adobe-air
EOS
end
def self.installer_exist?
INSTALLER_PATHNAME.exist?
criteria.path.extname == ".air"
end
def extract
install = @command.run(self.class.installer_cmd,
args: ["-silent", "-location", @cask.staged_path, Pathname.new(@path).realpath])
unpack_dir = @cask.staged_path
return unless install.exit_status == 9
raise CaskError, "Adobe AIR application #{@cask} already exists on the system, and cannot be reinstalled."
@command.run!(
"/Applications/Utilities/Adobe AIR Application Installer.app/Contents/MacOS/Adobe AIR Application Installer",
args: ["-silent", "-location", unpack_dir, path],
)
end
def dependencies
@dependencies ||= [CaskLoader.load("adobe-air")]
end
end
end

View File

@ -1,6 +1,8 @@
module Hbc
class Container
class Base
attr_reader :path
def initialize(cask, path, command, nested: false, verbose: false)
@cask = cask
@path = path
@ -41,6 +43,10 @@ module Hbc
true
end
def dependencies
[]
end
end
end
end

View File

@ -4,13 +4,13 @@ module Hbc
class Container
class Bzip2 < Base
def self.me?(criteria)
criteria.magic_number(/^BZh/n)
criteria.magic_number(/\ABZh/n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("bunzip2", args: ["--quiet", "--", Pathname.new(unpack_dir).join(@path.basename)])
extract_nested_inside(unpack_dir)
end

View File

@ -4,19 +4,21 @@ module Hbc
class Container
class Cab < Base
def self.me?(criteria)
criteria.magic_number(/^(MSCF|MZ)/n)
criteria.magic_number(/\A(MSCF|MZ)/n)
end
def extract
unless cabextract = which("cabextract", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
raise CaskError, "Expected to find cabextract executable. Cask '#{@cask}' must add: depends_on formula: 'cabextract'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!(cabextract, args: ["-d", unpack_dir, "--", @path])
@command.run!("cabextract",
args: ["-d", unpack_dir, "--", @path],
env: { "PATH" => PATH.new(Formula["cabextract"].opt_bin, ENV["PATH"]) })
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
def dependencies
@dependencies ||= [Formula["cabextract"]]
end
end
end
end

View File

@ -16,7 +16,7 @@ module Hbc
return false if path.directory?
# 262: length of the longest regex (currently: Hbc::Container::Tar)
@magic_number ||= File.open(path, "rb") { |f| f.read(262) }
@magic_number ||= File.binread(path, 262)
@magic_number.match?(regex)
end
end

View File

@ -1,24 +0,0 @@
require "hbc/container/base"
module Hbc
class Container
class Directory < Base
def self.me?(*)
false
end
def extract
@path.children.each do |child|
next if skip_path?(child)
FileUtils.cp child, @cask.staged_path
end
end
private
def skip_path?(*)
false
end
end
end
end

View File

@ -15,7 +15,7 @@ module Hbc
def extract
mount do |mounts|
begin
raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if mounts.empty?
raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad disk image?" if mounts.empty?
mounts.each(&method(:extract_mount))
ensure
mounts.each(&method(:eject))

View File

@ -5,7 +5,7 @@ module Hbc
class Container
class Executable < Naked
def self.me?(criteria)
return true if criteria.magic_number(/^#!\s*\S+/)
return true if criteria.magic_number(/\A#!\s*\S+/n)
begin
criteria.path.file? && MachO.open(criteria.path).header.executable?

View File

@ -3,22 +3,20 @@ require "hbc/container/base"
module Hbc
class Container
class GenericUnar < Base
def self.me?(criteria)
return false unless lsar = which("lsar", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
criteria.command.run(lsar,
args: ["-l", "-t", "--", criteria.path],
print_stderr: false).stdout.chomp.end_with?("passed, 0 failed.")
def self.me?(_criteria)
false
end
def extract
unless unar = which("unar", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
raise CaskError, "Expected to find unar executable. Cask #{@cask} must add: depends_on formula: 'unar'"
end
unpack_dir = @cask.staged_path
Dir.mktmpdir do |unpack_dir|
@command.run!(unar, args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", @path])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
@command.run!("unar",
args: ["-force-overwrite", "-quiet", "-no-directory", "-output-directory", unpack_dir, "--", path],
env: { "PATH" => PATH.new(Formula["unar"].opt_bin, ENV["PATH"]) })
end
def dependencies
@dependencies ||= [Formula["unar"]]
end
end
end

View File

@ -18,22 +18,26 @@ module Hbc
["--fetch-key", @cask.gpg.key_url.to_s]
end
@command.run!("gpg", args: args)
@command.run!("gpg",
args: args,
env: { "PATH" => PATH.new(Formula["gnupg"].opt_bin, ENV["PATH"]) })
end
def extract
unless gpg = which("gpg", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
raise CaskError, "Expected to find gpg executable. Cask '#{@cask}' must add: depends_on formula: 'gpg'"
end
import_key
Dir.mktmpdir do |unpack_dir|
@command.run!(gpg, args: ["--batch", "--yes", "--output", Pathname(unpack_dir).join(@path.basename(".gpg")), "--decrypt", @path])
@command.run!("gpg",
args: ["--batch", "--yes", "--output", Pathname(unpack_dir).join(@path.basename(".gpg")), "--decrypt", @path],
env: { "PATH" => PATH.new(Formula["gnupg"].opt_bin, ENV["PATH"]) })
extract_nested_inside(unpack_dir)
end
end
def dependencies
@dependencies ||= [Formula["gnupg"]]
end
end
end
end

View File

@ -4,13 +4,13 @@ module Hbc
class Container
class Gzip < Base
def self.me?(criteria)
criteria.magic_number(/^\037\213/n)
criteria.magic_number(/\A\037\213/n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("/usr/bin/gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!("gunzip", args: ["--quiet", "--name", "--", Pathname.new(unpack_dir).join(@path.basename)])
extract_nested_inside(unpack_dir)
end

View File

@ -4,20 +4,22 @@ module Hbc
class Container
class Lzma < Base
def self.me?(criteria)
criteria.magic_number(/^\]\000\000\200\000/n)
criteria.magic_number(/\A\]\000\000\200\000/n)
end
def extract
unless unlzma = which("unlzma", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
raise CaskError, "Expected to find unlzma executable. Cask '#{@cask}' must add: depends_on formula: 'lzma'"
end
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unlzma, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("unlzma",
args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)],
env: { "PATH" => PATH.new(Formula["unlzma"].opt_bin, ENV["PATH"]) })
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
end
def dependencies
@dependencies ||= [Formula["unlzma"]]
end
end
end
end

View File

@ -4,7 +4,7 @@ module Hbc
class Container
class Otf < Naked
def self.me?(criteria)
criteria.magic_number(/^OTTO/n)
criteria.magic_number(/\AOTTO/n)
end
end
end

View File

@ -5,8 +5,7 @@ module Hbc
class Pkg < Naked
def self.me?(criteria)
criteria.extension(/m?pkg$/) &&
(criteria.path.directory? ||
criteria.magic_number(/^xar!/n))
(criteria.path.directory? || criteria.magic_number(/\Axar!/n))
end
end
end

View File

@ -2,11 +2,21 @@ require "hbc/container/generic_unar"
module Hbc
class Container
class Rar < GenericUnar
class Rar
def self.me?(criteria)
criteria.magic_number(/^Rar!/n) &&
criteria.magic_number(/\ARar!/n) &&
super
end
def extract
path = @path
unpack_dir = @cask.staged_path
@command.run!(Formula["unrar"].opt_bin/"unrar", args: ["x", "-inul", path, unpack_dir])
end
def dependencies
@dependencies ||= [Formula["unrar"]]
end
end
end
end

View File

@ -4,9 +4,19 @@ module Hbc
class Container
class SevenZip < GenericUnar
def self.me?(criteria)
# TODO: cover self-extracting archives
criteria.magic_number(/^7z/n) &&
super
criteria.magic_number(/\A7z\xBC\xAF\x27\x1C/n)
end
def extract
unpack_dir = @cask.staged_path
@command.run!("7zr",
args: ["x", "-y", "-bd", "-bso0", path, "-o#{unpack_dir}"],
env: { "PATH" => PATH.new(Formula["p7zip"].opt_bin, ENV["PATH"]) })
end
def dependencies
@dependencies ||= [Formula["p7zip"]]
end
end
end

View File

@ -4,8 +4,7 @@ module Hbc
class Container
class Sit < GenericUnar
def self.me?(criteria)
criteria.magic_number(/^StuffIt/n) &&
super
criteria.magic_number(/\AStuffIt/n)
end
end
end

View File

@ -1,14 +1,15 @@
require "hbc/container/directory"
module Hbc
class Container
class SvnRepository < Directory
class SvnRepository < Base
def self.me?(criteria)
criteria.path.join(".svn").directory?
end
def skip_path?(path)
path.basename.to_s == ".svn"
def extract
path = @path
unpack_dir = @cask.staged_path
@command.run!("svn", args: ["export", "--force", path, unpack_dir])
end
end
end

View File

@ -4,16 +4,15 @@ module Hbc
class Container
class Tar < Base
def self.me?(criteria)
criteria.magic_number(/^.{257}ustar/n) ||
criteria.magic_number(/\A.{257}ustar/n) ||
# or compressed tar (bzip2/gzip/lzma/xz)
IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: "/dev/null") { |io| !io.read(1).nil? }
IO.popen(["/usr/bin/tar", "-t", "-f", criteria.path.to_s], err: File::NULL) { |io| !io.read(1).nil? }
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/tar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
unpack_dir = @cask.staged_path
@command.run!("tar", args: ["xf", path, "-C", unpack_dir])
end
end
end

View File

@ -5,9 +5,9 @@ module Hbc
class Ttf < Naked
def self.me?(criteria)
# TrueType Font
criteria.magic_number(/^\000\001\000\000\000/n) ||
criteria.magic_number(/\A\000\001\000\000\000/n) ||
# Truetype Font Collection
criteria.magic_number(/^ttcf/n)
criteria.magic_number(/\Attcf/n)
end
end
end

View File

@ -4,14 +4,13 @@ module Hbc
class Container
class Xar < Base
def self.me?(criteria)
criteria.magic_number(/^xar!/n)
criteria.magic_number(/\Axar!/n)
end
def extract
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/xar", args: ["-x", "-f", @path, "-C", unpack_dir])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
unpack_dir = @cask.staged_path
@command.run!("xar", args: ["-x", "-f", @path, "-C", unpack_dir])
end
end
end

View File

@ -4,19 +4,21 @@ module Hbc
class Container
class Xz < Base
def self.me?(criteria)
criteria.magic_number(/^\xFD7zXZ\x00/n)
criteria.magic_number(/\A\xFD7zXZ\x00/n)
end
def extract
unless unxz = which("unxz", PATH.new(ENV["PATH"], HOMEBREW_PREFIX/"bin"))
raise CaskError, "Expected to find unxz executable. Cask '#{@cask}' must add: depends_on formula: 'xz'"
end
unpack_dir = @cask.staged_path
basename = path.basename
Dir.mktmpdir do |unpack_dir|
@command.run!("/usr/bin/ditto", args: ["--", @path, unpack_dir])
@command.run!(unxz, args: ["-q", "--", Pathname(unpack_dir).join(@path.basename)])
@command.run!("/usr/bin/ditto", args: ["--", unpack_dir, @cask.staged_path])
end
@command.run!("/usr/bin/ditto", args: ["--", path, unpack_dir])
@command.run!("xz",
args: ["-q", "--", unpack_dir/basename],
env: { "PATH" => PATH.new(Formula["xz"].opt_bin, ENV["PATH"]) })
end
def dependencies
@dependencies ||= [Formula["xz"]]
end
end
end

View File

@ -4,7 +4,7 @@ module Hbc
class Container
class Zip < Base
def self.me?(criteria)
criteria.magic_number(/^PK(\003\004|\005\006)/n)
criteria.magic_number(/\APK(\003\004|\005\006)/n)
end
def extract

View File

@ -19,7 +19,7 @@ module Hbc
PERSISTENT_METADATA_SUBDIRS = ["gpg"].freeze
def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false, upgrade: false)
def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false, upgrade: false, installed_as_dependency: false)
@cask = cask
@command = command
@force = force
@ -29,9 +29,10 @@ module Hbc
@require_sha = require_sha
@reinstall = false
@upgrade = upgrade
@installed_as_dependency = installed_as_dependency
end
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?, :upgrade?, :verbose?
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?, :upgrade?, :verbose?, :installed_as_dependency?
def self.print_caveats(cask)
odebug "Printing caveats"
@ -47,6 +48,7 @@ module Hbc
odebug "Hbc::Installer#fetch"
satisfy_dependencies
verify_has_sha if require_sha? && !force?
download
verify
@ -57,6 +59,8 @@ module Hbc
Caskroom.ensure_caskroom_exists
unpack_dependencies
extract_primary_container
save_caskfile
rescue StandardError => e
@ -140,22 +144,28 @@ module Hbc
Verify.all(@cask, @downloaded_path)
end
def primary_container
@primary_container ||= begin
container = if @cask.container&.type
Container.from_type(@cask.container.type)
else
Container.for_path(@downloaded_path, @command)
end
container&.new(@cask, @downloaded_path, @command, verbose: verbose?)
end
end
def extract_primary_container
odebug "Extracting primary container"
FileUtils.mkdir_p @cask.staged_path
container = if @cask.container&.type
Container.from_type(@cask.container.type)
else
Container.for_path(@downloaded_path, @command)
end
unless container
unless primary_container
raise CaskError, "Uh oh, could not figure out how to unpack '#{@downloaded_path}'"
end
odebug "Using container class #{container} for #{@downloaded_path}"
container.new(@cask, @downloaded_path, @command, verbose: verbose?).extract
odebug "Using container class #{primary_container.class} for #{@downloaded_path}"
FileUtils.mkdir_p @cask.staged_path
primary_container.extract
end
def install_artifacts
@ -200,7 +210,7 @@ module Hbc
arch_dependencies
x11_dependencies
formula_dependencies
cask_dependencies unless skip_cask_deps?
cask_dependencies unless skip_cask_deps? || installed_as_dependency?
end
def macos_dependencies
@ -249,27 +259,21 @@ module Hbc
ohai "Installing Formula dependencies: #{not_installed.map(&:to_s).join(", ")}"
not_installed.each do |formula|
begin
old_argv = ARGV.dup
ARGV.replace([])
FormulaInstaller.new(formula).tap do |fi|
fi.installed_as_dependency = true
fi.installed_on_request = false
fi.show_header = true
fi.verbose = verbose?
fi.prelude
fi.install
fi.finish
end
ensure
ARGV.replace(old_argv)
FormulaInstaller.new(formula).tap do |fi|
fi.installed_as_dependency = true
fi.installed_on_request = false
fi.show_header = true
fi.verbose = verbose?
fi.prelude
fi.install
fi.finish
end
end
end
def cask_dependencies
return if @cask.depends_on.cask.empty?
casks = CaskDependencies.new(@cask)
return if casks.empty?
if casks.all?(&:installed?)
puts "All Cask dependencies satisfied."
@ -280,7 +284,36 @@ module Hbc
ohai "Installing Cask dependencies: #{not_installed.map(&:to_s).join(", ")}"
not_installed.each do |cask|
Installer.new(cask, binaries: binaries?, verbose: verbose?, skip_cask_deps: true, force: false).install
Installer.new(cask, binaries: binaries?, verbose: verbose?, installed_as_dependency: true, force: false).install
end
end
def unpack_dependencies
formulae = primary_container.dependencies.select { |dep| dep.is_a?(Formula) }
casks = primary_container.dependencies.select { |dep| dep.is_a?(Cask) }
.flat_map { |cask| [*CaskDependencies.new(cask), cask] }
not_installed_formulae = formulae.reject(&:any_version_installed?)
not_installed_casks = casks.reject(&:installed?)
return if (not_installed_formulae + not_installed_casks).empty?
ohai "Satisfying unpack dependencies"
not_installed_formulae.each do |formula|
FormulaInstaller.new(formula).tap do |fi|
fi.installed_as_dependency = true
fi.installed_on_request = false
fi.show_header = true
fi.verbose = verbose?
fi.prelude
fi.install
fi.finish
end
end
not_installed_casks.each do |cask|
Installer.new(cask, verbose: verbose?, installed_as_dependency: true).install
end
end

View File

@ -238,7 +238,7 @@ class RarUnpackStrategy < UnpackStrategy
private
def extract_to_dir(unpack_dir, basename:)
safe_system "unrar", "x", "-inul", path, unpack_dir
safe_system Formula["unrar"].opt_bin/"unrar", "x", "-inul", path, unpack_dir
end
end