Merge branch 'master' into audit_line_rubocop_part_4_rebase_attempt_1

This commit is contained in:
Gautham Goli 2017-10-12 00:29:19 +05:30
commit 7fa51f71f1
301 changed files with 3998 additions and 2862 deletions

View File

@ -1,17 +1,22 @@
**Please note we will close your issue without comment if you delete, do not read or do not fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again.**
# Please always follow these steps: # Please always follow these steps:
- [ ] Confirmed this is a problem with running a `brew` command and not `brew install`ing or the post-install behaviour of one or more formulae? If it's a formulae-specific problem please file this issue at https://github.com/Homebrew/homebrew-core/issues/new - [ ] Confirmed this is a problem with running a `brew` command and not `brew install`ing or the post-install behaviour of one or more formulae? If it's a formulae-specific problem please file this issue at the relevant tap e.g. for Homebrew/homebrew-core https://github.com/Homebrew/homebrew-core/issues/new
- [ ] Ran `brew update` and retried your prior step? - [ ] Ran `brew update` and retried your prior step?
- [ ] Ran `brew doctor`, fixed all issues and retried your prior step? - [ ] Ran `brew doctor`, fixed all issues and retried your prior step?
- [ ] Ran `brew config` and `brew doctor` and included their output with your issue? - [ ] Ran `brew config` and `brew doctor` and included their output with your issue?
**Please note we will close your issue without comment if you delete or do not fill out the issue checklist and provide ALL the requested information.**
To help us debug your issue please explain: To help us debug your issue please explain:
- What you were trying to do (and why) - What you were trying to do (and why)
- What happened (include command output) - What happened (include command output)
- What you expected to happen - What you expected to happen
- Step-by-step reproduction instructions (by running `brew` commands) - Step-by-step reproduction instructions (by running `brew` commands)
# Or propose a feature: # Features
Please replace this section with a detailed description of your proposed feature, the motivation for it, how it would be relevant to at least 90% of Homebrew users and alternatives considered. Please replace this section with:
Please note we will close this issue or ask you to create a pull-request if it's something we're not actively planning to work on. - a detailed description of your proposed feature
- the motivation for the feature
- how the feature would be relevant to at least 90% of Homebrew users
- what alternatives to the feature you have considered
We will close this issue or ask you to create a pull-request if it's something we're not actively planning to work on.

View File

