Merge branch 'master' into check-for-master-no-refactor

This commit is contained in:
Ben Muschol 2017-09-27 16:36:10 -04:00 committed by GitHub
commit 2d6bd04007
180 changed files with 2251 additions and 1137 deletions

View File

@ -15,26 +15,27 @@ matrix:
rvm: system rvm: system
- os: linux - os: linux
sudo: false sudo: false
rvm: 2.0.0 rvm: 2.3.3
before_install: before_install:
- export HOMEBREW_NO_AUTO_UPDATE=1 - export HOMEBREW_NO_AUTO_UPDATE=1
- export HOMEBREW_DEVELOPER=1 - export HOMEBREW_DEVELOPER=1
- git clone --depth=1 https://github.com/Homebrew/homebrew-test-bot Library/Taps/homebrew/homebrew-test-bot
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then
git clone --depth=1 https://github.com/Homebrew/homebrew-test-bot Library/Taps/homebrew/homebrew-test-bot;
HOMEBREW_REPOSITORY="$(brew --repo)"; HOMEBREW_REPOSITORY="$(brew --repo)";
sudo chown -R "$USER" "$HOMEBREW_REPOSITORY/Library/Taps"; sudo chown -R "$USER" "$HOMEBREW_REPOSITORY/Library/Taps";
mv "$HOMEBREW_REPOSITORY/Library/Taps" "$PWD/Library"; mv "$HOMEBREW_REPOSITORY/Library/Taps" "$PWD/Library";
sudo rm -rf "$HOMEBREW_REPOSITORY"; sudo rm -rf "$HOMEBREW_REPOSITORY";
sudo ln -s "$PWD" "$HOMEBREW_REPOSITORY"; sudo ln -s "$PWD" "$HOMEBREW_REPOSITORY";
else else
umask 022;
git clone --depth=1 https://github.com/Homebrew/homebrew-test-bot Library/Taps/homebrew/homebrew-test-bot;
git fetch --unshallow; git fetch --unshallow;
export PATH="$PWD/bin:$PATH"; export PATH="$PWD/bin:$PATH";
HOMEBREW_CORE_TAP_DIR="$(brew --repo "homebrew/core")"; HOMEBREW_CORE_TAP_DIR="$(brew --repo "homebrew/core")";
mkdir -p "$HOMEBREW_CORE_TAP_DIR"; mkdir -p "$HOMEBREW_CORE_TAP_DIR";
HOMEBREW_TEST_BOT_TAP_DIR="$(brew --repo "homebrew/test-bot")"; HOMEBREW_TEST_BOT_TAP_DIR="$(brew --repo "homebrew/test-bot")";
ln -s "$HOMEBREW_TEST_BOT_TAP_DIR/.git" "$HOMEBREW_TEST_BOT_TAP_DIR/Formula" "$HOMEBREW_CORE_TAP_DIR"; ln -s "$HOMEBREW_TEST_BOT_TAP_DIR/.git" "$HOMEBREW_TEST_BOT_TAP_DIR/Formula" "$HOMEBREW_CORE_TAP_DIR";
umask 022;
fi fi
script: script:

View File

@ -1,5 +1,5 @@
AllCops: AllCops:
TargetRubyVersion: 2.0 TargetRubyVersion: 2.3
Exclude: Exclude:
- '**/Casks/**/*' - '**/Casks/**/*'
- '**/vendor/**/*' - '**/vendor/**/*'
@ -119,7 +119,7 @@ Style/Encoding:
Enabled: true Enabled: true
# dashes in filenames are typical # dashes in filenames are typical
Style/FileName: Naming/FileName:
Regex: !ruby/regexp /^[\w\@\-\+\.]+(\.rb)?$/ Regex: !ruby/regexp /^[\w\@\-\+\.]+(\.rb)?$/
# falsely flags e.g. curl formatting arguments as format strings # falsely flags e.g. curl formatting arguments as format strings
@ -189,8 +189,25 @@ Style/TrailingCommaInArguments:
EnforcedStyleForMultiline: comma EnforcedStyleForMultiline: comma
# we have too many variables like sha256 where this harms readability # we have too many variables like sha256 where this harms readability
Style/VariableNumber: Naming/VariableNumber:
Enabled: false Enabled: false
Style/WordArray: Style/WordArray:
MinSize: 4 MinSize: 4
# we want to add this slowly and manually
Style/FrozenStringLiteralComment:
Enabled: false
# generally rescuing StandardError is fine
Lint/RescueWithoutErrorClass:
Enabled: false
# implicitly allow EOS as we use it everywhere
Naming/HeredocDelimiterNaming:
Blacklist:
- END, EOD, EOF
# we output how to use interpolated strings too often
Lint/InterpolationCheck:
Enabled: false

View File

@ -42,12 +42,12 @@ Style/HashSyntax:
EnforcedStyle: ruby19_no_mixed_keys EnforcedStyle: ruby19_no_mixed_keys
# we won't change backward compatible method names # we won't change backward compatible method names
Style/MethodName: Naming/MethodName:
Exclude: Exclude:
- 'compat/**/*' - 'compat/**/*'
# we won't change backward compatible predicate names # we won't change backward compatible predicate names
Style/PredicateName: Naming/PredicateName:
Exclude: Exclude:
- 'compat/**/*' - 'compat/**/*'
NameWhitelist: is_32_bit?, is_64_bit? NameWhitelist: is_32_bit?, is_64_bit?

View File

@ -81,7 +81,7 @@ Security/MarshalLoad:
- 'utils/fork.rb' - 'utils/fork.rb'
# Offense count: 1 # Offense count: 1
Style/AccessorMethodName: Naming/AccessorMethodName:
Exclude: Exclude:
- 'extend/ENV/super.rb' - 'extend/ENV/super.rb'
@ -136,10 +136,3 @@ Style/MutableConstant:
- 'formulary.rb' - 'formulary.rb'
- 'tab.rb' - 'tab.rb'
- 'tap.rb' - 'tap.rb'
# Offense count: 8
Style/OpMethod:
Exclude:
- 'dependencies.rb'
- 'install_renamed.rb'
- 'options.rb'

View File

@ -105,18 +105,16 @@ begin
possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) } possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) }
possible_tap = Tap.fetch(possible_tap.first) if possible_tap possible_tap = Tap.fetch(possible_tap.first) if possible_tap
if possible_tap && !possible_tap.installed? odie "Unknown command: #{cmd}" if !possible_tap || possible_tap.installed?
brew_uid = HOMEBREW_BREW_FILE.stat.uid
tap_commands = [] brew_uid = HOMEBREW_BREW_FILE.stat.uid
if Process.uid.zero? && !brew_uid.zero? tap_commands = []
tap_commands += %W[/usr/bin/sudo -u ##{brew_uid}] if Process.uid.zero? && !brew_uid.zero?
end tap_commands += %W[/usr/bin/sudo -u ##{brew_uid}]
tap_commands += %W[#{HOMEBREW_BREW_FILE} tap #{possible_tap}]
safe_system(*tap_commands)
exec HOMEBREW_BREW_FILE, cmd, *ARGV
else
odie "Unknown command: #{cmd}"
end end
tap_commands += %W[#{HOMEBREW_BREW_FILE} tap #{possible_tap}]
safe_system(*tap_commands)
exec HOMEBREW_BREW_FILE, cmd, *ARGV
end end
rescue UsageError => e rescue UsageError => e
require "cmd/help" require "cmd/help"

View File

@ -23,7 +23,7 @@ HOMEBREW_VERSION="$(git -C "$HOMEBREW_REPOSITORY" describe --tags --dirty --abbr
HOMEBREW_USER_AGENT_VERSION="$HOMEBREW_VERSION" HOMEBREW_USER_AGENT_VERSION="$HOMEBREW_VERSION"
if [[ -z "$HOMEBREW_VERSION" ]] if [[ -z "$HOMEBREW_VERSION" ]]
then then
HOMEBREW_VERSION=">1.2.0 (no git repository)" HOMEBREW_VERSION=">1.2.0 (shallow or no git repository)"
HOMEBREW_USER_AGENT_VERSION="1.X.Y" HOMEBREW_USER_AGENT_VERSION="1.X.Y"
fi fi
@ -105,7 +105,14 @@ then
HOMEBREW_OS_USER_AGENT_VERSION="Mac OS X $HOMEBREW_MACOS_VERSION" HOMEBREW_OS_USER_AGENT_VERSION="Mac OS X $HOMEBREW_MACOS_VERSION"
printf -v HOMEBREW_MACOS_VERSION_NUMERIC "%02d%02d%02d" ${HOMEBREW_MACOS_VERSION//./ } printf -v HOMEBREW_MACOS_VERSION_NUMERIC "%02d%02d%02d" ${HOMEBREW_MACOS_VERSION//./ }
if [[ "$HOMEBREW_MACOS_VERSION_NUMERIC" -lt "100900" && if [[ "$HOMEBREW_MACOS_VERSION_NUMERIC" -lt "101000" ]]
then
HOMEBREW_SYSTEM_CURL_TOO_OLD="1"
fi
# The system Curl is too old for some modern HTTPS certificates on
# older macOS versions.
if [[ -n "$HOMEBREW_SYSTEM_CURL_TOO_OLD" &&
-x "$HOMEBREW_PREFIX/opt/curl/bin/curl" ]] -x "$HOMEBREW_PREFIX/opt/curl/bin/curl" ]]
then then
HOMEBREW_CURL="$HOMEBREW_PREFIX/opt/curl/bin/curl" HOMEBREW_CURL="$HOMEBREW_PREFIX/opt/curl/bin/curl"

View File

@ -112,6 +112,10 @@ class Build
formula.extend(Debrew::Formula) if ARGV.debug? formula.extend(Debrew::Formula) if ARGV.debug?
formula.brew do |_formula, staging| formula.brew do |_formula, staging|
# For head builds, HOMEBREW_FORMULA_PREFIX should include the commit,
# which is not known until after the formula has been staged.
ENV["HOMEBREW_FORMULA_PREFIX"] = formula.prefix
staging.retain! if ARGV.keep_tmp? staging.retain! if ARGV.keep_tmp?
formula.patch formula.patch

View File

@ -33,7 +33,7 @@ module Hbc
# We want to extract nested containers before we # We want to extract nested containers before we
# handle any other artifacts. # handle any other artifacts.
# #
TYPES = [ CLASSES = [
PreflightBlock, PreflightBlock,
Uninstall, Uninstall,
NestedContainer, NestedContainer,
@ -60,12 +60,9 @@ module Hbc
Zap, Zap,
].freeze ].freeze
def self.for_cask(cask, options = {}) def self.for_cask(cask)
odebug "Determining which artifacts are present in Cask #{cask}" odebug "Determining which artifacts are present in Cask #{cask}"
CLASSES.flat_map { |klass| klass.for_cask(cask) }
TYPES
.select { |klass| klass.me?(cask) }
.map { |klass| klass.new(cask, options) }
end end
end end
end end

View File

@ -1,34 +1,28 @@
module Hbc module Hbc
module Artifact module Artifact
class Base class AbstractArtifact
extend Predicable extend Predicable
def self.artifact_name def self.english_name
@artifact_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase @english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')
end end
def self.artifact_english_name def self.english_article
@artifact_english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2') @english_article ||= (english_name =~ /^[aeiou]/i) ? "an" : "a"
end end
def self.artifact_english_article def self.dsl_key
@artifact_english_article ||= (artifact_english_name =~ /^[aeiou]/i) ? "an" : "a" @dsl_key ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym
end end
def self.artifact_dsl_key def self.dirmethod
@artifact_dsl_key ||= artifact_name.to_sym @dirmethod ||= "#{dsl_key}dir".to_sym
end end
def self.artifact_dirmethod def self.for_cask(cask)
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym cask.artifacts[dsl_key].to_a
end end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any?
end
attr_reader :force
# TODO: this sort of logic would make more sense in dsl.rb, or a # TODO: this sort of logic would make more sense in dsl.rb, or a
# constructor called from dsl.rb, so long as that isn't slow. # constructor called from dsl.rb, so long as that isn't slow.
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil) def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
@ -63,17 +57,14 @@ module Hbc
[executable, arguments] [executable, arguments]
end end
def summary attr_reader :cask
{}
def initialize(cask)
@cask = cask
end end
attr_predicate :force?, :verbose? def to_s
"#{summarize} (#{self.class.english_name})"
def initialize(cask, command: SystemCommand, force: false, verbose: false)
@cask = cask
@command = command
@force = force
@verbose = verbose
end end
end end
end end

View File

@ -1,39 +1,51 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class AbstractFlightBlock < Base class AbstractFlightBlock < AbstractArtifact
def self.artifact_dsl_key def self.dsl_key
super.to_s.sub(/_block$/, "").to_sym super.to_s.sub(/_block$/, "").to_sym
end end
def self.uninstall_artifact_dsl_key def self.uninstall_dsl_key
artifact_dsl_key.to_s.prepend("uninstall_").to_sym dsl_key.to_s.prepend("uninstall_").to_sym
end end
def self.class_for_dsl_key(dsl_key) def self.for_cask(cask)
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") [dsl_key, uninstall_dsl_key].flat_map do |key|
[*cask.artifacts[key]].map { |block| new(cask, key => block) }
end
end end
def self.me?(cask) attr_reader :directives
cask.artifacts[artifact_dsl_key].any? ||
cask.artifacts[uninstall_artifact_dsl_key].any? def initialize(cask, **directives)
super(cask)
@directives = directives
end end
def install_phase def install_phase(**)
abstract_phase(self.class.artifact_dsl_key) abstract_phase(self.class.dsl_key)
end end
def uninstall_phase def uninstall_phase(**)
abstract_phase(self.class.uninstall_artifact_dsl_key) abstract_phase(self.class.uninstall_dsl_key)
end end
private private
def class_for_dsl_key(dsl_key)
namespace = self.class.name.to_s.sub(/::.*::.*$/, "")
self.class.const_get("#{namespace}::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
end
def abstract_phase(dsl_key) def abstract_phase(dsl_key)
@cask.artifacts[dsl_key].each do |block| return if (block = directives[dsl_key]).nil?
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) class_for_dsl_key(dsl_key).new(cask).instance_eval(&block)
end end
def summarize
directives.keys.map(&:to_s).join(", ")
end end
end end
end end

View File

@ -1,11 +1,11 @@
require "pathname" require "pathname"
require "timeout" require "timeout"
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class UninstallBase < Base class AbstractUninstall < AbstractArtifact
ORDERED_DIRECTIVES = [ ORDERED_DIRECTIVES = [
:early_script, :early_script,
:launchctl, :launchctl,
@ -20,26 +20,42 @@ module Hbc
:rmdir, :rmdir,
].freeze ].freeze
def dispatch_uninstall_directives def self.from_args(cask, **directives)
directives_set = @cask.artifacts[stanza] new(cask, directives)
ohai "Running #{stanza} process for #{@cask}; your password may be necessary" end
directives_set.each do |directives| attr_reader :directives
warn_for_unknown_directives(directives)
end
ORDERED_DIRECTIVES.each do |directive_sym| def initialize(cask, directives)
directives_set.select { |h| h.key?(directive_sym) }.each do |directives| super(cask)
args = directives[directive_sym] directives[:signal] = [*directives[:signal]].flatten.each_slice(2).to_a
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args)) @directives = directives
end end
end
def to_h
directives.to_h
end
def summarize
to_h.map { |key, val| [*val].map { |v| "#{key.inspect} => #{v.inspect}" }.join(", ") }.join(", ")
end end
private private
def dispatch_uninstall_directives(**options)
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
warn_for_unknown_directives(directives)
ORDERED_DIRECTIVES.each do |directive_sym|
next unless directives.key?(directive_sym)
args = directives[directive_sym]
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options)
end
end
def stanza def stanza
self.class.artifact_dsl_key self.class.dsl_key
end end
def warn_for_unknown_directives(directives) def warn_for_unknown_directives(directives)
@ -51,18 +67,18 @@ module Hbc
# Preserve prior functionality of script which runs first. Should rarely be needed. # Preserve prior functionality of script which runs first. Should rarely be needed.
# :early_script should not delete files, better defer that to :script. # :early_script should not delete files, better defer that to :script.
# If Cask writers never need :early_script it may be removed in the future. # If Cask writers never need :early_script it may be removed in the future.
def uninstall_early_script(directives) def uninstall_early_script(directives, **options)
uninstall_script(directives, directive_name: :early_script) uninstall_script(directives, directive_name: :early_script, **options)
end end
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch # :launchctl must come before :quit/:signal for cases where app would instantly re-launch
def uninstall_launchctl(*services) def uninstall_launchctl(*services, command: nil, **_)
services.each do |service| services.each do |service|
ohai "Removing launchctl service #{service}" ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo| [false, true].each do |with_sudo|
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout plist_status = command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
if plist_status =~ /^\{/ if plist_status =~ /^\{/
@command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)
sleep 1 sleep 1
end end
paths = ["/Library/LaunchAgents/#{service}.plist", paths = ["/Library/LaunchAgents/#{service}.plist",
@ -70,38 +86,38 @@ module Hbc
paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo
paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)
paths.each do |path| paths.each do |path|
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo) command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo)
end end
# undocumented and untested: pass a path to uninstall :launchctl # undocumented and untested: pass a path to uninstall :launchctl
next unless Pathname(service).exist? next unless Pathname(service).exist?
@command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo)
@command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo) command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo)
sleep 1 sleep 1
end end
end end
end end
def running_processes(bundle_id) def running_processes(bundle_id, command: nil)
@command.run!("/bin/launchctl", args: ["list"]).stdout.lines command.run!("/bin/launchctl", args: ["list"]).stdout.lines
.map { |line| line.chomp.split("\t") } .map { |line| line.chomp.split("\t") }
.map { |pid, state, id| [pid.to_i, state.to_i, id] } .map { |pid, state, id| [pid.to_i, state.to_i, id] }
.select do |fields| .select do |fields|
next if fields[0].zero? next if fields[0].zero?
fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/
end end
end end
# :quit/:signal must come before :kext so the kext will not be in use by a running process # :quit/:signal must come before :kext so the kext will not be in use by a running process
def uninstall_quit(*bundle_ids) def uninstall_quit(*bundle_ids, command: nil, **_)
bundle_ids.each do |bundle_id| bundle_ids.each do |bundle_id|
ohai "Quitting application ID #{bundle_id}" ohai "Quitting application ID #{bundle_id}"
next if running_processes(bundle_id).empty? next if running_processes(bundle_id, command: command).empty?
@command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true) command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true)
begin begin
Timeout.timeout(3) do Timeout.timeout(3) do
Kernel.loop do Kernel.loop do
break if running_processes(bundle_id).empty? break if running_processes(bundle_id, command: command).empty?
end end
end end
rescue Timeout::Error rescue Timeout::Error
@ -111,15 +127,15 @@ module Hbc
end end
# :signal should come after :quit so it can be used as a backup when :quit fails # :signal should come after :quit so it can be used as a backup when :quit fails
def uninstall_signal(*signals) def uninstall_signal(*signals, command: nil, **_)
signals.flatten.each_slice(2) do |pair| signals.each do |pair|
unless pair.size == 2 unless pair.size == 2
raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must consist of 2 elements.") raise CaskInvalidError.new(cask, "Each #{stanza} :signal must consist of 2 elements.")
end end
signal, bundle_id = pair signal, bundle_id = pair
ohai "Signalling '#{signal}' to application ID '#{bundle_id}'" ohai "Signalling '#{signal}' to application ID '#{bundle_id}'"
pids = running_processes(bundle_id).map(&:first) pids = running_processes(bundle_id, command: command).map(&:first)
next unless pids.any? next unless pids.any?
# Note that unlike :quit, signals are sent from the current user (not # Note that unlike :quit, signals are sent from the current user (not
# upgraded to the superuser). This is a todo item for the future, but # upgraded to the superuser). This is a todo item for the future, but
@ -133,10 +149,10 @@ module Hbc
end end
end end
def uninstall_login_item(*login_items) def uninstall_login_item(*login_items, command: nil, **_)
login_items.each do |name| login_items.each do |name|
ohai "Removing login item #{name}" ohai "Removing login item #{name}"
@command.run!("/usr/bin/osascript", command.run!("/usr/bin/osascript",
args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")], args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")],
sudo: false) sudo: false)
sleep 1 sleep 1
@ -144,23 +160,24 @@ module Hbc
end end
# :kext should be unloaded before attempting to delete the relevant file # :kext should be unloaded before attempting to delete the relevant file
def uninstall_kext(*kexts) def uninstall_kext(*kexts, command: nil, **_)
kexts.each do |kext| kexts.each do |kext|
ohai "Unloading kernel extension #{kext}" ohai "Unloading kernel extension #{kext}"
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout is_loaded = command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
if is_loaded.length > 1 if is_loaded.length > 1
@command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true)
sleep 1 sleep 1
end end
@command.run!("/usr/sbin/kextfind", args: ["-b", kext], sudo: true).stdout.chomp.lines.each do |kext_path| command.run!("/usr/sbin/kextfind", args: ["-b", kext], sudo: true).stdout.chomp.lines.each do |kext_path|
ohai "Removing kernel extension #{kext_path}" ohai "Removing kernel extension #{kext_path}"
@command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true) command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true)
end end
end end
end end
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted # :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
def uninstall_script(directives, directive_name: :script) def uninstall_script(directives, directive_name: :script, force: false, command: nil, **_)
# TODO: Create a common `Script` class to run this and Artifact::Installer.
executable, script_arguments = self.class.read_script_arguments(directives, executable, script_arguments = self.class.read_script_arguments(directives,
"uninstall", "uninstall",
{ must_succeed: true, sudo: false }, { must_succeed: true, sudo: false },
@ -168,25 +185,25 @@ module Hbc
directive_name) directive_name)
ohai "Running uninstall script #{executable}" ohai "Running uninstall script #{executable}"
raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? raise CaskInvalidError.new(cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
executable_path = @cask.staged_path.join(executable) executable_path = cask.staged_path.join(executable)
unless executable_path.exist? unless executable_path.exist?
message = "uninstall script #{executable} does not exist" message = "uninstall script #{executable} does not exist"
raise CaskError, "#{message}." unless force? raise CaskError, "#{message}." unless force
opoo "#{message}, skipping." opoo "#{message}, skipping."
return return
end end
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) command.run("/bin/chmod", args: ["--", "+x", executable_path])
@command.run(executable_path, script_arguments) command.run(executable_path, script_arguments)
sleep 1 sleep 1
end end
def uninstall_pkgutil(*pkgs) def uninstall_pkgutil(*pkgs, command: nil, **_)
ohai "Uninstalling packages:" ohai "Uninstalling packages:"
pkgs.each do |regex| pkgs.each do |regex|
Hbc::Pkg.all_matching(regex, @command).each do |pkg| Hbc::Pkg.all_matching(regex, command).each do |pkg|
puts pkg.package_id puts pkg.package_id
pkg.uninstall pkg.uninstall
end end
@ -215,28 +232,28 @@ module Hbc
end end
end end
def uninstall_delete(*paths) def uninstall_delete(*paths, command: nil, **_)
return if paths.empty? return if paths.empty?
ohai "Removing files:" ohai "Removing files:"
each_resolved_path(:delete, paths) do |path, resolved_paths| each_resolved_path(:delete, paths) do |path, resolved_paths|
puts path puts path
@command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "-r", "-f", "--"], input: resolved_paths.join("\0"), sudo: true) command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "-r", "-f", "--"], input: resolved_paths.join("\0"), sudo: true)
end end
end end
def uninstall_trash(*paths) def uninstall_trash(*paths, **options)
return if paths.empty? return if paths.empty?
resolved_paths = each_resolved_path(:trash, paths).to_a resolved_paths = each_resolved_path(:trash, paths).to_a
ohai "Trashing files:" ohai "Trashing files:"
puts resolved_paths.map(&:first) puts resolved_paths.map(&:first)
trash_paths(*resolved_paths.flat_map(&:last)) trash_paths(*resolved_paths.flat_map(&:last), **options)
end end
def trash_paths(*paths) def trash_paths(*paths, command: nil, **_)
@command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths]) command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths])
on run argv on run argv
repeat with i from 1 to (count argv) repeat with i from 1 to (count argv)
set item i of argv to (item i of argv as POSIX file) set item i of argv to (item i of argv as POSIX file)
@ -260,7 +277,7 @@ module Hbc
EOS EOS
end end
def uninstall_rmdir(*directories) def uninstall_rmdir(*directories, command: nil, **_)
return if directories.empty? return if directories.empty?
ohai "Removing directories if empty:" ohai "Removing directories if empty:"
@ -268,10 +285,10 @@ module Hbc
puts path puts path
resolved_paths.select(&:directory?).each do |resolved_path| resolved_paths.select(&:directory?).each do |resolved_path|
if (ds_store = resolved_path.join(".DS_Store")).exist? if (ds_store = resolved_path.join(".DS_Store")).exist?
@command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false) command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false)
end end
@command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false) command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false)
end end
end end
end end

