diff options
Diffstat (limited to 'Library')
56 files changed, 671 insertions, 460 deletions
diff --git a/Library/Homebrew/cask/lib/hbc/audit.rb b/Library/Homebrew/cask/lib/hbc/audit.rb index cee1fe807..b8bb6ab81 100644 --- a/Library/Homebrew/cask/lib/hbc/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/audit.rb @@ -143,7 +143,15 @@ module Hbc      def check_appcast_http_code        odebug "Verifying appcast returns 200 HTTP response code" -      result = @command.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--output", "/dev/null", "--write-out", "%{http_code}", cask.appcast], print_stderr: false) + +      curl_executable, *args = curl_args( +        "--compressed", "--location", "--fail", +        "--write-out", "%{http_code}", +        "--output", "/dev/null", +        cask.appcast, +        user_agent: :fake +      ) +      result = @command.run(curl_executable, args: args, print_stderr: false)        if result.success?          http_code = result.stdout.chomp          add_warning "unexpected HTTP response code retrieving appcast: #{http_code}" unless http_code == "200" diff --git a/Library/Homebrew/cask/lib/hbc/cask.rb b/Library/Homebrew/cask/lib/hbc/cask.rb index 672d18954..6d89a997c 100644 --- a/Library/Homebrew/cask/lib/hbc/cask.rb +++ b/Library/Homebrew/cask/lib/hbc/cask.rb @@ -7,9 +7,16 @@ module Hbc      include Metadata      attr_reader :token, :sourcefile_path -    def initialize(token, sourcefile_path: nil, &block) + +    def tap +      return super if block_given? # Object#tap +      @tap +    end + +    def initialize(token, sourcefile_path: nil, tap: nil, &block)        @token = token        @sourcefile_path = sourcefile_path +      @tap = tap        @dsl = DSL.new(@token)        return unless block_given?        @dsl.instance_eval(&block) diff --git a/Library/Homebrew/cask/lib/hbc/cask_loader.rb b/Library/Homebrew/cask/lib/hbc/cask_loader.rb index 8cd010ef6..dd9c61089 100644 --- a/Library/Homebrew/cask/lib/hbc/cask_loader.rb +++ b/Library/Homebrew/cask/lib/hbc/cask_loader.rb @@ -13,8 +13,8 @@ module Hbc        private -      def cask(header_token, &block) -        Cask.new(header_token, &block) +      def cask(header_token, **options, &block) +        Cask.new(header_token, **options, &block)        end      end @@ -45,12 +45,12 @@ module Hbc        private -      def cask(header_token, &block) +      def cask(header_token, **options, &block)          if token != header_token            raise CaskTokenMismatchError.new(token, header_token)          end -        Cask.new(header_token, sourcefile_path: path, &block) +        super(header_token, **options, sourcefile_path: path, &block)        end      end @@ -71,7 +71,7 @@ module Hbc          begin            ohai "Downloading #{url}." -          curl url, "-o", path +          curl_download url, to: path          rescue ErrorDuringExecution            raise CaskUnavailableError.new(token, "Failed to download #{Formatter.url(url)}.")          end @@ -80,18 +80,33 @@ module Hbc        end      end -    class FromTapLoader < FromPathLoader +    class FromTapPathLoader < FromPathLoader        def self.can_load?(ref) -        ref.to_s.match?(HOMEBREW_TAP_CASK_REGEX) +        ref.to_s.match?(HOMEBREW_TAP_PATH_REGEX) && super        end        attr_reader :tap +      def initialize(tap_path) +        @tap = Tap.from_path(tap_path) +        super tap_path +      end + +      private + +      def cask(*args, &block) +        super(*args, tap: tap, &block) +      end +    end + +    class FromTapLoader < FromTapPathLoader +      def self.can_load?(ref) +        ref.to_s.match?(HOMEBREW_TAP_CASK_REGEX) +      end +        def initialize(tapped_name)          user, repo, token = tapped_name.split("/", 3) -        @tap = Tap.fetch(user, repo) - -        super @tap.cask_dir/"#{token}.rb" +        super Tap.fetch(user, repo).cask_dir/"#{token}.rb"        end        def load @@ -136,19 +151,26 @@ module Hbc        [          FromURILoader,          FromTapLoader, +        FromTapPathLoader,          FromPathLoader,        ].each do |loader_class|          return loader_class.new(ref) if loader_class.can_load?(ref)        end -      if FromPathLoader.can_load?(default_path(ref)) -        return FromPathLoader.new(default_path(ref)) +      if FromTapPathLoader.can_load?(default_path(ref)) +        return FromTapPathLoader.new(default_path(ref))        end -      possible_tap_casks = tap_paths(ref) -      if possible_tap_casks.count == 1 -        possible_tap_cask = possible_tap_casks.first -        return FromPathLoader.new(possible_tap_cask) +      case (possible_tap_casks = tap_paths(ref)).count +      when 1 +        return FromTapPathLoader.new(possible_tap_casks.first) +      when 2..Float::INFINITY +        loaders = possible_tap_casks.map(&FromTapPathLoader.method(:new)) + +        raise CaskError, <<-EOS.undent +          Cask #{ref} exists in multiple taps: +          #{loaders.map { |loader| "  #{loader.tap}/#{loader.token}" }.join("\n")} +        EOS        end        possible_installed_cask = Cask.new(ref) diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb index 643d18d55..e89dced92 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/search.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -15,8 +15,9 @@ module Hbc        end        def self.search_remote(query) -        matches = GitHub.search_code("user:caskroom", "path:Casks", "filename:#{query}", "extension:rb") -        [*matches].map do |match| +        matches = GitHub.search_code(user: "caskroom", path: "Casks", +                                     filename: query, extension: "rb") +        matches.map do |match|            tap = Tap.fetch(match["repository"]["full_name"])            next if tap.installed?            "#{tap.name}/#{File.basename(match["path"], ".rb")}" diff --git a/Library/Homebrew/cask/lib/hbc/container.rb b/Library/Homebrew/cask/lib/hbc/container.rb index 93e825e03..fab3a3c1c 100644 --- a/Library/Homebrew/cask/lib/hbc/container.rb +++ b/Library/Homebrew/cask/lib/hbc/container.rb @@ -4,6 +4,7 @@ require "hbc/container/bzip2"  require "hbc/container/cab"  require "hbc/container/criteria"  require "hbc/container/dmg" +require "hbc/container/directory"  require "hbc/container/executable"  require "hbc/container/generic_unar"  require "hbc/container/gpg" @@ -14,6 +15,7 @@ require "hbc/container/otf"  require "hbc/container/pkg"  require "hbc/container/seven_zip"  require "hbc/container/sit" +require "hbc/container/svn_repository"  require "hbc/container/tar"  require "hbc/container/ttf"  require "hbc/container/rar" @@ -43,6 +45,7 @@ module Hbc          Xz,    # pure xz          Gpg,   # GnuPG signed data          Executable, +        SvnRepository,        ]        # for explicit use only (never autodetected):        # Hbc::Container::Naked diff --git a/Library/Homebrew/cask/lib/hbc/container/criteria.rb b/Library/Homebrew/cask/lib/hbc/container/criteria.rb index 66ecb8c87..52f171d6a 100644 --- a/Library/Homebrew/cask/lib/hbc/container/criteria.rb +++ b/Library/Homebrew/cask/lib/hbc/container/criteria.rb @@ -13,9 +13,11 @@ module Hbc        end        def magic_number(regex) +        return false if path.directory? +          # 262: length of the longest regex (currently: Hbc::Container::Tar) -        @magic_number ||= File.open(@path, "rb") { |f| f.read(262) } -        @magic_number =~ regex +        @magic_number ||= File.open(path, "rb") { |f| f.read(262) } +        @magic_number.match?(regex)        end      end    end diff --git a/Library/Homebrew/cask/lib/hbc/container/directory.rb b/Library/Homebrew/cask/lib/hbc/container/directory.rb new file mode 100644 index 000000000..e4bb1095b --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/container/directory.rb @@ -0,0 +1,24 @@ +require "hbc/container/base" + +module Hbc +  class Container +    class Directory < Base +      def self.me?(*) +        false +      end + +      def extract +        @path.children.each do |child| +          next if skip_path?(child) +          FileUtils.cp child, @cask.staged_path +        end +      end + +      private + +      def skip_path?(*) +        false +      end +    end +  end +end diff --git a/Library/Homebrew/cask/lib/hbc/container/executable.rb b/Library/Homebrew/cask/lib/hbc/container/executable.rb index 848f6d4be..af3b36fd1 100644 --- a/Library/Homebrew/cask/lib/hbc/container/executable.rb +++ b/Library/Homebrew/cask/lib/hbc/container/executable.rb @@ -8,7 +8,7 @@ module Hbc          return true if criteria.magic_number(/^#!\s*\S+/)          begin -          MachO.open(criteria.path).header.executable? +          criteria.path.file? && MachO.open(criteria.path).header.executable?          rescue MachO::MagicError            false          end diff --git a/Library/Homebrew/cask/lib/hbc/container/svn_repository.rb b/Library/Homebrew/cask/lib/hbc/container/svn_repository.rb new file mode 100644 index 000000000..cae613b2d --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/container/svn_repository.rb @@ -0,0 +1,15 @@ +require "hbc/container/directory" + +module Hbc +  class Container +    class SvnRepository < Directory +      def self.me?(criteria) +        criteria.path.join(".svn").directory? +      end + +      def skip_path?(path) +        path.basename.to_s == ".svn" +      end +    end +  end +end diff --git a/Library/Homebrew/cask/lib/hbc/download_strategy.rb b/Library/Homebrew/cask/lib/hbc/download_strategy.rb index 28ae704ee..245ad4ade 100644 --- a/Library/Homebrew/cask/lib/hbc/download_strategy.rb +++ b/Library/Homebrew/cask/lib/hbc/download_strategy.rb @@ -10,7 +10,7 @@ module Hbc    class AbstractDownloadStrategy      attr_reader :cask, :name, :url, :uri_object, :version -    def initialize(cask, command = SystemCommand) +    def initialize(cask, command: SystemCommand)        @cask       = cask        @command    = command        # TODO: this excess of attributes is a function of integrating @@ -33,8 +33,8 @@ module Hbc    class HbVCSDownloadStrategy < AbstractDownloadStrategy      REF_TYPES = [:branch, :revision, :revisions, :tag].freeze -    def initialize(cask, command = SystemCommand) -      super +    def initialize(*args, **options) +      super(*args, **options)        @ref_type, @ref = extract_ref        @clone = Hbc.cache.join(cache_filename)      end @@ -64,11 +64,6 @@ module Hbc    end    class CurlDownloadStrategy < AbstractDownloadStrategy -    # TODO: should be part of url object -    def mirrors -      @mirrors ||= [] -    end -      def tarball_path        @tarball_path ||= Hbc.cache.join("#{name}--#{version}#{ext}")      end @@ -95,13 +90,8 @@ module Hbc        end      end -    def downloaded_size -      temporary_path.size? || 0 -    end -      def _fetch -      odebug "Calling curl with args #{cask_curl_args}" -      curl(*cask_curl_args) +      curl_download url, *cask_curl_args, to: temporary_path, user_agent: uri_object.user_agent      end      def fetch @@ -131,33 +121,12 @@ module Hbc          ignore_interrupts { temporary_path.rename(tarball_path) }        end        tarball_path -    rescue CurlDownloadStrategyError -      raise if mirrors.empty? -      puts "Trying a mirror..." -      @url = mirrors.shift -      retry      end      private      def cask_curl_args -      default_curl_args.tap do |args| -        args.concat(user_agent_args) -        args.concat(cookies_args) -        args.concat(referer_args) -      end -    end - -    def default_curl_args -      [url, "-C", downloaded_size, "-o", temporary_path] -    end - -    def user_agent_args -      if uri_object.user_agent -        ["-A", uri_object.user_agent] -      else -        [] -      end +      cookies_args + referer_args      end      def cookies_args @@ -185,14 +154,13 @@ module Hbc      end      def ext -      Pathname.new(@url).extname +      Pathname.new(@url).extname[/[^?]+/]      end    end    class CurlPostDownloadStrategy < CurlDownloadStrategy      def cask_curl_args -      super -      default_curl_args.concat(post_args) +      super.concat(post_args)      end      def post_args @@ -225,8 +193,8 @@ module Hbc      # super does not provide checks for already-existing downloads      def fetch -      if tarball_path.exist? -        puts "Already downloaded: #{tarball_path}" +      if cached_location.directory? +        puts "Already downloaded: #{cached_location}"        else          @url = @url.sub(/^svn\+/, "") if @url =~ %r{^svn\+http://}          ohai "Checking out #{@url}" @@ -252,9 +220,8 @@ module Hbc          else            fetch_repo @clone, @url          end -        compress        end -      tarball_path +      cached_location      end      # This primary reason for redefining this method is the trust_cert @@ -288,10 +255,6 @@ module Hbc                      print_stderr: false)      end -    def tarball_path -      @tarball_path ||= cached_location.dirname.join(cached_location.basename.to_s + "-#{@cask.version}.tar") -    end -      def shell_quote(str)        # Oh god escaping shell args.        # See http://notetoself.vrensk.com/2008/08/escaping-single-quotes-in-ruby-harder-than-expected/ @@ -304,35 +267,5 @@ module Hbc          yield name, url        end      end - -    private - -    # TODO/UPDATE: the tar approach explained below is fragile -    # against challenges such as case-sensitive filesystems, -    # and must be re-implemented. -    # -    # Seems nutty: we "download" the contents into a tape archive. -    # Why? -    # * A single file is tractable to the rest of the Cask toolchain, -    # * An alternative would be to create a Directory container type. -    #   However, some type of file-serialization trick would still be -    #   needed in order to enable calculating a single checksum over -    #   a directory.  So, in that alternative implementation, the -    #   special cases would propagate outside this class, including -    #   the use of tar or equivalent. -    # * SubversionDownloadStrategy.cached_location is not versioned -    # * tarball_path provides a needed return value for our overridden -    #   fetch method. -    # * We can also take this private opportunity to strip files from -    #   the download which are protocol-specific. - -    def compress -      Dir.chdir(cached_location) do -        @command.run!("/usr/bin/tar", -                      args:         ['-s/^\.//', "--exclude", ".svn", "-cf", Pathname.new(tarball_path), "--", "."], -                      print_stderr: false) -      end -      clear_cache -    end    end  end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb b/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb index d302d0946..fc7e83a20 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/appcast.rb @@ -12,7 +12,11 @@ module Hbc        end        def calculate_checkpoint -        result = SystemCommand.run("/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", URL::FAKE_USER_AGENT, "--fail", @uri], print_stderr: false) +        curl_executable, *args = curl_args( +          "--compressed", "--location", "--fail", @uri, +          user_agent: :fake +        ) +        result = SystemCommand.run(curl_executable, args: args, print_stderr: false)          checkpoint = if result.success?            processed_appcast_text = result.stdout.gsub(%r{<pubDate>[^<]*</pubDate>}m, "") diff --git a/Library/Homebrew/cask/lib/hbc/pkg.rb b/Library/Homebrew/cask/lib/hbc/pkg.rb index c9aa3180f..8938268a2 100644 --- a/Library/Homebrew/cask/lib/hbc/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/pkg.rb @@ -16,19 +16,17 @@ module Hbc      def uninstall        unless pkgutil_bom_files.empty?          odebug "Deleting pkg files" -        @command.run("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_files.join("\0"), sudo: true) +        @command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_files.join("\0"), sudo: true)        end        unless pkgutil_bom_specials.empty?          odebug "Deleting pkg symlinks and special files" -        @command.run("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_specials.join("\0"), sudo: true) +        @command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "--"], input: pkgutil_bom_specials.join("\0"), sudo: true)        end        unless pkgutil_bom_dirs.empty?          odebug "Deleting pkg directories"          deepest_path_first(pkgutil_bom_dirs).each do |dir| -          next if MacOS.undeletable?(dir) -            with_full_permissions(dir) do              clean_broken_symlinks(dir)              clean_ds_store(dir) @@ -67,6 +65,7 @@ module Hbc                                     .stdout                                     .split("\n")                                     .map { |path| root.join(path) } +                                   .reject(&MacOS.public_method(:undeletable?))      end      def root diff --git a/Library/Homebrew/cask/lib/hbc/system_command.rb b/Library/Homebrew/cask/lib/hbc/system_command.rb index 901617b71..b735ae4f9 100644 --- a/Library/Homebrew/cask/lib/hbc/system_command.rb +++ b/Library/Homebrew/cask/lib/hbc/system_command.rb @@ -112,11 +112,7 @@ module Hbc                   processed_output[:stderr],                   processed_status.exitstatus)      end -  end -end -module Hbc -  class SystemCommand      class Result        attr_accessor :command, :stdout, :stderr, :exit_status diff --git a/Library/Homebrew/cask/lib/hbc/url.rb b/Library/Homebrew/cask/lib/hbc/url.rb index 15da2ced2..8c652657b 100644 --- a/Library/Homebrew/cask/lib/hbc/url.rb +++ b/Library/Homebrew/cask/lib/hbc/url.rb @@ -1,8 +1,6 @@  module Hbc    class URL -    FAKE_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) https://caskroom.github.io".freeze - -    attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data +    attr_reader :using, :revision, :trust_cert, :uri, :cookies, :referer, :data, :user_agent      extend Forwardable      def_delegators :uri, :path, :scheme, :to_s @@ -17,7 +15,7 @@ module Hbc      def initialize(uri, options = {})        @uri        = Hbc::UnderscoreSupportingURI.parse(uri) -      @user_agent = options[:user_agent] +      @user_agent = options.fetch(:user_agent, :default)        @cookies    = options[:cookies]        @referer    = options[:referer]        @using      = options[:using] @@ -25,10 +23,5 @@ module Hbc        @trust_cert = options[:trust_cert]        @data       = options[:data]      end - -    def user_agent -      return FAKE_USER_AGENT if @user_agent == :fake -      @user_agent -    end    end  end diff --git a/Library/Homebrew/cask/lib/hbc/verify/gpg.rb b/Library/Homebrew/cask/lib/hbc/verify/gpg.rb index dbb537756..f4996a5b5 100644 --- a/Library/Homebrew/cask/lib/hbc/verify/gpg.rb +++ b/Library/Homebrew/cask/lib/hbc/verify/gpg.rb @@ -33,7 +33,7 @@ module Hbc          meta_dir = cached || cask.metadata_subdir("gpg", :now, true)          sig_path = meta_dir.join("signature.asc") -        curl(cask.gpg.signature, "-o", sig_path.to_s) unless cached || force +        curl_download cask.gpg.signature, to: sig_path unless cached || force          sig_path        end diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb index bbf0c1b0b..de7aa4a51 100644 --- a/Library/Homebrew/cmd/deps.rb +++ b/Library/Homebrew/cmd/deps.rb @@ -1,4 +1,4 @@ -#:  * `deps` [`--1`] [`-n`] [`--union`] [`--full-name`] [`--installed`] [`--include-build`] [`--include-optional`] [`--skip-recommended`] <formulae>: +#:  * `deps` [`--1`] [`-n`] [`--union`] [`--full-name`] [`--installed`] [`--include-build`] [`--include-optional`] [`--skip-recommended`] [`--include-requirements`] <formulae>:  #:    Show dependencies for <formulae>. When given multiple formula arguments,  #:    show the intersection of dependencies for <formulae>.  #: @@ -19,15 +19,22 @@  #:    <formulae>. To include the `:build` type dependencies, pass `--include-build`.  #:    Similarly, pass `--include-optional` to include `:optional` dependencies.  #:    To skip `:recommended` type dependencies, pass `--skip-recommended`. +#:    To include requirements in addition to dependencies, pass `--include-requirements`.  #: -#:  * `deps` `--tree` [<filters>] (<formulae>|`--installed`): +#:  * `deps` `--tree` [`--1`] [<filters>] [`--annotate`] (<formulae>|`--installed`):  #:    Show dependencies as a tree. When given multiple formula arguments, output  #:    individual trees for every formula.  #: +#:    If `--1` is passed, only one level of children is displayed. +#:  #:    If `--installed` is passed, output a tree for every installed formula.  #:  #:    The <filters> placeholder is any combination of options `--include-build`, -#:    `--include-optional`, and `--skip-recommended` as documented above. +#:    `--include-optional`, `--skip-recommended`, and `--include-requirements` as +#:    documented above. +#: +#:    If `--annotate` is passed, the build, optional, and recommended dependencies +#:    are marked as such in the output.  #:  #:  * `deps` [<filters>] (`--installed`|`--all`):  #:    Show dependencies for installed or all available formulae. Every line of @@ -37,6 +44,10 @@  #:    The <filters> placeholder is any combination of options `--include-build`,  #:    `--include-optional`, and `--skip-recommended` as documented above. +# The undocumented `--for-each` option will switch into the mode used by `deps --all`, +# but only list dependencies for specified formula, one specified formula per line. +# This is used for debugging the `--installed`/`--all` display mode. +  # encoding: UTF-8  require "formula" @@ -52,20 +63,26 @@ module Homebrew        all?: ARGV.include?("--all"),        topo_order?: ARGV.include?("-n"),        union?: ARGV.include?("--union"), +      for_each?: ARGV.include?("--for-each"),      ) -    if mode.installed? && mode.tree? -      puts_deps_tree Formula.installed +    if mode.tree? +      if mode.installed? +        puts_deps_tree Formula.installed, !ARGV.one? +      else +        raise FormulaUnspecifiedError if ARGV.named.empty? +        puts_deps_tree ARGV.formulae, !ARGV.one? +      end      elsif mode.all?        puts_deps Formula -    elsif mode.tree? -      raise FormulaUnspecifiedError if ARGV.named.empty? -      puts_deps_tree ARGV.formulae      elsif ARGV.named.empty?        raise FormulaUnspecifiedError unless mode.installed?        puts_deps Formula.installed +    elsif mode.for_each? +      puts_deps ARGV.formulae      else        all_deps = deps_for_formulae(ARGV.formulae, !ARGV.one?, &(mode.union? ? :| : :&)) +      all_deps = condense_requirements(all_deps)        all_deps = all_deps.select(&:installed?) if mode.installed?        all_deps = all_deps.map(&method(:dep_display_name)).uniq        all_deps.sort! unless mode.topo_order? @@ -73,24 +90,59 @@ module Homebrew      end    end -  def dep_display_name(d) -    ARGV.include?("--full-name") ? d.to_formula.full_name : d.name +  def condense_requirements(deps) +    if ARGV.include?("--include-requirements") +      deps +    else +      deps.map do |dep| +        if dep.is_a? Dependency +          dep +        elsif dep.default_formula? +          dep.to_dependency +        end +      end.compact +    end +  end + +  def dep_display_name(dep) +    str = if dep.is_a? Requirement +      if ARGV.include?("--include-requirements") +        if dep.default_formula? +          ":#{dep.display_s} (#{dep_display_name(dep.to_dependency)})" +        else +          ":#{dep.display_s}" +        end +      elsif dep.default_formula? +        dep_display_name(dep.to_dependency) +      else +        # This shouldn't happen, but we'll put something here to help debugging +        "::#{dep.name}" +      end +    else +      ARGV.include?("--full-name") ? dep.to_formula.full_name : dep.name +    end +    if ARGV.include?("--annotate") +      str = "#{str}  [build]" if dep.build? +      str = "#{str}  [optional" if dep.optional? +      str = "#{str}  [recommended]" if dep.recommended? +    end +    str    end    def deps_for_formula(f, recursive = false)      includes = []      ignores = [] -    if ARGV.include? "--include-build" +    if ARGV.include?("--include-build")        includes << "build?"      else        ignores << "build?"      end -    if ARGV.include? "--include-optional" +    if ARGV.include?("--include-optional")        includes << "optional?"      else        ignores << "optional?"      end -    ignores << "recommended?" if ARGV.include? "--skip-recommended" +    ignores << "recommended?" if ARGV.include?("--skip-recommended")      if recursive        deps = f.recursive_dependencies do |dependent, dep| @@ -120,7 +172,7 @@ module Homebrew        end      end -    deps + reqs.select(&:default_formula?).map(&:to_dependency) +    deps + reqs.to_a    end    def deps_for_formulae(formulae, recursive = false, &block) @@ -129,41 +181,55 @@ module Homebrew    def puts_deps(formulae)      formulae.each do |f| -      deps = deps_for_formula(f).sort_by(&:name).map(&method(:dep_display_name)) +      deps = deps_for_formula(f) +      deps = condense_requirements(deps) +      deps = deps.sort_by(&:name).map(&method(:dep_display_name))        puts "#{f.full_name}: #{deps.join(" ")}"      end    end -  def puts_deps_tree(formulae) +  def puts_deps_tree(formulae, recursive = false)      formulae.each do |f| -      puts "#{f.full_name} (required dependencies)" -      recursive_deps_tree(f, "") +      puts f.full_name +      @dep_stack = [] +      recursive_deps_tree(f, "", recursive)        puts      end    end -  def recursive_deps_tree(f, prefix) -    reqs = f.requirements.select(&:default_formula?) -    deps = f.deps.default -    max = reqs.length - 1 -    reqs.each_with_index do |req, i| -      chr = if i == max && deps.empty? +  def recursive_deps_tree(f, prefix, recursive) +    reqs = f.requirements +    deps = f.deps +    dependables = reqs + deps +    dependables = dependables.reject(&:optional?) unless ARGV.include?("--include-optional") +    dependables = dependables.reject(&:build?) unless ARGV.include?("--include-build") +    dependables = dependables.reject(&:recommended?) if ARGV.include?("--skip-recommended") +    max = dependables.length - 1 +    @dep_stack.push f.name +    dependables.each_with_index do |dep, i| +      next if !ARGV.include?("--include-requirements") && dep.is_a?(Requirement) && !dep.default_formula? +      tree_lines = if i == max          "└──"        else          "├──"        end -      puts prefix + "#{chr} :#{dep_display_name(req.to_dependency)}" -    end -    max = deps.length - 1 -    deps.each_with_index do |dep, i| -      chr = if i == max -        "└──" +      display_s = "#{tree_lines} #{dep_display_name(dep)}" +      is_circular = @dep_stack.include?(dep.name) +      display_s = "#{display_s} (CIRCULAR DEPENDENCY)" if is_circular +      puts "#{prefix}#{display_s}" +      next if !recursive || is_circular +      prefix_addition = if i == max +        "    "        else -        "├──" +        "│   " +      end +      if dep.is_a?(Requirement) && dep.default_formula? +        recursive_deps_tree(Formulary.factory(dep.to_dependency.name), prefix + prefix_addition, true) +      end +      if dep.is_a? Dependency +        recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true)        end -      prefix_ext = (i == max) ? "    " : "│   " -      puts prefix + "#{chr} #{dep_display_name(dep)}" -      recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_ext)      end +    @dep_stack.pop    end  end diff --git a/Library/Homebrew/cmd/pin.rb b/Library/Homebrew/cmd/pin.rb index c5087f6d4..5a14f853c 100644 --- a/Library/Homebrew/cmd/pin.rb +++ b/Library/Homebrew/cmd/pin.rb @@ -1,6 +1,7 @@  #:  * `pin` <formulae>:  #:    Pin the specified <formulae>, preventing them from being upgraded when -#:    issuing the `brew upgrade` command. See also `unpin`. +#:    issuing the `brew upgrade <formulae>` command (but can still be upgraded +#:    as dependencies for other formulae). See also `unpin`.  require "formula" diff --git a/Library/Homebrew/cmd/postinstall.rb b/Library/Homebrew/cmd/postinstall.rb index f5d091227..02fd8a5f6 100644 --- a/Library/Homebrew/cmd/postinstall.rb +++ b/Library/Homebrew/cmd/postinstall.rb @@ -29,8 +29,6 @@ module Homebrew        args << "--devel"      end -    Sandbox.print_sandbox_message if Sandbox.formula?(formula) -      Utils.safe_fork do        if Sandbox.formula?(formula)          sandbox = Sandbox.new diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb index b2d069744..0718a3af4 100644 --- a/Library/Homebrew/cmd/search.rb +++ b/Library/Homebrew/cmd/search.rb @@ -34,7 +34,7 @@ module Homebrew      elsif ARGV.include? "--opensuse"        exec_browser "https://software.opensuse.org/search?q=#{ARGV.next}"      elsif ARGV.include? "--fedora" -      exec_browser "https://admin.fedoraproject.org/pkgdb/packages/%2A#{ARGV.next}%2A/" +      exec_browser "https://apps.fedoraproject.org/packages/s/#{ARGV.next}"      elsif ARGV.include? "--ubuntu"        exec_browser "http://packages.ubuntu.com/search?keywords=#{ARGV.next}&searchon=names&suite=all§ion=all"      elsif ARGV.include? "--desc" @@ -43,27 +43,29 @@ module Homebrew        Descriptions.search(regex, :desc).print      elsif ARGV.first =~ HOMEBREW_TAP_FORMULA_REGEX        query = ARGV.first -      user, repo, name = query.split("/", 3)        begin          result = Formulary.factory(query).name +        results = Array(result)        rescue FormulaUnavailableError -        result = search_tap(user, repo, name) +        _, _, name = query.split("/", 3) +        results = search_taps(name)        end -      results = Array(result)        puts Formatter.columns(results) unless results.empty?      else        query = ARGV.first        regex = query_regexp(query)        local_results = search_formulae(regex)        puts Formatter.columns(local_results) unless local_results.empty? +        tap_results = search_taps(query)        puts Formatter.columns(tap_results) unless tap_results.empty?        if $stdout.tty?          count = local_results.length + tap_results.length +        ohai "Searching blacklisted, migrated and deleted formulae..."          if reason = Homebrew::MissingFormula.reason(query, silent: true)            if count > 0              puts @@ -101,9 +103,15 @@ module Homebrew    end    def search_taps(query) +    return [] if ENV["HOMEBREW_NO_GITHUB_API"] + +    # Use stderr to avoid breaking parsed output +    $stderr.puts Formatter.headline("Searching taps on GitHub...", color: :blue) +      valid_dirnames = ["Formula", "HomebrewFormula", "Casks", "."].freeze -    matches = GitHub.search_code("user:Homebrew", "user:caskroom", "filename:#{query}", "extension:rb") -    [*matches].map do |match| +    matches = GitHub.search_code(user: ["Homebrew", "caskroom"], filename: query, extension: "rb") + +    matches.map do |match|        dirname, filename = File.split(match["path"])        next unless valid_dirnames.include?(dirname)        tap = Tap.fetch(match["repository"]["full_name"]) @@ -113,6 +121,9 @@ module Homebrew    end    def search_formulae(regex) +    # Use stderr to avoid breaking parsed output +    $stderr.puts Formatter.headline("Searching local taps...", color: :blue) +      aliases = Formula.alias_full_names      results = (Formula.full_names + aliases).grep(regex).sort diff --git a/Library/Homebrew/cmd/unpin.rb b/Library/Homebrew/cmd/unpin.rb index a669df1ec..e15a156ea 100644 --- a/Library/Homebrew/cmd/unpin.rb +++ b/Library/Homebrew/cmd/unpin.rb @@ -1,6 +1,6 @@  #:  * `unpin` <formulae>: -#:    Unpin <formulae>, allowing them to be upgraded by `brew upgrade`. See also -#:    `pin`. +#:    Unpin <formulae>, allowing them to be upgraded by `brew upgrade <formulae>`. +#:    See also `pin`.  require "formula" diff --git a/Library/Homebrew/compat/hbc/cask_loader.rb b/Library/Homebrew/compat/hbc/cask_loader.rb index e6cb65b4f..e57aea71d 100644 --- a/Library/Homebrew/compat/hbc/cask_loader.rb +++ b/Library/Homebrew/compat/hbc/cask_loader.rb @@ -1,13 +1,13 @@  module CaskLoaderCompatibilityLayer    private -  def cask(header_token, &block) +  def cask(header_token, **options, &block)      if header_token.is_a?(Hash) && header_token.key?(:v1)        odeprecated %q("cask :v1 => 'token'"), %q("cask 'token'")        header_token = header_token[:v1]      end -    super(header_token, &block) +    super(header_token, **options, &block)    end  end diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 7b5befaa0..170fb6d5f 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -242,12 +242,10 @@ class FormulaAuditor    def self.http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default)      max_time = hash_needed ? "600" : "25" -    args = curl_args( -      extra_args: ["--connect-timeout", "15", "--include", "--max-time", max_time, url], -      show_output: true, -      user_agent: user_agent, +    output, = curl_output( +      "--connect-timeout", "15", "--include", "--max-time", max_time, "--location", url, +      user_agent: user_agent      ) -    output = Open3.popen3(*args) { |_, stdout, _, _| stdout.read }      status_code = :unknown      while status_code == :unknown || status_code.to_s.start_with?("3") @@ -330,6 +328,7 @@ class FormulaAuditor        valid_alias_names = [alias_name_major, alias_name_major_minor]        if formula.tap && !formula.tap.core_tap? +        versioned_aliases.map! { |a| "#{formula.tap}/#{a}" }          valid_alias_names.map! { |a| "#{formula.tap}/#{a}" }        end @@ -630,7 +629,6 @@ class FormulaAuditor        end        next if spec.patches.empty? -      spec.patches.each { |p| patch_problems(p) if p.external? }        next unless @new_formula        problem "New formulae should not require patches to build. Patches should be submitted and accepted upstream first."      end @@ -786,36 +784,6 @@ class FormulaAuditor      end    end -  def patch_problems(patch) -    case patch.url -    when %r{https?://github\.com/.+/.+/(?:commit|pull)/[a-fA-F0-9]*.(?:patch|diff)} -      unless patch.url =~ /\?full_index=\w+$/ -        problem <<-EOS.undent -          GitHub patches should use the full_index parameter: -            #{patch.url}?full_index=1 -        EOS -      end -    when /raw\.github\.com/, %r{gist\.github\.com/raw}, %r{gist\.github\.com/.+/raw}, -      %r{gist\.githubusercontent\.com/.+/raw} -      unless patch.url =~ /[a-fA-F0-9]{40}/ -        problem "GitHub/Gist patches should specify a revision:\n#{patch.url}" -      end -    when %r{https?://patch-diff\.githubusercontent\.com/raw/(.+)/(.+)/pull/(.+)\.(?:diff|patch)} -      problem <<-EOS.undent -        use GitHub pull request URLs: -          https://github.com/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}/pull/#{Regexp.last_match(3)}.patch?full_index=1 -        Rather than patch-diff: -          #{patch.url} -      EOS -    when %r{macports/trunk} -      problem "MacPorts patches should specify a revision instead of trunk:\n#{patch.url}" -    when %r{^http://trac\.macports\.org} -      problem "Patches from MacPorts Trac should be https://, not http:\n#{patch.url}" -    when %r{^http://bugs\.debian\.org} -      problem "Patches from Debian should be https://, not http:\n#{patch.url}" -    end -  end -    def audit_text      bin_names = Set.new      bin_names << formula.name diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 1c56749a3..e9e98d450 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -176,7 +176,10 @@ module Homebrew        rsrc.version = forced_version if forced_version        odie "No version specified!" unless rsrc.version        rsrc_path = rsrc.fetch -      if Utils.popen_read("/usr/bin/tar", "-tf", rsrc_path) =~ %r{/.*\.} +      gnu_tar_gtar_path = HOMEBREW_PREFIX/"opt/gnu-tar/bin/gtar" +      gnu_tar_gtar = gnu_tar_gtar_path if gnu_tar_gtar_path.executable? +      tar = which("gtar") || gnu_tar_gtar || which("tar") +      if Utils.popen_read(tar, "-tf", rsrc_path) =~ %r{/.*\.}          new_hash = rsrc_path.sha256        elsif new_url.include? ".tar"          odie "#{formula}: no url/#{hash_type} specified!" diff --git a/Library/Homebrew/dev-cmd/mirror.rb b/Library/Homebrew/dev-cmd/mirror.rb index e2492203d..6445bc34c 100644 --- a/Library/Homebrew/dev-cmd/mirror.rb +++ b/Library/Homebrew/dev-cmd/mirror.rb @@ -25,9 +25,9 @@ module Homebrew             "public_download_numbers": true,             "public_stats": true}          EOS -        curl "--silent", "--fail", "-u#{bintray_user}:#{bintray_key}", -             "-H", "Content-Type: application/json", -             "-d", package_blob, bintray_repo_url +        curl "--silent", "--fail", "--user", "#{bintray_user}:#{bintray_key}", +             "--header", "Content-Type: application/json", +             "--data", package_blob, bintray_repo_url          puts        end @@ -40,8 +40,8 @@ module Homebrew        content_url = "https://api.bintray.com/content/homebrew/mirror"        content_url += "/#{bintray_package}/#{f.pkg_version}/#{filename}"        content_url += "?publish=1" -      curl "--silent", "--fail", "-u#{bintray_user}:#{bintray_key}", -           "-T", download, content_url +      curl "--silent", "--fail", "--user", "#{bintray_user}:#{bintray_key}", +           "--upload-file", download, content_url        puts        ohai "Mirrored #{filename}!"      end diff --git a/Library/Homebrew/dev-cmd/pull.rb b/Library/Homebrew/dev-cmd/pull.rb index 9681bb2bc..dd2bc6270 100644 --- a/Library/Homebrew/dev-cmd/pull.rb +++ b/Library/Homebrew/dev-cmd/pull.rb @@ -228,7 +228,7 @@ module Homebrew            "https://github.com/BrewTestBot/homebrew-#{tap.repo}/compare/homebrew:master...pr-#{issue}"          end -        curl "--silent", "--fail", "-o", "/dev/null", "-I", bottle_commit_url +        curl "--silent", "--fail", "--output", "/dev/null", "--head", bottle_commit_url          safe_system "git", "checkout", "--quiet", "-B", bottle_branch, orig_revision          pull_patch bottle_commit_url, "bottle commit" @@ -303,7 +303,7 @@ module Homebrew        extra_msg = @description ? "(#{@description})" : nil        ohai "Fetching patch #{extra_msg}"        puts "Patch: #{patch_url}" -      curl patch_url, "-s", "-o", patchpath +      curl_download patch_url, to: patchpath      end      def apply_patch @@ -433,10 +433,10 @@ module Homebrew      end      version = info.pkg_version      ohai "Publishing on Bintray: #{package} #{version}" -    curl "-w", '\n', "--silent", "--fail", -         "-u#{creds[:user]}:#{creds[:key]}", "-X", "POST", -         "-H", "Content-Type: application/json", -         "-d", '{"publish_wait_for_secs": 0}', +    curl "--write-out", '\n', "--silent", "--fail", +         "--user", "#{creds[:user]}:#{creds[:key]}", "--request", "POST", +         "--header", "Content-Type: application/json", +         "--data", '{"publish_wait_for_secs": 0}',           "https://api.bintray.com/content/homebrew/#{repo}/#{package}/#{version}/publish"      true    rescue => e @@ -587,7 +587,7 @@ module Homebrew          # We're in the cache; make sure to force re-download          loop do            begin -            curl url, "-o", filename +            curl_download url, to: filename              break            rescue              if retry_count >= max_curl_retries @@ -606,7 +606,7 @@ module Homebrew    end    def check_bintray_mirror(name, url) -    headers = curl_output("--connect-timeout", "15", "--head", url)[0] +    headers, = curl_output("--connect-timeout", "15", "--location", "--head", url)      status_code = headers.scan(%r{^HTTP\/.* (\d+)}).last.first      return if status_code.start_with?("2")      opoo "The Bintray mirror #{url} is not reachable (HTTP status code #{status_code})." diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index c678171ac..ab2b0edb0 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -65,8 +65,6 @@ module Homebrew            args << "--devel"          end -        Sandbox.print_sandbox_message if Sandbox.test? -          Utils.safe_fork do            if Sandbox.test?              sandbox = Sandbox.new diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index 717334714..2a8b6e585 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -375,75 +375,59 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy        ohai "Downloading from #{url}"      end -    urls = actual_urls(url) -    unless urls.empty? -      ohai "Downloading from #{urls.last}" -      if !ENV["HOMEBREW_NO_INSECURE_REDIRECT"].nil? && url.start_with?("https://") && -         urls.any? { |u| !u.start_with? "https://" } -        puts "HTTPS to HTTP redirect detected & HOMEBREW_NO_INSECURE_REDIRECT is set." -        raise CurlDownloadStrategyError, url -      end -      url = urls.last -    end - -    curl url, "-C", downloaded_size, "-o", temporary_path +    curl_download resolved_url(url), to: temporary_path    end    # Curl options to be always passed to curl, -  # with raw head calls (`curl -I`) or with actual `fetch`. +  # with raw head calls (`curl --head`) or with actual `fetch`.    def _curl_opts -    copts = [] -    copts << "--user" << meta.fetch(:user) if meta.key?(:user) -    copts +    return ["--user" << meta.fetch(:user)] if meta.key?(:user) +    []    end -  def actual_urls(url) -    urls = [] -    curl_args = _curl_opts << "-I" << "-L" << url -    Utils.popen_read("curl", *curl_args).scan(/^Location: (.+)$/).map do |m| -      urls << URI.join(urls.last || url, m.first.chomp).to_s +  def resolved_url(url) +    redirect_url, _, status = curl_output( +      *_curl_opts, "--silent", "--head", +      "--write-out", "%{redirect_url}", +      "--output", "/dev/null", +      url.to_s +    ) + +    return url unless status.success? +    return url if redirect_url.empty? + +    ohai "Downloading from #{redirect_url}" +    if ENV["HOMEBREW_NO_INSECURE_REDIRECT"] && +       url.start_with?("https://") && !redirect_url.start_with?("https://") +      puts "HTTPS to HTTP redirect detected & HOMEBREW_NO_INSECURE_REDIRECT is set." +      raise CurlDownloadStrategyError, url      end -    urls -  end -  def downloaded_size -    temporary_path.size? || 0 +    redirect_url    end -  def curl(*args) +  def curl(*args, **options)      args.concat _curl_opts      args << "--connect-timeout" << "5" unless mirrors.empty? -    super +    super(*args, **options)    end  end  # Detect and download from Apache Mirror  class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy    def apache_mirrors -    rd, wr = IO.pipe -    buf = "" - -    pid = fork do -      ENV.delete "HOMEBREW_CURL_VERBOSE" -      rd.close -      $stdout.reopen(wr) -      $stderr.reopen(wr) -      curl "#{@url}&asjson=1" -    end -    wr.close +    mirrors, = Open3.capture3( +      *curl_args(*_curl_opts, "--silent", "--location", "#{@url}&asjson=1"), +    ) -    rd.readline if ARGV.verbose? # Remove Homebrew output -    buf << rd.read until rd.eof? -    rd.close -    Process.wait(pid) -    buf +    JSON.parse(mirrors)    end    def _fetch      return super if @tried_apache_mirror      @tried_apache_mirror = true -    mirrors = JSON.parse(apache_mirrors) +    mirrors = apache_mirrors      path_info = mirrors.fetch("path_info")      @url = mirrors.fetch("preferred") + path_info      @mirrors |= %W[https://archive.apache.org/dist/#{path_info}] @@ -460,7 +444,7 @@ end  class CurlPostDownloadStrategy < CurlDownloadStrategy    def _fetch      base_url, data = @url.split("?") -    curl base_url, "-d", data, "-C", downloaded_size, "-o", temporary_path +    curl_download base_url, "--data", data, to: temporary_path    end  end @@ -530,7 +514,7 @@ class S3DownloadStrategy < CurlDownloadStrategy        s3url = obj.public_url      end -    curl s3url, "-C", downloaded_size, "-o", temporary_path +    curl_download s3url, to: temporary_path    end  end @@ -566,7 +550,7 @@ class GitHubPrivateRepositoryDownloadStrategy < CurlDownloadStrategy    end    def _fetch -    curl download_url, "-C", downloaded_size, "-o", temporary_path +    curl_download download_url, to: temporary_path    end    private @@ -615,7 +599,7 @@ class GitHubPrivateRepositoryReleaseDownloadStrategy < GitHubPrivateRepositoryDo    def _fetch      # HTTP request header `Accept: application/octet-stream` is required.      # Without this, the GitHub API will respond with metadata, not binary. -    curl download_url, "-C", downloaded_size, "-o", temporary_path, "-H", "Accept: application/octet-stream" +    curl_download download_url, "--header", "Accept: application/octet-stream", to: temporary_path    end    private @@ -915,18 +899,27 @@ class GitHubGitDownloadStrategy < GitDownloadStrategy    def github_last_commit      return if ENV["HOMEBREW_NO_GITHUB_API"] -    output, _, status = curl_output "-H", "Accept: application/vnd.github.v3.sha", \ -      "-I", "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{@ref}" +    output, _, status = curl_output( +      "--silent", "--head", "--location", +      "-H", "Accept: application/vnd.github.v3.sha", +      "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{@ref}" +    ) + +    return unless status.success? -    commit = output[/^ETag: \"(\h+)\"/, 1] if status.success? +    commit = output[/^ETag: \"(\h+)\"/, 1]      version.update_commit(commit) if commit      commit    end    def multiple_short_commits_exist?(commit)      return if ENV["HOMEBREW_NO_GITHUB_API"] -    output, _, status = curl_output "-H", "Accept: application/vnd.github.v3.sha", \ -      "-I", "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{commit}" + +    output, _, status = curl_output( +      "--silent", "--head", "--location", +      "-H", "Accept: application/vnd.github.v3.sha", +      "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{commit}" +    )      !(status.success? && output && output[/^Status: (200)/, 1] == "200")    end @@ -1159,15 +1152,13 @@ class DownloadStrategyDetector        SubversionDownloadStrategy      when %r{^cvs://}        CVSDownloadStrategy -    when %r{^https?://(.+?\.)?googlecode\.com/hg} -      MercurialDownloadStrategy -    when %r{^hg://} +    when %r{^hg://}, %r{^https?://(.+?\.)?googlecode\.com/hg}        MercurialDownloadStrategy      when %r{^bzr://}        BazaarDownloadStrategy      when %r{^fossil://}        FossilDownloadStrategy -    when %r{^http://svn\.apache\.org/repos/}, %r{^svn\+http://} +    when %r{^svn\+http://}, %r{^http://svn\.apache\.org/repos/}        SubversionDownloadStrategy      when %r{^https?://(.+?\.)?sourceforge\.net/hgweb/}        MercurialDownloadStrategy diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 23a123c44..8b4cddc59 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -181,8 +181,8 @@ class TapFormulaAmbiguityError < RuntimeError      @name = name      @paths = paths      @formulae = paths.map do |path| -      path.to_s =~ HOMEBREW_TAP_PATH_REGEX -      "#{Tap.fetch(Regexp.last_match(1), Regexp.last_match(2))}/#{path.basename(".rb")}" +      match = path.to_s.match(HOMEBREW_TAP_PATH_REGEX) +      "#{Tap.fetch(match[:user], match[:repo])}/#{path.basename(".rb")}"      end      super <<-EOS.undent diff --git a/Library/Homebrew/extend/ENV/super.rb b/Library/Homebrew/extend/ENV/super.rb index 692fd3623..b518c22a1 100644 --- a/Library/Homebrew/extend/ENV/super.rb +++ b/Library/Homebrew/extend/ENV/super.rb @@ -138,7 +138,6 @@ module Superenv    def determine_pkg_config_libdir      PATH.new( -      "/usr/lib/pkgconfig",        homebrew_extra_pkg_config_paths,      ).existing    end diff --git a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb index f97a2dbbb..9c20cc7c6 100644 --- a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb +++ b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb @@ -28,7 +28,7 @@ module Superenv    # @private    def homebrew_extra_pkg_config_paths      paths = \ -      ["#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}"] +      ["/usr/lib/pkgconfig", "#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}"]      paths << "#{MacOS::X11.lib}/pkgconfig" << "#{MacOS::X11.share}/pkgconfig" if x11?      paths    end diff --git a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb index 10379c981..32e5774f6 100644 --- a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb +++ b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb @@ -6,7 +6,7 @@ module FormulaCellarChecks        formula.name.start_with?(formula_name)      end -    return if formula.name =~ /^php\d+$/ +    return if formula.name =~ /^php(@?\d+\.?\d*?)?$/      return if MacOS.version < :mavericks && formula.name.start_with?("postgresql")      return if MacOS.version < :yosemite  && formula.name.start_with?("memcached") @@ -67,11 +67,19 @@ module FormulaCellarChecks      checker = LinkageChecker.new(keg, formula)      return unless checker.broken_dylibs? -    problem_if_output <<-EOS.undent -      The installation was broken. -      Broken dylib links found: -        #{checker.broken_dylibs.to_a * "\n          "} +    output = <<-EOS.undent +      #{formula} has broken dynamic library links: +        #{checker.broken_dylibs.to_a * "\n  "}      EOS +    tab = Tab.for_keg(keg) +    if tab.poured_from_bottle +      output += <<-EOS.undent +        Rebuild this from source with: +          brew reinstall --build-from-source #{formula} +        If that's successful, file an issue#{formula.tap ? " here:\n  #{formula.tap.issues_url}" : "."} +      EOS +    end +    problem_if_output output    end    def audit_installed diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 5673a433f..8cea85a99 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -177,8 +177,8 @@ class Formula      @tap = if path == Formulary.core_path(name)        CoreTap.instance -    elsif path.to_s =~ HOMEBREW_TAP_PATH_REGEX -      Tap.fetch(Regexp.last_match(1), Regexp.last_match(2)) +    elsif match = path.to_s.match(HOMEBREW_TAP_PATH_REGEX) +      Tap.fetch(match[:user], match[:repo])      end      @full_name = full_name_with_optional_tap(name) @@ -2213,7 +2213,7 @@ class Formula      # depends_on :arch => :x86_64 # If this formula only builds on Intel x86 64-bit.      # depends_on :arch => :ppc # Only builds on PowerPC?      # depends_on :ld64 # Sometimes ld fails on `MacOS.version < :leopard`. Then use this. -    # depends_on :x11 # X11/XQuartz components. Non-optional X11 deps should go in Homebrew/Homebrew-x11 +    # depends_on :x11 # X11/XQuartz components.      # depends_on :osxfuse # Permits the use of the upstream signed binary or our source package.      # depends_on :tuntap # Does the same thing as above. This is vital for Yosemite and above.      # depends_on :mysql => :recommended</pre> diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 4e4a59972..6c5b8bdab 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -46,7 +46,7 @@ class FormulaInstaller      @ignore_deps = false      @only_deps = false      @build_from_source = ARGV.build_from_source? || ARGV.build_all_from_source? -    @build_bottle = ARGV.build_bottle? +    @build_bottle = false      @force_bottle = ARGV.force_bottle?      @interactive = false      @git = false @@ -543,7 +543,6 @@ class FormulaInstaller      fi.options           |= inherited_options      fi.options           &= df.options      fi.build_from_source  = ARGV.build_formula_from_source?(df) -    fi.build_bottle       = false      fi.force_bottle       = false      fi.verbose            = verbose?      fi.quieter            = quieter? @@ -680,8 +679,6 @@ class FormulaInstaller        #{formula.specified_path}      ].concat(build_argv) -    Sandbox.print_sandbox_message if Sandbox.formula?(formula) -      Utils.safe_fork do        # Invalidate the current sudo timestamp in case a build script calls sudo.        # Travis CI's Linux sudoless workers have a weird sudo that fails here. diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 195d15cec..dd67b4f24 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -165,7 +165,7 @@ module Formulary      def load_file        HOMEBREW_CACHE_FORMULA.mkpath        FileUtils.rm_f(path) -      curl url, "-o", path +      curl_download url, to: path        super      rescue MethodDeprecatedError => e        if url =~ %r{github.com/([\w-]+)/homebrew-([\w-]+)/} diff --git a/Library/Homebrew/gpg.rb b/Library/Homebrew/gpg.rb index cb9e367df..f56473df3 100644 --- a/Library/Homebrew/gpg.rb +++ b/Library/Homebrew/gpg.rb @@ -7,8 +7,8 @@ class Gpg        next unless gpg_short_version        gpg_version = Version.create(gpg_short_version.to_s)        @version = gpg_version -      gpg_version == Version.create("2.0") || -        gpg_version == Version.create("2.1") +      gpg_version == Version.create("2.1") || +        gpg_version == Version.create("2.0")      end    end @@ -20,7 +20,7 @@ class Gpg      find_gpg("gpg2")    end -  GPG_EXECUTABLE = gpg2 || gpg +  GPG_EXECUTABLE = gpg || gpg2    def self.available?      File.executable?(GPG_EXECUTABLE.to_s) @@ -38,6 +38,7 @@ class Gpg        Key-Length: 2048        Subkey-Type: RSA        Subkey-Length: 2048 +      Passphrase: ''        Name-Real: Testing        Name-Email: testing@foo.bar        Expire-Date: 1d diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 8fcbecfbd..92eab7ad3 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -253,6 +253,11 @@ class Keg        FileUtils.rm_rf bad_tap_opt if bad_tap_opt.directory?      end +    aliases.each do |a| +      alias_symlink = opt/a +      alias_symlink.delete if alias_symlink.symlink? || alias_symlink.exist? +    end +      Pathname.glob("#{opt_record}@*").each do |a|        a = a.basename        next if aliases.include?(a) diff --git a/Library/Homebrew/os/mac/xcode.rb b/Library/Homebrew/os/mac/xcode.rb index e23a7cf3d..6f7deaa10 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.31" +        when "10.13" then "900.0.34.1"          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/requirements/gpg2_requirement.rb b/Library/Homebrew/requirements/gpg2_requirement.rb index 97fabcca0..d570983eb 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" -  # MacGPG2/GPGTools installs GnuPG 2.0.x as a vanilla `gpg` symlink -  # pointing to `gpg2`, as do we. Ensure we're actually using a 2.0 `gpg`. -  # Support both the 2.0.x "stable" and 2.1.x "modern" series. -  satisfy(build_env: false) { Gpg.gpg2 || Gpg.gpg } +  # 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. +  satisfy(build_env: false) { Gpg.gpg || Gpg.gpg2 }  end diff --git a/Library/Homebrew/rubocops.rb b/Library/Homebrew/rubocops.rb index 4323e044c..b1144e075 100644 --- a/Library/Homebrew/rubocops.rb +++ b/Library/Homebrew/rubocops.rb @@ -6,7 +6,7 @@ require_relative "./rubocops/homepage_cop"  require_relative "./rubocops/text_cop"  require_relative "./rubocops/caveats_cop"  require_relative "./rubocops/checksum_cop" -require_relative "./rubocops/legacy_patches_cop" +require_relative "./rubocops/patches_cop"  require_relative "./rubocops/conflicts_cop"  require_relative "./rubocops/options_cop"  require_relative "./rubocops/urls_cop" diff --git a/Library/Homebrew/rubocops/extend/formula_cop.rb b/Library/Homebrew/rubocops/extend/formula_cop.rb index 4be0c0fe3..862664010 100644 --- a/Library/Homebrew/rubocops/extend/formula_cop.rb +++ b/Library/Homebrew/rubocops/extend/formula_cop.rb @@ -1,4 +1,5 @@  require "parser/current" +require_relative "../../extend/string"  module RuboCop    module Cop @@ -138,17 +139,14 @@ module RuboCop          case type          when :required -          type_match = !node.method_args.nil? && -                       (node.method_args.first.str_type? || node.method_args.first.sym_type?) +          type_match = required_dependency?(node)            if type_match && !name_match -            name_match = node_equals?(node.method_args.first, name) +            name_match = required_dependency_name?(node, name)            end          when :build, :optional, :recommended, :run -          type_match = !node.method_args.nil? && -                       node.method_args.first.hash_type? && -                       node.method_args.first.values.first.children.first == type +          type_match = dependency_type_hash_match?(node, type)            if type_match && !name_match -            name_match = node_equals?(node.method_args.first.keys.first.children.first, name) +            name_match = dependency_name_hash_match?(node, name)            end          else            type_match = false @@ -161,6 +159,22 @@ module RuboCop          type_match && name_match        end +      def_node_search :required_dependency?, <<-EOS.undent +        (send nil :depends_on ({str sym} _)) +      EOS + +      def_node_search :required_dependency_name?, <<-EOS.undent +        (send nil :depends_on ({str sym} %1)) +      EOS + +      def_node_search :dependency_type_hash_match?, <<-EOS.undent +        (hash (pair ({str sym} _) ({str sym} %1))) +      EOS + +      def_node_search :dependency_name_hash_match?, <<-EOS.undent +        (hash (pair ({str sym} %1) ({str sym} _))) +      EOS +        # To compare node with appropriate Ruby variable        def node_equals?(node, var)          node == Parser::CurrentRuby.parse(var.inspect) diff --git a/Library/Homebrew/rubocops/legacy_patches_cop.rb b/Library/Homebrew/rubocops/patches_cop.rb index e569f650e..fb14d8acc 100644 --- a/Library/Homebrew/rubocops/legacy_patches_cop.rb +++ b/Library/Homebrew/rubocops/patches_cop.rb @@ -4,9 +4,16 @@ require_relative "../extend/string"  module RuboCop    module Cop      module FormulaAudit -      # This cop checks for and audits legacy patches in Formulae -      class LegacyPatches < FormulaCop +      # This cop audits patches in Formulae +      class Patches < FormulaCop          def audit_formula(_node, _class_node, _parent_class_node, body) +          external_patches = find_all_blocks(body, :patch) +          external_patches.each do |patch_block| +            url_node = find_every_method_call_by_name(patch_block, :url).first +            url_string = parameters(url_node).first +            patch_problems(url_string) +          end +            patches_node = find_method_def(body, :patches)            return if patches_node.nil?            legacy_patches = find_strings(patches_node) @@ -14,6 +21,8 @@ module RuboCop            legacy_patches.each { |p| patch_problems(p) }          end +        private +          def patch_problems(patch)            patch_url = string_content(patch)            gh_patch_patterns = Regexp.union([%r{/raw\.github\.com/}, @@ -30,7 +39,7 @@ module RuboCop            if match_obj = regex_match_group(patch, gh_patch_diff_pattern)              problem <<-EOS.undent                use GitHub pull request URLs: -                https://github.com/#{match_obj[1]}/#{match_obj[2]}/pull/#{match_ojb[3]}.patch +                https://github.com/#{match_obj[1]}/#{match_obj[2]}/pull/#{match_obj[3]}.patch                Rather than patch-diff:                  #{patch_url}              EOS diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 0de970773..8c662857e 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -18,12 +18,6 @@ class Sandbox      !ARGV.no_sandbox?    end -  def self.print_sandbox_message -    return if @printed_sandbox_message -    ohai "Using the sandbox" -    @printed_sandbox_message = true -  end -    def initialize      @profile = SandboxProfile.new    end diff --git a/Library/Homebrew/shims/super/cc b/Library/Homebrew/shims/super/cc index d894d3d69..afe72156f 100755 --- a/Library/Homebrew/shims/super/cc +++ b/Library/Homebrew/shims/super/cc @@ -43,6 +43,8 @@ class Cmd        else          :cc        end +    elsif @args.include?("-xc++-header") || @args.each_cons(2).include?(["-x", "c++-header"]) +      :cxx      elsif @args.include? "-E"        :ccE      else diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index c6e704350..49d818f0f 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -51,8 +51,18 @@ class SoftwareSpec      @owner = owner      @resource.owner = self      resources.each_value do |r| -      r.owner     = self -      r.version ||= (version.head? ? Version.create("HEAD") : version.dup) +      r.owner = self +      r.version ||= begin +        if version.nil? +          raise "#{full_name}: version missing for \"#{r.name}\" resource!" +        end + +        if version.head? +          Version.create("HEAD") +        else +          version.dup +        end +      end      end      patches.each { |p| p.owner = self }    end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 84953726f..f232be428 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -42,9 +42,9 @@ class Tap    end    def self.from_path(path) -    path.to_s =~ HOMEBREW_TAP_PATH_REGEX -    raise "Invalid tap path '#{path}'" unless Regexp.last_match(1) -    fetch(Regexp.last_match(1), Regexp.last_match(2)) +    match = path.to_s.match(HOMEBREW_TAP_PATH_REGEX) +    raise "Invalid tap path '#{path}'" unless match +    fetch(match[:user], match[:repo])    rescue      # No need to error as a nil tap is sufficient to show failure.      nil diff --git a/Library/Homebrew/tap_constants.rb b/Library/Homebrew/tap_constants.rb index 773eff816..23bbc61ed 100644 --- a/Library/Homebrew/tap_constants.rb +++ b/Library/Homebrew/tap_constants.rb @@ -3,7 +3,7 @@ HOMEBREW_TAP_FORMULA_REGEX = %r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}  # match taps' casks, e.g. someuser/sometap/somecask  HOMEBREW_TAP_CASK_REGEX = %r{^([\w-]+)/([\w-]+)/([a-z0-9\-]+)$}  # match taps' directory paths, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap -HOMEBREW_TAP_DIR_REGEX = %r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Taps/([\w-]+)/([\w-]+)} +HOMEBREW_TAP_DIR_REGEX = %r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}  # match taps' formula paths, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula  HOMEBREW_TAP_PATH_REGEX = Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{/(.*)}.source)  # match the default and the versions brew-cask tap e.g. Caskroom/cask or Caskroom/versions diff --git a/Library/Homebrew/test/cask/download_strategy_spec.rb b/Library/Homebrew/test/cask/download_strategy_spec.rb index 222352c07..17da1e36e 100644 --- a/Library/Homebrew/test/cask/download_strategy_spec.rb +++ b/Library/Homebrew/test/cask/download_strategy_spec.rb @@ -26,9 +26,12 @@ describe "download strategies", :cask do        downloader.fetch        expect(downloader).to have_received(:curl).with( +        "--location", +        "--remote-time", +        "--continue-at", "-", +        "--output", kind_of(Pathname),          cask.url.to_s, -        "-C", 0, -        "-o", kind_of(Pathname) +        user_agent: :default        )      end @@ -36,25 +39,25 @@ describe "download strategies", :cask do        let(:url_options) { { user_agent: "Mozilla/25.0.1" } }        it "adds the appropriate curl args" do -        curl_args = [] -        allow(downloader).to receive(:curl) { |*args| curl_args = args } +        expect(downloader).to receive(:safe_system) { |*args| +          expect(args.each_cons(2)).to include(["--user-agent", "Mozilla/25.0.1"]) +        }          downloader.fetch - -        expect(curl_args.each_cons(2)).to include(["-A", "Mozilla/25.0.1"])        end      end      context "with a generalized fake user agent" do +      alias_matcher :a_string_matching, :match +        let(:url_options) { { user_agent: :fake } }        it "adds the appropriate curl args" do -        curl_args = [] -        allow(downloader).to receive(:curl) { |*args| curl_args = args } +        expect(downloader).to receive(:safe_system) { |*args| +          expect(args.each_cons(2).to_a).to include(["--user-agent", a_string_matching(/Mozilla.*Mac OS X 10.*AppleWebKit/)]) +        }          downloader.fetch - -        expect(curl_args.each_cons(2)).to include(["-A", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10) https://caskroom.github.io"])        end      end @@ -138,7 +141,7 @@ describe "download strategies", :cask do    describe Hbc::SubversionDownloadStrategy do      let(:url_options) { { using: :svn } }      let(:fake_system_command) { class_double(Hbc::SystemCommand) } -    let(:downloader) { Hbc::SubversionDownloadStrategy.new(cask, fake_system_command) } +    let(:downloader) { Hbc::SubversionDownloadStrategy.new(cask, command: fake_system_command) }      before do        allow(fake_system_command).to receive(:run!)      end @@ -147,7 +150,7 @@ describe "download strategies", :cask do        allow(downloader).to receive(:compress)        allow(downloader).to receive(:fetch_repo) -      expect(downloader.fetch).to equal(downloader.tarball_path) +      expect(downloader.fetch).to equal(downloader.cached_location)      end      it "calls fetch_repo with default arguments for a simple Cask" do @@ -237,44 +240,5 @@ describe "download strategies", :cask do          )        end      end - -    it "runs tar to serialize svn downloads" do -      # sneaky stub to remake the directory, since homebrew code removes it -      # before tar is called -      allow(downloader).to receive(:fetch_repo) { -        downloader.cached_location.mkdir -      } - -      downloader.fetch - -      expect(fake_system_command).to have_received(:run!).with( -        "/usr/bin/tar", -        hash_including(args: [ -                         '-s/^\\.//', -                         "--exclude", -                         ".svn", -                         "-cf", -                         downloader.tarball_path, -                         "--", -                         ".", -                       ]), -      ) -    end    end - -  # does not work yet, because (for unknown reasons), the tar command -  # returns an error code when running under the test suite -  # it 'creates a tarball matching the expected checksum' do -  #   cask = Hbc::CaskLoader.load('svn-download-check-cask') -  #   downloader = Hbc::SubversionDownloadStrategy.new(cask) -  #   # special mocking required for tar to have something to work with -  #   def downloader.fetch_repo(target, url, revision = nil, ignore_externals=false) -  #     target.mkpath -  #     FileUtils.touch(target.join('empty_file.txt')) -  #     File.utime(1000,1000,target.join('empty_file.txt')) -  #   end -  #   expect(downloader.fetch).to equal(downloader.tarball_path) -  #   d = Hbc::Download.new(cask) -  #   d.send(:_check_sums, downloader.tarball_path, cask.sums) -  # end  end diff --git a/Library/Homebrew/test/cask/dsl/appcast_spec.rb b/Library/Homebrew/test/cask/dsl/appcast_spec.rb index b8903b1be..ccc6a4633 100644 --- a/Library/Homebrew/test/cask/dsl/appcast_spec.rb +++ b/Library/Homebrew/test/cask/dsl/appcast_spec.rb @@ -33,13 +33,18 @@ describe Hbc::DSL::Appcast do    describe "#calculate_checkpoint" do      before do -      expect(Hbc::SystemCommand).to receive(:run).with(*cmd_args).and_return(cmd_result) +      expect(Hbc::SystemCommand).to receive(:run) do |executable, **options| +        expect(executable).to eq "/usr/bin/curl" +        expect(options[:args]).to include(*cmd_args) +        expect(options[:print_stderr]).to be false +        cmd_result +      end        allow(cmd_result).to receive(:success?).and_return(cmd_success)        allow(cmd_result).to receive(:stdout).and_return(cmd_stdout)      end      context "when server returns a successful HTTP status" do -      let(:cmd_args) { ["/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, "--fail", uri], print_stderr: false] } +      let(:cmd_args) { [HOMEBREW_USER_AGENT_FAKE_SAFARI, "--compressed", "--location", "--fail", uri] }        let(:cmd_result) { double("Hbc::SystemCommand::Result") }        let(:cmd_success) { true }        let(:cmd_stdout) { "hello world" } @@ -56,7 +61,7 @@ describe Hbc::DSL::Appcast do      end      context "when server returns a non-successful HTTP status" do -      let(:cmd_args) { ["/usr/bin/curl", args: ["--compressed", "--location", "--user-agent", Hbc::URL::FAKE_USER_AGENT, "--fail", uri], print_stderr: false] } +      let(:cmd_args) { [HOMEBREW_USER_AGENT_FAKE_SAFARI, "--compressed", "--location", "--fail", uri] }        let(:cmd_result) { double("Hbc::SystemCommand::Result") }        let(:cmd_success) { false }        let(:cmd_stdout) { "some error message from the server" } diff --git a/Library/Homebrew/test/cask/pkg_spec.rb b/Library/Homebrew/test/cask/pkg_spec.rb index 56061c9fd..07443e76e 100644 --- a/Library/Homebrew/test/cask/pkg_spec.rb +++ b/Library/Homebrew/test/cask/pkg_spec.rb @@ -5,16 +5,16 @@ describe Hbc::Pkg, :cask do      let(:pkg) { described_class.new("my.fake.pkg", fake_system_command) }      it "removes files and dirs referenced by the pkg" do -      some_files = Array.new(3) { Pathname.new(Tempfile.new("testfile").path) } +      some_files = Array.new(3) { Pathname.new(Tempfile.new("plain_file").path) }        allow(pkg).to receive(:pkgutil_bom_files).and_return(some_files) -      some_specials = Array.new(3) { Pathname.new(Tempfile.new("testfile").path) } +      some_specials = Array.new(3) { Pathname.new(Tempfile.new("special_file").path) }        allow(pkg).to receive(:pkgutil_bom_specials).and_return(some_specials) -      some_dirs = Array.new(3) { Pathname.new(Dir.mktmpdir) } +      some_dirs = Array.new(3) { mktmpdir }        allow(pkg).to receive(:pkgutil_bom_dirs).and_return(some_dirs) -      root_dir = Pathname.new(Dir.mktmpdir) +      root_dir = Pathname.new(mktmpdir)        allow(pkg).to receive(:root).and_return(root_dir)        allow(pkg).to receive(:forget) @@ -55,8 +55,8 @@ describe Hbc::Pkg, :cask do      end      it "removes broken symlinks" do -      fake_dir  = Pathname.new(Dir.mktmpdir) -      fake_root = Pathname.new(Dir.mktmpdir) +      fake_root = mktmpdir +      fake_dir  = mktmpdir        fake_file = fake_dir.join("ima_file").tap { |path| FileUtils.touch(path) }        intact_symlink = fake_dir.join("intact_symlink").tap { |path| path.make_symlink(fake_file) } @@ -77,13 +77,13 @@ describe Hbc::Pkg, :cask do      end      it "snags permissions on ornery dirs, but returns them afterwards" do -      fake_root = Pathname.new(Dir.mktmpdir) -      fake_dir = Pathname.new(Dir.mktmpdir) -      fake_file = fake_dir.join("ima_installed_file").tap { |path| FileUtils.touch(path) } +      fake_root = mktmpdir +      fake_dir = mktmpdir +      fake_file = fake_dir.join("ima_unrelated_file").tap { |path| FileUtils.touch(path) }        fake_dir.chmod(0000)        allow(pkg).to receive(:pkgutil_bom_specials).and_return([]) -      allow(pkg).to receive(:pkgutil_bom_files).and_return([fake_file]) +      allow(pkg).to receive(:pkgutil_bom_files).and_return([])        allow(pkg).to receive(:pkgutil_bom_dirs).and_return([fake_dir])        allow(pkg).to receive(:root).and_return(fake_root)        allow(pkg).to receive(:forget) @@ -91,8 +91,12 @@ describe Hbc::Pkg, :cask do        pkg.uninstall        expect(fake_dir).to be_a_directory -      expect(fake_file).not_to be_a_file -      expect((fake_dir.stat.mode % 01000).to_s(8)).to eq("0") +      expect((fake_dir.stat.mode % 01000)).to eq(0) + +      fake_dir.chmod(0777) +      expect(fake_file).to be_a_file + +      FileUtils.rm_r fake_dir      end    end diff --git a/Library/Homebrew/test/cmd/search_spec.rb b/Library/Homebrew/test/cmd/search_spec.rb index 06b7073d8..77c2c6352 100644 --- a/Library/Homebrew/test/cmd/search_spec.rb +++ b/Library/Homebrew/test/cmd/search_spec.rb @@ -1,6 +1,7 @@  describe "brew search", :integration_test do    before(:each) do      setup_test_formula "testball" +    setup_remote_tap "caskroom/cask"    end    it "lists all available Formulae when no argument is given" do @@ -13,7 +14,7 @@ describe "brew search", :integration_test do    it "supports searching by name" do      expect { brew "search", "testball" }        .to output(/testball/).to_stdout -      .and not_to_output.to_stderr +      .and output(/Searching/).to_stderr        .and be_a_success    end @@ -24,6 +25,13 @@ describe "brew search", :integration_test do        .and be_a_success    end +  it "falls back to a GitHub tap search when no formula is found", :needs_network do +    expect { brew "search", "caskroom/cask/firefox" } +      .to output(/firefox/).to_stdout +      .and output(/Searching/).to_stderr +      .and be_a_success +  end +    describe "--desc" do      let(:desc_cache) { HOMEBREW_CACHE/"desc_cache.json" } @@ -44,7 +52,7 @@ describe "brew search", :integration_test do      "fink" => "http://pdb.finkproject.org/pdb/browse.php?summary=testball",      "debian" => "https://packages.debian.org/search?keywords=testball&searchon=names&suite=all§ion=all",      "opensuse" => "https://software.opensuse.org/search?q=testball", -    "fedora" => "https://admin.fedoraproject.org/pkgdb/packages/%2Atestball%2A/", +    "fedora" => "https://apps.fedoraproject.org/packages/s/testball",      "ubuntu" => "http://packages.ubuntu.com/search?keywords=testball&searchon=names&suite=all§ion=all",    }.each do |flag, url|      specify "--#{flag}" do diff --git a/Library/Homebrew/test/formula_installer_spec.rb b/Library/Homebrew/test/formula_installer_spec.rb index c3573ae94..7365b2758 100644 --- a/Library/Homebrew/test/formula_installer_spec.rb +++ b/Library/Homebrew/test/formula_installer_spec.rb @@ -133,7 +133,7 @@ describe FormulaInstaller do      }.to raise_error(CannotInstallFormulaError)    end -  describe "#install_requirement_formula?", :focus do +  describe "#install_requirement_formula?" do      before do        @requirement = Python3Requirement.new        @requirement_dependency = @requirement.to_dependency diff --git a/Library/Homebrew/test/rubocops/legacy_patches_cop_spec.rb b/Library/Homebrew/test/rubocops/patches_cop_spec.rb index a08fa614d..4bd79bf35 100644 --- a/Library/Homebrew/test/rubocops/legacy_patches_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/patches_cop_spec.rb @@ -1,9 +1,9 @@  require "rubocop"  require "rubocop/rspec/support"  require_relative "../../extend/string" -require_relative "../../rubocops/legacy_patches_cop" +require_relative "../../rubocops/patches_cop" -describe RuboCop::Cop::FormulaAudit::LegacyPatches do +describe RuboCop::Cop::FormulaAudit::Patches do    subject(:cop) { described_class.new }    context "When auditing legacy patches" do @@ -47,6 +47,7 @@ describe RuboCop::Cop::FormulaAudit::LegacyPatches do          "https://mirrors.ustc.edu.cn/macports/trunk/",          "http://trac.macports.org/export/102865/trunk/dports/mail/uudeview/files/inews.c.patch",          "http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=5;filename=patch-libunac1.txt;att=1;bug=623340", +        "https://patch-diff.githubusercontent.com/raw/foo/foo-bar/pull/100.patch",        ]        patch_urls.each do |patch_url|          source = <<-EOS.undent @@ -84,6 +85,15 @@ describe RuboCop::Cop::FormulaAudit::LegacyPatches do                                    line: 5,                                    column: 5,                                    source: source }] +        elsif patch_url =~ %r{https?://patch-diff\.githubusercontent\.com/raw/(.+)/(.+)/pull/(.+)\.(?:diff|patch)} +          expected_offenses = [{  message:  "use GitHub pull request URLs:\n"\ +                                            "  https://github.com/foo/foo-bar/pull/100.patch\n"\ +                                            "Rather than patch-diff:\n"\ +                                            "  https://patch-diff.githubusercontent.com/raw/foo/foo-bar/pull/100.patch\n", +                                  severity: :convention, +                                  line: 5, +                                  column: 5, +                                  source: source }]          end          expected_offenses.zip([cop.offenses.last]).each do |expected, actual|            expect_offense(expected, actual) @@ -125,4 +135,67 @@ describe RuboCop::Cop::FormulaAudit::LegacyPatches do        end      end    end + +  context "When auditing external patches" do +    it "Patch URLs" do +      patch_urls = [ +        "https://raw.github.com/mogaal/sendemail", +        "https://mirrors.ustc.edu.cn/macports/trunk/", +        "http://trac.macports.org/export/102865/trunk/dports/mail/uudeview/files/inews.c.patch", +        "http://bugs.debian.org/cgi-bin/bugreport.cgi?msg=5;filename=patch-libunac1.txt;att=1;bug=623340", +        "https://patch-diff.githubusercontent.com/raw/foo/foo-bar/pull/100.patch", +      ] +      patch_urls.each do |patch_url| +        source = <<-EOS.undent +          class Foo < Formula +            homepage "ftp://example.com/foo" +            url "http://example.com/foo-1.0.tgz" +            patch do +              url "#{patch_url}" +              sha256 "63376b8fdd6613a91976106d9376069274191860cd58f039b29ff16de1925621" +            end +          end +        EOS + +        inspect_source(cop, source) +        if patch_url =~ %r{/raw\.github\.com/} +          expected_offenses = [{  message: "GitHub/Gist patches should specify a revision:\n#{patch_url}", +                                  severity: :convention, +                                  line: 5, +                                  column: 16, +                                  source: source }] +        elsif patch_url =~ %r{macports/trunk} +          expected_offenses = [{  message:  "MacPorts patches should specify a revision instead of trunk:\n#{patch_url}", +                                  severity: :convention, +                                  line: 5, +                                  column: 37, +                                  source: source }] +        elsif patch_url =~ %r{^http://trac\.macports\.org} +          expected_offenses = [{  message:  "Patches from MacPorts Trac should be https://, not http:\n#{patch_url}", +                                  severity: :convention, +                                  line: 5, +                                  column: 9, +                                  source: source }] +        elsif patch_url =~ %r{^http://bugs\.debian\.org} +          expected_offenses = [{  message:  "Patches from Debian should be https://, not http:\n#{patch_url}", +                                  severity: :convention, +                                  line: 5, +                                  column: 9, +                                  source: source }] +        elsif patch_url =~ %r{https?://patch-diff\.githubusercontent\.com/raw/(.+)/(.+)/pull/(.+)\.(?:diff|patch)} +          expected_offenses = [{  message:  "use GitHub pull request URLs:\n"\ +                                            "  https://github.com/foo/foo-bar/pull/100.patch\n"\ +                                            "Rather than patch-diff:\n"\ +                                            "  https://patch-diff.githubusercontent.com/raw/foo/foo-bar/pull/100.patch\n", +                                  severity: :convention, +                                  line: 5, +                                  column: 9, +                                  source: source }] +        end +        expected_offenses.zip([cop.offenses.last]).each do |expected, actual| +          expect_offense(expected, actual) +        end +      end +    end +  end  end diff --git a/Library/Homebrew/test/rubocops/text_cop_spec.rb b/Library/Homebrew/test/rubocops/text_cop_spec.rb index b218e9c25..ec13c4041 100644 --- a/Library/Homebrew/test/rubocops/text_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/text_cop_spec.rb @@ -7,6 +7,54 @@ describe RuboCop::Cop::FormulaAudit::Text do    subject(:cop) { described_class.new }    context "When auditing formula text" do +    it "with both openssl and libressl optional dependencies" do +      source = <<-EOS.undent +        class Foo < Formula +          url "http://example.com/foo-1.0.tgz" +          homepage "http://example.com" + +          depends_on "openssl" +          depends_on "libressl" => :optional +        end +      EOS + +      expected_offenses = [{  message: "Formulae should not depend on both OpenSSL and LibreSSL (even optionally).", +                              severity: :convention, +                              line: 6, +                              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 both openssl and libressl dependencies" do +      source = <<-EOS.undent +        class Foo < Formula +          url "http://example.com/foo-1.0.tgz" +          homepage "http://example.com" + +          depends_on "openssl" +          depends_on "libressl" +        end +      EOS + +      expected_offenses = [{  message: "Formulae should not depend on both OpenSSL and LibreSSL (even optionally).", +                              severity: :convention, +                              line: 6, +                              column: 2, +                              source: source }] + +      inspect_source(cop, source) + +      expected_offenses.zip(cop.offenses).each do |expected, actual| +        expect_offense(expected, actual) +      end +    end +      it "When xcodebuild is called without SYMROOT" do        source = <<-EOS.undent          class Foo < Formula diff --git a/Library/Homebrew/test/utils/github_spec.rb b/Library/Homebrew/test/utils/github_spec.rb index 9b539262f..9322898ee 100644 --- a/Library/Homebrew/test/utils/github_spec.rb +++ b/Library/Homebrew/test/utils/github_spec.rb @@ -2,12 +2,38 @@ require "utils/github"  describe GitHub do    describe "::search_code", :needs_network do -    it "searches code" do -      results = subject.search_code("repo:Homebrew/brew", "path:/", "filename:readme", "language:markdown") +    it "queries GitHub code with the passed paramaters" do +      results = subject.search_code(repo: "Homebrew/brew", path: "/", +                                    filename: "readme", language: "markdown")        expect(results.count).to eq(1)        expect(results.first["name"]).to eq("README.md")        expect(results.first["path"]).to eq("README.md")      end    end + +  describe "::query_string" do +    it "builds a query with the given hash parameters formatted as key:value" do +      query = subject.query_string(user: "Homebrew", repo: "brew") +      expect(query).to eq("q=user%3AHomebrew+repo%3Abrew&per_page=100") +    end + +    it "adds a variable number of top-level string parameters to the query when provided" do +      query = subject.query_string("value1", "value2", user: "Homebrew") +      expect(query).to eq("q=value1+value2+user%3AHomebrew&per_page=100") +    end + +    it "turns array values into multiple key:value parameters" do +      query = subject.query_string(user: ["Homebrew", "caskroom"]) +      expect(query).to eq("q=user%3AHomebrew+user%3Acaskroom&per_page=100") +    end +  end + +  describe "::search_issues", :needs_network do +    it "queries GitHub issues with the passed parameters" do +      results = subject.search_issues("brew search", repo: "Homebrew/brew", author: "avetamine", is: "closed") +      expect(results).not_to be_empty +      expect(results.last["title"]).to eq("brew search : 422 Unprocessable Entity") +    end +  end  end diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index 5a40ae846..52d03c93e 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -1,42 +1,55 @@  require "pathname"  require "open3" -def curl_args(options = {}) +def curl_executable    curl = Pathname.new ENV["HOMEBREW_CURL"]    curl = Pathname.new "/usr/bin/curl" unless curl.exist? -  raise "#{curl} is not executable" unless curl.exist? && curl.executable? +  return curl if curl.executable? +  raise "#{curl} is not executable" +end +def curl_args(*extra_args, show_output: false, user_agent: :default)    args = [ -    curl.to_s, -    "--remote-time", -    "--location", +    curl_executable.to_s, +    "--show-error",    ] -  case options[:user_agent] -  when :browser -    args << "--user-agent" << HOMEBREW_USER_AGENT_FAKE_SAFARI +  args << "--user-agent" << case user_agent +  when :browser, :fake +    HOMEBREW_USER_AGENT_FAKE_SAFARI +  when :default +    HOMEBREW_USER_AGENT_CURL    else -    args << "--user-agent" << HOMEBREW_USER_AGENT_CURL +    user_agent    end -  unless options[:show_output] +  unless show_output +    args << "--fail"      args << "--progress-bar" unless ARGV.verbose?      args << "--verbose" if ENV["HOMEBREW_CURL_VERBOSE"] -    args << "--fail"      args << "--silent" if !$stdout.tty? || ENV["TRAVIS"]    end -  args += options[:extra_args] if options[:extra_args] -  args +  args + extra_args  end  def curl(*args) -  safe_system(*curl_args(extra_args: args)) +  safe_system(*curl_args(*args))  end -def curl_output(*args) -  curl_args = curl_args(extra_args: args, show_output: true) -  Open3.popen3(*curl_args) do |_, stdout, stderr, wait_thread| -    [stdout.read, stderr.read, wait_thread.value] +def curl_download(*args, to: nil, **options) +  continue_at ||= "-" +  curl("--location", "--remote-time", "--continue-at", continue_at, "--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 == "-" +    continue_at = "0" +    retry    end + +  raise +end + +def curl_output(*args, **options) +  Open3.capture3(*curl_args(*args, show_output: true, **options))  end diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 1a781cee6..a1cf5fbba 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -133,7 +133,7 @@ module GitHub    def open(url, data: nil, scopes: [].freeze)      # This is a no-op if the user is opting out of using the GitHub API. -    return if ENV["HOMEBREW_NO_GITHUB_API"] +    return block_given? ? yield({}) : {} if ENV["HOMEBREW_NO_GITHUB_API"]      args = %W[--header application/vnd.github.v3+json --write-out \n%{http_code}]      args += curl_args @@ -166,7 +166,7 @@ module GitHub        args += ["--dump-header", headers_tmpfile.path] -      output, errors, status = curl_output(url.to_s, *args) +      output, errors, status = curl_output(url.to_s, "--location", *args)        output, _, http_code = output.rpartition("\n")        output, _, http_code = output.rpartition("\n") if http_code == "000"        headers = headers_tmpfile.read @@ -227,72 +227,60 @@ module GitHub      end    end -  def issues_matching(query, qualifiers = {}) -    uri = URI.parse("#{API_URL}/search/issues") -    uri.query = build_query_string(query, qualifiers) -    open(uri) { |json| json["items"] } +  def search_issues(query, **qualifiers) +    search("issues", query, **qualifiers)    end    def repository(user, repo) -    open(URI.parse("#{API_URL}/repos/#{user}/#{repo}")) +    open(url_to("repos", user, repo))    end -  def search_code(*params) -    uri = URI.parse("#{API_URL}/search/code") -    uri.query = "q=#{uri_escape(params.join(" "))}" -    open(uri) { |json| json["items"] } -  end - -  def build_query_string(query, qualifiers) -    s = "q=#{uri_escape(query)}+" -    s << build_search_qualifier_string(qualifiers) -    s << "&per_page=100" -  end - -  def build_search_qualifier_string(qualifiers) -    { -      repo: "Homebrew/homebrew-core", -      in: "title", -    }.update(qualifiers).map do |qualifier, value| -      "#{qualifier}:#{value}" -    end.join("+") -  end - -  def uri_escape(query) -    if URI.respond_to?(:encode_www_form_component) -      URI.encode_www_form_component(query) -    else -      require "erb" -      ERB::Util.url_encode(query) -    end +  def search_code(**qualifiers) +    search("code", **qualifiers)    end    def issues_for_formula(name, options = {})      tap = options[:tap] || CoreTap.instance -    issues_matching(name, state: "open", repo: "#{tap.user}/homebrew-#{tap.repo}") +    search_issues(name, state: "open", repo: "#{tap.user}/homebrew-#{tap.repo}")    end    def print_pull_requests_matching(query) -    return [] if ENV["HOMEBREW_NO_GITHUB_API"] - -    open_or_closed_prs = issues_matching(query, type: "pr") +    open_or_closed_prs = search_issues(query, type: "pr")      open_prs = open_or_closed_prs.select { |i| i["state"] == "open" } -    if !open_prs.empty? +    prs = if !open_prs.empty?        puts "Open pull requests:" -      prs = open_prs -    elsif !open_or_closed_prs.empty? -      puts "Closed pull requests:" -      prs = open_or_closed_prs +      open_prs      else -      return +      puts "Closed pull requests:" unless open_or_closed_prs.empty? +      open_or_closed_prs      end      prs.each { |i| puts "#{i["title"]} (#{i["html_url"]})" }    end    def private_repo?(full_name) -    uri = URI.parse("#{API_URL}/repos/#{full_name}") +    uri = url_to "repos", full_name      open(uri) { |json| json["private"] }    end + +  def query_string(*main_params, **qualifiers) +    params = main_params + +    params += qualifiers.flat_map do |key, value| +      Array(value).map { |v| "#{key}:#{v}" } +    end + +    "q=#{URI.encode_www_form_component(params.join(" "))}&per_page=100" +  end + +  def url_to(*subroutes) +    URI.parse([API_URL, *subroutes].join("/")) +  end + +  def search(entity, *queries, **qualifiers) +    uri = url_to "search", entity +    uri.query = query_string(*queries, **qualifiers) +    open(uri) { |json| json.fetch("items", []) } +  end  end  | 
