diff options
| author | Ben Muschol | 2017-09-27 16:36:10 -0400 | 
|---|---|---|
| committer | GitHub | 2017-09-27 16:36:10 -0400 | 
| commit | 2d6bd0400785b5fb061f793f70736f12e6ec8231 (patch) | |
| tree | fd0abc8937fc85fb49714ad9dc4a3735fa58242f /Library | |
| parent | fe5c885da0b8b69a5de74647e4181b137acb835f (diff) | |
| parent | cb139ca381cb7f00a4dfdc21482515f103aed417 (diff) | |
| download | brew-2d6bd0400785b5fb061f793f70736f12e6ec8231.tar.bz2 | |
Merge branch 'master' into check-for-master-no-refactor
Diffstat (limited to 'Library')
174 files changed, 2226 insertions, 1125 deletions
diff --git a/Library/.rubocop.yml b/Library/.rubocop.yml index cb065a1a4..dd6e039b0 100644 --- a/Library/.rubocop.yml +++ b/Library/.rubocop.yml @@ -1,5 +1,5 @@  AllCops: -  TargetRubyVersion: 2.0 +  TargetRubyVersion: 2.3    Exclude:      - '**/Casks/**/*'      - '**/vendor/**/*' @@ -119,7 +119,7 @@ Style/Encoding:    Enabled: true  # dashes in filenames are typical -Style/FileName: +Naming/FileName:    Regex: !ruby/regexp /^[\w\@\-\+\.]+(\.rb)?$/  # falsely flags e.g. curl formatting arguments as format strings @@ -189,8 +189,25 @@ Style/TrailingCommaInArguments:    EnforcedStyleForMultiline: comma  # we have too many variables like sha256 where this harms readability -Style/VariableNumber: +Naming/VariableNumber:    Enabled: false  Style/WordArray:    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 diff --git a/Library/Homebrew/.rubocop.yml b/Library/Homebrew/.rubocop.yml index 26c944529..dc4406527 100644 --- a/Library/Homebrew/.rubocop.yml +++ b/Library/Homebrew/.rubocop.yml @@ -42,12 +42,12 @@ Style/HashSyntax:    EnforcedStyle: ruby19_no_mixed_keys  # we won't change backward compatible method names -Style/MethodName: +Naming/MethodName:    Exclude:      - 'compat/**/*'  # we won't change backward compatible predicate names -Style/PredicateName: +Naming/PredicateName:    Exclude:      - 'compat/**/*'    NameWhitelist: is_32_bit?, is_64_bit? diff --git a/Library/Homebrew/.rubocop_todo.yml b/Library/Homebrew/.rubocop_todo.yml index 37518a5f0..96c2f3676 100644 --- a/Library/Homebrew/.rubocop_todo.yml +++ b/Library/Homebrew/.rubocop_todo.yml @@ -81,7 +81,7 @@ Security/MarshalLoad:      - 'utils/fork.rb'  # Offense count: 1 -Style/AccessorMethodName: +Naming/AccessorMethodName:    Exclude:      - 'extend/ENV/super.rb' @@ -136,10 +136,3 @@ Style/MutableConstant:      - 'formulary.rb'      - 'tab.rb'      - 'tap.rb' - -# Offense count: 8 -Style/OpMethod: -  Exclude: -    - 'dependencies.rb' -    - 'install_renamed.rb' -    - 'options.rb' diff --git a/Library/Homebrew/brew.rb b/Library/Homebrew/brew.rb index ec86bd794..2906fd93d 100644 --- a/Library/Homebrew/brew.rb +++ b/Library/Homebrew/brew.rb @@ -105,18 +105,16 @@ begin      possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) }      possible_tap = Tap.fetch(possible_tap.first) if possible_tap -    if possible_tap && !possible_tap.installed? -      brew_uid = HOMEBREW_BREW_FILE.stat.uid -      tap_commands = [] -      if Process.uid.zero? && !brew_uid.zero? -        tap_commands += %W[/usr/bin/sudo -u ##{brew_uid}] -      end -      tap_commands += %W[#{HOMEBREW_BREW_FILE} tap #{possible_tap}] -      safe_system(*tap_commands) -      exec HOMEBREW_BREW_FILE, cmd, *ARGV -    else -      odie "Unknown command: #{cmd}" +    odie "Unknown command: #{cmd}" if !possible_tap || possible_tap.installed? + +    brew_uid = HOMEBREW_BREW_FILE.stat.uid +    tap_commands = [] +    if Process.uid.zero? && !brew_uid.zero? +      tap_commands += %W[/usr/bin/sudo -u ##{brew_uid}]      end +    tap_commands += %W[#{HOMEBREW_BREW_FILE} tap #{possible_tap}] +    safe_system(*tap_commands) +    exec HOMEBREW_BREW_FILE, cmd, *ARGV    end  rescue UsageError => e    require "cmd/help" diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index c40ce8bf7..b2859c927 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -23,7 +23,7 @@ HOMEBREW_VERSION="$(git -C "$HOMEBREW_REPOSITORY" describe --tags --dirty --abbr  HOMEBREW_USER_AGENT_VERSION="$HOMEBREW_VERSION"  if [[ -z "$HOMEBREW_VERSION" ]]  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"  fi @@ -105,7 +105,14 @@ then    HOMEBREW_OS_USER_AGENT_VERSION="Mac OS X $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" ]]    then      HOMEBREW_CURL="$HOMEBREW_PREFIX/opt/curl/bin/curl" diff --git a/Library/Homebrew/build.rb b/Library/Homebrew/build.rb index 8dd4fb245..cd2fa4c02 100644 --- a/Library/Homebrew/build.rb +++ b/Library/Homebrew/build.rb @@ -112,6 +112,10 @@ class Build        formula.extend(Debrew::Formula) if ARGV.debug?        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?          formula.patch diff --git a/Library/Homebrew/cask/lib/hbc/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact.rb index 074d15017..cb15ec051 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact.rb @@ -33,7 +33,7 @@ module Hbc      # We want to extract nested containers before we      # handle any other artifacts.      # -    TYPES = [ +    CLASSES = [        PreflightBlock,        Uninstall,        NestedContainer, @@ -60,12 +60,9 @@ module Hbc        Zap,      ].freeze -    def self.for_cask(cask, options = {}) +    def self.for_cask(cask)        odebug "Determining which artifacts are present in Cask #{cask}" - -      TYPES -        .select { |klass| klass.me?(cask) } -        .map { |klass| klass.new(cask, options) } +      CLASSES.flat_map { |klass| klass.for_cask(cask) }      end    end  end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/base.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_artifact.rb index ae15552a4..1b18cc6e7 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_artifact.rb @@ -1,34 +1,28 @@  module Hbc    module Artifact -    class Base +    class AbstractArtifact        extend Predicable -      def self.artifact_name -        @artifact_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase +      def self.english_name +        @english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')        end -      def self.artifact_english_name -        @artifact_english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2') +      def self.english_article +        @english_article ||= (english_name =~ /^[aeiou]/i) ? "an" : "a"        end -      def self.artifact_english_article -        @artifact_english_article ||= (artifact_english_name =~ /^[aeiou]/i) ? "an" : "a" +      def self.dsl_key +        @dsl_key ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym        end -      def self.artifact_dsl_key -        @artifact_dsl_key ||= artifact_name.to_sym +      def self.dirmethod +        @dirmethod ||= "#{dsl_key}dir".to_sym        end -      def self.artifact_dirmethod -        @artifact_dirmethod ||= "#{artifact_name}dir".to_sym +      def self.for_cask(cask) +        cask.artifacts[dsl_key].to_a        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) @@ -63,17 +57,14 @@ module Hbc          [executable, arguments]        end -      def summary -        {} -      end +      attr_reader :cask -      attr_predicate :force?, :verbose? - -      def initialize(cask, command: SystemCommand, force: false, verbose: false) +      def initialize(cask)          @cask = cask -        @command = command -        @force = force -        @verbose = verbose +      end + +      def to_s +        "#{summarize} (#{self.class.english_name})"        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb index be3050acb..4e8edbc11 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb @@ -1,39 +1,51 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  module Hbc    module Artifact -    class AbstractFlightBlock < Base -      def self.artifact_dsl_key +    class AbstractFlightBlock < AbstractArtifact +      def self.dsl_key          super.to_s.sub(/_block$/, "").to_sym        end -      def self.uninstall_artifact_dsl_key -        artifact_dsl_key.to_s.prepend("uninstall_").to_sym +      def self.uninstall_dsl_key +        dsl_key.to_s.prepend("uninstall_").to_sym        end -      def self.class_for_dsl_key(dsl_key) -        Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") +      def self.for_cask(cask) +        [dsl_key, uninstall_dsl_key].flat_map do |key| +          [*cask.artifacts[key]].map { |block| new(cask, key => block) } +        end        end -      def self.me?(cask) -        cask.artifacts[artifact_dsl_key].any? || -          cask.artifacts[uninstall_artifact_dsl_key].any? +      attr_reader :directives + +      def initialize(cask, **directives) +        super(cask) +        @directives = directives        end -      def install_phase -        abstract_phase(self.class.artifact_dsl_key) +      def install_phase(**) +        abstract_phase(self.class.dsl_key)        end -      def uninstall_phase -        abstract_phase(self.class.uninstall_artifact_dsl_key) +      def uninstall_phase(**) +        abstract_phase(self.class.uninstall_dsl_key)        end        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) -        @cask.artifacts[dsl_key].each do |block| -          self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) -        end +        return if (block = directives[dsl_key]).nil? +        class_for_dsl_key(dsl_key).new(cask).instance_eval(&block) +      end + +      def summarize +        directives.keys.map(&:to_s).join(", ")        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_uninstall.rb index d92644150..badd549ce 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_uninstall.rb @@ -1,11 +1,11 @@  require "pathname"  require "timeout" -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  module Hbc    module Artifact -    class UninstallBase < Base +    class AbstractUninstall < AbstractArtifact        ORDERED_DIRECTIVES = [          :early_script,          :launchctl, @@ -20,26 +20,42 @@ module Hbc          :rmdir,        ].freeze -      def dispatch_uninstall_directives -        directives_set = @cask.artifacts[stanza] +      def self.from_args(cask, **directives) +        new(cask, directives) +      end + +      attr_reader :directives + +      def initialize(cask, directives) +        super(cask) +        directives[:signal] = [*directives[:signal]].flatten.each_slice(2).to_a +        @directives = directives +      end + +      def to_h +        directives.to_h +      end + +      def summarize +        to_h.map { |key, val| [*val].map { |v| "#{key.inspect} => #{v.inspect}" }.join(", ") }.join(", ") +      end + +      private + +      def dispatch_uninstall_directives(**options)          ohai "Running #{stanza} process for #{@cask}; your password may be necessary" -        directives_set.each do |directives| -          warn_for_unknown_directives(directives) -        end +        warn_for_unknown_directives(directives)          ORDERED_DIRECTIVES.each do |directive_sym| -          directives_set.select { |h| h.key?(directive_sym) }.each do |directives| -            args = directives[directive_sym] -            send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args)) -          end +          next unless directives.key?(directive_sym) +          args = directives[directive_sym] +          send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options)          end        end -      private -        def stanza -        self.class.artifact_dsl_key +        self.class.dsl_key        end        def warn_for_unknown_directives(directives) @@ -51,18 +67,18 @@ module Hbc        # Preserve prior functionality of script which runs first. Should rarely be needed.        # :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. -      def uninstall_early_script(directives) -        uninstall_script(directives, directive_name: :early_script) +      def uninstall_early_script(directives, **options) +        uninstall_script(directives, directive_name: :early_script, **options)        end        # :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|            ohai "Removing launchctl service #{service}"            [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 =~ /^\{/ -              @command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) +              command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)                sleep 1              end              paths = ["/Library/LaunchAgents/#{service}.plist", @@ -70,38 +86,38 @@ module Hbc              paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo              paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)              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              # undocumented and untested: pass a path to uninstall :launchctl              next unless Pathname(service).exist? -            @command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) -            @command.run!("/bin/rm",        args: ["-f", "--", 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)              sleep 1            end          end        end -      def running_processes(bundle_id) -        @command.run!("/bin/launchctl", args: ["list"]).stdout.lines -                .map { |line| line.chomp.split("\t") } -                .map { |pid, state, id| [pid.to_i, state.to_i, id] } -                .select do |fields| -                  next if fields[0].zero? -                  fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ -                end +      def running_processes(bundle_id, command: nil) +        command.run!("/bin/launchctl", args: ["list"]).stdout.lines +               .map { |line| line.chomp.split("\t") } +               .map { |pid, state, id| [pid.to_i, state.to_i, id] } +               .select do |fields| +                 next if fields[0].zero? +                 fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ +               end        end        # :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|            ohai "Quitting application ID #{bundle_id}" -          next if running_processes(bundle_id).empty? -          @command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true) +          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)            begin              Timeout.timeout(3) do                Kernel.loop do -                break if running_processes(bundle_id).empty? +                break if running_processes(bundle_id, command: command).empty?                end              end            rescue Timeout::Error @@ -111,15 +127,15 @@ module Hbc        end        # :signal should come after :quit so it can be used as a backup when :quit fails -      def uninstall_signal(*signals) -        signals.flatten.each_slice(2) do |pair| +      def uninstall_signal(*signals, command: nil, **_) +        signals.each do |pair|            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            signal, bundle_id = pair            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?            # 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 @@ -133,10 +149,10 @@ module Hbc          end        end -      def uninstall_login_item(*login_items) +      def uninstall_login_item(*login_items, command: nil, **_)          login_items.each do |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}")],                          sudo: false)            sleep 1 @@ -144,23 +160,24 @@ module Hbc        end        # :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|            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 -            @command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) +            command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true)              sleep 1            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}" -            @command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true) +            command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true)            end          end        end        # :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,                                                                          "uninstall",                                                                          { must_succeed: true, sudo: false }, @@ -168,25 +185,25 @@ module Hbc                                                                          directive_name)          ohai "Running uninstall script #{executable}" -        raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? -        executable_path = @cask.staged_path.join(executable) +        raise CaskInvalidError.new(cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? +        executable_path = cask.staged_path.join(executable)          unless executable_path.exist?            message = "uninstall script #{executable} does not exist" -          raise CaskError, "#{message}." unless force? +          raise CaskError, "#{message}." unless force            opoo "#{message}, skipping."            return          end -        @command.run("/bin/chmod", args: ["--", "+x", executable_path]) -        @command.run(executable_path, script_arguments) +        command.run("/bin/chmod", args: ["--", "+x", executable_path]) +        command.run(executable_path, script_arguments)          sleep 1        end -      def uninstall_pkgutil(*pkgs) +      def uninstall_pkgutil(*pkgs, command: nil, **_)          ohai "Uninstalling packages:"          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              pkg.uninstall            end @@ -215,28 +232,28 @@ module Hbc          end        end -      def uninstall_delete(*paths) +      def uninstall_delete(*paths, command: nil, **_)          return if paths.empty?          ohai "Removing files:"          each_resolved_path(:delete, paths) do |path, resolved_paths|            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 -      def uninstall_trash(*paths) +      def uninstall_trash(*paths, **options)          return if paths.empty?          resolved_paths = each_resolved_path(:trash, paths).to_a          ohai "Trashing files:"          puts resolved_paths.map(&:first) -        trash_paths(*resolved_paths.flat_map(&:last)) +        trash_paths(*resolved_paths.flat_map(&:last), **options)        end -      def trash_paths(*paths) -        @command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths]) +      def trash_paths(*paths, command: nil, **_) +        command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths])            on run argv              repeat with i from 1 to (count argv)                set item i of argv to (item i of argv as POSIX file) @@ -260,7 +277,7 @@ module Hbc          EOS        end -      def uninstall_rmdir(*directories) +      def uninstall_rmdir(*directories, command: nil, **_)          return if directories.empty?          ohai "Removing directories if empty:" @@ -268,10 +285,10 @@ module Hbc            puts path            resolved_paths.select(&:directory?).each do |resolved_path|              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 -            @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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb index b42db877d..0f37afade 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb @@ -5,21 +5,32 @@ require "hbc/utils/hash_validator"  module Hbc    module Artifact      class Artifact < Moved -      def self.artifact_english_name +      def self.english_name          "Generic Artifact"        end -      def self.artifact_dirmethod -        :appdir -      end +      def self.from_args(cask, *args) +        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 -      def load_specification(artifact_spec) -        source_string, target_hash = artifact_spec -        raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? -        @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)          target_hash.extend(HashValidator).assert_valid_keys(:target) -        @target = Pathname.new(target_hash[:target]) + +        new(cask, source_string, **target_hash) +      end + +      def self.resolve_target(target) +        Pathname(target) +      end + +      def initialize(cask, source, target: nil) +        super(cask, source, target: target)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb index 7178c2af6..68f4b074d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb @@ -3,13 +3,13 @@ require "hbc/artifact/symlinked"  module Hbc    module Artifact      class Binary < Symlinked -      def link -        super +      def link(command: nil, **options) +        super(command: command, **options)          return if source.executable?          if source.writable?            FileUtils.chmod "+x", source          else -          @command.run!("/bin/chmod", args: ["+x", source], sudo: true) +          command.run!("/bin/chmod", args: ["+x", source], sudo: true)          end        end      end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb index be857696e..588bcabd5 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb @@ -1,29 +1,82 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  module Hbc    module Artifact -    class Installer < Base -      def install_phase -        @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| -          if artifact.manual -            puts <<-EOS.undent -              To complete the installation of Cask #{@cask}, you must also -              run the installer at - -                '#{@cask.staged_path.join(artifact.manual)}' - -            EOS -          else -            executable, script_arguments = self.class.read_script_arguments(artifact.script, -                                                                            self.class.artifact_dsl_key.to_s, -                                                                            { must_succeed: true, sudo: false }, -                                                                            print_stdout: true) -            ohai "Running #{self.class.artifact_dsl_key} script #{executable}" -            raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil? -            executable_path = @cask.staged_path.join(executable) -            @command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path) -            @command.run(executable_path, script_arguments) +    class Installer < AbstractArtifact +      VALID_KEYS = Set.new [ +        :manual, +        :script, +      ] + +      module ManualInstaller +        def install_phase(**) +          puts <<-EOS.undent +            To complete the installation of Cask #{cask}, you must also +            run the installer at + +              '#{path}' +          EOS +        end +      end + +      module ScriptInstaller +        def install_phase(command: nil, **_) +          ohai "Running #{self.class.dsl_key} script '#{path.relative_path_from(cask.staged_path)}'" +          FileUtils.chmod "+x", path unless path.executable? +          command.run(path, **args) +        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 + +          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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb index 3fe969c0c..ba1c8e907 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb @@ -4,63 +4,61 @@ module Hbc    module Artifact      class Moved < Relocated        def self.english_description -        "#{artifact_english_name}s" +        "#{english_name}s"        end -      def install_phase -        each_artifact(&method(:move)) +      def install_phase(**options) +        move(**options)        end -      def uninstall_phase -        each_artifact(&method(:delete)) +      def uninstall_phase(**options) +        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        private -      def move +      def move(force: false, command: nil, **options)          if Utils.path_occupied?(target) -          message = "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'" -          raise CaskError, "#{message}." unless force? +          message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'" +          raise CaskError, "#{message}." unless force            opoo "#{message}; overwriting." -          delete +          delete(force: force, command: command, **options)          end          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 -        ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'." +        ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'."          target.dirname.mkpath          if target.parent.writable?            FileUtils.move(source, target)          else -          SystemCommand.run("/bin/mv", args: [source, target], sudo: true) +          command.run("/bin/mv", args: [source, target], sudo: true)          end -        add_altname_metadata target, source.basename.to_s +        add_altname_metadata(target, source.basename, command: command)        end -      def delete -        ohai "Removing #{self.class.artifact_english_name} '#{target}'." -        raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}." if MacOS.undeletable?(target) +      def delete(force: false, command: nil, **_) +        ohai "Removing #{self.class.english_name} '#{target}'." +        raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)          return unless Utils.path_occupied?(target)          if target.parent.writable? && !force            target.rmtree          else -          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}") +          Utils.gain_permissions_remove(target, command: command)          end        end      end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb index 84253ea30..81adf9029 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb @@ -1,23 +1,31 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  module Hbc    module Artifact -    class NestedContainer < Base -      def install_phase -        @cask.artifacts[:nested_container].each { |container| extract(container) } +    class NestedContainer < AbstractArtifact +      attr_reader :path + +      def initialize(cask, path) +        super(cask) +        @path = cask.staged_path.join(path) +      end + +      def install_phase(**options) +        extract(**options)        end -      def extract(container_relative_path) -        source = @cask.staged_path.join(container_relative_path) -        container = Container.for_path(source, @command) +      private + +      def extract(command: nil, verbose: nil, **_) +        container = Container.for_path(path, command)          unless container            raise CaskError, "Aw dang, could not identify nested container at '#{source}'"          end -        ohai "Extracting nested container #{source.basename}" -        container.new(@cask, source, @command, verbose: verbose?).extract -        FileUtils.remove_entry_secure(source) +        ohai "Extracting nested container #{path.relative_path_from(cask.staged_path)}" +        container.new(cask, path, command, verbose: verbose).extract +        FileUtils.remove_entry_secure(path)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb index be0a6be71..0967fd99d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb @@ -1,4 +1,4 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  require "hbc/utils/hash_validator" @@ -6,62 +6,57 @@ require "vendor/plist/plist"  module Hbc    module Artifact -    class Pkg < Base +    class Pkg < AbstractArtifact        attr_reader :pkg_relative_path -      def self.artifact_dsl_key -        :pkg +      def self.from_args(cask, path, **options) +        options.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) +        new(cask, path, **options)        end -      def load_pkg_description(pkg_description) -        @pkg_relative_path = pkg_description.shift -        @pkg_install_opts = pkg_description.shift -        begin -          if @pkg_install_opts.respond_to?(:keys) -            @pkg_install_opts.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) -          elsif @pkg_install_opts -            raise -          end -          raise if pkg_description.nil? -        rescue StandardError -          raise CaskInvalidError.new(@cask, "Bad pkg stanza") -        end +      attr_reader :path, :options + +      def initialize(cask, path, **options) +        super(cask) +        @path = cask.staged_path.join(path) +        @options = options        end -      def pkg_install_opts(opt) -        @pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) +      def summarize +        path.relative_path_from(cask.staged_path).to_s        end -      def install_phase -        @cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } +      def install_phase(**options) +        run_installer(**options)        end -      def run_installer(pkg_description) -        load_pkg_description pkg_description -        ohai "Running installer for #{@cask}; your password may be necessary." +      private + +      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." -        source = @cask.staged_path.join(pkg_relative_path) -        unless source.exist? -          raise CaskError, "pkg source file not found: '#{source}'" +        unless path.exist? +          raise CaskError, "pkg source file not found: '#{path.relative_path_from(cask.staged_path)}'"          end          args = [ -          "-pkg",    source, +          "-pkg",    path,            "-target", "/"          ] -        args << "-verboseR" if verbose? -        args << "-allowUntrusted" if pkg_install_opts :allow_untrusted +        args << "-verboseR" if verbose +        args << "-allowUntrusted" if options.fetch(:allow_untrusted, false)          with_choices_file do |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        def with_choices_file -        return yield nil unless pkg_install_opts(:choices) +        choices = options.fetch(:choices, {}) +        return yield nil if choices.empty?          Tempfile.open(["choices", ".xml"]) do |file|            begin -            file.write Plist::Emit.dump(pkg_install_opts(:choices)) +            file.write Plist::Emit.dump(choices)              file.close              yield file.path            ensure diff --git a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb index a44f8ae3a..87f120934 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb @@ -3,7 +3,7 @@ require "hbc/artifact/moved"  module Hbc    module Artifact      class Prefpane < Moved -      def self.artifact_english_name +      def self.english_name          "Preference Pane"        end      end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb index ee41de2fe..298714d89 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb @@ -3,22 +3,24 @@ require "hbc/artifact/moved"  module Hbc    module Artifact      class Qlplugin < Moved -      def self.artifact_english_name +      def self.english_name          "QuickLook Plugin"        end -      def install_phase -        super -        reload_quicklook +      def install_phase(**options) +        super(**options) +        reload_quicklook(**options)        end -      def uninstall_phase -        super -        reload_quicklook +      def uninstall_phase(**options) +        super(**options) +        reload_quicklook(**options)        end -      def reload_quicklook -        @command.run!("/usr/bin/qlmanage", args: ["-r"]) +      private + +      def reload_quicklook(command: nil, **_) +        command.run!("/usr/bin/qlmanage", args: ["-r"])        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb index 4dba46c9d..540699630 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb @@ -1,65 +1,79 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  require "hbc/utils/hash_validator"  module Hbc    module Artifact -    class Relocated < Base -      def summary -        { -          english_description: self.class.english_description, -          contents:            @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, -        } +    class Relocated < AbstractArtifact +      def self.from_args(cask, *args) +        source_string, target_hash = args + +        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        attr_reader :source, :target -      def printable_target -        target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/") +      def initialize(cask, source, target: nil) +        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 +      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        # Try to make the asset searchable under the target name.  Spotlight        # respects this attribute for many filetypes, but ignores it for App        # bundles. Alfred 2.2 respects it even for App bundles. -      def add_altname_metadata(file, altname) -        return if altname.casecmp(file.basename).zero? +      def add_altname_metadata(file, altname, command: nil) +        return if altname.to_s.casecmp(file.basename.to_s).zero?          odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" -        altnames = @command.run("/usr/bin/xattr", -                                args:         ["-p", ALT_NAME_ATTRIBUTE, file.to_s], +        altnames = command.run("/usr/bin/xattr", +                                args:         ["-p", ALT_NAME_ATTRIBUTE, file],                                  print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1')          odebug "Existing metadata is: '#{altnames}'"          altnames.concat(", ") unless altnames.empty?          altnames.concat(%Q("#{altname}"))          altnames = "(#{altnames})" -        # Some packges are shipped as u=rx (e.g. Bitcoin Core) -        @command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath]) +        # Some packages are shipped as u=rx (e.g. Bitcoin Core) +        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],                        print_stderr: false)        end -      def each_artifact -        @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| -          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 +      def printable_target +        target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/")        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb index 1122c1d02..8c32a52d0 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb @@ -1,10 +1,22 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact"  module Hbc    module Artifact -    class StageOnly < Base -      def self.artifact_dsl_key -        :stage_only +    class StageOnly < AbstractArtifact +      def self.from_args(cask, *args) +        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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb index 35251f70c..59ae58cf1 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb @@ -3,11 +3,11 @@ require "hbc/artifact/moved"  module Hbc    module Artifact      class Suite < Moved -      def self.artifact_english_name +      def self.english_name          "App Suite"        end -      def self.artifact_dirmethod +      def self.dirmethod          :appdir        end      end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb index 16715fe81..3726ebb5c 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb @@ -8,58 +8,56 @@ module Hbc        end        def self.english_description -        "#{artifact_english_name} #{link_type_english_name}s" +        "#{english_name} #{link_type_english_name}s"        end -      def install_phase -        each_artifact(&method(:link)) +      def install_phase(**options) +        link(**options)        end -      def uninstall_phase -        each_artifact(&method(:unlink)) +      def uninstall_phase(**options) +        unlink(**options) +      end + +      def summarize_installed +        if target.symlink? && target.exist? && target.readlink.exist? +          "#{printable_target} -> #{target.readlink} (#{target.readlink.abv})" +        else +          string = if target.symlink? +            "#{printable_target} -> #{target.readlink}" +          else +            printable_target +          end + +          Formatter.error(string, label: "Broken Link") +        end        end        private -      def link +      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.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking." +          raise CaskError, "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'; not linking."          end -        ohai "Linking #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'." -        create_filesystem_link(source, target) +        ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'." +        create_filesystem_link(**options)        end -      def unlink +      def unlink(**)          return unless target.symlink? -        ohai "Unlinking #{self.class.artifact_english_name} '#{target}'." +        ohai "Unlinking #{self.class.english_name} '#{target}'."          target.delete        end -      def create_filesystem_link(source, target) +      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.to_s -      end - -      def summarize_artifact(artifact_spec) -        load_specification artifact_spec - -        if target.symlink? && target.exist? && target.readlink.exist? -          "#{printable_target} -> #{target.readlink} (#{target.readlink.abv})" -        else -          string = if target.symlink? -            "#{printable_target} -> #{target.readlink}" -          else -            printable_target -          end - -          Formatter.error(string, label: "Broken Link") -        end +        command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target]) +        add_altname_metadata(source, target.basename, command: command)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb index 5a3dc098d..2bbf82862 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb @@ -1,10 +1,10 @@ -require "hbc/artifact/uninstall_base" +require "hbc/artifact/abstract_uninstall"  module Hbc    module Artifact -    class Uninstall < UninstallBase -      def uninstall_phase -        dispatch_uninstall_directives +    class Uninstall < AbstractUninstall +      def uninstall_phase(**options) +        dispatch_uninstall_directives(**options)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb index cdfe2531d..31ff54d20 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb @@ -1,10 +1,10 @@ -require "hbc/artifact/uninstall_base" +require "hbc/artifact/abstract_uninstall"  module Hbc    module Artifact -    class Zap < UninstallBase -      def zap_phase -        dispatch_uninstall_directives +    class Zap < AbstractUninstall +      def zap_phase(**options) +        dispatch_uninstall_directives(**options)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/audit.rb b/Library/Homebrew/cask/lib/hbc/audit.rb index b8bb6ab81..03d8cce82 100644 --- a/Library/Homebrew/cask/lib/hbc/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/audit.rb @@ -214,12 +214,10 @@ module Hbc      end      def check_generic_artifacts -      cask.artifacts[:artifact].each do |source, target_hash| -        unless target_hash.is_a?(Hash) && target_hash[:target] -          add_error "target required for generic artifact #{source}" -          next +      cask.artifacts[:artifact].each do |artifact| +        unless artifact.target.absolute? +          add_error "target must be absolute path for #{artifact.class.english_name} #{artifact.source}"          end -        add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute?        end      end diff --git a/Library/Homebrew/cask/lib/hbc/cask.rb b/Library/Homebrew/cask/lib/hbc/cask.rb index 6d89a997c..72a23066f 100644 --- a/Library/Homebrew/cask/lib/hbc/cask.rb +++ b/Library/Homebrew/cask/lib/hbc/cask.rb @@ -17,7 +17,7 @@ module Hbc        @token = token        @sourcefile_path = sourcefile_path        @tap = tap -      @dsl = DSL.new(@token) +      @dsl = DSL.new(self)        return unless block_given?        @dsl.instance_eval(&block)        @dsl.language_eval @@ -41,6 +41,14 @@ module Hbc                            .reverse      end +    def full_name +      if @tap.nil? || @tap == Hbc.default_tap +        token +      else +        "#{@tap}/#{token}" +      end +    end +      def installed?        !versions.empty?      end diff --git a/Library/Homebrew/cask/lib/hbc/cask_loader.rb b/Library/Homebrew/cask/lib/hbc/cask_loader.rb index dd9c61089..8fce9636a 100644 --- a/Library/Homebrew/cask/lib/hbc/cask_loader.rb +++ b/Library/Homebrew/cask/lib/hbc/cask_loader.rb @@ -56,7 +56,7 @@ module Hbc      class FromURILoader < FromPathLoader        def self.can_load?(ref) -        ref.to_s.match?(::URI.regexp) +        ref.to_s.match?(::URI::DEFAULT_PARSER.make_regexp)        end        attr_reader :url @@ -116,6 +116,22 @@ module Hbc        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        def self.can_load?(*)          true @@ -149,6 +165,7 @@ module Hbc      def self.for(ref)        [ +        FromInstanceLoader,          FromURILoader,          FromTapLoader,          FromTapPathLoader, diff --git a/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb b/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb index 77f85301e..001a9623b 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/abstract_command.rb @@ -42,41 +42,32 @@ module Hbc          @args = process_arguments(*args)        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        def casks(alternative: -> { [] }) -        return to_enum(:casks, alternative: alternative) unless block_given? - -        count = 0 - +        return @casks if defined?(@casks)          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 + +      def suggestion_message(cask_token) +        exact_match, partial_matches = Search.search(cask_token) -        casks.each do |cask_or_token| -          begin -            yield cask_or_token.respond_to?(:token) ? cask_or_token : CaskLoader.load(cask_or_token) -            count += 1 -          rescue CaskUnavailableError => e -            cask_token = cask_or_token -            self.class.warn_unavailable_with_suggestion cask_token, e -          rescue CaskError => e -            onoe e.message -          end +        if exact_match.nil? && partial_matches.count == 1 +          exact_match = partial_matches.first          end -        return :empty if casks.length.zero? -        (count == casks.length) ? :complete : :incomplete +        if exact_match +          "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 diff --git a/Library/Homebrew/cask/lib/hbc/cli/cat.rb b/Library/Homebrew/cask/lib/hbc/cli/cat.rb index d08c87bea..043080556 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/cat.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/cat.rb @@ -7,10 +7,6 @@ module Hbc        end        def run -        raise CaskError, "Cat incomplete." if cat_casks == :incomplete -      end - -      def cat_casks          casks.each do |cask|            puts File.open(cask.sourcefile_path, &:read)          end diff --git a/Library/Homebrew/cask/lib/hbc/cli/edit.rb b/Library/Homebrew/cask/lib/hbc/cli/edit.rb index b9485886c..8bce81c52 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/edit.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/edit.rb @@ -4,21 +4,18 @@ module Hbc        def initialize(*)          super          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        def run -        cask_token = args.first -        cask_path = begin -          CaskLoader.load(cask_token).sourcefile_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 - -        odebug "Opening editor for Cask #{cask_token}" +        cask = casks.first +        cask_path = cask.sourcefile_path +        odebug "Opening editor for Cask #{cask.token}"          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        def self.help diff --git a/Library/Homebrew/cask/lib/hbc/cli/fetch.rb b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb index e31b1a17c..12c794f5f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/fetch.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/fetch.rb @@ -9,10 +9,6 @@ module Hbc        end        def run -        raise CaskError, "Fetch incomplete." if fetch_casks == :incomplete -      end - -      def fetch_casks          casks.each do |cask|            ohai "Downloading external files for Cask #{cask}"            downloaded_path = Download.new(cask, force: force?).perform diff --git a/Library/Homebrew/cask/lib/hbc/cli/info.rb b/Library/Homebrew/cask/lib/hbc/cli/info.rb index d26747e17..9cdada62e 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/info.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/info.rb @@ -69,13 +69,11 @@ module Hbc        def self.artifact_info(cask)          ohai "Artifacts" -        DSL::ORDINARY_ARTIFACT_TYPES.each do |type| -          next if cask.artifacts[type].empty? -          cask.artifacts[type].each do |artifact| -            activatable_item = (type == :stage_only) ? "<none>" : artifact.first -            puts "#{activatable_item} (#{type})" -          end -        end +        DSL::ORDINARY_ARTIFACT_CLASSES.flat_map { |klass| klass.for_cask(cask) } +                                      .select { |artifact| artifact.respond_to?(:install_phase) } +                                      .each do |artifact| +                                        puts artifact.to_s +                                      end        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/cli/install.rb b/Library/Homebrew/cask/lib/hbc/cli/install.rb index 0f1a5dd34..9a2116e6a 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/install.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/install.rb @@ -10,10 +10,6 @@ module Hbc        end        def run -        raise CaskError, "Install incomplete." if install_casks == :incomplete -      end - -      def install_casks          casks.each do |cask|            begin              Installer.new(cask, binaries:       binaries?, diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_appcast_checkpoint.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_appcast_checkpoint.rb index cd2679782..a538ffd8c 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_appcast_checkpoint.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_appcast_checkpoint.rb @@ -12,7 +12,7 @@ module Hbc          if args.all? { |t| t =~ %r{^https?://} && t !~ /\.rb$/ }            self.class.appcask_checkpoint_for_url(args)          else -          self.class.appcask_checkpoint(args, calculate?) +          self.class.appcask_checkpoint(casks, calculate?)          end        end @@ -23,33 +23,27 @@ module Hbc          end        end -      def self.appcask_checkpoint(cask_tokens, calculate) -        count = 0 - -        cask_tokens.each do |cask_token| -          cask = CaskLoader.load(cask_token) - +      def self.appcask_checkpoint(casks, calculate) +        casks.each do |cask|            if cask.appcast.nil?              opoo "Cask '#{cask}' is missing an `appcast` stanza."            else -            if calculate +            checkpoint = if calculate                result = cask.appcast.calculate_checkpoint - -              checkpoint = result[:checkpoint] +              result[:checkpoint]              else -              checkpoint = cask.appcast.checkpoint +              cask.appcast.checkpoint              end -            if checkpoint.nil? +            if calculate && checkpoint.nil?                onoe "Could not retrieve `appcast` checkpoint for cask '#{cask}': #{result[:command_result].stderr}" +            elsif casks.count > 1 +              puts "#{checkpoint}  #{cask}"              else -              puts((cask_tokens.count > 1) ? "#{checkpoint}  #{cask}" : checkpoint) -              count += 1 +              puts checkpoint              end            end          end - -        count == cask_tokens.count        end        def self.help diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb index e21ce86b6..8a38ce1be 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_dump.rb @@ -7,10 +7,6 @@ module Hbc        end        def run -        raise CaskError, "Dump incomplete." if dump_casks == :incomplet -      end - -      def dump_casks          casks.each(&:dumpcask)        end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb index 4515fe931..c04619798 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb @@ -3,7 +3,7 @@ module Hbc      class InternalStanza < AbstractInternalCommand        # 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.        # @@ -14,41 +14,16 @@ module Hbc        # Examples        #        #     brew cask _stanza appcast   --table -      #     brew cask _stanza app       --table alfred google-chrome adium voicemac logisim vagrant -      #     brew cask _stanza url       --table alfred google-chrome adium voicemac logisim vagrant -      #     brew cask _stanza version   --table alfred google-chrome adium voicemac logisim vagrant -      #     brew cask _stanza artifacts --table --inspect alfred google-chrome adium voicemac logisim vagrant -      #     brew cask _stanza artifacts --table --yaml    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 vagrant +      #     brew cask _stanza version   --table           alfred google-chrome adium vagrant +      #     brew cask _stanza artifacts --table           alfred google-chrome adium vagrant +      #     brew cask _stanza artifacts --table --yaml    alfred google-chrome adium vagrant        # -      # TODO: this should be retrievable from Hbc::DSL -      ARTIFACTS = Set.new [ -        :app, -        :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, -      ] +      ARTIFACTS = +        DSL::ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) + +        DSL::ARTIFACT_BLOCK_CLASSES.map(&:dsl_key)        option "--table",   :table,   false        option "--quiet",   :quiet,   false @@ -68,16 +43,9 @@ module Hbc          @stanza = args.shift.to_sym          @format = :to_yaml if yaml? -        @format = :inspect if inspect?        end        def run -        return unless print_stanzas == :incomplete -        exit 1 if quiet? -        raise CaskError, "Print incomplete." -      end - -      def print_stanzas          if ARTIFACTS.include?(stanza)            artifact_name = stanza            @stanza = :artifacts @@ -93,7 +61,7 @@ module Hbc            end            begin -            value = cask.send(@stanza) +            value = cask.send(stanza)            rescue StandardError              opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet?              puts "" @@ -106,11 +74,25 @@ module Hbc              next            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 + +                [k, v] +              end +            ] + +            value = value.fetch(artifact_name) if artifact_name +          end -          if @format -            puts value.send(@format) -          elsif artifact_name || value.is_a?(Symbol) +          if format +            puts value.send(format) +          elsif value.is_a?(Symbol)              puts value.inspect            else              puts value.to_s diff --git a/Library/Homebrew/cask/lib/hbc/cli/list.rb b/Library/Homebrew/cask/lib/hbc/cli/list.rb index 9d978360e..32415af8a 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/list.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/list.rb @@ -3,6 +3,7 @@ module Hbc      class List < AbstractCommand        option "-1", :one, false        option "--versions", :versions, false +      option "--full-name", :full_name, false        option "-l", (lambda do |*|          one = true # rubocop:disable Lint/UselessAssignment @@ -10,8 +11,7 @@ module Hbc        end)        def run -        retval = args.any? ? list : list_installed -        raise CaskError, "Listing incomplete." if retval == :incomplete +        args.any? ? list : list_installed        end        def list @@ -30,9 +30,9 @@ module Hbc        end        def self.list_artifacts(cask) -        Artifact.for_cask(cask).each do |artifact| -          summary = artifact.summary -          ohai summary[:english_description], summary[:contents] unless summary.empty? +        Artifact.for_cask(cask).group_by(&:class).each do |klass, artifacts| +          next unless klass.respond_to?(:english_description) +          ohai klass.english_description, artifacts.map(&:summarize_installed)          end        end @@ -43,11 +43,11 @@ module Hbc            puts installed_casks.map(&:to_s)          elsif versions?            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?            puts Formatter.columns(installed_casks.map(&:to_s))          end - -        installed_casks.empty? ? :empty : :complete        end        def self.format_versioned(cask) diff --git a/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb b/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb index 337a2eb9d..408be134d 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb @@ -1,7 +1,7 @@  module Hbc    class CLI      class Reinstall < Install -      def install_casks +      def run          casks.each do |cask|            Installer.new(cask, binaries:       binaries?,                                verbose:        verbose?, diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb index e89dced92..d56d0c81f 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/search.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -2,8 +2,12 @@ module Hbc    class CLI      class Search < AbstractCommand        def run -        results = self.class.search(*args) -        self.class.render_results(*results) +        if args.empty? +          puts Formatter.columns(CLI.nice_listing(Hbc.all_tokens)) +        else +          results = self.class.search(*args) +          self.class.render_results(*results) +        end        end        def self.extract_regexp(string) @@ -15,8 +19,17 @@ module Hbc        end        def self.search_remote(query) -        matches = GitHub.search_code(user: "caskroom", path: "Casks", -                                     filename: query, extension: "rb") +        matches = begin +          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"])            next if tap.installed? diff --git a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb index c0697c808..7e55db5f1 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb @@ -9,10 +9,6 @@ module Hbc        end        def run -        raise CaskError, "Uninstall incomplete." if uninstall_casks == :incomplete -      end - -      def uninstall_casks          casks.each do |cask|            odebug "Uninstalling Cask #{cask}" diff --git a/Library/Homebrew/cask/lib/hbc/cli/zap.rb b/Library/Homebrew/cask/lib/hbc/cli/zap.rb index e709f4191..7f5e6785d 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/zap.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/zap.rb @@ -9,10 +9,6 @@ module Hbc        end        def run -        raise CaskError, "Zap incomplete." if zap_casks == :incomplete -      end - -      def zap_casks          casks.each do |cask|            odebug "Zapping Cask #{cask}"            Installer.new(cask, verbose: verbose?, force: force?).zap diff --git a/Library/Homebrew/cask/lib/hbc/container/naked.rb b/Library/Homebrew/cask/lib/hbc/container/naked.rb index 375d62f7a..dc265c402 100644 --- a/Library/Homebrew/cask/lib/hbc/container/naked.rb +++ b/Library/Homebrew/cask/lib/hbc/container/naked.rb @@ -16,7 +16,7 @@ module Hbc        def target_file          return @path.basename if @nested -        URI.decode(File.basename(@cask.url.path)) +        CGI.unescape(File.basename(@cask.url.path))        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/dsl.rb b/Library/Homebrew/cask/lib/hbc/dsl.rb index 8ad206c2f..3824b9761 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl.rb @@ -1,6 +1,8 @@  require "set"  require "locale" +require "hbc/artifact" +  require "hbc/dsl/appcast"  require "hbc/dsl/base"  require "hbc/dsl/caveats" @@ -8,7 +10,6 @@ require "hbc/dsl/conflicts_with"  require "hbc/dsl/container"  require "hbc/dsl/depends_on"  require "hbc/dsl/gpg" -require "hbc/dsl/installer"  require "hbc/dsl/postflight"  require "hbc/dsl/preflight"  require "hbc/dsl/stanza_proxy" @@ -18,39 +19,35 @@ require "hbc/dsl/version"  module Hbc    class DSL -    ORDINARY_ARTIFACT_TYPES = [ -      :app, -      :artifact, -      :audio_unit_plugin, -      :binary, -      :colorpicker, -      :dictionary, -      :font, -      :input_method, -      :internet_plugin, -      :pkg, -      :prefpane, -      :qlplugin, -      :screen_saver, -      :service, -      :stage_only, -      :suite, -      :vst_plugin, -      :vst3_plugin, +    ORDINARY_ARTIFACT_CLASSES = [ +      Artifact::Installer, +      Artifact::App, +      Artifact::Artifact, +      Artifact::AudioUnitPlugin, +      Artifact::Binary, +      Artifact::Colorpicker, +      Artifact::Dictionary, +      Artifact::Font, +      Artifact::InputMethod, +      Artifact::InternetPlugin, +      Artifact::Pkg, +      Artifact::Prefpane, +      Artifact::Qlplugin, +      Artifact::ScreenSaver, +      Artifact::Service, +      Artifact::StageOnly, +      Artifact::Suite, +      Artifact::VstPlugin, +      Artifact::Vst3Plugin, +      Artifact::Uninstall, +      Artifact::Zap,      ].freeze -    ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze - -    SPECIAL_ARTIFACT_TYPES = [ -      :uninstall, -      :zap, -    ].freeze +    ACTIVATABLE_ARTIFACT_TYPES = (ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) - [:stage_only]).freeze -    ARTIFACT_BLOCK_TYPES = [ -      :preflight, -      :postflight, -      :uninstall_preflight, -      :uninstall_postflight, +    ARTIFACT_BLOCK_CLASSES = [ +      Artifact::PreflightBlock, +      Artifact::PostflightBlock,      ].freeze      DSL_METHODS = Set.new [ @@ -72,15 +69,15 @@ module Hbc        :url,        :version,        :appdir, -      *ORDINARY_ARTIFACT_TYPES, +      *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),        *ACTIVATABLE_ARTIFACT_TYPES, -      *SPECIAL_ARTIFACT_TYPES, -      *ARTIFACT_BLOCK_TYPES, +      *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },      ].freeze -    attr_reader :token -    def initialize(token) -      @token = token +    attr_reader :token, :cask +    def initialize(cask) +      @cask = cask +      @token = cask.token      end      def name(*args) @@ -93,12 +90,14 @@ module Hbc        return instance_variable_get("@#{stanza}") if should_return        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        instance_variable_set("@#{stanza}", yield) +    rescue CaskInvalidError +      raise      rescue StandardError => e -      raise CaskInvalidError.new(token, "'#{stanza}' stanza failed with: #{e}") +      raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}")      end      def homepage(homepage = nil) @@ -113,7 +112,7 @@ module Hbc          return unless default          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          @language_blocks.default = block @@ -162,8 +161,8 @@ module Hbc          begin            DSL::Container.new(*args).tap do |container|              # TODO: remove this backward-compatibility section after removing nested_container -            if container && container.nested -              artifacts[:nested_container] << container.nested +            if container&.nested +              artifacts[:nested_container] << Artifact::NestedContainer.new(cask, container.nested)              end            end          end @@ -173,7 +172,7 @@ module Hbc      def version(arg = nil)        set_unique_stanza(:version, arg.nil?) do          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          DSL::Version.new(arg)        end @@ -182,7 +181,7 @@ module Hbc      def sha256(arg = nil)        set_unique_stanza(:sha256, arg.nil?) do          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          arg        end @@ -195,7 +194,7 @@ module Hbc        begin          @depends_on.load(*args)        rescue RuntimeError => e -        raise CaskInvalidError.new(token, e) +        raise CaskInvalidError.new(cask, e)        end        @depends_on      end @@ -237,39 +236,29 @@ module Hbc        set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates }      end -    ORDINARY_ARTIFACT_TYPES.each do |type| +    ORDINARY_ARTIFACT_CLASSES.each do |klass| +      type = klass.dsl_key +        define_method(type) do |*args| -        if type == :stage_only -          if args != [true] -            raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true") +        begin +          if [*artifacts.keys, type].include?(:stage_only) && (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).any? +            raise CaskInvalidError.new(cask, "'stage_only' must be the only activatable artifact.")            end -          unless (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? -            raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") -          end +          artifacts[type].add(klass.from_args(cask, *args)) +        rescue CaskInvalidError +          raise +        rescue StandardError => e +          raise CaskInvalidError.new(cask, "invalid '#{klass.dsl_key}' stanza: #{e}")          end - -        artifacts[type].add(args)        end      end -    def installer(*args) -      return artifacts[:installer] if args.empty? -      artifacts[:installer] << DSL::Installer.new(*args) -      raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) -    rescue StandardError => e -      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 +    ARTIFACT_BLOCK_CLASSES.each do |klass| +      [klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key| +        define_method(dsl_key) do |&block| +          artifacts[dsl_key] << block +        end        end      end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb b/Library/Homebrew/cask/lib/hbc/dsl/installer.rb deleted file mode 100644 index b01b28d76..000000000 --- a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/cask/lib/hbc/dsl/version.rb b/Library/Homebrew/cask/lib/hbc/dsl/version.rb index d73205f52..9605feb57 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/version.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/version.rb @@ -49,7 +49,7 @@ module Hbc          end        end -      DIVIDERS.keys.each do |divider| +      DIVIDERS.each_key do |divider|          define_divider_methods(divider)        end diff --git a/Library/Homebrew/cask/lib/hbc/installer.rb b/Library/Homebrew/cask/lib/hbc/installer.rb index 37cc4e561..01aae935d 100644 --- a/Library/Homebrew/cask/lib/hbc/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/installer.rb @@ -159,7 +159,7 @@ module Hbc        odebug "Extracting primary container"        FileUtils.mkdir_p @cask.staged_path -      container = if @cask.container && @cask.container.type +      container = if @cask.container&.type          Container.from_type(@cask.container.type)        else          Container.for_path(@downloaded_path, @command) @@ -177,7 +177,7 @@ module Hbc        already_installed_artifacts = []        odebug "Installing artifacts" -      artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) +      artifacts = Artifact.for_cask(@cask)        odebug "#{artifacts.length} artifact/s defined", artifacts        artifacts.each do |artifact| @@ -188,7 +188,7 @@ module Hbc            next unless binaries?          end -        artifact.install_phase +        artifact.install_phase(command: @command, verbose: verbose?, force: force?)          already_installed_artifacts.unshift(artifact)        end      rescue StandardError => e @@ -196,7 +196,7 @@ module Hbc          already_installed_artifacts.each do |artifact|            next unless artifact.respond_to?(:uninstall_phase)            odebug "Reverting installation of artifact of class #{artifact.class}" -          artifact.uninstall_phase +          artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)          end        ensure          purge_versioned_files @@ -361,7 +361,7 @@ module Hbc        savedir = @cask.metadata_subdir("Casks", timestamp: :now, create: true)        FileUtils.copy @cask.sourcefile_path, savedir -      old_savedir.rmtree unless old_savedir.nil? +      old_savedir&.rmtree      end      def uninstall @@ -374,25 +374,27 @@ module Hbc      def uninstall_artifacts        odebug "Un-installing artifacts" -      artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) +      artifacts = Artifact.for_cask(@cask)        odebug "#{artifacts.length} artifact/s defined", artifacts        artifacts.each do |artifact|          next unless artifact.respond_to?(:uninstall_phase)          odebug "Un-installing artifact of class #{artifact.class}" -        artifact.uninstall_phase +        artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)        end      end      def zap        ohai %Q(Implied "brew cask uninstall #{@cask}")        uninstall_artifacts -      if Artifact::Zap.me?(@cask) -        ohai "Dispatching zap stanza" -        Artifact::Zap.new(@cask, command: @command).zap_phase -      else +      if (zap_stanzas = Artifact::Zap.for_cask(@cask)).empty?          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        ohai "Removing all staged versions of Cask '#{@cask}'"        purge_caskroom_path diff --git a/Library/Homebrew/cask/lib/hbc/staged.rb b/Library/Homebrew/cask/lib/hbc/staged.rb index c1aa01b29..dc21279de 100644 --- a/Library/Homebrew/cask/lib/hbc/staged.rb +++ b/Library/Homebrew/cask/lib/hbc/staged.rb @@ -4,7 +4,7 @@ module Hbc        index =  0 if index == :first        index =  1 if index == :second        index = -1 if index == :last -      Hbc.appdir.join(@cask.artifacts[:app].to_a.at(index).first, "Contents", "Info.plist") +      @cask.artifacts[:app].to_a.at(index).target.join("Contents", "Info.plist")      end      def plist_exec(cmd) diff --git a/Library/Homebrew/cask/lib/hbc/system_command.rb b/Library/Homebrew/cask/lib/hbc/system_command.rb index b735ae4f9..be083c29e 100644 --- a/Library/Homebrew/cask/lib/hbc/system_command.rb +++ b/Library/Homebrew/cask/lib/hbc/system_command.rb @@ -61,7 +61,7 @@ module Hbc      end      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)      end diff --git a/Library/Homebrew/caveats.rb b/Library/Homebrew/caveats.rb index 578b292fa..1849ea79b 100644 --- a/Library/Homebrew/caveats.rb +++ b/Library/Homebrew/caveats.rb @@ -163,7 +163,7 @@ class Caveats    def plist_caveats      s = [] -    if f.plist || (keg && keg.plist_installed?) +    if f.plist || (keg&.plist_installed?)        plist_domain = f.plist_path.basename(".plist")        # we readlink because this path probably doesn't exist since caveats diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index d1f0b2516..340161204 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -6,6 +6,10 @@ module Homebrew    module Cleanup      @disk_cleanup_size = 0 +    class << self +      attr_reader :disk_cleanup_size +    end +      module_function      def cleanup @@ -21,10 +25,6 @@ module Homebrew        @disk_cleanup_size += path_size      end -    def disk_cleanup_size -      @disk_cleanup_size -    end -      def unremovable_kegs        @unremovable_kegs ||= []      end diff --git a/Library/Homebrew/cmd/irb.rb b/Library/Homebrew/cmd/irb.rb index cba8a5b82..4cd3d4c9e 100644 --- a/Library/Homebrew/cmd/irb.rb +++ b/Library/Homebrew/cmd/irb.rb @@ -19,15 +19,13 @@ class String    end  end -def cask -  $LOAD_PATH.unshift("#{HOMEBREW_LIBRARY_PATH}/cask/lib") -  require "hbc" -end -  module Homebrew    module_function    def irb +    $LOAD_PATH.unshift("#{HOMEBREW_LIBRARY_PATH}/cask/lib") +    require "hbc" +      if ARGV.include? "--examples"        puts "'v8'.f # => instance of the v8 formula"        puts ":hub.f.installed?" diff --git a/Library/Homebrew/cmd/list.rb b/Library/Homebrew/cmd/list.rb index f5c4e68ac..436fc1f97 100644 --- a/Library/Homebrew/cmd/list.rb +++ b/Library/Homebrew/cmd/list.rb @@ -39,15 +39,7 @@ module Homebrew        filtered_list      elsif ARGV.named.empty?        if ARGV.include? "--full-name" -        full_names = Formula.installed.map(&:full_name).sort do |a, b| -          if a.include?("/") && !b.include?("/") -            1 -          elsif !a.include?("/") && b.include?("/") -            -1 -          else -            a <=> b -          end -        end +        full_names = Formula.installed.map(&:full_name).sort &tap_and_name_comparison          return if full_names.empty?          puts Formatter.columns(full_names)        else diff --git a/Library/Homebrew/cmd/prune.rb b/Library/Homebrew/cmd/prune.rb index 9fc6dbcd9..7ec2838ba 100644 --- a/Library/Homebrew/cmd/prune.rb +++ b/Library/Homebrew/cmd/prune.rb @@ -55,7 +55,7 @@ module Homebrew        else          n, d = ObserverPathnameExtension.counts          print "Pruned #{n} symbolic links " -        print "and #{d} directories " if d > 0 +        print "and #{d} directories " if d.positive?          puts "from #{HOMEBREW_PREFIX}"        end      end diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb index acee9817f..c01a11c10 100644 --- a/Library/Homebrew/cmd/search.rb +++ b/Library/Homebrew/cmd/search.rb @@ -67,7 +67,7 @@ module Homebrew          ohai "Searching blacklisted, migrated and deleted formulae..."          if reason = Homebrew::MissingFormula.reason(query, silent: true) -          if count > 0 +          if count.positive?              puts              puts "If you meant #{query.inspect} specifically:"            end diff --git a/Library/Homebrew/cmd/style.rb b/Library/Homebrew/cmd/style.rb index b0f46fadc..e816db5dc 100644 --- a/Library/Homebrew/cmd/style.rb +++ b/Library/Homebrew/cmd/style.rb @@ -119,10 +119,10 @@ module Homebrew      when :print        args << "--display-cop-names" if ARGV.include? "--display-cop-names"        args << "--format" << "simple" if files -      system(cache_env, "rubocop", *args) +      system(cache_env, "rubocop", "_#{HOMEBREW_RUBOCOP_VERSION}_", *args)        !$CHILD_STATUS.success?      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        # execution errors.        # exitstatus can also be nil if RuboCop process crashes, e.g. due to diff --git a/Library/Homebrew/cmd/tap-info.rb b/Library/Homebrew/cmd/tap-info.rb index af087645d..cb0e0b387 100644 --- a/Library/Homebrew/cmd/tap-info.rb +++ b/Library/Homebrew/cmd/tap-info.rb @@ -64,10 +64,10 @@ module Homebrew          if tap.installed?            info += tap.pinned? ? "pinned" : "unpinned"            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")}"            end -          if (command_count = tap.command_files.size) > 0 +          if (command_count = tap.command_files.size).positive?              info += ", #{Formatter.pluralize(command_count, "command")}"            end            info += ", no formulae/commands" if (formula_count + command_count).zero? diff --git a/Library/Homebrew/cmd/unlinkapps.rb b/Library/Homebrew/cmd/unlinkapps.rb index 7cae97e27..56dba3603 100644 --- a/Library/Homebrew/cmd/unlinkapps.rb +++ b/Library/Homebrew/cmd/unlinkapps.rb @@ -77,7 +77,7 @@ module Homebrew    def unlinkapps_unlink?(target_app, opts = {})      # Skip non-symlinks and symlinks that don't point into the Homebrew prefix.      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)        !File.exist?(app) # Remove only broken symlinks in prune mode. diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index ea915f99c..781ee8808 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -124,7 +124,7 @@ module Homebrew      return if ENV["HOMEBREW_UPDATE_TEST"]      core_tap = CoreTap.instance      return if core_tap.installed? -    CoreTap.ensure_installed! quiet: false +    CoreTap.ensure_installed!      revision = core_tap.git_head      ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision      ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision @@ -203,6 +203,7 @@ module Homebrew      end      new_homebrew_repository = Pathname.new "/usr/local/Homebrew" +    new_homebrew_repository.rmdir_if_possible      if new_homebrew_repository.exist?        ofail <<-EOS.undent          #{new_homebrew_repository} already exists. diff --git a/Library/Homebrew/cmd/update.sh b/Library/Homebrew/cmd/update.sh index fb6a3459c..e8211e4dd 100644 --- a/Library/Homebrew/cmd/update.sh +++ b/Library/Homebrew/cmd/update.sh @@ -385,6 +385,12 @@ EOS    if ! git --version >/dev/null 2>&1    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.      [[ -d "$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core" ]] && brew install git      unset GIT_EXECUTABLE diff --git a/Library/Homebrew/cmd/vendor-install.sh b/Library/Homebrew/cmd/vendor-install.sh index fe7e26dd4..6d16a297d 100644 --- a/Library/Homebrew/cmd/vendor-install.sh +++ b/Library/Homebrew/cmd/vendor-install.sh @@ -13,16 +13,16 @@ if [[ -n "$HOMEBREW_MACOS" ]]  then    if [[ "$HOMEBREW_PROCESSOR" = "Intel" ]]    then -    ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.0.0-p648.leopard_64.bottle.tar.gz" -    ruby_SHA="5c1240abe4be91c9774a0089c2a38a8ccfff87c009e8e5786730c659d5e633f7" +    ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.3.3.leopard_64.bottle.1.tar.gz" +    ruby_SHA="34ce9e4c9c1be28db564d744165aa29291426f8a3d2ef806ba4f0b9175aedb2b"    else      ruby_URL=""      ruby_SHA=""    fi  elif [[ -n "$HOMEBREW_LINUX" ]]  then -  ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.0.0-p648.x86_64_linux.bottle.tar.gz" -  ruby_SHA="dbb5118a22a6a75cc77e62544a3d8786d383fab1bdaf8c154951268807357bf0" +  ruby_URL="https://homebrew.bintray.com/bottles-portable/portable-ruby-2.3.3.x86_64_linux.bottle.tar.gz" +  ruby_SHA="543c18bd33a300e6c16671437b1e0f17b03bb64e6a485fc15ff7de1eb1a0bc2a"  fi  fetch() { @@ -45,20 +45,25 @@ fetch() {      curl_args[${#curl_args[*]}]="--progress-bar"    fi +  if [[ "$HOMEBREW_MACOS_VERSION_NUMERIC" -lt "100600" ]] +  then +    curl_args[${#curl_args[*]}]="--insecure" +  fi +    temporary_path="$CACHED_LOCATION.incomplete"    mkdir -p "$HOMEBREW_CACHE" -  [[ -n "$HOMEBREW_QUIET" ]] || echo "==> Downloading $VENDOR_URL" +  [[ -n "$HOMEBREW_QUIET" ]] || echo "==> Downloading $VENDOR_URL" >&2    if [[ -f "$CACHED_LOCATION" ]]    then -    [[ -n "$HOMEBREW_QUIET" ]] || echo "Already downloaded: $CACHED_LOCATION" +    [[ -n "$HOMEBREW_QUIET" ]] || echo "Already downloaded: $CACHED_LOCATION" >&2    else      if [[ -f "$temporary_path" ]]      then        "$HOMEBREW_CURL" "${curl_args[@]}" -C - "$VENDOR_URL" -o "$temporary_path"        if [[ $? -eq 33 ]]        then -        [[ -n "$HOMEBREW_QUIET" ]] || echo "Trying a full download" +        [[ -n "$HOMEBREW_QUIET" ]] || echo "Trying a full download" >&2          rm -f "$temporary_path"          "$HOMEBREW_CURL" "${curl_args[@]}" "$VENDOR_URL" -o "$temporary_path"        fi @@ -135,7 +140,7 @@ install() {    fi    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"    safe_cd "$VENDOR_DIR/portable-$VENDOR_NAME" diff --git a/Library/Homebrew/compat/ENV/shared.rb b/Library/Homebrew/compat/ENV/shared.rb index 200e7b132..c700b1e00 100644 --- a/Library/Homebrew/compat/ENV/shared.rb +++ b/Library/Homebrew/compat/ENV/shared.rb @@ -3,4 +3,8 @@ module SharedEnvExtension      odeprecated "ENV.j1", "ENV.deparallelize"      deparallelize    end + +  def java_cache +    # odeprecated "ENV.java_cache" +  end  end diff --git a/Library/Homebrew/compat/formula_specialties.rb b/Library/Homebrew/compat/formula_specialties.rb index ec5e91ce8..78966625e 100644 --- a/Library/Homebrew/compat/formula_specialties.rb +++ b/Library/Homebrew/compat/formula_specialties.rb @@ -16,7 +16,7 @@ end  # This formula serves as the base class for several very similar  # formulae for Amazon Web Services related tools.  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    def install      odeprecated "AmazonWebServicesFormula#install", "Formula#install" diff --git a/Library/Homebrew/constants.rb b/Library/Homebrew/constants.rb index 23be70bcc..b122946c8 100644 --- a/Library/Homebrew/constants.rb +++ b/Library/Homebrew/constants.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true +  # RuboCop version used for `brew style` and `brew cask style` -HOMEBREW_RUBOCOP_VERSION = "0.49.1".freeze -HOMEBREW_RUBOCOP_CASK_VERSION = "~> 0.13.1".freeze # has to be updated when RuboCop version changes +HOMEBREW_RUBOCOP_VERSION = "0.50.0" +HOMEBREW_RUBOCOP_CASK_VERSION = "~> 0.14.2" # has to be updated when RuboCop version changes diff --git a/Library/Homebrew/debrew.rb b/Library/Homebrew/debrew.rb index c2662c9ee..5bc3d2daa 100644 --- a/Library/Homebrew/debrew.rb +++ b/Library/Homebrew/debrew.rb @@ -57,7 +57,7 @@ module Debrew          input.chomp!          i = input.to_i -        if i > 0 +        if i.positive?            choice = menu.entries[i - 1]          else            possible = menu.entries.find_all { |e| e.name.start_with?(input) } diff --git a/Library/Homebrew/debrew/irb.rb b/Library/Homebrew/debrew/irb.rb index f97403782..069dbe676 100644 --- a/Library/Homebrew/debrew/irb.rb +++ b/Library/Homebrew/debrew/irb.rb @@ -16,7 +16,7 @@ module IRB        workspace = WorkSpace.new(binding)        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        trap("SIGINT") do diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index d7d5ec59c..0fbc2625b 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -51,7 +51,7 @@ class Dependency    end    def modify_build_environment -    env_proc.call unless env_proc.nil? +    env_proc&.call    end    def inspect diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 2c9336481..a7d498c0d 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -54,6 +54,7 @@ module Homebrew    def audit      Homebrew.inject_dump_stats!(FormulaAuditor, /^audit_/) if ARGV.switch? "D" +    Homebrew.auditing = true      formula_count = 0      problem_count = 0 @@ -201,19 +202,24 @@ class FormulaAuditor      @specs = %w[stable devel head].map { |s| formula.send(s) }.compact    end -  def self.check_http_content(url, name, user_agents: [:default]) +  def self.check_http_content(url, user_agents: [:default], check_content: false, strict: false, require_http: false)      return unless url.start_with? "http"      details = nil      user_agent = nil -    hash_needed = url.start_with?("http:") && name != "curl" +    hash_needed = url.start_with?("http:") && !require_http      user_agents.each do |ua|        details = http_content_headers_and_checksum(url, hash_needed: hash_needed, user_agent: ua)        user_agent = ua        break if details[:status].to_s.start_with?("2")      end -    return "The URL #{url} is not reachable" unless details[:status] +    unless details[:status] +      # Hack around https://github.com/Homebrew/brew/issues/3199 +      return if MacOS.version == :el_capitan +      return "The URL #{url} is not reachable" +    end +      unless details[:status].start_with? "2"        return "The URL #{url} is not reachable (HTTP status code #{details[:status]})"      end @@ -236,8 +242,32 @@ class FormulaAuditor        details[:content_length] == secure_details[:content_length]      file_match = details[:file_hash] == secure_details[:file_hash] -    return if !etag_match && !content_length_match && !file_match -    "The URL #{url} could use HTTPS rather than HTTP" +    if etag_match || content_length_match || file_match +      return "The URL #{url} should use HTTPS rather than HTTP" +    end + +    return unless check_content + +    no_protocol_file_contents = %r{https?:\\?/\\?/} +    details[:file] = details[:file].gsub(no_protocol_file_contents, "/") +    secure_details[:file] = secure_details[:file].gsub(no_protocol_file_contents, "/") + +    # Check for the same content after removing all protocols +    if details[:file] == secure_details[:file] +      return "The URL #{url} should use HTTPS rather than HTTP" +    end + +    return unless strict + +    # Same size, different content after normalization +    # (typical causes: Generated ID, Timestamp, Unix time) +    if details[:file].length == secure_details[:file].length +      return "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser." +    end + +    lenratio = (100 * secure_details[:file].length / details[:file].length).to_i +    return unless (90..110).cover?(lenratio) +    "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser."    end    def self.http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) @@ -260,6 +290,7 @@ class FormulaAuditor        etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2],        content_length: headers[/Content-Length: (\d+)/, 1],        file_hash: output_hash, +      file: output,      }    end @@ -327,7 +358,7 @@ class FormulaAuditor        end        valid_alias_names = [alias_name_major, alias_name_major_minor] -      if formula.tap && !formula.tap.core_tap? +      unless formula.tap&.core_tap?          versioned_aliases.map! { |a| "#{formula.tap}/#{a}" }          valid_alias_names.map! { |a| "#{formula.tap}/#{a}" }        end @@ -356,21 +387,6 @@ class FormulaAuditor      end    end -  def audit_class -    if @strict -      unless formula.test_defined? -        problem "A `test do` test block should be added" -      end -    end - -    classes = %w[GithubGistFormula ScriptFileFormula AmazonWebServicesFormula] -    klass = classes.find do |c| -      Object.const_defined?(c) && formula.class < Object.const_get(c) -    end - -    problem "#{klass} is deprecated, use Formula instead" if klass -  end -    # core aliases + tap alias names + tap alias full name    @@aliases ||= Formula.aliases + Formula.tap_aliases @@ -403,6 +419,7 @@ class FormulaAuditor      @@local_official_taps_name_map ||= Tap.select(&:official?).flat_map(&:formula_names)                                            .each_with_object({}) do |tap_formula_full_name, name_map| +      next if tap_formula_full_name.start_with?("homebrew/science/")        tap_formula_name = tap_formula_full_name.split("/").last        name_map[tap_formula_name] ||= []        name_map[tap_formula_name] << tap_formula_full_name @@ -413,6 +430,7 @@ class FormulaAuditor      if @online        Homebrew.search_taps(name, silent: true).each do |tap_formula_full_name| +        next if tap_formula_full_name.start_with?("homebrew/science/")          tap_formula_name = tap_formula_full_name.split("/").last          next if tap_formula_name != name          same_name_tap_formulae << tap_formula_full_name @@ -563,10 +581,11 @@ class FormulaAuditor      return unless @online -    return unless DevelopmentTools.curl_handles_most_https_homepages? +    return unless DevelopmentTools.curl_handles_most_https_certificates?      if http_content_problem = FormulaAuditor.check_http_content(homepage, -                                               formula.name, -                                               user_agents: [:browser, :default]) +                                               user_agents: [:browser, :default], +                                               check_content: true, +                                               strict: @strict)        problem http_content_problem      end    end @@ -616,13 +635,14 @@ class FormulaAuditor      end      %w[Stable Devel HEAD].each do |name| -      next unless spec = formula.send(name.downcase) +      spec_name = name.downcase.to_sym +      next unless spec = formula.send(spec_name) -      ra = ResourceAuditor.new(spec, online: @online, strict: @strict).audit +      ra = ResourceAuditor.new(spec, spec_name, online: @online, strict: @strict).audit        problems.concat ra.problems.map { |problem| "#{name}: #{problem}" }        spec.resources.each_value do |resource| -        ra = ResourceAuditor.new(resource, online: @online, strict: @strict).audit +        ra = ResourceAuditor.new(resource, spec_name, online: @online, strict: @strict).audit          problems.concat ra.problems.map { |problem|            "#{name} resource #{resource.name.inspect}: #{problem}"          } @@ -687,7 +707,7 @@ class FormulaAuditor      end      stable = formula.stable -    case stable && stable.url +    case stable&.url      when /[\d\._-](alpha|beta|rc\d)/        matched = Regexp.last_match(1)        version_prefix = stable.version.to_s.sub(/\d+$/, "") @@ -865,8 +885,8 @@ class FormulaAuditor      problem "Use spaces instead of tabs for indentation" if line =~ /^[ ]*\t/ -    if line.include?("ENV.x11") -      problem "Use \"depends_on :x11\" instead of \"ENV.x11\"" +    if line.include?("ENV.java_cache") +      problem "In-formula ENV.java_cache usage has been deprecated & should be removed."      end      # Avoid hard-coding compilers @@ -882,14 +902,6 @@ class FormulaAuditor        problem "Use ENV instead of invoking '#{Regexp.last_match(1)}' to modify the environment"      end -    if formula.name != "wine" && line =~ /ENV\.universal_binary/ -      problem "macOS has been 64-bit only since 10.6 so ENV.universal_binary is deprecated." -    end - -    if line =~ /build\.universal\?/ -      problem "macOS has been 64-bit only so build.universal? is deprecated." -    end -      if line =~ /version == ['"]HEAD['"]/        problem "Use 'build.head?' instead of inspecting 'version'"      end @@ -930,12 +942,6 @@ class FormulaAuditor        problem "Use build instead of ARGV to check options"      end -    problem "Use new-style option definitions" if line.include?("def options") - -    if line.end_with?("def test") -      problem "Use new-style test definitions (test do)" -    end -      if line.include?("MACOS_VERSION")        problem "Use MacOS.version instead of MACOS_VERSION"      end @@ -949,11 +955,6 @@ class FormulaAuditor        problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead"      end -    if line =~ /skip_clean\s+:all/ -      problem "`skip_clean :all` is deprecated; brew no longer strips symbols\n" \ -              "\tPass explicit paths to prevent Homebrew from removing empty folders." -    end -      if line =~ /depends_on [A-Z][\w:]+\.new$/        problem "`depends_on` can take requirement classes instead of instances"      end @@ -992,30 +993,6 @@ class FormulaAuditor        problem "Use `assert_match` instead of `assert ...include?`"      end -    if line.include?('system "npm", "install"') && !line.include?("Language::Node") && -       formula.name !~ /^kibana(\@\d+(\.\d+)?)?$/ -      problem "Use Language::Node for npm install args" -    end - -    if line.include?("fails_with :llvm") -      problem "'fails_with :llvm' is now a no-op so should be removed" -    end - -    if line =~ /system\s+['"](otool|install_name_tool|lipo)/ && formula.name != "cctools" -      problem "Use ruby-macho instead of calling #{Regexp.last_match(1)}" -    end - -    if formula.tap.to_s == "homebrew/core" -      ["OS.mac?", "OS.linux?"].each do |check| -        next unless line.include?(check) -        problem "Don't use #{check}; Homebrew/core only supports macOS" -      end -    end - -    if line =~ /((revision|version_scheme)\s+0)/ -      problem "'#{Regexp.last_match(1)}' should be removed" -    end -      return unless @strict      problem "`#{Regexp.last_match(1)}` in formulae is deprecated" if line =~ /(env :(std|userpaths))/ @@ -1041,7 +1018,7 @@ class FormulaAuditor    def audit_reverse_migration      # Only enforce for new formula being re-added to core and official taps      return unless @strict -    return unless formula.tap && formula.tap.official? +    return unless formula.tap&.official?      return unless formula.tap.tap_migrations.key?(formula.name)      problem <<-EOS.undent @@ -1116,10 +1093,10 @@ class FormulaAuditor  end  class ResourceAuditor -  attr_reader :problems -  attr_reader :version, :checksum, :using, :specs, :url, :mirrors, :name +  attr_reader :name, :version, :checksum, :url, :mirrors, :using, :specs, :owner +  attr_reader :spec_name, :problems -  def initialize(resource, options = {}) +  def initialize(resource, spec_name, options = {})      @name     = resource.name      @version  = resource.version      @checksum = resource.checksum @@ -1127,9 +1104,11 @@ class ResourceAuditor      @mirrors  = resource.mirrors      @using    = resource.using      @specs    = resource.specs -    @online   = options[:online] -    @strict   = options[:strict] -    @problems = [] +    @owner    = resource.owner +    @spec_name = spec_name +    @online    = options[:online] +    @strict    = options[:strict] +    @problems  = []    end    def audit @@ -1203,11 +1182,29 @@ class ResourceAuditor      problem "Redundant :using value in URL"    end +  def self.curl_openssl_and_deps +    @curl_openssl_and_deps ||= begin +      formulae_names = ["curl", "openssl"] +      formulae_names += formulae_names.flat_map do |f| +        Formula[f].recursive_dependencies.map(&:name) +      end +      formulae_names.uniq +    rescue FormulaUnavailableError +      [] +    end +  end +    def audit_urls      urls = [url] + mirrors -    if name == "curl" && !urls.find { |u| u.start_with?("http://") } -      problem "should always include at least one HTTP url" +    curl_openssl_or_deps = ResourceAuditor.curl_openssl_and_deps.include?(owner.name) + +    if spec_name == :stable && curl_openssl_or_deps +      problem "should not use xz tarballs" if url.end_with?(".xz") + +      unless urls.find { |u| u.start_with?("http://") } +        problem "should always include at least one HTTP mirror" +      end      end      return unless @online @@ -1219,7 +1216,7 @@ class ResourceAuditor          # A `brew mirror`'ed URL is usually not yet reachable at the time of          # pull request.          next if url =~ %r{^https://dl.bintray.com/homebrew/mirror/} -        if http_content_problem = FormulaAuditor.check_http_content(url, name) +        if http_content_problem = FormulaAuditor.check_http_content(url, require_http: curl_openssl_or_deps)            problem http_content_problem          end        elsif strategy <= GitDownloadStrategy @@ -1228,6 +1225,7 @@ class ResourceAuditor          end        elsif strategy <= SubversionDownloadStrategy          next unless DevelopmentTools.subversion_handles_most_https_certificates? +        next unless Utils.svn_available?          unless Utils.svn_remote_exists url            problem "The URL #{url} is not a valid svn URL"          end diff --git a/Library/Homebrew/dev-cmd/bottle.rb b/Library/Homebrew/dev-cmd/bottle.rb index d8aefc4c0..8dfd0d12c 100644 --- a/Library/Homebrew/dev-cmd/bottle.rb +++ b/Library/Homebrew/dev-cmd/bottle.rb @@ -47,7 +47,7 @@ BOTTLE_ERB = <<-EOS.freeze      <% elsif cellar != BottleSpecification::DEFAULT_CELLAR %>      cellar "<%= cellar %>"      <% end %> -    <% if rebuild > 0 %> +    <% if rebuild.positive? %>      rebuild <%= rebuild %>      <% end %>      <% checksums.each do |checksum_type, checksum_values| %> @@ -186,7 +186,7 @@ module Homebrew        ohai "Determining #{f.full_name} bottle rebuild..."        versions = FormulaVersions.new(f)        rebuilds = versions.bottle_version_map("origin/master")[f.pkg_version] -      rebuilds.pop if rebuilds.last.to_i > 0 +      rebuilds.pop if rebuilds.last.to_i.positive?        rebuild = rebuilds.empty? ? 0 : rebuilds.max.to_i + 1      end @@ -283,7 +283,7 @@ module Homebrew          raise        ensure          ignore_interrupts do -          original_tab.write if original_tab +          original_tab&.write            unless ARGV.include? "--skip-relocation"              keg.replace_placeholders_with_locations changed_files            end diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index e9e98d450..87d8274cc 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -89,7 +89,8 @@ module Homebrew    def check_for_duplicate_pull_requests(formula)      pull_requests = fetch_pull_requests(formula) -    return unless pull_requests && !pull_requests.empty? +    return unless pull_requests +    return if pull_requests.empty?      duplicates_message = <<-EOS.undent        These open pull requests may be duplicates:        #{pull_requests.map { |pr| "#{pr["title"]} #{pr["html_url"]}" }.join("\n")} @@ -124,7 +125,7 @@ module Homebrew        Formula.each do |f|          if is_devel && f.devel && f.devel.url && f.devel.url.match(base_url)            guesses << f -        elsif f.stable && f.stable.url && f.stable.url.match(base_url) +        elsif f.stable&.url && f.stable.url.match(base_url)            guesses << f          end        end @@ -296,9 +297,7 @@ module Homebrew          ohai "git fetch --unshallow origin" if shallow          ohai "git checkout --no-track -b #{branch} origin/master"          ohai "git commit --no-edit --verbose --message='#{formula.name} #{new_formula_version}#{devel_message}' -- #{formula.path}" -        ohai "hub fork --no-remote" -        ohai "hub fork" -        ohai "hub fork (to read $HUB_REMOTE)" +        ohai "hub fork # read $HUB_REMOTE"          ohai "git push --set-upstream $HUB_REMOTE #{branch}:#{branch}"          ohai "hub pull-request --browse -m '#{formula.name} #{new_formula_version}#{devel_message}'"          ohai "git checkout -" @@ -308,9 +307,9 @@ module Homebrew          safe_system "git", "commit", "--no-edit", "--verbose",            "--message=#{formula.name} #{new_formula_version}#{devel_message}",            "--", formula.path -        safe_system "hub", "fork", "--no-remote" -        quiet_system "hub", "fork" -        remote = Utils.popen_read("hub fork 2>&1")[/fatal: remote (.+) already exists\./, 1] +        remote = Utils.popen_read("hub fork 2>&1")[/remote:? (\S+)/, 1] +        # repeat for hub 2.2 backwards compatibility: +        remote = Utils.popen_read("hub fork 2>&1")[/remote:? (\S+)/, 1] if remote.to_s.empty?          odie "cannot get remote from 'hub'!" if remote.to_s.empty?          safe_system "git", "push", "--set-upstream", remote, "#{branch}:#{branch}"          pr_message = <<-EOS.undent diff --git a/Library/Homebrew/dev-cmd/pull.rb b/Library/Homebrew/dev-cmd/pull.rb index a8f35531f..cd0d6fbd0 100644 --- a/Library/Homebrew/dev-cmd/pull.rb +++ b/Library/Homebrew/dev-cmd/pull.rb @@ -69,13 +69,13 @@ module Homebrew      tap = nil      ARGV.named.each do |arg| -      if arg.to_i > 0 +      if arg.to_i.positive?          issue = arg          url = "https://github.com/Homebrew/homebrew-core/pull/#{arg}"          tap = CoreTap.instance        elsif (testing_match = arg.match %r{/job/Homebrew.*Testing/(\d+)/})          tap = ARGV.value("tap") -        tap = if tap && tap.start_with?("homebrew/") +        tap = if tap&.start_with?("homebrew/")            Tap.fetch("homebrew", tap.strip_prefix("homebrew/"))          elsif tap            odie "Tap option did not start with \"homebrew/\": #{tap}" @@ -350,7 +350,7 @@ module Homebrew        files << Regexp.last_match(1) if line =~ %r{^\+\+\+ b/(.*)}      end      files.each do |file| -      if tap && tap.formula_file?(file) +      if tap&.formula_file?(file)          formula_name = File.basename(file, ".rb")          formulae << formula_name unless formulae.include?(formula_name)        else diff --git a/Library/Homebrew/dev-cmd/release-notes.rb b/Library/Homebrew/dev-cmd/release-notes.rb index e578869bf..496023956 100644 --- a/Library/Homebrew/dev-cmd/release-notes.rb +++ b/Library/Homebrew/dev-cmd/release-notes.rb @@ -10,10 +10,8 @@ module Homebrew    def release_notes      previous_tag = ARGV.named.first -    unless previous_tag -      previous_tag = Utils.popen_read("git tag --list --sort=-version:refname") +    previous_tag ||= Utils.popen_read("git tag --list --sort=-version:refname")                            .lines.first.chomp -    end      odie "Could not find any previous tags!" unless previous_tag      end_ref = ARGV.named[1] || "origin/master" diff --git a/Library/Homebrew/development_tools.rb b/Library/Homebrew/development_tools.rb index 996dea87c..b7787d849 100644 --- a/Library/Homebrew/development_tools.rb +++ b/Library/Homebrew/development_tools.rb @@ -114,7 +114,7 @@ class DevelopmentTools        @non_apple_gcc_version = {}      end -    def curl_handles_most_https_homepages? +    def curl_handles_most_https_certificates?        true      end diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index f42fb95e2..020594f0c 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -522,7 +522,7 @@ module Homebrew          homebrew_owned = @found.all? do |path|            Pathname.new(path).realpath.to_s.start_with? "#{HOMEBREW_CELLAR}/gettext"          end -        return if gettext && gettext.linked_keg.directory? && homebrew_owned +        return if gettext&.linked_keg&.directory? && homebrew_owned          inject_file_list @found, <<-EOS.undent            gettext files detected at a system prefix. @@ -540,7 +540,7 @@ module Homebrew          rescue            nil          end -        if libiconv && libiconv.linked_keg.directory? +        if libiconv&.linked_keg&.directory?            unless libiconv.keg_only?              <<-EOS.undent                A libiconv formula is installed and linked. diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index adeb0a02a..7012fccc8 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -331,20 +331,10 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy      if cached_location.exist?        puts "Already downloaded: #{cached_location}"      else -      had_incomplete_download = temporary_path.exist?        begin          _fetch        rescue ErrorDuringExecution -        # 33 == range not supported -        # try wiping the incomplete download and retrying once -        unless $CHILD_STATUS.exitstatus == 33 && had_incomplete_download -          raise CurlDownloadStrategyError, @url -        end - -        ohai "Trying a full download" -        temporary_path.unlink -        had_incomplete_download = false -        retry +        raise CurlDownloadStrategyError, @url        end        ignore_interrupts { temporary_path.rename(cached_location) }      end diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 8b4cddc59..5418f9331 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -416,7 +416,7 @@ class BuildError < RuntimeError      puts -    if issues && !issues.empty? +    unless issues&.empty?        puts "These open issues may also help:"        puts issues.map { |i| "#{i["title"]} #{i["html_url"]}" }.join("\n")      end diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb index c6cb54f5d..63a0f3e40 100644 --- a/Library/Homebrew/extend/ARGV.rb +++ b/Library/Homebrew/extend/ARGV.rb @@ -144,10 +144,10 @@ module HomebrewArgvExtension    def value(name)      arg_prefix = "--#{name}="      flag_with_value = find { |arg| arg.start_with?(arg_prefix) } -    flag_with_value.strip_prefix(arg_prefix) if flag_with_value +    flag_with_value&.strip_prefix(arg_prefix)    end -  # Returns an array of values that were given as a comma-seperated list. +  # Returns an array of values that were given as a comma-separated list.    # @see value    def values(name)      return unless val = value(name) @@ -236,7 +236,7 @@ module HomebrewArgvExtension    def bottle_arch      arch = value "bottle-arch" -    arch.to_sym if arch +    arch&.to_sym    end    def build_from_source? diff --git a/Library/Homebrew/extend/ENV.rb b/Library/Homebrew/extend/ENV.rb index 283e90b69..ea1b99501 100644 --- a/Library/Homebrew/extend/ENV.rb +++ b/Library/Homebrew/extend/ENV.rb @@ -28,7 +28,7 @@ module EnvActivation    end    def clear_sensitive_environment! -    ENV.keys.each do |key| +    ENV.each_key do |key|        next unless /(cookie|key|token)/i =~ key        ENV.delete key      end diff --git a/Library/Homebrew/extend/ENV/shared.rb b/Library/Homebrew/extend/ENV/shared.rb index b51ade48b..15488ee19 100644 --- a/Library/Homebrew/extend/ENV/shared.rb +++ b/Library/Homebrew/extend/ENV/shared.rb @@ -260,10 +260,6 @@ module SharedEnvExtension      set_cpu_flags(flags)    end -  def java_cache -    append "_JAVA_OPTIONS", "-Duser.home=#{HOMEBREW_CACHE}/java_cache" -  end -    # ld64 is a newer linker provided for Xcode 2.5    # @private    def ld64 diff --git a/Library/Homebrew/extend/ENV/std.rb b/Library/Homebrew/extend/ENV/std.rb index a2e800803..4e5d0683a 100644 --- a/Library/Homebrew/extend/ENV/std.rb +++ b/Library/Homebrew/extend/ENV/std.rb @@ -233,8 +233,8 @@ module Stdenv    def make_jobs      # '-j' requires a positive integral argument -    if self["HOMEBREW_MAKE_JOBS"].to_i > 0 -      self["HOMEBREW_MAKE_JOBS"].to_i +    if (jobs = self["HOMEBREW_MAKE_JOBS"].to_i).positive? +      jobs      else        Hardware::CPU.cores      end diff --git a/Library/Homebrew/extend/os/mac/development_tools.rb b/Library/Homebrew/extend/os/mac/development_tools.rb index caa85ffca..1931b398d 100644 --- a/Library/Homebrew/extend/os/mac/development_tools.rb +++ b/Library/Homebrew/extend/os/mac/development_tools.rb @@ -9,7 +9,7 @@ class DevelopmentTools          @locate[key] = if (located_tool = original_locate(tool))            located_tool          elsif MacOS.version > :tiger -          path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool).chomp +          path = Utils.popen_read("/usr/bin/xcrun", "-no-cache", "-find", tool, err: :close).chomp            Pathname.new(path) if File.executable?(path)          end        end @@ -43,11 +43,16 @@ class DevelopmentTools      end      def custom_installation_instructions -      if MacOS.version > :tiger +      if MacOS.version > :leopard          <<-EOS.undent            Install GNU's GCC              brew install gcc          EOS +      elsif MacOS.version > :tiger +        <<-EOS.undent +          Install GNU's GCC +            brew install gcc@4.6 +        EOS        else          # Tiger doesn't ship with apple-gcc42, and this is required to build          # some software that doesn't build properly with FSF GCC. @@ -55,7 +60,7 @@ class DevelopmentTools            Install Apple's GCC              brew install apple-gcc42            or GNU's GCC -            brew install gcc +            brew install gcc@4.6          EOS        end      end @@ -77,10 +82,10 @@ class DevelopmentTools        end      end -    def curl_handles_most_https_homepages? -      # The system Curl is too old for some modern HTTPS homepages on +    def curl_handles_most_https_certificates? +      # The system Curl is too old for some modern HTTPS certificates on        # older macOS versions. -      MacOS.version >= :el_capitan +      ENV["HOMEBREW_SYSTEM_CURL_TOO_OLD"].nil?      end      def subversion_handles_most_https_certificates? diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb index 0cdd7b115..9f7b18b49 100644 --- a/Library/Homebrew/extend/os/mac/diagnostic.rb +++ b/Library/Homebrew/extend/os/mac/diagnostic.rb @@ -195,8 +195,9 @@ module Homebrew        end        def check_ruby_version -        ruby_version = "2.0" -        return if RUBY_VERSION[/\d\.\d/] == ruby_version +        ruby_version = "2.3.3" +        return if RUBY_VERSION == ruby_version +        return if ARGV.homebrew_developer? && OS::Mac.prerelease?          <<-EOS.undent            Ruby version #{RUBY_VERSION} is unsupported on #{MacOS.version}. Homebrew diff --git a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb index 9c20cc7c6..5872c2264 100644 --- a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb +++ b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb @@ -96,9 +96,13 @@ module Superenv        self["SDKROOT"] = MacOS.sdk_path      end -    # Filter out symbols known not to be defined on 10.11 since GNU Autotools -    # can't reliably figure this out with Xcode 8 on its own yet. -    if MacOS.version == "10.11" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "8.0" +    # Filter out symbols known not to be defined since GNU Autotools can't +    # reliably figure this out with Xcode 8 and above. +    if MacOS.version == "10.12" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "9.0" +      %w[fmemopen futimens open_memstream utimensat].each do |s| +        ENV["ac_cv_func_#{s}"] = "no" +      end +    elsif MacOS.version == "10.11" && MacOS::Xcode.installed? && MacOS::Xcode.version >= "8.0"        %w[basename_r clock_getres clock_gettime clock_settime dirname_r           getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|          ENV["ac_cv_func_#{s}"] = "no" diff --git a/Library/Homebrew/extend/os/mac/hardware/cpu.rb b/Library/Homebrew/extend/os/mac/hardware/cpu.rb index 22d118e1a..b97c280cd 100644 --- a/Library/Homebrew/extend/os/mac/hardware/cpu.rb +++ b/Library/Homebrew/extend/os/mac/hardware/cpu.rb @@ -50,6 +50,8 @@ module Hardware              :broadwell            when 0x37fc219f # Skylake              :skylake +          when 0x0f817246 # Kaby Lake +            :kabylake            else              :dunno            end diff --git a/Library/Homebrew/extend/string.rb b/Library/Homebrew/extend/string.rb index ae7a209db..b96f12994 100644 --- a/Library/Homebrew/extend/string.rb +++ b/Library/Homebrew/extend/string.rb @@ -60,7 +60,7 @@ module StringInreplaceExtension      result    end -  # Looks for Makefile style variable defintions and replaces the +  # Looks for Makefile style variable definitions and replaces the    # value with "new_value", or removes the definition entirely.    def change_make_var!(flag, new_value)      return if gsub!(/^#{Regexp.escape(flag)}[ \t]*=[ \t]*(.*)$/, "#{flag}=#{new_value}", false) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 8cea85a99..d999b9c5f 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -472,7 +472,7 @@ class Formula      return true if devel && tab.devel_version && tab.devel_version < devel.version      if options[:fetch_head] -      return false unless head && head.downloader.is_a?(VCSDownloadStrategy) +      return false unless head&.downloader.is_a?(VCSDownloadStrategy)        downloader = head.downloader        downloader.shutup! unless ARGV.verbose?        downloader.commit_outdated?(version.version.commit) @@ -1115,8 +1115,8 @@ class Formula    # @private    def unlock -    @lock.unlock unless @lock.nil? -    @oldname_lock.unlock unless @oldname_lock.nil? +    @lock&.unlock +    @oldname_lock&.unlock    end    def migration_needed? @@ -1182,7 +1182,8 @@ class Formula    # Returns false if the formula wasn't installed with an alias.    def installed_alias_target_changed?      target = current_installed_alias_target -    target && target.name != name +    return false unless target +    target.name != name    end    # Is this formula the target of an alias used to install an old formula? @@ -1440,13 +1441,14 @@ class Formula    # True if this formula is provided by Homebrew itself    # @private    def core_formula? -    tap && tap.core_tap? +    tap&.core_tap?    end    # True if this formula is provided by external Tap    # @private    def tap? -    tap && !tap.core_tap? +    return false unless tap +    !tap.core_tap?    end    # @private @@ -1525,10 +1527,10 @@ class Formula        "oldname" => oldname,        "aliases" => aliases,        "versions" => { -        "stable" => (stable.version.to_s if stable), +        "stable" => stable&.version.to_s,          "bottle" => bottle ? true : false, -        "devel" => (devel.version.to_s if devel), -        "head" => (head.version.to_s if head), +        "devel" => devel&.version.to_s, +        "head" => head&.version.to_s,        },        "revision" => revision,        "version_scheme" => version_scheme, @@ -1570,7 +1572,7 @@ class Formula          "root_url" => bottle_spec.root_url,        }        bottle_info["files"] = {} -      bottle_spec.collector.keys.each do |os| +      bottle_spec.collector.keys.each do |os| # rubocop:disable Performance/HashEachMethods          checksum = bottle_spec.collector[os]          bottle_info["files"][os] = {            "url" => "#{bottle_spec.root_url}/#{Bottle::Filename.create(self, os, bottle_spec.rebuild)}", @@ -1613,6 +1615,7 @@ class Formula    def run_test      @prefix_returns_versioned_prefix = true      old_home = ENV["HOME"] +    old_java_opts = ENV["_JAVA_OPTIONS"]      old_curl_home = ENV["CURL_HOME"]      old_tmpdir = ENV["TMPDIR"]      old_temp = ENV["TEMP"] @@ -1626,6 +1629,7 @@ class Formula      ENV["TERM"] = "dumb"      ENV["PATH"] = PATH.new(old_path).append(HOMEBREW_PREFIX/"bin")      ENV["HOMEBREW_PATH"] = nil +    ENV["_JAVA_OPTIONS"] = "#{old_java_opts} -Duser.home=#{HOMEBREW_CACHE}/java_cache"      ENV.clear_sensitive_environment! @@ -1646,6 +1650,7 @@ class Formula    ensure      @testpath = nil      ENV["HOME"] = old_home +    ENV["_JAVA_OPTIONS"] = old_java_opts      ENV["CURL_HOME"] = old_curl_home      ENV["TMPDIR"] = old_tmpdir      ENV["TEMP"] = old_temp @@ -1888,11 +1893,13 @@ class Formula        mkdir_p env_home        old_home = ENV["HOME"] +      old_java_opts = ENV["_JAVA_OPTIONS"]        old_curl_home = ENV["CURL_HOME"]        old_path = ENV["HOMEBREW_PATH"]        unless ARGV.interactive?          ENV["HOME"] = env_home +        ENV["_JAVA_OPTIONS"] = "#{old_java_opts} -Duser.home=#{HOMEBREW_CACHE}/java_cache"          ENV["CURL_HOME"] = old_curl_home || old_home        end        ENV["HOMEBREW_PATH"] = nil @@ -1907,6 +1914,7 @@ class Formula          @buildpath = nil          unless ARGV.interactive?            ENV["HOME"] = old_home +          ENV["_JAVA_OPTIONS"] = old_java_opts            ENV["CURL_HOME"] = old_curl_home          end          ENV["HOMEBREW_PATH"] = old_path diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 6c5b8bdab..b4f9db845 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -85,13 +85,12 @@ class FormulaInstaller      return false if @pour_failed      bottle = formula.bottle -    return false unless bottle +    return false if !bottle && !formula.local_bottle_path      return true  if force_bottle?      return false if build_from_source? || build_bottle? || interactive?      return false if ARGV.cc      return false unless options.empty?      return false if formula.bottle_disabled? -    return true  if formula.local_bottle_path      unless formula.pour_bottle?        if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason          opoo <<-EOS.undent @@ -270,7 +269,7 @@ class FormulaInstaller        oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip      end -    if formula.tap && !formula.tap.private? +    unless formula.tap&.private?        action = "#{formula.full_name} #{options}".strip        Utils::Analytics.report_event("install", action) @@ -561,7 +560,7 @@ class FormulaInstaller      end      raise    else -    ignore_interrupts { tmp_keg.rmtree if tmp_keg && tmp_keg.directory? } +    ignore_interrupts { tmp_keg.rmtree if tmp_keg&.directory? }    end    def caveats @@ -604,6 +603,12 @@ class FormulaInstaller      # let's reset Utils.git_available? if we just installed git      Utils.clear_git_available_cache if formula.name == "git" + +    # use installed curl when it's needed and available +    if formula.name == "curl" && +       !DevelopmentTools.curl_handles_most_https_certificates? +      ENV["HOMEBREW_CURL"] = formula.opt_bin/"curl" +    end    ensure      unlock    end diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index 9f79781b4..32b5377a0 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -45,11 +45,15 @@ module Homebrew        @failed == true      end -    attr_writer :raise_deprecation_exceptions +    attr_writer :raise_deprecation_exceptions, :auditing      def raise_deprecation_exceptions?        @raise_deprecation_exceptions == true      end + +    def auditing? +      @auditing == true +    end    end  end diff --git a/Library/Homebrew/gpg.rb b/Library/Homebrew/gpg.rb index f56473df3..de2089dda 100644 --- a/Library/Homebrew/gpg.rb +++ b/Library/Homebrew/gpg.rb @@ -7,8 +7,7 @@ class Gpg        next unless gpg_short_version        gpg_version = Version.create(gpg_short_version.to_s)        @version = gpg_version -      gpg_version == Version.create("2.1") || -        gpg_version == Version.create("2.0") +      gpg_version >= Version.create("2.0")      end    end @@ -38,12 +37,30 @@ class Gpg        Key-Length: 2048        Subkey-Type: RSA        Subkey-Length: 2048 -      Passphrase: ''        Name-Real: Testing        Name-Email: testing@foo.bar        Expire-Date: 1d +      %no-protection        %commit      EOS      system GPG_EXECUTABLE, "--batch", "--gen-key", "batch.gpg"    end + +  def self.cleanup_test_processes! +    odie "No GPG present to test against!" unless available? +    gpgconf = Pathname.new(GPG_EXECUTABLE).parent/"gpgconf" + +    system gpgconf, "--kill", "gpg-agent" +    system gpgconf, "--homedir", "keyrings/live", "--kill", +                                 "gpg-agent" +  end + +  def self.test(path) +    create_test_key(path) +    begin +      yield +    ensure +      cleanup_test_processes! +    end +  end  end diff --git a/Library/Homebrew/install_renamed.rb b/Library/Homebrew/install_renamed.rb index 5e200244f..dc5d4cda8 100644 --- a/Library/Homebrew/install_renamed.rb +++ b/Library/Homebrew/install_renamed.rb @@ -16,12 +16,12 @@ module InstallRenamed      end    end -  def +(path) -    super(path).extend(InstallRenamed) +  def +(other) +    super(other).extend(InstallRenamed)    end -  def /(path) -    super(path).extend(InstallRenamed) +  def /(other) +    super(other).extend(InstallRenamed)    end    private diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 92eab7ad3..677a97c85 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -338,7 +338,7 @@ class Keg        dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") }      when :fish then path/"share/fish/vendor_completions.d"      end -    dir && dir.directory? && !dir.children.empty? +    dir&.directory? && !dir.children.empty?    end    def functions_installed?(shell) diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index ad4c01021..085748632 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -156,7 +156,7 @@ class Keg      libtool_files = []      path.find do |pn| -      next if pn.symlink? || pn.directory? || pn.extname != ".la" +      next if pn.symlink? || pn.directory? || ![".la", ".lai"].include?(pn.extname)        libtool_files << pn      end      libtool_files diff --git a/Library/Homebrew/language/python.rb b/Library/Homebrew/language/python.rb index 0f8e3a4e6..bfec556d0 100644 --- a/Library/Homebrew/language/python.rb +++ b/Library/Homebrew/language/python.rb @@ -23,7 +23,7 @@ module Language          else            homebrew_site_packages(version)          end -        block.call python, version if block +        block&.call python, version        end        ENV["PYTHONPATH"] = original_pythonpath      end diff --git a/Library/Homebrew/locale.rb b/Library/Homebrew/locale.rb index 5e778f3b4..d918e2a2a 100644 --- a/Library/Homebrew/locale.rb +++ b/Library/Homebrew/locale.rb @@ -44,8 +44,6 @@ class Locale        raise ParserError, "'#{value}' does not match #{regex}" unless value =~ regex        instance_variable_set(:"@#{key}", value)      end - -    self    end    def include?(other) diff --git a/Library/Homebrew/manpages/brew-cask.1.md b/Library/Homebrew/manpages/brew-cask.1.md index bfb9cd7a5..715d8fd77 100644 --- a/Library/Homebrew/manpages/brew-cask.1.md +++ b/Library/Homebrew/manpages/brew-cask.1.md @@ -1,5 +1,5 @@  brew-cask(1) - a friendly binary installer for macOS -======================================================== +====================================================  ## SYNOPSIS @@ -85,7 +85,7 @@ names, and other aspects of this manual are still subject to change.      If <token> is given, summarize the staged files associated with the      given Cask. -     +    * `outdated` [--greedy] [--verbose|--quiet] [ <token> ...]:      Without token arguments, display all the installed Casks that have newer      versions available in the tap; otherwise check only the tokens given @@ -101,9 +101,10 @@ names, and other aspects of this manual are still subject to change.      Reinstall the given Cask.    * `search` or `-S` [<text> | /<regexp>/]: -    Without an argument, display all Casks available for install; otherwise -    perform a substring search of known Cask tokens for <text> or, if the -    text is delimited by slashes (/<regexp>/), it is interpreted as a +    Without an argument, display all locally available Casks for install; no +    online search is performed. +    Otherwise perform a substring search of known Cask tokens for <text> or, +    if the text is delimited by slashes (/<regexp>/), it is interpreted as a      Ruby regular expression.    * `style` [--fix] [ <token> ... ]: @@ -255,7 +256,7 @@ Environment variables specific to Homebrew-Cask:                 export HOMEBREW_CASK_OPTS='--appdir=~/Applications --fontdir=/Library/Fonts'  Other environment variables: -            +    * `SUDO_ASKPASS`:      When this variable is set, Homebrew-Cask will call `sudo`(8) with the `-A` option. diff --git a/Library/Homebrew/missing_formula.rb b/Library/Homebrew/missing_formula.rb index c3625c8bb..a3d182a2b 100644 --- a/Library/Homebrew/missing_formula.rb +++ b/Library/Homebrew/missing_formula.rb @@ -31,7 +31,7 @@ module Homebrew              #{Formatter.url("https://pip.readthedocs.io/en/stable/installing/")}            EOS          when "pil" then <<-EOS.undent -          Instead of PIL, consider `pip install pillow` or `brew install Homebrew/science/pillow`. +          Instead of PIL, consider `pip2 install pillow`.            EOS          when "macruby" then <<-EOS.undent            MacRuby is not packaged and is on an indefinite development hiatus. @@ -53,7 +53,7 @@ module Homebrew            ruin SSH's security.            EOS          when "gsutil" then <<-EOS.undent -          Install gsutil with `pip install gsutil` +          Install gsutil with `pip2 install gsutil`            EOS          when "gfortran" then <<-EOS.undent            GNU Fortran is now provided as part of GCC, and can be installed with: diff --git a/Library/Homebrew/options.rb b/Library/Homebrew/options.rb index 9f1253531..05dd643ff 100644 --- a/Library/Homebrew/options.rb +++ b/Library/Homebrew/options.rb @@ -69,29 +69,29 @@ class Options      @options.each(*args, &block)    end -  def <<(o) -    @options << o +  def <<(other) +    @options << other      self    end -  def +(o) -    self.class.new(@options + o) +  def +(other) +    self.class.new(@options + other)    end -  def -(o) -    self.class.new(@options - o) +  def -(other) +    self.class.new(@options - other)    end -  def &(o) -    self.class.new(@options & o) +  def &(other) +    self.class.new(@options & other)    end -  def |(o) -    self.class.new(@options | o) +  def |(other) +    self.class.new(@options | other)    end -  def *(arg) -    @options.to_a * arg +  def *(other) +    @options.to_a * other    end    def empty? diff --git a/Library/Homebrew/os/mac.rb b/Library/Homebrew/os/mac.rb index 5074665fc..15c301f99 100644 --- a/Library/Homebrew/os/mac.rb +++ b/Library/Homebrew/os/mac.rb @@ -11,7 +11,7 @@ module OS    module Mac      module_function -    ::MacOS = self # rubocop:disable Style/ConstantName +    ::MacOS = self # rubocop:disable Naming/ConstantName      raise "Loaded OS::Mac on generic OS!" if ENV["HOMEBREW_TEST_GENERIC_OS"] @@ -34,12 +34,12 @@ module OS      def prerelease?        # TODO: bump version when new OS is released -      version >= "10.13" +      version >= "10.14"      end      def outdated_release?        # TODO: bump version when new OS is released -      version < "10.10" +      version < "10.11"      end      def cat @@ -104,7 +104,7 @@ module OS      # Returns the path to an SDK or nil, following the rules set by #sdk.      def sdk_path(v = nil)        s = sdk(v) -      s.path unless s.nil? +      s&.path      end      # See these issues for some history: @@ -129,8 +129,8 @@ module OS          paths << path if path.exist?        end -      # Finally, some users make their MacPorts or Fink directorie -      # read-only in order to try out Homebrew, but this doens't work as +      # Finally, some users make their MacPorts or Fink directories +      # read-only in order to try out Homebrew, but this doesn't work as        # some build scripts error out when trying to read from these now        # unreadable paths.        %w[/sw /opt/local].map { |p| Pathname.new(p) }.each do |path| diff --git a/Library/Homebrew/os/mac/linkage_checker.rb b/Library/Homebrew/os/mac/linkage_checker.rb index 786827852..f6aa4c2f3 100644 --- a/Library/Homebrew/os/mac/linkage_checker.rb +++ b/Library/Homebrew/os/mac/linkage_checker.rb @@ -5,7 +5,7 @@ require "formula"  class LinkageChecker    attr_reader :keg, :formula    attr_reader :brewed_dylibs, :system_dylibs, :broken_dylibs, :variable_dylibs -  attr_reader :undeclared_deps, :reverse_links +  attr_reader :undeclared_deps, :unnecessary_deps, :reverse_links    def initialize(keg, formula = nil)      @keg = keg @@ -16,6 +16,7 @@ class LinkageChecker      @variable_dylibs = Set.new      @undeclared_deps = []      @reverse_links = Hash.new { |h, k| h[k] = Set.new } +    @unnecessary_deps = []      check_dylibs    end @@ -51,7 +52,7 @@ class LinkageChecker        end      end -    @undeclared_deps = check_undeclared_deps if formula +    @undeclared_deps, @unnecessary_deps = check_undeclared_deps if formula    end    def check_undeclared_deps @@ -77,6 +78,12 @@ class LinkageChecker          a <=> b        end      end +    unnecessary_deps = declared_dep_names.reject do |full_name| +      name = full_name.split("/").last +      next true if Formula[name].bin.directory? +      @brewed_dylibs.keys.map { |x| x.split("/").last }.include?(name) +    end +    [undeclared_deps, unnecessary_deps]    end    def display_normal_output @@ -84,7 +91,8 @@ class LinkageChecker      display_items "Homebrew libraries", @brewed_dylibs      display_items "Variable-referenced libraries", @variable_dylibs      display_items "Missing libraries", @broken_dylibs -    display_items "Possible undeclared dependencies", @undeclared_deps +    display_items "Undeclared dependencies with linkage", @undeclared_deps +    display_items "Dependencies with no linkage", @unnecessary_deps    end    def display_reverse_output @@ -102,6 +110,7 @@ class LinkageChecker    def display_test_output      display_items "Missing libraries", @broken_dylibs +    display_items "Possible unnecessary dependencies", @unnecessary_deps      puts "No broken dylib links" if @broken_dylibs.empty?    end @@ -113,6 +122,10 @@ class LinkageChecker      !@undeclared_deps.empty?    end +  def unnecessary_deps? +    !@unnecessary_deps.empty? +  end +    private    # Whether or not dylib is a harmless broken link, meaning that it's diff --git a/Library/Homebrew/os/mac/version.rb b/Library/Homebrew/os/mac/version.rb index 89016bb4d..db6dfcb1a 100644 --- a/Library/Homebrew/os/mac/version.rb +++ b/Library/Homebrew/os/mac/version.rb @@ -12,6 +12,7 @@ module OS          mountain_lion: "10.8",          lion: "10.7",          snow_leopard: "10.6", +        leopard_64: "10.5",          leopard: "10.5",          tiger: "10.4",        }.freeze diff --git a/Library/Homebrew/os/mac/xcode.rb b/Library/Homebrew/os/mac/xcode.rb index 6f7deaa10..59e7026bd 100644 --- a/Library/Homebrew/os/mac/xcode.rb +++ b/Library/Homebrew/os/mac/xcode.rb @@ -216,7 +216,7 @@ module OS          # on the older supported platform for that Xcode release, i.e there's no          # CLT package for 10.11 that contains the Clang version from Xcode 8.          case MacOS.version -        when "10.13" then "900.0.34.1" +        when "10.13" then "900.0.37"          when "10.12" then "802.0.42"          when "10.11" then "800.0.42.1"          when "10.10" then "700.1.81" diff --git a/Library/Homebrew/pkg_version.rb b/Library/Homebrew/pkg_version.rb index 761a349fd..b68d78cf8 100644 --- a/Library/Homebrew/pkg_version.rb +++ b/Library/Homebrew/pkg_version.rb @@ -23,7 +23,7 @@ class PkgVersion    end    def to_s -    if revision > 0 +    if revision.positive?        "#{version}_#{revision}"      else        version.to_s diff --git a/Library/Homebrew/requirements/gpg2_requirement.rb b/Library/Homebrew/requirements/gpg2_requirement.rb index d570983eb..ebdd71f6e 100644 --- a/Library/Homebrew/requirements/gpg2_requirement.rb +++ b/Library/Homebrew/requirements/gpg2_requirement.rb @@ -5,8 +5,8 @@ class GPG2Requirement < Requirement    fatal true    default_formula "gnupg" -  # GPGTools installs GnuPG 2.0.x as a vanilla `gpg` symlink -  # pointing to `gpg2`. Homebrew install 2.1.x as a non-symlink `gpg`. -  # We support both the 2.0.x "stable" and 2.1.x "modern" series here. +  # GPGTools installs GnuPG 2.0.x as a `gpg` symlink pointing +  # to `gpg2`. Our `gnupg` installs only a non-symlink `gpg`. +  # The aim is to retain support for any version above 2.0.    satisfy(build_env: false) { Gpg.gpg || Gpg.gpg2 }  end diff --git a/Library/Homebrew/requirements/java_requirement.rb b/Library/Homebrew/requirements/java_requirement.rb index ab6dca51d..de3a33eb4 100644 --- a/Library/Homebrew/requirements/java_requirement.rb +++ b/Library/Homebrew/requirements/java_requirement.rb @@ -69,14 +69,14 @@ class JavaRequirement < Requirement      rescue FormulaUnavailableError        nil      end -    javas << jdk.bin/"java" if jdk && jdk.installed? +    javas << jdk.bin/"java" if jdk&.installed?      javas << which("java")      javas    end    def preferred_java      possible_javas.detect do |java| -      next false unless java && java.executable? +      next false unless java&.executable?        next true unless @version        next true if satisfies_version(java)      end diff --git a/Library/Homebrew/requirements/ruby_requirement.rb b/Library/Homebrew/requirements/ruby_requirement.rb index acc655924..a9ec8c42d 100644 --- a/Library/Homebrew/requirements/ruby_requirement.rb +++ b/Library/Homebrew/requirements/ruby_requirement.rb @@ -41,9 +41,7 @@ class RubyRequirement < Requirement    def rubies      rubies = which_all("ruby")      ruby_formula = Formula["ruby"] -    if ruby_formula && ruby_formula.installed? -      rubies.unshift ruby_formula.bin/"ruby" -    end +    rubies.unshift ruby_formula.bin/"ruby" if ruby_formula&.installed?      rubies.uniq    end diff --git a/Library/Homebrew/rubocops.rb b/Library/Homebrew/rubocops.rb index b1144e075..8dc49bb45 100644 --- a/Library/Homebrew/rubocops.rb +++ b/Library/Homebrew/rubocops.rb @@ -11,3 +11,4 @@ require_relative "./rubocops/conflicts_cop"  require_relative "./rubocops/options_cop"  require_relative "./rubocops/urls_cop"  require_relative "./rubocops/lines_cop" +require_relative "./rubocops/class_cop" diff --git a/Library/Homebrew/rubocops/class_cop.rb b/Library/Homebrew/rubocops/class_cop.rb new file mode 100644 index 000000000..dad81abfc --- /dev/null +++ b/Library/Homebrew/rubocops/class_cop.rb @@ -0,0 +1,41 @@ +require_relative "./extend/formula_cop" + +module RuboCop +  module Cop +    module FormulaAudit +      class ClassName < FormulaCop +        DEPRECATED_CLASSES = %w[ +          GithubGistFormula +          ScriptFileFormula +          AmazonWebServicesFormula +        ].freeze + +        def audit_formula(_node, _class_node, parent_class_node, _body_node) +          parent_class = class_name(parent_class_node) +          return unless DEPRECATED_CLASSES.include?(parent_class) +          problem "#{parent_class} is deprecated, use Formula instead" +        end + +        private + +        def autocorrect(node) +          lambda do |corrector| +            corrector.replace(node.source_range, "Formula") +          end +        end +      end +    end + +    module FormulaAuditStrict +      # - `test do ..end` should be defined in the formula +      class Test < FormulaCop +        MSG = "A `test do` test block should be added".freeze + +        def audit_formula(_node, _class_node, _parent_class_node, body_node) +          return if find_block(body_node, :test) +          problem MSG +        end +      end +    end +  end +end diff --git a/Library/Homebrew/rubocops/components_order_cop.rb b/Library/Homebrew/rubocops/components_order_cop.rb index f1179d9a4..3bf2ede16 100644 --- a/Library/Homebrew/rubocops/components_order_cop.rb +++ b/Library/Homebrew/rubocops/components_order_cop.rb @@ -87,8 +87,8 @@ module RuboCop            # preceding_comp_arr: array containing components of same type            order_idx, curr_p_idx, preceding_comp_arr = get_state(node1) -          # curr_p_idx > 0 means node1 needs to be grouped with its own kind -          if curr_p_idx > 0 +          # curr_p_idx.positive? means node1 needs to be grouped with its own kind +          if curr_p_idx.positive?              node2 = preceding_comp_arr[curr_p_idx - 1]              indentation = " " * (start_column(node2) - line_start_column(node2))              line_breaks = node2.multiline? ? "\n\n" : "\n" diff --git a/Library/Homebrew/rubocops/conflicts_cop.rb b/Library/Homebrew/rubocops/conflicts_cop.rb index c1b801559..6f05d0567 100644 --- a/Library/Homebrew/rubocops/conflicts_cop.rb +++ b/Library/Homebrew/rubocops/conflicts_cop.rb @@ -18,7 +18,7 @@ module RuboCop          def audit_formula(_node, _class_node, _parent_class_node, body)            return unless versioned_formula? -          problem MSG if !formula_file_name.start_with?(*WHITELIST) && +          problem MSG if !@formula_name.start_with?(*WHITELIST) &&                           method_called_ever?(body, :conflicts_with)          end        end diff --git a/Library/Homebrew/rubocops/extend/formula_cop.rb b/Library/Homebrew/rubocops/extend/formula_cop.rb index 7844f7bf2..47dd2d803 100644 --- a/Library/Homebrew/rubocops/extend/formula_cop.rb +++ b/Library/Homebrew/rubocops/extend/formula_cop.rb @@ -4,16 +4,17 @@ require_relative "../../extend/string"  module RuboCop    module Cop      class FormulaCop < Cop +      attr_accessor :file_path        @registry = Cop.registry        # This method is called by RuboCop and is the main entry point        def on_class(node) -        file_path = processed_source.buffer.name -        return unless file_path_allowed?(file_path) +        @file_path = processed_source.buffer.name +        return unless file_path_allowed?          return unless formula_class?(node)          return unless respond_to?(:audit_formula)          class_node, parent_class_node, @body = *node -        @formula_name = class_name(class_node) +        @formula_name = Pathname.new(@file_path).basename(".rb").to_s          audit_formula(node, class_node, parent_class_node, @body)        end @@ -100,19 +101,22 @@ module RuboCop        def find_method_with_args(node, method_name, *args)          methods = find_every_method_call_by_name(node, method_name)          methods.each do |method| -          next unless parameters_passed?(method, *args) -          yield method +          yield method if parameters_passed?(method, *args)          end        end        # Matches a method with a receiver,        # EX: to match `Formula.factory(name)`        # call `find_instance_method_call(node, "Formula", :factory)` +      # EX: to match `build.head?` +      # call `find_instance_method_call(node, :build, :head?)`        # yields to a block with matching method node        def find_instance_method_call(node, instance, method_name)          methods = find_every_method_call_by_name(node, method_name)          methods.each do |method| -          next unless method.receiver && method.receiver.const_name == instance +          next if method.receiver.nil? +          next if method.receiver.const_name != instance && +                  method.receiver.method_name != instance            @offense_source_range = method.source_range            @offensive_node = method            yield method @@ -400,12 +404,7 @@ module RuboCop        # Returns true if the formula is versioned        def versioned_formula? -        formula_file_name.include?("@") || @formula_name.match(/AT\d+/) -      end - -      # Returns filename of the formula without the extension -      def formula_file_name -        File.basename(processed_source.buffer.name, ".rb") +        @formula_name.include?("@")        end        # Returns printable component name @@ -414,6 +413,12 @@ module RuboCop          method_name(component_node) if component_node.def_type?        end +      # Returns the formula tap +      def formula_tap +        return unless match_obj = @file_path.match(%r{/(homebrew-\w+)/}) +        match_obj[1] +      end +        def problem(msg)          add_offense(@offensive_node, @offense_source_range, msg)        end @@ -422,14 +427,21 @@ module RuboCop        def formula_class?(node)          _, class_node, = *node -        class_node && string_content(class_node) == "Formula" +        class_names = %w[ +          Formula +          GithubGistFormula +          ScriptFileFormula +          AmazonWebServicesFormula +        ] + +        class_node && class_names.include?(string_content(class_node))        end -      def file_path_allowed?(file_path) +      def file_path_allowed?          paths_to_exclude = [%r{/Library/Homebrew/compat/},                              %r{/Library/Homebrew/test/}] -        return true if file_path.nil? # file_path is nil when source is directly passed to the cop eg., in specs -        file_path !~ Regexp.union(paths_to_exclude) +        return true if @file_path.nil? # file_path is nil when source is directly passed to the cop eg., in specs +        @file_path !~ Regexp.union(paths_to_exclude)        end      end    end diff --git a/Library/Homebrew/rubocops/lines_cop.rb b/Library/Homebrew/rubocops/lines_cop.rb index ed50ba49c..01b13585c 100644 --- a/Library/Homebrew/rubocops/lines_cop.rb +++ b/Library/Homebrew/rubocops/lines_cop.rb @@ -21,7 +21,7 @@ module RuboCop            begin_pos = start_column(parent_class_node)            end_pos = end_column(class_node)            return unless begin_pos-end_pos != 3 -          problem "Use a space in class inheritance: class #{@formula_name} < #{class_name(parent_class_node)}" +          problem "Use a space in class inheritance: class #{class_name(class_node)} < #{class_name(parent_class_node)}"          end        end @@ -67,7 +67,72 @@ module RuboCop              next unless block_arg.source.size>1              problem "\"inreplace <filenames> do |s|\" is preferred over \"|#{block_arg.source}|\"."            end + +          [:rebuild, :version_scheme].each do |method_name| +            find_method_with_args(body_node, method_name, 0) do +              problem "'#{method_name} 0' should be removed" +            end +          end + +          [:mac?, :linux?].each do |method_name| +            next unless formula_tap == "homebrew-core" +            find_instance_method_call(body_node, "OS", method_name) do |check| +              problem "Don't use #{check.source}; Homebrew/core only supports macOS" +            end +          end + +          find_method_with_args(body_node, :fails_with, :llvm) do +            problem "'fails_with :llvm' is now a no-op so should be removed" +          end + +          find_method_with_args(body_node, :system, /^(otool|install_name_tool|lipo)$/) do +            next if @formula_name == "cctools" +            problem "Use ruby-macho instead of calling #{@offensive_node.source}" +          end + +          find_every_method_call_by_name(body_node, :system).each do |method_node| +            # Skip Kibana: npm cache edge (see formula for more details) +            next if @formula_name =~ /^kibana(\@\d+(\.\d+)?)?$/ +            first_param, second_param = parameters(method_node) +            next if !node_equals?(first_param, "npm") || +                    !node_equals?(second_param, "install") +            offending_node(method_node) +            problem "Use Language::Node for npm install args" unless languageNodeModule?(method_node) +          end + +          if find_method_def(body_node, :test) +            problem "Use new-style test definitions (test do)" +          end + +          if find_method_def(body_node, :options) +            problem "Use new-style option definitions" +          end + +          find_method_with_args(body_node, :skip_clean, :all) do +            problem <<-EOS.undent.chomp +              `skip_clean :all` is deprecated; brew no longer strips symbols +                      Pass explicit paths to prevent Homebrew from removing empty folders. +            EOS +          end + +          find_instance_method_call(body_node, :build, :universal?) do +            next if @formula_name == "wine" +            problem "macOS has been 64-bit only since 10.6 so build.universal? is deprecated." +          end + +          find_instance_method_call(body_node, "ENV", :universal_binary) do +            problem "macOS has been 64-bit only since 10.6 so ENV.universal_binary is deprecated." +          end + +          find_instance_method_call(body_node, "ENV", :x11) do +            problem 'Use "depends_on :x11" instead of "ENV.x11"' +          end          end + +        # Node Pattern search for Language::Node +        def_node_search :languageNodeModule?, <<-EOS.undent +          (const (const nil :Language) :Node) +        EOS        end      end    end diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 8c662857e..7d23e5966 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -167,7 +167,7 @@ class Sandbox      def add_rule(rule)        s = "(" -      s << ((rule[:allow]) ? "allow" : "deny") +      s << (rule[:allow] ? "allow" : "deny")        s << " #{rule[:operation]}"        s << " (#{rule[:filter]})" if rule[:filter]        s << " (with #{rule[:modifier]})" if rule[:modifier] diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index 49d818f0f..dd6026fcf 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -267,7 +267,7 @@ class Bottle      end      def suffix -      s = (rebuild > 0) ? ".#{rebuild}" : "" +      s = rebuild.positive? ? ".#{rebuild}" : ""        ".bottle#{s}.tar.gz"      end    end diff --git a/Library/Homebrew/system_config.rb b/Library/Homebrew/system_config.rb index 5663295c2..3e1acd4ff 100644 --- a/Library/Homebrew/system_config.rb +++ b/Library/Homebrew/system_config.rb @@ -134,7 +134,7 @@ class SystemConfig        # java_home doesn't exist on all macOSs; it might be missing on older versions.        return "N/A" unless File.executable? "/usr/libexec/java_home" -      java_xml = Utils.popen_read("/usr/libexec/java_home", "--xml", "--failfast") +      java_xml = Utils.popen_read("/usr/libexec/java_home", "--xml", "--failfast", err: :close)        return "N/A" unless $CHILD_STATUS.success?        javas = []        REXML::XPath.each(REXML::Document.new(java_xml), "//key[text()='JVMVersion']/following-sibling::string") do |item| diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb index e7df88356..af19cabe6 100644 --- a/Library/Homebrew/tab.rb +++ b/Library/Homebrew/tab.rb @@ -324,7 +324,7 @@ class Tab < OpenStruct        "poured_from_bottle" => poured_from_bottle,        "installed_as_dependency" => installed_as_dependency,        "installed_on_request" => installed_on_request, -      "changed_files" => changed_files && changed_files.map(&:to_s), +      "changed_files" => changed_files&.map(&:to_s),        "time" => time,        "source_modified_time" => source_modified_time.to_i,        "HEAD" => self.HEAD, diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index acf2d196b..fff58b754 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -557,11 +557,9 @@ class CoreTap < Tap      @instance ||= new    end -  def self.ensure_installed!(options = {}) +  def self.ensure_installed!      return if instance.installed? -    args = ["tap", instance.name] -    args << "-q" if options.fetch(:quiet, true) -    safe_system HOMEBREW_BREW_FILE, *args +    safe_system HOMEBREW_BREW_FILE, "tap", instance.name    end    # @private @@ -656,6 +654,5 @@ class TapConfig      tap.path.cd do        safe_system "git", "config", "--local", "--replace-all", "homebrew.#{key}", value.to_s      end -    value    end  end diff --git a/Library/Homebrew/test/cask/artifact/alt_target_spec.rb b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb index 9e8d83bb4..02be796ed 100644 --- a/Library/Homebrew/test/cask/artifact/alt_target_spec.rb +++ b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb @@ -3,7 +3,7 @@ describe Hbc::Artifact::App, :cask do      let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-alt-target.rb") }      let(:install_phase) { -      -> { Hbc::Artifact::App.new(cask).install_phase } +      -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }      }      let(:source_path) { cask.staged_path.join("Caffeine.app") } diff --git a/Library/Homebrew/test/cask/artifact/app_spec.rb b/Library/Homebrew/test/cask/artifact/app_spec.rb index 0add472e2..f67ffd31b 100644 --- a/Library/Homebrew/test/cask/artifact/app_spec.rb +++ b/Library/Homebrew/test/cask/artifact/app_spec.rb @@ -2,13 +2,13 @@ describe Hbc::Artifact::App, :cask do    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") }    let(:command) { Hbc::SystemCommand }    let(:force) { false } -  let(:app) { Hbc::Artifact::App.new(cask, command: command, force: force) } +  let(:app) { described_class.for_cask(cask).first }    let(:source_path) { cask.staged_path.join("Caffeine.app") }    let(:target_path) { Hbc.appdir.join("Caffeine.app") } -  let(:install_phase) { app.install_phase } -  let(:uninstall_phase) { app.uninstall_phase } +  let(:install_phase) { app.install_phase(command: command, force: force) } +  let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) }    before(:each) do      InstallHelper.install_without_artifacts(cask) @@ -105,8 +105,8 @@ describe Hbc::Artifact::App, :cask do          describe "target is user-owned but contains read-only files" do            before(:each) do -            system "/usr/bin/touch", "--", "#{target_path}/foo" -            system "/bin/chmod", "--", "0555", target_path +            FileUtils.touch "#{target_path}/foo" +            FileUtils.chmod 0555, target_path            end            it "overwrites the existing app" do @@ -138,7 +138,7 @@ describe Hbc::Artifact::App, :cask do            end            after(:each) do -            system "/bin/chmod", "--", "0755", target_path +            FileUtils.chmod 0755, target_path            end          end        end @@ -206,8 +206,8 @@ describe Hbc::Artifact::App, :cask do    end    describe "summary" do -    let(:description) { app.summary[:english_description] } -    let(:contents) { app.summary[:contents] } +    let(:description) { app.class.english_description } +    let(:contents) { app.summarize_installed }      it "returns the correct english_description" do        expect(description).to eq("Apps") @@ -217,14 +217,13 @@ describe Hbc::Artifact::App, :cask do        it "returns the path to the app" do          install_phase -        expect(contents).to eq(["#{target_path} (#{target_path.abv})"]) +        expect(contents).to eq("#{target_path} (#{target_path.abv})")        end      end      describe "app is missing" do        it "returns a warning and the supposed path to the app" do -        expect(contents.size).to eq(1) -        expect(contents[0]).to match(/.*Missing App.*: #{target_path}/) +        expect(contents).to match(/.*Missing App.*: #{target_path}/)        end      end    end diff --git a/Library/Homebrew/test/cask/artifact/binary_spec.rb b/Library/Homebrew/test/cask/artifact/binary_spec.rb index ce00e3935..5ffaca861 100644 --- a/Library/Homebrew/test/cask/artifact/binary_spec.rb +++ b/Library/Homebrew/test/cask/artifact/binary_spec.rb @@ -26,7 +26,8 @@ describe Hbc::Artifact::Binary, :cask do    end    it "links the binary to the proper directory" do -    Hbc::Artifact::Binary.new(cask).install_phase +    described_class.for_cask(cask) +      .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }      expect(expected_path).to be_a_symlink      expect(expected_path.readlink).to exist @@ -45,7 +46,8 @@ describe Hbc::Artifact::Binary, :cask do        expect(FileUtils).to receive(:chmod)          .with("+x", cask.staged_path.join("naked_non_executable")).and_call_original -      Hbc::Artifact::Binary.new(cask).install_phase +      described_class.for_cask(cask) +      .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(expected_path).to be_a_symlink        expect(expected_path.readlink).to be_executable @@ -56,7 +58,8 @@ describe Hbc::Artifact::Binary, :cask do      FileUtils.touch expected_path      expect { -      Hbc::Artifact::Binary.new(cask).install_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }      }.to raise_error(Hbc::CaskError)      expect(expected_path).not_to be :symlink? @@ -65,7 +68,8 @@ describe Hbc::Artifact::Binary, :cask do    it "clobbers an existing symlink" do      expected_path.make_symlink("/tmp") -    Hbc::Artifact::Binary.new(cask).install_phase +    described_class.for_cask(cask) +      .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }      expect(File.readlink(expected_path)).not_to eq("/tmp")    end @@ -73,7 +77,8 @@ describe Hbc::Artifact::Binary, :cask do    it "creates parent directory if it doesn't exist" do      FileUtils.rmdir Hbc.binarydir -    Hbc::Artifact::Binary.new(cask).install_phase +    described_class.for_cask(cask) +      .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }      expect(expected_path.exist?).to be true    end @@ -86,8 +91,10 @@ describe Hbc::Artifact::Binary, :cask do      }      it "links the binary to the proper directory" do -      Hbc::Artifact::App.new(cask).install_phase -      Hbc::Artifact::Binary.new(cask).install_phase +      Hbc::Artifact::App.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } +      described_class.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(expected_path).to be_a_symlink        expect(expected_path.readlink).to exist diff --git a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb index cb2ef9850..bec8c2742 100644 --- a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb +++ b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb @@ -2,7 +2,7 @@ describe Hbc::Artifact::Artifact, :cask do    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact.rb") }    let(:install_phase) { -    -> { Hbc::Artifact::Artifact.new(cask).install_phase } +    -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }    }    let(:source_path) { cask.staged_path.join("Caffeine.app") } @@ -12,11 +12,11 @@ describe Hbc::Artifact::Artifact, :cask do      InstallHelper.install_without_artifacts(cask)    end -  describe "with no target" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") } - -    it "fails to install with no target" do -      expect(install_phase).to raise_error(Hbc::CaskInvalidError) +  context "without target" do +    it "fails to load" do +      expect { +        Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") +      }.to raise_error(Hbc::CaskInvalidError, /target required for Generic Artifact/)      end    end diff --git a/Library/Homebrew/test/cask/artifact/nested_container_spec.rb b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb index be7ba5ff8..41d143764 100644 --- a/Library/Homebrew/test/cask/artifact/nested_container_spec.rb +++ b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb @@ -5,7 +5,8 @@ describe Hbc::Artifact::NestedContainer, :cask do          InstallHelper.install_without_artifacts(c)        end -      Hbc::Artifact::NestedContainer.new(cask).install_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(cask.staged_path.join("MyNestedApp.app")).to be_a_directory      end diff --git a/Library/Homebrew/test/cask/artifact/pkg_spec.rb b/Library/Homebrew/test/cask/artifact/pkg_spec.rb index 3e62616ea..c6a45c49a 100644 --- a/Library/Homebrew/test/cask/artifact/pkg_spec.rb +++ b/Library/Homebrew/test/cask/artifact/pkg_spec.rb @@ -8,7 +8,7 @@ describe Hbc::Artifact::Pkg, :cask do    describe "install_phase" do      it "runs the system installer on the specified pkgs" do -      pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command) +      pkg = described_class.for_cask(cask).first        expect(fake_system_command).to receive(:run!).with(          "/usr/sbin/installer", @@ -17,7 +17,7 @@ describe Hbc::Artifact::Pkg, :cask do          print_stdout: true,        ) -      pkg.install_phase +      pkg.install_phase(command: fake_system_command)      end    end @@ -25,7 +25,7 @@ describe Hbc::Artifact::Pkg, :cask do      let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-choices.rb") }      it "passes the choice changes xml to the system installer" do -      pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command) +      pkg = described_class.for_cask(cask).first        file = double(path: Pathname.new("/tmp/choices.xml")) @@ -57,7 +57,7 @@ describe Hbc::Artifact::Pkg, :cask do          print_stdout: true,        ) -      pkg.install_phase +      pkg.install_phase(command: fake_system_command)      end    end  end diff --git a/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb index 51b1431f0..4a44bb59b 100644 --- a/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb +++ b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb @@ -11,7 +11,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do          end        end -      described_class.new(cask).install_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(called).to be true        expect(yielded_arg).to be_kind_of(Hbc::DSL::Postflight) @@ -30,7 +31,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do          end        end -      described_class.new(cask).uninstall_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.uninstall_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(called).to be true        expect(yielded_arg).to be_kind_of(Hbc::DSL::UninstallPostflight) diff --git a/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb index b13c4ab9d..d7d4e72d9 100644 --- a/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb +++ b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb @@ -11,7 +11,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do          end        end -      described_class.new(cask).install_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(called).to be true        expect(yielded_arg).to be_kind_of Hbc::DSL::Preflight @@ -30,7 +31,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do          end        end -      described_class.new(cask).uninstall_phase +      described_class.for_cask(cask) +        .each { |artifact| artifact.uninstall_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect(called).to be true        expect(yielded_arg).to be_kind_of Hbc::DSL::UninstallPreflight diff --git a/Library/Homebrew/test/cask/artifact/suite_spec.rb b/Library/Homebrew/test/cask/artifact/suite_spec.rb index 8c217a9e0..2f913fecc 100644 --- a/Library/Homebrew/test/cask/artifact/suite_spec.rb +++ b/Library/Homebrew/test/cask/artifact/suite_spec.rb @@ -1,7 +1,9 @@  describe Hbc::Artifact::Suite, :cask do    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-suite.rb") } -  let(:install_phase) { -> { Hbc::Artifact::Suite.new(cask).install_phase } } +  let(:install_phase) { +    -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } } +  }    let(:target_path) { Hbc.appdir.join("Caffeine") }    let(:source_path) { cask.staged_path.join("Caffeine") } diff --git a/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb index a1fdd3b74..f6e0d3c97 100644 --- a/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb +++ b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb @@ -3,7 +3,7 @@ describe Hbc::Artifact::App, :cask do      let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-correct.rb") }      let(:install_phase) { -      -> { Hbc::Artifact::App.new(cask).install_phase } +      -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }      }      let(:source_path_mini) { cask.staged_path.join("Caffeine Mini.app") } diff --git a/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb index 8cd0b1e41..d6a8393da 100644 --- a/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb +++ b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb @@ -2,7 +2,7 @@ describe Hbc::Artifact::Zap, :cask do    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }    let(:zap_artifact) { -    Hbc::Artifact::Zap.new(cask) +    described_class.for_cask(cask).first    }    before(:each) do diff --git a/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb b/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb index 0e522bc21..06eec4a01 100644 --- a/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb +++ b/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb @@ -1,12 +1,12 @@  shared_examples "#uninstall_phase or #zap_phase" do -  let(:artifact_name) { described_class.artifact_name } -  let(:artifact) { described_class.new(cask, command: fake_system_command) } +  let(:artifact_dsl_key) { described_class.dsl_key } +  let(:artifact) { described_class.for_cask(cask).first }    let(:fake_system_command) { Hbc::FakeSystemCommand } -  subject { artifact.public_send(:"#{artifact_name}_phase") } +  subject { artifact.public_send(:"#{artifact_dsl_key}_phase", command: fake_system_command) }    context "using :launchctl" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-launchctl.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-launchctl.rb") }      let(:launchctl_list_cmd) { %w[/bin/launchctl list my.fancy.package.service] }      let(:launchctl_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] }      let(:unknown_response) { "launchctl list returned unknown response\n" } @@ -61,7 +61,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    context "using :pkgutil" do      let(:fake_system_command) { class_double(Hbc::SystemCommand) } -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-pkgutil.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-pkgutil.rb") }      let(:main_pkg_id) { "my.fancy.package.main" }      let(:agent_pkg_id) { "my.fancy.package.agent" } @@ -85,7 +85,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    end    context "using :kext" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-kext.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-kext.rb") }      let(:kext_id) { "my.fancy.package.kernelextension" }      it "is supported" do @@ -110,7 +110,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    end    context "using :quit" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-quit.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-quit.rb") }      let(:bundle_id) { "my.fancy.package.app" }      let(:quit_application_script) do        %Q(tell application id "#{bundle_id}" to quit) @@ -130,7 +130,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    end    context "using :signal" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-signal.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-signal.rb") }      let(:bundle_id) { "my.fancy.package.app" }      let(:signals) { %w[TERM KILL] }      let(:unix_pids) { [12_345, 67_890] } @@ -170,10 +170,10 @@ shared_examples "#uninstall_phase or #zap_phase" do        end        let(:fake_system_command) { Hbc::NeverSudoSystemCommand } -      let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-#{directive}.rb") } +      let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-#{directive}.rb") }        before(:each) do -        allow_any_instance_of(Hbc::Artifact::UninstallBase).to receive(:trash_paths) +        allow_any_instance_of(Hbc::Artifact::AbstractUninstall).to receive(:trash_paths)            .and_wrap_original do |method, *args|              result = method.call(*args)              FileUtils.rm_rf result.stdout.split("\0") @@ -196,7 +196,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    context "using :rmdir" do      let(:fake_system_command) { Hbc::NeverSudoSystemCommand } -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-rmdir.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-rmdir.rb") }      let(:empty_directory) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") }      let(:ds_store) { empty_directory.join(".DS_Store") } @@ -223,7 +223,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    [:script, :early_script].each do |script_type|      context "using #{script_type.inspect}" do        let(:fake_system_command) { Hbc::NeverSudoSystemCommand } -      let(:token) { "with-#{artifact_name}-#{script_type}".tr("_", "-") } +      let(:token) { "with-#{artifact_dsl_key}-#{script_type}".tr("_", "-") }        let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/#{token}.rb") }        let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") } @@ -250,7 +250,7 @@ shared_examples "#uninstall_phase or #zap_phase" do    end    context "using :login_item" do -    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-login-item.rb") } +    let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-login-item.rb") }      it "is supported" do        Hbc::FakeSystemCommand.expects_command( diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index ddc773e3e..7e140acb2 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -265,19 +265,14 @@ describe Hbc::Audit, :cask do      end      describe "generic artifact checks" do -      context "with no target" do -        let(:cask_token) { "generic-artifact-no-target" } -        it { is_expected.to fail_with(/target required for generic artifact/) } -      end -        context "with relative target" do          let(:cask_token) { "generic-artifact-relative-target" } -        it { is_expected.to fail_with(/target must be absolute path for generic artifact/) } +        it { is_expected.to fail_with(/target must be absolute path for Generic Artifact/) }        end        context "with absolute target" do          let(:cask_token) { "generic-artifact-absolute-target" } -        it { should_not fail_with(/target required for generic artifact/) } +        it { should_not fail_with(/target required for Generic Artifact/) }        end      end diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index a6ecc207f..5858a7c6d 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -171,4 +171,38 @@ describe Hbc::Cask, :cask do        end      end    end + +  describe "full_name" do +    context "when it is a core cask" do +      it "is the cask token" do +        c = Hbc::CaskLoader.load("local-caffeine") +        expect(c.full_name).to eq("local-caffeine") +      end +    end + +    context "when it is from a non-core tap" do +      it "returns the fully-qualified name of the cask" do +        c = Hbc::CaskLoader.load("third-party/tap/third-party-cask") +        expect(c.full_name).to eq("third-party/tap/third-party-cask") +      end +    end + +    context "when it is from no known tap" do +      it "retuns the cask token" do +        file = Tempfile.new(%w[tapless-cask .rb]) + +        begin +          cask_name = File.basename(file.path, ".rb") +          file.write "cask '#{cask_name}'" +          file.close + +          c = Hbc::CaskLoader.load(file.path) +          expect(c.full_name).to eq(cask_name) +        ensure +          file.close +          file.unlink +        end +      end +    end +  end  end diff --git a/Library/Homebrew/test/cask/cli/audit_spec.rb b/Library/Homebrew/test/cask/cli/audit_spec.rb index 01f506c8c..30ab437cb 100644 --- a/Library/Homebrew/test/cask/cli/audit_spec.rb +++ b/Library/Homebrew/test/cask/cli/audit_spec.rb @@ -1,8 +1,10 @@  describe Hbc::CLI::Audit, :cask do -  let(:cask) { double("cask", token: nil) } +  let(:cask) { Hbc::Cask.new(nil) }    describe "selection of Casks to audit" do      it "audits all Casks if no tokens are given" do +      expect(cask).to be_a Hbc::Cask +        allow(Hbc).to receive(:all).and_return([cask, cask])        expect(Hbc::Auditor).to receive(:audit).twice.and_return(true) diff --git a/Library/Homebrew/test/cask/cli/cat_spec.rb b/Library/Homebrew/test/cask/cli/cat_spec.rb index b726a0b36..5a4b29c6f 100644 --- a/Library/Homebrew/test/cask/cli/cat_spec.rb +++ b/Library/Homebrew/test/cask/cli/cat_spec.rb @@ -35,8 +35,7 @@ describe Hbc::CLI::Cat, :cask do    it "raises an exception when the Cask does not exist" do      expect { Hbc::CLI::Cat.run("notacask") } -      .to output(/is unavailable/).to_stderr -      .and raise_error(Hbc::CaskError, "Cat incomplete.") +      .to raise_error(Hbc::CaskUnavailableError, /is unavailable/)    end    describe "when no Cask is specified" do diff --git a/Library/Homebrew/test/cask/cli/create_spec.rb b/Library/Homebrew/test/cask/cli/create_spec.rb index d77b0a2aa..17d426f78 100644 --- a/Library/Homebrew/test/cask/cli/create_spec.rb +++ b/Library/Homebrew/test/cask/cli/create_spec.rb @@ -39,7 +39,7 @@ describe Hbc::CLI::Create, :cask do    it "raises an exception when more than one Cask is given" do      expect {        described_class.run("additional-cask", "another-cask") -    }.to raise_error(/Only one Cask can be created at a time./) +    }.to raise_error(/Only one Cask can be created at a time\./)    end    it "raises an exception when the Cask already exists" do diff --git a/Library/Homebrew/test/cask/cli/edit_spec.rb b/Library/Homebrew/test/cask/cli/edit_spec.rb index 5d5cbf4b9..51542807f 100644 --- a/Library/Homebrew/test/cask/cli/edit_spec.rb +++ b/Library/Homebrew/test/cask/cli/edit_spec.rb @@ -12,7 +12,7 @@ describe Hbc::CLI::Edit, :cask do    it "raises an error when given more than one argument" do      expect {        described_class.new("local-caffeine", "local-transmission") -    }.to raise_error(/Only one Cask can be created at a time./) +    }.to raise_error(/Only one Cask can be edited at a time\./)    end    it "raises an exception when the Cask doesnt exist" do diff --git a/Library/Homebrew/test/cask/cli/fetch_spec.rb b/Library/Homebrew/test/cask/cli/fetch_spec.rb index f71c23fb6..faaa69b35 100644 --- a/Library/Homebrew/test/cask/cli/fetch_spec.rb +++ b/Library/Homebrew/test/cask/cli/fetch_spec.rb @@ -42,7 +42,7 @@ describe Hbc::CLI::Fetch, :cask do    it "properly handles Casks that are not present" do      expect {        Hbc::CLI::Fetch.run("notacask") -    }.to raise_error(Hbc::CaskError, "Fetch incomplete.") +    }.to raise_error(Hbc::CaskUnavailableError)    end    describe "when no Cask is specified" do diff --git a/Library/Homebrew/test/cask/cli/info_spec.rb b/Library/Homebrew/test/cask/cli/info_spec.rb index bffe900ec..aec7080de 100644 --- a/Library/Homebrew/test/cask/cli/info_spec.rb +++ b/Library/Homebrew/test/cask/cli/info_spec.rb @@ -10,7 +10,7 @@ describe Hbc::CLI::Info, :cask do        ==> Name        None        ==> Artifacts -      Caffeine.app (app) +      Caffeine.app (App)      EOS    end @@ -24,7 +24,7 @@ describe Hbc::CLI::Info, :cask do          ==> Name          None          ==> Artifacts -        Caffeine.app (app) +        Caffeine.app (App)          local-transmission: 2.61          http://example.com/local-transmission          Not installed @@ -32,7 +32,7 @@ describe Hbc::CLI::Info, :cask do          ==> Name          None          ==> Artifacts -        Transmission.app (app) +        Transmission.app (App)        EOS      } @@ -60,7 +60,7 @@ describe Hbc::CLI::Info, :cask do        ==> Name        None        ==> Artifacts -      Caffeine.app (app) +      Caffeine.app (App)        ==> Caveats        Here are some things you might want to know. @@ -86,7 +86,7 @@ describe Hbc::CLI::Info, :cask do        ==> Name        None        ==> Artifacts -      Caffeine.app (app) +      Caffeine.app (App)      EOS    end diff --git a/Library/Homebrew/test/cask/cli/install_spec.rb b/Library/Homebrew/test/cask/cli/install_spec.rb index 64feacce9..e30489789 100644 --- a/Library/Homebrew/test/cask/cli/install_spec.rb +++ b/Library/Homebrew/test/cask/cli/install_spec.rb @@ -56,27 +56,19 @@ describe Hbc::CLI::Install, :cask do    it "properly handles Casks that are not present" do      expect {        Hbc::CLI::Install.run("notacask") -    }.to raise_error(Hbc::CaskError, "Install incomplete.") +    }.to raise_error(Hbc::CaskUnavailableError)    end    it "returns a suggestion for a misspelled Cask" do      expect { -      begin -        Hbc::CLI::Install.run("localcaffeine") -      rescue Hbc::CaskError -        nil -      end -    }.to output(/Cask 'localcaffeine' is unavailable: No Cask with this name exists\. Did you mean:\nlocal-caffeine/).to_stderr +      Hbc::CLI::Install.run("localcaffeine") +    }.to raise_error(Hbc::CaskUnavailableError, /Cask 'localcaffeine' is unavailable: No Cask with this name exists\. Did you mean “local-caffeine”?/)    end    it "returns multiple suggestions for a Cask fragment" do      expect { -      begin -        Hbc::CLI::Install.run("local-caf") -      rescue Hbc::CaskError -        nil -      end -    }.to output(/Cask 'local-caf' is unavailable: No Cask with this name exists\. Did you mean one of:\nlocal-caffeine/).to_stderr +      Hbc::CLI::Install.run("local") +    }.to raise_error(Hbc::CaskUnavailableError, /Cask 'local' is unavailable: No Cask with this name exists\. Did you mean one of these\?\nlocal-caffeine\nlocal-transmission/)    end    describe "when no Cask is specified" do diff --git a/Library/Homebrew/test/cask/cli/list_spec.rb b/Library/Homebrew/test/cask/cli/list_spec.rb index ecca3035f..d2d7efd3b 100644 --- a/Library/Homebrew/test/cask/cli/list_spec.rb +++ b/Library/Homebrew/test/cask/cli/list_spec.rb @@ -14,6 +14,26 @@ describe Hbc::CLI::List, :cask do      EOS    end +  it "lists full names" do +    casks = %w[ +      local-caffeine +      third-party/tap/third-party-cask +      local-transmission +    ].map { |c| Hbc::CaskLoader.load(c) } + +    casks.each do |c| +      InstallHelper.install_with_caskfile(c) +    end + +    expect { +      Hbc::CLI::List.run("--full-name") +    }.to output(<<-EOS.undent).to_stdout +      local-caffeine +      local-transmission +      third-party/tap/third-party-cask +    EOS +  end +    describe "lists versions" do      let(:casks) { ["local-caffeine", "local-transmission"] }      let(:expected_output) { @@ -48,7 +68,8 @@ describe Hbc::CLI::List, :cask do      it "lists the installed files for those Casks" do        casks.each(&InstallHelper.method(:install_without_artifacts_with_caskfile)) -      Hbc::Artifact::App.new(transmission).install_phase +      Hbc::Artifact::App.for_cask(transmission) +        .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }        expect {          Hbc::CLI::List.run("local-transmission", "local-caffeine") diff --git a/Library/Homebrew/test/cask/cli/search_spec.rb b/Library/Homebrew/test/cask/cli/search_spec.rb index e237ad464..3d58e6a15 100644 --- a/Library/Homebrew/test/cask/cli/search_spec.rb +++ b/Library/Homebrew/test/cask/cli/search_spec.rb @@ -4,6 +4,8 @@ describe Hbc::CLI::Search, :cask do    end    it "lists the available Casks that match the search term" do +    allow(GitHub).to receive(:search_code).and_return([]) +      expect {        Hbc::CLI::Search.run("local")      }.to output(<<-EOS.undent).to_stdout.as_tty @@ -14,6 +16,8 @@ describe Hbc::CLI::Search, :cask do    end    it "outputs a plain list when stdout is not a TTY" do +    allow(GitHub).to receive(:search_code).and_return([]) +      expect {        Hbc::CLI::Search.run("local")      }.to output(<<-EOS.undent).to_stdout @@ -22,16 +26,37 @@ describe Hbc::CLI::Search, :cask do      EOS    end +  it "returns matches even when online search failed" do +    allow(GitHub).to receive(:search_code).and_raise(GitHub::Error.new("reason")) + +    expect { +      Hbc::CLI::Search.run("local") +    }.to output(<<-EOS.undent).to_stdout +      local-caffeine +      local-transmission +    EOS +    .and output(/^Warning: Error searching on GitHub: reason/).to_stderr +  end +    it "shows that there are no Casks matching a search term that did not result in anything" do      expect {        Hbc::CLI::Search.run("foo-bar-baz") -    }.to output("No Cask found for \"foo-bar-baz\".\n").to_stdout.as_tty +    }.to output(<<-EOS.undent).to_stdout.as_tty +      No Cask found for "foo-bar-baz". +    EOS    end -  it "lists all available Casks with no search term" do -    expect { -      Hbc::CLI::Search.run -    }.to output(/local-caffeine/).to_stdout.as_tty +  it "doesn't output anything to non-TTY stdout when there are no matches" do +    expect { Hbc::CLI::Search.run("foo-bar-baz") } +      .to not_to_output.to_stdout +      .and not_to_output.to_stderr +  end + +  it "lists all Casks available offline with no search term" do +    allow(GitHub).to receive(:search_code).and_raise(GitHub::Error.new("reason")) +    expect { Hbc::CLI::Search.run } +      .to output(/local-caffeine/).to_stdout.as_tty +      .and not_to_output.to_stderr    end    it "ignores hyphens in search terms" do @@ -55,19 +80,29 @@ describe Hbc::CLI::Search, :cask do    it "accepts a regexp argument" do      expect {        Hbc::CLI::Search.run("/^local-c[a-z]ffeine$/") -    }.to output("==> Regexp Matches\nlocal-caffeine\n").to_stdout.as_tty +    }.to output(<<-EOS.undent).to_stdout.as_tty +      ==> Regexp Matches +      local-caffeine +    EOS    end -  it "Returns both exact and partial matches" do +  it "returns both exact and partial matches" do      expect {        Hbc::CLI::Search.run("test-opera") -    }.to output(/^==> Exact Match\ntest-opera\n==> Partial Matches\ntest-opera-mail/).to_stdout.as_tty +    }.to output(<<-EOS.undent).to_stdout.as_tty +      ==> Exact Match +      test-opera +      ==> Partial Matches +      test-opera-mail +    EOS    end    it "does not search the Tap name" do      expect {        Hbc::CLI::Search.run("caskroom") -    }.to output(/^No Cask found for "caskroom"\.\n/).to_stdout.as_tty +    }.to output(<<-EOS.undent).to_stdout.as_tty +      No Cask found for "caskroom". +    EOS    end    it "doesn't highlight packages that aren't installed" do diff --git a/Library/Homebrew/test/cask/cli/uninstall_spec.rb b/Library/Homebrew/test/cask/cli/uninstall_spec.rb index 1a1c57e88..2ec506839 100644 --- a/Library/Homebrew/test/cask/cli/uninstall_spec.rb +++ b/Library/Homebrew/test/cask/cli/uninstall_spec.rb @@ -16,14 +16,12 @@ describe Hbc::CLI::Uninstall, :cask do    it "shows an error when a bad Cask is provided" do      expect { Hbc::CLI::Uninstall.run("notacask") } -      .to output(/is unavailable/).to_stderr -      .and raise_error(Hbc::CaskError, "Uninstall incomplete.") +      .to raise_error(Hbc::CaskUnavailableError, /is unavailable/)    end    it "shows an error when a Cask is provided that's not installed" do      expect { Hbc::CLI::Uninstall.run("local-caffeine") } -    .to output(/is not installed/).to_stderr -    .and raise_error(Hbc::CaskError, "Uninstall incomplete.") +    .to raise_error(Hbc::CaskNotInstalledError, /is not installed/)    end    it "tries anyway on a non-present Cask when --force is given" do @@ -76,8 +74,7 @@ describe Hbc::CLI::Uninstall, :cask do      Hbc.appdir.join("MyFancyApp.app").rmtree      expect { Hbc::CLI::Uninstall.run("with-uninstall-script-app") } -    .to output(/does not exist/).to_stderr -    .and raise_error(Hbc::CaskError, "Uninstall incomplete.") +    .to raise_error(Hbc::CaskError, /uninstall script .* does not exist/)      expect(cask).to be_installed diff --git a/Library/Homebrew/test/cask/cli/zap_spec.rb b/Library/Homebrew/test/cask/cli/zap_spec.rb index fdc5b4125..502bf8e69 100644 --- a/Library/Homebrew/test/cask/cli/zap_spec.rb +++ b/Library/Homebrew/test/cask/cli/zap_spec.rb @@ -1,8 +1,7 @@  describe Hbc::CLI::Zap, :cask do    it "shows an error when a bad Cask is provided" do      expect { Hbc::CLI::Zap.run("notacask") } -      .to output(/is unavailable/).to_stderr -      .and raise_error(Hbc::CaskError, "Zap incomplete.") +      .to raise_error(Hbc::CaskUnavailableError, /is unavailable/)    end    it "can zap and unlink multiple Casks at once" do diff --git a/Library/Homebrew/test/cask/conflicts_with_spec.rb b/Library/Homebrew/test/cask/conflicts_with_spec.rb index 0dc51cb2d..00dc252fe 100644 --- a/Library/Homebrew/test/cask/conflicts_with_spec.rb +++ b/Library/Homebrew/test/cask/conflicts_with_spec.rb @@ -8,7 +8,7 @@ describe "conflicts_with", :cask do        Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-conflicts-with.rb")      } -    it "installs the dependency of a Cask and the Cask itself", :focus do +    it "installs the dependency of a Cask and the Cask itself" do        Hbc::Installer.new(local_caffeine).install        expect(local_caffeine).to be_installed diff --git a/Library/Homebrew/test/cask/depends_on_spec.rb b/Library/Homebrew/test/cask/depends_on_spec.rb index c603cf6e1..fb92a9a24 100644 --- a/Library/Homebrew/test/cask/depends_on_spec.rb +++ b/Library/Homebrew/test/cask/depends_on_spec.rb @@ -31,7 +31,7 @@ describe "Satisfy Dependencies and Requirements", :cask do        it { is_expected.not_to raise_error }      end -    context "given a comparisson" do +    context "given a comparison" do        let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-depends-on-macos-comparison.rb") }        it { is_expected.not_to raise_error }      end diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb index 7f2207a87..aec1e917f 100644 --- a/Library/Homebrew/test/cask/dsl_spec.rb +++ b/Library/Homebrew/test/cask/dsl_spec.rb @@ -186,12 +186,12 @@ describe Hbc::DSL, :cask do          app "Bar.app"        end -      expect(Array(cask.artifacts[:app])).to eq([["Foo.app"], ["Bar.app"]]) +      expect(cask.artifacts[:app].map(&:to_s)).to eq(["Foo.app (App)", "Bar.app (App)"])      end      it "allow app stanzas to be empty" do        cask = Hbc::Cask.new("cask-with-no-apps") -      expect(Array(cask.artifacts[:app])).to eq([]) +      expect(cask.artifacts[:app]).to be_empty      end    end @@ -219,7 +219,7 @@ describe Hbc::DSL, :cask do          pkg "Bar.pkg"        end -      expect(Array(cask.artifacts[:pkg])).to eq([["Foo.pkg"], ["Bar.pkg"]]) +      expect(cask.artifacts[:pkg].map(&:to_s)).to eq(["Foo.pkg (Pkg)", "Bar.pkg (Pkg)"])      end    end @@ -471,10 +471,10 @@ describe Hbc::DSL, :cask do        let(:token) { "with-installer-script" }        it "allows installer script to be specified" do -        expect(cask.artifacts[:installer].first.script[:executable]).to eq("/usr/bin/true") -        expect(cask.artifacts[:installer].first.script[:args]).to eq(["--flag"]) -        expect(cask.artifacts[:installer].to_a[1].script[:executable]).to eq("/usr/bin/false") -        expect(cask.artifacts[:installer].to_a[1].script[:args]).to eq(["--flag"]) +        expect(cask.artifacts[:installer].first.path).to eq(Pathname("/usr/bin/true")) +        expect(cask.artifacts[:installer].first.args[:args]).to eq(["--flag"]) +        expect(cask.artifacts[:installer].to_a[1].path).to eq(Pathname("/usr/bin/false")) +        expect(cask.artifacts[:installer].to_a[1].args[:args]).to eq(["--flag"])        end      end @@ -482,7 +482,9 @@ describe Hbc::DSL, :cask do        let(:token) { "with-installer-manual" }        it "allows installer manual to be specified" do -        expect(cask.artifacts[:installer].first.manual).to eq("Caffeine.app") +        installer = cask.artifacts[:installer].first +        expect(installer).to be_a(Hbc::Artifact::Installer::ManualInstaller) +        expect(installer.path).to eq(cask.staged_path.join("Caffeine.app"))        end      end    end @@ -492,7 +494,7 @@ describe Hbc::DSL, :cask do        let(:token) { "stage-only" }        it "allows stage_only stanza to be specified" do -        expect(cask.artifacts[:stage_only].first).to eq([true]) +        expect(cask.artifacts[:stage_only]).not_to be_empty        end      end @@ -518,7 +520,7 @@ describe Hbc::DSL, :cask do        let(:token) { "appdir-interpolation" }        it "is allowed" do -        expect(cask.artifacts[:binary].first).to eq(["#{Hbc.appdir}/some/path"]) +        expect(cask.artifacts[:binary].first.source).to eq(Hbc.appdir/"some/path")        end      end @@ -531,7 +533,7 @@ describe Hbc::DSL, :cask do            binary "#{appdir}/some/path"          end -        expect(cask.artifacts[:binary].first).to eq(["#{original_appdir}/some/path"]) +        expect(cask.artifacts[:binary].first.source).to eq(original_appdir/"some/path")        ensure          Hbc.appdir = original_appdir        end diff --git a/Library/Homebrew/test/cleanup_spec.rb b/Library/Homebrew/test/cleanup_spec.rb index 4e5e42efa..da262c5ca 100644 --- a/Library/Homebrew/test/cleanup_spec.rb +++ b/Library/Homebrew/test/cleanup_spec.rb @@ -5,6 +5,7 @@ require "pathname"  describe Homebrew::Cleanup do    let(:ds_store) { Pathname.new("#{HOMEBREW_PREFIX}/Library/.DS_Store") } +  let(:sec_in_a_day) { 60 * 60 * 24 }    around(:each) do |example|      begin @@ -104,14 +105,30 @@ describe Homebrew::Cleanup do      expect(f4).to be_installed    end -  specify "::cleanup_logs" do -    path = (HOMEBREW_LOGS/"delete_me") -    path.mkpath -    ARGV << "--prune=all" +  describe "::cleanup_logs" do +    let(:path) { (HOMEBREW_LOGS/"delete_me") } -    described_class.cleanup_logs +    before do +      path.mkpath +    end + +    it "cleans all logs if prune all" do +      ARGV << "--prune=all" +      described_class.cleanup_logs +      expect(path).not_to exist +    end -    expect(path).not_to exist +    it "cleans up logs if older than 14 days" do +      allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - sec_in_a_day * 15) +      described_class.cleanup_logs +      expect(path).not_to exist +    end + +    it "does not clean up logs less than 14 days old" do +      allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - sec_in_a_day * 2) +      described_class.cleanup_logs +      expect(path).to exist +    end    end    describe "::cleanup_cache" do @@ -124,6 +141,15 @@ describe Homebrew::Cleanup do        expect(incomplete).not_to exist      end +    it "cleans up 'glide_home'" do +      glide_home = (HOMEBREW_CACHE/"glide_home") +      glide_home.mkpath + +      described_class.cleanup_cache + +      expect(glide_home).not_to exist +    end +      it "cleans up 'java_cache'" do        java_cache = (HOMEBREW_CACHE/"java_cache")        java_cache.mkpath @@ -141,5 +167,99 @@ describe Homebrew::Cleanup do        expect(npm_cache).not_to exist      end + +    it "cleans up all files and directories" do +      git = (HOMEBREW_CACHE/"gist--git") +      gist = (HOMEBREW_CACHE/"gist") +      svn = (HOMEBREW_CACHE/"gist--svn") + +      git.mkpath +      gist.mkpath +      FileUtils.touch svn + +      allow(ARGV).to receive(:value).with("prune").and_return("all") + +      described_class.cleanup_cache + +      expect(git).not_to exist +      expect(gist).to exist +      expect(svn).not_to exist +    end + +    it "does not clean up directories that are not VCS checkouts" do +      git = (HOMEBREW_CACHE/"git") +      git.mkpath +      allow(ARGV).to receive(:value).with("prune").and_return("all") + +      described_class.cleanup_cache + +      expect(git).to exist +    end + +    it "cleans up VCS checkout directories with modified time < prune time" do +      foo = (HOMEBREW_CACHE/"--foo") +      foo.mkpath +      allow(ARGV).to receive(:value).with("prune").and_return("1") +      allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - sec_in_a_day * 2) +      described_class.cleanup_cache +      expect(foo).not_to exist +    end + +    it "does not clean up VCS checkout directories with modified time >= prune time" do +      foo = (HOMEBREW_CACHE/"--foo") +      foo.mkpath +      allow(ARGV).to receive(:value).with("prune").and_return("1") +      described_class.cleanup_cache +      expect(foo).to exist +    end + +    context "cleans old files in HOMEBREW_CACHE" do +      let(:bottle) { (HOMEBREW_CACHE/"testball-0.0.1.bottle.tar.gz") } +      let(:testball) { (HOMEBREW_CACHE/"testball-0.0.1") } + +      before(:each) do +        FileUtils.touch(bottle) +        FileUtils.touch(testball) +        (HOMEBREW_CELLAR/"testball"/"0.0.1").mkpath +        FileUtils.touch(CoreTap.instance.formula_dir/"testball.rb") +      end + +      it "cleans up file if outdated" do +        allow(Utils::Bottles).to receive(:file_outdated?).with(any_args).and_return(true) +        described_class.cleanup_cache +        expect(bottle).not_to exist +        expect(testball).not_to exist +      end + +      it "cleans up file if ARGV has -s and formula not installed" do +        ARGV << "-s" +        described_class.cleanup_cache +        expect(bottle).not_to exist +        expect(testball).not_to exist +      end + +      it "cleans up file if stale" do +        described_class.cleanup_cache +        expect(bottle).not_to exist +        expect(testball).not_to exist +      end +    end +  end + +  describe "::prune?" do +    before do +      foo.mkpath +    end + +    let(:foo) { HOMEBREW_CACHE/"foo" } + +    it "returns true when path_modified_time < days_default" do +      allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - sec_in_a_day * 2) +      expect(described_class.prune?(foo, days_default: "1")).to be_truthy +    end + +    it "returns false when path_modified_time >= days_default" do +      expect(described_class.prune?(foo, days_default: "2")).to be_falsey +    end    end  end diff --git a/Library/Homebrew/test/cmd/commands_spec.rb b/Library/Homebrew/test/cmd/commands_spec.rb index cf6f56740..46ed3ddcf 100644 --- a/Library/Homebrew/test/cmd/commands_spec.rb +++ b/Library/Homebrew/test/cmd/commands_spec.rb @@ -68,7 +68,7 @@ describe Homebrew do        expect(cmds).to include("t1"), "Executable files should be included"        expect(cmds).to include("t2"), "Executable Ruby files should be included" -      expect(cmds).not_to include("t3"), "Executable files with a non Ruby extension shoudn't be included" +      expect(cmds).not_to include("t3"), "Executable files with a non Ruby extension shouldn't be included"        expect(cmds).not_to include("t4"), "Non-executable files shouldn't be included"      end    end diff --git a/Library/Homebrew/test/cmd/home_spec.rb b/Library/Homebrew/test/cmd/home_spec.rb index 5a4070492..cf9453af2 100644 --- a/Library/Homebrew/test/cmd/home_spec.rb +++ b/Library/Homebrew/test/cmd/home_spec.rb @@ -7,10 +7,10 @@ describe "brew home", :integration_test do    end    it "opens the homepage for a given Formula" do -    setup_test_formula "testball" +    setup_test_formula "testballhome" -    expect { brew "home", "testball", "HOMEBREW_BROWSER" => "echo" } -      .to output("#{Formula["testball"].homepage}\n").to_stdout +    expect { brew "home", "testballhome", "HOMEBREW_BROWSER" => "echo" } +      .to output("#{Formula["testballhome"].homepage}\n").to_stdout        .and not_to_output.to_stderr        .and be_a_success    end diff --git a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb b/Library/Homebrew/test/cmd/search_remote_tap_spec.rb index b0beb122c..eb256b924 100644 --- a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb +++ b/Library/Homebrew/test/cmd/search_remote_tap_spec.rb @@ -2,6 +2,9 @@ require "cmd/search"  describe Homebrew do    specify "#search_taps" do +    # Otherwise the tested method returns [], regardless of our stub +    ENV.delete("HOMEBREW_NO_GITHUB_API") +      json_response = {        "items" => [          { diff --git a/Library/Homebrew/test/dependency_expansion_spec.rb b/Library/Homebrew/test/dependency_expansion_spec.rb index f955237a9..d6ecdf552 100644 --- a/Library/Homebrew/test/dependency_expansion_spec.rb +++ b/Library/Homebrew/test/dependency_expansion_spec.rb @@ -69,7 +69,7 @@ describe Dependency do      end    end -  it "merges dependencies and perserves env_proc" do +  it "merges dependencies and preserves env_proc" do      env_proc = double      dep = described_class.new("foo", [], env_proc)      allow(dep).to receive(:to_formula).and_return(double(deps: [], name: "foo")) diff --git a/Library/Homebrew/test/dev-cmd/audit_spec.rb b/Library/Homebrew/test/dev-cmd/audit_spec.rb index 037865fdf..3e99bd06b 100644 --- a/Library/Homebrew/test/dev-cmd/audit_spec.rb +++ b/Library/Homebrew/test/dev-cmd/audit_spec.rb @@ -150,70 +150,6 @@ describe FormulaAuditor do      end    end -  describe "#audit_class" do -    specify "missing test" do -      fa = formula_auditor "foo", <<-EOS.undent -        class Foo < Formula -          url "http://example.com/foo-1.0.tgz" -        end -      EOS - -      fa.audit_class -      expect(fa.problems).to eq([]) - -      fa = formula_auditor "foo", <<-EOS.undent, strict: true -        class Foo < Formula -          url "http://example.com/foo-1.0.tgz" -        end -      EOS - -      fa.audit_class -      expect(fa.problems).to eq(["A `test do` test block should be added"]) -    end - -    specify "GithubGistFormula", :needs_compat do -      ENV.delete("HOMEBREW_DEVELOPER") - -      fa = formula_auditor "foo", <<-EOS.undent -        class Foo < GithubGistFormula -          url "http://example.com/foo-1.0.tgz" -        end -      EOS - -      fa.audit_class -      expect(fa.problems) -        .to eq(["GithubGistFormula is deprecated, use Formula instead"]) -    end - -    specify "ScriptFileFormula", :needs_compat do -      ENV.delete("HOMEBREW_DEVELOPER") - -      fa = formula_auditor "foo", <<-EOS.undent -        class Foo < ScriptFileFormula -          url "http://example.com/foo-1.0.tgz" -        end -      EOS - -      fa.audit_class -      expect(fa.problems) -        .to eq(["ScriptFileFormula is deprecated, use Formula instead"]) -    end - -    specify "AmazonWebServicesFormula", :needs_compat do -      ENV.delete("HOMEBREW_DEVELOPER") - -      fa = formula_auditor "foo", <<-EOS.undent -        class Foo < AmazonWebServicesFormula -          url "http://example.com/foo-1.0.tgz" -        end -      EOS - -      fa.audit_class -      expect(fa.problems) -        .to eq(["AmazonWebServicesFormula is deprecated, use Formula instead"]) -    end -  end -    describe "#line_problems" do      specify "pkgshare" do        fa = formula_auditor "foo", <<-EOS.undent, strict: true diff --git a/Library/Homebrew/test/os/mac/diagnostic_spec.rb b/Library/Homebrew/test/os/mac/diagnostic_spec.rb index d6186e46b..83d95c2ef 100644 --- a/Library/Homebrew/test/os/mac/diagnostic_spec.rb +++ b/Library/Homebrew/test/os/mac/diagnostic_spec.rb @@ -47,15 +47,10 @@ describe Homebrew::Diagnostic::Checks do    end    specify "#check_ruby_version" do -    allow(MacOS).to receive(:version).and_return(OS::Mac::Version.new("10.13")) -    stub_const("RUBY_VERSION", "2.3.3p222") +    allow(MacOS).to receive(:version).and_return(OS::Mac::Version.new("10.12")) +    stub_const("RUBY_VERSION", "1.8.6")      expect(subject.check_ruby_version) -      .to match <<-EOS.undent -      Ruby version 2.3.3p222 is unsupported on 10.13. Homebrew -      is developed and tested on Ruby 2.0, and may not work correctly -      on other Rubies. Patches are accepted as long as they don't cause breakage -      on supported Rubies. -    EOS +      .to match "Ruby version 1.8.6 is unsupported on 10.12"    end  end diff --git a/Library/Homebrew/test/pathname_spec.rb b/Library/Homebrew/test/pathname_spec.rb index 0bc19c5ac..69314e5f4 100644 --- a/Library/Homebrew/test/pathname_spec.rb +++ b/Library/Homebrew/test/pathname_spec.rb @@ -295,7 +295,7 @@ describe FileUtils do    let(:dst) { mktmpdir }    describe "#mkdir" do -    it "creates indermediate directories" do +    it "creates intermediate directories" do        described_class.mkdir dst/"foo/bar/baz" do          expect(dst/"foo/bar/baz").to exist, "foo/bar/baz was not created"          expect(dst/"foo/bar/baz").to be_a_directory, "foo/bar/baz was not a directory structure" diff --git a/Library/Homebrew/test/requirement_spec.rb b/Library/Homebrew/test/requirement_spec.rb index 71372aa69..11a3da8f4 100644 --- a/Library/Homebrew/test/requirement_spec.rb +++ b/Library/Homebrew/test/requirement_spec.rb @@ -48,7 +48,7 @@ describe Requirement do        it { is_expected.to be_fatal }      end -    context "#fatal is ommitted" do +    context "#fatal is omitted" do        it { is_expected.not_to be_fatal }      end    end @@ -184,7 +184,7 @@ describe Requirement do        it { is_expected.to have_a_default_formula }      end -    context "#default_formula ommitted" do +    context "#default_formula omitted" do        it { is_expected.not_to have_a_default_formula }      end    end diff --git a/Library/Homebrew/test/rubocops/class_cop_spec.rb b/Library/Homebrew/test/rubocops/class_cop_spec.rb new file mode 100644 index 000000000..676dd4f6e --- /dev/null +++ b/Library/Homebrew/test/rubocops/class_cop_spec.rb @@ -0,0 +1,81 @@ +require "rubocop" +require "rubocop/rspec/support" +require_relative "../../extend/string" +require_relative "../../rubocops/class_cop" + +describe RuboCop::Cop::FormulaAudit::ClassName do +  subject(:cop) { described_class.new } + +  context "When auditing formula" do +    it "with deprecated inheritance" do +      formulas = [{ +        "class" => "GithubGistFormula", +      }, { +        "class" => "ScriptFileFormula", +      }, { +        "class" => "AmazonWebServicesFormula", +      }] + +      formulas.each do |formula| +        source = <<-EOS.undent +        class Foo < #{formula["class"]} +          url 'http://example.com/foo-1.0.tgz' +        end +        EOS + +        expected_offenses = [{  message: "#{formula["class"]} is deprecated, use Formula instead", +                                severity: :convention, +                                line: 1, +                                column: 12, +                                source: source }] + +        inspect_source(cop, source) + +        expected_offenses.zip(cop.offenses.reverse).each do |expected, actual| +          expect_offense(expected, actual) +        end +      end +    end + +    it "with deprecated inheritance and autocorrect" do +      source = <<-EOS.undent +        class Foo < AmazonWebServicesFormula +          url 'http://example.com/foo-1.0.tgz' +        end +      EOS +      corrected_source = <<-EOS.undent +        class Foo < Formula +          url 'http://example.com/foo-1.0.tgz' +        end +      EOS + +      new_source = autocorrect_source(cop, source) +      expect(new_source).to eq(corrected_source) +    end +  end +end + +describe RuboCop::Cop::FormulaAuditStrict::Test do +  subject(:cop) { described_class.new } + +  context "When auditing formula" do +    it "without a test block" do +      source = <<-EOS.undent +        class Foo < Formula +          url 'http://example.com/foo-1.0.tgz' +        end +      EOS +      expected_offenses = [{  message: described_class::MSG, +                              severity: :convention, +                              line: 1, +                              column: 0, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end +  end +end diff --git a/Library/Homebrew/test/rubocops/conflicts_cop_spec.rb b/Library/Homebrew/test/rubocops/conflicts_cop_spec.rb index 4fbab6c9e..3af0f9669 100644 --- a/Library/Homebrew/test/rubocops/conflicts_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/conflicts_cop_spec.rb @@ -22,7 +22,7 @@ describe RuboCop::Cop::FormulaAudit::Conflicts do                                column: 2,                                source: source }] -      inspect_source(cop, source) +      inspect_source(cop, source, "/homebrew-core/Formula/foo@2.0.rb")        expected_offenses.zip(cop.offenses).each do |expected, actual|          expect_offense(expected, actual) @@ -36,7 +36,7 @@ describe RuboCop::Cop::FormulaAudit::Conflicts do            desc 'Bar'          end        EOS -      inspect_source(cop, source) +      inspect_source(cop, source, "/homebrew-core/Formula/foo@2.0.rb")        expect(cop.offenses).to eq([])      end    end diff --git a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb index 74ce478fb..48342e8bc 100644 --- a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb @@ -37,7 +37,7 @@ describe RuboCop::Cop::FormulaAuditStrict::DescLength do        msg = <<-EOS.undent          Description is too long. "name: desc" should be less than 80 characters. -        Length is calculated as Foo + desc. (currently 95) +        Length is calculated as foo + desc. (currently 95)        EOS        expected_offenses = [{ message: msg,                               severity: :convention, @@ -45,7 +45,7 @@ describe RuboCop::Cop::FormulaAuditStrict::DescLength do                               column: 2,                               source: source }] -      inspect_source(cop, source) +      inspect_source(cop, source, "/homebrew-core/Formula/foo.rb")        expected_offenses.zip(cop.offenses).each do |expected, actual|          expect_offense(expected, actual)        end @@ -62,7 +62,7 @@ describe RuboCop::Cop::FormulaAuditStrict::DescLength do        msg = <<-EOS.undent          Description is too long. "name: desc" should be less than 80 characters. -        Length is calculated as Foo + desc. (currently 98) +        Length is calculated as foo + desc. (currently 98)        EOS        expected_offenses = [{ message: msg,                               severity: :convention, @@ -70,7 +70,7 @@ describe RuboCop::Cop::FormulaAuditStrict::DescLength do                               column: 2,                               source: source }] -      inspect_source(cop, source) +      inspect_source(cop, source, "/homebrew-core/Formula/foo.rb")        expected_offenses.zip(cop.offenses).each do |expected, actual|          expect_offense(expected, actual)        end @@ -156,7 +156,7 @@ describe RuboCop::Cop::FormulaAuditStrict::Desc do                               column: 8,                               source: source }] -      inspect_source(cop, source) +      inspect_source(cop, source, "/homebrew-core/Formula/foo.rb")        expected_offenses.zip(cop.offenses).each do |expected, actual|          expect_offense(expected, actual)        end @@ -176,7 +176,7 @@ describe RuboCop::Cop::FormulaAuditStrict::Desc do          end        EOS -      corrected_source = autocorrect_source(cop, source) +      corrected_source = autocorrect_source(cop, source, "/homebrew-core/Formula/foo.rb")        expect(corrected_source).to eq(correct_source)      end    end diff --git a/Library/Homebrew/test/rubocops/lines_cop_spec.rb b/Library/Homebrew/test/rubocops/lines_cop_spec.rb index b0ed8f4d1..d93962688 100644 --- a/Library/Homebrew/test/rubocops/lines_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/lines_cop_spec.rb @@ -200,5 +200,317 @@ describe RuboCop::Cop::FormulaAudit::Miscellaneous do          expect_offense(expected, actual)        end      end + +    it "with invalid rebuild" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          bottle do +            rebuild 0 +            sha256 "fe0679b932dd43a87fd415b609a7fbac7a069d117642ae8ebaac46ae1fb9f0b3" => :sierra +          end +        end +      EOS + +      expected_offenses = [{  message: "'rebuild 0' should be removed", +                              severity: :convention, +                              line: 5, +                              column: 4, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with OS.linux? check" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          bottle do +            if OS.linux? +              nil +            end +            sha256 "fe0679b932dd43a87fd415b609a7fbac7a069d117642ae8ebaac46ae1fb9f0b3" => :sierra +          end +        end +      EOS + +      expected_offenses = [{  message: "Don't use OS.linux?; Homebrew/core only supports macOS", +                              severity: :convention, +                              line: 5, +                              column: 7, +                              source: source }] + +      inspect_source(cop, source, "/homebrew-core/") + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with fails_with :llvm" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          bottle do +            sha256 "fe0679b932dd43a87fd415b609a7fbac7a069d117642ae8ebaac46ae1fb9f0b3" => :sierra +          end +          fails_with :llvm do +            build 2335 +            cause "foo" +          end +        end +      EOS + +      expected_offenses = [{  message: "'fails_with :llvm' is now a no-op so should be removed", +                              severity: :convention, +                              line: 7, +                              column: 2, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with def test" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' + +          def test +            assert_equals "1", "1" +          end +        end +      EOS + +      expected_offenses = [{  message: "Use new-style test definitions (test do)", +                              severity: :convention, +                              line: 5, +                              column: 2, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with def options" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' + +          def options +            [["--bar", "desc"]] +          end +        end +      EOS + +      expected_offenses = [{  message: "Use new-style option definitions", +                              severity: :convention, +                              line: 5, +                              column: 2, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with deprecated skip_clean call" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          skip_clean :all +        end +      EOS + +      expected_offenses = [{ message: <<-EOS.undent.chomp, +                              `skip_clean :all` is deprecated; brew no longer strips symbols +                                      Pass explicit paths to prevent Homebrew from removing empty folders. +                             EOS +                             severity: :convention, +                             line: 4, +                             column: 2, +                             source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with build.universal?" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          if build.universal? +             "foo" +          end +        end +      EOS + +      expected_offenses = [{  message: "macOS has been 64-bit only since 10.6 so build.universal? is deprecated.", +                              severity: :convention, +                              line: 4, +                              column: 5, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with build.universal? exempted formula" do +      source = <<-EOS.undent +        class Wine < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          if build.universal? +             "foo" +          end +        end +      EOS + +      inspect_source(cop, source, "/homebrew-core/Formula/wine.rb") +      expect(cop.offenses).to eq([]) +    end + +    it "with ENV.universal_binary" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          if build? +             ENV.universal_binary +          end +        end +      EOS + +      expected_offenses = [{  message: "macOS has been 64-bit only since 10.6 so ENV.universal_binary is deprecated.", +                              severity: :convention, +                              line: 5, +                              column: 5, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with ENV.universal_binary" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          if build? +             ENV.x11 +          end +        end +      EOS + +      expected_offenses = [{  message: 'Use "depends_on :x11" instead of "ENV.x11"', +                              severity: :convention, +                              line: 5, +                              column: 5, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with ruby-macho alternatives" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          system "install_name_tool", "-id" +        end +      EOS + +      expected_offenses = [{  message: 'Use ruby-macho instead of calling "install_name_tool"', +                              severity: :convention, +                              line: 4, +                              column: 10, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with ruby-macho alternatives audit exempted formula" do +      source = <<-EOS.undent +        class Cctools < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          system "install_name_tool", "-id" +        end +      EOS + +      inspect_source(cop, source, "/homebrew-core/Formula/cctools.rb") +      expect(cop.offenses).to eq([]) +    end + +    it "with npm install without language::Node args" do +      source = <<-EOS.undent +        class Foo < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          system "npm", "install" +        end +      EOS + +      expected_offenses = [{  message: "Use Language::Node for npm install args", +                              severity: :convention, +                              line: 4, +                              column: 2, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end + +    it "with npm install without language::Node args in kibana" do +      source = <<-EOS.undent +        class KibanaAT44 < Formula +          desc "foo" +          url 'http://example.com/foo-1.0.tgz' +          system "npm", "install" +        end +      EOS + +      inspect_source(cop, source, "/homebrew-core/Formula/kibana@4.4.rb") +      expect(cop.offenses).to eq([]) +    end    end  end diff --git a/Library/Homebrew/test/support/fixtures/third-party/Casks/third-party-cask.rb b/Library/Homebrew/test/support/fixtures/third-party/Casks/third-party-cask.rb new file mode 100644 index 000000000..d7add0522 --- /dev/null +++ b/Library/Homebrew/test/support/fixtures/third-party/Casks/third-party-cask.rb @@ -0,0 +1,9 @@ +cask 'third-party-cask' do +  version '1.2.3' +  sha256 '8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b' + +  url 'http://example.com/ThirdParty.dmg' +  homepage 'http://example.com/' + +  app 'ThirdParty.app' +end diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb b/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb index c51d339a7..fc83149d0 100644 --- a/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb +++ b/Library/Homebrew/test/support/helper/spec/shared_context/homebrew_cask.rb @@ -18,6 +18,7 @@ HOMEBREW_CASK_DIRS = [  RSpec.shared_context "Homebrew-Cask" do    around(:each) do |example| +    third_party_tap = Tap.fetch("third-party", "tap")      begin        dirs = HOMEBREW_CASK_DIRS.map do |dir|          Pathname.new(TEST_TMPDIR).join("cask-#{dir}").tap do |path| @@ -31,11 +32,18 @@ RSpec.shared_context "Homebrew-Cask" do          FileUtils.ln_sf TEST_FIXTURE_DIR.join("cask"), tap.path        end +      third_party_tap.tap do |tap| +        FileUtils.mkdir_p tap.path.dirname +        FileUtils.ln_sf TEST_FIXTURE_DIR.join("third-party"), tap.path +      end +        example.run      ensure        FileUtils.rm_rf dirs        Hbc.default_tap.path.unlink        FileUtils.rm_rf Hbc.default_tap.path.parent +      third_party_tap.path.unlink +      FileUtils.rm_rf third_party_tap.path.parent      end    end  end diff --git a/Library/Homebrew/test/utils/analytics_spec.rb b/Library/Homebrew/test/utils/analytics_spec.rb new file mode 100644 index 000000000..bb6cda0b1 --- /dev/null +++ b/Library/Homebrew/test/utils/analytics_spec.rb @@ -0,0 +1,88 @@ +require "utils/analytics" +require "formula_installer" + +describe Utils::Analytics do +  describe "::os_prefix_ci" do +    context "when anonymous_os_prefix_ci is not set" do +      before(:each) do +        described_class.clear_anonymous_os_prefix_ci_cache +      end + +      it "returns OS_VERSION and prefix when HOMEBREW_PREFIX is not /usr/local" do +        stub_const("HOMEBREW_PREFIX", "blah") +        expect(described_class.os_prefix_ci).to include("#{OS_VERSION}, non-/usr/local") +      end + +      it "includes CI when ENV['CI'] is set" do +        ENV["CI"] = "true" +        expect(described_class.os_prefix_ci).to include("CI") +      end + +      it "does not include prefix when HOMEBREW_PREFIX is /usr/local" do +        stub_const("HOMEBREW_PREFIX", "/usr/local") +        expect(described_class.os_prefix_ci).not_to include("non-/usr/local") +      end +    end +  end + +  describe "::report_event" do +    let(:f) { formula { url "foo-1.0" } } +    let(:options) { FormulaInstaller.new(f).display_options(f) } +    let(:action)  { "#{f.full_name} #{options}".strip } + +    context "when ENV vars is set" do +      it "returns nil when HOMEBREW_NO_ANALYTICS is true" do +        ENV["HOMEBREW_NO_ANALYTICS"] = "true" +        expect(described_class.report_event("install", action)).to be_nil +      end + +      it "returns nil when HOMEBREW_NO_ANALYTICS_THIS_RUN is true" do +        ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "true" +        expect(described_class.report_event("install", action)).to be_nil +      end + +      it "returns nil when HOMEBREW_ANALYTICS_DEBUG is true" do +        ENV.delete("HOMEBREW_NO_ANALYTICS_THIS_RUN") +        ENV.delete("HOMEBREW_NO_ANALYTICS") +        ENV["HOMEBREW_ANALYTICS_DEBUG"] = "true" +        expect(described_class.report_event("install", action)).to be_nil +      end +    end +  end + +  describe "::report_build_error" do +    context "when tap is installed" do +      let(:err) { BuildError.new(f, "badprg", %w[arg1 arg2], {}) } +      let(:f) { formula { url "foo-1.0" } } + +      it "reports event if BuildError raised for a formula with a public remote repository" do +        allow_any_instance_of(Tap).to receive(:custom_remote?).and_return(false) +        expect(described_class).to respond_to(:report_event) +        described_class.report_build_error(err) +      end + +      it "does not report event if BuildError raised for a formula with a private remote repository" do +        expect(described_class.report_build_error(err)).to be_nil +      end +    end + +    context "when formula does not have a tap" do +      let(:err) { BuildError.new(f, "badprg", %w[arg1 arg2], {}) } +      let(:f) { double(Formula, name: "foo", path: "blah", tap: nil) } + +      it "does not report event if BuildError is raised" do +        expect(described_class.report_build_error(err)).to be_nil +      end +    end + +    context "when tap for a formula is not installed" do +      let(:err) { BuildError.new(f, "badprg", %w[arg1 arg2], {}) } +      let(:f) { double(Formula, name: "foo", path: "blah", tap: CoreTap.instance) } + +      it "does not report event if BuildError is raised" do +        allow_any_instance_of(Pathname).to receive(:directory?).and_return(false) +        expect(described_class.report_build_error(err)).to be_nil +      end +    end +  end +end diff --git a/Library/Homebrew/test/utils/git_spec.rb b/Library/Homebrew/test/utils/git_spec.rb new file mode 100644 index 000000000..48fc1338e --- /dev/null +++ b/Library/Homebrew/test/utils/git_spec.rb @@ -0,0 +1,150 @@ +require "utils/git" + +describe Git do +  before(:each) do +    git = HOMEBREW_SHIMS_PATH/"scm/git" + +    HOMEBREW_CACHE.cd do +      system git, "init" + +      File.open(file, "w") { |f| f.write("blah") } +      system git, "add", HOMEBREW_CACHE/file +      system git, "commit", "-m", "'File added'" +      @h1 = `git rev-parse HEAD` + +      File.open(file, "w") { |f| f.write("brew") } +      system git, "add", HOMEBREW_CACHE/file +      system git, "commit", "-m", "'written to File'" +      @h2 = `git rev-parse HEAD` +    end +  end + +  let(:file) { "blah.rb" } +  let(:hash1) { @h1[0..6] } +  let(:hash2) { @h2[0..6] } + +  describe "#last_revision_commit_of_file" do +    it "gives last revision commit when before_commit is nil" do +      expect( +        described_class.last_revision_commit_of_file(HOMEBREW_CACHE, file), +      ).to eq(hash1) +    end + +    it "gives revision commit based on before_commit when it is not nil" do +      expect( +        described_class.last_revision_commit_of_file(HOMEBREW_CACHE, +                                                    file, +                                                    before_commit: hash2), +      ).to eq(hash2) +    end +  end + +  describe "#last_revision_of_file" do +    it "returns last revision of file" do +      expect( +        described_class.last_revision_of_file(HOMEBREW_CACHE, +                                              HOMEBREW_CACHE/file), +      ).to eq("blah") +    end + +    it "returns last revision of file based on before_commit" do +      expect( +        described_class.last_revision_of_file(HOMEBREW_CACHE, HOMEBREW_CACHE/file, +                                              before_commit: "0..3"), +      ).to eq("brew") +    end +  end +end + +describe Utils do +  before(:each) do +    described_class.clear_git_available_cache +  end + +  describe "::git_available?" do +    it "returns true if git --version command succeeds" do +      expect(described_class.git_available?).to be_truthy +    end + +    it "returns false if git --version command does not succeed" do +      stub_const("HOMEBREW_SHIMS_PATH", HOMEBREW_PREFIX/"bin/shim") +      expect(described_class.git_available?).to be_falsey +    end +  end + +  describe "::git_path" do +    it "returns nil when git is not available" do +      stub_const("HOMEBREW_SHIMS_PATH", HOMEBREW_PREFIX/"bin/shim") +      expect(described_class.git_path).to eq(nil) +    end + +    it "returns path of git when git is available" do +      expect(described_class.git_path).to end_with("git") +    end +  end + +  describe "::git_version" do +    it "returns nil when git is not available" do +      stub_const("HOMEBREW_SHIMS_PATH", HOMEBREW_PREFIX/"bin/shim") +      expect(described_class.git_path).to eq(nil) +    end + +    it "returns version of git when git is available" do +      expect(described_class.git_version).not_to be_nil +    end +  end + +  describe "::ensure_git_installed!" do +    it "returns nil if git already available" do +      expect(described_class.ensure_git_installed!).to be_nil +    end + +    context "when git is not already available" do +      before do +        stub_const("HOMEBREW_SHIMS_PATH", HOMEBREW_PREFIX/"bin/shim") +      end + +      it "can't install brewed git if homebrew/core is unavailable" do +        allow_any_instance_of(Pathname).to receive(:directory?).and_return(false) +        expect { described_class.ensure_git_installed! }.to raise_error("Git is unavailable") +      end + +      it "raises error if can't install git" do +        stub_const("HOMEBREW_BREW_FILE", HOMEBREW_PREFIX/"bin/brew") +        expect { described_class.ensure_git_installed! }.to raise_error("Git is unavailable") +      end + +      it "installs git" do +        allow(Homebrew).to receive(:_system).with(any_args).and_return(true) +        described_class.ensure_git_installed! +      end +    end +  end + +  describe "::git_remote_exists" do +    it "returns true when git is not available" do +      stub_const("HOMEBREW_SHIMS_PATH", HOMEBREW_PREFIX/"bin/shim") +      expect(described_class.git_remote_exists("blah")).to be_truthy +    end + +    context "when git is available" do +      it "returns true when git remote exists", :needs_network do +        git = HOMEBREW_SHIMS_PATH/"scm/git" +        url = "https://github.com/Homebrew/homebrew.github.io" +        repo = HOMEBREW_CACHE/"hey" +        repo.mkpath + +        repo.cd do +          system git, "init" +          system git, "remote", "add", "origin", url +        end + +        expect(described_class.git_remote_exists(url)).to be_truthy +      end + +      it "returns false when git remote does not exist" do +        expect(described_class.git_remote_exists("blah")).to be_falsey +      end +    end +  end +end diff --git a/Library/Homebrew/test/utils/github_spec.rb b/Library/Homebrew/test/utils/github_spec.rb index 9322898ee..a132894f9 100644 --- a/Library/Homebrew/test/utils/github_spec.rb +++ b/Library/Homebrew/test/utils/github_spec.rb @@ -2,7 +2,7 @@ require "utils/github"  describe GitHub do    describe "::search_code", :needs_network do -    it "queries GitHub code with the passed paramaters" do +    it "queries GitHub code with the passed parameters" do        results = subject.search_code(repo: "Homebrew/brew", path: "/",                                      filename: "readme", language: "markdown") diff --git a/Library/Homebrew/test/utils/svn_spec.rb b/Library/Homebrew/test/utils/svn_spec.rb new file mode 100644 index 000000000..4edb365a0 --- /dev/null +++ b/Library/Homebrew/test/utils/svn_spec.rb @@ -0,0 +1,39 @@ +require "utils/svn" + +describe Utils do +  describe "#self.svn_available?" do +    before(:each) do +      described_class.clear_svn_version_cache +    end + +    it "returns svn version if svn available" do +      expect(described_class.svn_available?).to be_truthy +    end +  end + +  describe "#self.svn_remote_exists" do +    it "returns true when svn is not available" do +      allow(Utils).to receive(:svn_available?).and_return(false) +      expect(described_class.svn_remote_exists("blah")).to be_truthy +    end + +    context "when svn is available" do +      before do +        allow(Utils).to receive(:svn_available?).and_return(true) +      end + +      it "returns false when remote does not exist" do +        expect(described_class.svn_remote_exists(HOMEBREW_CACHE/"install")).to be_falsey +      end + +      it "returns true when remote exists", :needs_network do +        remote = "http://github.com/Homebrew/install" +        svn = HOMEBREW_SHIMS_PATH/"scm/svn" + +        HOMEBREW_CACHE.cd { system svn, "checkout", remote } + +        expect(described_class.svn_remote_exists(HOMEBREW_CACHE/"install")).to be_truthy +      end +    end +  end +end diff --git a/Library/Homebrew/test/utils_spec.rb b/Library/Homebrew/test/utils_spec.rb index 37bd83c4f..3b5355b15 100644 --- a/Library/Homebrew/test/utils_spec.rb +++ b/Library/Homebrew/test/utils_spec.rb @@ -296,4 +296,33 @@ describe "globally-scoped helper methods" do        expect(ENV["PATH"]).not_to eq("/bin")      end    end + +  describe "#tap_and_name_comparison" do +    describe "both strings are only names" do +      it "alphabetizes the strings" do +        expect(%w[a b].sort(&tap_and_name_comparison)).to eq(%w[a b]) +        expect(%w[b a].sort(&tap_and_name_comparison)).to eq(%w[a b]) +      end +    end + +    describe "both strings include tap" do +      it "alphabetizes the strings" do +        expect(%w[a/z/z b/z/z].sort(&tap_and_name_comparison)).to eq(%w[a/z/z b/z/z]) +        expect(%w[b/z/z a/z/z].sort(&tap_and_name_comparison)).to eq(%w[a/z/z b/z/z]) + +        expect(%w[z/a/z z/b/z].sort(&tap_and_name_comparison)).to eq(%w[z/a/z z/b/z]) +        expect(%w[z/b/z z/a/z].sort(&tap_and_name_comparison)).to eq(%w[z/a/z z/b/z]) + +        expect(%w[z/z/a z/z/b].sort(&tap_and_name_comparison)).to eq(%w[z/z/a z/z/b]) +        expect(%w[z/z/b z/z/a].sort(&tap_and_name_comparison)).to eq(%w[z/z/a z/z/b]) +      end +    end + +    describe "only one string includes tap" do +      it "prefers the string without tap" do +        expect(%w[a/z/z z].sort(&tap_and_name_comparison)).to eq(%w[z a/z/z]) +        expect(%w[z a/z/z].sort(&tap_and_name_comparison)).to eq(%w[z a/z/z]) +      end +    end +  end  end diff --git a/Library/Homebrew/test/version_spec.rb b/Library/Homebrew/test/version_spec.rb index cee57e935..d670d79c8 100644 --- a/Library/Homebrew/test/version_spec.rb +++ b/Library/Homebrew/test/version_spec.rb @@ -241,8 +241,18 @@ describe Version do    describe "::detect" do      matcher :be_detected_from do |url, specs = {}| -      match do |version| -        Version.detect(url, specs) == version +      detected = Version.detect(url, specs) + +      match do |expected| +        detected == expected +      end + +      failure_message do |expected| +        message = <<-EOS +        expected: %s +        detected: %s +        EOS +        format(message, expected, detected)        end      end diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index e3137ac49..3033eb4dd 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -102,7 +102,7 @@ def odeprecated(method, replacement = nil, disable: false, disable_on: nil, call    if ARGV.homebrew_developer? || disable ||       Homebrew.raise_deprecation_exceptions?      raise MethodDeprecatedError, message -  else +  elsif !Homebrew.auditing?      opoo "#{message}\n"    end  end @@ -560,3 +560,15 @@ end  def shell_profile    Utils::Shell.profile  end + +def tap_and_name_comparison +  proc do |a, b| +    if a.include?("/") && !b.include?("/") +      1 +    elsif !a.include?("/") && b.include?("/") +      -1 +    else +      a <=> b +    end +  end +end diff --git a/Library/Homebrew/utils/analytics.rb b/Library/Homebrew/utils/analytics.rb index a89995ba9..9766c14db 100644 --- a/Library/Homebrew/utils/analytics.rb +++ b/Library/Homebrew/utils/analytics.rb @@ -3,6 +3,11 @@ require "erb"  module Utils    module Analytics      class << self +      def clear_anonymous_os_prefix_ci_cache +        return unless instance_variable_defined?(:@anonymous_os_prefix_ci) +        remove_instance_variable(:@anonymous_os_prefix_ci) +      end +        def os_prefix_ci          @anonymous_os_prefix_ci ||= begin            os = OS_VERSION diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index bc7055c0c..7807d2034 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -38,11 +38,14 @@ def curl(*args)  end  def curl_download(*args, to: nil, continue_at: "-", **options) +  had_incomplete_download ||= File.exist?(to)    curl("--location", "--remote-time", "--continue-at", continue_at.to_s, "--output", to, *args, **options)  rescue ErrorDuringExecution    # `curl` error 33: HTTP server doesn't seem to support byte ranges. Cannot resume. -  if $CHILD_STATUS.exitstatus == 33 && continue_at == "-" +  # HTTP status 416: Requested range not satisfiable +  if ($CHILD_STATUS.exitstatus == 33 || had_incomplete_download) && continue_at == "-"      continue_at = 0 +    had_incomplete_download = false      retry    end diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index 43d93b64e..f1113af66 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -16,8 +16,7 @@ module Git    def last_revision_of_file(repo, file, before_commit: nil)      relative_file = Pathname(file).relative_path_from(repo) -    commit_hash = last_revision_commit_of_file(repo, file, before_commit: before_commit) - +    commit_hash = last_revision_commit_of_file(repo, relative_file, before_commit: before_commit)      out, = Open3.capture3(        HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo,        "show", "#{commit_hash}:#{relative_file}" @@ -28,8 +27,7 @@ end  module Utils    def self.git_available? -    return @git if instance_variable_defined?(:@git) -    @git = quiet_system HOMEBREW_SHIMS_PATH/"scm/git", "--version" +    @git ||= quiet_system HOMEBREW_SHIMS_PATH/"scm/git", "--version"    end    def self.git_path @@ -50,21 +48,20 @@ module Utils      return if git_available?      # we cannot install brewed git if homebrew/core is unavailable. -    raise "Git is unavailable" unless CoreTap.instance.installed? - -    begin -      oh1 "Installing git" -      safe_system HOMEBREW_BREW_FILE, "install", "git" -    rescue -      raise "Git is unavailable" +    if CoreTap.instance.installed? +      begin +        oh1 "Installing git" +        safe_system HOMEBREW_BREW_FILE, "install", "git" +      rescue +        raise "Git is unavailable" +      end      end -    clear_git_available_cache      raise "Git is unavailable" unless git_available?    end    def self.clear_git_available_cache -    remove_instance_variable(:@git) if instance_variable_defined?(:@git) +    @git = nil      @git_path = nil      @git_version = nil    end diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index a1cf5fbba..df0811e95 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -86,15 +86,9 @@ module GitHub    def api_credentials_type      token, username = api_credentials -    if token && !token.empty? -      if username && !username.empty? -        :keychain -      else -        :environment -      end -    else -      :none -    end +    return :none if !token || token.empty? +    return :keychain if !username || username.empty? +    :environment    end    def api_credentials_error_message(response_headers, needed_scopes) @@ -245,7 +239,7 @@ module GitHub    end    def print_pull_requests_matching(query) -    open_or_closed_prs = search_issues(query, type: "pr") +    open_or_closed_prs = search_issues(query, type: "pr", user: "Homebrew")      open_prs = open_or_closed_prs.select { |i| i["state"] == "open" }      prs = if !open_prs.empty? diff --git a/Library/Homebrew/utils/popen.rb b/Library/Homebrew/utils/popen.rb index 4e03711a1..2fa3ade46 100644 --- a/Library/Homebrew/utils/popen.rb +++ b/Library/Homebrew/utils/popen.rb @@ -1,20 +1,20 @@  module Utils -  def self.popen_read(*args, &block) -    popen(args, "rb", &block) +  def self.popen_read(*args, **options, &block) +    popen(args, "rb", options, &block)    end -  def self.popen_write(*args, &block) -    popen(args, "wb", &block) +  def self.popen_write(*args, **options, &block) +    popen(args, "wb", options, &block)    end -  def self.popen(args, mode) +  def self.popen(args, mode, options = {})      IO.popen("-", mode) do |pipe|        if pipe          return pipe.read unless block_given?          yield pipe        else -        $stderr.reopen("/dev/null", "w") -        exec(*args) +        options[:err] ||= :close unless ENV["HOMEBREW_STDERR"] +        exec(*args, options)        end      end    end diff --git a/Library/Homebrew/utils/ruby.sh b/Library/Homebrew/utils/ruby.sh index 6945c068b..9a3ab2e81 100644 --- a/Library/Homebrew/utils/ruby.sh +++ b/Library/Homebrew/utils/ruby.sh @@ -2,7 +2,8 @@ setup-ruby-path() {    local vendor_dir    local vendor_ruby_current_version    local vendor_ruby_path -  local ruby_version_major +  local ruby_old_version +  local minimum_ruby_version="2.3.3"    vendor_dir="$HOMEBREW_LIBRARY/Homebrew/vendor"    vendor_ruby_current_version="$vendor_dir/portable-ruby/current" @@ -21,7 +22,7 @@ setup-ruby-path() {        if [[ $(readlink "$vendor_ruby_current_version") != "$(<"$vendor_dir/portable-ruby-version")" ]]        then -        if ! brew vendor-install ruby --quiet +        if ! brew vendor-install ruby          then            onoe "Failed to upgrade vendor Ruby."          fi @@ -36,14 +37,12 @@ setup-ruby-path() {        if [[ -n "$HOMEBREW_RUBY_PATH" ]]        then -        ruby_version_major="$("$HOMEBREW_RUBY_PATH" --version)" -        ruby_version_major="${ruby_version_major#ruby }" -        ruby_version_major="${ruby_version_major%%.*}" +        ruby_old_version="$("$HOMEBREW_RUBY_PATH" -rrubygems -e "puts Gem::Version.new('$minimum_ruby_version') > Gem::Version.new(RUBY_VERSION)")"        fi -      if [[ "$ruby_version_major" != "2" || -n "$HOMEBREW_FORCE_VENDOR_RUBY" ]] +      if [[ "$ruby_old_version" == "true" || -n "$HOMEBREW_FORCE_VENDOR_RUBY" ]]        then -        brew vendor-install ruby --quiet +        brew vendor-install ruby          if [[ ! -x "$vendor_ruby_path" ]]          then            odie "Failed to install vendor Ruby." diff --git a/Library/Homebrew/utils/shell.rb b/Library/Homebrew/utils/shell.rb index 5327f6ecf..8c1c5f984 100644 --- a/Library/Homebrew/utils/shell.rb +++ b/Library/Homebrew/utils/shell.rb @@ -51,8 +51,6 @@ module Utils        end      end -    private -      SHELL_PROFILE_MAP = {        bash: "~/.bash_profile",        csh: "~/.cshrc", @@ -65,8 +63,6 @@ module Utils      UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@\n])} -    module_function -      def csh_quote(str)        # ruby's implementation of shell_escape        str = str.to_s diff --git a/Library/Homebrew/utils/svn.rb b/Library/Homebrew/utils/svn.rb index fb49ac2e9..150b7eee7 100644 --- a/Library/Homebrew/utils/svn.rb +++ b/Library/Homebrew/utils/svn.rb @@ -1,4 +1,8 @@  module Utils +  def self.clear_svn_version_cache +    remove_instance_variable(:@svn) if instance_variable_defined?(:@svn) +  end +    def self.svn_available?      return @svn if instance_variable_defined?(:@svn)      @svn = quiet_system HOMEBREW_SHIMS_PATH/"scm/svn", "--version" diff --git a/Library/Homebrew/vendor/portable-ruby-version b/Library/Homebrew/vendor/portable-ruby-version index 633c00da3..0bee604df 100644 --- a/Library/Homebrew/vendor/portable-ruby-version +++ b/Library/Homebrew/vendor/portable-ruby-version @@ -1 +1 @@ -2.0.0-p648 +2.3.3  | 