View File

@ -5,21 +5,32 @@ require "hbc/utils/hash_validator"
module Hbc module Hbc
module Artifact module Artifact
class Artifact < Moved class Artifact < Moved
def self.artifact_english_name def self.english_name
"Generic Artifact" "Generic Artifact"
end end
def self.artifact_dirmethod def self.from_args(cask, *args)
:appdir source_string, target_hash = args
if source_string.nil?
raise CaskInvalidError.new(cask.token, "no source given for #{english_name}")
end
unless target_hash.is_a?(Hash)
raise CaskInvalidError.new(cask.token, "target required for #{english_name} '#{source_string}'")
end
target_hash.extend(HashValidator).assert_valid_keys(:target)
new(cask, source_string, **target_hash)
end end
def load_specification(artifact_spec) def self.resolve_target(target)
source_string, target_hash = artifact_spec Pathname(target)
raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? end
@source = @cask.staged_path.join(source_string)
raise CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash) def initialize(cask, source, target: nil)
target_hash.extend(HashValidator).assert_valid_keys(:target) super(cask, source, target: target)
@target = Pathname.new(target_hash[:target])
end end
end end
end end

View File

@ -3,13 +3,13 @@ require "hbc/artifact/symlinked"
module Hbc module Hbc
module Artifact module Artifact
class Binary < Symlinked class Binary < Symlinked
def link def link(command: nil, **options)
super super(command: command, **options)
return if source.executable? return if source.executable?
if source.writable? if source.writable?
FileUtils.chmod "+x", source FileUtils.chmod "+x", source
else else
@command.run!("/bin/chmod", args: ["+x", source], sudo: true) command.run!("/bin/chmod", args: ["+x", source], sudo: true)
end end
end end
end end

View File

@ -1,29 +1,82 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class Installer < Base class Installer < AbstractArtifact
def install_phase VALID_KEYS = Set.new [
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact| :manual,
if artifact.manual :script,
puts <<-EOS.undent ]
To complete the installation of Cask #{@cask}, you must also
run the installer at
'#{@cask.staged_path.join(artifact.manual)}' module ManualInstaller
def install_phase(**)
puts <<-EOS.undent
To complete the installation of Cask #{cask}, you must also
run the installer at
EOS '#{path}'
else EOS
executable, script_arguments = self.class.read_script_arguments(artifact.script, end
self.class.artifact_dsl_key.to_s, end
{ must_succeed: true, sudo: false },
print_stdout: true) module ScriptInstaller
ohai "Running #{self.class.artifact_dsl_key} script #{executable}" def install_phase(command: nil, **_)
raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil? ohai "Running #{self.class.dsl_key} script '#{path.relative_path_from(cask.staged_path)}'"
executable_path = @cask.staged_path.join(executable) FileUtils.chmod "+x", path unless path.executable?
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) command.run(path, **args)
@command.run(executable_path, script_arguments) end
end
def self.from_args(cask, **args)
raise CaskInvalidError.new(cask, "'installer' stanza requires an argument.") if args.empty?
if args.key?(:script) && !args[:script].respond_to?(:key?)
if args.key?(:executable)
raise CaskInvalidError.new(cask, "'installer' stanza gave arguments for both :script and :executable.")
end end
args[:executable] = args[:script]
args.delete(:script)
args = { script: args }
end
unless args.keys.count == 1
raise CaskInvalidError.new(cask, "invalid 'installer' stanza: Only one of #{VALID_KEYS.inspect} is permitted.")
end
args.extend(HashValidator).assert_valid_keys(*VALID_KEYS)
new(cask, **args)
end
attr_reader :path, :args
def initialize(cask, **args)
super(cask)
if args.key?(:manual)
@path = cask.staged_path.join(args[:manual])
@args = []
extend(ManualInstaller)
return
end
path, @args = self.class.read_script_arguments(
args[:script], self.class.dsl_key.to_s, { must_succeed: true, sudo: false }, print_stdout: true
)
raise CaskInvalidError.new(cask, "#{self.class.dsl_key} missing executable") if path.nil?
path = Pathname(path)
@path = path.absolute? ? path : cask.staged_path.join(path)
extend(ScriptInstaller)
end
def summarize
path.relative_path_from(cask.staged_path).to_s
end
def to_h
{ path: path.relative_path_from(cask.staged_path).to_s }.tap do |h|
h[:args] = args unless is_a?(ManualInstaller)
end end
end end
end end

View File

@ -4,63 +4,61 @@ module Hbc
module Artifact module Artifact
class Moved < Relocated class Moved < Relocated
def self.english_description def self.english_description
"#{artifact_english_name}s" "#{english_name}s"
end end
def install_phase def install_phase(**options)
each_artifact(&method(:move)) move(**options)
end end
def uninstall_phase def uninstall_phase(**options)
each_artifact(&method(:delete)) delete(**options)
end
def summarize_installed
if target.exist?
"#{printable_target} (#{target.abv})"
else
Formatter.error(printable_target, label: "Missing #{self.class.english_name}")
end
end end
private private
def move def move(force: false, command: nil, **options)
if Utils.path_occupied?(target) if Utils.path_occupied?(target)
message = "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'" message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'"
raise CaskError, "#{message}." unless force? raise CaskError, "#{message}." unless force
opoo "#{message}; overwriting." opoo "#{message}; overwriting."
delete delete(force: force, command: command, **options)
end end
unless source.exist? unless source.exist?
raise CaskError, "It seems the #{self.class.artifact_english_name} source '#{source}' is not there." raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
end end
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'." ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'."
target.dirname.mkpath target.dirname.mkpath
if target.parent.writable? if target.parent.writable?
FileUtils.move(source, target) FileUtils.move(source, target)
else else
SystemCommand.run("/bin/mv", args: [source, target], sudo: true) command.run("/bin/mv", args: [source, target], sudo: true)
end end
add_altname_metadata target, source.basename.to_s add_altname_metadata(target, source.basename, command: command)
end end
def delete def delete(force: false, command: nil, **_)
ohai "Removing #{self.class.artifact_english_name} '#{target}'." ohai "Removing #{self.class.english_name} '#{target}'."
raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}." if MacOS.undeletable?(target) raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)
return unless Utils.path_occupied?(target) return unless Utils.path_occupied?(target)
if target.parent.writable? && !force if target.parent.writable? && !force
target.rmtree target.rmtree
else else
Utils.gain_permissions_remove(target, command: @command) Utils.gain_permissions_remove(target, command: command)
end
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.exist?
"#{printable_target} (#{target.abv})"
else
Formatter.error(printable_target, label: "Missing #{self.class.artifact_english_name}")
end end
end end
end end

View File

@ -1,23 +1,31 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class NestedContainer < Base class NestedContainer < AbstractArtifact
def install_phase attr_reader :path
@cask.artifacts[:nested_container].each { |container| extract(container) }
def initialize(cask, path)
super(cask)
@path = cask.staged_path.join(path)
end end
def extract(container_relative_path) def install_phase(**options)
source = @cask.staged_path.join(container_relative_path) extract(**options)
container = Container.for_path(source, @command) end
private
def extract(command: nil, verbose: nil, **_)
container = Container.for_path(path, command)
unless container unless container
raise CaskError, "Aw dang, could not identify nested container at '#{source}'" raise CaskError, "Aw dang, could not identify nested container at '#{source}'"
end end
ohai "Extracting nested container #{source.basename}" ohai "Extracting nested container #{path.relative_path_from(cask.staged_path)}"
container.new(@cask, source, @command, verbose: verbose?).extract container.new(cask, path, command, verbose: verbose).extract
FileUtils.remove_entry_secure(source) FileUtils.remove_entry_secure(path)
end end
end end
end end

View File

@ -1,4 +1,4 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
require "hbc/utils/hash_validator" require "hbc/utils/hash_validator"
@ -6,62 +6,57 @@ require "vendor/plist/plist"
module Hbc module Hbc
module Artifact module Artifact
class Pkg < Base class Pkg < AbstractArtifact
attr_reader :pkg_relative_path attr_reader :pkg_relative_path
def self.artifact_dsl_key def self.from_args(cask, path, **options)
:pkg options.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices)
new(cask, path, **options)
end end
def load_pkg_description(pkg_description) attr_reader :path, :options
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift def initialize(cask, path, **options)
begin super(cask)
if @pkg_install_opts.respond_to?(:keys) @path = cask.staged_path.join(path)
@pkg_install_opts.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) @options = options
elsif @pkg_install_opts
raise
end
raise if pkg_description.nil?
rescue StandardError
raise CaskInvalidError.new(@cask, "Bad pkg stanza")
end
end end
def pkg_install_opts(opt) def summarize
@pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) path.relative_path_from(cask.staged_path).to_s
end end
def install_phase def install_phase(**options)
@cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } run_installer(**options)
end end
def run_installer(pkg_description) private
load_pkg_description pkg_description
ohai "Running installer for #{@cask}; your password may be necessary." def run_installer(command: nil, verbose: false, **options)
ohai "Running installer for #{cask}; your password may be necessary."
ohai "Package installers may write to any location; options such as --appdir are ignored." ohai "Package installers may write to any location; options such as --appdir are ignored."
source = @cask.staged_path.join(pkg_relative_path) unless path.exist?
unless source.exist? raise CaskError, "pkg source file not found: '#{path.relative_path_from(cask.staged_path)}'"
raise CaskError, "pkg source file not found: '#{source}'"
end end
args = [ args = [
"-pkg", source, "-pkg", path,
"-target", "/" "-target", "/"
] ]
args << "-verboseR" if verbose? args << "-verboseR" if verbose
args << "-allowUntrusted" if pkg_install_opts :allow_untrusted args << "-allowUntrusted" if options.fetch(:allow_untrusted, false)
with_choices_file do |choices_path| with_choices_file do |choices_path|
args << "-applyChoiceChangesXML" << choices_path if choices_path args << "-applyChoiceChangesXML" << choices_path if choices_path
@command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true) command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true)
end end
end end
def with_choices_file def with_choices_file
return yield nil unless pkg_install_opts(:choices) choices = options.fetch(:choices, {})
return yield nil if choices.empty?
Tempfile.open(["choices", ".xml"]) do |file| Tempfile.open(["choices", ".xml"]) do |file|
begin begin
file.write Plist::Emit.dump(pkg_install_opts(:choices)) file.write Plist::Emit.dump(choices)
file.close file.close
yield file.path yield file.path
ensure ensure

