diff options
43 files changed, 555 insertions, 535 deletions
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..6670d4249 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,47 @@ -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      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..9499d5c03 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,33 @@ 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 = directives +      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 +58,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 +77,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 +118,15 @@ module Hbc        end        # :signal should come after :quit so it can be used as a backup when :quit fails -      def uninstall_signal(*signals) +      def uninstall_signal(*signals, **options)          signals.flatten.each_slice(2) 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, **options).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 +140,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 +151,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 +176,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 +223,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 +268,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 +276,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..64ce2d4bc 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb @@ -1,30 +1,77 @@ -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      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 0e8e42c9b..72a6f6bb5 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb @@ -1,33 +1,57 @@ -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 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? @@ -35,31 +59,15 @@ module Hbc          altnames = "(#{altnames})"          # Some packages are shipped as u=rx (e.g. Bitcoin Core) -        @command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath]) +        command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath]) -        @command.run!("/usr/bin/xattr", +        command.run!("/usr/bin/xattr",                        args:         ["-w", ALT_NAME_ATTRIBUTE, altnames, file],                        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..7aef66469 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb @@ -1,10 +1,18 @@ -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      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..27f8ae791 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 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/internal_stanza.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb index 4515fe931..2727f95b7 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb @@ -21,34 +21,9 @@ module Hbc        #     brew cask _stanza artifacts --table --yaml    alfred google-chrome adium voicemac logisim 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 @@ -93,7 +68,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 "" @@ -108,8 +83,8 @@ module Hbc            value = value.fetch(artifact_name).to_a.flatten if artifact_name -          if @format -            puts value.send(@format) +          if format +            puts value.send(format)            elsif artifact_name || value.is_a?(Symbol)              puts value.inspect            else diff --git a/Library/Homebrew/cask/lib/hbc/cli/list.rb b/Library/Homebrew/cask/lib/hbc/cli/list.rb index 9d978360e..4b5fcd873 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/list.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/list.rb @@ -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 diff --git a/Library/Homebrew/cask/lib/hbc/dsl.rb b/Library/Homebrew/cask/lib/hbc/dsl.rb index 8ad206c2f..2dda47627 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 @@ -163,7 +162,7 @@ module Hbc            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 +              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/installer.rb b/Library/Homebrew/cask/lib/hbc/installer.rb index 37cc4e561..b9c34e3a1 100644 --- a/Library/Homebrew/cask/lib/hbc/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/installer.rb @@ -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 @@ -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/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/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/list_spec.rb b/Library/Homebrew/test/cask/cli/list_spec.rb index ecca3035f..fd6997f41 100644 --- a/Library/Homebrew/test/cask/cli/list_spec.rb +++ b/Library/Homebrew/test/cask/cli/list_spec.rb @@ -48,7 +48,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/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  | 