@ -11,30 +11,31 @@ matrix:
fast_finish: true fast_finish: true
include: include:
- os: osx - os: osx
osx_image: xcode8.3 osx_image: xcode9
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
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";
git clone --depth=1 https://github.com/Homebrew/homebrew-test-bot Library/Taps/homebrew/homebrew-test-bot;
else else
umask 022;
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";
git clone --depth=1 https://github.com/Homebrew/homebrew-test-bot Library/Taps/homebrew/homebrew-test-bot;
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 @@
# Contributing to Homebrew # Contributing to Homebrew
First time contributing to Homebrew? Read our [Code of Conduct](https://github.com/Homebrew/brew/blob/master/CODEOFCONDUCT.md#code-of-conduct). First time contributing to Homebrew? Read our [Code of Conduct](https://github.com/Homebrew/brew/blob/master/CODE_OF_CONDUCT.md#code-of-conduct).
### Report a bug ### Report a bug

View File

@ -1,5 +1,5 @@
AllCops: AllCops:
TargetRubyVersion: 2.0 TargetRubyVersion: 2.3
Exclude: Exclude:
- '**/Casks/**/*' - '**/Casks/**/*'
- '**/vendor/**/*' - '**/vendor/**/*'
@ -123,7 +123,7 @@ Style/Tab:
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
@ -193,8 +193,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

@ -1,6 +1,5 @@
inherit_from: inherit_from:
- ../.rubocop.yml - ../.rubocop.yml
- .rubocop_todo.yml
AllCops: AllCops:
Include: Include:
@ -26,12 +25,30 @@ Lint/NestedMethodDefinition:
Lint/ParenthesesAsGroupedExpression: Lint/ParenthesesAsGroupedExpression:
Enabled: true Enabled: true
Metrics/BlockNesting:
Max: 5
Metrics/ModuleLength:
Max: 360
Metrics/ParameterLists: Metrics/ParameterLists:
CountKeywordArgs: false CountKeywordArgs: false
# we won't change backward compatible method names
Naming/MethodName:
Exclude:
- 'compat/**/*'
# we won't change backward compatible predicate names
Naming/PredicateName:
Exclude:
- 'compat/**/*'
NameWhitelist: is_32_bit?, is_64_bit?
Style/BlockDelimiters: Style/BlockDelimiters:
Exclude: Exclude:
- '**/*_spec.rb' - '**/*_spec.rb'
- '**/shared_examples/**/*.rb'
# so many of these in formulae but none in here # so many of these in formulae but none in here
Style/GuardClause: Style/GuardClause:
@ -40,14 +57,3 @@ Style/GuardClause:
# hash-rockets preferred for formulae, a: 1 preferred elsewhere # hash-rockets preferred for formulae, a: 1 preferred elsewhere
Style/HashSyntax: Style/HashSyntax:
EnforcedStyle: ruby19_no_mixed_keys EnforcedStyle: ruby19_no_mixed_keys
# we won't change backward compatible method names
Style/MethodName:
Exclude:
- 'compat/**/*'
# we won't change backward compatible predicate names
Style/PredicateName:
Exclude:
- 'compat/**/*'
NameWhitelist: is_32_bit?, is_64_bit?

View File

@ -1,145 +0,0 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 100`
# on 2017-01-27 21:44:55 +0000 using RuboCop version 0.47.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 17
Lint/HandleExceptions:
Exclude:
- 'cmd/install.rb'
- 'cmd/reinstall.rb'
- 'cmd/tap.rb'
- 'cmd/update-report.rb'
- 'cmd/upgrade.rb'
- 'cmd/uses.rb'
- 'descriptions.rb'
- 'diagnostic.rb'
- 'extend/ENV/super.rb'
- 'extend/pathname.rb'
- 'formula.rb'
- 'formula_versions.rb'
- 'test/ENV_test.rb'
# Offense count: 3
Lint/IneffectiveAccessModifier:
Exclude:
- 'formula.rb'
- 'version.rb'
# Offense count: 1
Lint/Loop:
Exclude:
- 'patch.rb'
# Offense count: 28
Lint/RescueException:
Exclude:
- 'brew.rb'
- 'build.rb'
- 'cmd/fetch.rb'
- 'cmd/reinstall.rb'
- 'cmd/update-report.rb'
- 'debrew.rb'
- 'dev-cmd/pull.rb'
- 'dev-cmd/test.rb'
- 'formula.rb'
- 'formula_installer.rb'
- 'migrator.rb'
- 'postinstall.rb'
- 'readall.rb'
- 'test.rb'
- 'test/ENV_test.rb'
- 'utils/fork.rb'
# Offense count: 1
Lint/ShadowedException:
Exclude:
- 'utils/fork.rb'
# Offense count: 13
# Configuration parameters: CountBlocks.
Metrics/BlockNesting:
Max: 5
# Offense count: 19
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 400
# Offense count: 1
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 6
# Offense count: 2
Security/MarshalLoad:
Exclude:
- 'dependency.rb'
- 'utils/fork.rb'
# Offense count: 1
Style/AccessorMethodName:
Exclude:
- 'extend/ENV/super.rb'
# Offense count: 6
Style/ClassVars:
Exclude:
- 'dev-cmd/audit.rb'
- 'formula_installer.rb'
- 'test/support/helper/fs_leak_logger.rb'
# Offense count: 13
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Exclude:
- 'debrew/irb.rb'
- 'download_strategy.rb'
- 'extend/ENV/super.rb'
- 'formula.rb'
- 'patch.rb'
# Offense count: 13
# Configuration parameters: AllowedVariables.
Style/GlobalVars:
Exclude:
- 'diagnostic.rb'
- 'utils.rb'
# Offense count: 1
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: module_function, extend_self
Style/ModuleFunction:
Exclude:
- 'os/mac/xcode.rb'
# Offense count: 8
Style/MultilineBlockChain:
Exclude:
- 'cmd/search.rb'
- 'dev-cmd/aspell-dictionaries.rb'
- 'dev-cmd/audit.rb'
- 'dev-cmd/man.rb'
- 'diagnostic.rb'
- 'test/patching_test.rb'
# Offense count: 4
# Cop supports --auto-correct.
Style/MutableConstant:
Exclude:
- 'dependency_collector.rb'
- 'formulary.rb'
- 'tab.rb'
- 'tap.rb'
# Offense count: 8
Style/OpMethod:
Exclude:
- 'dependencies.rb'
- 'install_renamed.rb'
- 'options.rb'

View File

@ -11,11 +11,6 @@ SimpleCov.start do
# tests to be dropped. This causes random fluctuations in test coverage. # tests to be dropped. This causes random fluctuations in test coverage.
merge_timeout 86400 merge_timeout 86400
add_filter "/Homebrew/compat/"
add_filter "/Homebrew/dev-cmd/tests.rb"
add_filter "/Homebrew/test/"
add_filter "/Homebrew/vendor/"
if ENV["HOMEBREW_INTEGRATION_TEST"] if ENV["HOMEBREW_INTEGRATION_TEST"]
command_name "#{ENV["HOMEBREW_INTEGRATION_TEST"]} (#{$PROCESS_ID})" command_name "#{ENV["HOMEBREW_INTEGRATION_TEST"]} (#{$PROCESS_ID})"
@ -33,22 +28,32 @@ SimpleCov.start do
end end
else else
command_name "#{command_name} (#{$PROCESS_ID})" command_name "#{command_name} (#{$PROCESS_ID})"
subdirs = Dir.chdir(SimpleCov.root) { Dir.glob("*") }
.reject { |d| d.end_with?(".rb") || ["test", "vendor"].include?(d) }
.map { |d| "#{d}/**/*.rb" }.join(",")
# Not using this during integration tests makes the tests 4x times faster # Not using this during integration tests makes the tests 4x times faster
# without changing the coverage. # without changing the coverage.
track_files "#{SimpleCov.root}/**/*.rb" track_files "#{SimpleCov.root}/{#{subdirs},*.rb}"
end end
add_filter %r{^/compat/}
add_filter %r{^/dev-cmd/tests.rb$}
add_filter %r{^/test/}
add_filter %r{^/vendor/}
# Add groups and the proper project name to the output. # Add groups and the proper project name to the output.
project_name "Homebrew" project_name "Homebrew"
add_group "Cask", "/Homebrew/cask/" add_group "Cask", %r{^/cask/}
add_group "Commands", %w[/Homebrew/cmd/ /Homebrew/dev-cmd/] add_group "Commands", [%r{/cmd/}, %r{^/dev-cmd/}]
add_group "Extensions", "/Homebrew/extend/" add_group "Extensions", %r{^/extend/}
add_group "OS", %w[/Homebrew/extend/os/ /Homebrew/os/] add_group "OS", [%r{^/extend/os/}, %r{^/os/}]
add_group "Requirements", "/Homebrew/requirements/" add_group "Requirements", %r{^/requirements/}
add_group "Scripts", %w[ add_group "Scripts", [
/Homebrew/brew.rb %r{^/brew.rb$},
/Homebrew/build.rb %r{^/build.rb$},
/Homebrew/postinstall.rb %r{^/postinstall.rb$},
/Homebrew/test.rb %r{^/test.rb$},
] ]
end end

View File

@ -5,8 +5,12 @@ end
std_trap = trap("INT") { exit! 130 } # no backtrace thanks std_trap = trap("INT") { exit! 130 } # no backtrace thanks
# check ruby version before requiring any modules. # check ruby version before requiring any modules.
RUBY_TWO = RUBY_VERSION.split(".").first.to_i >= 2 RUBY_VERSION_SPLIT = RUBY_VERSION.split "."
raise "Homebrew must be run under Ruby 2!" unless RUBY_TWO RUBY_X = RUBY_VERSION_SPLIT[0].to_i
RUBY_Y = RUBY_VERSION_SPLIT[1].to_i
if RUBY_X < 2 || (RUBY_X == 2 && RUBY_Y < 3)
raise "Homebrew must be run under Ruby 2.3!"
end
require "pathname" require "pathname"
HOMEBREW_LIBRARY_PATH = Pathname.new(__FILE__).realpath.parent HOMEBREW_LIBRARY_PATH = Pathname.new(__FILE__).realpath.parent
@ -105,18 +109,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"
@ -144,7 +146,7 @@ rescue MethodDeprecatedError => e
$stderr.puts " #{Formatter.url(e.issues_url)}" $stderr.puts " #{Formatter.url(e.issues_url)}"
end end
exit 1 exit 1
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
onoe e onoe e
if internal_cmd && defined?(OS::ISSUES_URL) && if internal_cmd && defined?(OS::ISSUES_URL) &&
!ENV["HOMEBREW_NO_AUTO_UPDATE"] !ENV["HOMEBREW_NO_AUTO_UPDATE"]

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
@ -186,7 +190,7 @@ begin
options = Options.create(ARGV.flags_only) options = Options.create(ARGV.flags_only)
build = Build.new(formula, options) build = Build.new(formula, options)
build.install build.install
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
Marshal.dump(e, error_pipe) Marshal.dump(e, error_pipe)
error_pipe.close error_pipe.close
exit! 1 exit! 1

View File

@ -25,7 +25,6 @@ require "hbc/scopes"
require "hbc/staged" require "hbc/staged"
require "hbc/system_command" require "hbc/system_command"
require "hbc/topological_hash" require "hbc/topological_hash"
require "hbc/underscore_supporting_uri"
require "hbc/url" require "hbc/url"
require "hbc/utils" require "hbc/utils"
require "hbc/verify" require "hbc/verify"

View File

@ -25,47 +25,5 @@ require "hbc/artifact/zap"
module Hbc module Hbc
module Artifact module Artifact
# NOTE: Order is important here!
#
# The `uninstall` stanza should be run first, as it may
# depend on other artifacts still being installed.
#
# We want to extract nested containers before we
# handle any other artifacts.
#
TYPES = [
PreflightBlock,
Uninstall,
NestedContainer,
Installer,
App,
Suite,
Artifact, # generic 'artifact' stanza
Colorpicker,
Pkg,
Prefpane,
Qlplugin,
Dictionary,
Font,
Service,
StageOnly,
Binary,
InputMethod,
InternetPlugin,
AudioUnitPlugin,
VstPlugin,
Vst3Plugin,
ScreenSaver,
PostflightBlock,
Zap,
].freeze
def self.for_cask(cask, options = {})
odebug "Determining which artifacts are present in Cask #{cask}"
TYPES
.select { |klass| klass.me?(cask) }
.map { |klass| klass.new(cask, options) }
end
end end
end end

View File

@ -0,0 +1,109 @@
module Hbc
module Artifact
class AbstractArtifact
include Comparable
extend Predicable
def self.english_name
@english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')
end
def self.english_article
@english_article ||= (english_name =~ /^[aeiou]/i) ? "an" : "a"
end
def self.dsl_key
@dsl_key ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym
end
def self.dirmethod
@dirmethod ||= "#{dsl_key}dir".to_sym
end
def <=>(other)
return unless other.class < AbstractArtifact
return 0 if self.class == other.class
@@sort_order ||= [ # rubocop:disable Style/ClassVars
PreflightBlock,
# The `uninstall` stanza should be run first, as it may
# depend on other artifacts still being installed.
Uninstall,
# We want to extract nested containers before we
# handle any other artifacts.
NestedContainer,
Installer,
[
App,
Suite,
Artifact,
Colorpicker,
Prefpane,
Qlplugin,
Dictionary,
Font,
Service,
InputMethod,
InternetPlugin,
AudioUnitPlugin,
VstPlugin,
Vst3Plugin,
ScreenSaver,
],
# `pkg` should be run before `binary`, so
# targets are created prior to linking.
Pkg,
Binary,
PostflightBlock,
Zap,
].each_with_index.flat_map { |classes, i| [*classes].map { |c| [c, i] } }.to_h
(@@sort_order[self.class] <=> @@sort_order[other.class]).to_i
end
# 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.
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
# TODO: when stanza names are harmonized with class names,
# stanza may not be needed as an explicit argument
description = key ? "#{stanza} #{key.inspect}" : stanza.to_s
# backward-compatible string value
arguments = { executable: arguments } if arguments.is_a?(String)
# key sanity
permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :print_stdout, :print_stderr]
unknown_keys = arguments.keys - permitted_keys
unless unknown_keys.empty?
opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
arguments.select! { |k| permitted_keys.include?(k) }
# key warnings
override_keys = override_arguments.keys
ignored_keys = arguments.keys & override_keys
unless ignored_keys.empty?
onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)."
end
# extract executable
executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil
arguments = default_arguments.merge arguments
arguments.merge! override_arguments
[executable, arguments]
end
attr_reader :cask
def initialize(cask)
@cask = cask
end
def to_s
"#{summarize} (#{self.class.english_name})"
end
end
end
end

View File

@ -1,39 +1,45 @@
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) attr_reader :directives
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
def initialize(cask, **directives)
super(cask)
@directives = directives
end end
def self.me?(cask) def install_phase(**)
cask.artifacts[artifact_dsl_key].any? || abstract_phase(self.class.dsl_key)
cask.artifacts[uninstall_artifact_dsl_key].any?
end end
def install_phase def uninstall_phase(**)
abstract_phase(self.class.artifact_dsl_key) abstract_phase(self.class.uninstall_dsl_key)
end
def uninstall_phase
abstract_phase(self.class.uninstall_artifact_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.flat_map { |key, val| [*val].map { |v| "#{key.inspect} => #{v.inspect}" } }.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]) result = 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)
@ -250,7 +267,7 @@ module Hbc
set trashedItem to POSIX path of (item i of trashedItems as string) set trashedItem to POSIX path of (item i of trashedItems as string)
set output to output & trashedItem set output to output & trashedItem
if i < count trashedItems then if i < count trashedItems then
set output to output & (do shell script "printf \"\\0\"") set output to output & character id 0
end if end if
end repeat end repeat
@ -258,9 +275,12 @@ module Hbc
end tell end tell
end run end run
EOS EOS
# Remove AppleScript's automatic newline.
result.tap { |r| r.stdout.sub!(/\n$/, "") }
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 +288,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

@ -1,80 +0,0 @@
module Hbc
module Artifact
class Base
extend Predicable
def self.artifact_name
@artifact_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase
end
def self.artifact_english_name
@artifact_english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')
end
def self.artifact_english_article
@artifact_english_article ||= (artifact_english_name =~ /^[aeiou]/i) ? "an" : "a"
end
def self.artifact_dsl_key
@artifact_dsl_key ||= artifact_name.to_sym
end
def self.artifact_dirmethod
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym
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
# 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)
# TODO: when stanza names are harmonized with class names,
# stanza may not be needed as an explicit argument
description = key ? "#{stanza} #{key.inspect}" : stanza.to_s
# backward-compatible string value
arguments = { executable: arguments } if arguments.is_a?(String)
# key sanity
permitted_keys = [:args, :input, :executable, :must_succeed, :sudo, :print_stdout, :print_stderr]
unknown_keys = arguments.keys - permitted_keys
unless unknown_keys.empty?
opoo %Q{Unknown arguments to #{description} -- #{unknown_keys.inspect} (ignored). Running "brew update; brew cleanup; brew cask cleanup" will likely fix it.}
end
arguments.select! { |k| permitted_keys.include?(k) }
# key warnings
override_keys = override_arguments.keys
ignored_keys = arguments.keys & override_keys
unless ignored_keys.empty?
onoe "Some arguments to #{description} will be ignored -- :#{unknown_keys.inspect} (overridden)."
end
# extract executable
executable = arguments.key?(:executable) ? arguments.delete(:executable) : nil
arguments = default_arguments.merge arguments
arguments.merge! override_arguments
[executable, arguments]
end
def summary
{}
end
attr_predicate :force?, :verbose?
def initialize(cask, command: SystemCommand, force: false, verbose: false)
@cask = cask
@command = command
@force = force
@verbose = verbose
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,35 @@
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 summarize
path.relative_path_from(cask.staged_path).to_s
end
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,61 @@ 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, **stanza_options)
:pkg stanza_options.extend(HashValidator).assert_valid_keys(
:allow_untrusted, :choices
)
new(cask, path, **stanza_options)
end end
def load_pkg_description(pkg_description) attr_reader :path, :stanza_options
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift def initialize(cask, path, **stanza_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) @stanza_options = stanza_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 if stanza_options.fetch(:allow_untrusted, false)
args << "-allowUntrusted"
end
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 = stanza_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

@ -70,12 +70,16 @@ module Hbc
previous_cask_contents = Git.last_revision_of_file(tap.path, @cask.sourcefile_path, before_commit: commit_range) previous_cask_contents = Git.last_revision_of_file(tap.path, @cask.sourcefile_path, before_commit: commit_range)
return if previous_cask_contents.empty? return if previous_cask_contents.empty?
previous_cask = CaskLoader.load_from_string(previous_cask_contents) begin
previous_cask = CaskLoader.load(previous_cask_contents)
return unless previous_cask.version == cask.version return unless previous_cask.version == cask.version
return if previous_cask.sha256 == cask.sha256 return if previous_cask.sha256 == cask.sha256
add_error "only sha256 changed (see: https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/sha256.md)" add_error "only sha256 changed (see: https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/sha256.md)"
rescue CaskError => e
add_warning "Skipped version and checksum comparison. Reading previous version failed: #{e}"
end
end end
def check_version def check_version
@ -143,7 +147,15 @@ module Hbc
def check_appcast_http_code def check_appcast_http_code
odebug "Verifying appcast returns 200 HTTP response code" odebug "Verifying appcast returns 200 HTTP response code"
result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false)
curl_executable, *args = curl_args(
"--compressed", "--location", "--fail",
"--write-out", "%{http_code}",
"--output", "/dev/null",
cask.appcast,
user_agent: :fake
)
result = @command.run(curl_executable, args: args, print_stderr: false)
if result.success? if result.success?
http_code = result.stdout.chomp http_code = result.stdout.chomp
add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200" add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200"
@ -206,12 +218,10 @@ module Hbc
end end
def check_generic_artifacts def check_generic_artifacts
cask.artifacts[:artifact].each do |source, target_hash| cask.artifacts.select { |a| a.is_a?(Hbc::Artifact::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

@ -43,7 +43,7 @@ module Hbc
def audit_languages(languages) def audit_languages(languages)
ohai "Auditing language: #{languages.map { |lang| "'#{lang}'" }.join(", ")}" ohai "Auditing language: #{languages.map { |lang| "'#{lang}'" }.join(", ")}"
MacOS.instance_variable_set(:@languages, languages) MacOS.instance_variable_set(:@languages, languages)
audit_cask_instance(CaskLoader.load_from_file(cask.sourcefile_path)) audit_cask_instance(CaskLoader.load(cask.sourcefile_path))
ensure ensure
CLI::Cleanup.run(cask.token) if audit_download? CLI::Cleanup.run(cask.token) if audit_download?
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

@ -3,6 +3,18 @@ module Hbc
class FromContentLoader class FromContentLoader
attr_reader :content attr_reader :content
def self.can_load?(ref)
return false unless ref.respond_to?(:to_str)
content = ref.to_str
token = /(?:"[^"]*"|'[^']*')/
curly = /\(\s*#{token}\s*\)\s*\{.*\}/
do_end = /\s+#{token}\s+do(?:\s*;\s*|\s+).*end/
regex = /\A\s*cask(?:#{curly.source}|#{do_end.source})\s*\Z/m
content.match?(regex)
end
def initialize(content) def initialize(content)
@content = content @content = content
end end
@ -56,7 +68,8 @@ module Hbc
class FromURILoader < FromPathLoader class FromURILoader < FromPathLoader
def self.can_load?(ref) def self.can_load?(ref)
ref.to_s.match?(::URI.regexp) uri_regex = ::URI::DEFAULT_PARSER.make_regexp
ref.to_s.match?(Regexp.new('\A' + uri_regex.source + '\Z', uri_regex.options))
end end
attr_reader :url attr_reader :url
@ -71,7 +84,7 @@ module Hbc
begin begin
ohai "Downloading #{url}." ohai "Downloading #{url}."
curl url, "-o", path curl_download url, to: path
rescue ErrorDuringExecution rescue ErrorDuringExecution
raise CaskUnavailableError.new(token, "Failed to download #{Formatter.url(url)}.") raise CaskUnavailableError.new(token, "Failed to download #{Formatter.url(url)}.")
end end
@ -116,6 +129,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
@ -131,14 +160,6 @@ module Hbc
end end
end end
def self.load_from_file(path)
FromPathLoader.new(path).load
end
def self.load_from_string(content)
FromContentLoader.new(content).load
end
def self.path(ref) def self.path(ref)
self.for(ref).path self.for(ref).path
end end
@ -149,6 +170,8 @@ module Hbc
def self.for(ref) def self.for(ref)
[ [
FromInstanceLoader,
FromContentLoader,
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

@ -23,6 +23,7 @@ module Hbc
installation_info(cask) installation_info(cask)
repo_info(cask) repo_info(cask)
name_info(cask) name_info(cask)
language_info(cask)
artifact_info(cask) artifact_info(cask)
Installer.print_caveats(cask) Installer.print_caveats(cask)
end end
@ -51,6 +52,13 @@ module Hbc
puts cask.name.empty? ? Formatter.error("None") : cask.name puts cask.name.empty? ? Formatter.error("None") : cask.name
end end
def self.language_info(cask)
return if cask.languages.empty?
ohai "Languages"
puts cask.languages.join(", ")
end
def self.repo_info(cask) def self.repo_info(cask)
user, repo, token = QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token }) user, repo, token = QualifiedToken.parse(Hbc.all_tokens.detect { |t| t.split("/").last == cask.token })
@ -69,12 +77,10 @@ module Hbc
def self.artifact_info(cask) def self.artifact_info(cask)
ohai "Artifacts" ohai "Artifacts"
DSL::ORDINARY_ARTIFACT_TYPES.each do |type| cask.artifacts.each do |artifact|
next if cask.artifacts[type].empty? next unless artifact.respond_to?(:install_phase)
cask.artifacts[type].each do |artifact| next unless DSL::ORDINARY_ARTIFACT_CLASSES.include?(artifact.class)
activatable_item = (type == :stage_only) ? "<none>" : artifact.first puts artifact.to_s
puts "#{activatable_item} (#{type})"
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

@ -59,7 +59,7 @@ module Hbc
end end
def modified_cask_files def modified_cask_files
@modified_cask_files ||= git_filter_cask_files("AM") @modified_cask_files ||= git_filter_cask_files("AMR")
end end
def added_cask_files def added_cask_files

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
@ -23,16 +23,16 @@ module Hbc
elsif versions? elsif versions?
puts self.class.format_versioned(cask) puts self.class.format_versioned(cask)
else else
cask = CaskLoader.load_from_file(cask.installed_caskfile) cask = CaskLoader.load(cask.installed_caskfile)
self.class.list_artifacts(cask) self.class.list_artifacts(cask)
end end
end end
end end
def self.list_artifacts(cask) def self.list_artifacts(cask)
Artifact.for_cask(cask).each do |artifact| cask.artifacts.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,18 @@ module Hbc
end end
def self.search_remote(query) def self.search_remote(query)
matches = GitHub.search_code("user:caskroom", "path:Casks", "filename:#{query}", "extension:rb") matches = begin
[*matches].map do |match| 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|
tap = Tap.fetch(match["repository"]["full_name"]) tap = Tap.fetch(match["repository"]["full_name"])
next if tap.installed? next if tap.installed?
"#{tap.name}/#{File.basename(match["path"], ".rb")}" "#{tap.name}/#{File.basename(match["path"], ".rb")}"

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}"
@ -20,7 +16,7 @@ module Hbc
if cask.installed? && !cask.installed_caskfile.nil? if cask.installed? && !cask.installed_caskfile.nil?
# use the same cask file that was used for installation, if possible # use the same cask file that was used for installation, if possible
cask = CaskLoader.load_from_file(cask.installed_caskfile) if cask.installed_caskfile.exist? cask = CaskLoader.load(cask.installed_caskfile) if cask.installed_caskfile.exist?
end end
Installer.new(cask, binaries: binaries?, verbose: verbose?, force: force?).uninstall Installer.new(cask, binaries: binaries?, verbose: verbose?, force: force?).uninstall

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

@ -4,6 +4,7 @@ require "hbc/container/bzip2"
require "hbc/container/cab" require "hbc/container/cab"
require "hbc/container/criteria" require "hbc/container/criteria"
require "hbc/container/dmg" require "hbc/container/dmg"
require "hbc/container/directory"
require "hbc/container/executable" require "hbc/container/executable"
require "hbc/container/generic_unar" require "hbc/container/generic_unar"
require "hbc/container/gpg" require "hbc/container/gpg"
@ -14,6 +15,7 @@ require "hbc/container/otf"
require "hbc/container/pkg" require "hbc/container/pkg"
require "hbc/container/seven_zip" require "hbc/container/seven_zip"
require "hbc/container/sit" require "hbc/container/sit"
require "hbc/container/svn_repository"
require "hbc/container/tar" require "hbc/container/tar"
require "hbc/container/ttf" require "hbc/container/ttf"
require "hbc/container/rar" require "hbc/container/rar"
@ -43,6 +45,7 @@ module Hbc
Xz, # pure xz Xz, # pure xz
Gpg, # GnuPG signed data Gpg, # GnuPG signed data
Executable, Executable,
SvnRepository,
] ]
# for explicit use only (never autodetected): # for explicit use only (never autodetected):
# Hbc::Container::Naked # Hbc::Container::Naked

View File

@ -20,7 +20,7 @@ module Hbc
unless children.count == 1 && unless children.count == 1 &&
!nested_container.directory? && !nested_container.directory? &&
@cask.artifacts[:nested_container].empty? && @cask.artifacts.none? { |a| a.is_a?(Artifact::NestedContainer) } &&
extract_nested_container(nested_container) extract_nested_container(nested_container)
children.each do |src| children.each do |src|

View File

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

View File

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

View File

@ -8,7 +8,7 @@ module Hbc
return true if criteria.magic_number(/^#!\s*\S+/) return true if criteria.magic_number(/^#!\s*\S+/)
begin begin
MachO.open(criteria.path).header.executable? criteria.path.file? && MachO.open(criteria.path).header.executable?
rescue MachO::MagicError rescue MachO::MagicError
false false
end end

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

@ -0,0 +1,15 @@
require "hbc/container/directory"
module Hbc
class Container
class SvnRepository < Directory
def self.me?(criteria)
criteria.path.join(".svn").directory?
end
def skip_path?(path)
path.basename.to_s == ".svn"
end
end
end
end

View File

@ -10,7 +10,7 @@ module Hbc
class AbstractDownloadStrategy class AbstractDownloadStrategy
attr_reader :cask, :name, :url, :uri_object, :version attr_reader :cask, :name, :url, :uri_object, :version
def initialize(cask, command = SystemCommand) def initialize(cask, command: SystemCommand)
@cask = cask @cask = cask
@command = command @command = command
# TODO: this excess of attributes is a function of integrating # TODO: this excess of attributes is a function of integrating
@ -33,8 +33,8 @@ module Hbc
class HbVCSDownloadStrategy < AbstractDownloadStrategy class HbVCSDownloadStrategy < AbstractDownloadStrategy
REF_TYPES = [:branch, :revision, :revisions, :tag].freeze REF_TYPES = [:branch, :revision, :revisions, :tag].freeze
def initialize(cask, command = SystemCommand) def initialize(*args, **options)
super super(*args, **options)
@ref_type, @ref = extract_ref @ref_type, @ref = extract_ref
@clone = Hbc.cache.join(cache_filename) @clone = Hbc.cache.join(cache_filename)
end end
@ -64,11 +64,6 @@ module Hbc
end end
class CurlDownloadStrategy < AbstractDownloadStrategy class CurlDownloadStrategy < AbstractDownloadStrategy
# TODO: should be part of url object
def mirrors
@mirrors ||= []
end
def tarball_path def tarball_path
@tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}") @tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}")
end end
@ -95,13 +90,8 @@ module Hbc
end end
end end
def downloaded_size
temporary_path.size? || 0
end
def _fetch def _fetch
odebug "Calling curl with args #{cask_curl_args}" curl_download url, *cask_curl_args, to: temporary_path, user_agent: uri_object.user_agent
curl(*cask_curl_args)
end end
def fetch def fetch
@ -131,33 +121,12 @@ module Hbc
ignore_interrupts { temporary_path.rename(tarball_path) } ignore_interrupts { temporary_path.rename(tarball_path) }
end end
tarball_path tarball_path
rescue CurlDownloadStrategyError
raise if mirrors.empty?
puts "Trying a mirror..."
@url = mirrors.shift
retry
end end
private private
def cask_curl_args def cask_curl_args
default_curl_args.tap do |args| cookies_args + referer_args
args.concat(user_agent_args)
args.concat(cookies_args)
args.concat(referer_args)
end
end
def default_curl_args
[url, "-C", downloaded_size, "-o", temporary_path]
end
def user_agent_args
if uri_object.user_agent
["-A", uri_object.user_agent]
else
[]
end
end end
def cookies_args def cookies_args
@ -191,8 +160,7 @@ module Hbc
class CurlPostDownloadStrategy < CurlDownloadStrategy class CurlPostDownloadStrategy < CurlDownloadStrategy
def cask_curl_args def cask_curl_args
super super.concat(post_args)
default_curl_args.concat(post_args)
end end
def post_args def post_args
@ -225,8 +193,8 @@ module Hbc
# super does not provide checks for already-existing downloads # super does not provide checks for already-existing downloads
def fetch def fetch
if tarball_path.exist? if cached_location.directory?
puts "Already downloaded: #{tarball_path}" puts "Already downloaded: #{cached_location}"
else else
@url = @url.sub(/^svn\+/, "") if @url =~ %r{^svn\+http://} @url = @url.sub(/^svn\+/, "") if @url =~ %r{^svn\+http://}
ohai "Checking out #{@url}" ohai "Checking out #{@url}"
@ -252,9 +220,8 @@ module Hbc
else else
fetch_repo @clone, @url fetch_repo @clone, @url
end end
compress
end end
tarball_path cached_location
end end
# This primary reason for redefining this method is the trust_cert # This primary reason for redefining this method is the trust_cert
@ -288,10 +255,6 @@ module Hbc
print_stderr: false) print_stderr: false)
end end
def tarball_path
@tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar")
end
def shell_quote(str) def shell_quote(str)
# Oh god escaping shell args. # Oh god escaping shell args.
# See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/ # See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/
@ -304,35 +267,5 @@ module Hbc
yield name, url yield name, url
end end
end end
private
# TODO/UPDATE: the tar approach explained below is fragile
# against challenges such as case-sensitive filesystems,
# and must be re-implemented.
#
# Seems nutty: we "download" the contents into a tape archive.
# Why?
# * A single file is tractable to the rest of the Cask toolchain,
# * An alternative would be to create a Directory container type.
# However, some type of file-serialization trick would still be
# needed in order to enable calculating a single checksum over
# a directory. So, in that alternative implementation, the
# special cases would propagate outside this class, including
# the use of tar or equivalent.
# * SubversionDownloadStrategy.cached_location is not versioned
# * tarball_path provides a needed return value for our overridden
# fetch method.
# * We can also take this private opportunity to strip files from
# the download which are protocol-specific.
def compress
Dir.chdir(cached_location) do
@command.run!("/usr/bin/tar",
args: ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."],
print_stderr: false)
end
clear_cache
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_CLASSES = ORDINARY_ARTIFACT_CLASSES - [Artifact::StageOnly]
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 [
@ -66,21 +63,23 @@ module Hbc
:gpg, :gpg,
:homepage, :homepage,
:language, :language,
:languages,
:name, :name,
:sha256, :sha256,
:staged_path, :staged_path,
:url, :url,
:version, :version,
:appdir, :appdir,
*ORDINARY_ARTIFACT_TYPES, *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),
*ACTIVATABLE_ARTIFACT_TYPES, *ACTIVATABLE_ARTIFACT_CLASSES.map(&:dsl_key),
*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 :cask, :token
def initialize(token)
@token = token def initialize(cask)
@cask = cask
@token = cask.token
end end
def name(*args) def name(*args)
@ -93,12 +92,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)
@ -106,19 +107,21 @@ module Hbc
end end
def language(*args, default: false, &block) def language(*args, default: false, &block)
if !args.empty? && block_given? if args.empty?
language_eval
elsif block_given?
@language_blocks ||= {} @language_blocks ||= {}
@language_blocks[args] = block @language_blocks[args] = block
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
else else
language_eval raise CaskInvalidError.new(cask, "No block given to language stanza.")
end end
end end
@ -127,6 +130,10 @@ module Hbc
return @language = nil if @language_blocks.nil? || @language_blocks.empty? return @language = nil if @language_blocks.nil? || @language_blocks.empty?
if @language_blocks.default.nil?
raise CaskInvalidError.new(cask, "No default language specified.")
end
MacOS.languages.map(&Locale.method(:parse)).each do |locale| MacOS.languages.map(&Locale.method(:parse)).each do |locale|
key = @language_blocks.keys.detect do |strings| key = @language_blocks.keys.detect do |strings|
strings.any? { |string| locale.include?(string) } strings.any? { |string| locale.include?(string) }
@ -140,6 +147,12 @@ module Hbc
@language = @language_blocks.default.call @language = @language_blocks.default.call
end end
def languages
return [] if @language_blocks.nil?
@language_blocks.keys.flatten
end
def url(*args, &block) def url(*args, &block)
set_unique_stanza(:url, args.empty? && !block_given?) do set_unique_stanza(:url, args.empty? && !block_given?) do
begin begin
@ -162,8 +175,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.add(Artifact::NestedContainer.new(cask, container.nested))
end end
end end
end end
@ -173,7 +186,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 +195,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 +208,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
@ -206,7 +219,7 @@ module Hbc
end end
def artifacts def artifacts
@artifacts ||= Hash.new { |hash, key| hash[key] = Set.new } @artifacts ||= SortedSet.new
end end
def caskroom_path def caskroom_path
@ -237,39 +250,27 @@ 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|
define_method(type) do |*args| define_method(klass.dsl_key) do |*args|
if type == :stage_only begin
if args != [true] if [*artifacts.map(&:class), klass].include?(Artifact::StageOnly) && (artifacts.map(&:class) & ACTIVATABLE_ARTIFACT_CLASSES).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.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.add(klass.new(cask, 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

@ -3,16 +3,20 @@ require "hbc/system_command"
module Hbc module Hbc
class DSL class DSL
class Appcast class Appcast
attr_reader :parameters, :checkpoint attr_reader :uri, :checkpoint, :parameters
def initialize(uri, parameters = {}) def initialize(uri, **parameters)
@parameters = parameters @uri = URI(uri)
@uri = UnderscoreSupportingURI.parse(uri) @parameters = parameters
@checkpoint = @parameters[:checkpoint] @checkpoint = parameters[:checkpoint]
end end
def calculate_checkpoint def calculate_checkpoint
result = SystemCommand.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--fail", @uri], print_stderr: false) curl_executable, *args = curl_args(
"--compressed", "--location", "--fail", uri,
user_agent: :fake
)
result = SystemCommand.run(curl_executable, args: args, print_stderr: false)
checkpoint = if result.success? checkpoint = if result.success?
processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}m, "") processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}m, "")
@ -26,11 +30,11 @@ module Hbc
end end
def to_yaml def to_yaml
[@uri, @parameters].to_yaml [uri, parameters].to_yaml
end end
def to_s def to_s
@uri.to_s uri.to_s
end end
end end
end end

View File

@ -10,14 +10,14 @@ module Hbc
def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir, :language def_delegators :@cask, :token, :version, :caskroom_path, :staged_path, :appdir, :language
def system_command(executable, options = {}) def system_command(executable, **options)
@command.run!(executable, options) @command.run!(executable, **options)
end end
def method_missing(method, *) def method_missing(method, *)
if method if method
underscored_class = self.class.name.gsub(/([[:lower:]])([[:upper:]][[:lower:]])/, '\1_\2').downcase underscored_class = self.class.name.gsub(/([[:lower:]])([[:upper:]][[:lower:]])/, '\1_\2').downcase
section = underscored_class.downcase.split("::").last section = underscored_class.split("::").last
Utils.method_missing_message(method, @cask.to_s, section) Utils.method_missing_message(method, @cask.to_s, section)
nil nil
else else

View File

@ -48,7 +48,7 @@ module Hbc
brew cask install java brew cask install java
EOS EOS
elsif java_version.include?("8") || java_version.include?("+") elsif java_version.include?("9") || java_version.include?("+")
puts <<-EOS.undent puts <<-EOS.undent
#{@cask} requires Java #{java_version}. You can install the latest version with #{@cask} requires Java #{java_version}. You can install the latest version with

View File

@ -10,25 +10,20 @@ module Hbc
:java, :java,
] ]
attr_accessor(*VALID_KEYS) attr_reader *VALID_KEYS
attr_accessor :pairs
def initialize(pairs = {}) def initialize(pairs = {})
@pairs = pairs @pairs = pairs
VALID_KEYS.each do |key|
instance_variable_set("@#{key}", Set.new)
end
pairs.each do |key, value| pairs.each do |key, value|
raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key) raise "invalid conflicts_with key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym instance_variable_set("@#{key}", instance_variable_get("@#{key}").merge([*value]))
send(writer_method, value)
end end
end end
def to_yaml
@pairs.to_yaml
end
def to_s
@pairs.inspect
end
end end
end end
end end

View File

@ -14,11 +14,11 @@ module Hbc
def initialize(signature, parameters = {}) def initialize(signature, parameters = {})
@parameters = parameters @parameters = parameters
@signature = UnderscoreSupportingURI.parse(signature) @signature = URI(signature)
parameters.each do |hkey, hvalue| parameters.each do |hkey, hvalue|
raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey) raise "invalid 'gpg' parameter: '#{hkey.inspect}'" unless VALID_PARAMETERS.include?(hkey)
writer_method = "#{hkey}=".to_sym writer_method = "#{hkey}=".to_sym
hvalue = UnderscoreSupportingURI.parse(hvalue) if hkey == :key_url hvalue = URI(hvalue) if hkey == :key_url
valid_id?(hvalue) if hkey == :key_id valid_id?(hvalue) if hkey == :key_id
send(writer_method, hvalue) send(writer_method, hvalue)
end end
@ -35,7 +35,7 @@ module Hbc
end end
def to_yaml def to_yaml
# bug, :key_url value is not represented as an instance of Hbc::UnderscoreSupportingURI # bug, :key_url value is not represented as an instance of URI
[@signature, @parameters].to_yaml [@signature, @parameters].to_yaml
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

@ -17,6 +17,19 @@ module Hbc
end end
end end
class CaskConflictError < AbstractCaskErrorWithToken
attr_reader :conflicting_cask
def initialize(token, conflicting_cask)
super(token)
@conflicting_cask = conflicting_cask
end
def to_s
"Cask '#{token}' conflicts with '#{conflicting_cask}'."
end
end
class CaskUnavailableError < AbstractCaskErrorWithToken class CaskUnavailableError < AbstractCaskErrorWithToken
def to_s def to_s
"Cask '#{token}' is unavailable" << (reason.empty? ? "." : ": #{reason}") "Cask '#{token}' is unavailable" << (reason.empty? ? "." : ": #{reason}")

View File

@ -86,6 +86,8 @@ module Hbc
raise CaskAlreadyInstalledError, @cask raise CaskAlreadyInstalledError, @cask
end end
check_conflicts
print_caveats print_caveats
fetch fetch
uninstall_existing_cask if @reinstall uninstall_existing_cask if @reinstall
@ -98,6 +100,21 @@ module Hbc
puts summary puts summary
end end
def check_conflicts
return unless @cask.conflicts_with
@cask.conflicts_with.cask.each do |conflicting_cask|
begin
conflicting_cask = CaskLoader.load(conflicting_cask)
if conflicting_cask.installed?
raise CaskConflictError.new(@cask, conflicting_cask)
end
rescue CaskUnavailableError
next # Ignore conflicting Casks that do not exist.
end
end
end
def reinstall def reinstall
odebug "Hbc::Installer#reinstall" odebug "Hbc::Installer#reinstall"
@reinstall = true @reinstall = true
@ -109,7 +126,7 @@ module Hbc
# use the same cask file that was used for installation, if possible # use the same cask file that was used for installation, if possible
installed_caskfile = @cask.installed_caskfile installed_caskfile = @cask.installed_caskfile
installed_cask = installed_caskfile.exist? ? CaskLoader.load_from_file(installed_caskfile) : @cask installed_cask = installed_caskfile.exist? ? CaskLoader.load(installed_caskfile) : @cask
# Always force uninstallation, ignore method parameter # Always force uninstallation, ignore method parameter
Installer.new(installed_cask, binaries: binaries?, verbose: verbose?, force: true).uninstall Installer.new(installed_cask, binaries: binaries?, verbose: verbose?, force: true).uninstall
@ -142,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)
@ -160,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 = @cask.artifacts
odebug "#{artifacts.length} artifact/s defined", artifacts odebug "#{artifacts.length} artifact/s defined", artifacts
artifacts.each do |artifact| artifacts.each do |artifact|
@ -171,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
@ -179,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
@ -344,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
@ -357,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 = @cask.artifacts
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 = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).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.select { |a| a.is_a?(Artifact::App) }.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
@ -112,11 +112,7 @@ module Hbc
processed_output[:stderr], processed_output[:stderr],
processed_status.exitstatus) processed_status.exitstatus)
end end
end
end
module Hbc
class SystemCommand
class Result class Result
attr_accessor :command, :stdout, :stderr, :exit_status attr_accessor :command, :stdout, :stderr, :exit_status

View File

@ -1,28 +0,0 @@
require "uri"
module Hbc
module UnderscoreSupportingURI
def self.parse(maybe_uri)
return nil if maybe_uri.nil?
URI.parse(maybe_uri)
rescue URI::InvalidURIError => e
scheme, host, path = simple_parse(maybe_uri)
raise e unless path && host.include?("_")
URI.parse(without_host_underscores(scheme, host, path)).tap do |uri|
uri.instance_variable_set("@host", host)
end
end
def self.simple_parse(maybe_uri)
scheme, host_and_path = maybe_uri.split("://")
host, path = host_and_path.split("/", 2)
[scheme, host, path]
rescue StandardError
nil
end
def self.without_host_underscores(scheme, host, path)
["#{scheme}:/", host.tr("_", "-"), path].join("/")
end
end
end

View File

@ -1,8 +1,6 @@
module Hbc module Hbc
class URL class URL
FAKE_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) https://caskroom.github.io".freeze attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data, :user_agent
attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data
extend Forwardable extend Forwardable
def_delegators :uri, :path, :scheme, :to_s def_delegators :uri, :path, :scheme, :to_s
@ -16,8 +14,8 @@ module Hbc
end end
def initialize(uri, options = {}) def initialize(uri, options = {})
@uri = Hbc::UnderscoreSupportingURI.parse(uri) @uri = URI(uri)
@user_agent = options[:user_agent] @user_agent = options.fetch(:user_agent, :default)
@cookies = options[:cookies] @cookies = options[:cookies]
@referer = options[:referer] @referer = options[:referer]
@using = options[:using] @using = options[:using]
@ -25,10 +23,5 @@ module Hbc
@trust_cert = options[:trust_cert] @trust_cert = options[:trust_cert]
@data = options[:data] @data = options[:data]
end end
def user_agent
return FAKE_USER_AGENT if @user_agent == :fake
@user_agent
end
end end
end end

View File

@ -33,7 +33,7 @@ module Hbc
meta_dir = cached || cask.metadata_subdir("gpg", :now, true) meta_dir = cached || cask.metadata_subdir("gpg", :now, true)
sig_path = meta_dir.join("signature.asc") sig_path = meta_dir.join("signature.asc")
curl(cask.gpg.signature, "-o", sig_path.to_s) unless cached || force curl_download cask.gpg.signature, to: sig_path unless cached || force
sig_path sig_path
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

@ -1,4 +1,4 @@
#: * `deps` [`--1`] [`-n`] [`--union`] [`--full-name`] [`--installed`] [`--include-build`] [`--include-optional`] [`--skip-recommended`] <formulae>: #: * `deps` [`--1`] [`-n`] [`--union`] [`--full-name`] [`--installed`] [`--include-build`] [`--include-optional`] [`--skip-recommended`] [`--include-requirements`] <formulae>:
#: Show dependencies for <formulae>. When given multiple formula arguments, #: Show dependencies for <formulae>. When given multiple formula arguments,
#: show the intersection of dependencies for <formulae>. #: show the intersection of dependencies for <formulae>.
#: #:
@ -19,15 +19,22 @@
#: <formulae>. To include the `:build` type dependencies, pass `--include-build`. #: <formulae>. To include the `:build` type dependencies, pass `--include-build`.
#: Similarly, pass `--include-optional` to include `:optional` dependencies. #: Similarly, pass `--include-optional` to include `:optional` dependencies.
#: To skip `:recommended` type dependencies, pass `--skip-recommended`. #: To skip `:recommended` type dependencies, pass `--skip-recommended`.
#: To include requirements in addition to dependencies, pass `--include-requirements`.
#: #:
#: * `deps` `--tree` [<filters>] (<formulae>|`--installed`): #: * `deps` `--tree` [`--1`] [<filters>] [`--annotate`] (<formulae>|`--installed`):
#: Show dependencies as a tree. When given multiple formula arguments, output #: Show dependencies as a tree. When given multiple formula arguments, output
#: individual trees for every formula. #: individual trees for every formula.
#: #:
#: If `--1` is passed, only one level of children is displayed.
#:
#: If `--installed` is passed, output a tree for every installed formula. #: If `--installed` is passed, output a tree for every installed formula.
#: #:
#: The <filters> placeholder is any combination of options `--include-build`, #: The <filters> placeholder is any combination of options `--include-build`,
#: `--include-optional`, and `--skip-recommended` as documented above. #: `--include-optional`, `--skip-recommended`, and `--include-requirements` as
#: documented above.
#:
#: If `--annotate` is passed, the build, optional, and recommended dependencies
#: are marked as such in the output.
#: #:
#: * `deps` [<filters>] (`--installed`|`--all`): #: * `deps` [<filters>] (`--installed`|`--all`):
#: Show dependencies for installed or all available formulae. Every line of #: Show dependencies for installed or all available formulae. Every line of
@ -37,6 +44,10 @@
#: The <filters> placeholder is any combination of options `--include-build`, #: The <filters> placeholder is any combination of options `--include-build`,
#: `--include-optional`, and `--skip-recommended` as documented above. #: `--include-optional`, and `--skip-recommended` as documented above.
# The undocumented `--for-each` option will switch into the mode used by `deps --all`,
# but only list dependencies for specified formula, one specified formula per line.
# This is used for debugging the `--installed`/`--all` display mode.
# encoding: UTF-8 # encoding: UTF-8
require "formula" require "formula"
@ -52,20 +63,26 @@ module Homebrew
all?: ARGV.include?("--all"), all?: ARGV.include?("--all"),
topo_order?: ARGV.include?("-n"), topo_order?: ARGV.include?("-n"),
union?: ARGV.include?("--union"), union?: ARGV.include?("--union"),
for_each?: ARGV.include?("--for-each"),
) )
if mode.installed? && mode.tree? if mode.tree?
puts_deps_tree Formula.installed if mode.installed?
puts_deps_tree Formula.installed, !ARGV.one?
else
raise FormulaUnspecifiedError if ARGV.named.empty?
puts_deps_tree ARGV.formulae, !ARGV.one?
end
elsif mode.all? elsif mode.all?
puts_deps Formula puts_deps Formula
elsif mode.tree?
raise FormulaUnspecifiedError if ARGV.named.empty?
puts_deps_tree ARGV.formulae
elsif ARGV.named.empty? elsif ARGV.named.empty?
raise FormulaUnspecifiedError unless mode.installed? raise FormulaUnspecifiedError unless mode.installed?
puts_deps Formula.installed puts_deps Formula.installed
elsif mode.for_each?
puts_deps ARGV.formulae
else else
all_deps = deps_for_formulae(ARGV.formulae, !ARGV.one?, &(mode.union? ? :| : :&)) all_deps = deps_for_formulae(ARGV.formulae, !ARGV.one?, &(mode.union? ? :| : :&))
all_deps = condense_requirements(all_deps)
all_deps = all_deps.select(&:installed?) if mode.installed? all_deps = all_deps.select(&:installed?) if mode.installed?
all_deps = all_deps.map(&method(:dep_display_name)).uniq all_deps = all_deps.map(&method(:dep_display_name)).uniq
all_deps.sort! unless mode.topo_order? all_deps.sort! unless mode.topo_order?
@ -73,24 +90,59 @@ module Homebrew
end end
end end
def dep_display_name(d) def condense_requirements(deps)
ARGV.include?("--full-name") ? d.to_formula.full_name : d.name if ARGV.include?("--include-requirements")
deps
else
deps.map do |dep|
if dep.is_a? Dependency
dep
elsif dep.default_formula?
dep.to_dependency
end
end.compact
end
end
def dep_display_name(dep)
str = if dep.is_a? Requirement
if ARGV.include?("--include-requirements")
if dep.default_formula?
":#{dep.display_s} (#{dep_display_name(dep.to_dependency)})"
else
":#{dep.display_s}"
end
elsif dep.default_formula?
dep_display_name(dep.to_dependency)
else
# This shouldn't happen, but we'll put something here to help debugging
"::#{dep.name}"
end
else
ARGV.include?("--full-name") ? dep.to_formula.full_name : dep.name
end
if ARGV.include?("--annotate")
str = "#{str} [build]" if dep.build?
str = "#{str} [optional" if dep.optional?
str = "#{str} [recommended]" if dep.recommended?
end
str
end end
def deps_for_formula(f, recursive = false) def deps_for_formula(f, recursive = false)
includes = [] includes = []
ignores = [] ignores = []
if ARGV.include? "--include-build" if ARGV.include?("--include-build")
includes << "build?" includes << "build?"
else else
ignores << "build?" ignores << "build?"
end end
if ARGV.include? "--include-optional" if ARGV.include?("--include-optional")
includes << "optional?" includes << "optional?"
else else
ignores << "optional?" ignores << "optional?"
end end
ignores << "recommended?" if ARGV.include? "--skip-recommended" ignores << "recommended?" if ARGV.include?("--skip-recommended")
if recursive if recursive
deps = f.recursive_dependencies do |dependent, dep| deps = f.recursive_dependencies do |dependent, dep|
@ -120,7 +172,7 @@ module Homebrew
end end
end end
deps + reqs.select(&:default_formula?).map(&:to_dependency) deps + reqs.to_a
end end
def deps_for_formulae(formulae, recursive = false, &block) def deps_for_formulae(formulae, recursive = false, &block)
@ -129,41 +181,55 @@ module Homebrew
def puts_deps(formulae) def puts_deps(formulae)
formulae.each do |f| formulae.each do |f|
deps = deps_for_formula(f).sort_by(&:name).map(&method(:dep_display_name)) deps = deps_for_formula(f)
deps = condense_requirements(deps)
deps = deps.sort_by(&:name).map(&method(:dep_display_name))
puts "#{f.full_name}: #{deps.join(" ")}" puts "#{f.full_name}: #{deps.join(" ")}"
end end
end end
def puts_deps_tree(formulae) def puts_deps_tree(formulae, recursive = false)
formulae.each do |f| formulae.each do |f|
puts "#{f.full_name} (required dependencies)" puts f.full_name
recursive_deps_tree(f, "") @dep_stack = []
recursive_deps_tree(f, "", recursive)
puts puts
end end
end end
def recursive_deps_tree(f, prefix) def recursive_deps_tree(f, prefix, recursive)
reqs = f.requirements.select(&:default_formula?) reqs = f.requirements
deps = f.deps.default deps = f.deps
max = reqs.length - 1 dependables = reqs + deps
reqs.each_with_index do |req, i| dependables = dependables.reject(&:optional?) unless ARGV.include?("--include-optional")
chr = if i == max && deps.empty? dependables = dependables.reject(&:build?) unless ARGV.include?("--include-build")
dependables = dependables.reject(&:recommended?) if ARGV.include?("--skip-recommended")
max = dependables.length - 1
@dep_stack.push f.name
dependables.each_with_index do |dep, i|
next if !ARGV.include?("--include-requirements") && dep.is_a?(Requirement) && !dep.default_formula?
tree_lines = if i == max
"└──" "└──"
else else
"├──" "├──"
end end
puts prefix + "#{chr} :#{dep_display_name(req.to_dependency)}" display_s = "#{tree_lines} #{dep_display_name(dep)}"
end is_circular = @dep_stack.include?(dep.name)
max = deps.length - 1 display_s = "#{display_s} (CIRCULAR DEPENDENCY)" if is_circular
deps.each_with_index do |dep, i| puts "#{prefix}#{display_s}"
chr = if i == max next if !recursive || is_circular
"└──" prefix_addition = if i == max
" "
else else
"├──" ""
end
if dep.is_a?(Requirement) && dep.default_formula?
recursive_deps_tree(Formulary.factory(dep.to_dependency.name), prefix + prefix_addition, true)
end
if dep.is_a? Dependency
recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true)
end end
prefix_ext = (i == max) ? " " : ""
puts prefix + "#{chr} #{dep_display_name(dep)}"
recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_ext)
end end
@dep_stack.pop
end end
end end

View File

@ -49,8 +49,10 @@ module Homebrew
if fetch_bottle?(f) if fetch_bottle?(f)
begin begin
fetch_formula(f.bottle) fetch_formula(f.bottle)
rescue Exception => e rescue Interrupt
raise if ARGV.homebrew_developer? || e.is_a?(Interrupt) raise
rescue => e
raise if ARGV.homebrew_developer?
fetched_bottle = false fetched_bottle = false
onoe e.message onoe e.message
opoo "Bottle fetch failed: fetching the source." opoo "Bottle fetch failed: fetching the source."

View File

@ -219,6 +219,7 @@ module Homebrew
end end
end end
return if formulae.empty?
perform_preinstall_checks perform_preinstall_checks
formulae.each do |f| formulae.each do |f|
@ -338,6 +339,7 @@ module Homebrew
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to install f as part of the dependency tree of # We already attempted to install f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on. # another formula. In that case, don't generate an error, just move on.
return
rescue CannotInstallFormulaError => e rescue CannotInstallFormulaError => e
ofail e.message ofail e.message
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

@ -1,6 +1,7 @@
#: * `pin` <formulae>: #: * `pin` <formulae>:
#: Pin the specified <formulae>, preventing them from being upgraded when #: Pin the specified <formulae>, preventing them from being upgraded when
#: issuing the `brew upgrade` command. See also `unpin`. #: issuing the `brew upgrade <formulae>` command (but can still be upgraded
#: as dependencies for other formulae). See also `unpin`.
require "formula" require "formula"

View File

@ -29,8 +29,6 @@ module Homebrew
args << "--devel" args << "--devel"
end end
Sandbox.print_sandbox_message if Sandbox.formula?(formula)
Utils.safe_fork do Utils.safe_fork do
if Sandbox.formula?(formula) if Sandbox.formula?(formula)
sandbox = Sandbox.new sandbox = Sandbox.new

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

@ -47,8 +47,8 @@ module Homebrew
fi.install fi.install
fi.finish fi.finish
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
# next return
rescue Exception rescue Exception # rubocop:disable Lint/RescueException
ignore_interrupts { restore_backup(keg, keg_was_linked) } ignore_interrupts { restore_backup(keg, keg_was_linked) }
raise raise
else else

View File

@ -34,38 +34,40 @@ module Homebrew
elsif ARGV.include? "--opensuse" elsif ARGV.include? "--opensuse"
exec_browser "https://software.opensuse.org/search?q=#{ARGV.next}" exec_browser "https://software.opensuse.org/search?q=#{ARGV.next}"
elsif ARGV.include? "--fedora" elsif ARGV.include? "--fedora"
exec_browser "https://admin.fedoraproject.org/pkgdb/packages/%2A#{ARGV.next}%2A/" exec_browser "https://apps.fedoraproject.org/packages/s/#{ARGV.next}"
elsif ARGV.include? "--ubuntu" elsif ARGV.include? "--ubuntu"
exec_browser "http://packages.ubuntu.com/search?keywords=#{ARGV.next}&searchon=names&suite=all&section=all" exec_browser "https://packages.ubuntu.com/search?keywords=#{ARGV.next}&searchon=names&suite=all&section=all"
elsif ARGV.include? "--desc" elsif ARGV.include? "--desc"
query = ARGV.next query = ARGV.next
regex = query_regexp(query) regex = query_regexp(query)
Descriptions.search(regex, :desc).print Descriptions.search(regex, :desc).print
elsif ARGV.first =~ HOMEBREW_TAP_FORMULA_REGEX elsif ARGV.first =~ HOMEBREW_TAP_FORMULA_REGEX
query = ARGV.first query = ARGV.first
user, repo, name = query.split("/", 3)
begin begin
result = Formulary.factory(query).name result = Formulary.factory(query).name
results = Array(result)
rescue FormulaUnavailableError rescue FormulaUnavailableError
result = search_tap(user, repo, name) _, _, name = query.split("/", 3)
results = search_taps(name)
end end
results = Array(result)
puts Formatter.columns(results) unless results.empty? puts Formatter.columns(results) unless results.empty?
else else
query = ARGV.first query = ARGV.first
regex = query_regexp(query) regex = query_regexp(query)
local_results = search_formulae(regex) local_results = search_formulae(regex)
puts Formatter.columns(local_results) unless local_results.empty? puts Formatter.columns(local_results) unless local_results.empty?
tap_results = search_taps(query) tap_results = search_taps(query)
puts Formatter.columns(tap_results) unless tap_results.empty? puts Formatter.columns(tap_results) unless tap_results.empty?
if $stdout.tty? if $stdout.tty?
count = local_results.length + tap_results.length count = local_results.length + tap_results.length
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
@ -100,10 +102,18 @@ module Homebrew
odie "#{query} is not a valid regex" odie "#{query} is not a valid regex"
end end
def search_taps(query) def search_taps(query, silent: false)
return [] if ENV["HOMEBREW_NO_GITHUB_API"]
# Use stderr to avoid breaking parsed output
unless silent
$stderr.puts Formatter.headline("Searching taps on GitHub...", color: :blue)
end
valid_dirnames = ["Formula", "HomebrewFormula", "Casks", "."].freeze valid_dirnames = ["Formula", "HomebrewFormula", "Casks", "."].freeze
matches = GitHub.search_code("user:Homebrew", "user:caskroom", "filename:#{query}", "extension:rb") matches = GitHub.search_code(user: ["Homebrew", "caskroom"], filename: query, extension: "rb")
[*matches].map do |match|
matches.map do |match|
dirname, filename = File.split(match["path"]) dirname, filename = File.split(match["path"])
next unless valid_dirnames.include?(dirname) next unless valid_dirnames.include?(dirname)
tap = Tap.fetch(match["repository"]["full_name"]) tap = Tap.fetch(match["repository"]["full_name"])
@ -113,6 +123,9 @@ module Homebrew
end end
def search_formulae(regex) def search_formulae(regex)
# Use stderr to avoid breaking parsed output
$stderr.puts Formatter.headline("Searching local taps...", color: :blue)
aliases = Formula.alias_full_names aliases = Formula.alias_full_names
results = (Formula.full_names + aliases).grep(regex).sort results = (Formula.full_names + aliases).grep(regex).sort

View File

@ -117,12 +117,13 @@ module Homebrew
case output_type case output_type
when :print when :print
args << "--debug" if ARGV.debug?
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

@ -54,8 +54,7 @@ module Homebrew
quiet: ARGV.quieter? quiet: ARGV.quieter?
rescue TapRemoteMismatchError => e rescue TapRemoteMismatchError => e
odie e odie e
rescue TapAlreadyTappedError, TapAlreadyUnshallowError rescue TapAlreadyTappedError, TapAlreadyUnshallowError # rubocop:disable Lint/HandleExceptions
# Do nothing.
end end
end end
end end

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

@ -1,6 +1,6 @@
#: * `unpin` <formulae>: #: * `unpin` <formulae>:
#: Unpin <formulae>, allowing them to be upgraded by `brew upgrade`. See also #: Unpin <formulae>, allowing them to be upgraded by `brew upgrade <formulae>`.
#: `pin`. #: See also `pin`.
require "formula" require "formula"

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.
@ -371,7 +372,7 @@ class Reporter
new_version = formula.pkg_version new_version = formula.pkg_version
old_version = FormulaVersions.new(formula).formula_at_revision(@initial_revision, &:pkg_version) old_version = FormulaVersions.new(formula).formula_at_revision(@initial_revision, &:pkg_version)
next if new_version == old_version next if new_version == old_version
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer? onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer?
end end
@report[:M] << tap.formula_file_to_name(src) @report[:M] << tap.formula_file_to_name(src)
@ -459,7 +460,7 @@ class Reporter
unless Formulary.factory(new_full_name).keg_only? unless Formulary.factory(new_full_name).keg_only?
system HOMEBREW_BREW_FILE, "link", new_full_name, "--overwrite" system HOMEBREW_BREW_FILE, "link", new_full_name, "--overwrite"
end end
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer? onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer?
end end
next next
@ -520,7 +521,7 @@ class Reporter
begin begin
f = Formulary.factory(new_full_name) f = Formulary.factory(new_full_name)
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer? onoe "#{e.message}\n#{e.backtrace.join "\n"}" if ARGV.homebrew_developer?
next next
end end

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
@ -564,6 +570,7 @@ EOS
-d "$HOMEBREW_LIBRARY/LinkedKegs" || -d "$HOMEBREW_LIBRARY/LinkedKegs" ||
(-n "$HOMEBREW_DEVELOPER" && -z "$HOMEBREW_UPDATE_PREINSTALL") ]] (-n "$HOMEBREW_DEVELOPER" && -z "$HOMEBREW_UPDATE_PREINSTALL") ]]
then then
unset HOMEBREW_RUBY_PATH
brew update-report "$@" brew update-report "$@"
return $? return $?
elif [[ -z "$HOMEBREW_UPDATE_PREINSTALL" ]] elif [[ -z "$HOMEBREW_UPDATE_PREINSTALL" ]]

View File

@ -150,6 +150,7 @@ module Homebrew
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of # We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on. # another formula. In that case, don't generate an error, just move on.
return
rescue CannotInstallFormulaError => e rescue CannotInstallFormulaError => e
ofail e ofail e
rescue BuildError => e rescue BuildError => e

View File

@ -74,12 +74,13 @@ module Homebrew
end end
end end
dep_formulae = deps.map do |dep| dep_formulae = deps.flat_map do |dep|
begin begin
dep.to_formula dep.to_formula
rescue rescue
[]
end end
end.compact end
reqs_by_formula = ([f] + dep_formulae).flat_map do |formula| reqs_by_formula = ([f] + dep_formulae).flat_map do |formula|
formula.requirements.map { |req| [formula, req] } formula.requirements.map { |req| [formula, req] }
@ -118,6 +119,7 @@ module Homebrew
rescue FormulaUnavailableError rescue FormulaUnavailableError
# Silently ignore this case as we don't care about things used in # Silently ignore this case as we don't care about things used in
# taps that aren't currently tapped. # taps that aren't currently tapped.
next
end end
end end
end end

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

@ -9,7 +9,7 @@ module Debrew
module Raise module Raise
def raise(*) def raise(*)
super super
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
e.extend(Ignorable) e.extend(Ignorable)
super(e) unless Debrew.debug(e) == :ignore super(e) unless Debrew.debug(e) == :ignore
end end
@ -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) }
@ -92,7 +92,7 @@ module Debrew
yield yield
rescue SystemExit rescue SystemExit
original_raise original_raise
rescue Exception => e rescue Exception => e # rubocop:disable Lint/RescueException
debug(e) debug(e)
ensure ensure
@active = false @active = false
@ -119,7 +119,7 @@ module Debrew
if e.is_a?(Ignorable) if e.is_a?(Ignorable)
menu.choice(:irb) do menu.choice(:irb) do
puts "When you exit this IRB session, execution will continue." puts "When you exit this IRB session, execution will continue."
set_trace_func proc { |event, _, _, id, binding, klass| set_trace_func proc { |event, _, _, id, binding, klass| # rubocop:disable Metrics/ParameterLists
if klass == Raise && id == :raise && event == "return" if klass == Raise && id == :raise && event == "return"
set_trace_func(nil) set_trace_func(nil)
synchronize { IRB.start_within(binding) } synchronize { IRB.start_within(binding) }

View File

@ -4,8 +4,7 @@ module IRB
@setup_done = false @setup_done = false
extend Module.new { extend Module.new {
def parse_opts def parse_opts; end
end
def start_within(binding) def start_within(binding)
unless @setup_done unless @setup_done
@ -16,7 +15,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

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