View File

@ -3,7 +3,7 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Prefpane < Moved class Prefpane < Moved
def self.artifact_english_name def self.english_name
"Preference Pane" "Preference Pane"
end end
end end

View File

@ -3,22 +3,24 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Qlplugin < Moved class Qlplugin < Moved
def self.artifact_english_name def self.english_name
"QuickLook Plugin" "QuickLook Plugin"
end end
def install_phase def install_phase(**options)
super super(**options)
reload_quicklook reload_quicklook(**options)
end end
def uninstall_phase def uninstall_phase(**options)
super super(**options)
reload_quicklook reload_quicklook(**options)
end end
def reload_quicklook private
@command.run!("/usr/bin/qlmanage", args: ["-r"])
def reload_quicklook(command: nil, **_)
command.run!("/usr/bin/qlmanage", args: ["-r"])
end end
end end
end end

View File

@ -1,65 +1,79 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
require "hbc/utils/hash_validator" require "hbc/utils/hash_validator"
module Hbc module Hbc
module Artifact module Artifact
class Relocated < Base class Relocated < AbstractArtifact
def summary def self.from_args(cask, *args)
{ source_string, target_hash = args
english_description: self.class.english_description,
contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, if target_hash
} raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.extend(HashValidator).assert_valid_keys(:target)
end
target_hash ||= {}
new(cask, source_string, **target_hash)
end
def self.resolve_target(target)
Hbc.public_send(dirmethod).join(target)
end end
attr_reader :source, :target attr_reader :source, :target
def printable_target def initialize(cask, source, target: nil)
target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/") super(cask)
@source_string = source.to_s
@target_string = target.to_s
source = cask.staged_path.join(source)
@source = source
target ||= source.basename
@target = self.class.resolve_target(target)
end end
def to_a
[@source_string].tap do |ary|
ary << { target: @target_string } unless @target_string.empty?
end
end
def summarize
target_string = @target_string.empty? ? "" : " -> #{@target_string}"
"#{@source_string}#{target_string}"
end
private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze
# Try to make the asset searchable under the target name. Spotlight # Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App # respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles. # bundles. Alfred 2.2 respects it even for App bundles.
def add_altname_metadata(file, altname) def add_altname_metadata(file, altname, command: nil)
return if altname.casecmp(file.basename).zero? return if altname.to_s.casecmp(file.basename.to_s).zero?
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
altnames = @command.run("/usr/bin/xattr", altnames = command.run("/usr/bin/xattr",
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s], args: ["-p", ALT_NAME_ATTRIBUTE, file],
print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1') print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1')
odebug "Existing metadata is: '#{altnames}'" odebug "Existing metadata is: '#{altnames}'"
altnames.concat(", ") unless altnames.empty? altnames.concat(", ") unless altnames.empty?
altnames.concat(%Q("#{altname}")) altnames.concat(%Q("#{altname}"))
altnames = "(#{altnames})" altnames = "(#{altnames})"
# Some packges are shipped as u=rx (e.g. Bitcoin Core) # Some packages are shipped as u=rx (e.g. Bitcoin Core)
@command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath]) command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath])
@command.run!("/usr/bin/xattr", command.run!("/usr/bin/xattr",
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file], args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file],
print_stderr: false) print_stderr: false)
end end
def each_artifact def printable_target
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact| target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/")
load_specification(artifact)
yield
end
end
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise CaskInvalidError if source_string.nil?
@source = @cask.staged_path.join(source_string)
if target_hash
raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.extend(HashValidator).assert_valid_keys(:target)
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
else
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
end
end end
end end
end end

View File

@ -1,10 +1,22 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class StageOnly < Base class StageOnly < AbstractArtifact
def self.artifact_dsl_key def self.from_args(cask, *args)
:stage_only if args != [true]
raise CaskInvalidError.new(cask.token, "'stage_only' takes only a single argument: true")
end
new(cask)
end
def initialize(cask)
super(cask)
end
def to_a
[true]
end end
end end
end end

View File

@ -3,11 +3,11 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Suite < Moved class Suite < Moved
def self.artifact_english_name def self.english_name
"App Suite" "App Suite"
end end
def self.artifact_dirmethod def self.dirmethod
:appdir :appdir
end end
end end

View File

@ -8,47 +8,18 @@ module Hbc
end end
def self.english_description def self.english_description
"#{artifact_english_name} #{link_type_english_name}s" "#{english_name} #{link_type_english_name}s"
end end
def install_phase def install_phase(**options)
each_artifact(&method(:link)) link(**options)
end end
def uninstall_phase def uninstall_phase(**options)
each_artifact(&method(:unlink)) unlink(**options)
end end
private def summarize_installed
def link
unless source.exist?
raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source '#{source}' is not there."
end
if target.exist? && !target.symlink?
raise CaskError, "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking."
end
ohai "Linking #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'."
create_filesystem_link(source, target)
end
def unlink
return unless target.symlink?
ohai "Unlinking #{self.class.artifact_english_name} '#{target}'."
target.delete
end
def create_filesystem_link(source, target)
target.dirname.mkpath
@command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target])
add_altname_metadata source, target.basename.to_s
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.symlink? && target.exist? && target.readlink.exist? if target.symlink? && target.exist? && target.readlink.exist?
"#{printable_target} -> #{target.readlink} (#{target.readlink.abv})" "#{printable_target} -> #{target.readlink} (#{target.readlink.abv})"
else else
@ -61,6 +32,33 @@ module Hbc
Formatter.error(string, label: "Broken Link") Formatter.error(string, label: "Broken Link")
end end
end end
private
def link(**options)
unless source.exist?
raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source '#{source}' is not there."
end
if target.exist? && !target.symlink?
raise CaskError, "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'; not linking."
end
ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'."
create_filesystem_link(**options)
end
def unlink(**)
return unless target.symlink?
ohai "Unlinking #{self.class.english_name} '#{target}'."
target.delete
end
def create_filesystem_link(command: nil, **_)
target.dirname.mkpath
command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target])
add_altname_metadata(source, target.basename, command: command)
end
end end
end end
end end

View File

@ -1,10 +1,10 @@
require "hbc/artifact/uninstall_base" require "hbc/artifact/abstract_uninstall"
module Hbc module Hbc
module Artifact module Artifact
class Uninstall < UninstallBase class Uninstall < AbstractUninstall
def uninstall_phase def uninstall_phase(**options)
dispatch_uninstall_directives dispatch_uninstall_directives(**options)
end end
end end
end end

View File

@ -1,10 +1,10 @@
require "hbc/artifact/uninstall_base" require "hbc/artifact/abstract_uninstall"
module Hbc module Hbc
module Artifact module Artifact
class Zap < UninstallBase class Zap < AbstractUninstall
def zap_phase def zap_phase(**options)
dispatch_uninstall_directives dispatch_uninstall_directives(**options)
end end
end end
end end

View File

@ -214,12 +214,10 @@ module Hbc
end end
def check_generic_artifacts def check_generic_artifacts
cask.artifacts[:artifact].each do |source, target_hash| cask.artifacts[:artifact].each do |artifact|
unless target_hash.is_a?(Hash) && target_hash[:target] unless artifact.target.absolute?
add_error "target required for generic artifact #{source}" add_error "target must be absolute path for #{artifact.class.english_name} #{artifact.source}"
next
end end
add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute?
end end
end end

View File

@ -17,7 +17,7 @@ module Hbc
@token = token @token = token
@sourcefile_path = sourcefile_path @sourcefile_path = sourcefile_path
@tap = tap @tap = tap
@dsl = DSL.new(@token) @dsl = DSL.new(self)
return unless block_given? return unless block_given?
@dsl.instance_eval(&block) @dsl.instance_eval(&block)
@dsl.language_eval @dsl.language_eval
@ -41,6 +41,14 @@ module Hbc
.reverse .reverse
end end
def full_name
if @tap.nil? || @tap == Hbc.default_tap
token
else
"#{@tap}/#{token}"
end
end
def installed? def installed?
!versions.empty? !versions.empty?
end end

View File

