Allow installing casks with brew install.

This commit is contained in:
Markus Reiter 2020-09-25 21:13:26 +02:00
parent 9fbe13c17d
commit f54b458cda
8 changed files with 288 additions and 120 deletions

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require "cask/config"
require "search"
module Cask

View File

@ -26,20 +26,41 @@ module Cask
end
def run
require "cask/installer"
options = {
self.class.install_casks(
*casks,
binaries: args.binaries?,
verbose: args.verbose?,
force: args.force?,
skip_cask_deps: args.skip_cask_deps?,
require_sha: args.require_sha?,
quarantine: args.quarantine?,
)
end
def self.install_casks(
*casks,
verbose: nil,
force: nil,
binaries: nil,
skip_cask_deps: nil,
require_sha: nil,
quarantine: nil
)
odie "Installing casks is supported only on macOS" unless OS.mac?
options = {
verbose: verbose,
force: force,
binaries: binaries,
skip_cask_deps: skip_cask_deps,
require_sha: require_sha,
quarantine: quarantine,
}.compact
options[:quarantine] = true if options[:quarantine].nil?
odie "Installing casks is supported only on macOS" unless OS.mac?
require "cask/installer"
casks.each do |cask|
Installer.new(cask, **options).install
rescue CaskAlreadyInstalledError => e

View File

@ -24,9 +24,9 @@ module Cask
def self.reinstall_casks(
*casks,
verbose: false,
force: false,
skip_cask_deps: false,
verbose: nil,
force: nil,
skip_cask_deps: nil,
binaries: nil,
require_sha: nil,
quarantine: nil

View File

@ -21,34 +21,47 @@ module Homebrew
end
def to_formulae
@to_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.factory(name, spec, force_bottle: @force_bottle, flags: @flags)
end.uniq(&:name).freeze
@to_formulae ||= to_formulae_and_casks.select { |o| o.is_a?(Formula) }.freeze
end
def to_formulae_and_casks
@to_formulae_and_casks ||= begin
formulae_and_casks = []
def to_formulae_and_casks(only: nil)
@to_formulae_and_casks ||= {}
@to_formulae_and_casks[only] ||= begin
to_objects(only: only).reject { |o| o.is_a?(Tap) }.freeze
end
end
downcased_unique_named.each do |name|
formulae_and_casks << Formulary.factory(name, spec)
warn_if_cask_conflicts(name, "formula")
rescue FormulaUnavailableError
def load_formula_or_cask(name, only: nil)
if only != :cask
begin
formulae_and_casks << Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise "No available formula or cask with the name \"#{name}\""
formula = Formulary.factory(name, spec, force_bottle: @force_bottle, flags: @flags)
warn_if_cask_conflicts(name, "formula") unless only == :formula
return formula
rescue FormulaUnavailableError => e
raise e if only == :formula
end
end
formulae_and_casks.freeze
if only != :formula
begin
return Cask::CaskLoader.load(name)
rescue Cask::CaskUnavailableError
raise e if only == :cask
end
end
raise FormulaOrCaskUnavailableError, name
end
private :load_formula_or_cask
def resolve_formula(name)
Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
end
private :resolve_formula
def to_resolved_formulae
@to_resolved_formulae ||= (downcased_unique_named - homebrew_tap_cask_names).map do |name|
Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
resolve_formula(name)
end.uniq(&:name).freeze
end
@ -58,7 +71,7 @@ module Homebrew
casks = []
downcased_unique_named.each do |name|
resolved_formulae << Formulary.resolve(name, spec: spec(nil), force_bottle: @force_bottle, flags: @flags)
resolved_formulae << resolve_formula(name)
warn_if_cask_conflicts(name, "formula")
rescue FormulaUnavailableError
@ -73,6 +86,18 @@ module Homebrew
end
end
# Convert named arguments to `Tap`, `Formula` or `Cask` objects.
# If both a formula and cask exist with the same name, returns the
# formula and prints a warning unless `only` is specified.
def to_objects(only: nil)
@to_objects ||= {}
@to_objects[only] ||= downcased_unique_named.flat_map do |name|
next Tap.fetch(name) if only == :tap || (only.nil? && name.count("/") == 1 && !name.start_with?("./", "/"))
load_formula_or_cask(name, only: only)
end.uniq.freeze
end
def to_formulae_paths
to_paths(only: :formulae)
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
require "cask/config"
require "missing_formula"
require "formula_installer"
require "development_tools"
@ -15,6 +16,111 @@ module Homebrew
module_function
def install_args
cask_only_options = {
["--cask", "--casks"] => {
description: "Treat all named arguments as casks.",
},
["--[no-]binaries"] => {
description: "Disable/enable linking of helper executables to `#{Cask::Config.global.binarydir}`. " \
"Default: enabled",
env: :cask_opts_binaries,
},
["--require-sha"] => {
description: "Require all casks to have a checksum.",
env: :cask_opts_require_sha,
},
["--[no-]quarantine"] => {
description: "Disable/enable quarantining of downloads. Default: enabled",
env: :cask_opts_quarantine,
},
["--skip-cask-deps"] => {
description: "Skip installing cask dependencies.",
},
}.freeze
formula_only_options = {
["--formula", "--formulae"] => {
description: "Treat all named arguments as formulae.",
},
["--env="] => {
description: "If `std` is passed, use the standard build environment instead of superenv. "\
"If `super` is passed, use superenv even if the formula specifies the "\
"standard build environment.",
},
["--ignore-dependencies"] => {
description: "An unsupported Homebrew development flag to skip installing any dependencies of "\
"any kind. If the dependencies are not already present, the formula will have issues. "\
"If you're not developing Homebrew, consider adjusting your PATH rather than "\
"using this flag.",
},
["--only-dependencies"] => {
description: "Install the dependencies with specified options but do not install the "\
"formula itself.",
},
["--cc="] => {
description: "Attempt to compile using the specified <compiler>, which should be the "\
"name of the compiler's executable, e.g. `gcc-7` for GCC 7. "\
"In order to use LLVM's clang, specify `llvm_clang`. To use the "\
"Apple-provided clang, specify `clang`. This option will only accept "\
"compilers that are provided by Homebrew or bundled with macOS. "\
"Please do not file issues if you encounter errors while using this option.",
},
["-s", "--build-from-source"] => {
description: "Compile <formula> from source even if a bottle is provided. "\
"Dependencies will still be installed from bottles if they are available.",
},
["--force-bottle"] => {
description: "Install from a bottle if it exists for the current or newest version of "\
"macOS, even if it would not normally be used for installation.",
},
["--include-test"] => {
description: "Install testing dependencies required to run `brew test` <formula>.",
},
["--HEAD"] => {
description: "If <formula> defines it, install the HEAD version, aka. master, trunk, unstable.",
},
["--fetch-HEAD"] => {
description: "Fetch the upstream repository to detect if the HEAD installation of the "\
"formula is outdated. Otherwise, the repository's HEAD will only be checked for "\
"updates when a new stable or development version has been released.",
},
["--keep-tmp"] => {
description: "Retain the temporary files created during installation.",
},
["--build-bottle"] => {
description: "Prepare the formula for eventual bottling during installation, skipping any "\
"post-install steps.",
},
["--bottle-arch="] => {
depends_on: "--build-bottle",
description: "Optimise bottles for the specified architecture rather than the oldest "\
"architecture supported by the version of macOS the bottles are built on.",
},
["--display-times"] => {
env: :display_install_times,
description: "Print install times for each formula at the end of the run.",
},
["-i", "--interactive"] => {
description: "Download and patch <formula>, then open a shell. This allows the user to "\
"run `./configure --help` and otherwise determine how to turn the software "\
"package into a Homebrew package.",
},
["-g", "--git"] => {
description: "Create a Git repository, useful for creating patches to the software.",
},
}.freeze
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`install` [<options>] <formula>
@ -27,65 +133,30 @@ module Homebrew
switch "-d", "--debug",
description: "If brewing fails, open an interactive debugging session with access to IRB "\
"or a shell inside the temporary build directory."
flag "--env=",
description: "If `std` is passed, use the standard build environment instead of superenv. "\
"If `super` is passed, use superenv even if the formula specifies the "\
"standard build environment."
switch "--ignore-dependencies",
description: "An unsupported Homebrew development flag to skip installing any dependencies of "\
"any kind. If the dependencies are not already present, the formula will have issues. "\
"If you're not developing Homebrew, consider adjusting your PATH rather than "\
"using this flag."
switch "--only-dependencies",
description: "Install the dependencies with specified options but do not install the "\
"formula itself."
flag "--cc=",
description: "Attempt to compile using the specified <compiler>, which should be the "\
"name of the compiler's executable, e.g. `gcc-7` for GCC 7. "\
"In order to use LLVM's clang, specify `llvm_clang`. To use the "\
"Apple-provided clang, specify `clang`. This option will only accept "\
"compilers that are provided by Homebrew or bundled with macOS. "\
"Please do not file issues if you encounter errors while using this option."
switch "-s", "--build-from-source",
description: "Compile <formula> from source even if a bottle is provided. "\
"Dependencies will still be installed from bottles if they are available."
switch "--force-bottle",
description: "Install from a bottle if it exists for the current or newest version of "\
"macOS, even if it would not normally be used for installation."
switch "--include-test",
description: "Install testing dependencies required to run `brew test` <formula>."
switch "--HEAD",
description: "If <formula> defines it, install the HEAD version, aka. master, trunk, unstable."
switch "--fetch-HEAD",
description: "Fetch the upstream repository to detect if the HEAD installation of the "\
"formula is outdated. Otherwise, the repository's HEAD will only be checked for "\
"updates when a new stable or development version has been released."
switch "--keep-tmp",
description: "Retain the temporary files created during installation."
switch "--build-bottle",
description: "Prepare the formula for eventual bottling during installation, skipping any "\
"post-install steps."
flag "--bottle-arch=",
depends_on: "--build-bottle",
description: "Optimise bottles for the specified architecture rather than the oldest "\
"architecture supported by the version of macOS the bottles are built on."
switch "-f", "--force",
description: "Install without checking for previously installed keg-only or "\
"non-migrated versions."
switch "-v", "--verbose",
description: "Print the verification and postinstall steps."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each formula at the end of the run."
switch "-i", "--interactive",
description: "Download and patch <formula>, then open a shell. This allows the user to "\
"run `./configure --help` and otherwise determine how to turn the software "\
"package into a Homebrew package."
switch "-g", "--git",
description: "Create a Git repository, useful for creating patches to the software."
conflicts "--ignore-dependencies", "--only-dependencies"
conflicts "--devel", "--HEAD"
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
formula_only_options.each do |flags, **options|
if flags.last.end_with?("=")
flag(*flags, **options)
else
switch(*flags, **options)
end
conflicts "--cask", flags.last
end
cask_only_options.each do |flags, **options|
switch(*flags, **options)
conflicts "--formula", flags.last
end
formula_options
min_named :formula
end
@ -94,13 +165,15 @@ module Homebrew
def install
args = install_args.parse
args.named.each do |name|
next if File.exist?(name)
next if name !~ HOMEBREW_TAP_FORMULA_REGEX && name !~ HOMEBREW_CASK_TAP_CASK_REGEX
only = :formula if args.formula?
only = :cask if args.cask?
tap = Tap.fetch(Regexp.last_match(1), Regexp.last_match(2))
tap.install unless tap.installed?
end
objects = args.named.to_objects(only: only)
taps, formulae_or_casks = objects.partition { |o| o.is_a?(Tap) }
taps = (taps + formulae_or_casks.map(&:tap).compact).uniq.sort_by(&:name)
taps.reject(&:installed?).each(&:install)
if args.ignore_dependencies?
opoo <<~EOS
@ -111,25 +184,29 @@ module Homebrew
EOS
end
formulae = []
formulae, casks = formulae_or_casks.partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
unless args.named.homebrew_tap_cask_names.empty?
cask_args = []
cask_args << "--force" if args.force?
cask_args << "--debug" if args.debug?
cask_args << "--verbose" if args.verbose?
if casks.any?
require "cask/cmd/install"
args.named.homebrew_tap_cask_names.each do |c|
ohai "brew cask install #{c} #{cask_args.join " "}"
system("#{HOMEBREW_PREFIX}/bin/brew", "cask", "install", c, *cask_args)
end
Cask::Cmd::Install.install_casks(
*casks,
binaries: args.binaries?,
verbose: args.verbose?,
force: args.force?,
skip_cask_deps: args.skip_cask_deps?,
require_sha: args.require_sha?,
quarantine: args.quarantine?,
)
end
# if the user's flags will prevent bottle only-installations when no
# developer tools are available, we need to stop them early on
FormulaInstaller.prevent_build_flags(args)
args.named.to_formulae.each do |f|
installed_formulae = []
formulae.each do |f|
# head-only without --HEAD is an error
if !args.HEAD? && f.stable.nil?
raise <<~EOS
@ -160,7 +237,7 @@ module Homebrew
To upgrade to #{f.version}, run `brew upgrade #{f.full_name}`
EOS
elsif args.only_dependencies?
formulae << f
installed_formulae << f
else
opoo <<~EOS
#{f.full_name} #{f.pkg_version} is already installed and up-to-date
@ -193,7 +270,7 @@ module Homebrew
EOS
elsif args.only_dependencies?
msg = nil
formulae << f
installed_formulae << f
else
msg = <<~EOS
#{msg} and up-to-date
@ -221,7 +298,7 @@ module Homebrew
else
# If none of the above is true and the formula is linked, then
# FormulaInstaller will handle this case.
formulae << f
installed_formulae << f
end
# Even if we don't install this formula mark it as no longer just
@ -236,11 +313,11 @@ module Homebrew
end
end
return if formulae.empty?
return if installed_formulae.empty?
Install.perform_preinstall_checks(cc: args.cc)
formulae.each do |f|
installed_formulae.each do |f|
Migrator.migrate_if_needed(f, force: args.force?)
install_formula(f, args: args)
Cleanup.install_formula_clean!(f)
@ -256,7 +333,7 @@ module Homebrew
# formula was found, but there's a problem with its implementation).
$stderr.puts e.backtrace if Homebrew::EnvConfig.developer?
ofail e.message
rescue FormulaUnavailableError => e
rescue FormulaOrCaskUnavailableError => e
if e.name == "updog"
ofail "What's updog?"
return

View File

@ -68,17 +68,25 @@ class MethodDeprecatedError < StandardError
attr_accessor :issues_url
end
# Raised when a formula is not available.
class FormulaUnavailableError < RuntimeError
# Raised when neither a formula nor a cask with the given name is available.
class FormulaOrCaskUnavailableError < RuntimeError
attr_reader :name
attr_accessor :dependent
def initialize(name)
super
super()
@name = name
end
def to_s
"No available formula or cask with the name \"#{name}\"."
end
end
# Raised when a formula is not available.
class FormulaUnavailableError < FormulaOrCaskUnavailableError
attr_accessor :dependent
def dependent_s
"(dependency of #{dependent})" if dependent && dependent != name
end

View File

@ -313,6 +313,12 @@ installed formulae or, every 30 days, for all formulae.
* `-d`, `--debug`:
If brewing fails, open an interactive debugging session with access to IRB or a shell inside the temporary build directory.
* `-f`, `--force`:
Install without checking for previously installed keg-only or non-migrated versions.
* `-v`, `--verbose`:
Print the verification and postinstall steps.
* `--formula`:
Treat all named arguments as formulae.
* `--env`:
If `std` is passed, use the standard build environment instead of superenv. If `super` is passed, use superenv even if the formula specifies the standard build environment.
* `--ignore-dependencies`:
@ -337,16 +343,22 @@ installed formulae or, every 30 days, for all formulae.
Prepare the formula for eventual bottling during installation, skipping any post-install steps.
* `--bottle-arch`:
Optimise bottles for the specified architecture rather than the oldest architecture supported by the version of macOS the bottles are built on.
* `-f`, `--force`:
Install without checking for previously installed keg-only or non-migrated versions.
* `-v`, `--verbose`:
Print the verification and postinstall steps.
* `--display-times`:
Print install times for each formula at the end of the run.
* `-i`, `--interactive`:
Download and patch *`formula`*, then open a shell. This allows the user to run `./configure --help` and otherwise determine how to turn the software package into a Homebrew package.
* `-g`, `--git`:
Create a Git repository, useful for creating patches to the software.
* `--cask`:
Treat all named arguments as casks.
* `--[no-]binaries`:
Disable/enable linking of helper executables to `/usr/local/bin`. Default: enabled
* `--require-sha`:
Require all casks to have a checksum.
* `--[no-]quarantine`:
Disable/enable quarantining of downloads. Default: enabled
* `--skip-cask-deps`:
Skip installing cask dependencies.
### `leaves`

View File

@ -456,6 +456,18 @@ Unless \fBHOMEBREW_NO_INSTALL_CLEANUP\fR is set, \fBbrew cleanup\fR will then be
If brewing fails, open an interactive debugging session with access to IRB or a shell inside the temporary build directory\.
.
.TP
\fB\-f\fR, \fB\-\-force\fR
Install without checking for previously installed keg\-only or non\-migrated versions\.
.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Print the verification and postinstall steps\.
.
.TP
\fB\-\-formula\fR
Treat all named arguments as formulae\.
.
.TP
\fB\-\-env\fR
If \fBstd\fR is passed, use the standard build environment instead of superenv\. If \fBsuper\fR is passed, use superenv even if the formula specifies the standard build environment\.
.
@ -504,14 +516,6 @@ Prepare the formula for eventual bottling during installation, skipping any post
Optimise bottles for the specified architecture rather than the oldest architecture supported by the version of macOS the bottles are built on\.
.
.TP
\fB\-f\fR, \fB\-\-force\fR
Install without checking for previously installed keg\-only or non\-migrated versions\.
.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Print the verification and postinstall steps\.
.
.TP
\fB\-\-display\-times\fR
Print install times for each formula at the end of the run\.
.
@ -523,6 +527,26 @@ Download and patch \fIformula\fR, then open a shell\. This allows the user to ru
\fB\-g\fR, \fB\-\-git\fR
Create a Git repository, useful for creating patches to the software\.
.
.TP
\fB\-\-cask\fR
Treat all named arguments as casks\.
.
.TP
\fB\-\-[no\-]binaries\fR
Disable/enable linking of helper executables to \fB/usr/local/bin\fR\. Default: enabled
.
.TP
\fB\-\-require\-sha\fR
Require all casks to have a checksum\.
.
.TP
\fB\-\-[no\-]quarantine\fR
Disable/enable quarantining of downloads\. Default: enabled
.
.TP
\fB\-\-skip\-cask\-deps\fR
Skip installing cask dependencies\.
.
.SS "\fBleaves\fR"
List installed formulae that are not dependencies of another installed formula\.
.