@ -56,7 +56,7 @@ module Hbc
class FromURILoader < FromPathLoader class FromURILoader < FromPathLoader
def self.can_load?(ref) def self.can_load?(ref)
ref.to_s.match?(::URI.regexp) ref.to_s.match?(::URI::DEFAULT_PARSER.make_regexp)
end end
attr_reader :url attr_reader :url
@ -116,6 +116,22 @@ module Hbc
end end
end end
class FromInstanceLoader
attr_reader :cask
def self.can_load?(ref)
ref.is_a?(Cask)
end
def initialize(cask)
@cask = cask
end
def load
cask
end
end
class NullLoader < FromPathLoader class NullLoader < FromPathLoader
def self.can_load?(*) def self.can_load?(*)
true true
@ -149,6 +165,7 @@ module Hbc
def self.for(ref) def self.for(ref)
[ [
FromInstanceLoader,
FromURILoader, FromURILoader,
FromTapLoader, FromTapLoader,
FromTapPathLoader, FromTapPathLoader,

View File

@ -42,41 +42,32 @@ module Hbc
@args = process_arguments(*args) @args = process_arguments(*args)
end end
def self.warn_unavailable_with_suggestion(cask_token, e)
exact_match, partial_matches = Search.search(cask_token)
error_message = e.message
if exact_match
error_message.concat(" Did you mean:\n#{exact_match}")
elsif !partial_matches.empty?
error_message.concat(" Did you mean one of:\n")
.concat(Formatter.columns(partial_matches.take(20)))
end
onoe error_message
end
private private
def casks(alternative: -> { [] }) def casks(alternative: -> { [] })
return to_enum(:casks, alternative: alternative) unless block_given? return @casks if defined?(@casks)
count = 0
casks = args.empty? ? alternative.call : args casks = args.empty? ? alternative.call : args
@casks = casks.map { |cask| CaskLoader.load(cask) }
rescue CaskUnavailableError => e
reason = [e.reason, suggestion_message(e.token)].join(" ")
raise e.class.new(e.token, reason)
end
casks.each do |cask_or_token| def suggestion_message(cask_token)
begin exact_match, partial_matches = Search.search(cask_token)
yield cask_or_token.respond_to?(:token) ? cask_or_token : CaskLoader.load(cask_or_token)
count += 1 if exact_match.nil? && partial_matches.count == 1
rescue CaskUnavailableError => e exact_match = partial_matches.first
cask_token = cask_or_token
self.class.warn_unavailable_with_suggestion cask_token, e
rescue CaskError => e
onoe e.message
end
end end
return :empty if casks.length.zero? if exact_match
(count == casks.length) ? :complete : :incomplete "Did you mean “#{exact_match}”?"
elsif !partial_matches.empty?
"Did you mean one of these?\n"
.concat(Formatter.columns(partial_matches.take(20)))
else
""
end
end end
end end
end end

View File

@ -7,10 +7,6 @@ module Hbc
end end
def run def run
raise CaskError, "Cat incomplete." if cat_casks == :incomplete
end
def cat_casks
casks.each do |cask| casks.each do |cask|
puts File.open(cask.sourcefile_path, &:read) puts File.open(cask.sourcefile_path, &:read)
end end

View File

@ -4,21 +4,18 @@ module Hbc
def initialize(*) def initialize(*)
super super
raise CaskUnspecifiedError if args.empty? raise CaskUnspecifiedError if args.empty?
raise ArgumentError, "Only one Cask can be created at a time." if args.count > 1 raise ArgumentError, "Only one Cask can be edited at a time." if args.count > 1
end end
def run def run
cask_token = args.first cask = casks.first
cask_path = begin cask_path = cask.sourcefile_path
CaskLoader.load(cask_token).sourcefile_path odebug "Opening editor for Cask #{cask.token}"
rescue CaskUnavailableError => e
reason = e.reason.empty? ? "" : "#{e.reason} "
reason.concat("Run #{Formatter.identifier("brew cask create #{e.token}")} to create a new Cask.")
raise e.class.new(e.token, reason)
end
odebug "Opening editor for Cask #{cask_token}"
exec_editor cask_path exec_editor cask_path
rescue CaskUnavailableError => e
reason = e.reason.empty? ? "" : "#{e.reason} "
reason.concat("Run #{Formatter.identifier("brew cask create #{e.token}")} to create a new Cask.")
raise e.class.new(e.token, reason)
end end
def self.help def self.help

View File

@ -9,10 +9,6 @@ module Hbc
end end
def run def run
raise CaskError, "Fetch incomplete." if fetch_casks == :incomplete
end
def fetch_casks
casks.each do |cask| casks.each do |cask|
ohai "Downloading external files for Cask #{cask}" ohai "Downloading external files for Cask #{cask}"
downloaded_path = Download.new(cask, force: force?).perform downloaded_path = Download.new(cask, force: force?).perform

View File

@ -69,13 +69,11 @@ module Hbc
def self.artifact_info(cask) def self.artifact_info(cask)
ohai "Artifacts" ohai "Artifacts"
DSL::ORDINARY_ARTIFACT_TYPES.each do |type| DSL::ORDINARY_ARTIFACT_CLASSES.flat_map { |klass| klass.for_cask(cask) }
next if cask.artifacts[type].empty? .select { |artifact| artifact.respond_to?(:install_phase) }
cask.artifacts[type].each do |artifact| .each do |artifact|
activatable_item = (type == :stage_only) ? "<none>" : artifact.first puts artifact.to_s
puts "#{activatable_item} (#{type})" end
end
end
end end
end end
end end

View File

@ -10,10 +10,6 @@ module Hbc
end end
def run def run
raise CaskError, "Install incomplete." if install_casks == :incomplete
end
def install_casks
casks.each do |cask| casks.each do |cask|
begin begin
Installer.new(cask, binaries: binaries?, Installer.new(cask, binaries: binaries?,

View File

@ -12,7 +12,7 @@ module Hbc
if args.all? { |t| t =~ %r{^https?://} && t !~ /\.rb$/ } if args.all? { |t| t =~ %r{^https?://} && t !~ /\.rb$/ }
self.class.appcask_checkpoint_for_url(args) self.class.appcask_checkpoint_for_url(args)
else else
self.class.appcask_checkpoint(args, calculate?) self.class.appcask_checkpoint(casks, calculate?)
end end
end end
@ -23,33 +23,27 @@ module Hbc
end end
end end
def self.appcask_checkpoint(cask_tokens, calculate) def self.appcask_checkpoint(casks, calculate)
count = 0 casks.each do |cask|
cask_tokens.each do |cask_token|
cask = CaskLoader.load(cask_token)
if cask.appcast.nil? if cask.appcast.nil?
opoo "Cask '#{cask}' is missing an `appcast` stanza." opoo "Cask '#{cask}' is missing an `appcast` stanza."
else else
if calculate checkpoint = if calculate
result = cask.appcast.calculate_checkpoint result = cask.appcast.calculate_checkpoint
result[:checkpoint]
checkpoint = result[:checkpoint]
else else
checkpoint = cask.appcast.checkpoint cask.appcast.checkpoint
end end
if checkpoint.nil? if calculate && checkpoint.nil?
onoe "Could not retrieve `appcast` checkpoint for cask '#{cask}': #{result[:command_result].stderr}" onoe "Could not retrieve `appcast` checkpoint for cask '#{cask}': #{result[:command_result].stderr}"
elsif casks.count > 1
puts "#{checkpoint} #{cask}"
else else
puts((cask_tokens.count > 1) ? "#{checkpoint} #{cask}" : checkpoint) puts checkpoint
count += 1
end end
end end
end end
count == cask_tokens.count
end end
def self.help def self.help

View File

@ -7,10 +7,6 @@ module Hbc
end end
def run def run
raise CaskError, "Dump incomplete." if dump_casks == :incomplet
end
def dump_casks
casks.each(&:dumpcask) casks.each(&:dumpcask)
end end

View File

@ -3,7 +3,7 @@ module Hbc
class InternalStanza < AbstractInternalCommand class InternalStanza < AbstractInternalCommand
# Syntax # Syntax
# #
# brew cask _stanza <stanza_name> [ --table | --yaml | --inspect | --quiet ] [ <cask_token> ... ] # brew cask _stanza <stanza_name> [ --quiet ] [ --table | --yaml ] [ <cask_token> ... ]
# #
# If no tokens are given, then data for all Casks is returned. # If no tokens are given, then data for all Casks is returned.
# #
@ -14,41 +14,16 @@ module Hbc
# Examples # Examples
# #
# brew cask _stanza appcast --table # brew cask _stanza appcast --table
# brew cask _stanza app --table alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza app --table alfred google-chrome adium vagrant
# brew cask _stanza url --table alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza url --table alfred google-chrome adium vagrant
# brew cask _stanza version --table alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza version --table alfred google-chrome adium vagrant
# brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza artifacts --table alfred google-chrome adium vagrant
# brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza artifacts --table --yaml alfred google-chrome adium vagrant
# #
# TODO: this should be retrievable from Hbc::DSL ARTIFACTS =
ARTIFACTS = Set.new [ DSL::ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) +
:app, DSL::ARTIFACT_BLOCK_CLASSES.map(&:dsl_key)
:suite,
:artifact,
:prefpane,
:qlplugin,
:dictionary,
:font,
:service,
:colorpicker,
:binary,
:input_method,
:internet_plugin,
:audio_unit_plugin,
:vst_plugin,
:vst3_plugin,
:screen_saver,
:pkg,
:installer,
:stage_only,
:nested_container,
:uninstall,
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
]
option "--table", :table, false option "--table", :table, false
option "--quiet", :quiet, false option "--quiet", :quiet, false
@ -68,16 +43,9 @@ module Hbc
@stanza = args.shift.to_sym @stanza = args.shift.to_sym
@format = :to_yaml if yaml? @format = :to_yaml if yaml?
@format = :inspect if inspect?
end end
def run def run
return unless print_stanzas == :incomplete
exit 1 if quiet?
raise CaskError, "Print incomplete."
end
def print_stanzas
if ARTIFACTS.include?(stanza) if ARTIFACTS.include?(stanza)
artifact_name = stanza artifact_name = stanza
@stanza = :artifacts @stanza = :artifacts
@ -93,7 +61,7 @@ module Hbc
end end
begin begin
value = cask.send(@stanza) value = cask.send(stanza)
rescue StandardError rescue StandardError
opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet? opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet?
puts "" puts ""
@ -106,11 +74,25 @@ module Hbc
next next
end end
value = value.fetch(artifact_name).to_a.flatten if artifact_name if stanza == :artifacts
value = Hash[
value.map do |k, v|
v = v.map do |a|
next a.to_a if a.respond_to?(:to_a)
next a.to_h if a.respond_to?(:to_h)
a
end
if @format [k, v]
puts value.send(@format) end
elsif artifact_name || value.is_a?(Symbol) ]
value = value.fetch(artifact_name) if artifact_name
end
if format
puts value.send(format)
elsif value.is_a?(Symbol)
puts value.inspect puts value.inspect
else else
puts value.to_s puts value.to_s

View File

@ -3,6 +3,7 @@ module Hbc
class List < AbstractCommand class List < AbstractCommand
option "-1", :one, false option "-1", :one, false
option "--versions", :versions, false option "--versions", :versions, false
option "--full-name", :full_name, false
option "-l", (lambda do |*| option "-l", (lambda do |*|
one = true # rubocop:disable Lint/UselessAssignment one = true # rubocop:disable Lint/UselessAssignment
@ -10,8 +11,7 @@ module Hbc
end) end)
def run def run
retval = args.any? ? list : list_installed args.any? ? list : list_installed
raise CaskError, "Listing incomplete." if retval == :incomplete
end end
def list def list
@ -30,9 +30,9 @@ module Hbc
end end
def self.list_artifacts(cask) def self.list_artifacts(cask)
Artifact.for_cask(cask).each do |artifact| Artifact.for_cask(cask).group_by(&:class).each do |klass, artifacts|
summary = artifact.summary next unless klass.respond_to?(:english_description)
ohai summary[:english_description], summary[:contents] unless summary.empty? ohai klass.english_description, artifacts.map(&:summarize_installed)
end end
end end
@ -43,11 +43,11 @@ module Hbc
puts installed_casks.map(&:to_s) puts installed_casks.map(&:to_s)
elsif versions? elsif versions?
puts installed_casks.map(&self.class.method(:format_versioned)) puts installed_casks.map(&self.class.method(:format_versioned))
elsif full_name?
puts installed_casks.map(&:full_name).sort &tap_and_name_comparison
elsif !installed_casks.empty? elsif !installed_casks.empty?
puts Formatter.columns(installed_casks.map(&:to_s)) puts Formatter.columns(installed_casks.map(&:to_s))
end end
installed_casks.empty? ? :empty : :complete
end end
def self.format_versioned(cask) def self.format_versioned(cask)

View File

@ -1,7 +1,7 @@
module Hbc module Hbc
class CLI class CLI
class Reinstall < Install class Reinstall < Install
def install_casks def run
casks.each do |cask| casks.each do |cask|
Installer.new(cask, binaries: binaries?, Installer.new(cask, binaries: binaries?,
verbose: verbose?, verbose: verbose?,

View File

@ -2,8 +2,12 @@ module Hbc
class CLI class CLI
class Search < AbstractCommand class Search < AbstractCommand
def run def run
results = self.class.search(*args) if args.empty?
self.class.render_results(*results) puts Formatter.columns(CLI.nice_listing(Hbc.all_tokens))
else
results = self.class.search(*args)
self.class.render_results(*results)
end
end end
def self.extract_regexp(string) def self.extract_regexp(string)
@ -15,8 +19,17 @@ module Hbc
end end
def self.search_remote(query) def self.search_remote(query)
matches = GitHub.search_code(user: "caskroom", path: "Casks", matches = begin
filename: query, extension: "rb") GitHub.search_code(
user: "caskroom",
path: "Casks",
filename: query,
extension: "rb",
)
rescue GitHub::Error => error
opoo "Error searching on GitHub: #{error}\n"
[]
end
matches.map do |match| matches.map do |match|
tap = Tap.fetch(match["repository"]["full_name"]) tap = Tap.fetch(match["repository"]["full_name"])
next if tap.installed? next if tap.installed?

View File

@ -9,10 +9,6 @@ module Hbc
end end
def run def run
raise CaskError, "Uninstall incomplete." if uninstall_casks == :incomplete
end
def uninstall_casks
casks.each do |cask| casks.each do |cask|
odebug "Uninstalling Cask #{cask}" odebug "Uninstalling Cask #{cask}"

View File

@ -9,10 +9,6 @@ module Hbc
end end
def run def run
raise CaskError, "Zap incomplete." if zap_casks == :incomplete
end
def zap_casks
casks.each do |cask| casks.each do |cask|
odebug "Zapping Cask #{cask}" odebug "Zapping Cask #{cask}"
Installer.new(cask, verbose: verbose?, force: force?).zap Installer.new(cask, verbose: verbose?, force: force?).zap

View File

@ -16,7 +16,7 @@ module Hbc
def target_file def target_file
return @path.basename if @nested return @path.basename if @nested
URI.decode(File.basename(@cask.url.path)) CGI.unescape(File.basename(@cask.url.path))
end end
end end
end end

View File

@ -1,6 +1,8 @@
require "set" require "set"
require "locale" require "locale"
require "hbc/artifact"
require "hbc/dsl/appcast" require "hbc/dsl/appcast"
require "hbc/dsl/base" require "hbc/dsl/base"
require "hbc/dsl/caveats" require "hbc/dsl/caveats"
@ -8,7 +10,6 @@ require "hbc/dsl/conflicts_with"
require "hbc/dsl/container" require "hbc/dsl/container"
require "hbc/dsl/depends_on" require "hbc/dsl/depends_on"
require "hbc/dsl/gpg" require "hbc/dsl/gpg"
require "hbc/dsl/installer"
require "hbc/dsl/postflight" require "hbc/dsl/postflight"
require "hbc/dsl/preflight" require "hbc/dsl/preflight"
require "hbc/dsl/stanza_proxy" require "hbc/dsl/stanza_proxy"
@ -18,39 +19,35 @@ require "hbc/dsl/version"
module Hbc module Hbc
class DSL class DSL
ORDINARY_ARTIFACT_TYPES = [ ORDINARY_ARTIFACT_CLASSES = [
:app, Artifact::Installer,
:artifact, Artifact::App,
:audio_unit_plugin, Artifact::Artifact,
:binary, Artifact::AudioUnitPlugin,
:colorpicker, Artifact::Binary,
:dictionary, Artifact::Colorpicker,
:font, Artifact::Dictionary,
:input_method, Artifact::Font,
:internet_plugin, Artifact::InputMethod,
:pkg, Artifact::InternetPlugin,
:prefpane, Artifact::Pkg,
:qlplugin, Artifact::Prefpane,
:screen_saver, Artifact::Qlplugin,
:service, Artifact::ScreenSaver,
:stage_only, Artifact::Service,
:suite, Artifact::StageOnly,
:vst_plugin, Artifact::Suite,
:vst3_plugin, Artifact::VstPlugin,
Artifact::Vst3Plugin,
Artifact::Uninstall,
Artifact::Zap,
].freeze ].freeze
ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze ACTIVATABLE_ARTIFACT_TYPES = (ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) - [:stage_only]).freeze
SPECIAL_ARTIFACT_TYPES = [ ARTIFACT_BLOCK_CLASSES = [
:uninstall, Artifact::PreflightBlock,
:zap, Artifact::PostflightBlock,
].freeze
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
].freeze ].freeze
DSL_METHODS = Set.new [ DSL_METHODS = Set.new [
@ -72,15 +69,15 @@ module Hbc
:url, :url,
:version, :version,
:appdir, :appdir,
*ORDINARY_ARTIFACT_TYPES, *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),
*ACTIVATABLE_ARTIFACT_TYPES, *ACTIVATABLE_ARTIFACT_TYPES,
*SPECIAL_ARTIFACT_TYPES, *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
*ARTIFACT_BLOCK_TYPES,
].freeze ].freeze
attr_reader :token attr_reader :token, :cask
def initialize(token) def initialize(cask)
@token = token @cask = cask
@token = cask.token
end end
def name(*args) def name(*args)
@ -93,12 +90,14 @@ module Hbc
return instance_variable_get("@#{stanza}") if should_return return instance_variable_get("@#{stanza}") if should_return
if instance_variable_defined?("@#{stanza}") if instance_variable_defined?("@#{stanza}")
raise CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once") raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only appear once.")
end end
instance_variable_set("@#{stanza}", yield) instance_variable_set("@#{stanza}", yield)
rescue CaskInvalidError
raise
rescue StandardError => e rescue StandardError => e
raise CaskInvalidError.new(token, "'#{stanza}' stanza failed with: #{e}") raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}")
end end
def homepage(homepage = nil) def homepage(homepage = nil)
@ -113,7 +112,7 @@ module Hbc
return unless default return unless default
unless @language_blocks.default.nil? unless @language_blocks.default.nil?
raise CaskInvalidError.new(token, "Only one default language may be defined") raise CaskInvalidError.new(cask, "Only one default language may be defined.")
end end
@language_blocks.default = block @language_blocks.default = block
@ -162,8 +161,8 @@ module Hbc
begin begin
DSL::Container.new(*args).tap do |container| DSL::Container.new(*args).tap do |container|
# TODO: remove this backward-compatibility section after removing nested_container # TODO: remove this backward-compatibility section after removing nested_container
if container && container.nested if container&.nested
artifacts[:nested_container] << container.nested artifacts[:nested_container] << Artifact::NestedContainer.new(cask, container.nested)
end end
end end
end end
@ -173,7 +172,7 @@ module Hbc
def version(arg = nil) def version(arg = nil)
set_unique_stanza(:version, arg.nil?) do set_unique_stanza(:version, arg.nil?) do
if !arg.is_a?(String) && arg != :latest if !arg.is_a?(String) && arg != :latest
raise CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") raise CaskInvalidError.new(cask, "invalid 'version' value: '#{arg.inspect}'")
end end
DSL::Version.new(arg) DSL::Version.new(arg)
end end
@ -182,7 +181,7 @@ module Hbc
def sha256(arg = nil) def sha256(arg = nil)
set_unique_stanza(:sha256, arg.nil?) do set_unique_stanza(:sha256, arg.nil?) do
if !arg.is_a?(String) && arg != :no_check if !arg.is_a?(String) && arg != :no_check
raise CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'")
end end
arg arg
end end
@ -195,7 +194,7 @@ module Hbc
begin begin
@depends_on.load(*args) @depends_on.load(*args)
rescue RuntimeError => e rescue RuntimeError => e
raise CaskInvalidError.new(token, e) raise CaskInvalidError.new(cask, e)
end end
@depends_on @depends_on
end end
@ -237,39 +236,29 @@ module Hbc
set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates } set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates }
end end
ORDINARY_ARTIFACT_TYPES.each do |type| ORDINARY_ARTIFACT_CLASSES.each do |klass|
type = klass.dsl_key
define_method(type) do |*args| define_method(type) do |*args|
if type == :stage_only begin
if args != [true] if [*artifacts.keys, type].include?(:stage_only) && (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).any?
raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true") raise CaskInvalidError.new(cask, "'stage_only' must be the only activatable artifact.")
end end
unless (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? artifacts[type].add(klass.from_args(cask, *args))
raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") rescue CaskInvalidError
end raise
rescue StandardError => e
raise CaskInvalidError.new(cask, "invalid '#{klass.dsl_key}' stanza: #{e}")
end end
artifacts[type].add(args)
end end
end end
def installer(*args) ARTIFACT_BLOCK_CLASSES.each do |klass|
return artifacts[:installer] if args.empty? [klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key|
artifacts[:installer] << DSL::Installer.new(*args) define_method(dsl_key) do |&block|
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) artifacts[dsl_key] << block
rescue StandardError => e end
raise CaskInvalidError.new(token, e)
end
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
end
end
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
end end
end end

View File

@ -1,32 +0,0 @@
module Hbc
class DSL
class Installer
VALID_KEYS = Set.new [
:manual,
:script,
]
attr_accessor(*VALID_KEYS)
def initialize(*parameters)
raise CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
parameters = {}.merge(*parameters)
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
if parameters.key?(:executable)
raise CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable")
end
parameters[:executable] = parameters[:script]
parameters.delete(:script)
parameters = { script: parameters }
end
unless parameters.keys.length == 1
raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted"
end
key = parameters.keys.first
raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, parameters[key])
end
end
end
end

View File

@ -49,7 +49,7 @@ module Hbc
end end
end end
DIVIDERS.keys.each do |divider| DIVIDERS.each_key do |divider|
define_divider_methods(divider) define_divider_methods(divider)
end end

View File

@ -159,7 +159,7 @@ module Hbc
odebug "Extracting primary container" odebug "Extracting primary container"
FileUtils.mkdir_p @cask.staged_path FileUtils.mkdir_p @cask.staged_path
container = if @cask.container && @cask.container.type container = if @cask.container&.type
Container.from_type(@cask.container.type) Container.from_type(@cask.container.type)
else else
Container.for_path(@downloaded_path, @command) Container.for_path(@downloaded_path, @command)
@ -177,7 +177,7 @@ module Hbc
already_installed_artifacts = [] already_installed_artifacts = []
odebug "Installing artifacts" odebug "Installing artifacts"
artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) artifacts = Artifact.for_cask(@cask)
odebug "#{artifacts.length} artifact/s defined", artifacts odebug "#{artifacts.length} artifact/s defined", artifacts
artifacts.each do |artifact| artifacts.each do |artifact|
@ -188,7 +188,7 @@ module Hbc
next unless binaries? next unless binaries?
end end
artifact.install_phase artifact.install_phase(command: @command, verbose: verbose?, force: force?)
already_installed_artifacts.unshift(artifact) already_installed_artifacts.unshift(artifact)
end end
rescue StandardError => e rescue StandardError => e
@ -196,7 +196,7 @@ module Hbc
already_installed_artifacts.each do |artifact| already_installed_artifacts.each do |artifact|
next unless artifact.respond_to?(:uninstall_phase) next unless artifact.respond_to?(:uninstall_phase)
odebug "Reverting installation of artifact of class #{artifact.class}" odebug "Reverting installation of artifact of class #{artifact.class}"
artifact.uninstall_phase artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
end end
ensure ensure
purge_versioned_files purge_versioned_files
@ -361,7 +361,7 @@ module Hbc
savedir = @cask.metadata_subdir("Casks", timestamp: :now, create: true) savedir = @cask.metadata_subdir("Casks", timestamp: :now, create: true)
FileUtils.copy @cask.sourcefile_path, savedir FileUtils.copy @cask.sourcefile_path, savedir
old_savedir.rmtree unless old_savedir.nil? old_savedir&.rmtree
end end
def uninstall def uninstall
@ -374,25 +374,27 @@ module Hbc
def uninstall_artifacts def uninstall_artifacts
odebug "Un-installing artifacts" odebug "Un-installing artifacts"
artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) artifacts = Artifact.for_cask(@cask)
odebug "#{artifacts.length} artifact/s defined", artifacts odebug "#{artifacts.length} artifact/s defined", artifacts
artifacts.each do |artifact| artifacts.each do |artifact|
next unless artifact.respond_to?(:uninstall_phase) next unless artifact.respond_to?(:uninstall_phase)
odebug "Un-installing artifact of class #{artifact.class}" odebug "Un-installing artifact of class #{artifact.class}"
artifact.uninstall_phase artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
end end
end end
def zap def zap
ohai %Q(Implied "brew cask uninstall #{@cask}") ohai %Q(Implied "brew cask uninstall #{@cask}")
uninstall_artifacts uninstall_artifacts
if Artifact::Zap.me?(@cask) if (zap_stanzas = Artifact::Zap.for_cask(@cask)).empty?
ohai "Dispatching zap stanza"
Artifact::Zap.new(@cask, command: @command).zap_phase
else
opoo "No zap stanza present for Cask '#{@cask}'" opoo "No zap stanza present for Cask '#{@cask}'"
else
ohai "Dispatching zap stanza"
zap_stanzas.each do |stanza|
stanza.zap_phase(command: @command, verbose: verbose?, force: force?)
end
end end
ohai "Removing all staged versions of Cask '#{@cask}'" ohai "Removing all staged versions of Cask '#{@cask}'"
purge_caskroom_path purge_caskroom_path

View File

@ -4,7 +4,7 @@ module Hbc
index = 0 if index == :first index = 0 if index == :first
index = 1 if index == :second index = 1 if index == :second
index = -1 if index == :last index = -1 if index == :last
Hbc.appdir.join(@cask.artifacts[:app].to_a.at(index).first, "Contents", "Info.plist") @cask.artifacts[:app].to_a.at(index).target.join("Contents", "Info.plist")
end end
def plist_exec(cmd) def plist_exec(cmd)

View File

@ -61,7 +61,7 @@ module Hbc
end end
def assert_success def assert_success
return if processed_status && processed_status.success? return if processed_status&.success?
raise CaskCommandFailedError.new(command, processed_output[:stdout], processed_output[:stderr], processed_status) raise CaskCommandFailedError.new(command, processed_output[:stdout], processed_output[:stderr], processed_status)
end end

View File

@ -163,7 +163,7 @@ class Caveats
def plist_caveats def plist_caveats
s = [] s = []
if f.plist || (keg && keg.plist_installed?) if f.plist || (keg&.plist_installed?)
plist_domain = f.plist_path.basename(".plist") plist_domain = f.plist_path.basename(".plist")
# we readlink because this path probably doesn't exist since caveats # we readlink because this path probably doesn't exist since caveats

View File

@ -6,6 +6,10 @@ module Homebrew
module Cleanup module Cleanup
@disk_cleanup_size = 0 @disk_cleanup_size = 0
class << self
attr_reader :disk_cleanup_size
end
module_function module_function
def cleanup def cleanup
@ -21,10 +25,6 @@ module Homebrew
@disk_cleanup_size += path_size @disk_cleanup_size += path_size
end end
def disk_cleanup_size
@disk_cleanup_size
end
def unremovable_kegs def unremovable_kegs
@unremovable_kegs ||= [] @unremovable_kegs ||= []
end end

View File

@ -19,15 +19,13 @@ class String
end end
end end
def cask
$LOAD_PATH.unshift("#{HOMEBREW_LIBRARY_PATH}/cask/lib")
require "hbc"
end
module Homebrew module Homebrew
module_function module_function
def irb def irb
$LOAD_PATH.unshift("#{HOMEBREW_LIBRARY_PATH}/cask/lib")
require "hbc"
if ARGV.include? "--examples" if ARGV.include? "--examples"
puts "'v8'.f # => instance of the v8 formula" puts "'v8'.f # => instance of the v8 formula"
puts ":hub.f.installed?" puts ":hub.f.installed?"

View File

@ -39,15 +39,7 @@ module Homebrew
filtered_list filtered_list
elsif ARGV.named.empty? elsif ARGV.named.empty?
if ARGV.include? "--full-name" if ARGV.include? "--full-name"
full_names = Formula.installed.map(&:full_name).sort do |a, b| full_names = Formula.installed.map(&:full_name).sort &tap_and_name_comparison
if a.include?("/") && !b.include?("/")
1
elsif !a.include?("/") && b.include?("/")
-1
else
a <=> b
end
end
return if full_names.empty? return if full_names.empty?
puts Formatter.columns(full_names) puts Formatter.columns(full_names)
else else

View File

@ -55,7 +55,7 @@ module Homebrew
else else
n, d = ObserverPathnameExtension.counts n, d = ObserverPathnameExtension.counts
print "Pruned #{n} symbolic links " print "Pruned #{n} symbolic links "
print "and #{d} directories " if d > 0 print "and #{d} directories " if d.positive?
puts "from #{HOMEBREW_PREFIX}" puts "from #{HOMEBREW_PREFIX}"
end end
end end

View File

@ -67,7 +67,7 @@ module Homebrew
ohai "Searching blacklisted, migrated and deleted formulae..." ohai "Searching blacklisted, migrated and deleted formulae..."
if reason = Homebrew::MissingFormula.reason(query, silent: true) if reason = Homebrew::MissingFormula.reason(query, silent: true)
if count > 0 if count.positive?
puts puts
puts "If you meant #{query.inspect} specifically:" puts "If you meant #{query.inspect} specifically:"
end end

View File

@ -119,10 +119,10 @@ module Homebrew
when :print when :print
args << "--display-cop-names" if ARGV.include? "--display-cop-names" args << "--display-cop-names" if ARGV.include? "--display-cop-names"
args << "--format" << "simple" if files args << "--format" << "simple" if files
system(cache_env, "rubocop", *args) system(cache_env, "rubocop", "_#{HOMEBREW_RUBOCOP_VERSION}_", *args)
!$CHILD_STATUS.success? !$CHILD_STATUS.success?
when :json when :json
json, _, status = Open3.capture3(cache_env, "rubocop", "--format", "json", *args) json, _, status = Open3.capture3(cache_env, "rubocop", "_#{HOMEBREW_RUBOCOP_VERSION}_", "--format", "json", *args)
# exit status of 1 just means violations were found; other numbers mean # exit status of 1 just means violations were found; other numbers mean
# execution errors. # execution errors.
# exitstatus can also be nil if RuboCop process crashes, e.g. due to # exitstatus can also be nil if RuboCop process crashes, e.g. due to

View File

@ -64,10 +64,10 @@ module Homebrew
if tap.installed? if tap.installed?
info += tap.pinned? ? "pinned" : "unpinned" info += tap.pinned? ? "pinned" : "unpinned"
info += ", private" if tap.private? info += ", private" if tap.private?
if (formula_count = tap.formula_files.size) > 0 if (formula_count = tap.formula_files.size).positive?
info += ", #{Formatter.pluralize(formula_count, "formula")}" info += ", #{Formatter.pluralize(formula_count, "formula")}"
end end
if (command_count = tap.command_files.size) > 0 if (command_count = tap.command_files.size).positive?
info += ", #{Formatter.pluralize(command_count, "command")}" info += ", #{Formatter.pluralize(command_count, "command")}"
end end
info += ", no formulae/commands" if (formula_count + command_count).zero? info += ", no formulae/commands" if (formula_count + command_count).zero?

View File

@ -77,7 +77,7 @@ module Homebrew
def unlinkapps_unlink?(target_app, opts = {}) def unlinkapps_unlink?(target_app, opts = {})
# Skip non-symlinks and symlinks that don't point into the Homebrew prefix. # Skip non-symlinks and symlinks that don't point into the Homebrew prefix.
app = target_app.readlink.to_s if target_app.symlink? app = target_app.readlink.to_s if target_app.symlink?
return false unless app && app.start_with?(*UNLINKAPPS_PREFIXES) return false unless app&.start_with?(*UNLINKAPPS_PREFIXES)
if opts.fetch(:prune, false) if opts.fetch(:prune, false)
!File.exist?(app) # Remove only broken symlinks in prune mode. !File.exist?(app) # Remove only broken symlinks in prune mode.

View File

@ -124,7 +124,7 @@ module Homebrew
return if ENV["HOMEBREW_UPDATE_TEST"] return if ENV["HOMEBREW_UPDATE_TEST"]
core_tap = CoreTap.instance core_tap = CoreTap.instance
return if core_tap.installed? return if core_tap.installed?
CoreTap.ensure_installed! quiet: false CoreTap.ensure_installed!
revision = core_tap.git_head revision = core_tap.git_head
ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision
ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision
@ -203,6 +203,7 @@ module Homebrew
end end
new_homebrew_repository = Pathname.new "/usr/local/Homebrew" new_homebrew_repository = Pathname.new "/usr/local/Homebrew"
new_homebrew_repository.rmdir_if_possible
if new_homebrew_repository.exist? if new_homebrew_repository.exist?
ofail <<-EOS.undent ofail <<-EOS.undent
#{new_homebrew_repository} already exists. #{new_homebrew_repository} already exists.

View File

@ -385,6 +385,12 @@ EOS
if ! git --version >/dev/null 2>&1 if ! git --version >/dev/null 2>&1
then then
# we need a new enough curl to install git
if [[ -n "$HOMEBREW_SYSTEM_CURL_TOO_OLD" &&
! -x "$HOMEBREW_PREFIX/opt/curl/bin/curl" ]]
then
brew install curl
fi
# we cannot install brewed git if homebrew/core is unavailable. # we cannot install brewed git if homebrew/core is unavailable.
[[ -d "$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core" ]] && brew install git [[ -d "$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core" ]] && brew install git
unset GIT_EXECUTABLE unset GIT_EXECUTABLE

View File

@ -13,16 +13,16 @@ if [[ -n "$HOMEBREW_MACOS" ]]
then then
if [[ "$HOMEBREW_PROCESSOR" = "Intel" ]] if [[ "$HOMEBREW_PROCESSOR" = "Intel" ]]
then then
ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.0.0-p648.leopard_64.bottle.tar.gz" ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.3.3.leopard_64.bottle.1.tar.gz"
ruby_SHA="5c1240abe4be91c9774a0089c2a38a8ccfff87c009e8e5786730c659d5e633f7" ruby_SHA="34ce9e4c9c1be28db564d744165aa29291426f8a3d2ef806ba4f0b9175aedb2b"
else else
ruby_URL="" ruby_URL=""
ruby_SHA="" ruby_SHA=""
fi fi
elif [[ -n "$HOMEBREW_LINUX" ]] elif [[ -n "$HOMEBREW_LINUX" ]]
then then
ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.0.0-p648.x86_64_linux.bottle.tar.gz" ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.3.3.x86_64_linux.bottle.tar.gz"
ruby_SHA="dbb5118a22a6a75cc77e62544a3d8786d383fab1bdaf8c154951268807357bf0" ruby_SHA="543c18bd33a300e6c16671437b1e0f17b03bb64e6a485fc15ff7de1eb1a0bc2a"
fi fi
fetch() { fetch() {
@ -45,20 +45,25 @@ fetch() {
curl_args[${#curl_args[*]}]="--progress-bar" curl_args[${#curl_args[*]}]="--progress-bar"
fi fi
if [[ "$HOMEBREW_MACOS_VERSION_NUMERIC" -lt "100600" ]]
then
curl_args[${#curl_args[*]}]="--insecure"
fi
temporary_path="$CACHED_LOCATION.incomplete" temporary_path="$CACHED_LOCATION.incomplete"
mkdir -p "$HOMEBREW_CACHE" mkdir -p "$HOMEBREW_CACHE"
[[ -n "$HOMEBREW_QUIET" ]] || echo "==> Downloading $VENDOR_URL" [[ -n "$HOMEBREW_QUIET" ]] || echo "==> Downloading $VENDOR_URL" >&2
if [[ -f "$CACHED_LOCATION" ]] if [[ -f "$CACHED_LOCATION" ]]
then then
[[ -n "$HOMEBREW_QUIET" ]] || echo "Already downloaded: $CACHED_LOCATION" [[ -n "$HOMEBREW_QUIET" ]] || echo "Already downloaded: $CACHED_LOCATION" >&2
else else
if [[ -f "$temporary_path" ]] if [[ -f "$temporary_path" ]]
then then
"$HOMEBREW_CURL" "${curl_args[@]}" -C - "$VENDOR_URL" -o "$temporary_path" "$HOMEBREW_CURL" "${curl_args[@]}" -C - "$VENDOR_URL" -o "$temporary_path"
if [[ $? -eq 33 ]] if [[ $? -eq 33 ]]
then then
[[ -n "$HOMEBREW_QUIET" ]] || echo "Trying a full download" [[ -n "$HOMEBREW_QUIET" ]] || echo "Trying a full download" >&2
rm -f "$temporary_path" rm -f "$temporary_path"
"$HOMEBREW_CURL" "${curl_args[@]}" "$VENDOR_URL" -o "$temporary_path" "$HOMEBREW_CURL" "${curl_args[@]}" "$VENDOR_URL" -o "$temporary_path"
fi fi
@ -135,7 +140,7 @@ install() {
fi fi
safe_cd "$VENDOR_DIR" safe_cd "$VENDOR_DIR"
[[ -n "$HOMEBREW_QUIET" ]] || echo "==> Unpacking $(basename "$VENDOR_URL")" [[ -n "$HOMEBREW_QUIET" ]] || echo "==> Pouring $(basename "$VENDOR_URL")" >&2
tar "$tar_args" "$CACHED_LOCATION" tar "$tar_args" "$CACHED_LOCATION"
safe_cd "$VENDOR_DIR/portable-$VENDOR_NAME" safe_cd "$VENDOR_DIR/portable-$VENDOR_NAME"

View File

@ -3,4 +3,8 @@ module SharedEnvExtension
odeprecated "ENV.j1", "ENV.deparallelize" odeprecated "ENV.j1", "ENV.deparallelize"
deparallelize deparallelize
end end
def java_cache
# odeprecated "ENV.java_cache"
end
end end

View File

@ -16,7 +16,7 @@ end
# This formula serves as the base class for several very similar # This formula serves as the base class for several very similar
# formulae for Amazon Web Services related tools. # formulae for Amazon Web Services related tools.
class AmazonWebServicesFormula < Formula class AmazonWebServicesFormula < Formula
# Use this method to peform a standard install for Java-based tools, # Use this method to perform a standard install for Java-based tools,
# keeping the .jars out of HOMEBREW_PREFIX/lib # keeping the .jars out of HOMEBREW_PREFIX/lib
def install def install
odeprecated "AmazonWebServicesFormula#install", "Formula#install" odeprecated "AmazonWebServicesFormula#install", "Formula#install"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# RuboCop version used for `brew style` and `brew cask style` # RuboCop version used for `brew style` and `brew cask style`
HOMEBREW_RUBOCOP_VERSION = "0.49.1".freeze HOMEBREW_RUBOCOP_VERSION = "0.50.0"
HOMEBREW_RUBOCOP_CASK_VERSION = "~> 0.13.1".freeze # has to be updated when RuboCop version changes HOMEBREW_RUBOCOP_CASK_VERSION = "~> 0.14.2" # has to be updated when RuboCop version changes

View File

@ -57,7 +57,7 @@ module Debrew
input.chomp! input.chomp!
i = input.to_i i = input.to_i
if i > 0 if i.positive?
choice = menu.entries[i - 1] choice = menu.entries[i - 1]
else else
possible = menu.entries.find_all { |e| e.name.start_with?(input) } possible = menu.entries.find_all { |e| e.name.start_with?(input) }

View File

@ -16,7 +16,7 @@ module IRB
workspace = WorkSpace.new(binding) workspace = WorkSpace.new(binding)
irb = Irb.new(workspace) irb = Irb.new(workspace)
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @CONF[:IRB_RC]&.call(irb.context)
@CONF[:MAIN_CONTEXT] = irb.context @CONF[:MAIN_CONTEXT] = irb.context
trap("SIGINT") do trap("SIGINT") do

View File

@ -51,7 +51,7 @@ class Dependency
end end
def modify_build_environment def modify_build_environment
env_proc.call unless env_proc.nil? env_proc&.call
end end
def inspect def inspect

View File

@ -54,6 +54,7 @@ module Homebrew
def audit def audit
Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) if ARGV.switch? "D" Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) if ARGV.switch? "D"
Homebrew.auditing = true
formula_count = 0 formula_count = 0
problem_count = 0 problem_count = 0
@ -201,19 +202,24 @@ class FormulaAuditor
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact @specs = %w[stable devel head].map { |s| formula.send(s) }.compact
end end
def self.check_http_content(url, name, user_agents: [:default]) def self.check_http_content(url, user_agents: [:default], check_content: false, strict: false, require_http: false)
return unless url.start_with? "http" return unless url.start_with? "http"
details = nil details = nil
user_agent = nil user_agent = nil
hash_needed = url.start_with?("http:") && name != "curl" hash_needed = url.start_with?("http:") && !require_http
user_agents.each do |ua| user_agents.each do |ua|
details = http_content_headers_and_checksum(url, hash_needed: hash_needed, user_agent: ua) details = http_content_headers_and_checksum(url, hash_needed: hash_needed, user_agent: ua)
user_agent = ua user_agent = ua
break if details[:status].to_s.start_with?("2") break if details[:status].to_s.start_with?("2")
end end
return "The URL #{url} is not reachable" unless details[:status] unless details[:status]
# Hack around https://github.com/Homebrew/brew/issues/3199
return if MacOS.version == :el_capitan
return "The URL #{url} is not reachable"
end
unless details[:status].start_with? "2" unless details[:status].start_with? "2"
return "The URL #{url} is not reachable (HTTP status code #{details[:status]})" return "The URL #{url} is not reachable (HTTP status code #{details[:status]})"
end end
@ -236,8 +242,32 @@ class FormulaAuditor
details[:content_length] == secure_details[:content_length] details[:content_length] == secure_details[:content_length]
file_match = details[:file_hash] == secure_details[:file_hash] file_match = details[:file_hash] == secure_details[:file_hash]
return if !etag_match && !content_length_match && !file_match if etag_match || content_length_match || file_match
"The URL #{url} could use HTTPS rather than HTTP" return "The URL #{url} should use HTTPS rather than HTTP"
end
return unless check_content
no_protocol_file_contents = %r{https?:\\?/\\?/}
details[:file] = details[:file].gsub(no_protocol_file_contents, "/")
secure_details[:file] = secure_details[:file].gsub(no_protocol_file_contents, "/")
# Check for the same content after removing all protocols
if details[:file] == secure_details[:file]
return "The URL #{url} should use HTTPS rather than HTTP"
end
return unless strict
# Same size, different content after normalization
# (typical causes: Generated ID, Timestamp, Unix time)
if details[:file].length == secure_details[:file].length
return "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser."
end
lenratio = (100 * secure_details[:file].length / details[:file].length).to_i
return unless (90..110).cover?(lenratio)
"The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser."
end end
def self.http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) def self.http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default)
@ -260,6 +290,7 @@ class FormulaAuditor
etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2], etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2],
content_length: headers[/Content-Length: (\d+)/, 1], content_length: headers[/Content-Length: (\d+)/, 1],
file_hash: output_hash, file_hash: output_hash,
file: output,
} }
end end
@ -327,7 +358,7 @@ class FormulaAuditor
end end
valid_alias_names = [alias_name_major, alias_name_major_minor] valid_alias_names = [alias_name_major, alias_name_major_minor]
if formula.tap && !formula.tap.core_tap? unless formula.tap&.core_tap?
versioned_aliases.map! { |a| "#{formula.tap}/#{a}" } versioned_aliases.map! { |a| "#{formula.tap}/#{a}" }
valid_alias_names.map! { |a| "#{formula.tap}/#{a}" } valid_alias_names.map! { |a| "#{formula.tap}/#{a}" }
end end
@ -356,21 +387,6 @@ class FormulaAuditor
end end
end end
def audit_class
if @strict
unless formula.test_defined?
problem "A `test do` test block should be added"
end
end
classes = %w[GithubGistFormula ScriptFileFormula AmazonWebServicesFormula]
klass = classes.find do |c|
Object.const_defined?(c) && formula.class < Object.const_get(c)
end
problem "#{klass} is deprecated, use Formula instead" if klass
end
# core aliases + tap alias names + tap alias full name # core aliases + tap alias names + tap alias full name
@@aliases ||= Formula.aliases + Formula.tap_aliases @@aliases ||= Formula.aliases + Formula.tap_aliases
@ -403,6 +419,7 @@ class FormulaAuditor
@@local_official_taps_name_map ||= Tap.select(&:official?).flat_map(&:formula_names) @@local_official_taps_name_map ||= Tap.select(&:official?).flat_map(&:formula_names)
.each_with_object({}) do |tap_formula_full_name, name_map| .each_with_object({}) do |tap_formula_full_name, name_map|
next if tap_formula_full_name.start_with?("homebrew/science/")
tap_formula_name = tap_formula_full_name.split("/").last tap_formula_name = tap_formula_full_name.split("/").last
name_map[tap_formula_name] ||= [] name_map[tap_formula_name] ||= []
name_map[tap_formula_name] << tap_formula_full_name name_map[tap_formula_name] << tap_formula_full_name
@ -413,6 +430,7 @@ class FormulaAuditor
if @online if @online
Homebrew.search_taps(name, silent: true).each do |tap_formula_full_name| Homebrew.search_taps(name, silent: true).each do |tap_formula_full_name|
next if tap_formula_full_name.start_with?("homebrew/science/")
tap_formula_name = tap_formula_full_name.split("/").last tap_formula_name = tap_formula_full_name.split("/").last
next if tap_formula_name != name next if tap_formula_name != name
same_name_tap_formulae << tap_formula_full_name same_name_tap_formulae << tap_formula_full_name
@ -563,10 +581,11 @@ class FormulaAuditor
return unless @online return unless @online
return unless DevelopmentTools.curl_handles_most_https_homepages? return unless DevelopmentTools.curl_handles_most_https_certificates?
if http_content_problem = FormulaAuditor.check_http_content(homepage, if http_content_problem = FormulaAuditor.check_http_content(homepage,
formula.name, user_agents: [:browser, :default],
user_agents: [:browser, :default]) check_content: true,
strict: @strict)
problem http_content_problem problem http_content_problem
end end
end end
@ -616,13 +635,14 @@ class FormulaAuditor
end end
%w[Stable Devel HEAD].each do |name| %w[Stable Devel HEAD].each do |name|
next unless spec = formula.send(name.downcase) spec_name = name.downcase.to_sym
next unless spec = formula.send(spec_name)
ra = ResourceAuditor.new(spec, online: @online, strict: @strict).audit ra = ResourceAuditor.new(spec, spec_name, online: @online, strict: @strict).audit
problems.concat ra.problems.map { |problem| "#{name}: #{problem}" } problems.concat ra.problems.map { |problem| "#{name}: #{problem}" }
spec.resources.each_value do |resource| spec.resources.each_value do |resource|
ra = ResourceAuditor.new(resource, online: @online, strict: @strict).audit ra = ResourceAuditor.new(resource, spec_name, online: @online, strict: @strict).audit
problems.concat ra.problems.map { |problem| problems.concat ra.problems.map { |problem|
"#{name} resource #{resource.name.inspect}: #{problem}" "#{name} resource #{resource.name.inspect}: #{problem}"
} }
@ -687,7 +707,7 @@ class FormulaAuditor
end end
stable = formula.stable stable = formula.stable
case stable && stable.url case stable&.url
when /[\d\._-](alpha|beta|rc\d)/ when /[\d\._-](alpha|beta|rc\d)/
matched = Regexp.last_match(1) matched = Regexp.last_match(1)
version_prefix = stable.version.to_s.sub(/\d+$/, "") version_prefix = stable.version.to_s.sub(/\d+$/, "")
@ -865,8 +885,8 @@ class FormulaAuditor
problem "Use spaces instead of tabs for indentation" if line =~ /^[ ]*\t/ problem "Use spaces instead of tabs for indentation" if line =~ /^[ ]*\t/
if line.include?("ENV.x11") if line.include?("ENV.java_cache")
problem "Use \"depends_on :x11\" instead of \"ENV.x11\"" problem "In-formula ENV.java_cache usage has been deprecated & should be removed."
end end
# Avoid hard-coding compilers # Avoid hard-coding compilers
@ -882,14 +902,6 @@ class FormulaAuditor
problem "Use ENV instead of invoking '#{Regexp.last_match(1)}' to modify the environment" problem "Use ENV instead of invoking '#{Regexp.last_match(1)}' to modify the environment"
end end
if formula.name != "wine" && line =~ /ENV\.universal_binary/
problem "macOS has been 64-bit only since 10.6 so ENV.universal_binary is deprecated."
end
if line =~ /build\.universal\?/
problem "macOS has been 64-bit only so build.universal? is deprecated."
end
if line =~ /version == ['"]HEAD['"]/ if line =~ /version == ['"]HEAD['"]/
problem "Use 'build.head?' instead of inspecting 'version'" problem "Use 'build.head?' instead of inspecting 'version'"
end end
@ -930,12 +942,6 @@ class FormulaAuditor
problem "Use build instead of ARGV to check options" problem "Use build instead of ARGV to check options"
end end
problem "Use new-style option definitions" if line.include?("def options")
if line.end_with?("def test")
problem "Use new-style test definitions (test do)"
end
if line.include?("MACOS_VERSION") if line.include?("MACOS_VERSION")
problem "Use MacOS.version instead of MACOS_VERSION" problem "Use MacOS.version instead of MACOS_VERSION"
end end
@ -949,11 +955,6 @@ class FormulaAuditor
problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead" problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead"
end end
if line =~ /skip_clean\s+:all/
problem "`skip_clean :all` is deprecated; brew no longer strips symbols\n" \
"\tPass explicit paths to prevent Homebrew from removing empty folders."
end
if line =~ /depends_on [A-Z][\w:]+\.new$/ if line =~ /depends_on [A-Z][\w:]+\.new$/
problem "`depends_on` can take requirement classes instead of instances" problem "`depends_on` can take requirement classes instead of instances"
end end
@ -992,30 +993,6 @@ class FormulaAuditor
problem "Use `assert_match` instead of `assert ...include?`" problem "Use `assert_match` instead of `assert ...include?`"
end end
if line.include?('system "npm", "install"') && !line.include?("Language::Node") &&
formula.name !~ /^kibana(\@\d+(\.\d+)?)?$/
problem "Use Language::Node for npm install args"
end
if line.include?("fails_with :llvm")
problem "'fails_with :llvm' is now a no-op so should be removed"
end
if line =~ /system\s+['"](otool|install_name_tool|lipo)/ && formula.name != "cctools"
problem "Use ruby-macho instead of calling #{Regexp.last_match(1)}"
end
if formula.tap.to_s == "homebrew/core"
["OS.mac?", "OS.linux?"].each do |check|
next unless line.include?(check)
problem "Don't use #{check}; Homebrew/core only supports macOS"
end
end
if line =~ /((revision|version_scheme)\s+0)/
problem "'#{Regexp.last_match(1)}' should be removed"
end
return unless @strict return unless @strict
problem "`#{Regexp.last_match(1)}` in formulae is deprecated" if line =~ /(env :(std|userpaths))/ problem "`#{Regexp.last_match(1)}` in formulae is deprecated" if line =~ /(env :(std|userpaths))/
@ -1041,7 +1018,7 @@ class FormulaAuditor
def audit_reverse_migration def audit_reverse_migration
# Only enforce for new formula being re-added to core and official taps # Only enforce for new formula being re-added to core and official taps
return unless @strict return unless @strict
return unless formula.tap && formula.tap.official? return unless formula.tap&.official?
return unless formula.tap.tap_migrations.key?(formula.name) return unless formula.tap.tap_migrations.key?(formula.name)
problem <<-EOS.undent problem <<-EOS.undent
@ -1116,10 +1093,10 @@ class FormulaAuditor
end end
class ResourceAuditor class ResourceAuditor
attr_reader :problems attr_reader :name, :version, :checksum, :url, :mirrors, :using, :specs, :owner
attr_reader :version, :checksum, :using, :specs, :url, :mirrors, :name attr_reader :spec_name, :problems
def initialize(resource, options = {}) def initialize(resource, spec_name, options = {})
@name = resource.name @name = resource.name
@version = resource.version @version = resource.version
@checksum = resource.checksum @checksum = resource.checksum
@ -1127,9 +1104,11 @@ class ResourceAuditor
@mirrors = resource.mirrors @mirrors = resource.mirrors
@using = resource.using @using = resource.using
@specs = resource.specs @specs = resource.specs
@online = options[:online] @owner = resource.owner
@strict = options[:strict] @spec_name = spec_name
@problems = [] @online = options[:online]
@strict = options[:strict]
@problems = []
end end
def audit def audit
@ -1203,11 +1182,29 @@ class ResourceAuditor
problem "Redundant :using value in URL" problem "Redundant :using value in URL"
end end
def self.curl_openssl_and_deps
@curl_openssl_and_deps ||= begin
formulae_names = ["curl", "openssl"]
formulae_names += formulae_names.flat_map do |f|
Formula[f].recursive_dependencies.map(&:name)
end
formulae_names.uniq
rescue FormulaUnavailableError
[]
end
end
def audit_urls def audit_urls
urls = [url] + mirrors urls = [url] + mirrors
if name == "curl" && !urls.find { |u| u.start_with?("http://") } curl_openssl_or_deps = ResourceAuditor.curl_openssl_and_deps.include?(owner.name)
problem "should always include at least one HTTP url"
if spec_name == :stable && curl_openssl_or_deps
problem "should not use xz tarballs" if url.end_with?(".xz")
unless urls.find { |u| u.start_with?("http://") }
problem "should always include at least one HTTP mirror"
end
end end
return unless @online return unless @online
@ -1219,7 +1216,7 @@ class ResourceAuditor
# A `brew mirror`'ed URL is usually not yet reachable at the time of # A `brew mirror`'ed URL is usually not yet reachable at the time of
# pull request. # pull request.
next if url =~ %r{^https://dl.bintray.com/homebrew/mirror/} next if url =~ %r{^https://dl.bintray.com/homebrew/mirror/}
if http_content_problem = FormulaAuditor.check_http_content(url, name) if http_content_problem = FormulaAuditor.check_http_content(url, require_http: curl_openssl_or_deps)
problem http_content_problem problem http_content_problem
end end
elsif strategy <= GitDownloadStrategy elsif strategy <= GitDownloadStrategy
@ -1228,6 +1225,7 @@ class ResourceAuditor
end end
elsif strategy <= SubversionDownloadStrategy elsif strategy <= SubversionDownloadStrategy
next unless DevelopmentTools.subversion_handles_most_https_certificates? next unless DevelopmentTools.subversion_handles_most_https_certificates?
next unless Utils.svn_available?
unless Utils.svn_remote_exists url unless Utils.svn_remote_exists url
problem "The URL #{url} is not a valid svn URL" problem "The URL #{url} is not a valid svn URL"
end end

View File

@ -47,7 +47,7 @@ BOTTLE_ERB = <<-EOS.freeze
<% elsif cellar != BottleSpecification::DEFAULT_CELLAR %> <% elsif cellar != BottleSpecification::DEFAULT_CELLAR %>
cellar "<%= cellar %>" cellar "<%= cellar %>"
<% end %> <% end %>
<% if rebuild > 0 %> <% if rebuild.positive? %>
rebuild <%= rebuild %> rebuild <%= rebuild %>
<% end %> <% end %>
<% checksums.each do |checksum_type, checksum_values| %> <% checksums.each do |checksum_type, checksum_values| %>
@ -186,7 +186,7 @@ module Homebrew
ohai "Determining #{f.full_name} bottle rebuild..." ohai "Determining #{f.full_name} bottle rebuild..."
versions = FormulaVersions.new(f) versions = FormulaVersions.new(f)
rebuilds = versions.bottle_version_map("origin/master")[f.pkg_version] rebuilds = versions.bottle_version_map("origin/master")[f.pkg_version]
rebuilds.pop if rebuilds.last.to_i > 0 rebuilds.pop if rebuilds.last.to_i.positive?
rebuild = rebuilds.empty? ? 0 : rebuilds.max.to_i + 1 rebuild = rebuilds.empty? ? 0 : rebuilds.max.to_i + 1
end end
@ -283,7 +283,7 @@ module Homebrew
raise raise
ensure ensure
ignore_interrupts do ignore_interrupts do
original_tab.write if original_tab original_tab&.write
unless ARGV.include? "--skip-relocation" unless ARGV.include? "--skip-relocation"
keg.replace_placeholders_with_locations changed_files keg.replace_placeholders_with_locations changed_files
end end

View File

@ -89,7 +89,8 @@ module Homebrew
def check_for_duplicate_pull_requests(formula) def check_for_duplicate_pull_requests(formula)
pull_requests = fetch_pull_requests(formula) pull_requests = fetch_pull_requests(formula)
return unless pull_requests && !pull_requests.empty? return unless pull_requests
return if pull_requests.empty?
duplicates_message = <<-EOS.undent duplicates_message = <<-EOS.undent
These open pull requests may be duplicates: These open pull requests may be duplicates:
#{pull_requests.map { |pr| "#{pr["title"]} #{pr["html_url"]}" }.join("\n")} #{pull_requests.map { |pr| "#{pr["title"]} #{pr["html_url"]}" }.join("\n")}
@ -124,7 +125,7 @@ module Homebrew
Formula.each do |f| Formula.each do |f|
if is_devel && f.devel && f.devel.url && f.devel.url.match(base_url) if is_devel && f.devel && f.devel.url && f.devel.url.match(base_url)
guesses << f guesses << f
elsif f.stable && f.stable.url && f.stable.url.match(base_url) elsif f.stable&.url && f.stable.url.match(base_url)
guesses << f guesses << f
end end
end end
@ -296,9 +297,7 @@ module Homebrew
ohai "git fetch --unshallow origin" if shallow ohai "git fetch --unshallow origin" if shallow
ohai "git checkout --no-track -b #{branch} origin/master" ohai "git checkout --no-track -b #{branch} origin/master"
ohai "git commit --no-edit --verbose --message='#{formula.name} #{new_formula_version}#{devel_message}' -- #{formula.path}" ohai "git commit --no-edit --verbose --message='#{formula.name} #{new_formula_version}#{devel_message}' -- #{formula.path}"
ohai "hub fork --no-remote" ohai "hub fork # read $HUB_REMOTE"
ohai "hub fork"
ohai "hub fork (to read $HUB_REMOTE)"
ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{branch}" ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{branch}"
ohai "hub pull-request --browse -m '#{formula.name} #{new_formula_version}#{devel_message}'" ohai "hub pull-request --browse -m '#{formula.name} #{new_formula_version}#{devel_message}'"
ohai "git checkout -" ohai "git checkout -"
@ -308,9 +307,9 @@ module Homebrew
safe_system "git", "commit", "--no-edit", "--verbose", safe_system "git", "commit", "--no-edit", "--verbose",
"--message=#{formula.name} #{new_formula_version}#{devel_message}", "--message=#{formula.name} #{new_formula_version}#{devel_message}",
"--", formula.path "--", formula.path
safe_system "hub", "fork", "--no-remote" remote = Utils.popen_read("hub fork 2>&1")[/remote:? (\S+)/, 1]
quiet_system "hub", "fork" # repeat for hub 2.2 backwards compatibility:
remote = Utils.popen_read("hub fork 2>&1")[/fatal: remote (.+) already exists\./, 1] remote = Utils.popen_read("hub fork 2>&1")[/remote:? (\S+)/, 1] if remote.to_s.empty?
odie "cannot get remote from 'hub'!" if remote.to_s.empty? odie "cannot get remote from 'hub'!" if remote.to_s.empty?
safe_system "git", "push", "--set-upstream", remote, "#{branch}:#{branch}" safe_system "git", "push", "--set-upstream", remote, "#{branch}:#{branch}"
pr_message = <<-EOS.undent pr_message = <<-EOS.undent

View File

@ -69,13 +69,13 @@ module Homebrew
tap = nil tap = nil
ARGV.named.each do |arg| ARGV.named.each do |arg|
if arg.to_i > 0 if arg.to_i.positive?
issue = arg issue = arg
url = "https://github.com/Homebrew/homebrew-core/pull/#{arg}" url = "https://github.com/Homebrew/homebrew-core/pull/#{arg}"
tap = CoreTap.instance tap = CoreTap.instance
elsif (testing_match = arg.match %r{/job/Homebrew.*Testing/(\d+)/}) elsif (testing_match = arg.match %r{/job/Homebrew.*Testing/(\d+)/})
tap = ARGV.value("tap") tap = ARGV.value("tap")
tap = if tap && tap.start_with?("homebrew/") tap = if tap&.start_with?("homebrew/")
Tap.fetch("homebrew", tap.strip_prefix("homebrew/")) Tap.fetch("homebrew", tap.strip_prefix("homebrew/"))
elsif tap elsif tap
odie "Tap option did not start with \"homebrew/\": #{tap}" odie "Tap option did not start with \"homebrew/\": #{tap}"
@ -350,7 +350,7 @@ module Homebrew
files << Regexp.last_match(1) if line =~ %r{^\+\+\+ b/(.*)} files << Regexp.last_match(1) if line =~ %r{^\+\+\+ b/(.*)}
end end
files.each do |file| files.each do |file|
if tap && tap.formula_file?(file) if tap&.formula_file?(file)
formula_name = File.basename(file, ".rb") formula_name = File.basename(file, ".rb")
formulae << formula_name unless formulae.include?(formula_name) formulae << formula_name unless formulae.include?(formula_name)
else else

View File

@ -10,10 +10,8 @@ module Homebrew
def release_notes def release_notes
previous_tag = ARGV.named.first previous_tag = ARGV.named.first
unless previous_tag previous_tag ||= Utils.popen_read("git tag --list --sort=-version:refname")
previous_tag = Utils.popen_read("git tag --list --sort=-version:refname")
.lines.first.chomp .lines.first.chomp
end
odie "Could not find any previous tags!" unless previous_tag odie "Could not find any previous tags!" unless previous_tag
end_ref = ARGV.named[1] || "origin/master" end_ref = ARGV.named[1] || "origin/master"

View File

@ -114,7 +114,7 @@ class DevelopmentTools
@non_apple_gcc_version = {} @non_apple_gcc_version = {}
end end
def curl_handles_most_https_homepages? def curl_handles_most_https_certificates?
true true
end end

View File

@ -522,7 +522,7 @@ module Homebrew
homebrew_owned = @found.all? do |path| homebrew_owned = @found.all? do |path|
Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext" Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext"
end end
return if gettext && gettext.linked_keg.directory? && homebrew_owned return if gettext&.linked_keg&.directory? && homebrew_owned
inject_file_list @found, <<-EOS.undent inject_file_list @found, <<-EOS.undent
gettext files detected at a system prefix. gettext files detected at a system prefix.
@ -540,7 +540,7 @@ module Homebrew
rescue rescue
nil nil
end end
if libiconv && libiconv.linked_keg.directory? if libiconv&.linked_keg&.directory?
unless libiconv.keg_only? unless libiconv.keg_only?
<<-EOS.undent <<-EOS.undent
A libiconv formula is installed and linked. A libiconv formula is installed and linked.

View File

@ -331,20 +331,10 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
if cached_location.exist? if cached_location.exist?
puts "Already downloaded: #{cached_location}" puts "Already downloaded: #{cached_location}"
else else
had_incomplete_download = temporary_path.exist?
begin begin
_fetch _fetch
rescue ErrorDuringExecution rescue ErrorDuringExecution
# 33 == range not supported raise CurlDownloadStrategyError, @url
# try wiping the incomplete download and retrying once
unless $CHILD_STATUS.exitstatus == 33 && had_incomplete_download
raise CurlDownloadStrategyError, @url
end
ohai "Trying a full download"
temporary_path.unlink
had_incomplete_download = false
retry
end end
ignore_interrupts { temporary_path.rename(cached_location) } ignore_interrupts { temporary_path.rename(cached_location) }
end end

View File

@ -416,7 +416,7 @@ class BuildError < RuntimeError
puts puts
if issues && !issues.empty? unless issues&.empty?
puts "These open issues may also help:" puts "These open issues may also help:"
puts issues.map { |i| "#{i["title"]} #{i["html_url"]}" }.join("\n") puts issues.map { |i| "#{i["title"]} #{i["html_url"]}" }.join("\n")
end end

View File

@ -144,10 +144,10 @@ module HomebrewArgvExtension
def value(name) def value(name)
arg_prefix = "--#{name}=" arg_prefix = "--#{name}="
flag_with_value = find { |arg| arg.start_with?(arg_prefix) } flag_with_value = find { |arg| arg.start_with?(arg_prefix) }
flag_with_value.strip_prefix(arg_prefix) if flag_with_value flag_with_value&.strip_prefix(arg_prefix)
end end
# Returns an array of values that were given as a comma-seperated list. # Returns an array of values that were given as a comma-separated list.
# @see value # @see value
def values(name) def values(name)
return unless val = value(name) return unless val = value(name)
@ -236,7 +236,7 @@ module HomebrewArgvExtension
def bottle_arch def bottle_arch
arch = value "bottle-arch" arch = value "bottle-arch"
arch.to_sym if arch arch&.to_sym
end end
def build_from_source? def build_from_source?

View File

@ -28,7 +28,7 @@ module EnvActivation
end end
def clear_sensitive_environment! def clear_sensitive_environment!
ENV.keys.each do |key| ENV.each_key do |key|
next unless /(cookie|key|token)/i =~ key next unless /(cookie|key|token)/i =~ key
ENV.delete key ENV.delete key
end end

View File

@ -260,10 +260,6 @@ module SharedEnvExtension
set_cpu_flags(flags) set_cpu_flags(flags)
end end
def java_cache
append "_JAVA_OPTIONS", "-Duser.home=#{HOMEBREW_CACHE}/java_cache"
end
# ld64 is a newer linker provided for Xcode 2.5 # ld64 is a newer linker provided for Xcode 2.5
# @private # @private
def ld64 def ld64

View File

@ -233,8 +233,8 @@ module Stdenv
def make_jobs def make_jobs
# '-j' requires a positive integral argument # '-j' requires a positive integral argument
if self["HOMEBREW_MAKE_JOBS"].to_i > 0 if (jobs = self["HOMEBREW_MAKE_JOBS"].to_i).positive?
self["HOMEBREW_MAKE_JOBS"].to_i jobs
else else
Hardware::CPU.cores Hardware::CPU.cores
end end

View File

@ -9,7 +9,7 @@ class DevelopmentTools
@locate[key] = if (located_tool = original_locate(tool)) @locate[key] = if (located_tool = original_locate(tool))
located_tool located_tool
elsif MacOS.version > :tiger elsif MacOS.version > :tiger
path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool).chomp path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool, err: :close).chomp
Pathname.new(path) if File.executable?(path) Pathname.new(path) if File.executable?(path)
end end
end end
@ -43,11 +43,16 @@ class DevelopmentTools
end end
def custom_installation_instructions def custom_installation_instructions
if MacOS.version > :tiger if MacOS.version > :leopard
<<-EOS.undent <<-EOS.undent
Install GNU's GCC Install GNU's GCC
brew install gcc brew install gcc
EOS EOS
elsif MacOS.version > :tiger
<<-EOS.undent
Install GNU's GCC
brew install gcc@4.6
EOS
else else
# Tiger doesn't ship with apple-gcc42, and this is required to build # Tiger doesn't ship with apple-gcc42, and this is required to build
# some software that doesn't build properly with FSF GCC. # some software that doesn't build properly with FSF GCC.
@ -55,7 +60,7 @@ class DevelopmentTools
Install Apple's GCC Install Apple's GCC
brew install apple-gcc42 brew install apple-gcc42
or GNU's GCC or GNU's GCC
brew install gcc brew install gcc@4.6
EOS EOS
end end
end end
@ -77,10 +82,10 @@ class DevelopmentTools
end end
end end
def curl_handles_most_https_homepages? def curl_handles_most_https_certificates?
# The system Curl is too old for some modern HTTPS homepages on # The system Curl is too old for some modern HTTPS certificates on
# older macOS versions. # older macOS versions.
MacOS.version >= :el_capitan ENV["HOMEBREW_SYSTEM_CURL_TOO_OLD"].nil?
end end
def subversion_handles_most_https_certificates? def subversion_handles_most_https_certificates?

View File

@ -195,8 +195,9 @@ module Homebrew
end end
def check_ruby_version def check_ruby_version
ruby_version = "2.0" ruby_version = "2.3.3"
return if RUBY_VERSION[/\d\.\d/] == ruby_version return if RUBY_VERSION == ruby_version
return if ARGV.homebrew_developer? && OS::Mac.prerelease?
<<-EOS.undent <<-EOS.undent
Ruby version #{RUBY_VERSION} is unsupported on #{MacOS.version}. Homebrew Ruby version #{RUBY_VERSION} is unsupported on #{MacOS.version}. Homebrew

View File

@ -96,9 +96,13 @@ module Superenv
self["SDKROOT"] = MacOS.sdk_path self["SDKROOT"] = MacOS.sdk_path
end end
# Filter out symbols known not to be defined on 10.11 since GNU Autotools # Filter out symbols known not to be defined since GNU Autotools can't
# can't reliably figure this out with Xcode 8 on its own yet. # reliably figure this out with Xcode 8 and above.
if MacOS.version == "10.11" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "8.0" if MacOS.version == "10.12" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "9.0"
%w[fmemopen futimens open_memstream utimensat].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
elsif MacOS.version == "10.11" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "8.0"
%w[basename_r clock_getres clock_gettime clock_settime dirname_r %w[basename_r clock_getres clock_gettime clock_settime dirname_r
getentropy mkostemp mkostemps timingsafe_bcmp].each do |s| getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|
ENV["ac_cv_func_#{s}"] = "no" ENV["ac_cv_func_#{s}"] = "no"

View File

@ -50,6 +50,8 @@ module Hardware
:broadwell :broadwell
when 0x37fc219f # Skylake when 0x37fc219f # Skylake
:skylake :skylake
when 0x0f817246 # Kaby Lake
:kabylake
else else
:dunno :dunno
end end

View File

@ -60,7 +60,7 @@ module StringInreplaceExtension
result result
end end
# Looks for Makefile style variable defintions and replaces the # Looks for Makefile style variable definitions and replaces the
# value with "new_value", or removes the definition entirely. # value with "new_value", or removes the definition entirely.
def change_make_var!(flag, new_value) def change_make_var!(flag, new_value)
return if gsub!(/^#{Regexp.escape(flag)}[ \t]*=[ \t]*(.*)$/, "#{flag}=#{new_value}", false) return if gsub!(/^#{Regexp.escape(flag)}[ \t]*=[ \t]*(.*)$/, "#{flag}=#{new_value}", false)

View File

@ -472,7 +472,7 @@ class Formula
return true if devel && tab.devel_version && tab.devel_version < devel.version return true if devel && tab.devel_version && tab.devel_version < devel.version
if options[:fetch_head] if options[:fetch_head]
return false unless head && head.downloader.is_a?(VCSDownloadStrategy) return false unless head&.downloader.is_a?(VCSDownloadStrategy)
downloader = head.downloader downloader = head.downloader
downloader.shutup! unless ARGV.verbose? downloader.shutup! unless ARGV.verbose?
downloader.commit_outdated?(version.version.commit) downloader.commit_outdated?(version.version.commit)
@ -1115,8 +1115,8 @@ class Formula
# @private # @private
def unlock def unlock
@lock.unlock unless @lock.nil? @lock&.unlock
@oldname_lock.unlock unless @oldname_lock.nil? @oldname_lock&.unlock
end end
def migration_needed? def migration_needed?
@ -1182,7 +1182,8 @@ class Formula
# Returns false if the formula wasn't installed with an alias. # Returns false if the formula wasn't installed with an alias.
def installed_alias_target_changed? def installed_alias_target_changed?
target = current_installed_alias_target target = current_installed_alias_target
target && target.name != name return false unless target
target.name != name
end end
# Is this formula the target of an alias used to install an old formula? # Is this formula the target of an alias used to install an old formula?
@ -1440,13 +1441,14 @@ class Formula
# True if this formula is provided by Homebrew itself # True if this formula is provided by Homebrew itself
# @private # @private
def core_formula? def core_formula?
tap && tap.core_tap? tap&.core_tap?
end end
# True if this formula is provided by external Tap # True if this formula is provided by external Tap
# @private # @private
def tap? def tap?
tap && !tap.core_tap? return false unless tap
!tap.core_tap?
end end
# @private # @private
@ -1525,10 +1527,10 @@ class Formula
"oldname" => oldname, "oldname" => oldname,
"aliases" => aliases, "aliases" => aliases,
"versions" => { "versions" => {
"stable" => (stable.version.to_s if stable), "stable" => stable&.version.to_s,
"bottle" => bottle ? true : false, "bottle" => bottle ? true : false,
"devel" => (devel.version.to_s if devel), "devel" => devel&.version.to_s,
"head" => (head.version.to_s if head), "head" => head&.version.to_s,
}, },
"revision" => revision, "revision" => revision,
"version_scheme" => version_scheme, "version_scheme" => version_scheme,
@ -1570,7 +1572,7 @@ class Formula
"root_url" => bottle_spec.root_url, "root_url" => bottle_spec.root_url,
} }
bottle_info["files"] = {} bottle_info["files"] = {}
bottle_spec.collector.keys.each do |os| bottle_spec.collector.keys.each do |os| # rubocop:disable Performance/HashEachMethods
checksum = bottle_spec.collector[os] checksum = bottle_spec.collector[os]
bottle_info["files"][os] = { bottle_info["files"][os] = {
"url" => "#{bottle_spec.root_url}/#{Bottle::Filename.create(self, os, bottle_spec.rebuild)}", "url" => "#{bottle_spec.root_url}/#{Bottle::Filename.create(self, os, bottle_spec.rebuild)}",
@ -1613,6 +1615,7 @@ class Formula
def run_test def run_test
@prefix_returns_versioned_prefix = true @prefix_returns_versioned_prefix = true
old_home = ENV["HOME"] old_home = ENV["HOME"]
old_java_opts = ENV["_JAVA_OPTIONS"]
old_curl_home = ENV["CURL_HOME"] old_curl_home = ENV["CURL_HOME"]
old_tmpdir = ENV["TMPDIR"] old_tmpdir = ENV["TMPDIR"]
old_temp = ENV["TEMP"] old_temp = ENV["TEMP"]
@ -1626,6 +1629,7 @@ class Formula
ENV["TERM"] = "dumb" ENV["TERM"] = "dumb"
ENV["PATH"] = PATH.new(old_path).append(HOMEBREW_PREFIX/"bin") ENV["PATH"] = PATH.new(old_path).append(HOMEBREW_PREFIX/"bin")
ENV["HOMEBREW_PATH"] = nil ENV["HOMEBREW_PATH"] = nil
ENV["_JAVA_OPTIONS"] = "#{old_java_opts} -Duser.home=#{HOMEBREW_CACHE}/java_cache"
ENV.clear_sensitive_environment! ENV.clear_sensitive_environment!
@ -1646,6 +1650,7 @@ class Formula
ensure ensure
@testpath = nil @testpath = nil
ENV["HOME"] = old_home ENV["HOME"] = old_home
ENV["_JAVA_OPTIONS"] = old_java_opts
ENV["CURL_HOME"] = old_curl_home ENV["CURL_HOME"] = old_curl_home
ENV["TMPDIR"] = old_tmpdir ENV["TMPDIR"] = old_tmpdir
ENV["TEMP"] = old_temp ENV["TEMP"] = old_temp
@ -1888,11 +1893,13 @@ class Formula
mkdir_p env_home mkdir_p env_home
old_home = ENV["HOME"] old_home = ENV["HOME"]
old_java_opts = ENV["_JAVA_OPTIONS"]
old_curl_home = ENV["CURL_HOME"] old_curl_home = ENV["CURL_HOME"]
old_path = ENV["HOMEBREW_PATH"] old_path = ENV["HOMEBREW_PATH"]
unless ARGV.interactive? unless ARGV.interactive?
ENV["HOME"] = env_home ENV["HOME"] = env_home
ENV["_JAVA_OPTIONS"] = "#{old_java_opts} -Duser.home=#{HOMEBREW_CACHE}/java_cache"
ENV["CURL_HOME"] = old_curl_home || old_home ENV["CURL_HOME"] = old_curl_home || old_home
end end
ENV["HOMEBREW_PATH"] = nil ENV["HOMEBREW_PATH"] = nil
@ -1907,6 +1914,7 @@ class Formula
@buildpath = nil @buildpath = nil
unless ARGV.interactive? unless ARGV.interactive?
ENV["HOME"] = old_home ENV["HOME"] = old_home
ENV["_JAVA_OPTIONS"] = old_java_opts
ENV["CURL_HOME"] = old_curl_home ENV["CURL_HOME"] = old_curl_home
end end
ENV["HOMEBREW_PATH"] = old_path ENV["HOMEBREW_PATH"] = old_path

View File

@ -85,13 +85,12 @@ class FormulaInstaller
return false if @pour_failed return false if @pour_failed
bottle = formula.bottle bottle = formula.bottle
return false unless bottle return false if !bottle && !formula.local_bottle_path
return true if force_bottle? return true if force_bottle?
return false if build_from_source? || build_bottle? || interactive? return false if build_from_source? || build_bottle? || interactive?
return false if ARGV.cc return false if ARGV.cc
return false unless options.empty? return false unless options.empty?
return false if formula.bottle_disabled? return false if formula.bottle_disabled?
return true if formula.local_bottle_path
unless formula.pour_bottle? unless formula.pour_bottle?
if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason
opoo <<-EOS.undent opoo <<-EOS.undent
@ -270,7 +269,7 @@ class FormulaInstaller
oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip
end end
if formula.tap && !formula.tap.private? unless formula.tap&.private?
action = "#{formula.full_name} #{options}".strip action = "#{formula.full_name} #{options}".strip
Utils::Analytics.report_event("install", action) Utils::Analytics.report_event("install", action)
@ -561,7 +560,7 @@ class FormulaInstaller
end end
raise raise
else else
ignore_interrupts { tmp_keg.rmtree if tmp_keg && tmp_keg.directory? } ignore_interrupts { tmp_keg.rmtree if tmp_keg&.directory? }
end end
def caveats def caveats
@ -604,6 +603,12 @@ class FormulaInstaller
# let's reset Utils.git_available? if we just installed git # let's reset Utils.git_available? if we just installed git
Utils.clear_git_available_cache if formula.name == "git" Utils.clear_git_available_cache if formula.name == "git"
# use installed curl when it's needed and available
if formula.name == "curl" &&
!DevelopmentTools.curl_handles_most_https_certificates?
ENV["HOMEBREW_CURL"] = formula.opt_bin/"curl"
end
ensure ensure
unlock unlock
end end

View File

@ -45,11 +45,15 @@ module Homebrew
@failed == true @failed == true
end end
attr_writer :raise_deprecation_exceptions attr_writer :raise_deprecation_exceptions, :auditing
def raise_deprecation_exceptions? def raise_deprecation_exceptions?
@raise_deprecation_exceptions == true @raise_deprecation_exceptions == true
end end
def auditing?
@auditing == true
end
end end
end end

View File

@ -7,8 +7,7 @@ class Gpg
next unless gpg_short_version next unless gpg_short_version
gpg_version = Version.create(gpg_short_version.to_s) gpg_version = Version.create(gpg_short_version.to_s)
@version = gpg_version @version = gpg_version
gpg_version == Version.create("2.1") || gpg_version >= Version.create("2.0")
gpg_version == Version.create("2.0")
end end
end end
@ -38,12 +37,30 @@ class Gpg
Key-Length: 2048 Key-Length: 2048
Subkey-Type: RSA Subkey-Type: RSA
Subkey-Length: 2048 Subkey-Length: 2048
Passphrase: ''
Name-Real: Testing Name-Real: Testing
Name-Email: testing@foo.bar Name-Email: testing@foo.bar
Expire-Date: 1d Expire-Date: 1d
%no-protection
%commit %commit
EOS EOS
system GPG_EXECUTABLE, "--batch", "--gen-key", "batch.gpg" system GPG_EXECUTABLE, "--batch", "--gen-key", "batch.gpg"
end end
def self.cleanup_test_processes!
odie "No GPG present to test against!" unless available?
gpgconf = Pathname.new(GPG_EXECUTABLE).parent/"gpgconf"
system gpgconf, "--kill", "gpg-agent"
system gpgconf, "--homedir", "keyrings/live", "--kill",
"gpg-agent"
end
def self.test(path)
create_test_key(path)
begin
yield
ensure
cleanup_test_processes!
end
end
end end

View File

@ -16,12 +16,12 @@ module InstallRenamed
end end
end end
def +(path) def +(other)
super(path).extend(InstallRenamed) super(other).extend(InstallRenamed)
end end
def /(path) def /(other)
super(path).extend(InstallRenamed) super(other).extend(InstallRenamed)
end end
private private

View File

@ -338,7 +338,7 @@ class Keg
dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") } dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") }
when :fish then path/"share/fish/vendor_completions.d" when :fish then path/"share/fish/vendor_completions.d"
end end
dir && dir.directory? && !dir.children.empty? dir&.directory? && !dir.children.empty?
end end
def functions_installed?(shell) def functions_installed?(shell)

View File

@ -156,7 +156,7 @@ class Keg
libtool_files = [] libtool_files = []
path.find do |pn| path.find do |pn|
next if pn.symlink? || pn.directory? || pn.extname != ".la" next if pn.symlink? || pn.directory? || ![".la", ".lai"].include?(pn.extname)
libtool_files << pn libtool_files << pn
end end
libtool_files libtool_files

View File

@ -23,7 +23,7 @@ module Language
else else
homebrew_site_packages(version) homebrew_site_packages(version)
end end
block.call python, version if block block&.call python, version
end end
ENV["PYTHONPATH"] = original_pythonpath ENV["PYTHONPATH"] = original_pythonpath
end end

View File

@ -44,8 +44,6 @@ class Locale
raise ParserError, "'#{value}' does not match #{regex}" unless value =~ regex raise ParserError, "'#{value}' does not match #{regex}" unless value =~ regex
instance_variable_set(:"@#{key}", value) instance_variable_set(:"@#{key}", value)
end end
self
end end
def include?(other) def include?(other)

View File

@ -1,5 +1,5 @@
brew-cask(1) - a friendly binary installer for macOS brew-cask(1) - a friendly binary installer for macOS
======================================================== ====================================================
## SYNOPSIS ## SYNOPSIS
@ -85,7 +85,7 @@ names, and other aspects of this manual are still subject to change.
If <token> is given, summarize the staged files associated with the If <token> is given, summarize the staged files associated with the
given Cask. given Cask.
* `outdated` [--greedy] [--verbose|--quiet] [ <token> ...]: * `outdated` [--greedy] [--verbose|--quiet] [ <token> ...]:
Without token arguments, display all the installed Casks that have newer Without token arguments, display all the installed Casks that have newer
versions available in the tap; otherwise check only the tokens given versions available in the tap; otherwise check only the tokens given
@ -101,9 +101,10 @@ names, and other aspects of this manual are still subject to change.
Reinstall the given Cask. Reinstall the given Cask.
* `search` or `-S` [<text> | /<regexp>/]: * `search` or `-S` [<text> | /<regexp>/]:
Without an argument, display all Casks available for install; otherwise Without an argument, display all locally available Casks for install; no
perform a substring search of known Cask tokens for <text> or, if the online search is performed.
text is delimited by slashes (/<regexp>/), it is interpreted as a Otherwise perform a substring search of known Cask tokens for <text> or,
if the text is delimited by slashes (/<regexp>/), it is interpreted as a
Ruby regular expression. Ruby regular expression.
* `style` [--fix] [ <token> ... ]: * `style` [--fix] [ <token> ... ]:
@ -255,7 +256,7 @@ Environment variables specific to Homebrew-Cask:
export HOMEBREW_CASK_OPTS='--appdir=~/Applications --fontdir=/Library/Fonts' export HOMEBREW_CASK_OPTS='--appdir=~/Applications --fontdir=/Library/Fonts'
Other environment variables: Other environment variables:
* `SUDO_ASKPASS`: * `SUDO_ASKPASS`:
When this variable is set, Homebrew-Cask will call `sudo`(8) with the `-A` option. When this variable is set, Homebrew-Cask will call `sudo`(8) with the `-A` option.

View File

@ -31,7 +31,7 @@ module Homebrew
#{Formatter.url("https://pip.readthedocs.io/en/stable/installing/")} #{Formatter.url("https://pip.readthedocs.io/en/stable/installing/")}
EOS EOS
when "pil" then <<-EOS.undent when "pil" then <<-EOS.undent
Instead of PIL, consider `pip install pillow` or `brew install Homebrew/science/pillow`. Instead of PIL, consider `pip2 install pillow`.
EOS EOS
when "macruby" then <<-EOS.undent when "macruby" then <<-EOS.undent
MacRuby is not packaged and is on an indefinite development hiatus. MacRuby is not packaged and is on an indefinite development hiatus.
@ -53,7 +53,7 @@ module Homebrew
ruin SSH's security. ruin SSH's security.
EOS EOS
when "gsutil" then <<-EOS.undent when "gsutil" then <<-EOS.undent
Install gsutil with `pip install gsutil` Install gsutil with `pip2 install gsutil`
EOS EOS
when "gfortran" then <<-EOS.undent when "gfortran" then <<-EOS.undent
GNU Fortran is now provided as part of GCC, and can be installed with: GNU Fortran is now provided as part of GCC, and can be installed with:

View File

@ -69,29 +69,29 @@ class Options
@options.each(*args, &block) @options.each(*args, &block)
end end
def <<(o) def <<(other)
@options << o @options << other
self self
end end
def +(o) def +(other)
self.class.new(@options + o) self.class.new(@options + other)
end end
def -(o) def -(other)
self.class.new(@options - o) self.class.new(@options - other)
end end
def &(o) def &(other)
self.class.new(@options & o) self.class.new(@options & other)
end end
def |(o) def |(other)
self.class.new(@options | o) self.class.new(@options | other)
end end
def *(arg) def *(other)
@options.to_a * arg @options.to_a * other
end end
def empty? def empty?

View File

@ -11,7 +11,7 @@ module OS
module Mac module Mac
module_function module_function
::MacOS = self # rubocop:disable Style/ConstantName ::MacOS = self # rubocop:disable Naming/ConstantName
raise "Loaded OS::Mac on generic OS!" if ENV["HOMEBREW_TEST_GENERIC_OS"] raise "Loaded OS::Mac on generic OS!" if ENV["HOMEBREW_TEST_GENERIC_OS"]
@ -34,12 +34,12 @@ module OS
def prerelease? def prerelease?
# TODO: bump version when new OS is released # TODO: bump version when new OS is released
version >= "10.13" version >= "10.14"
end end
def outdated_release? def outdated_release?
# TODO: bump version when new OS is released # TODO: bump version when new OS is released
version < "10.10" version < "10.11"
end end
def cat def cat
@ -104,7 +104,7 @@ module OS
# Returns the path to an SDK or nil, following the rules set by #sdk. # Returns the path to an SDK or nil, following the rules set by #sdk.
def sdk_path(v = nil) def sdk_path(v = nil)
s = sdk(v) s = sdk(v)
s.path unless s.nil? s&.path
end end
# See these issues for some history: # See these issues for some history:
@ -129,8 +129,8 @@ module OS
paths << path if path.exist? paths << path if path.exist?
end end
# Finally, some users make their MacPorts or Fink directorie # Finally, some users make their MacPorts or Fink directories
# read-only in order to try out Homebrew, but this doens't work as # read-only in order to try out Homebrew, but this doesn't work as
# some build scripts error out when trying to read from these now # some build scripts error out when trying to read from these now
# unreadable paths. # unreadable paths.
%w[/sw /opt/local].map { |p| Pathname.new(p) }.each do |path| %w[/sw /opt/local].map { |p| Pathname.new(p) }.each do |path|

View File

@ -5,7 +5,7 @@ require "formula"
class LinkageChecker class LinkageChecker
attr_reader :keg, :formula attr_reader :keg, :formula
attr_reader :brewed_dylibs, :system_dylibs, :broken_dylibs, :variable_dylibs attr_reader :brewed_dylibs, :system_dylibs, :broken_dylibs, :variable_dylibs
attr_reader :undeclared_deps, :reverse_links attr_reader :undeclared_deps, :unnecessary_deps, :reverse_links
def initialize(keg, formula = nil) def initialize(keg, formula = nil)
@keg = keg @keg = keg
@ -16,6 +16,7 @@ class LinkageChecker
@variable_dylibs = Set.new @variable_dylibs = Set.new
@undeclared_deps = [] @undeclared_deps = []
@reverse_links = Hash.new { |h, k| h[k] = Set.new } @reverse_links = Hash.new { |h, k| h[k] = Set.new }
@unnecessary_deps = []
check_dylibs check_dylibs
end end
@ -51,7 +52,7 @@ class LinkageChecker
end end
end end
@undeclared_deps = check_undeclared_deps if formula @undeclared_deps, @unnecessary_deps = check_undeclared_deps if formula
end end
def check_undeclared_deps def check_undeclared_deps
@ -77,6 +78,12 @@ class LinkageChecker
a <=> b a <=> b
end end
end end
unnecessary_deps = declared_dep_names.reject do |full_name|
name = full_name.split("/").last
next true if Formula[name].bin.directory?
@brewed_dylibs.keys.map { |x| x.split("/").last }.include?(name)
end
[undeclared_deps, unnecessary_deps]
end end
def display_normal_output def display_normal_output
@ -84,7 +91,8 @@ class LinkageChecker
display_items "Homebrew libraries", @brewed_dylibs display_items "Homebrew libraries", @brewed_dylibs
display_items "Variable-referenced libraries", @variable_dylibs display_items "Variable-referenced libraries", @variable_dylibs
display_items "Missing libraries", @broken_dylibs display_items "Missing libraries", @broken_dylibs
display_items "Possible undeclared dependencies", @undeclared_deps display_items "Undeclared dependencies with linkage", @undeclared_deps
display_items "Dependencies with no linkage", @unnecessary_deps
end end
def display_reverse_output def display_reverse_output
@ -102,6 +110,7 @@ class LinkageChecker
def display_test_output def display_test_output
display_items "Missing libraries", @broken_dylibs display_items "Missing libraries", @broken_dylibs
display_items "Possible unnecessary dependencies", @unnecessary_deps
puts "No broken dylib links" if @broken_dylibs.empty? puts "No broken dylib links" if @broken_dylibs.empty?
end end
@ -113,6 +122,10 @@ class LinkageChecker
!@undeclared_deps.empty? !@undeclared_deps.empty?
end end
def unnecessary_deps?
!@unnecessary_deps.empty?
end
private private
# Whether or not dylib is a harmless broken link, meaning that it's # Whether or not dylib is a harmless broken link, meaning that it's

View File

@ -12,6 +12,7 @@ module OS
mountain_lion: "10.8", mountain_lion: "10.8",
lion: "10.7", lion: "10.7",
snow_leopard: "10.6", snow_leopard: "10.6",
leopard_64: "10.5",
leopard: "10.5", leopard: "10.5",
tiger: "10.4", tiger: "10.4",
}.freeze }.freeze

Some files were not shown because too many files have changed in this diff Show More