diff options
Diffstat (limited to 'Library')
143 files changed, 2925 insertions, 1391 deletions
diff --git a/Library/.rubocop.yml b/Library/.rubocop.yml index a782c1117..12886a508 100644 --- a/Library/.rubocop.yml +++ b/Library/.rubocop.yml @@ -8,10 +8,19 @@ AllCops: require: ./Homebrew/rubocops.rb -Homebrew/CorrectBottleBlock: +FormulaAuditStrict/BottleBlock: Enabled: true -Homebrew/FormulaDesc: +FormulaAuditStrict/Desc: + Enabled: true + +FormulaAuditStrict/ComponentsOrder: + Enabled: true + +FormulaAuditStrict/ComponentsRedundancy: + Enabled: true + +FormulaAudit/Homepage: Enabled: true Metrics/AbcSize: @@ -34,6 +43,11 @@ Metrics/MethodLength: Metrics/ModuleLength: CountComments: false + Exclude: + - '**/bin/**/*' + - '**/cmd/**/*' + - '**/lib/**/*' + - '**/spec/**/*' Metrics/PerceivedComplexity: Enabled: false diff --git a/Library/Homebrew/PATH.rb b/Library/Homebrew/PATH.rb new file mode 100644 index 000000000..de7167eb4 --- /dev/null +++ b/Library/Homebrew/PATH.rb @@ -0,0 +1,74 @@ +class PATH + include Enumerable + extend Forwardable + + def_delegator :@paths, :each + + def initialize(*paths) + @paths = parse(*paths) + end + + def prepend(*paths) + @paths = parse(*paths, *@paths) + self + end + + def append(*paths) + @paths = parse(*@paths, *paths) + self + end + + def insert(index, *paths) + @paths = parse(*@paths.insert(index, *paths)) + self + end + + def select(&block) + self.class.new(@paths.select(&block)) + end + + def reject(&block) + self.class.new(@paths.reject(&block)) + end + + def to_ary + @paths + end + alias to_a to_ary + + def to_str + @paths.join(File::PATH_SEPARATOR) + end + alias to_s to_str + + def ==(other) + if other.respond_to?(:to_ary) + return true if to_ary == other.to_ary + end + + if other.respond_to?(:to_str) + return true if to_str == other.to_str + end + + false + end + + def empty? + @paths.empty? + end + + def existing + existing_path = select(&File.method(:directory?)) + # return nil instead of empty PATH, to unset environment variables + existing_path unless existing_path.empty? + end + + private + + def parse(*paths) + paths.flatten + .compact + .flat_map { |p| Pathname.new(p).to_path.split(File::PATH_SEPARATOR) } + .uniq + end +end diff --git a/Library/Homebrew/brew.rb b/Library/Homebrew/brew.rb index aa38b54f4..e07599ac6 100644 --- a/Library/Homebrew/brew.rb +++ b/Library/Homebrew/brew.rb @@ -12,9 +12,9 @@ require "pathname" HOMEBREW_LIBRARY_PATH = Pathname.new(__FILE__).realpath.parent $:.unshift(HOMEBREW_LIBRARY_PATH.to_s) require "global" +require "tap" if ARGV == %w[--version] || ARGV == %w[-v] - require "tap" puts "Homebrew #{HOMEBREW_VERSION}" puts "Homebrew/homebrew-core #{CoreTap.instance.version_string}" exit 0 @@ -23,8 +23,7 @@ end def require?(path) require path rescue LoadError => e - # HACK: ( because we should raise on syntax errors but - # not if the file doesn't exist. TODO make robust! + # we should raise on syntax errors but not if the file doesn't exist. raise unless e.to_s.include? path end @@ -48,13 +47,15 @@ begin end end + path = PATH.new(ENV["PATH"]) + # Add contributed commands to PATH before checking. - Dir["#{HOMEBREW_LIBRARY}/Taps/*/*/cmd"].each do |tap_cmd_dir| - ENV["PATH"] += "#{File::PATH_SEPARATOR}#{tap_cmd_dir}" - end + path.append(Pathname.glob(Tap::TAP_DIRECTORY/"*/*/cmd")) # Add SCM wrappers. - ENV["PATH"] += "#{File::PATH_SEPARATOR}#{HOMEBREW_SHIMS_PATH}/scm" + path.append(HOMEBREW_SHIMS_PATH/"scm") + + ENV["PATH"] = path if cmd internal_cmd = require? HOMEBREW_LIBRARY_PATH.join("cmd", cmd) @@ -122,7 +123,7 @@ rescue SystemExit => e onoe "Kernel.exit" if ARGV.verbose? && !e.success? $stderr.puts e.backtrace if ARGV.debug? raise -rescue Interrupt => e +rescue Interrupt $stderr.puts # seemingly a newline is typical exit 130 rescue BuildError => e diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index 11c1a6c59..02ce5e1c1 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -2,7 +2,7 @@ HOMEBREW_VERSION="$(git -C "$HOMEBREW_REPOSITORY" describe --tags --dirty 2>/dev HOMEBREW_USER_AGENT_VERSION="$HOMEBREW_VERSION" if [[ -z "$HOMEBREW_VERSION" ]] then - HOMEBREW_VERSION=">1.1.0 (no git repository)" + HOMEBREW_VERSION=">1.2.0 (no git repository)" HOMEBREW_USER_AGENT_VERSION="1.X.Y" fi @@ -101,6 +101,8 @@ then [[ "$HOMEBREW_PROCESSOR" = "i386" ]] && HOMEBREW_PROCESSOR="Intel" HOMEBREW_MACOS_VERSION="$(/usr/bin/sw_vers -productVersion)" HOMEBREW_OS_VERSION="macOS $HOMEBREW_MACOS_VERSION" + # Don't change this from Mac OS X to match what macOS itself does in Safari on 10.12 + HOMEBREW_OS_USER_AGENT_VERSION="Mac OS X $HOMEBREW_MACOS_VERSION" printf -v HOMEBREW_MACOS_VERSION_NUMERIC "%02d%02d%02d" ${HOMEBREW_MACOS_VERSION//./ } if [[ "$HOMEBREW_MACOS_VERSION_NUMERIC" -lt "100900" && @@ -113,8 +115,9 @@ else HOMEBREW_PRODUCT="${HOMEBREW_SYSTEM}brew" [[ -n "$HOMEBREW_LINUX" ]] && HOMEBREW_OS_VERSION="$(lsb_release -sd 2>/dev/null)" : "${HOMEBREW_OS_VERSION:=$(uname -r)}" + HOMEBREW_OS_USER_AGENT_VERSION="$HOMEBREW_OS_VERSION" fi -HOMEBREW_USER_AGENT="$HOMEBREW_PRODUCT/$HOMEBREW_USER_AGENT_VERSION ($HOMEBREW_SYSTEM; $HOMEBREW_PROCESSOR $HOMEBREW_OS_VERSION)" +HOMEBREW_USER_AGENT="$HOMEBREW_PRODUCT/$HOMEBREW_USER_AGENT_VERSION ($HOMEBREW_SYSTEM; $HOMEBREW_PROCESSOR $HOMEBREW_OS_USER_AGENT_VERSION)" HOMEBREW_CURL_VERSION="$("$HOMEBREW_CURL" --version 2>/dev/null | head -n1 | /usr/bin/awk '{print $1"/"$2}')" HOMEBREW_USER_AGENT_CURL="$HOMEBREW_USER_AGENT $HOMEBREW_CURL_VERSION" diff --git a/Library/Homebrew/build_options.rb b/Library/Homebrew/build_options.rb index d9020ecba..6c6952d71 100644 --- a/Library/Homebrew/build_options.rb +++ b/Library/Homebrew/build_options.rb @@ -47,7 +47,6 @@ class BuildOptions def bottle? include? "build-bottle" end - alias build_bottle? bottle? # True if a {Formula} is being built with {Formula.head} instead of {Formula.stable}. # <pre>args << "--some-new-stuff" if build.head?</pre> diff --git a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb index 06bdfe157..21d123ab9 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb @@ -9,7 +9,12 @@ module Hbc def link super - FileUtils.chmod "+x", source + return if source.executable? + if source.writable? + FileUtils.chmod "+x", source + else + @command.run!("/bin/chmod", args: ["+x", source], sudo: true) + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/cask.rb b/Library/Homebrew/cask/lib/hbc/cask.rb index cf5f2b37a..a193a394e 100644 --- a/Library/Homebrew/cask/lib/hbc/cask.rb +++ b/Library/Homebrew/cask/lib/hbc/cask.rb @@ -1,10 +1,12 @@ require "forwardable" require "hbc/dsl" +require "hbc/metadata" module Hbc class Cask extend Forwardable + include Metadata attr_reader :token, :sourcefile_path def initialize(token, sourcefile_path: nil, &block) @@ -20,55 +22,9 @@ module Hbc define_method(method_name) { @dsl.send(method_name) } end - METADATA_SUBDIR = ".metadata".freeze - - def metadata_master_container_path - @metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR) - end - - def metadata_versioned_container_path - cask_version = version ? version : :unknown - metadata_master_container_path.join(cask_version.to_s) - end - - def metadata_path(timestamp = :latest, create = false) - if create && timestamp == :latest - raise CaskError, "Cannot create metadata path when timestamp is :latest" - end - path = if timestamp == :latest - Pathname.glob(metadata_versioned_container_path.join("*")).sort.last - elsif timestamp == :now - Utils.nowstamp_metadata_path(metadata_versioned_container_path) - else - metadata_versioned_container_path.join(timestamp) - end - if create - odebug "Creating metadata directory #{path}" - FileUtils.mkdir_p path - end - path - end - - def metadata_subdir(leaf, timestamp = :latest, create = false) - if create && timestamp == :latest - raise CaskError, "Cannot create metadata subdir when timestamp is :latest" - end - unless leaf.respond_to?(:length) && !leaf.empty? - raise CaskError, "Cannot create metadata subdir for empty leaf" - end - parent = metadata_path(timestamp, create) - return nil unless parent.respond_to?(:join) - subdir = parent.join(leaf) - if create - odebug "Creating metadata subdirectory #{subdir}" - FileUtils.mkdir_p subdir - end - subdir - end - def timestamped_versions - Pathname.glob(metadata_master_container_path.join("*", "*")) - .map { |p| p.relative_path_from(metadata_master_container_path) } + Pathname.glob(metadata_timestamped_path(version: "*", timestamp: "*")) + .map { |p| p.relative_path_from(p.parent.parent) } .sort_by(&:basename) # sort by timestamp .map { |p| p.split.map(&:to_s) } end diff --git a/Library/Homebrew/cask/lib/hbc/cli/doctor.rb b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb index 6b2f4caab..031f78824 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/doctor.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/doctor.rb @@ -92,7 +92,7 @@ module Hbc def self.render_staging_location(path) path = Pathname.new(user_tilde(path.to_s)) if !path.exist? - "#{path} #{error_string "error: path does not exist"}}" + "#{path} #{error_string "error: path does not exist"}" elsif !path.writable? "#{path} #{error_string "error: not writable by current user"}" else diff --git a/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb b/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb index b52c43328..c2ed8f462 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/reinstall.rb @@ -7,30 +7,10 @@ module Hbc begin cask = CaskLoader.load(cask_token) - installer = Installer.new(cask, - force: force, - skip_cask_deps: skip_cask_deps, - require_sha: require_sha) - installer.print_caveats - installer.fetch - - if cask.installed? - # use copy of cask for uninstallation to avoid 'No such file or directory' bug - installed_cask = cask - - # use the same cask file that was used for installation, if possible - if (installed_caskfile = installed_cask.installed_caskfile).exist? - installed_cask = CaskLoader.load_from_file(installed_caskfile) - end - - # Always force uninstallation, ignore method parameter - Installer.new(installed_cask, force: true).uninstall - end - - installer.stage - installer.install_artifacts - installer.enable_accessibility_access - puts installer.summary + Installer.new(cask, + force: force, + skip_cask_deps: skip_cask_deps, + require_sha: require_sha).reinstall count += 1 rescue CaskUnavailableError => e diff --git a/Library/Homebrew/cask/lib/hbc/cli/search.rb b/Library/Homebrew/cask/lib/hbc/cli/search.rb index 992aca583..7abd744e4 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/search.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/search.rb @@ -13,6 +13,15 @@ module Hbc end end + def self.search_remote(query) + 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")}" + end.compact + end + def self.search(*arguments) exact_match = nil partial_matches = [] @@ -29,27 +38,34 @@ module Hbc partial_matches = simplified_tokens.grep(/#{simplified_search_term}/i) { |t| all_tokens[simplified_tokens.index(t)] } partial_matches.delete(exact_match) end - [exact_match, partial_matches, search_term] + + remote_matches = search_remote(search_term) + + [exact_match, partial_matches, remote_matches, search_term] end - def self.render_results(exact_match, partial_matches, search_term) + def self.render_results(exact_match, partial_matches, remote_matches, search_term) if !exact_match && partial_matches.empty? puts "No Cask found for \"#{search_term}\"." return end if exact_match - ohai "Exact match" + ohai "Exact Match" puts highlight_installed exact_match end - return if partial_matches.empty? - - if extract_regexp search_term - ohai "Regexp matches" - else - ohai "Partial matches" + unless partial_matches.empty? + if extract_regexp search_term + ohai "Regexp Matches" + else + ohai "Partial Matches" + end + puts Formatter.columns(partial_matches.map(&method(:highlight_installed))) end - puts Formatter.columns(partial_matches.map(&method(:highlight_installed))) + + return if remote_matches.empty? + ohai "Remote Matches" + puts Formatter.columns(remote_matches.map(&method(:highlight_installed))) end def self.highlight_installed(token) diff --git a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb index 6887aaf4f..1ee3230ad 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/uninstall.rb @@ -12,15 +12,9 @@ module Hbc raise CaskNotInstalledError, cask unless cask.installed? || force - latest_installed_version = cask.timestamped_versions.last - - unless latest_installed_version.nil? - latest_installed_cask_file = cask.metadata_master_container_path - .join(latest_installed_version.join(File::Separator), - "Casks", "#{cask_token}.rb") - + if cask.installed? && !cask.installed_caskfile.nil? # use the same cask file that was used for installation, if possible - cask = CaskLoader.load_from_file(latest_installed_cask_file) if latest_installed_cask_file.exist? + cask = CaskLoader.load_from_file(cask.installed_caskfile) if cask.installed_caskfile.exist? end Installer.new(cask, force: force).uninstall diff --git a/Library/Homebrew/cask/lib/hbc/container.rb b/Library/Homebrew/cask/lib/hbc/container.rb index c6b2c3c37..961e31968 100644 --- a/Library/Homebrew/cask/lib/hbc/container.rb +++ b/Library/Homebrew/cask/lib/hbc/container.rb @@ -61,7 +61,7 @@ module Hbc begin const_get(type.to_s.split("_").map(&:capitalize).join) rescue NameError - false + nil end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/container.rb b/Library/Homebrew/cask/lib/hbc/dsl/container.rb index ab260c98c..caaf25bca 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl/container.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl/container.rb @@ -1,3 +1,5 @@ +require "hbc/container" + module Hbc class DSL class Container @@ -13,9 +15,12 @@ module Hbc @pairs = pairs pairs.each do |key, value| raise "invalid container key: '#{key.inspect}'" unless VALID_KEYS.include?(key) - writer_method = "#{key}=".to_sym - send(writer_method, value) + send(:"#{key}=", value) end + + return if type.nil? + return unless Hbc::Container.from_type(type).nil? + raise "invalid container type: #{type.inspect}" end def to_yaml diff --git a/Library/Homebrew/cask/lib/hbc/installer.rb b/Library/Homebrew/cask/lib/hbc/installer.rb index 824c1b1be..f02f07806 100644 --- a/Library/Homebrew/cask/lib/hbc/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/installer.rb @@ -24,6 +24,7 @@ module Hbc @force = force @skip_cask_deps = skip_cask_deps @require_sha = require_sha + @reinstall = false end def self.print_caveats(cask) @@ -76,13 +77,16 @@ module Hbc def install odebug "Hbc::Installer#install" - if @cask.installed? && !force + if @cask.installed? && !force && !@reinstall raise CaskAlreadyInstalledAutoUpdatesError, @cask if @cask.auto_updates raise CaskAlreadyInstalledError, @cask end print_caveats fetch + uninstall_existing_cask if @reinstall + + oh1 "Installing Cask #{@cask}" stage install_artifacts enable_accessibility_access @@ -90,6 +94,23 @@ module Hbc puts summary end + def reinstall + odebug "Hbc::Installer#reinstall" + @reinstall = true + install + end + + def uninstall_existing_cask + return unless @cask.installed? + + # use the same cask file that was used for installation, if possible + installed_caskfile = @cask.installed_caskfile + installed_cask = installed_caskfile.exist? ? CaskLoader.load_from_file(installed_caskfile) : @cask + + # Always force uninstallation, ignore method parameter + Installer.new(installed_cask, force: true).uninstall + end + def summary s = "" s << "#{Emoji.install_badge} " if Emoji.enabled? @@ -295,19 +316,17 @@ module Hbc end def save_caskfile - unless (old_savedirs = Pathname.glob(@cask.metadata_path("*"))).empty? - old_savedirs.each(&:rmtree) - end + old_savedir = @cask.metadata_timestamped_path return unless @cask.sourcefile_path - savedir = @cask.metadata_subdir("Casks", :now, true) - savedir.mkpath + savedir = @cask.metadata_subdir("Casks", timestamp: :now, create: true) FileUtils.copy @cask.sourcefile_path, savedir + old_savedir.rmtree unless old_savedir.nil? end def uninstall - odebug "Hbc::Installer#uninstall" + oh1 "Uninstalling Cask #{@cask}" disable_accessibility_access uninstall_artifacts purge_versioned_files @@ -355,15 +374,15 @@ module Hbc gain_permissions_remove(@cask.staged_path) if !@cask.staged_path.nil? && @cask.staged_path.exist? # Homebrew-Cask metadata - if @cask.metadata_versioned_container_path.respond_to?(:children) && - @cask.metadata_versioned_container_path.exist? - @cask.metadata_versioned_container_path.children.each do |subdir| + if @cask.metadata_versioned_path.respond_to?(:children) && + @cask.metadata_versioned_path.exist? + @cask.metadata_versioned_path.children.each do |subdir| unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename) gain_permissions_remove(subdir) end end end - @cask.metadata_versioned_container_path.rmdir_if_possible + @cask.metadata_versioned_path.rmdir_if_possible @cask.metadata_master_container_path.rmdir_if_possible # toplevel staged distribution diff --git a/Library/Homebrew/cask/lib/hbc/metadata.rb b/Library/Homebrew/cask/lib/hbc/metadata.rb new file mode 100644 index 000000000..344c38cee --- /dev/null +++ b/Library/Homebrew/cask/lib/hbc/metadata.rb @@ -0,0 +1,73 @@ +module Hbc + module Metadata + METADATA_SUBDIR = ".metadata".freeze + + def metadata_master_container_path + @metadata_master_container_path ||= caskroom_path.join(METADATA_SUBDIR) + end + + def metadata_versioned_path(version: self.version) + cask_version = (version || :unknown).to_s + + if cask_version.empty? + raise CaskError, "Cannot create metadata path with empty version." + end + + metadata_master_container_path.join(cask_version) + end + + def metadata_timestamped_path(version: self.version, timestamp: :latest, create: false) + if create && timestamp == :latest + raise CaskError, "Cannot create metadata path when timestamp is :latest." + end + + path = if timestamp == :latest + Pathname.glob(metadata_versioned_path(version: version).join("*")).sort.last + else + timestamp = new_timestamp if timestamp == :now + metadata_versioned_path(version: version).join(timestamp) + end + + if create && !path.directory? + odebug "Creating metadata directory #{path}." + path.mkpath + end + + path + end + + def metadata_subdir(leaf, version: self.version, timestamp: :latest, create: false) + if create && timestamp == :latest + raise CaskError, "Cannot create metadata subdir when timestamp is :latest." + end + + unless leaf.respond_to?(:empty?) && !leaf.empty? + raise CaskError, "Cannot create metadata subdir for empty leaf." + end + + parent = metadata_timestamped_path(version: version, timestamp: timestamp, create: create) + + return nil if parent.nil? + + subdir = parent.join(leaf) + + if create && !subdir.directory? + odebug "Creating metadata subdirectory #{subdir}." + subdir.mkpath + end + + subdir + end + + private + + def new_timestamp(time = Time.now) + time = time.utc + + timestamp = time.strftime("%Y%m%d%H%M%S") + fraction = format("%.3f", time.to_f - time.to_i)[1..-1] + + timestamp.concat(fraction) + end + end +end diff --git a/Library/Homebrew/cask/lib/hbc/pkg.rb b/Library/Homebrew/cask/lib/hbc/pkg.rb index 902d56449..c9aa3180f 100644 --- a/Library/Homebrew/cask/lib/hbc/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/pkg.rb @@ -63,7 +63,10 @@ module Hbc end def pkgutil_bom_all - @pkgutil_bom_all ||= info.fetch("paths").keys.map { |p| root.join(p) } + @pkgutil_bom_all ||= @command.run!("/usr/sbin/pkgutil", args: ["--files", package_id]) + .stdout + .split("\n") + .map { |path| root.join(path) } end def root @@ -71,7 +74,7 @@ module Hbc end def info - @info ||= @command.run!("/usr/sbin/pkgutil", args: ["--export-plist", package_id]) + @info ||= @command.run!("/usr/sbin/pkgutil", args: ["--pkg-info-plist", package_id]) .plist end diff --git a/Library/Homebrew/cask/lib/hbc/scopes.rb b/Library/Homebrew/cask/lib/hbc/scopes.rb index db12409e5..149c2c343 100644 --- a/Library/Homebrew/cask/lib/hbc/scopes.rb +++ b/Library/Homebrew/cask/lib/hbc/scopes.rb @@ -6,8 +6,7 @@ module Hbc module ClassMethods def all - @all_casks ||= {} - all_tokens.map { |t| @all_casks[t] ||= load(t) } + all_tokens.map(&CaskLoader.public_method(:load)) end def all_tapped_cask_dirs diff --git a/Library/Homebrew/cask/lib/hbc/utils.rb b/Library/Homebrew/cask/lib/hbc/utils.rb index ecb565e8e..b2b65a08e 100644 --- a/Library/Homebrew/cask/lib/hbc/utils.rb +++ b/Library/Homebrew/cask/lib/hbc/utils.rb @@ -43,7 +43,7 @@ module Hbc p.rmtree else command.run("/bin/rm", - args: command_args + ["-r", "-f", "--", p], + args: ["-r", "-f", "--", p], sudo: true) end end @@ -115,16 +115,5 @@ module Hbc opoo(poo.join(" ") + "\n" + error_message_with_suggestions) end - - def self.nowstamp_metadata_path(container_path) - @timenow ||= Time.now.gmtime - return unless container_path.respond_to?(:join) - - precision = 3 - timestamp = @timenow.strftime("%Y%m%d%H%M%S") - fraction = format("%.#{precision}f", @timenow.to_f - @timenow.to_i)[1..-1] - timestamp.concat(fraction) - container_path.join(timestamp) - end end end diff --git a/Library/Homebrew/caveats.rb b/Library/Homebrew/caveats.rb index 61b703469..ee09063fd 100644 --- a/Library/Homebrew/caveats.rb +++ b/Library/Homebrew/caveats.rb @@ -44,15 +44,17 @@ class Caveats def keg_only_text return unless f.keg_only? - s = "This formula is keg-only, which means it was not symlinked into #{HOMEBREW_PREFIX}." - s << "\n\n#{f.keg_only_reason}\n" + s = <<-EOS.undent + This formula is keg-only, which means it was not symlinked into #{HOMEBREW_PREFIX}, + because #{f.keg_only_reason.to_s.chomp}. + EOS if f.bin.directory? || f.sbin.directory? s << "\nIf you need to have this software first in your PATH run:\n" if f.bin.directory? - s << " #{Utils::Shell.prepend_path_in_shell_profile(f.opt_bin.to_s)}\n" + s << " #{Utils::Shell.prepend_path_in_profile(f.opt_bin.to_s)}\n" end if f.sbin.directory? - s << " #{Utils::Shell.prepend_path_in_shell_profile(f.opt_sbin.to_s)}\n" + s << " #{Utils::Shell.prepend_path_in_profile(f.opt_sbin.to_s)}\n" end end diff --git a/Library/Homebrew/cmd/--env.rb b/Library/Homebrew/cmd/--env.rb index 323964dad..90beee89c 100644 --- a/Library/Homebrew/cmd/--env.rb +++ b/Library/Homebrew/cmd/--env.rb @@ -22,9 +22,9 @@ module Homebrew # legacy behavior shell = :bash unless $stdout.tty? elsif shell_value == "auto" - shell = Utils::Shell.parent_shell || Utils::Shell.preferred_shell + shell = Utils::Shell.parent || Utils::Shell.preferred elsif shell_value - shell = Utils::Shell.path_to_shell(shell_value) + shell = Utils::Shell.from_path(shell_value) end env_keys = build_env_keys(ENV) diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 1808c4d9c..394b31db0 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -206,7 +206,8 @@ module Homebrew Migrator.migrate_if_needed(f) install_formula(f) end - rescue FormulaClassUnavailableError => e + rescue FormulaUnreadableError, FormulaClassUnavailableError, + TapFormulaUnreadableError, TapFormulaClassUnavailableError => e # Need to rescue before `FormulaUnavailableError` (superclass of this) # is handled, as searching for a formula doesn't make sense here (the # formula was found, but there's a problem with its implementation). @@ -240,6 +241,8 @@ module Homebrew puts "To install one of them, run (for example):\n brew install #{formulae_search_results.first}" end + # Do not search taps if the formula name is qualified + return if e.name.include?("/") ohai "Searching taps..." taps_search_results = search_taps(query) case taps_search_results.length diff --git a/Library/Homebrew/cmd/link.rb b/Library/Homebrew/cmd/link.rb index 5ce6bea48..b8bd135e0 100644 --- a/Library/Homebrew/cmd/link.rb +++ b/Library/Homebrew/cmd/link.rb @@ -86,8 +86,8 @@ module Homebrew opt = HOMEBREW_PREFIX/"opt/#{keg.name}" puts "\nIf you need to have this software first in your PATH instead consider running:" - puts " #{Utils::Shell.prepend_path_in_shell_profile(opt/"bin")}" if bin.directory? - puts " #{Utils::Shell.prepend_path_in_shell_profile(opt/"sbin")}" if sbin.directory? + puts " #{Utils::Shell.prepend_path_in_profile(opt/"bin")}" if bin.directory? + puts " #{Utils::Shell.prepend_path_in_profile(opt/"sbin")}" if sbin.directory? end def keg_only?(rack) diff --git a/Library/Homebrew/cmd/postinstall.rb b/Library/Homebrew/cmd/postinstall.rb index 8808a2602..f5d091227 100644 --- a/Library/Homebrew/cmd/postinstall.rb +++ b/Library/Homebrew/cmd/postinstall.rb @@ -7,7 +7,10 @@ module Homebrew module_function def postinstall - ARGV.resolved_formulae.each { |f| run_post_install(f) if f.post_install_defined? } + ARGV.resolved_formulae.each do |f| + ohai "Postinstalling #{f}" + run_post_install(f) + end end def run_post_install(formula) diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb index 443739f8c..f71a14ba1 100644 --- a/Library/Homebrew/cmd/search.rb +++ b/Library/Homebrew/cmd/search.rb @@ -16,15 +16,12 @@ require "formula" require "missing_formula" require "utils" -require "thread" require "official_taps" require "descriptions" module Homebrew module_function - SEARCH_ERROR_QUEUE = Queue.new - def search if ARGV.empty? puts Formatter.columns(Formula.full_names) @@ -61,7 +58,7 @@ module Homebrew regex = query_regexp(query) local_results = search_formulae(regex) puts Formatter.columns(local_results) unless local_results.empty? - tap_results = search_taps(regex) + tap_results = search_taps(query) puts Formatter.columns(tap_results) unless tap_results.empty? if $stdout.tty? @@ -75,36 +72,25 @@ module Homebrew puts reason elsif count.zero? puts "No formula found for #{query.inspect}." - begin - GitHub.print_pull_requests_matching(query) - rescue GitHub::Error => e - SEARCH_ERROR_QUEUE << e - end + GitHub.print_pull_requests_matching(query) end end end - if $stdout.tty? - metacharacters = %w[\\ | ( ) [ ] { } ^ $ * + ?] - bad_regex = metacharacters.any? do |char| - ARGV.any? do |arg| - arg.include?(char) && !arg.start_with?("/") - end - end - if !ARGV.empty? && bad_regex - ohai "Did you mean to perform a regular expression search?" - ohai "Surround your query with /slashes/ to search by regex." + return unless $stdout.tty? + return if ARGV.empty? + metacharacters = %w[\\ | ( ) [ ] { } ^ $ * + ?].freeze + return unless metacharacters.any? do |char| + ARGV.any? do |arg| + arg.include?(char) && !arg.start_with?("/") end end - - raise SEARCH_ERROR_QUEUE.pop unless SEARCH_ERROR_QUEUE.empty? + ohai <<-EOS.undent + Did you mean to perform a regular expression search? + Surround your query with /slashes/ to search locally by regex. + EOS end - SEARCHABLE_TAPS = OFFICIAL_TAPS.map { |tap| ["Homebrew", tap] } + [ - %w[Caskroom cask], - %w[Caskroom versions], - ] - def query_regexp(query) case query when %r{^/(.*)/$} then Regexp.new($1) @@ -114,53 +100,16 @@ module Homebrew odie "#{query} is not a valid regex" end - def search_taps(regex_or_string) - SEARCHABLE_TAPS.map do |user, repo| - Thread.new { search_tap(user, repo, regex_or_string) } - end.inject([]) do |results, t| - results.concat(t.value) - end - end - - def search_tap(user, repo, regex_or_string) - regex = regex_or_string.is_a?(String) ? /^#{Regexp.escape(regex_or_string)}$/ : regex_or_string - - if (HOMEBREW_LIBRARY/"Taps/#{user.downcase}/homebrew-#{repo.downcase}").directory? && \ - user != "Caskroom" - return [] - end - - remote_tap_formulae = Hash.new do |cache, key| - user, repo = key.split("/", 2) - tree = {} - - GitHub.open "https://api.github.com/repos/#{user}/homebrew-#{repo}/git/trees/HEAD?recursive=1" do |json| - json["tree"].each do |object| - next unless object["type"] == "blob" - - subtree, file = File.split(object["path"]) - - if File.extname(file) == ".rb" - tree[subtree] ||= [] - tree[subtree] << file - end - end - end - - paths = tree["Formula"] || tree["HomebrewFormula"] || tree["."] || [] - paths += tree["Casks"] || [] - cache[key] = paths.map { |path| File.basename(path, ".rb") } - end - - names = remote_tap_formulae["#{user}/#{repo}"] - user = user.downcase if user == "Homebrew" # special handling for the Homebrew organization - names.select { |name| name =~ regex }.map { |name| "#{user}/#{repo}/#{name}" } - rescue GitHub::HTTPNotFoundError - opoo "Failed to search tap: #{user}/#{repo}. Please run `brew update`" - [] - rescue GitHub::Error => e - SEARCH_ERROR_QUEUE << e - [] + def search_taps(query) + valid_dirnames = ["Formula", "HomebrewFormula", "Casks", "."].freeze + matches = GitHub.search_code("user:Homebrew", "user: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"]) + next if tap.installed? + "#{tap.name}/#{File.basename(filename, ".rb")}" + end.compact end def search_formulae(regex) diff --git a/Library/Homebrew/cmd/sh.rb b/Library/Homebrew/cmd/sh.rb index 249753355..69f329cb3 100644 --- a/Library/Homebrew/cmd/sh.rb +++ b/Library/Homebrew/cmd/sh.rb @@ -23,7 +23,7 @@ module Homebrew ENV.setup_build_environment if superenv? # superenv stopped adding brew's bin but generally users will want it - ENV["PATH"] = ENV["PATH"].split(File::PATH_SEPARATOR).insert(1, "#{HOMEBREW_PREFIX}/bin").join(File::PATH_SEPARATOR) + ENV["PATH"] = PATH.new(ENV["PATH"]).insert(1, HOMEBREW_PREFIX/"bin") end ENV["PS1"] = 'brew \[\033[1;32m\]\w\[\033[0m\]$ ' ENV["VERBOSE"] = "1" diff --git a/Library/Homebrew/cmd/style.rb b/Library/Homebrew/cmd/style.rb index 2a7f37031..cf41d91ee 100644 --- a/Library/Homebrew/cmd/style.rb +++ b/Library/Homebrew/cmd/style.rb @@ -1,4 +1,4 @@ -#: * `style` [`--fix`] [`--display-cop-names`] [<files>|<taps>|<formulae>]: +#: * `style` [`--fix`] [`--display-cop-names`] [`--only-cops=`[COP1,COP2..]|`--except-cops=`[COP1,COP2..]] [<files>|<taps>|<formulae>]: #: Check formulae or files for conformance to Homebrew style guidelines. #: #: <formulae> and <files> may not be combined. If both are omitted, style will run @@ -11,6 +11,10 @@ #: If `--display-cop-names` is passed, the RuboCop cop name for each violation #: is included in the output. #: +#: If `--only-cops` is passed, only the given Rubocop cop(s)' violations would be checked. +#: +#: If `--except-cops` is passed, the given Rubocop cop(s)' checks would be skipped. +#: #: Exits with a non-zero status if any style violations are found. require "utils" @@ -30,7 +34,20 @@ module Homebrew ARGV.formulae.map(&:path) end - Homebrew.failed = check_style_and_print(target, fix: ARGV.flag?("--fix")) + only_cops = ARGV.value("only-cops").to_s.split(",") + except_cops = ARGV.value("except-cops").to_s.split(",") + if !only_cops.empty? && !except_cops.empty? + odie "--only-cops and --except-cops cannot be used simultaneously!" + end + + options = { fix: ARGV.flag?("--fix") } + if !only_cops.empty? + options[:only_cops] = only_cops + elsif !except_cops.empty? + options[:except_cops] = except_cops + end + + Homebrew.failed = check_style_and_print(target, options) end # Checks style for a list of files, printing simple RuboCop output. @@ -47,13 +64,34 @@ module Homebrew def check_style_impl(files, output_type, options = {}) fix = options[:fix] + Homebrew.install_gem_setup_path! "rubocop", HOMEBREW_RUBOCOP_VERSION + require "rubocop" + require_relative "../rubocops" args = %w[ --force-exclusion ] args << "--auto-correct" if fix + if options[:except_cops] + options[:except_cops].map! { |cop| RuboCop::Cop::Cop.registry.qualified_cop_name(cop.to_s, "") } + cops_to_exclude = options[:except_cops].select do |cop| + RuboCop::Cop::Cop.registry.names.include?(cop) || + RuboCop::Cop::Cop.registry.departments.include?(cop.to_sym) + end + + args << "--except" << cops_to_exclude.join(",") unless cops_to_exclude.empty? + elsif options[:only_cops] + options[:only_cops].map! { |cop| RuboCop::Cop::Cop.registry.qualified_cop_name(cop.to_s, "") } + cops_to_include = options[:only_cops].select do |cop| + RuboCop::Cop::Cop.registry.names.include?(cop) || + RuboCop::Cop::Cop.registry.departments.include?(cop.to_sym) + end + + args << "--only" << cops_to_include.join(",") unless cops_to_include.empty? + end + if files.nil? args << "--config" << HOMEBREW_LIBRARY_PATH/".rubocop.yml" args += [HOMEBREW_LIBRARY_PATH] diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb index 114c4a8b6..2a07c1b2f 100644 --- a/Library/Homebrew/cmd/tap.rb +++ b/Library/Homebrew/cmd/tap.rb @@ -63,17 +63,4 @@ module Homebrew def full_clone? ARGV.include?("--full") || ARGV.homebrew_developer? end - - # @deprecated this method will be removed in the future, if no external commands use it. - def install_tap(user, repo, clone_target = nil) - opoo "Homebrew.install_tap is deprecated, use Tap#install." - tap = Tap.fetch(user, repo) - begin - tap.install(clone_target: clone_target, full_clone: full_clone?) - rescue TapAlreadyTappedError - false - else - true - end - end end diff --git a/Library/Homebrew/cmd/update.sh b/Library/Homebrew/cmd/update.sh index 197a99f2e..77a5c1cd2 100644 --- a/Library/Homebrew/cmd/update.sh +++ b/Library/Homebrew/cmd/update.sh @@ -23,7 +23,7 @@ git() { } git_init_if_necessary() { - if [[ -n "$HOMEBREW_MACOS" ]] + if [[ -n "$HOMEBREW_MACOS" ]] || [[ -n "$HOMEBREW_FORCE_HOMEBREW_ORG" ]] then BREW_OFFICIAL_REMOTE="https://github.com/Homebrew/brew" CORE_OFFICIAL_REMOTE="https://github.com/Homebrew/homebrew-core" diff --git a/Library/Homebrew/cmd/uses.rb b/Library/Homebrew/cmd/uses.rb index b1122c90a..bab174184 100644 --- a/Library/Homebrew/cmd/uses.rb +++ b/Library/Homebrew/cmd/uses.rb @@ -28,7 +28,16 @@ module Homebrew def uses raise FormulaUnspecifiedError if ARGV.named.empty? - used_formulae = ARGV.formulae + used_formulae_missing = false + used_formulae = begin + ARGV.formulae + rescue FormulaUnavailableError => e + opoo e + used_formulae_missing = true + # If the formula doesn't exist: fake the needed formula object name. + ARGV.named.map { |name| OpenStruct.new name: name, full_name: name } + end + formulae = ARGV.include?("--installed") ? Formula.installed : Formula recursive = ARGV.flag? "--recursive" includes = [] @@ -115,5 +124,6 @@ module Homebrew return if uses.empty? puts Formatter.columns(uses.map(&:full_name)) + odie "Missing formulae should not have dependents!" if used_formulae_missing end end diff --git a/Library/Homebrew/compat.rb b/Library/Homebrew/compat.rb index 92b687725..3c080f616 100644 --- a/Library/Homebrew/compat.rb +++ b/Library/Homebrew/compat.rb @@ -25,3 +25,4 @@ require "compat/tab" require "compat/ENV/shared" require "compat/ENV/std" require "compat/ENV/super" +require "compat/utils/shell" diff --git a/Library/Homebrew/compat/ARGV.rb b/Library/Homebrew/compat/ARGV.rb index 23d02ce1a..e5fa8188f 100644 --- a/Library/Homebrew/compat/ARGV.rb +++ b/Library/Homebrew/compat/ARGV.rb @@ -1,6 +1,6 @@ module HomebrewArgvExtension def build_32_bit? - # odeprecated "ARGV.build_32_bit?" + odeprecated "ARGV.build_32_bit?" include? "--32-bit" end end diff --git a/Library/Homebrew/compat/build_options.rb b/Library/Homebrew/compat/build_options.rb index 52aa9b951..73722dadb 100644 --- a/Library/Homebrew/compat/build_options.rb +++ b/Library/Homebrew/compat/build_options.rb @@ -1,6 +1,11 @@ class BuildOptions def build_32_bit? - # odeprecated "build.build_32_bit?" + odeprecated "build.build_32_bit?" include?("32-bit") && option_defined?("32-bit") end + + def build_bottle? + odeprecated "build.build_bottle?", "build.bottle?" + bottle? + end end diff --git a/Library/Homebrew/compat/dependency_collector.rb b/Library/Homebrew/compat/dependency_collector.rb index bd72c55d4..fbcf1c2a0 100644 --- a/Library/Homebrew/compat/dependency_collector.rb +++ b/Library/Homebrew/compat/dependency_collector.rb @@ -14,7 +14,6 @@ class DependencyCollector output_deprecation(spec, tags) Dependency.new(spec.to_s, tags) when :apr - # TODO: reenable in future when we've fixed a few of the audits. # output_deprecation(spec, tags, "apr-util") Dependency.new("apr-util", tags) when :libltdl diff --git a/Library/Homebrew/compat/global.rb b/Library/Homebrew/compat/global.rb index 797e9ffe5..82c452cc0 100644 --- a/Library/Homebrew/compat/global.rb +++ b/Library/Homebrew/compat/global.rb @@ -3,7 +3,7 @@ module Homebrew def method_missing(method, *args, &block) if instance_methods.include?(method) - # odeprecated "#{self}##{method}", "'module_function' or 'def self.#{method}' to convert it to a class method" + odeprecated "#{self}##{method}", "'module_function' or 'def self.#{method}' to convert it to a class method" return instance_method(method).bind(self).call(*args, &block) end super diff --git a/Library/Homebrew/compat/software_spec.rb b/Library/Homebrew/compat/software_spec.rb index 51b0f3a0b..5efd2aeb4 100644 --- a/Library/Homebrew/compat/software_spec.rb +++ b/Library/Homebrew/compat/software_spec.rb @@ -1,7 +1,5 @@ class BottleSpecification def revision(*args) - # Don't announce deprecation yet as this is quite a big change - # to a public interface. # odeprecated "BottleSpecification.revision", "BottleSpecification.rebuild" rebuild(*args) end diff --git a/Library/Homebrew/compat/tab.rb b/Library/Homebrew/compat/tab.rb index 58fdc4913..2cf71c923 100644 --- a/Library/Homebrew/compat/tab.rb +++ b/Library/Homebrew/compat/tab.rb @@ -1,6 +1,6 @@ class Tab < OpenStruct def build_32_bit? - # odeprecated "Tab.build_32_bit?" + odeprecated "Tab.build_32_bit?" include?("32-bit") end end diff --git a/Library/Homebrew/compat/tap.rb b/Library/Homebrew/compat/tap.rb index d1cf7f1d5..37b1eeac1 100644 --- a/Library/Homebrew/compat/tap.rb +++ b/Library/Homebrew/compat/tap.rb @@ -6,5 +6,3 @@ class Tap core_tap? end end - -CoreFormulaRepository = CoreTap diff --git a/Library/Homebrew/compat/utils.rb b/Library/Homebrew/compat/utils.rb index 56b0824ba..3842e8a83 100644 --- a/Library/Homebrew/compat/utils.rb +++ b/Library/Homebrew/compat/utils.rb @@ -1,18 +1,13 @@ -# return the shell profile file based on users' preference shell def shell_profile - opoo "shell_profile has been deprecated in favor of Utils::Shell.profile" - case ENV["SHELL"] - when %r{/(ba)?sh} then "~/.bash_profile" - when %r{/zsh} then "~/.zshrc" - when %r{/ksh} then "~/.kshrc" - else "~/.bash_profile" - end + # odeprecated "shell_profile", "Utils::Shell.profile" + Utils::Shell.profile end module Tty module_function def white + odeprecated "Tty.white", "Tty.reset.bold" reset.bold end end diff --git a/Library/Homebrew/compat/utils/shell.rb b/Library/Homebrew/compat/utils/shell.rb new file mode 100644 index 000000000..161f10ebb --- /dev/null +++ b/Library/Homebrew/compat/utils/shell.rb @@ -0,0 +1,8 @@ +module Utils + module Shell + def self.shell_profile + odeprecated "Utils::Shell.shell_profile", "Utils::Shell.profile" + Utils::Shell.profile + end + end +end diff --git a/Library/Homebrew/dependable.rb b/Library/Homebrew/dependable.rb index 0834b08ec..785eb94d8 100644 --- a/Library/Homebrew/dependable.rb +++ b/Library/Homebrew/dependable.rb @@ -20,8 +20,6 @@ module Dependable end def required? - # FIXME: Should `required?` really imply `!build?`? And if so, why doesn't - # any of `optional?` and `recommended?` equally imply `!build?`? !build? && !optional? && !recommended? end diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index ba6c3f333..cbe26b422 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -1,4 +1,4 @@ -#: * `audit` [`--strict`] [`--fix`] [`--online`] [`--new-formula`] [`--display-cop-names`] [`--display-filename`] [<formulae>]: +#: * `audit` [`--strict`] [`--fix`] [`--online`] [`--new-formula`] [`--display-cop-names`] [`--display-filename`] [`--only=`<method>|`--except=`<method>] [`--only-cops=`[COP1,COP2..]|`--except-cops=`[COP1,COP2..]] [<formulae>]: #: Check <formulae> for Homebrew coding style violations. This should be #: run before submitting a new formula. #: @@ -23,6 +23,14 @@ #: If `--display-filename` is passed, every line of output is prefixed with the #: name of the file or formula being audited, to make the output easy to grep. #: +#: If `--only` is passed, only the methods named `audit_<method>` will be run. +#: +#: If `--except` is passed, the methods named `audit_<method>` will not be run. +#: +#: If `--only-cops` is passed, only the given Rubocop cop(s)' violations would be checked. +#: +#: If `--except-cops` is passed, the given Rubocop cop(s)' checks would be skipped. +#: #: `audit` exits with a non-zero status if any errors are found. This is useful, #: for instance, for implementing pre-commit hooks. @@ -65,15 +73,30 @@ module Homebrew files = ARGV.resolved_formulae.map(&:path) end - if strict - options = { fix: ARGV.flag?("--fix"), realpath: true } - # Check style in a single batch run up front for performance - style_results = check_style_json(files, options) + only_cops = ARGV.value("only-cops").to_s.split(",") + except_cops = ARGV.value("except-cops").to_s.split(",") + if !only_cops.empty? && !except_cops.empty? + odie "--only-cops and --except-cops cannot be used simultaneously!" + elsif (!only_cops.empty? || !except_cops.empty?) && strict + odie "--only-cops/--except-cops and --strict cannot be used simultaneously" + end + + options = { fix: ARGV.flag?("--fix"), realpath: true } + + if !only_cops.empty? + options[:only_cops] = only_cops + elsif !except_cops.empty? + options[:except_cops] = except_cops + elsif !strict + options[:except_cops] = [:FormulaAuditStrict] end + # Check style in a single batch run up front for performance + style_results = check_style_json(files, options) + ff.each do |f| options = { new_formula: new_formula, strict: strict, online: online } - options[:style_offenses] = style_results.file_offenses(f.path) if strict + options[:style_offenses] = style_results.file_offenses(f.path) fa = FormulaAuditor.new(f, options) fa.audit @@ -244,70 +267,6 @@ class FormulaAuditor end end - def component_problem(before, after, offset = 0) - problem "`#{before[1]}` (line #{before[0] + offset}) should be put before `#{after[1]}` (line #{after[0] + offset})" - end - - # scan in the reverse direction for remaining problems but report problems - # in the forward direction so that contributors don't reverse the order of - # lines in the file simply by following instructions - def audit_components(reverse = true, previous_pair = nil) - component_list = [ - [/^ include Language::/, "include directive"], - [/^ desc ["'][\S\ ]+["']/, "desc"], - [/^ homepage ["'][\S\ ]+["']/, "homepage"], - [/^ url ["'][\S\ ]+["']/, "url"], - [/^ mirror ["'][\S\ ]+["']/, "mirror"], - [/^ version ["'][\S\ ]+["']/, "version"], - [/^ (sha1|sha256) ["'][\S\ ]+["']/, "checksum"], - [/^ revision/, "revision"], - [/^ version_scheme/, "version_scheme"], - [/^ head ["'][\S\ ]+["']/, "head"], - [/^ stable do/, "stable block"], - [/^ bottle do/, "bottle block"], - [/^ devel do/, "devel block"], - [/^ head do/, "head block"], - [/^ bottle (:unneeded|:disable)/, "bottle modifier"], - [/^ keg_only/, "keg_only"], - [/^ option/, "option"], - [/^ depends_on/, "depends_on"], - [/^ conflicts_with/, "conflicts_with"], - [/^ (go_)?resource/, "resource"], - [/^ def install/, "install method"], - [/^ def caveats/, "caveats method"], - [/^ (plist_options|def plist)/, "plist block"], - [/^ test do/, "test block"], - ] - if previous_pair - previous_before = previous_pair[0] - previous_after = previous_pair[1] - end - offset = previous_after && previous_after[0] && previous_after[0] >= 1 ? previous_after[0] - 1 : 0 - present = component_list.map do |regex, name| - lineno = if reverse - text.reverse_line_number regex - else - text.line_number regex, offset - end - next unless lineno - [lineno, name] - end.compact - no_problem = true - present.each_cons(2) do |c1, c2| - if reverse - # scan in the forward direction from the offset - audit_components(false, [c1, c2]) if c1[0] > c2[0] # at least one more offense - elsif c1[0] > c2[0] && (offset.zero? || previous_pair.nil? || (c1[0] + offset) != previous_before[0] || (c2[0] + offset) != previous_after[0]) - component_problem c1, c2, offset - no_problem = false - end - end - if no_problem && previous_pair - component_problem previous_before, previous_after - end - present - end - def audit_file # Under normal circumstances (umask 0022), we expect a file mode of 644. If # the user's umask is more restrictive, respect that by masking out the @@ -332,53 +291,60 @@ class FormulaAuditor problem "File should end with a newline" unless text.trailing_newline? if formula.versioned_formula? - unversioned_formula = Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") + unversioned_formula = begin + # build this ourselves as we want e.g. homebrew/core to be present + full_name = if formula.tap + "#{formula.tap}/#{formula.name}" + else + formula.name + end + Formulary.factory(full_name.gsub(/@.*$/, "")).path + rescue FormulaUnavailableError, TapFormulaAmbiguityError, + TapFormulaWithOldnameAmbiguityError + Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") + end unless unversioned_formula.exist? unversioned_name = unversioned_formula.basename(".rb") problem "#{formula} is versioned but no #{unversioned_name} formula exists" end - else - versioned_formulae = Dir[formula.path.to_s.gsub(/\.rb$/, "@*.rb")] - needs_versioned_alias = !versioned_formulae.empty? && - formula.tap && - formula.aliases.grep(/.@\d/).empty? - if needs_versioned_alias - _, last_alias_version = File.basename(versioned_formulae.sort.reverse.first) - .gsub(/\.rb$/, "") - .split("@") - major, minor, = formula.version.to_s.split(".") - alias_name = if last_alias_version.split(".").length == 1 - "#{formula.name}@#{major}" - else - "#{formula.name}@#{major}.#{minor}" - end - problem <<-EOS.undent - Formula has other versions so create an alias: - cd #{formula.tap.alias_dir} - ln -s #{formula.path.to_s.gsub(formula.tap.path, "..")} #{alias_name} - EOS + elsif ARGV.build_stable? && + !(versioned_formulae = Dir[formula.path.to_s.gsub(/\.rb$/, "@*.rb")]).empty? + versioned_aliases = formula.aliases.grep(/.@\d/) + _, last_alias_version = + File.basename(versioned_formulae.sort.reverse.first) + .gsub(/\.rb$/, "").split("@") + major, minor, = formula.version.to_s.split(".") + alias_name_major = "#{formula.name}@#{major}" + alias_name_major_minor = "#{alias_name_major}.#{minor}" + alias_name = if last_alias_version.split(".").length == 1 + alias_name_major + else + alias_name_major_minor end - end - - return unless @strict + valid_alias_names = [alias_name_major, alias_name_major_minor] - present = audit_components + valid_versioned_aliases = versioned_aliases & valid_alias_names + invalid_versioned_aliases = versioned_aliases - valid_alias_names - present.map!(&:last) - if present.include?("stable block") - %w[url checksum mirror].each do |component| - if present.include?(component) - problem "`#{component}` should be put inside `stable block`" + if valid_versioned_aliases.empty? + if formula.tap + problem <<-EOS.undent + Formula has other versions so create a versioned alias: + cd #{formula.tap.alias_dir} + ln -s #{formula.path.to_s.gsub(formula.tap.path, "..")} #{alias_name} + EOS + else + problem "Formula has other versions so create an alias named #{alias_name}." end end - end - if present.include?("head") && present.include?("head block") - problem "Should not have both `head` and `head do`" + unless invalid_versioned_aliases.empty? + problem <<-EOS.undent + Formula has invalid versioned aliases: + #{invalid_versioned_aliases.join("\n ")} + EOS + end end - - return unless present.include?("bottle modifier") && present.include?("bottle block") - problem "Should not have `bottle :unneeded/:disable` and `bottle do`" end def audit_class @@ -437,11 +403,11 @@ class FormulaAuditor same_name_tap_formulae = @@local_official_taps_name_map[name] || [] if @online - @@remote_official_taps ||= OFFICIAL_TAPS - Tap.select(&:official?).map(&:repo) - - same_name_tap_formulae += @@remote_official_taps.map do |tap| - Thread.new { Homebrew.search_tap "homebrew", tap, name } - end.flat_map(&:value) + Homebrew.search_taps(name).each do |tap_formula_full_name| + tap_formula_name = tap_formula_full_name.split("/").last + next if tap_formula_name != name + same_name_tap_formulae << tap_formula_full_name + end end same_name_tap_formulae.delete(full_name) @@ -558,6 +524,38 @@ class FormulaAuditor EOS end + def audit_keg_only_style + return unless @strict + return unless formula.keg_only? + + whitelist = %w[ + Apple + macOS + OS + Homebrew + Xcode + GPG + GNOME + BSD + Firefox + ].freeze + + reason = formula.keg_only_reason.to_s + # Formulae names can legitimately be uppercase/lowercase/both. + name = Regexp.new(formula.name, Regexp::IGNORECASE) + reason.sub!(name, "") + first_word = reason.split[0] + + if reason =~ /\A[A-Z]/ && !reason.start_with?(*whitelist) + problem <<-EOS.undent + '#{first_word}' from the keg_only reason should be '#{first_word.downcase}'. + EOS + end + + return unless reason.end_with?(".") + problem "keg_only reason should not end with a period." + end + def audit_options formula.options.each do |o| if o.name == "32-bit" @@ -590,78 +588,9 @@ class FormulaAuditor homepage = formula.homepage if homepage.nil? || homepage.empty? - problem "Formula should have a homepage." return end - unless homepage =~ %r{^https?://} - problem "The homepage should start with http or https (URL is #{homepage})." - end - - # Check for http:// GitHub homepage urls, https:// is preferred. - # Note: only check homepages that are repo pages, not *.github.com hosts - if homepage.start_with? "http://github.com/" - problem "Please use https:// for #{homepage}" - end - - # Savannah has full SSL/TLS support but no auto-redirect. - # Doesn't apply to the download URLs, only the homepage. - if homepage.start_with? "http://savannah.nongnu.org/" - problem "Please use https:// for #{homepage}" - end - - # Freedesktop is complicated to handle - It has SSL/TLS, but only on certain subdomains. - # To enable https Freedesktop change the URL from http://project.freedesktop.org/wiki to - # https://wiki.freedesktop.org/project_name. - # "Software" is redirected to https://wiki.freedesktop.org/www/Software/project_name - if homepage =~ %r{^http://((?:www|nice|libopenraw|liboil|telepathy|xorg)\.)?freedesktop\.org/(?:wiki/)?} - if homepage =~ /Software/ - problem "#{homepage} should be styled `https://wiki.freedesktop.org/www/Software/project_name`" - else - problem "#{homepage} should be styled `https://wiki.freedesktop.org/project_name`" - end - end - - # Google Code homepages should end in a slash - if homepage =~ %r{^https?://code\.google\.com/p/[^/]+[^/]$} - problem "#{homepage} should end with a slash" - end - - # People will run into mixed content sometimes, but we should enforce and then add - # exemptions as they are discovered. Treat mixed content on homepages as a bug. - # Justify each exemptions with a code comment so we can keep track here. - case homepage - when %r{^http://[^/]*\.github\.io/}, - %r{^http://[^/]*\.sourceforge\.io/} - problem "Please use https:// for #{homepage}" - end - - if homepage =~ %r{^http://([^/]*)\.(sf|sourceforge)\.net(/|$)} - problem "#{homepage} should be `https://#{$1}.sourceforge.io/`" - end - - # There's an auto-redirect here, but this mistake is incredibly common too. - # Only applies to the homepage and subdomains for now, not the FTP URLs. - if homepage =~ %r{^http://((?:build|cloud|developer|download|extensions|git|glade|help|library|live|nagios|news|people|projects|rt|static|wiki|www)\.)?gnome\.org} - problem "Please use https:// for #{homepage}" - end - - # Compact the above into this list as we're able to remove detailed notations, etc over time. - case homepage - when %r{^http://[^/]*\.apache\.org}, - %r{^http://packages\.debian\.org}, - %r{^http://wiki\.freedesktop\.org/}, - %r{^http://((?:www)\.)?gnupg\.org/}, - %r{^http://ietf\.org}, - %r{^http://[^/.]+\.ietf\.org}, - %r{^http://[^/.]+\.tools\.ietf\.org}, - %r{^http://www\.gnu\.org/}, - %r{^http://code\.google\.com/}, - %r{^http://bitbucket\.org/}, - %r{^http://(?:[^/]*\.)?archive\.org} - problem "Please use https:// for #{homepage}" - end - return unless @online return unless DevelopmentTools.curl_handles_most_https_homepages? @@ -728,7 +657,10 @@ class FormulaAuditor } end - spec.patches.each { |p| audit_patch(p) if p.external? } + 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 %w[Stable Devel].each do |name| @@ -809,51 +741,67 @@ class FormulaAuditor return unless formula.tap.git? # git log is required return if @new_formula - fv = FormulaVersions.new(formula, max_depth: 1) + fv = FormulaVersions.new(formula) attributes = [:revision, :version_scheme] - attributes_map = fv.version_attributes_map(attributes, "origin/master") - attributes.each do |attribute| - stable_attribute_map = attributes_map[attribute][:stable] - next if stable_attribute_map.nil? || stable_attribute_map.empty? - - attributes_for_version = stable_attribute_map[formula.version] - next if attributes_for_version.nil? || attributes_for_version.empty? - - old_attribute = formula.send(attribute) - max_attribute = attributes_for_version.max - if max_attribute && old_attribute < max_attribute - problem "#{attribute} should not decrease (from #{max_attribute} to #{old_attribute})" - end - end - + current_version_scheme = formula.version_scheme [:stable, :devel].each do |spec| spec_version_scheme_map = attributes_map[:version_scheme][spec] - next if spec_version_scheme_map.nil? || spec_version_scheme_map.empty? + next if spec_version_scheme_map.empty? - max_version_scheme = spec_version_scheme_map.values.flatten.max + version_schemes = spec_version_scheme_map.values.flatten + max_version_scheme = version_schemes.max max_version = spec_version_scheme_map.select do |_, version_scheme| version_scheme.first == max_version_scheme end.keys.max - formula_spec = formula.send(spec) - next if formula_spec.nil? + if max_version_scheme && current_version_scheme < max_version_scheme + problem "version_scheme should not decrease (from #{max_version_scheme} to #{current_version_scheme})" + end - if max_version && formula_spec.version < max_version - problem "#{spec} version should not decrease (from #{max_version} to #{formula_spec.version})" + if max_version_scheme && current_version_scheme >= max_version_scheme && + current_version_scheme > 1 && + !version_schemes.include?(current_version_scheme - 1) + problem "version_schemes should only increment by 1" end + + formula_spec = formula.send(spec) + next unless formula_spec + + spec_version = formula_spec.version + next unless max_version + next if spec_version >= max_version + + above_max_version_scheme = current_version_scheme > max_version_scheme + map_includes_version = spec_version_scheme_map.keys.include?(spec_version) + next if !current_version_scheme.zero? && + (above_max_version_scheme || map_includes_version) + problem "#{spec} version should not decrease (from #{max_version} to #{spec_version})" end - return if formula.revision.zero? - if formula.stable - revision_map = attributes_map[:revision][:stable] - stable_revisions = revision_map[formula.stable.version] if revision_map - if !stable_revisions || stable_revisions.empty? + current_revision = formula.revision + revision_map = attributes_map[:revision][:stable] + if formula.stable && !revision_map.empty? + stable_revisions = revision_map[formula.stable.version] + stable_revisions ||= [] + max_revision = stable_revisions.max || 0 + + if current_revision < max_revision + problem "revision should not decrease (from #{max_revision} to #{current_revision})" + end + + stable_revisions -= [formula.revision] + if !current_revision.zero? && stable_revisions.empty? && + revision_map.keys.length > 1 problem "'revision #{formula.revision}' should be removed" + elsif current_revision > 1 && + current_revision != max_revision && + !stable_revisions.include?(current_revision - 1) + problem "revisions should only increment by 1" end - else # head/devel-only formula - problem "'revision #{formula.revision}' should be removed" + elsif !current_revision.zero? # head/devel-only formula + problem "'revision #{current_revision}' should be removed" end end @@ -864,10 +812,10 @@ class FormulaAuditor return if legacy_patches.empty? problem "Use the patch DSL instead of defining a 'patches' method" - legacy_patches.each { |p| audit_patch(p) } + legacy_patches.each { |p| patch_problems(p) } end - def audit_patch(patch) + def patch_problems(patch) case patch.url when /raw\.github\.com/, %r{gist\.github\.com/raw}, %r{gist\.github\.com/.+/raw}, %r{gist\.githubusercontent\.com/.+/raw} @@ -935,11 +883,21 @@ class FormulaAuditor problem "Formulae using virtualenvs do not need a `setuptools` resource." end + if text =~ /system\s+['"]go['"],\s+['"]get['"]/ + problem "Formulae should not use `go get`. If non-vendored resources are required use `go_resource`s." + end + return unless text.include?('require "language/go"') && !text.include?("go_resource") problem "require \"language/go\" is unnecessary unless using `go_resource`s" end - def audit_line(line, _lineno) + def audit_lines + text.without_patch.split("\n").each_with_index do |line, lineno| + line_problems(line, lineno+1) + end + end + + def line_problems(line, _lineno) if line =~ /<(Formula|AmazonWebServicesFormula|ScriptFileFormula|GithubGistFormula)/ problem "Use a space in class inheritance: class Foo < #{$1}" end @@ -1018,7 +976,19 @@ class FormulaAuditor end if line =~ /depends_on :tex/ - problem ":tex is deprecated." + problem ":tex is deprecated" + end + + if line =~ /depends_on\s+['"](.+)['"]\s+=>\s+:(lua|perl|python|ruby)(\d*)/ + problem "#{$2} modules should be vendored rather than use deprecated `depends_on \"#{$1}\" => :#{$2}#{$3}`" + end + + if line =~ /depends_on\s+['"](.+)['"]\s+=>\s+(.*)/ + dep = $1 + $2.split(" ").map do |o| + next unless o =~ /^\[?['"](.*)['"]/ + problem "Dependency #{dep} should not use option #{$1}" + end end # Commented-out depends_on @@ -1049,6 +1019,14 @@ class FormulaAuditor problem "Use ENV instead of invoking '#{$1}' to modify the environment" end + if formula.name != "wine" && line =~ /ENV\.universal_binary/ + problem "macOS has been 64-bit only since 10.6 so ENV.universal_binary is deprecated." + end + + if line =~ /build\.universal\?/ + problem "macOS has been 64-bit only so build.universal? is deprecated." + end + if line =~ /version == ['"]HEAD['"]/ problem "Use 'build.head?' instead of inspecting 'version'" end @@ -1078,11 +1056,11 @@ class FormulaAuditor end if line =~ /(not\s|!)\s*build\.with?\?/ - problem "Don't negate 'build.without?': use 'build.with?'" + problem "Don't negate 'build.with?': use 'build.without?'" end if line =~ /(not\s|!)\s*build\.without?\?/ - problem "Don't negate 'build.with?': use 'build.without?'" + problem "Don't negate 'build.without?': use 'build.with?'" end if line =~ /ARGV\.(?!(debug\?|verbose\?|value[\(\s]))/ @@ -1130,11 +1108,11 @@ class FormulaAuditor end if line =~ /depends_on :(.+) (if.+|unless.+)$/ - audit_conditional_dep($1.to_sym, $2, $&) + conditional_dep_problems($1.to_sym, $2, $&) end if line =~ /depends_on ['"](.+)['"] (if.+|unless.+)$/ - audit_conditional_dep($1, $2, $&) + conditional_dep_problems($1, $2, $&) end if line =~ /(Dir\[("[^\*{},]+")\])/ @@ -1171,6 +1149,10 @@ class FormulaAuditor end end + if line =~ /((revision|version_scheme)\s+0)/ + problem "'#{$1}' should be removed" + end + return unless @strict problem "`#{$1}` in formulae is deprecated" if line =~ /(env :(std|userpaths))/ @@ -1222,7 +1204,7 @@ class FormulaAuditor EOS end - def audit_conditional_dep(dep, condition, line) + def conditional_dep_problems(dep, condition, line) quoted_dep = quote_dep(dep) dep = Regexp.escape(dep.to_s) @@ -1238,30 +1220,26 @@ class FormulaAuditor dep.is_a?(Symbol) ? dep.inspect : "'#{dep}'" end - def audit_check_output(output) + def problem_if_output(output) problem(output) if output end def audit - audit_file - audit_formula_name - audit_class - audit_specs - audit_revision_and_version_scheme - audit_homepage - audit_bottle_spec - audit_github_repository - audit_deps - audit_conflicts - audit_options - audit_legacy_patches - audit_text - audit_caveats - text.without_patch.split("\n").each_with_index { |line, lineno| audit_line(line, lineno+1) } - audit_installed - audit_prefix_has_contents - audit_reverse_migration - audit_style + only_audits = ARGV.value("only").to_s.split(",") + except_audits = ARGV.value("except").to_s.split(",") + if !only_audits.empty? && !except_audits.empty? + odie "--only and --except cannot be used simultaneously!" + end + + methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name| + name = audit_method_name.gsub(/^audit_/, "") + if !only_audits.empty? + next unless only_audits.include?(name) + elsif !except_audits.empty? + next if except_audits.include?(name) + end + send(audit_method_name) + end end private @@ -1392,8 +1370,8 @@ class ResourceAuditor def audit_urls # Check GNU urls; doesn't apply to mirrors - if url =~ %r{^(?:https?|ftp)://(?!alpha).+/gnu/} - problem "Please use \"https://ftpmirror.gnu.org\" instead of #{url}." + if url =~ %r{^(?:https?|ftp)://ftpmirror.gnu.org/(.*)} + problem "Please use \"https://ftp.gnu.org/gnu/#{$1}\" instead of #{url}." end if mirrors.include?(url) diff --git a/Library/Homebrew/dev-cmd/create.rb b/Library/Homebrew/dev-cmd/create.rb index 6855d6f37..908f65f8f 100644 --- a/Library/Homebrew/dev-cmd/create.rb +++ b/Library/Homebrew/dev-cmd/create.rb @@ -10,7 +10,8 @@ #: If `--meson` is passed, create a basic template for a Meson-style build. #: #: If `--no-fetch` is passed, Homebrew will not download <URL> to the cache and -#: will thus not add the SHA256 to the formula for you. +#: will thus not add the SHA256 to the formula for you. It will also not check +#: the GitHub API for GitHub projects (to fill out the description and homepage). #: #: The options `--set-name` and `--set-version` each take an argument and allow #: you to explicitly set the name and version of the package you are creating. @@ -28,15 +29,6 @@ module Homebrew # Create a formula from a tarball URL def create - # Allow searching MacPorts or Fink. - if ARGV.include? "--macports" - opoo "`brew create --macports` is deprecated; use `brew search --macports` instead" - exec_browser "https://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" - elsif ARGV.include? "--fink" - opoo "`brew create --fink` is deprecated; use `brew search --fink` instead" - exec_browser "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" - end - raise UsageError if ARGV.named.empty? # Ensure that the cache exists so we can fetch the tarball @@ -100,7 +92,7 @@ module Homebrew end class FormulaCreator - attr_reader :url, :sha256 + attr_reader :url, :sha256, :desc, :homepage attr_accessor :name, :version, :tap, :path, :mode def url=(url) @@ -108,11 +100,15 @@ class FormulaCreator path = Pathname.new(url) if @name.nil? case url - when %r{github\.com/\S+/(\S+)\.git} - @name = $1 + when %r{github\.com/(\S+)/(\S+)\.git} + @user = $1 + @name = $2 @head = true - when %r{github\.com/\S+/(\S+)/archive/} - @name = $1 + @github = true + when %r{github\.com/(\S+)/(\S+)/(archive|releases)/} + @user = $1 + @name = $2 + @github = true else @name = path.basename.to_s[/(.*?)[-_.]?#{Regexp.escape(path.version.to_s)}/, 1] end @@ -131,7 +127,7 @@ class FormulaCreator end def fetch? - !head? && !ARGV.include?("--no-fetch") + !ARGV.include?("--no-fetch") end def head? @@ -145,11 +141,25 @@ class FormulaCreator opoo "Version cannot be determined from URL." puts "You'll need to add an explicit 'version' to the formula." elsif fetch? - r = Resource.new - r.url(url) - r.version(version) - r.owner = self - @sha256 = r.fetch.sha256 if r.download_strategy == CurlDownloadStrategy + unless head? + r = Resource.new + r.url(url) + r.version(version) + r.owner = self + @sha256 = r.fetch.sha256 if r.download_strategy == CurlDownloadStrategy + end + + if @user && @name + begin + metadata = GitHub.repository(@user, @name) + @desc = metadata["description"] + @homepage = metadata["homepage"] + rescue GitHub::HTTPNotFoundError + # If there was no repository found assume the network connection is at + # fault rather than the input URL. + nil + end + end end path.write ERB.new(template, nil, ">").result(binding) @@ -161,8 +171,8 @@ class FormulaCreator # PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST! class #{Formulary.class_s(name)} < Formula - desc "" - homepage "" + desc "#{desc}" + homepage "#{homepage}" <% if head? %> head "#{url}" <% else %> diff --git a/Library/Homebrew/dev-cmd/mirror.rb b/Library/Homebrew/dev-cmd/mirror.rb index 10811493c..e2492203d 100644 --- a/Library/Homebrew/dev-cmd/mirror.rb +++ b/Library/Homebrew/dev-cmd/mirror.rb @@ -8,10 +8,10 @@ module Homebrew def mirror odie "This command requires at least formula argument!" if ARGV.named.empty? - bintray_user = ENV["BINTRAY_USER"] - bintray_key = ENV["BINTRAY_KEY"] + bintray_user = ENV["HOMEBREW_BINTRAY_USER"] + bintray_key = ENV["HOMEBREW_BINTRAY_KEY"] if !bintray_user || !bintray_key - raise "Missing BINTRAY_USER or BINTRAY_KEY variables!" + raise "Missing HOMEBREW_BINTRAY_USER or HOMEBREW_BINTRAY_KEY variables!" end ARGV.formulae.each do |f| diff --git a/Library/Homebrew/dev-cmd/pull.rb b/Library/Homebrew/dev-cmd/pull.rb index 36c9ac27c..9d08da95b 100644 --- a/Library/Homebrew/dev-cmd/pull.rb +++ b/Library/Homebrew/dev-cmd/pull.rb @@ -263,7 +263,7 @@ module Homebrew end published = [] - bintray_creds = { user: ENV["BINTRAY_USER"], key: ENV["BINTRAY_KEY"] } + bintray_creds = { user: ENV["HOMEBREW_BINTRAY_USER"], key: ENV["HOMEBREW_BINTRAY_KEY"] } if bintray_creds[:user] && bintray_creds[:key] changed_formulae_names.each do |name| f = Formula[name] @@ -272,7 +272,7 @@ module Homebrew published << f.full_name end else - opoo "You must set BINTRAY_USER and BINTRAY_KEY to add or update bottles on Bintray!" + opoo "You must set HOMEBREW_BINTRAY_USER and HOMEBREW_BINTRAY_KEY to add or update bottles on Bintray!" end published end @@ -608,7 +608,7 @@ module Homebrew def check_bintray_mirror(name, url) headers = curl_output("--connect-timeout", "15", "--head", url)[0] status_code = headers.scan(%r{^HTTP\/.* (\d+)}).last.first - return if status_code.start_with?("3") + return if status_code.start_with?("2") opoo "The Bintray mirror #{url} is not reachable (HTTP status code #{status_code})." opoo "Do you need to upload it with `brew mirror #{name}`?" end diff --git a/Library/Homebrew/dev-cmd/tap-new.rb b/Library/Homebrew/dev-cmd/tap-new.rb index df295bf26..dcb41265c 100644 --- a/Library/Homebrew/dev-cmd/tap-new.rb +++ b/Library/Homebrew/dev-cmd/tap-new.rb @@ -47,8 +47,12 @@ module Homebrew language: ruby os: osx env: OSX=10.12 - osx_image: xcode8.1 + osx_image: xcode8.3 rvm: system + cache: + directories: + - $HOME/.gem/ruby + - Library/Homebrew/vendor/bundle before_install: - export TRAVIS_COMMIT="$(git rev-parse --verify -q HEAD)" diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index 91c7d880b..d90326768 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -33,7 +33,12 @@ module Homebrew ENV["HOMEBREW_DEVELOPER"] = "1" ENV["HOMEBREW_NO_COMPAT"] = "1" if ARGV.include? "--no-compat" ENV["HOMEBREW_TEST_GENERIC_OS"] = "1" if ARGV.include? "--generic" - ENV["HOMEBREW_NO_GITHUB_API"] = "1" unless ARGV.include? "--online" + + if ARGV.include? "--online" + ENV["HOMEBREW_TEST_ONLINE"] = "1" + else + ENV["HOMEBREW_NO_GITHUB_API"] = "1" + end if ARGV.include? "--official-cmd-taps" ENV["HOMEBREW_TEST_OFFICIAL_CMD_TAPS"] = "1" @@ -74,20 +79,24 @@ module Homebrew Dir.glob("test/**/*_spec.rb").reject { |p| p =~ %r{^test/vendor/bundle/} } end - opts = [] - - if ENV["CI"] - opts << "--combine-stderr" - opts << "--serialize-stdout" + opts = if ENV["CI"] + %w[ + --combine-stderr + --serialize-stdout + ] + else + %w[ + --nice + ] end - args = [ - "--color", - "-I", HOMEBREW_LIBRARY_PATH/"test", - "--require", "spec_helper", - "--format", "progress", - "--format", "ParallelTests::RSpec::RuntimeLogger", - "--out", "tmp/parallel_runtime_rspec.log" + args = ["-I", HOMEBREW_LIBRARY_PATH/"test"] + args += %w[ + --color + --require spec_helper + --format progress + --format ParallelTests::RSpec::RuntimeLogger + --out tmp/parallel_runtime_rspec.log ] args << "--seed" << ARGV.next if ARGV.include? "--seed" @@ -97,6 +106,10 @@ module Homebrew files = files.reject { |p| p =~ %r{^test/(os/mac|cask)(/.*|_spec\.rb)$} } end + unless OS.linux? + files = files.reject { |p| p =~ %r{^test/os/linux(/.*|_spec\.rb)$} } + end + if parallel system "bundle", "exec", "parallel_rspec", *opts, "--", *args, "--", *files else diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 61cdf2f1a..6bb5c8b8e 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -100,8 +100,7 @@ module Homebrew # See https://github.com/Homebrew/legacy-homebrew/pull/9986 def check_path_for_trailing_slashes - all_paths = ENV["PATH"].split(File::PATH_SEPARATOR) - bad_paths = all_paths.select { |p| p[-1..-1] == "/" } + bad_paths = PATH.new(ENV["HOMEBREW_PATH"]).select { |p| p.end_with?("/") } return if bad_paths.empty? inject_file_list bad_paths, <<-EOS.undent @@ -439,7 +438,7 @@ module Homebrew message = "" - paths.each do |p| + paths(ENV["HOMEBREW_PATH"]).each do |p| case p when "/usr/bin" unless $seen_prefix_bin @@ -460,7 +459,7 @@ module Homebrew Consider setting your PATH so that #{HOMEBREW_PREFIX}/bin occurs before /usr/bin. Here is a one-liner: - #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/bin")} + #{Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/bin")} EOS end end @@ -480,7 +479,7 @@ module Homebrew <<-EOS.undent Homebrew's bin was not found in your PATH. Consider setting the PATH for example like so - #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/bin")} + #{Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/bin")} EOS end @@ -495,7 +494,7 @@ module Homebrew Homebrew's sbin was not found in your PATH but you have installed formulae that put executables in #{HOMEBREW_PREFIX}/sbin. Consider setting the PATH for example like so - #{Utils::Shell.prepend_path_in_shell_profile("#{HOMEBREW_PREFIX}/sbin")} + #{Utils::Shell.prepend_path_in_profile("#{HOMEBREW_PREFIX}/sbin")} EOS end @@ -609,7 +608,7 @@ module Homebrew /Applications/Server.app/Contents/ServerRoot/usr/sbin ].map(&:downcase) - paths.each do |p| + paths(ENV["HOMEBREW_PATH"]).each do |p| next if whitelist.include?(p.downcase) || !File.directory?(p) realpath = Pathname.new(p).realpath.to_s diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb index a0025cef3..d74efe0bb 100644 --- a/Library/Homebrew/download_strategy.rb +++ b/Library/Homebrew/download_strategy.rb @@ -520,6 +520,9 @@ class S3DownloadStrategy < CurlDownloadStrategy bucket = $1 key = $2 + ENV["AWS_ACCESS_KEY_ID"] = ENV["HOMEBREW_AWS_ACCESS_KEY_ID"] + ENV["AWS_SECRET_ACCESS_KEY"] = ENV["HOMEBREW_AWS_SECRET_ACCESS_KEY"] + obj = AWS::S3.new.buckets[bucket].objects[key] begin s3url = obj.url_for(:get) diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index cfdf5e12d..6751b2224 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -77,35 +77,11 @@ class FormulaUnavailableError < RuntimeError end end -class TapFormulaUnavailableError < FormulaUnavailableError - attr_reader :tap, :user, :repo - - def initialize(tap, name) - @tap = tap - @user = tap.user - @repo = tap.repo - super "#{tap}/#{name}" - end - - def to_s - s = super - s += "\nPlease tap it and then try again: brew tap #{tap}" unless tap.installed? - s - end -end - -class FormulaClassUnavailableError < FormulaUnavailableError +module FormulaClassUnavailableErrorModule attr_reader :path attr_reader :class_name attr_reader :class_list - def initialize(name, path, class_name, class_list) - @path = path - @class_name = class_name - @class_list = class_list - super name - end - def to_s s = super s += "\nIn formula file: #{path}" @@ -131,16 +107,70 @@ class FormulaClassUnavailableError < FormulaUnavailableError end end -class FormulaUnreadableError < FormulaUnavailableError +class FormulaClassUnavailableError < FormulaUnavailableError + include FormulaClassUnavailableErrorModule + + def initialize(name, path, class_name, class_list) + @path = path + @class_name = class_name + @class_list = class_list + super name + end +end + +module FormulaUnreadableErrorModule attr_reader :formula_error + def to_s + "#{name}: " + formula_error.to_s + end +end + +class FormulaUnreadableError < FormulaUnavailableError + include FormulaUnreadableErrorModule + def initialize(name, error) super(name) @formula_error = error end +end + +class TapFormulaUnavailableError < FormulaUnavailableError + attr_reader :tap, :user, :repo + + def initialize(tap, name) + @tap = tap + @user = tap.user + @repo = tap.repo + super "#{tap}/#{name}" + end def to_s - "#{name}: " + formula_error.to_s + s = super + s += "\nPlease tap it and then try again: brew tap #{tap}" unless tap.installed? + s + end +end + +class TapFormulaClassUnavailableError < TapFormulaUnavailableError + include FormulaClassUnavailableErrorModule + + attr_reader :tap + + def initialize(tap, name, path, class_name, class_list) + @path = path + @class_name = class_name + @class_list = class_list + super tap, name + end +end + +class TapFormulaUnreadableError < TapFormulaUnavailableError + include FormulaUnreadableErrorModule + + def initialize(tap, name, error) + super(tap, name) + @formula_error = error end end diff --git a/Library/Homebrew/extend/ENV.rb b/Library/Homebrew/extend/ENV.rb index 729598e28..283e90b69 100644 --- a/Library/Homebrew/extend/ENV.rb +++ b/Library/Homebrew/extend/ENV.rb @@ -26,6 +26,13 @@ module EnvActivation ensure replace(old_env) end + + def clear_sensitive_environment! + ENV.keys.each do |key| + next unless /(cookie|key|token)/i =~ key + ENV.delete key + end + end end ENV.extend(EnvActivation) diff --git a/Library/Homebrew/extend/ENV/shared.rb b/Library/Homebrew/extend/ENV/shared.rb index 2cdc2f83a..b51ade48b 100644 --- a/Library/Homebrew/extend/ENV/shared.rb +++ b/Library/Homebrew/extend/ENV/shared.rb @@ -1,6 +1,7 @@ require "formula" require "compilers" require "development_tools" +require "PATH" # Homebrew extends Ruby's `ENV` to make our code more readable. # Implemented in {SharedEnvExtension} and either {Superenv} or @@ -80,7 +81,7 @@ module SharedEnvExtension end def append_path(key, path) - append key, path, File::PATH_SEPARATOR if File.directory? path + self[key] = PATH.new(self[key]).append(path) end # Prepends a directory to `PATH`. @@ -92,7 +93,7 @@ module SharedEnvExtension # (e.g. <pre>ENV.prepend_path "PATH", which("emacs").dirname</pre>) def prepend_path(key, path) return if %w[/usr/bin /bin /usr/sbin /sbin].include? path.to_s - prepend key, path, File::PATH_SEPARATOR if File.directory? path + self[key] = PATH.new(self[key]).prepend(path) end def prepend_create_path(key, path) @@ -196,22 +197,23 @@ module SharedEnvExtension # @private def userpaths! - paths = self["PATH"].split(File::PATH_SEPARATOR) - # put Superenv.bin and opt path at the first - new_paths = paths.select { |p| p.start_with?("#{HOMEBREW_REPOSITORY}/Library/ENV", "#{HOMEBREW_PREFIX}/opt") } - # XXX hot fix to prefer brewed stuff (e.g. python) over /usr/bin. - new_paths << "#{HOMEBREW_PREFIX}/bin" - # reset of self["PATH"] - new_paths += paths - # user paths - new_paths += ORIGINAL_PATHS.map do |p| - begin - p.realpath.to_s - rescue - nil - end - end - %w[/usr/X11/bin /opt/X11/bin] - self["PATH"] = new_paths.uniq.join(File::PATH_SEPARATOR) + path = PATH.new(self["PATH"]).select do |p| + # put Superenv.bin and opt path at the first + p.start_with?("#{HOMEBREW_REPOSITORY}/Library/ENV", "#{HOMEBREW_PREFIX}/opt") + end + path.append(HOMEBREW_PREFIX/"bin") # XXX hot fix to prefer brewed stuff (e.g. python) over /usr/bin. + path.append(self["PATH"]) # reset of self["PATH"] + path.append( + # user paths + ORIGINAL_PATHS.map do |p| + begin + p.realpath.to_s + rescue + nil + end + end - %w[/usr/X11/bin /opt/X11/bin], + ) + self["PATH"] = path end def fortran @@ -244,7 +246,7 @@ module SharedEnvExtension else if (gfortran = which("gfortran", (HOMEBREW_PREFIX/"bin").to_s)) ohai "Using Homebrew-provided fortran compiler." - elsif (gfortran = which("gfortran", ORIGINAL_PATHS.join(File::PATH_SEPARATOR))) + elsif (gfortran = which("gfortran", PATH.new(ORIGINAL_PATHS))) ohai "Using a fortran compiler found at #{gfortran}." end if gfortran diff --git a/Library/Homebrew/extend/ENV/std.rb b/Library/Homebrew/extend/ENV/std.rb index 403ea1978..c4cc0985f 100644 --- a/Library/Homebrew/extend/ENV/std.rb +++ b/Library/Homebrew/extend/ENV/std.rb @@ -1,7 +1,6 @@ require "hardware" require "extend/ENV/shared" -# TODO: deprecate compiling related codes after it's only used by brew test. # @private module Stdenv include SharedEnvExtension @@ -58,12 +57,12 @@ module Stdenv end def determine_pkg_config_libdir - paths = [] - paths << "#{HOMEBREW_PREFIX}/lib/pkgconfig" - paths << "#{HOMEBREW_PREFIX}/share/pkgconfig" - paths += homebrew_extra_pkg_config_paths - paths << "/usr/lib/pkgconfig" - paths.select { |d| File.directory? d }.join(File::PATH_SEPARATOR) + PATH.new( + HOMEBREW_PREFIX/"lib/pkgconfig", + HOMEBREW_PREFIX/"share/pkgconfig", + homebrew_extra_pkg_config_paths, + "/usr/lib/pkgconfig", + ).existing end # Removes the MAKEFLAGS environment variable, causing make to use a single job. diff --git a/Library/Homebrew/extend/ENV/super.rb b/Library/Homebrew/extend/ENV/super.rb index 0935647f5..ef41161af 100644 --- a/Library/Homebrew/extend/ENV/super.rb +++ b/Library/Homebrew/extend/ENV/super.rb @@ -101,29 +101,28 @@ module Superenv end def determine_path - paths = [Superenv.bin] + path = PATH.new(Superenv.bin) # Formula dependencies can override standard tools. - paths += deps.map { |d| d.opt_bin.to_s } - - paths += homebrew_extra_paths - paths += %w[/usr/bin /bin /usr/sbin /sbin] + path.append(deps.map(&:opt_bin)) + path.append(homebrew_extra_paths) + path.append("/usr/bin", "/bin", "/usr/sbin", "/sbin") # Homebrew's apple-gcc42 will be outside the PATH in superenv, # so xcrun may not be able to find it begin case homebrew_cc when "gcc-4.2" - paths << Formulary.factory("apple-gcc42").opt_bin + path.append(Formulary.factory("apple-gcc42").opt_bin) when GNU_GCC_REGEXP - paths << gcc_version_formula($&).opt_bin + path.append(gcc_version_formula($&).opt_bin) end rescue FormulaUnavailableError # Don't fail and don't add these formulae to the path if they don't exist. nil end - paths.to_path_s + path.existing end def homebrew_extra_pkg_config_paths @@ -131,15 +130,17 @@ module Superenv end def determine_pkg_config_path - paths = deps.map { |d| "#{d.opt_lib}/pkgconfig" } - paths += deps.map { |d| "#{d.opt_share}/pkgconfig" } - paths.to_path_s + PATH.new( + deps.map { |d| d.opt_lib/"pkgconfig" }, + deps.map { |d| d.opt_share/"pkgconfig" }, + ).existing end def determine_pkg_config_libdir - paths = %w[/usr/lib/pkgconfig] - paths += homebrew_extra_pkg_config_paths - paths.to_path_s + PATH.new( + "/usr/lib/pkgconfig", + homebrew_extra_pkg_config_paths, + ).existing end def homebrew_extra_aclocal_paths @@ -147,10 +148,11 @@ module Superenv end def determine_aclocal_path - paths = keg_only_deps.map { |d| "#{d.opt_share}/aclocal" } - paths << "#{HOMEBREW_PREFIX}/share/aclocal" - paths += homebrew_extra_aclocal_paths - paths.to_path_s + PATH.new( + keg_only_deps.map { |d| d.opt_share/"aclocal" }, + HOMEBREW_PREFIX/"share/aclocal", + homebrew_extra_aclocal_paths, + ).existing end def homebrew_extra_isystem_paths @@ -158,13 +160,14 @@ module Superenv end def determine_isystem_paths - paths = ["#{HOMEBREW_PREFIX}/include"] - paths += homebrew_extra_isystem_paths - paths.to_path_s + PATH.new( + HOMEBREW_PREFIX/"include", + homebrew_extra_isystem_paths, + ).existing end def determine_include_paths - keg_only_deps.map { |d| d.opt_include.to_s }.to_path_s + PATH.new(keg_only_deps.map(&:opt_include)).existing end def homebrew_extra_library_paths @@ -172,10 +175,11 @@ module Superenv end def determine_library_paths - paths = keg_only_deps.map { |d| d.opt_lib.to_s } - paths << "#{HOMEBREW_PREFIX}/lib" - paths += homebrew_extra_library_paths - paths.to_path_s + PATH.new( + keg_only_deps.map(&:opt_lib), + HOMEBREW_PREFIX/"lib", + homebrew_extra_library_paths, + ).existing end def determine_dependencies @@ -183,9 +187,10 @@ module Superenv end def determine_cmake_prefix_path - paths = keg_only_deps.map { |d| d.opt_prefix.to_s } - paths << HOMEBREW_PREFIX.to_s - paths.to_path_s + PATH.new( + keg_only_deps.map(&:opt_prefix), + HOMEBREW_PREFIX.to_s, + ).existing end def homebrew_extra_cmake_include_paths @@ -193,9 +198,7 @@ module Superenv end def determine_cmake_include_path - paths = [] - paths += homebrew_extra_cmake_include_paths - paths.to_path_s + PATH.new(homebrew_extra_cmake_include_paths).existing end def homebrew_extra_cmake_library_paths @@ -203,9 +206,7 @@ module Superenv end def determine_cmake_library_path - paths = [] - paths += homebrew_extra_cmake_library_paths - paths.to_path_s + PATH.new(homebrew_extra_cmake_library_paths).existing end def homebrew_extra_cmake_frameworks_paths @@ -213,9 +214,10 @@ module Superenv end def determine_cmake_frameworks_path - paths = deps.map { |d| d.opt_frameworks.to_s } - paths += homebrew_extra_cmake_frameworks_paths - paths.to_path_s + PATH.new( + deps.map(&:opt_frameworks), + homebrew_extra_cmake_frameworks_paths, + ).existing end def determine_make_jobs @@ -326,15 +328,7 @@ module Superenv def set_x11_env_if_installed end - # This method does nothing in superenv since there's no custom CFLAGS API - # @private - def set_cpu_flags(*_args) - end -end - -class Array - def to_path_s - map(&:to_s).uniq.select { |s| File.directory? s }.join(File::PATH_SEPARATOR).chuzzle + def set_cpu_flags(*) end end diff --git a/Library/Homebrew/extend/io.rb b/Library/Homebrew/extend/io.rb index 1357293cd..53bca196e 100644 --- a/Library/Homebrew/extend/io.rb +++ b/Library/Homebrew/extend/io.rb @@ -1,10 +1,17 @@ class IO def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR) + line = "" buffer = "" - buffer.concat(read_nonblock(1)) while buffer[-1] != sep - buffer + + loop do + break if buffer == sep + read_nonblock(1, buffer) + line.concat(buffer) + end + + line rescue IO::WaitReadable, EOFError => e - raise e if buffer.empty? - buffer + raise e if line.empty? + line end end diff --git a/Library/Homebrew/extend/os/linux/requirements/osxfuse_requirement.rb b/Library/Homebrew/extend/os/linux/requirements/osxfuse_requirement.rb new file mode 100644 index 000000000..3fd847bc4 --- /dev/null +++ b/Library/Homebrew/extend/os/linux/requirements/osxfuse_requirement.rb @@ -0,0 +1,39 @@ +require "requirement" + +class OsxfuseRequirement < Requirement + download "https://github.com/libfuse/libfuse" + + satisfy(build_env: false) do + next true if libfuse_formula_exists? && Formula["libfuse"].installed? + includedirs = %w[ + /usr/include + /usr/local/include + ] + next true if (includedirs.map do |dir| + File.exist? "#{dir}/fuse.h" + end).any? + false + end + + def message + msg = "libfuse is required to install this formula.\n" + if libfuse_formula_exists? + msg + <<-EOS.undent + Run "brew install libfuse" to install it. + EOS + else + msg + super + end + end + + private + + def libfuse_formula_exists? + begin + Formula["libfuse"] + rescue FormulaUnavailableError + return false + end + true + end +end diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb index fb6b30836..ff936c75a 100644 --- a/Library/Homebrew/extend/os/mac/diagnostic.rb +++ b/Library/Homebrew/extend/os/mac/diagnostic.rb @@ -263,7 +263,7 @@ module Homebrew SSL_CERT_DIR support was removed from Apple's curl. If fetching formulae fails you should: unset SSL_CERT_DIR - and remove it from #{Utils::Shell.shell_profile} if present. + and remove it from #{Utils::Shell.profile} if present. EOS end diff --git a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb index 5b1c648bf..10379c981 100644 --- a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb +++ b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb @@ -67,7 +67,7 @@ module FormulaCellarChecks checker = LinkageChecker.new(keg, formula) return unless checker.broken_dylibs? - audit_check_output <<-EOS.undent + problem_if_output <<-EOS.undent The installation was broken. Broken dylib links found: #{checker.broken_dylibs.to_a * "\n "} @@ -76,9 +76,9 @@ module FormulaCellarChecks def audit_installed generic_audit_installed - audit_check_output(check_shadowed_headers) - audit_check_output(check_openssl_links) - audit_check_output(check_python_framework_links(formula.lib)) + problem_if_output(check_shadowed_headers) + problem_if_output(check_openssl_links) + problem_if_output(check_python_framework_links(formula.lib)) check_linkage end end diff --git a/Library/Homebrew/extend/os/mac/hardware/cpu.rb b/Library/Homebrew/extend/os/mac/hardware/cpu.rb index f180995fb..22d118e1a 100644 --- a/Library/Homebrew/extend/os/mac/hardware/cpu.rb +++ b/Library/Homebrew/extend/os/mac/hardware/cpu.rb @@ -107,6 +107,20 @@ module Hardware end end + # Determines whether the current CPU and macOS combination + # can run an executable of the specified architecture. + # `arch` is a symbol in the same format returned by + # Hardware::CPU.family + def can_run?(arch) + if Hardware::CPU.intel? + intel_can_run? arch + elsif Hardware::CPU.ppc? + ppc_can_run? arch + else + false + end + end + def features @features ||= sysctl_n( "machdep.cpu.features", @@ -162,6 +176,35 @@ module Hardware @properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys) end end + + def intel_can_run?(arch) + case arch + when :ppc + # Rosetta is still available + MacOS.version < :lion + when :ppc64 + # Rosetta never supported PPC64 + false + when :x86_64 + Hardware::CPU.is_64_bit? + when :i386 + true + else # dunno + false + end + end + + def ppc_can_run?(arch) + case arch + when :ppc + true + when :ppc64 + Hardware::CPU.is_64_bit? + else + # Intel is never supported + false + end + end end end end diff --git a/Library/Homebrew/extend/os/mac/requirements/osxfuse_requirement.rb b/Library/Homebrew/extend/os/mac/requirements/osxfuse_requirement.rb new file mode 100644 index 000000000..8c898a272 --- /dev/null +++ b/Library/Homebrew/extend/os/mac/requirements/osxfuse_requirement.rb @@ -0,0 +1,34 @@ +require "requirement" + +class OsxfuseRequirement < Requirement + cask "osxfuse" + download "https://osxfuse.github.io/" + + satisfy(build_env: false) { self.class.binary_osxfuse_installed? } + + def self.binary_osxfuse_installed? + File.exist?("/usr/local/include/osxfuse/fuse.h") && + !File.symlink?("/usr/local/include/osxfuse") + end + + env do + ENV.append_path "PKG_CONFIG_PATH", HOMEBREW_LIBRARY/"Homebrew/os/mac/pkgconfig/fuse" + + unless HOMEBREW_PREFIX.to_s == "/usr/local" + ENV.append_path "HOMEBREW_LIBRARY_PATHS", "/usr/local/lib" + ENV.append_path "HOMEBREW_INCLUDE_PATHS", "/usr/local/include/osxfuse" + end + end +end + +class NonBinaryOsxfuseRequirement < Requirement + fatal true + satisfy(build_env: false) { HOMEBREW_PREFIX.to_s != "/usr/local" || !OsxfuseRequirement.binary_osxfuse_installed? } + + def message + <<-EOS.undent + osxfuse is already installed from the binary distribution and + conflicts with this formula. + EOS + end +end diff --git a/Library/Homebrew/extend/os/requirements/osxfuse_requirement.rb b/Library/Homebrew/extend/os/requirements/osxfuse_requirement.rb new file mode 100644 index 000000000..5f56d48c4 --- /dev/null +++ b/Library/Homebrew/extend/os/requirements/osxfuse_requirement.rb @@ -0,0 +1,7 @@ +require "requirements/osxfuse_requirement" + +if OS.mac? + require "extend/os/mac/requirements/osxfuse_requirement" +elsif OS.linux? + require "extend/os/linux/requirements/osxfuse_requirement" +end diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb index eb254c624..c413e9e94 100644 --- a/Library/Homebrew/extend/pathname.rb +++ b/Library/Homebrew/extend/pathname.rb @@ -331,7 +331,6 @@ class Pathname raise ChecksumMismatchError.new(self, expected, actual) unless expected == actual end - # FIXME: eliminate the places where we rely on this method alias to_str to_s unless method_defined?(:to_str) def cd @@ -365,9 +364,8 @@ class Pathname unless method_defined?(:/) def /(other) - unless other.respond_to?(:to_str) || other.respond_to?(:to_path) - opoo "Pathname#/ called on #{inspect} with #{other.inspect} as an argument" - puts "This behavior is deprecated, please pass either a String or a Pathname" + if !other.respond_to?(:to_str) && !other.respond_to?(:to_path) + odeprecated "Pathname#/ with #{other.class}", "a String or a Pathname" end self + other.to_s end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index d544bd668..1230199bf 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -13,6 +13,7 @@ require "pkg_version" require "tap" require "keg" require "migrator" +require "extend/ENV" # A formula provides instructions and metadata for Homebrew to install a piece # of software. Every Homebrew formula is a {Formula}. @@ -1004,19 +1005,26 @@ class Formula def post_install; end # @private - def post_install_defined? - method(:post_install).owner == self.class - end - - # @private def run_post_install @prefix_returns_versioned_prefix = true build = self.build self.build = Tab.for_formula(self) + old_tmpdir = ENV["TMPDIR"] old_temp = ENV["TEMP"] old_tmp = ENV["TMP"] + old_path = ENV["HOMEBREW_PATH"] + ENV["TMPDIR"] = ENV["TEMP"] = ENV["TMP"] = HOMEBREW_TEMP + ENV["HOMEBREW_PATH"] = nil + + ENV.clear_sensitive_environment! + + Pathname.glob("#{bottle_prefix}/{etc,var}/**/*") do |path| + path.extend(InstallRenamed) + path.cp_path_sub(bottle_prefix, HOMEBREW_PREFIX) + end + with_logging("post_install") do post_install end @@ -1025,6 +1033,7 @@ class Formula ENV["TMPDIR"] = old_tmpdir ENV["TEMP"] = old_temp ENV["TMP"] = old_tmp + ENV["HOMEBREW_PATH"] = old_path @prefix_returns_versioned_prefix = false end @@ -1664,9 +1673,15 @@ class Formula old_temp = ENV["TEMP"] old_tmp = ENV["TMP"] old_term = ENV["TERM"] + old_path = ENV["HOMEBREW_PATH"] + ENV["CURL_HOME"] = old_curl_home || old_home ENV["TMPDIR"] = ENV["TEMP"] = ENV["TMP"] = HOMEBREW_TEMP ENV["TERM"] = "dumb" + ENV["HOMEBREW_PATH"] = nil + + ENV.clear_sensitive_environment! + mktemp("#{name}-test") do |staging| staging.retain! if ARGV.keep_tmp? @testpath = staging.tmpdir @@ -1689,6 +1704,7 @@ class Formula ENV["TEMP"] = old_temp ENV["TMP"] = old_tmp ENV["TERM"] = old_term + ENV["HOMEBREW_PATH"] = old_path @prefix_returns_versioned_prefix = false end @@ -1892,7 +1908,6 @@ class Formula def exec_cmd(cmd, args, out, logfn) ENV["HOMEBREW_CC_LOG_PATH"] = logfn - # TODO: system "xcodebuild" is deprecated, this should be removed soon. ENV.remove_cc_etc if cmd.to_s.start_with? "xcodebuild" # Turn on argument filtering in the superenv compiler wrapper. @@ -1925,17 +1940,28 @@ class Formula mkdir_p env_home old_home = ENV["HOME"] - ENV["HOME"] = env_home old_curl_home = ENV["CURL_HOME"] - ENV["CURL_HOME"] = old_curl_home || old_home + old_path = ENV["HOMEBREW_PATH"] + + unless ARGV.interactive? + ENV["HOME"] = env_home + ENV["CURL_HOME"] = old_curl_home || old_home + end + ENV["HOMEBREW_PATH"] = nil + setup_home env_home + ENV.clear_sensitive_environment! + begin yield staging ensure @buildpath = nil - ENV["HOME"] = old_home - ENV["CURL_HOME"] = old_curl_home + unless ARGV.interactive? + ENV["HOME"] = old_home + ENV["CURL_HOME"] = old_curl_home + end + ENV["HOMEBREW_PATH"] = old_path end end end @@ -2396,7 +2422,6 @@ class Formula # version '4.8.1' # end</pre> def fails_with(compiler, &block) - # TODO: deprecate this in future. # odeprecated "fails_with :llvm" if compiler == :llvm specs.each { |spec| spec.fails_with(compiler, &block) } end diff --git a/Library/Homebrew/formula_cellar_checks.rb b/Library/Homebrew/formula_cellar_checks.rb index 7db5e748b..7f7d77569 100644 --- a/Library/Homebrew/formula_cellar_checks.rb +++ b/Library/Homebrew/formula_cellar_checks.rb @@ -14,7 +14,7 @@ module FormulaCellarChecks <<-EOS.undent #{prefix_bin} is not in your PATH - You can amend this by altering your #{Utils::Shell.shell_profile} file + You can amend this by altering your #{Utils::Shell.profile} file EOS end @@ -154,17 +154,17 @@ module FormulaCellarChecks end def audit_installed - audit_check_output(check_manpages) - audit_check_output(check_infopages) - audit_check_output(check_jars) - audit_check_output(check_non_libraries) - audit_check_output(check_non_executables(formula.bin)) - audit_check_output(check_generic_executables(formula.bin)) - audit_check_output(check_non_executables(formula.sbin)) - audit_check_output(check_generic_executables(formula.sbin)) - audit_check_output(check_easy_install_pth(formula.lib)) - audit_check_output(check_elisp_dirname(formula.share, formula.name)) - audit_check_output(check_elisp_root(formula.share, formula.name)) + problem_if_output(check_manpages) + problem_if_output(check_infopages) + problem_if_output(check_jars) + problem_if_output(check_non_libraries) + problem_if_output(check_non_executables(formula.bin)) + problem_if_output(check_generic_executables(formula.bin)) + problem_if_output(check_non_executables(formula.sbin)) + problem_if_output(check_generic_executables(formula.sbin)) + problem_if_output(check_easy_install_pth(formula.lib)) + problem_if_output(check_elisp_dirname(formula.share, formula.name)) + problem_if_output(check_elisp_root(formula.share, formula.name)) end alias generic_audit_installed audit_installed diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 017be51dc..f50d9ed9e 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -173,34 +173,22 @@ class FormulaInstaller EOS end - if ENV["HOMEBREW_CHECK_RECURSIVE_VERSION_DEPENDENCIES"] - version_hash = {} - version_conflicts = Set.new - recursive_formulae.each do |f| - name = f.name - unversioned_name, = name.split("@") - version_hash[unversioned_name] ||= Set.new - version_hash[unversioned_name] << name - next if version_hash[unversioned_name].length < 2 - version_conflicts += version_hash[unversioned_name] - end - unless version_conflicts.empty? - raise CannotInstallFormulaError, <<-EOS.undent - #{formula.full_name} contains conflicting version recursive dependencies: - #{version_conflicts.to_a.join ", "} - View these with `brew deps --tree #{formula.full_name}`. - EOS - end - end - - unless ENV["HOMEBREW_NO_CHECK_UNLINKED_DEPENDENCIES"] - unlinked_deps = recursive_formulae.select do |dep| - dep.installed? && !dep.keg_only? && !dep.linked_keg.directory? - end - - unless unlinked_deps.empty? - raise CannotInstallFormulaError, "You must `brew link #{unlinked_deps*" "}` before #{formula.full_name} can be installed" - end + version_hash = {} + version_conflicts = Set.new + recursive_formulae.each do |f| + name = f.name + unversioned_name, = name.split("@") + version_hash[unversioned_name] ||= Set.new + version_hash[unversioned_name] << name + next if version_hash[unversioned_name].length < 2 + version_conflicts += version_hash[unversioned_name] + end + unless version_conflicts.empty? + raise CannotInstallFormulaError, <<-EOS.undent + #{formula.full_name} contains conflicting version recursive dependencies: + #{version_conflicts.to_a.join ", "} + View these with `brew deps --tree #{formula.full_name}`. + EOS end pinned_unsatisfied_deps = recursive_deps.select do |dep| @@ -229,11 +217,20 @@ class FormulaInstaller # function but after instantiating this class so that it can avoid having to # relink the active keg if possible (because it is slow). if formula.linked_keg.directory? - # some other version is already installed *and* linked - raise CannotInstallFormulaError, <<-EOS.undent - #{formula.name}-#{formula.linked_keg.resolved_path.basename} already installed - To install this version, first `brew unlink #{formula.name}` + message = <<-EOS.undent + #{formula.name} #{formula.linked_keg.resolved_path.basename} is already installed EOS + message += if formula.outdated? && !formula.head? + <<-EOS.undent + To upgrade to #{formula.version}, run `brew upgrade #{formula.name}` + EOS + else + # some other version is already installed *and* linked + <<-EOS.undent + To install #{formula.version}, first run `brew unlink #{formula.name}` + EOS + end + raise CannotInstallFormulaError, message end check_conflicts @@ -567,13 +564,11 @@ class FormulaInstaller fix_dynamic_linkage(keg) end - if formula.post_install_defined? - if build_bottle? - ohai "Not running post_install as we're building a bottle" - puts "You can run it manually using `brew postinstall #{formula.full_name}`" - else - post_install - end + if build_bottle? + ohai "Not running post_install as we're building a bottle" + puts "You can run it manually using `brew postinstall #{formula.full_name}`" + else + post_install end caveats @@ -661,13 +656,15 @@ class FormulaInstaller Sandbox.print_sandbox_message if Sandbox.formula?(formula) Utils.safe_fork do - # Invalidate the current sudo timestamp in case a build script calls sudo - system "/usr/bin/sudo", "-k" + # 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. + system "/usr/bin/sudo", "-k" unless ENV["TRAVIS_SUDO"] == "false" if Sandbox.formula?(formula) sandbox = Sandbox.new formula.logs.mkpath sandbox.record_log(formula.logs/"build.sandbox.log") + sandbox.allow_write_path(ENV["HOME"]) if ARGV.interactive? sandbox.allow_write_temp_and_cache sandbox.allow_write_log(formula) sandbox.allow_write_xcode @@ -835,12 +832,6 @@ class FormulaInstaller skip_linkage = formula.bottle_specification.skip_relocation? keg.replace_placeholders_with_locations tab.changed_files, skip_linkage: skip_linkage - Pathname.glob("#{formula.bottle_prefix}/{etc,var}/**/*") do |path| - path.extend(InstallRenamed) - path.cp_path_sub(formula.bottle_prefix, HOMEBREW_PREFIX) - end - FileUtils.rm_rf formula.bottle_prefix - tab = Tab.for_keg(keg) CxxStdlib.check_compatibility( @@ -858,15 +849,15 @@ class FormulaInstaller tab.write end - def audit_check_output(output) + def problem_if_output(output) return unless output opoo output @show_summary_heading = true end def audit_installed - audit_check_output(check_env_path(formula.bin)) - audit_check_output(check_env_path(formula.sbin)) + problem_if_output(check_env_path(formula.bin)) + problem_if_output(check_env_path(formula.sbin)) super end @@ -899,6 +890,6 @@ class FormulaInstaller def puts_requirement_messages return unless @requirement_messages return if @requirement_messages.empty? - puts @requirement_messages + $stderr.puts @requirement_messages end end diff --git a/Library/Homebrew/formula_support.rb b/Library/Homebrew/formula_support.rb index b8476f5cc..4d963a55e 100644 --- a/Library/Homebrew/formula_support.rb +++ b/Library/Homebrew/formula_support.rb @@ -32,30 +32,30 @@ class KegOnlyReason return @explanation unless @explanation.empty? case @reason when :versioned_formula then <<-EOS.undent - This is an alternate version of another formula. + this is an alternate version of another formula EOS when :provided_by_macos, :provided_by_osx then <<-EOS.undent macOS already provides this software and installing another version in - parallel can cause all kinds of trouble. + parallel can cause all kinds of trouble EOS when :shadowed_by_macos, :shadowed_by_osx then <<-EOS.undent macOS provides similar software and installing this software in - parallel can cause all kinds of trouble. + parallel can cause all kinds of trouble EOS when :provided_pre_mountain_lion then <<-EOS.undent - macOS already provides this software in versions before Mountain Lion. + macOS already provides this software in versions before Mountain Lion EOS when :provided_pre_mavericks then <<-EOS.undent - macOS already provides this software in versions before Mavericks. + macOS already provides this software in versions before Mavericks EOS when :provided_pre_el_capitan then <<-EOS.undent - macOS already provides this software in versions before El Capitan. + macOS already provides this software in versions before El Capitan EOS when :provided_until_xcode43 then <<-EOS.undent - Xcode provides this software prior to version 4.3. + Xcode provides this software prior to version 4.3 EOS when :provided_until_xcode5 then <<-EOS.undent - Xcode provides this software prior to version 5. + Xcode provides this software prior to version 5 EOS else @reason diff --git a/Library/Homebrew/formula_versions.rb b/Library/Homebrew/formula_versions.rb index 28c2a3be8..70706a2f0 100644 --- a/Library/Homebrew/formula_versions.rb +++ b/Library/Homebrew/formula_versions.rb @@ -7,21 +7,22 @@ class FormulaVersions ErrorDuringExecution, LoadError, MethodDeprecatedError ].freeze + MAX_VERSIONS_DEPTH = 2 + attr_reader :name, :path, :repository, :entry_name - def initialize(formula, options = {}) + def initialize(formula) @name = formula.name @path = formula.path @repository = formula.tap.path @entry_name = @path.relative_path_from(repository).to_s - @max_depth = options[:max_depth] + @current_formula = formula end def rev_list(branch) repository.cd do - depth = 0 Utils.popen_read("git", "rev-list", "--abbrev-commit", "--remove-empty", branch, "--", entry_name) do |io| - yield io.readline.chomp until io.eof? || (@max_depth && (depth += 1) > @max_depth) + yield io.readline.chomp until io.eof? end end end @@ -49,11 +50,15 @@ class FormulaVersions def bottle_version_map(branch) map = Hash.new { |h, k| h[k] = [] } + + versions_seen = 0 rev_list(branch) do |rev| formula_at_revision(rev) do |f| bottle = f.bottle_specification map[f.pkg_version] << bottle.rebuild unless bottle.checksums.empty? + versions_seen = (map.keys + [f.pkg_version]).uniq.length end + return map if versions_seen > MAX_VERSIONS_DEPTH end map end @@ -63,26 +68,38 @@ class FormulaVersions return attributes_map if attributes.empty? attributes.each do |attribute| - attributes_map[attribute] ||= {} + attributes_map[attribute] ||= { + stable: {}, + devel: {}, + } end + stable_versions_seen = 0 rev_list(branch) do |rev| formula_at_revision(rev) do |f| attributes.each do |attribute| map = attributes_map[attribute] - if f.stable - map[:stable] ||= {} - map[:stable][f.stable.version] ||= [] - map[:stable][f.stable.version] << f.send(attribute) - end - next unless f.devel - map[:devel] ||= {} - map[:devel][f.devel.version] ||= [] - map[:devel][f.devel.version] << f.send(attribute) + set_attribute_map(map, f, attribute) + + stable_keys_length = (map[:stable].keys + [f.version]).uniq.length + stable_versions_seen = [stable_versions_seen, stable_keys_length].max end end + break if stable_versions_seen > MAX_VERSIONS_DEPTH end attributes_map end + + private + + def set_attribute_map(map, f, attribute) + if f.stable + map[:stable][f.stable.version] ||= [] + map[:stable][f.stable.version] << f.send(attribute) + end + return unless f.devel + map[:devel][f.devel.version] ||= [] + map[:devel][f.devel.version] << f.send(attribute) + end end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index 4a65f8704..446d5dc10 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -105,7 +105,18 @@ module Formulary # Loads formulae from bottles. class BottleLoader < FormulaLoader def initialize(bottle_name) - @bottle_filename = Pathname(bottle_name).realpath + case bottle_name + when %r{(https?|ftp|file)://} + # The name of the formula is found between the last slash and the last hyphen. + resource = Resource.new bottle_name[%r{([^/]+)-}, 1] { url bottle_name } + downloader = CurlBottleDownloadStrategy.new resource.name, resource + @bottle_filename = downloader.cached_location + cached = @bottle_filename.exist? + downloader.fetch + ohai "Pouring the cached bottle" if cached + else + @bottle_filename = Pathname(bottle_name).realpath + end name, full_name = Utils::Bottles.resolve_formula_names @bottle_filename super name, Formulary.path(full_name) end @@ -210,6 +221,10 @@ module Formulary def get_formula(spec, alias_path: nil) super + rescue FormulaUnreadableError => e + raise TapFormulaUnreadableError.new(tap, name, e.formula_error), "", e.backtrace + rescue FormulaClassUnavailableError => e + raise TapFormulaClassUnavailableError.new(tap, name, e.path, e.class_name, e.class_list), "", e.backtrace rescue FormulaUnavailableError => e raise TapFormulaUnavailableError.new(tap, name), "", e.backtrace end @@ -331,10 +346,10 @@ module Formulary def self.loader_for(ref, from: nil) case ref - when %r{(https?|ftp|file)://} - return FromUrlLoader.new(ref) when Pathname::BOTTLE_EXTNAME_RX return BottleLoader.new(ref) + when %r{(https?|ftp|file)://} + return FromUrlLoader.new(ref) when HOMEBREW_TAP_FORMULA_REGEX return TapLoader.new(ref, from: from) end @@ -402,7 +417,7 @@ module Formulary end def self.tap_paths(name, taps = Dir["#{HOMEBREW_LIBRARY}/Taps/*/*/"]) - name = name.downcase + name = name.to_s.downcase taps.map do |tap| Pathname.glob([ "#{tap}Formula/#{name}.rb", diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index 391f5b012..877253072 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -3,6 +3,7 @@ require "extend/fileutils" require "extend/pathname" require "extend/git_repository" require "extend/ARGV" +require "PATH" require "extend/string" require "os" require "utils" @@ -10,6 +11,7 @@ require "exceptions" require "set" require "rbconfig" require "official_taps" +require "pp" ARGV.extend(HomebrewArgvExtension) @@ -53,7 +55,8 @@ HOMEBREW_PULL_OR_COMMIT_URL_REGEX = %r[https://github\.com/([\w-]+)/([\w-]+)?/(? require "compat" unless ARGV.include?("--no-compat") || ENV["HOMEBREW_NO_COMPAT"] -ORIGINAL_PATHS = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p| +ENV["HOMEBREW_PATH"] ||= ENV["PATH"] +ORIGINAL_PATHS = PATH.new(ENV["HOMEBREW_PATH"]).map do |p| begin Pathname.new(p).expand_path rescue @@ -61,7 +64,6 @@ ORIGINAL_PATHS = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p| end end.compact.freeze -# TODO: remove this as soon as it's removed from commands.rb. HOMEBREW_INTERNAL_COMMAND_ALIASES = { "ls" => "list", "homepage" => "home", diff --git a/Library/Homebrew/gpg.rb b/Library/Homebrew/gpg.rb index 777542055..cb9e367df 100644 --- a/Library/Homebrew/gpg.rb +++ b/Library/Homebrew/gpg.rb @@ -6,6 +6,7 @@ class Gpg gpg_short_version = Utils.popen_read(gpg, "--version")[/\d\.\d/, 0] 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") end @@ -25,6 +26,10 @@ class Gpg File.executable?(GPG_EXECUTABLE.to_s) end + def self.version + @version if available? + end + def self.create_test_key(path) odie "No GPG present to test against!" unless available? diff --git a/Library/Homebrew/hardware.rb b/Library/Homebrew/hardware.rb index fe07aee9d..997598def 100644 --- a/Library/Homebrew/hardware.rb +++ b/Library/Homebrew/hardware.rb @@ -80,6 +80,18 @@ module Hardware def feature?(name) features.include?(name) end + + def can_run?(arch) + if is_32_bit? + arch_32_bit == arch + elsif intel? + [:i386, :x86_64].include? arch + elsif ppc? + [:ppc, :ppc64].include? arch + else + false + end + end end end diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index d4b9c5d77..8733def27 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -468,7 +468,10 @@ class Keg end def aliases - Formulary.from_rack(rack).aliases + formula = Formulary.from_rack(rack) + aliases = formula.aliases + return aliases if formula.stable? + aliases.reject { |a| a.include?("@") } rescue FormulaUnavailableError [] end diff --git a/Library/Homebrew/language/python.rb b/Library/Homebrew/language/python.rb index 23c5246ba..0f8e3a4e6 100644 --- a/Library/Homebrew/language/python.rb +++ b/Library/Homebrew/language/python.rb @@ -53,29 +53,6 @@ module Language quiet_system python, "-c", script end - # deprecated; use system "python", *setup_install_args(prefix) instead - def self.setup_install(python, prefix, *args) - opoo <<-EOS.undent - Language::Python.setup_install is deprecated. - If you are a formula author, please use - system "python", *Language::Python.setup_install_args(prefix) - instead. - EOS - - # force-import setuptools, which monkey-patches distutils, to make - # sure that we always call a setuptools setup.py. trick borrowed from pip: - # https://github.com/pypa/pip/blob/043af83/pip/req/req_install.py#L743-L780 - shim = <<-EOS.undent - import setuptools, tokenize - __file__ = 'setup.py' - exec(compile(getattr(tokenize, 'open', open)(__file__).read() - .replace('\\r\\n', '\\n'), __file__, 'exec')) - EOS - args += %w[--single-version-externally-managed --record=installed.txt] - args << "--prefix=#{prefix}" - system python, "-c", shim, "install", *args - end - def self.setup_install_args(prefix) shim = <<-EOS.undent import setuptools, tokenize diff --git a/Library/Homebrew/manpages/brew.1.md.erb b/Library/Homebrew/manpages/brew.1.md.erb index 5b0228e3f..29f8d0bec 100644 --- a/Library/Homebrew/manpages/brew.1.md.erb +++ b/Library/Homebrew/manpages/brew.1.md.erb @@ -98,8 +98,15 @@ can take several different forms: The formula file will be cached for later use. ## ENVIRONMENT + * `HOMEBREW_ARTIFACT_DOMAIN`: + If set, instructs Homebrew to use the given URL as a download mirror for bottles and binaries. + + * `HOMEBREW_AUTO_UPDATE_SECS`: + If set, Homebrew will only check for autoupdates once per this seconds interval. + + *Default:* `60`. - * `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`: + * `HOMEBREW_AWS_ACCESS_KEY_ID`, `HOMEBREW_AWS_SECRET_ACCESS_KEY`: When using the `S3` download strategy, Homebrew will look in these variables for access credentials (see <https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-environment> @@ -107,30 +114,9 @@ can take several different forms: the `S3` download strategy will download with a public (unsigned) URL. - * `BROWSER`: - If set, and `HOMEBREW_BROWSER` is not, use `BROWSER` as the web browser - when opening project homepages. - - * `EDITOR`: - If set, and `HOMEBREW_EDITOR` and `VISUAL` are not, use `EDITOR` as the text editor. - - * `GIT`: - When using Git, Homebrew will use `GIT` if set, - a Homebrew-built Git if installed, or the system-provided binary. - - Set this to force Homebrew to use a particular git binary. - * `HOMEBREW_BOTTLE_DOMAIN`: If set, instructs Homebrew to use the given URL as a download mirror for bottles. - * `HOMEBREW_ARTIFACT_DOMAIN`: - If set, instructs Homebrew to use the given URL as a download mirror for bottles and binaries. - - * `HOMEBREW_AUTO_UPDATE_SECS`: - If set, Homebrew will only check for autoupdates once per this seconds interval. - - *Default:* `60`. - * `HOMEBREW_BROWSER`: If set, uses this setting as the browser when opening project homepages, instead of the OS default browser. @@ -178,6 +164,12 @@ can take several different forms: If set, Homebrew will always use its vendored, relocatable Ruby 2.0 version even if the system version of Ruby is >=2.0. + * `HOMEBREW_GIT`: + When using Git, Homebrew will use `GIT` if set, + a Homebrew-built Git if installed, or the system-provided binary. + + Set this to force Homebrew to use a particular git binary. + * `HOMEBREW_GITHUB_API_TOKEN`: A personal access token for the GitHub API, which you can create at <https://github.com/settings/tokens>. If set, GitHub will allow you a @@ -243,9 +235,6 @@ can take several different forms: * `HOMEBREW_VERBOSE`: If set, Homebrew always assumes `--verbose` when running commands. - * `VISUAL`: - If set, and `HOMEBREW_EDITOR` is not, use `VISUAL` as the text editor. - ## USING HOMEBREW BEHIND A PROXY Homebrew uses several commands for downloading files (e.g. `curl`, `git`, `svn`). diff --git a/Library/Homebrew/missing_formula.rb b/Library/Homebrew/missing_formula.rb index 5b903b899..eac3d7725 100644 --- a/Library/Homebrew/missing_formula.rb +++ b/Library/Homebrew/missing_formula.rb @@ -122,7 +122,7 @@ module Homebrew path = Formulary.path name return if File.exist? path tap = Tap.from_path(path) - return unless File.exist? tap.path + return if tap.nil? || !File.exist?(tap.path) relative_path = path.relative_path_from tap.path tap.path.cd do diff --git a/Library/Homebrew/official_taps.rb b/Library/Homebrew/official_taps.rb index a7bc4a1d6..dcb65d9f8 100644 --- a/Library/Homebrew/official_taps.rb +++ b/Library/Homebrew/official_taps.rb @@ -5,8 +5,29 @@ OFFICIAL_TAPS = %w[ science ].freeze +OFFICIAL_CASK_TAPS = %w[ + cask + versions +].freeze + OFFICIAL_CMD_TAPS = { "homebrew/bundle" => ["bundle"], "homebrew/test-bot" => ["test-bot"], "homebrew/services" => ["services"], }.freeze + +DEPRECATED_OFFICIAL_TAPS = %w[ + binary + completions + devel-only + dupes + emacs + fuse + games + gui + head-only + python + tex + versions + x11 +].freeze diff --git a/Library/Homebrew/os/mac.rb b/Library/Homebrew/os/mac.rb index 518936647..dba2480ef 100644 --- a/Library/Homebrew/os/mac.rb +++ b/Library/Homebrew/os/mac.rb @@ -205,6 +205,8 @@ module OS "8.2" => { clang: "8.0", clang_build: 800 }, "8.2.1" => { clang: "8.0", clang_build: 800 }, "8.3" => { clang: "8.1", clang_build: 802 }, + "8.3.1" => { clang: "8.1", clang_build: 802 }, + "8.3.2" => { clang: "8.1", clang_build: 802 }, }.freeze def compilers_standard? diff --git a/Library/Homebrew/os/mac/xcode.rb b/Library/Homebrew/os/mac/xcode.rb index 70fe3c14d..9ed95d3f2 100644 --- a/Library/Homebrew/os/mac/xcode.rb +++ b/Library/Homebrew/os/mac/xcode.rb @@ -17,12 +17,12 @@ module OS when "10.9" then "6.2" when "10.10" then "7.2.1" when "10.11" then "8.2.1" - when "10.12" then "8.3" + when "10.12" then "8.3.2" else raise "macOS '#{MacOS.version}' is invalid" unless OS::Mac.prerelease? # Default to newest known version of Xcode for unreleased macOS versions. - "8.3" + "8.3.2" end end @@ -128,11 +128,10 @@ module OS end end - # The remaining logic provides a fake Xcode version for CLT-only - # systems. This behavior only exists because Homebrew used to assume - # Xcode.version would always be non-nil. This is deprecated, and will - # be removed in a future version. To remain compatible, guard usage of - # Xcode.version with an Xcode.installed? check. + # The remaining logic provides a fake Xcode version based on the + # installed CLT version. This is useful as they are packaged + # simultaneously so workarounds need to apply to both based on their + # comparable version. case (DevelopmentTools.clang_version.to_f * 10).to_i when 0 then "dunno" when 1..14 then "3.2.2" @@ -214,7 +213,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.12" then "802.0.38" + when "10.12" then "802.0.42" when "10.11" then "800.0.42.1" when "10.10" then "700.1.81" when "10.9" then "600.0.57" diff --git a/Library/Homebrew/os/mac/xquartz.rb b/Library/Homebrew/os/mac/xquartz.rb index b82772faf..2fdf0abea 100644 --- a/Library/Homebrew/os/mac/xquartz.rb +++ b/Library/Homebrew/os/mac/xquartz.rb @@ -5,7 +5,6 @@ module OS module XQuartz module_function - # TODO: confirm this path when you have internet DEFAULT_BUNDLE_PATH = Pathname.new("Applications/Utilities/XQuartz.app").freeze FORGE_BUNDLE_ID = "org.macosforge.xquartz.X11".freeze APPLE_BUNDLE_ID = "org.x.X11".freeze diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index 3dd7075ec..ddac58444 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -24,14 +24,21 @@ module Readall !failed end - def valid_aliases?(alias_dirs) + def valid_aliases?(alias_dir, formula_dir) + return true unless alias_dir.directory? + failed = false - alias_dirs.each do |alias_dir| - next unless alias_dir.directory? - alias_dir.children.each do |f| - next unless f.symlink? - next if f.file? - onoe "Broken alias: #{f}" + alias_dir.each_child do |f| + if !f.symlink? + onoe "Non-symlink alias: #{f}" + failed = true + elsif !f.file? + onoe "Non-file alias: #{f}" + failed = true + end + + if (formula_dir/"#{f.basename}.rb").exist? + onoe "Formula duplicating alias: #{f}" failed = true end end @@ -57,7 +64,7 @@ module Readall def valid_tap?(tap, options = {}) failed = false if options[:aliases] - valid_aliases = valid_aliases?([tap.alias_dir]) + valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir) failed = true unless valid_aliases end valid_formulae = valid_formulae?(tap.formula_files) diff --git a/Library/Homebrew/requirement.rb b/Library/Homebrew/requirement.rb index bd8cf20c8..6c20e7917 100644 --- a/Library/Homebrew/requirement.rb +++ b/Library/Homebrew/requirement.rb @@ -96,7 +96,7 @@ class Requirement # PATH. parent = satisfied_result_parent return unless parent - return if ENV["PATH"].split(File::PATH_SEPARATOR).include?(parent.to_s) + return if PATH.new(ENV["PATH"]).include?(parent.to_s) ENV.append_path("PATH", parent) end @@ -151,21 +151,19 @@ class Requirement end def which(cmd) - super(cmd, ORIGINAL_PATHS.join(File::PATH_SEPARATOR)) + super(cmd, PATH.new(ORIGINAL_PATHS)) end def which_all(cmd) - super(cmd, ORIGINAL_PATHS.join(File::PATH_SEPARATOR)) + super(cmd, PATH.new(ORIGINAL_PATHS)) end class << self include BuildEnvironmentDSL - attr_reader :env_proc + attr_reader :env_proc, :build attr_rw :fatal, :default_formula attr_rw :cask, :download - # build is deprecated, use `depends_on <requirement> => :build` instead - attr_rw :build def satisfy(options = {}, &block) @satisfied ||= Requirement::Satisfier.new(options, &block) diff --git a/Library/Homebrew/requirements/osxfuse_requirement.rb b/Library/Homebrew/requirements/osxfuse_requirement.rb index d5a341567..9a07209d4 100644 --- a/Library/Homebrew/requirements/osxfuse_requirement.rb +++ b/Library/Homebrew/requirements/osxfuse_requirement.rb @@ -2,34 +2,10 @@ require "requirement" class OsxfuseRequirement < Requirement fatal true - cask "osxfuse" - download "https://osxfuse.github.io/" - - satisfy(build_env: false) { self.class.binary_osxfuse_installed? } - - def self.binary_osxfuse_installed? - File.exist?("/usr/local/include/osxfuse/fuse.h") && - !File.symlink?("/usr/local/include/osxfuse") - end - - env do - ENV.append_path "PKG_CONFIG_PATH", HOMEBREW_LIBRARY/"Homebrew/os/mac/pkgconfig/fuse" - - unless HOMEBREW_PREFIX.to_s == "/usr/local" - ENV.append_path "HOMEBREW_LIBRARY_PATHS", "/usr/local/lib" - ENV.append_path "HOMEBREW_INCLUDE_PATHS", "/usr/local/include/osxfuse" - end - end end class NonBinaryOsxfuseRequirement < Requirement - fatal true - satisfy(build_env: false) { HOMEBREW_PREFIX.to_s != "/usr/local" || !OsxfuseRequirement.binary_osxfuse_installed? } - - def message - <<-EOS.undent - osxfuse is already installed from the binary distribution and - conflicts with this formula. - EOS - end + fatal false end + +require "extend/os/requirements/osxfuse_requirement" diff --git a/Library/Homebrew/rubocops.rb b/Library/Homebrew/rubocops.rb index 3625f2004..587bf4b20 100644 --- a/Library/Homebrew/rubocops.rb +++ b/Library/Homebrew/rubocops.rb @@ -1,2 +1,5 @@ require_relative "./rubocops/bottle_block_cop" require_relative "./rubocops/formula_desc_cop" +require_relative "./rubocops/components_order_cop" +require_relative "./rubocops/components_redundancy_cop" +require_relative "./rubocops/homepage_cop" diff --git a/Library/Homebrew/rubocops/bottle_block_cop.rb b/Library/Homebrew/rubocops/bottle_block_cop.rb index 4d7a94461..f0c7d59bb 100644 --- a/Library/Homebrew/rubocops/bottle_block_cop.rb +++ b/Library/Homebrew/rubocops/bottle_block_cop.rb @@ -2,18 +2,18 @@ require_relative "./extend/formula_cop" module RuboCop module Cop - module Homebrew + module FormulaAuditStrict # This cop audits `bottle` block in Formulae # # - `rebuild` should be used instead of `revision` in `bottle` block - class CorrectBottleBlock < FormulaCop + class BottleBlock < FormulaCop MSG = "Use rebuild instead of revision in bottle block".freeze def audit_formula(_node, _class_node, _parent_class_node, formula_class_body_node) bottle = find_block(formula_class_body_node, :bottle) return if bottle.nil? || block_size(bottle).zero? - problem "Use rebuild instead of revision in bottle block" if method_called?(bottle, :revision) + problem "Use rebuild instead of revision in bottle block" if method_called_in_block?(bottle, :revision) end private diff --git a/Library/Homebrew/rubocops/components_order_cop.rb b/Library/Homebrew/rubocops/components_order_cop.rb new file mode 100644 index 000000000..dfddbe145 --- /dev/null +++ b/Library/Homebrew/rubocops/components_order_cop.rb @@ -0,0 +1,114 @@ +require_relative "./extend/formula_cop" + +module RuboCop + module Cop + module FormulaAuditStrict + # This cop checks for correct order of components in a Formula + # + # - component_precedence_list has component hierarchy in a nested list + # where each sub array contains components' details which are at same precedence level + class ComponentsOrder < FormulaCop + def audit_formula(_node, _class_node, _parent_class_node, formula_class_body_node) + component_precedence_list = [ + [{ name: :include, type: :method_call }], + [{ name: :desc, type: :method_call }], + [{ name: :homepage, type: :method_call }], + [{ name: :url, type: :method_call }], + [{ name: :mirror, type: :method_call }], + [{ name: :version, type: :method_call }], + [{ name: :sha256, type: :method_call }], + [{ name: :revision, type: :method_call }], + [{ name: :version_scheme, type: :method_call }], + [{ name: :head, type: :method_call }], + [{ name: :stable, type: :block_call }], + [{ name: :bottle, type: :block_call }], + [{ name: :devel, type: :block_call }], + [{ name: :head, type: :block_call }], + [{ name: :bottle, type: :method_call }], + [{ name: :keg_only, type: :method_call }], + [{ name: :option, type: :method_call }], + [{ name: :depends_on, type: :method_call }], + [{ name: :conflicts_with, type: :method_call }], + [{ name: :go_resource, type: :block_call }, { name: :resource, type: :block_call }], + [{ name: :install, type: :method_definition }], + [{ name: :caveats, type: :method_definition }], + [{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }], + [{ name: :test, type: :block_call }], + ] + + @present_components = component_precedence_list.map do |components| + relevant_components = [] + components.each do |component| + case component[:type] + when :method_call + relevant_components += find_method_calls_by_name(formula_class_body_node, component[:name]).to_a + when :block_call + relevant_components += find_blocks(formula_class_body_node, component[:name]).to_a + when :method_definition + relevant_components << find_method_def(formula_class_body_node, component[:name]) + end + end + relevant_components.delete_if(&:nil?) + end + + # Check if each present_components is above rest of the present_components + @present_components.take(@present_components.size-1).each_with_index do |preceding_component, p_idx| + next if preceding_component.empty? + @present_components.drop(p_idx+1).each do |succeeding_component| + next if succeeding_component.empty? + @offensive_nodes = check_precedence(preceding_component, succeeding_component) + component_problem @offensive_nodes[0], @offensive_nodes[1] if @offensive_nodes + end + end + end + + private + + # Method to format message for reporting component precedence violations + def component_problem(c1, c2) + problem "`#{format_component(c1)}` (line #{line_number(c1)}) should be put before `#{format_component(c2)}` (line #{line_number(c2)})" + end + + # autocorrect method gets called just after component_problem method call + def autocorrect(_node) + succeeding_node = @offensive_nodes[0] + preceding_node = @offensive_nodes[1] + lambda do |corrector| + reorder_components(corrector, succeeding_node, preceding_node) + end + end + + # Reorder two nodes in the source, using the corrector instance in autocorrect method + # Components of same type are grouped together when rewriting the source + # Linebreaks are introduced if components are of two different methods/blocks/multilines + def reorder_components(corrector, node1, node2) + # order_idx : node1's index in component_precedence_list + # curr_p_idx: node1's index in preceding_comp_arr + # preceding_comp_arr: array containing components of same type + order_idx, curr_p_idx, preceding_comp_arr = get_state(node1) + + # curr_p_idx > 0 means node1 needs to be grouped with its own kind + if curr_p_idx>0 + node2 = preceding_comp_arr[curr_p_idx-1] + indentation = " " * (start_column(node2) - line_start_column(node2)) + line_breaks = node2.multiline? ? "\n\n" : "\n" + corrector.insert_after(node2.source_range, line_breaks+indentation+node1.source) + else + indentation = " " * (start_column(node2) - line_start_column(node2)) + # No line breaks upto version_scheme, order_idx == 8 + line_breaks = order_idx>8 ? "\n\n" : "\n" + corrector.insert_before(node2.source_range, node1.source+line_breaks+indentation) + end + corrector.remove(range_with_surrounding_space(node1.source_range, :left)) + end + + # Returns precedence index and component's index to properly reorder and group during autocorrect + def get_state(node1) + @present_components.each_with_index do |comp, idx| + return [idx, comp.index(node1), comp] if comp.member?(node1) + end + end + end + end + end +end diff --git a/Library/Homebrew/rubocops/components_redundancy_cop.rb b/Library/Homebrew/rubocops/components_redundancy_cop.rb new file mode 100644 index 000000000..52dba4718 --- /dev/null +++ b/Library/Homebrew/rubocops/components_redundancy_cop.rb @@ -0,0 +1,33 @@ +require_relative "./extend/formula_cop" + +module RuboCop + module Cop + module FormulaAuditStrict + # This cop checks if redundant components are present and other component errors + # + # - `url|checksum|mirror` should be inside `stable` block + # - `head` and `head do` should not be simultaneously present + # - `bottle :unneeded/:disable` and `bottle do` should not be simultaneously present + + class ComponentsRedundancy < FormulaCop + HEAD_MSG = "`head` and `head do` should not be simultaneously present".freeze + BOTTLE_MSG = "`bottle :modifier` and `bottle do` should not be simultaneously present".freeze + + def audit_formula(_node, _class_node, _parent_class_node, formula_class_body_node) + stable_block = find_block(formula_class_body_node, :stable) + if stable_block + [:url, :sha256, :mirror].each do |method_name| + problem "`#{method_name}` should be put inside `stable` block" if method_called?(formula_class_body_node, method_name) + end + end + + problem HEAD_MSG if method_called?(formula_class_body_node, :head) && + find_block(formula_class_body_node, :head) + + problem BOTTLE_MSG if method_called?(formula_class_body_node, :bottle) && + find_block(formula_class_body_node, :bottle) + end + end + end + end +end diff --git a/Library/Homebrew/rubocops/extend/formula_cop.rb b/Library/Homebrew/rubocops/extend/formula_cop.rb index 49108bd48..3f70086b3 100644 --- a/Library/Homebrew/rubocops/extend/formula_cop.rb +++ b/Library/Homebrew/rubocops/extend/formula_cop.rb @@ -1,143 +1,212 @@ module RuboCop module Cop - module Homebrew - class FormulaCop < Cop - @registry = Cop.registry - - def on_class(node) - # This method is called by RuboCop and is the main entry point - file_path = processed_source.buffer.name - return unless file_path_allowed?(file_path) - class_node, parent_class_node, body = *node - return unless formula_class?(parent_class_node) - return unless respond_to?(:audit_formula) - @formula_name = class_name(class_node) - audit_formula(node, class_node, parent_class_node, body) - end + class FormulaCop < Cop + @registry = Cop.registry + + # This method is called by RuboCop and is the main entry point + def on_class(node) + file_path = processed_source.buffer.name + return unless file_path_allowed?(file_path) + class_node, parent_class_node, body = *node + return unless formula_class?(parent_class_node) + return unless respond_to?(:audit_formula) + @formula_name = class_name(class_node) + audit_formula(node, class_node, parent_class_node, body) + end - def regex_match_group(node, pattern) - # Checks for regex match of pattern in the node and - # Sets the appropriate instance variables to report the match - string_repr = string_content(node) - match_object = string_repr.match(pattern) - return unless match_object - node_begin_pos = start_column(node) - line_begin_pos = line_start_column(node) - @column = node_begin_pos + match_object.begin(0) - line_begin_pos + 1 - @length = match_object.to_s.length - @line_no = line_number(node) - @source_buf = source_buffer(node) - @offense_source_range = source_range(@source_buf, @line_no, @column, @length) - @offensive_node = node - match_object - end + # Checks for regex match of pattern in the node and + # Sets the appropriate instance variables to report the match + def regex_match_group(node, pattern) + string_repr = string_content(node) + match_object = string_repr.match(pattern) + return unless match_object + node_begin_pos = start_column(node) + line_begin_pos = line_start_column(node) + @column = node_begin_pos + match_object.begin(0) - line_begin_pos + 1 + @length = match_object.to_s.length + @line_no = line_number(node) + @source_buf = source_buffer(node) + @offense_source_range = source_range(@source_buf, @line_no, @column, @length) + @offensive_node = node + match_object + end - def find_node_method_by_name(node, method_name) - # Returns method_node matching method_name - return if node.nil? - node.each_child_node(:send) do |method_node| - next unless method_node.method_name == method_name - @offensive_node = method_node - @offense_source_range = method_node.source_range - return method_node - end - # If not found then, parent node becomes the offensive node - @offensive_node = node.parent - @offense_source_range = node.parent.source_range - nil + # Returns method_node matching method_name + def find_node_method_by_name(node, method_name) + return if node.nil? + node.each_child_node(:send) do |method_node| + next unless method_node.method_name == method_name + @offensive_node = method_node + @offense_source_range = method_node.source_range + return method_node + end + # If not found then, parent node becomes the offensive node + @offensive_node = node.parent + @offense_source_range = node.parent.source_range + nil + end + + # Returns an array of method call nodes matching method_name inside node + def find_method_calls_by_name(node, method_name) + return if node.nil? + node.each_child_node(:send).select { |method_node| method_name == method_node.method_name } + end + + # Returns a block named block_name inside node + def find_block(node, block_name) + return if node.nil? + node.each_child_node(:block) do |block_node| + next if block_node.method_name != block_name + @offensive_node = block_node + @offense_source_range = block_node.source_range + return block_node + end + # If not found then, parent node becomes the offensive node + @offensive_node = node.parent + @offense_source_range = node.parent.source_range + nil + end + + # Returns an array of block nodes named block_name inside node + def find_blocks(node, block_name) + return if node.nil? + node.each_child_node(:block).select { |block_node| block_name == block_node.method_name } + end + + # Returns a method definition node with method_name + def find_method_def(node, method_name) + return if node.nil? + node.each_child_node(:def) do |def_node| + def_method_name = method_name(def_node) + next unless method_name == def_method_name + @offensive_node = def_node + @offense_source_range = def_node.source_range + return def_node + end + # If not found then, parent node becomes the offensive node + @offensive_node = node.parent + @offense_source_range = node.parent.source_range + nil + end + + # Check if a method is called inside a block + def method_called_in_block?(node, method_name) + block_body = node.children[2] + block_body.each_child_node(:send) do |call_node| + next unless call_node.method_name == method_name + @offensive_node = call_node + @offense_source_range = call_node.source_range + return true end + false + end - def find_block(node, block_name) - # Returns a block named block_name inside node - return if node.nil? - node.each_child_node(:block) do |block_node| - next if block_node.method_name != block_name - @offensive_node = block_node - @offense_source_range = block_node.source_range - return block_node - end - # If not found then, parent node becomes the offensive node - @offensive_node = node.parent - @offense_source_range = node.parent.source_range - nil + # Check if method_name is called among the direct children nodes in the given node + def method_called?(node, method_name) + node.each_child_node(:send) do |call_node| + next unless call_node.method_name == method_name + @offensive_node = call_node + @offense_source_range = call_node.source_range + return true end + false + end - def method_called?(node, method_name) - # Check if a method is called inside a block - block_body = node.children[2] - block_body.each_child_node(:send) do |call_node| - next unless call_node.method_name == method_name - @offensive_node = call_node - @offense_source_range = call_node.source_range - return true + # Checks for precedence, returns the first pair of precedence violating nodes + def check_precedence(first_nodes, next_nodes) + next_nodes.each do |each_next_node| + first_nodes.each do |each_first_node| + if component_precedes?(each_first_node, each_next_node) + return [each_first_node, each_next_node] + end end - false end + nil + end - def parameters(method_node) - # Returns the array of arguments of the method_node - return unless method_node.send_type? - method_node.method_args - end + # If first node does not precede next_node, sets appropriate instance variables for reporting + def component_precedes?(first_node, next_node) + return false if line_number(first_node) < line_number(next_node) + @offense_source_range = first_node.source_range + @offensive_node = first_node + true + end - def line_start_column(node) - # Returns the begin position of the node's line in source code - node.source_range.source_buffer.line_range(node.loc.line).begin_pos - end + # Returns the array of arguments of the method_node + def parameters(method_node) + return unless method_node.send_type? + method_node.method_args + end - def start_column(node) - # Returns the begin position of the node in source code - node.source_range.begin_pos - end + # Returns the begin position of the node's line in source code + def line_start_column(node) + node.source_range.source_buffer.line_range(node.loc.line).begin_pos + end - def line_number(node) - # Returns the line number of the node - node.loc.line - end + # Returns the begin position of the node in source code + def start_column(node) + node.source_range.begin_pos + end - def class_name(node) - # Returns the class node's name, nil if not a class node - @offensive_node = node - @offense_source_range = node.source_range - node.const_name - end + # Returns the line number of the node + def line_number(node) + node.loc.line + end - def size(node) - # Returns the node size in the source code - node.source_range.size - end + # Returns the class node's name, nil if not a class node + def class_name(node) + @offensive_node = node + @offense_source_range = node.source_range + node.const_name + end - def block_size(block) - # Returns the block length of the block node - block_length(block) - end + # Returns the method name for a def node + def method_name(node) + node.children[0] if node.def_type? + end - def source_buffer(node) - # Source buffer is required as an argument to report style violations - node.source_range.source_buffer - end + # Returns the node size in the source code + def size(node) + node.source_range.size + end - def string_content(node) - # Returns the string representation if node is of type str - node.str_content if node.type == :str - end + # Returns the block length of the block node + def block_size(block) + block_length(block) + end - def problem(msg) - add_offense(@offensive_node, @offense_source_range, msg) - end + # Source buffer is required as an argument to report style violations + def source_buffer(node) + node.source_range.source_buffer + end - private + # Returns the string representation if node is of type str(plain) or dstr(interpolated) + def string_content(node) + return node.str_content if node.type == :str + node.each_child_node(:str).map(&:str_content).join("") if node.type == :dstr + end - def formula_class?(parent_class_node) - parent_class_node && parent_class_node.const_name == "Formula" - end + # Returns printable component name + def format_component(component_node) + return component_node.method_name if component_node.send_type? || component_node.block_type? + method_name(component_node) if component_node.def_type? + end - def file_path_allowed?(file_path) - paths_to_exclude = [%r{/Library/Homebrew/compat/}, - %r{/Library/Homebrew/test/}] - return true if file_path.nil? # file_path is nil when source is directly passed to the cop eg., in specs - file_path !~ Regexp.union(paths_to_exclude) - end + def problem(msg) + add_offense(@offensive_node, @offense_source_range, msg) + end + + private + + def formula_class?(parent_class_node) + parent_class_node && parent_class_node.const_name == "Formula" + end + + def file_path_allowed?(file_path) + paths_to_exclude = [%r{/Library/Homebrew/compat/}, + %r{/Library/Homebrew/test/}] + return true if file_path.nil? # file_path is nil when source is directly passed to the cop eg., in specs + file_path !~ Regexp.union(paths_to_exclude) end end end diff --git a/Library/Homebrew/rubocops/formula_desc_cop.rb b/Library/Homebrew/rubocops/formula_desc_cop.rb index 7d69a48e7..1fbf1ddbf 100644 --- a/Library/Homebrew/rubocops/formula_desc_cop.rb +++ b/Library/Homebrew/rubocops/formula_desc_cop.rb @@ -3,7 +3,7 @@ require_relative "../extend/string" module RuboCop module Cop - module Homebrew + module FormulaAuditStrict # This cop audits `desc` in Formulae # # - Checks for existence of `desc` @@ -11,7 +11,7 @@ module RuboCop # - Checks if `desc` begins with an article # - Checks for correct usage of `command-line` in `desc` # - Checks if `desc` contains the formula name - class FormulaDesc < FormulaCop + class Desc < FormulaCop def audit_formula(_node, _class_node, _parent_class_node, body) desc_call = find_node_method_by_name(body, :desc) diff --git a/Library/Homebrew/rubocops/homepage_cop.rb b/Library/Homebrew/rubocops/homepage_cop.rb new file mode 100644 index 000000000..a40c7b049 --- /dev/null +++ b/Library/Homebrew/rubocops/homepage_cop.rb @@ -0,0 +1,82 @@ +require_relative "./extend/formula_cop" + +module RuboCop + module Cop + module FormulaAudit + # This cop audits `homepage` url in Formulae + class Homepage < FormulaCop + def audit_formula(_node, _class_node, _parent_class_node, formula_class_body_node) + homepage_node = find_node_method_by_name(formula_class_body_node, :homepage) + homepage = if homepage_node + string_content(parameters(homepage_node).first) + else + "" + end + + problem "Formula should have a homepage." if homepage_node.nil? || homepage.empty? + + unless homepage =~ %r{^https?://} + problem "The homepage should start with http or https (URL is #{homepage})." + end + + case homepage + # Check for http:// GitHub homepage urls, https:// is preferred. + # Note: only check homepages that are repo pages, not *.github.com hosts + when %r{^http://github.com/} + problem "Please use https:// for #{homepage}" + + # Savannah has full SSL/TLS support but no auto-redirect. + # Doesn't apply to the download URLs, only the homepage. + when %r{^http://savannah.nongnu.org/} + problem "Please use https:// for #{homepage}" + + # Freedesktop is complicated to handle - It has SSL/TLS, but only on certain subdomains. + # To enable https Freedesktop change the URL from http://project.freedesktop.org/wiki to + # https://wiki.freedesktop.org/project_name. + # "Software" is redirected to https://wiki.freedesktop.org/www/Software/project_name + when %r{^http://((?:www|nice|libopenraw|liboil|telepathy|xorg)\.)?freedesktop\.org/(?:wiki/)?} + if homepage =~ /Software/ + problem "#{homepage} should be styled `https://wiki.freedesktop.org/www/Software/project_name`" + else + problem "#{homepage} should be styled `https://wiki.freedesktop.org/project_name`" + end + + # Google Code homepages should end in a slash + when %r{^https?://code\.google\.com/p/[^/]+[^/]$} + problem "#{homepage} should end with a slash" + + # People will run into mixed content sometimes, but we should enforce and then add + # exemptions as they are discovered. Treat mixed content on homepages as a bug. + # Justify each exemptions with a code comment so we can keep track here. + + when %r{^http://[^/]*\.github\.io/}, + %r{^http://[^/]*\.sourceforge\.io/} + problem "Please use https:// for #{homepage}" + + when %r{^http://([^/]*)\.(sf|sourceforge)\.net(/|$)} + problem "#{homepage} should be `https://#{$1}.sourceforge.io/`" + + # There's an auto-redirect here, but this mistake is incredibly common too. + # Only applies to the homepage and subdomains for now, not the FTP URLs. + when %r{^http://((?:build|cloud|developer|download|extensions|git|glade|help|library|live|nagios|news|people|projects|rt|static|wiki|www)\.)?gnome\.org} + problem "Please use https:// for #{homepage}" + + # Compact the above into this list as we're able to remove detailed notations, etc over time. + when %r{^http://[^/]*\.apache\.org}, + %r{^http://packages\.debian\.org}, + %r{^http://wiki\.freedesktop\.org/}, + %r{^http://((?:www)\.)?gnupg\.org/}, + %r{^http://ietf\.org}, + %r{^http://[^/.]+\.ietf\.org}, + %r{^http://[^/.]+\.tools\.ietf\.org}, + %r{^http://www\.gnu\.org/}, + %r{^http://code\.google\.com/}, + %r{^http://bitbucket\.org/}, + %r{^http://(?:[^/]*\.)?archive\.org} + problem "Please use https:// for #{homepage}" + end + end + end + end + end +end diff --git a/Library/Homebrew/sandbox.rb b/Library/Homebrew/sandbox.rb index 9597dafa8..b16bbde1a 100644 --- a/Library/Homebrew/sandbox.rb +++ b/Library/Homebrew/sandbox.rb @@ -5,10 +5,6 @@ class Sandbox SANDBOX_EXEC = "/usr/bin/sandbox-exec".freeze SANDBOXED_TAPS = %w[ homebrew/core - homebrew/dupes - homebrew/fuse - homebrew/devel-only - homebrew/tex ].freeze def self.available? diff --git a/Library/Homebrew/shims/scm/git b/Library/Homebrew/shims/scm/git index 82bb47c25..bfb779c25 100755 --- a/Library/Homebrew/shims/scm/git +++ b/Library/Homebrew/shims/scm/git @@ -86,7 +86,6 @@ fi case "$(lowercase "$SCM_FILE")" in git) [[ -n "$HOMEBREW_GIT" ]] && safe_exec "$(which "$HOMEBREW_GIT")" "$@" - [[ -n "$GIT" ]] && safe_exec "$(which "$GIT")" "$@" ;; svn) [[ -n "$HOMEBREW_SVN" ]] && safe_exec "$(which "$HOMEBREW_SVN")" "$@" diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index fb07f6c55..be851ca16 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -8,6 +8,7 @@ require "dependency_collector" require "utils/bottles" require "patch" require "compilers" +require "os/mac/version" class SoftwareSpec extend Forwardable @@ -117,8 +118,7 @@ class SoftwareSpec def option(name, description = "") opt = PREDEFINED_OPTIONS.fetch(name) do if name.is_a?(Symbol) - opoo "Passing arbitrary symbols to `option` is deprecated: #{name.inspect}" - puts "Symbols are reserved for future use, please pass a string instead" + odeprecated "passing arbitrary symbols (i.e. #{name.inspect}) to `option`" name = name.to_s end unless name.is_a?(String) @@ -172,7 +172,6 @@ class SoftwareSpec end def fails_with(compiler, &block) - # TODO: deprecate this in future. # odeprecated "fails_with :llvm" if compiler == :llvm compiler_failures << CompilerFailure.create(compiler, &block) end @@ -340,8 +339,8 @@ class BottleSpecification tags = collector.keys.sort_by do |tag| # Sort non-MacOS tags below MacOS tags. begin - MacOS::Version.from_symbol tag - rescue + OS::Mac::Version.from_symbol tag + rescue ArgumentError "0.#{tag}" end end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index 99138330b..66aa10158 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -201,6 +201,10 @@ class Tap quiet = options.fetch(:quiet, false) requested_remote = options[:clone_target] || default_remote + if official? && DEPRECATED_OFFICIAL_TAPS.include?(repo) + opoo "#{name} was deprecated. This tap is now empty as all its formulae were migrated." + end + if installed? raise TapAlreadyTappedError, name unless full_clone raise TapAlreadyUnshallowError, name unless shallow? @@ -522,13 +526,11 @@ end # A specialized {Tap} class for the core formulae class CoreTap < Tap - if OS.mac? - def default_remote - "https://github.com/Homebrew/homebrew-core" - end - else - def default_remote - "https://github.com/Linuxbrew/homebrew-core" + def default_remote + if OS.mac? || ENV["HOMEBREW_FORCE_HOMEBREW_ORG"] + "https://github.com/Homebrew/homebrew-core".freeze + else + "https://github.com/Linuxbrew/homebrew-core".freeze end end diff --git a/Library/Homebrew/test/.bundle/config b/Library/Homebrew/test/.bundle/config index e451829e9..20549341c 100644 --- a/Library/Homebrew/test/.bundle/config +++ b/Library/Homebrew/test/.bundle/config @@ -1,3 +1,4 @@ --- BUNDLE_PATH: "../vendor/bundle" BUNDLE_DISABLE_SHARED_GEMS: "true" +BUNDLE_BIN: "../bin" diff --git a/Library/Homebrew/test/Gemfile.lock b/Library/Homebrew/test/Gemfile.lock index 4d4eefd7d..1f6adae93 100644 --- a/Library/Homebrew/test/Gemfile.lock +++ b/Library/Homebrew/test/Gemfile.lock @@ -2,51 +2,53 @@ GEM remote: https://rubygems.org/ specs: ast (2.3.0) - codecov (0.1.9) + codecov (0.1.10) json simplecov url diff-lcs (1.3) docile (1.1.5) - json (2.0.3) - parallel (1.10.0) - parallel_tests (2.13.0) + json (2.1.0) + parallel (1.11.2) + parallel_tests (2.14.1) parallel parser (2.4.0.0) ast (~> 2.2) powerpack (0.1.1) - rainbow (2.2.1) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.4) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) + rainbow (2.2.2) + rake + rake (12.0.0) + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) + rspec-support (~> 3.6.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) - rspec-mocks (3.5.0) + rspec-mocks (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-support (3.5.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) rspec-wait (0.0.9) rspec (>= 3, < 4) - rubocop (0.47.1) + rubocop (0.48.1) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.8.1) - simplecov (0.13.0) + simplecov (0.14.1) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - unicode-display_width (1.1.3) + unicode-display_width (1.2.1) url (0.3.2) PLATFORMS @@ -62,4 +64,4 @@ DEPENDENCIES simplecov BUNDLED WITH - 1.14.5 + 1.14.6 diff --git a/Library/Homebrew/test/PATH_spec.rb b/Library/Homebrew/test/PATH_spec.rb new file mode 100644 index 000000000..68233c23c --- /dev/null +++ b/Library/Homebrew/test/PATH_spec.rb @@ -0,0 +1,115 @@ +require "PATH" + +describe PATH do + describe "#initialize" do + it "can take multiple arguments" do + expect(described_class.new("/path1", "/path2")).to eq("/path1:/path2") + end + + it "can parse a mix of arrays and arguments" do + expect(described_class.new(["/path1", "/path2"], "/path3")).to eq("/path1:/path2:/path3") + end + + it "splits an existing PATH" do + expect(described_class.new("/path1:/path2")).to eq(["/path1", "/path2"]) + end + + it "removes duplicates" do + expect(described_class.new("/path1", "/path1")).to eq("/path1") + end + end + + describe "#to_ary" do + it "returns a PATH array" do + expect(described_class.new("/path1", "/path2").to_ary).to eq(["/path1", "/path2"]) + end + end + + describe "#to_str" do + it "returns a PATH string" do + expect(described_class.new("/path1", "/path2").to_str).to eq("/path1:/path2") + end + end + + describe "#prepend" do + it "prepends a path to a PATH" do + expect(described_class.new("/path1").prepend("/path2").to_str).to eq("/path2:/path1") + end + + it "removes duplicates" do + expect(described_class.new("/path1").prepend("/path1").to_str).to eq("/path1") + end + end + + describe "#append" do + it "prepends a path to a PATH" do + expect(described_class.new("/path1").append("/path2").to_str).to eq("/path1:/path2") + end + + it "removes duplicates" do + expect(described_class.new("/path1").append("/path1").to_str).to eq("/path1") + end + end + + describe "#insert" do + it "inserts a path at a given index" do + expect(described_class.new("/path1").insert(0, "/path2").to_str).to eq("/path2:/path1") + end + + it "can insert multiple paths" do + expect(described_class.new("/path1").insert(0, "/path2", "/path3")).to eq("/path2:/path3:/path1") + end + end + + describe "#include?" do + it "returns true if a path is included" do + path = described_class.new("/path1", "/path2") + expect(path).to include("/path1") + expect(path).to include("/path2") + end + + it "returns false if a path is not included" do + expect(described_class.new("/path1")).not_to include("/path2") + end + + it "returns false if the given string contains a separator" do + expect(described_class.new("/path1", "/path2")).not_to include("/path1:") + end + end + + describe "#each" do + it "loops through each path" do + enum = described_class.new("/path1", "/path2").each + + expect(enum.next).to eq("/path1") + expect(enum.next).to eq("/path2") + end + end + + describe "#select" do + it "returns an object of the same class instead of an Array" do + expect(described_class.new.select { true }).to be_a(described_class) + end + end + + describe "#reject" do + it "returns an object of the same class instead of an Array" do + expect(described_class.new.reject { true }).to be_a(described_class) + end + end + + describe "#existing" do + it "returns a new PATH without non-existent paths" do + allow(File).to receive(:directory?).with("/path1").and_return(true) + allow(File).to receive(:directory?).with("/path2").and_return(false) + + path = described_class.new("/path1", "/path2") + expect(path.existing.to_ary).to eq(["/path1"]) + expect(path.to_ary).to eq(["/path1", "/path2"]) + end + + it "returns nil instead of an empty #{described_class}" do + expect(described_class.new.existing).to be nil + end + end +end diff --git a/Library/Homebrew/test/cask/artifact/binary_spec.rb b/Library/Homebrew/test/cask/artifact/binary_spec.rb index 69bde3438..ee62e6439 100644 --- a/Library/Homebrew/test/cask/artifact/binary_spec.rb +++ b/Library/Homebrew/test/cask/artifact/binary_spec.rb @@ -6,9 +6,7 @@ describe Hbc::Artifact::Binary, :cask do end end } - let(:expected_path) { - Hbc.binarydir.join("binary") - } + let(:expected_path) { Hbc.binarydir.join("binary") } before(:each) do Hbc.binarydir.mkpath @@ -26,15 +24,28 @@ describe Hbc::Artifact::Binary, :cask do expect(expected_path.readlink).to exist end - it "makes the binary executable" do - expect(FileUtils).to receive(:chmod).with("+x", cask.staged_path.join("binary")) + context "when the binary is not executable" do + let(:cask) { + Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-non-executable-binary.rb").tap do |cask| + shutup do + InstallHelper.install_without_artifacts(cask) + end + end + } - shutup do - Hbc::Artifact::Binary.new(cask).install_phase - end + let(:expected_path) { Hbc.binarydir.join("naked_non_executable") } - expect(expected_path).to be_a_symlink - expect(expected_path.readlink).to be_executable + it "makes the binary executable" do + expect(FileUtils).to receive(:chmod) + .with("+x", cask.staged_path.join("naked_non_executable")).and_call_original + + shutup do + Hbc::Artifact::Binary.new(cask).install_phase + end + + expect(expected_path).to be_a_symlink + expect(expected_path.readlink).to be_executable + end end it "avoids clobbering an existing binary by linking over it" do diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index 2ab966f82..3736f3c01 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -85,8 +85,8 @@ describe Hbc::Cask, :cask do it "proposes a versioned metadata directory name for each instance" do cask_token = "local-caffeine" c = Hbc::CaskLoader.load(cask_token) - metadata_path = Hbc.caskroom.join(cask_token, ".metadata", c.version) - expect(c.metadata_versioned_container_path.to_s).to eq(metadata_path.to_s) + metadata_timestamped_path = Hbc.caskroom.join(cask_token, ".metadata", c.version) + expect(c.metadata_versioned_path.to_s).to eq(metadata_timestamped_path.to_s) end end diff --git a/Library/Homebrew/test/cask/cli/install_spec.rb b/Library/Homebrew/test/cask/cli/install_spec.rb index 5a40017e8..219b9522e 100644 --- a/Library/Homebrew/test/cask/cli/install_spec.rb +++ b/Library/Homebrew/test/cask/cli/install_spec.rb @@ -1,4 +1,18 @@ describe Hbc::CLI::Install, :cask do + it "displays the installation progress" do + output = Regexp.new <<-EOS.undent + ==> Downloading file:.*caffeine.zip + ==> Verifying checksum for Cask local-caffeine + ==> Installing Cask local-caffeine + ==> Moving App 'Caffeine.app' to '.*Caffeine.app'. + .*local-caffeine was successfully installed! + EOS + + expect { + Hbc::CLI::Install.run("local-caffeine") + }.to output(output).to_stdout + end + it "allows staging and activation of multiple Casks at once" do shutup do Hbc::CLI::Install.run("local-transmission", "local-caffeine") diff --git a/Library/Homebrew/test/cask/cli/reinstall_spec.rb b/Library/Homebrew/test/cask/cli/reinstall_spec.rb index e573a3470..8885fa199 100644 --- a/Library/Homebrew/test/cask/cli/reinstall_spec.rb +++ b/Library/Homebrew/test/cask/cli/reinstall_spec.rb @@ -1,4 +1,27 @@ describe Hbc::CLI::Reinstall, :cask do + it "displays the reinstallation progress" do + caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") + + shutup do + Hbc::Installer.new(caffeine).install + end + + output = Regexp.new <<-EOS.undent + ==> Downloading file:.*caffeine.zip + Already downloaded: .*local-caffeine--1.2.3.zip + ==> Verifying checksum for Cask local-caffeine + ==> Uninstalling Cask local-caffeine + ==> Removing App '.*Caffeine.app'. + ==> Installing Cask local-caffeine + ==> Moving App 'Caffeine.app' to '.*Caffeine.app'. + .*local-caffeine was successfully installed! + EOS + + expect { + Hbc::CLI::Reinstall.run("local-caffeine") + }.to output(output).to_stdout + end + it "allows reinstalling a Cask" do shutup do Hbc::CLI::Install.run("local-transmission") diff --git a/Library/Homebrew/test/cask/cli/search_spec.rb b/Library/Homebrew/test/cask/cli/search_spec.rb index 9843a6de6..00fcf7382 100644 --- a/Library/Homebrew/test/cask/cli/search_spec.rb +++ b/Library/Homebrew/test/cask/cli/search_spec.rb @@ -3,7 +3,7 @@ describe Hbc::CLI::Search, :cask do expect { Hbc::CLI::Search.run("local") }.to output(<<-EOS.undent).to_stdout - ==> Partial matches + ==> Partial Matches local-caffeine local-transmission EOS @@ -42,13 +42,13 @@ describe Hbc::CLI::Search, :cask do it "accepts a regexp argument" do expect { Hbc::CLI::Search.run("/^local-c[a-z]ffeine$/") - }.to output("==> Regexp matches\nlocal-caffeine\n").to_stdout + }.to output("==> Regexp Matches\nlocal-caffeine\n").to_stdout end it "Returns both exact and partial matches" do expect { Hbc::CLI::Search.run("test-opera") - }.to output(/^==> Exact match\ntest-opera\n==> Partial matches\ntest-opera-mail/).to_stdout + }.to output(/^==> Exact Match\ntest-opera\n==> Partial Matches\ntest-opera-mail/).to_stdout end it "does not search the Tap name" do diff --git a/Library/Homebrew/test/cask/cli/style_spec.rb b/Library/Homebrew/test/cask/cli/style_spec.rb index ca17c5e46..d41636beb 100644 --- a/Library/Homebrew/test/cask/cli/style_spec.rb +++ b/Library/Homebrew/test/cask/cli/style_spec.rb @@ -81,7 +81,7 @@ describe Hbc::CLI::Style, :cask do end context "version" do - it "matches `HOMEBREW_RUBOCOP_VERSION`" do + it "matches `HOMEBREW_RUBOCOP_VERSION`", :needs_network do stdout, status = Open3.capture2("gem", "dependency", "rubocop-cask", "--version", HOMEBREW_RUBOCOP_CASK_VERSION, "--pipe", "--remote") expect(status).to be_a_success diff --git a/Library/Homebrew/test/cask/cli/uninstall_spec.rb b/Library/Homebrew/test/cask/cli/uninstall_spec.rb index fb196ee72..4089c47b4 100644 --- a/Library/Homebrew/test/cask/cli/uninstall_spec.rb +++ b/Library/Homebrew/test/cask/cli/uninstall_spec.rb @@ -1,4 +1,21 @@ describe Hbc::CLI::Uninstall, :cask do + it "displays the uninstallation progress" do + caffeine = Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") + + shutup do + Hbc::Installer.new(caffeine).install + end + + output = Regexp.new <<-EOS.undent + ==> Uninstalling Cask local-caffeine + ==> Removing App '.*Caffeine.app'. + EOS + + expect { + Hbc::CLI::Uninstall.run("local-caffeine") + }.to output(output).to_stdout + end + it "shows an error when a bad Cask is provided" do expect { Hbc::CLI::Uninstall.run("notacask") @@ -13,7 +30,9 @@ describe Hbc::CLI::Uninstall, :cask do it "tries anyway on a non-present Cask when --force is given" do expect { - Hbc::CLI::Uninstall.run("local-caffeine", "--force") + shutup do + Hbc::CLI::Uninstall.run("local-caffeine", "--force") + end }.not_to raise_error end diff --git a/Library/Homebrew/test/cask/installer_spec.rb b/Library/Homebrew/test/cask/installer_spec.rb index 7dd5b2bda..59d61bbdd 100644 --- a/Library/Homebrew/test/cask/installer_spec.rb +++ b/Library/Homebrew/test/cask/installer_spec.rb @@ -336,9 +336,8 @@ describe Hbc::Installer, :cask do Hbc::Installer.new(caffeine).install end - m_path = caffeine.metadata_path(:now, true) - expect(caffeine.metadata_path(:now, false)).to eq(m_path) - expect(caffeine.metadata_path(:latest)).to eq(m_path) + m_path = caffeine.metadata_timestamped_path(timestamp: :now, create: true) + expect(caffeine.metadata_timestamped_path(timestamp: :latest)).to eq(m_path) end it "generates and finds a metadata subdirectory for an installed Cask" do @@ -349,9 +348,8 @@ describe Hbc::Installer, :cask do end subdir_name = "Casks" - m_subdir = caffeine.metadata_subdir(subdir_name, :now, true) - expect(caffeine.metadata_subdir(subdir_name, :now, false)).to eq(m_subdir) - expect(caffeine.metadata_subdir(subdir_name, :latest)).to eq(m_subdir) + m_subdir = caffeine.metadata_subdir(subdir_name, timestamp: :now, create: true) + expect(caffeine.metadata_subdir(subdir_name, timestamp: :latest)).to eq(m_subdir) end end diff --git a/Library/Homebrew/test/cask/pkg_spec.rb b/Library/Homebrew/test/cask/pkg_spec.rb index 9930cd00f..e507ceda0 100644 --- a/Library/Homebrew/test/cask/pkg_spec.rb +++ b/Library/Homebrew/test/cask/pkg_spec.rb @@ -36,7 +36,12 @@ describe Hbc::Pkg, :cask do it "forgets the pkg" do allow(fake_system_command).to receive(:run!).with( "/usr/sbin/pkgutil", - args: ["--export-plist", "my.fake.pkg"], + args: ["--pkg-info-plist", "my.fake.pkg"], + ).and_return(empty_response) + + expect(fake_system_command).to receive(:run!).with( + "/usr/sbin/pkgutil", + args: ["--files", "my.fake.pkg"], ).and_return(empty_response) expect(fake_system_command).to receive(:run!).with( @@ -139,7 +144,7 @@ describe Hbc::Pkg, :cask do expect(fake_system_command).to receive(:run!).with( "/usr/sbin/pkgutil", - args: ["--export-plist", pkg_id], + args: ["--pkg-info-plist", pkg_id], ).and_return( Hbc::SystemCommand::Result.new(nil, pkg_info_plist, nil, 0), ) diff --git a/Library/Homebrew/test/cmd/bundle_spec.rb b/Library/Homebrew/test/cmd/bundle_spec.rb index 13f13485c..286ddba97 100644 --- a/Library/Homebrew/test/cmd/bundle_spec.rb +++ b/Library/Homebrew/test/cmd/bundle_spec.rb @@ -1,6 +1,6 @@ describe "brew bundle", :integration_test, :needs_test_cmd_taps do describe "check" do - it "checks if a Brewfile's dependencies are satisfied" do + it "checks if a Brewfile's dependencies are satisfied", :needs_network do setup_remote_tap "homebrew/bundle" HOMEBREW_REPOSITORY.cd do diff --git a/Library/Homebrew/test/cmd/install_spec.rb b/Library/Homebrew/test/cmd/install_spec.rb index c1240a30e..b819c17be 100644 --- a/Library/Homebrew/test/cmd/install_spec.rb +++ b/Library/Homebrew/test/cmd/install_spec.rb @@ -77,7 +77,7 @@ describe "brew install", :integration_test do EOS expect { brew "install", "testball1" } - .to output(/first `brew unlink testball1`/).to_stderr + .to output(/`brew upgrade testball1`/).to_stderr .and not_to_output.to_stdout .and be_a_failure @@ -217,10 +217,9 @@ describe "brew install", :integration_test do depends_on NonFatalRequirement EOS - # FIXME: This should output to STDERR. expect { brew "install", "testball1" } - .to output(/NonFatalRequirement unsatisfied!/).to_stdout - .and not_to_output.to_stderr + .to output(/NonFatalRequirement unsatisfied!/).to_stderr + .and output(/built in/).to_stdout .and be_a_success end @@ -234,10 +233,9 @@ describe "brew install", :integration_test do depends_on FatalRequirement EOS - # FIXME: This should output to STDERR. expect { brew "install", "testball1" } - .to output(/FatalRequirement unsatisfied!/).to_stdout - .and output(/An unsatisfied requirement failed this build./).to_stderr + .to output(/FatalRequirement unsatisfied!/).to_stderr + .and not_to_output.to_stdout .and be_a_failure end end diff --git a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb b/Library/Homebrew/test/cmd/search_remote_tap_spec.rb index 99c2cc20b..b0beb122c 100644 --- a/Library/Homebrew/test/cmd/search_remote_tap_spec.rb +++ b/Library/Homebrew/test/cmd/search_remote_tap_spec.rb @@ -1,19 +1,21 @@ require "cmd/search" describe Homebrew do - specify "#search_tap" do + specify "#search_taps" do json_response = { - "tree" => [ + "items" => [ { - "path" => "Formula/not-a-formula.rb", - "type" => "blob", + "path" => "Formula/some-formula.rb", + "repository" => { + "full_name" => "Homebrew/homebrew-foo", + }, }, ], } allow(GitHub).to receive(:open).and_yield(json_response) - expect(described_class.search_tap("homebrew", "not-a-tap", "not-a-formula")) - .to eq(["homebrew/not-a-tap/not-a-formula"]) + expect(described_class.search_taps("some-formula")) + .to match(["homebrew/foo/some-formula"]) end end diff --git a/Library/Homebrew/test/compiler_selector_spec.rb b/Library/Homebrew/test/compiler_selector_spec.rb index 0f6f6b5f2..18efbfd42 100644 --- a/Library/Homebrew/test/compiler_selector_spec.rb +++ b/Library/Homebrew/test/compiler_selector_spec.rb @@ -3,7 +3,7 @@ require "software_spec" describe CompilerSelector do subject { described_class.new(software_spec, versions, compilers) } - let(:compilers) { [:clang, :gcc, :llvm, :gnu] } + let(:compilers) { [:clang, :gcc, :gnu] } let(:software_spec) { SoftwareSpec.new } let(:cc) { :clang } let(:versions) do @@ -28,7 +28,6 @@ describe CompilerSelector do describe "#compiler" do it "raises an error if no matching compiler can be found" do software_spec.fails_with(:clang) - software_spec.fails_with(:llvm) software_spec.fails_with(:gcc) software_spec.fails_with(gcc: "4.8") software_spec.fails_with(gcc: "4.7") @@ -45,11 +44,6 @@ describe CompilerSelector do expect(subject.compiler).to eq(:gcc) end - it "returns clang if it fails with llvm" do - software_spec.fails_with(:llvm) - expect(subject.compiler).to eq(:clang) - end - it "returns clang if it fails with gcc" do software_spec.fails_with(:gcc) expect(subject.compiler).to eq(:clang) @@ -68,13 +62,11 @@ describe CompilerSelector do it "returns gcc if it fails with clang and llvm" do software_spec.fails_with(:clang) - software_spec.fails_with(:llvm) expect(subject.compiler).to eq(:gcc) end it "returns clang if it fails with gcc and llvm" do software_spec.fails_with(:gcc) - software_spec.fails_with(:llvm) expect(subject.compiler).to eq(:clang) end @@ -87,7 +79,6 @@ describe CompilerSelector do example "returns a lower version of gcc if it fails with the highest version" do software_spec.fails_with(:clang) software_spec.fails_with(:gcc) - software_spec.fails_with(:llvm) software_spec.fails_with(gcc: "4.8") expect(subject.compiler).to eq("gcc-4.7") end @@ -102,7 +93,6 @@ describe CompilerSelector do allow(versions).to receive(:gcc_build_version).and_return(Version::NULL) software_spec.fails_with(:clang) - software_spec.fails_with(:llvm) software_spec.fails_with(gcc: "4.8") software_spec.fails_with(gcc: "4.7") diff --git a/Library/Homebrew/test/dev-cmd/audit_spec.rb b/Library/Homebrew/test/dev-cmd/audit_spec.rb index a6bb22837..b07ffaadc 100644 --- a/Library/Homebrew/test/dev-cmd/audit_spec.rb +++ b/Library/Homebrew/test/dev-cmd/audit_spec.rb @@ -5,6 +5,13 @@ RSpec::Matchers.alias_matcher :have_data, :be_data RSpec::Matchers.alias_matcher :have_end, :be_end RSpec::Matchers.alias_matcher :have_trailing_newline, :be_trailing_newline +module Count + def self.increment + @count ||= 0 + @count += 1 + end +end + describe FormulaText do let(:dir) { mktmpdir } @@ -141,100 +148,6 @@ describe FormulaAuditor do fa.audit_file expect(fa.problems).to eq([]) end - - specify "strict: ordering issue" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - url "http://example.com/foo-1.0.tgz" - homepage "http://example.com" - end - EOS - - fa.audit_file - expect(fa.problems) - .to eq(["`homepage` (line 3) should be put before `url` (line 2)"]) - end - - specify "strict: resource placement" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - url "https://example.com/foo-1.0.tgz" - - resource "foo2" do - url "https://example.com/foo-2.0.tgz" - end - - depends_on "openssl" - end - EOS - - fa.audit_file - expect(fa.problems) - .to eq(["`depends_on` (line 8) should be put before `resource` (line 4)"]) - end - - specify "strict: plist placement" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - url "https://example.com/foo-1.0.tgz" - - test do - expect(shell_output("./dogs")).to match("Dogs are terrific") - end - - def plist - end - end - EOS - - fa.audit_file - expect(fa.problems) - .to eq(["`plist block` (line 8) should be put before `test block` (line 4)"]) - end - - specify "strict: url outside of stable block" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - url "http://example.com/foo-1.0.tgz" - stable do - # stuff - end - end - EOS - - fa.audit_file - expect(fa.problems).to eq(["`url` should be put inside `stable block`"]) - end - - specify "strict: head and head do" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - head "http://example.com/foo.git" - head do - # stuff - end - end - EOS - - fa.audit_file - expect(fa.problems).to eq(["Should not have both `head` and `head do`"]) - end - - specify "strict: bottle and bottle do" do - fa = formula_auditor "foo", <<-EOS.undent, strict: true - class Foo < Formula - url "http://example.com/foo-1.0.tgz" - bottle do - # bottles go here - end - bottle :unneeded - end - EOS - - fa.audit_file - expect(fa.problems) - .to eq(["Should not have `bottle :unneeded/:disable` and `bottle do`"]) - end end describe "#audit_class" do @@ -303,7 +216,7 @@ describe FormulaAuditor do end end - describe "#audit_line" do + describe "#line_problems" do specify "pkgshare" do fa = formula_auditor "foo", <<-EOS.undent, strict: true class Foo < Formula @@ -311,25 +224,25 @@ describe FormulaAuditor do end EOS - fa.audit_line 'ohai "#{share}/foo"', 3 + fa.line_problems 'ohai "#{share}/foo"', 3 expect(fa.problems.shift).to eq("Use \#{pkgshare} instead of \#{share}/foo") - fa.audit_line 'ohai "#{share}/foo/bar"', 3 + fa.line_problems 'ohai "#{share}/foo/bar"', 3 expect(fa.problems.shift).to eq("Use \#{pkgshare} instead of \#{share}/foo") - fa.audit_line 'ohai share/"foo"', 3 + fa.line_problems 'ohai share/"foo"', 3 expect(fa.problems.shift).to eq('Use pkgshare instead of (share/"foo")') - fa.audit_line 'ohai share/"foo/bar"', 3 + fa.line_problems 'ohai share/"foo/bar"', 3 expect(fa.problems.shift).to eq('Use pkgshare instead of (share/"foo")') - fa.audit_line 'ohai "#{share}/foo-bar"', 3 + fa.line_problems 'ohai "#{share}/foo-bar"', 3 expect(fa.problems).to eq([]) - fa.audit_line 'ohai share/"foo-bar"', 3 + fa.line_problems 'ohai share/"foo-bar"', 3 expect(fa.problems).to eq([]) - fa.audit_line 'ohai share/"bar"', 3 + fa.line_problems 'ohai share/"bar"', 3 expect(fa.problems).to eq([]) end @@ -344,11 +257,11 @@ describe FormulaAuditor do end EOS - fa.audit_line 'ohai "#{share}/foolibc++"', 3 + fa.line_problems 'ohai "#{share}/foolibc++"', 3 expect(fa.problems.shift) .to eq("Use \#{pkgshare} instead of \#{share}/foolibc++") - fa.audit_line 'ohai share/"foolibc++"', 3 + fa.line_problems 'ohai share/"foolibc++"', 3 expect(fa.problems.shift) .to eq('Use pkgshare instead of (share/"foolibc++")') end @@ -360,7 +273,7 @@ describe FormulaAuditor do end EOS - fa.audit_line "class Foo<Formula", 1 + fa.line_problems "class Foo<Formula", 1 expect(fa.problems.shift) .to eq("Use a space in class inheritance: class Foo < Formula") end @@ -368,10 +281,10 @@ describe FormulaAuditor do specify "default template" do fa = formula_auditor "foo", "class Foo < Formula; url '/foo-1.0.tgz'; end" - fa.audit_line '# system "cmake", ".", *std_cmake_args', 3 + fa.line_problems '# system "cmake", ".", *std_cmake_args', 3 expect(fa.problems.shift).to eq("Commented cmake call found") - fa.audit_line "# PLEASE REMOVE", 3 + fa.line_problems "# PLEASE REMOVE", 3 expect(fa.problems.shift).to eq("Please remove default template comments") end end @@ -409,77 +322,66 @@ describe FormulaAuditor do .to eq(["Don't recommend setuid in the caveats, suggest sudo instead."]) end - describe "#audit_homepage" do - specify "homepage URLs" do - fa = formula_auditor "foo", <<-EOS.undent, online: true + describe "#audit_keg_only_style" do + specify "keg_only_needs_downcasing" do + fa = formula_auditor "foo", <<-EOS.undent, strict: true class Foo < Formula - homepage "ftp://example.com/foo" url "http://example.com/foo-1.0.tgz" + + keg_only "Because why not" end EOS - fa.audit_homepage + fa.audit_keg_only_style expect(fa.problems) - .to eq(["The homepage should start with http or https (URL is #{fa.formula.homepage})."]) - - formula_homepages = { - "bar" => "http://www.freedesktop.org/wiki/bar", - "baz" => "http://www.freedesktop.org/wiki/Software/baz", - "qux" => "https://code.google.com/p/qux", - "quux" => "http://github.com/quux", - "corge" => "http://savannah.nongnu.org/corge", - "grault" => "http://grault.github.io/", - "garply" => "http://www.gnome.org/garply", - "sf1" => "http://foo.sourceforge.net/", - "sf2" => "http://foo.sourceforge.net", - "sf3" => "http://foo.sf.net/", - "sf4" => "http://foo.sourceforge.io/", - "waldo" => "http://www.gnu.org/waldo", - } - - formula_homepages.each do |name, homepage| - fa = formula_auditor name, <<-EOS.undent - class #{Formulary.class_s(name)} < Formula - homepage "#{homepage}" - url "http://example.com/#{name}-1.0.tgz" - end - EOS + .to eq(["'Because' from the keg_only reason should be 'because'.\n"]) + end - fa.audit_homepage - if homepage =~ %r{http:\/\/www\.freedesktop\.org} - if homepage =~ /Software/ - expect(fa.problems.first).to match( - "#{homepage} should be styled " \ - "`https://wiki.freedesktop.org/www/Software/project_name`", - ) - else - expect(fa.problems.first).to match( - "#{homepage} should be styled " \ - "`https://wiki.freedesktop.org/project_name`", - ) - end - elsif homepage =~ %r{https:\/\/code\.google\.com} - expect(fa.problems.first) - .to match("#{homepage} should end with a slash") - elsif homepage =~ /foo\.(sf|sourceforge)\.net/ - expect(fa.problems.first) - .to match("#{homepage} should be `https://foo.sourceforge.io/`") - else - expect(fa.problems.first) - .to match("Please use https:// for #{homepage}") + specify "keg_only_redundant_period" do + fa = formula_auditor "foo", <<-EOS.undent, strict: true + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + + keg_only "because this line ends in a period." end - end + EOS + + fa.audit_keg_only_style + expect(fa.problems) + .to eq(["keg_only reason should not end with a period."]) + end + + specify "keg_only_handles_block_correctly" do + fa = formula_auditor "foo", <<-EOS.undent, strict: true + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + + keg_only <<-EOF.undent + this line starts with a lowercase word. + + This line does not but that shouldn't be a + problem + EOF + end + EOS + + fa.audit_keg_only_style + expect(fa.problems) + .to eq([]) end - specify "missing homepage" do - fa = formula_auditor "foo", <<-EOS.undent, online: true + specify "keg_only_handles_whitelist_correctly" do + fa = formula_auditor "foo", <<-EOS.undent, strict: true class Foo < Formula url "http://example.com/foo-1.0.tgz" + + keg_only "Apple ships foo in the CLT package" end EOS - fa.audit_homepage - expect(fa.problems.first).to match("Formula should have a homepage.") + fa.audit_keg_only_style + expect(fa.problems) + .to eq([]) end end @@ -517,5 +419,203 @@ describe FormulaAuditor do expect(fa.problems.first) .to match('xcodebuild should be passed an explicit "SYMROOT"') end + + specify "disallow go get usage" do + fa = formula_auditor "foo", <<-EOS.undent + class Foo <Formula + url "http://example.com/foo-1.0.tgz" + + def install + system "go", "get", "bar" + end + end + EOS + fa.audit_text + expect(fa.problems.first) + .to match("Formulae should not use `go get`. If non-vendored resources are required use `go_resource`s.") + end + end + + describe "#audit_revision_and_version_scheme" do + let(:origin_tap_path) { Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" } + let(:formula_subpath) { "Formula/foo#{@foo_version}.rb" } + let(:origin_formula_path) { origin_tap_path/formula_subpath } + let(:tap_path) { Tap::TAP_DIRECTORY/"homebrew/homebrew-bar" } + let(:formula_path) { tap_path/formula_subpath } + + before(:each) do + @foo_version = Count.increment + + origin_formula_path.write <<-EOS.undent + class Foo#{@foo_version} < Formula + url "https://example.com/foo-1.0.tar.gz" + revision 2 + version_scheme 1 + end + EOS + + origin_tap_path.mkpath + origin_tap_path.cd do + shutup do + system "git", "init" + system "git", "add", "--all" + system "git", "commit", "-m", "init" + end + end + + tap_path.mkpath + tap_path.cd do + shutup do + system "git", "clone", origin_tap_path, "." + end + end + end + + subject do + fa = described_class.new(Formulary.factory(formula_path)) + fa.audit_revision_and_version_scheme + fa.problems.first + end + + def formula_gsub(before, after = "") + text = formula_path.read + text.gsub! before, after + formula_path.unlink + formula_path.write text + end + + def formula_gsub_commit(before, after = "") + text = origin_formula_path.read + text.gsub!(before, after) + origin_formula_path.unlink + origin_formula_path.write text + + origin_tap_path.cd do + shutup do + system "git", "commit", "-am", "commit" + end + end + + tap_path.cd do + shutup do + system "git", "fetch" + system "git", "reset", "--hard", "origin/master" + end + end + end + + context "revisions" do + context "should not be removed when first committed above 0" do + it { is_expected.to be_nil } + end + + context "should not decrease with the same version" do + before { formula_gsub_commit "revision 2", "revision 1" } + + it { is_expected.to match("revision should not decrease (from 2 to 1)") } + end + + context "should not be removed with the same version" do + before { formula_gsub_commit "revision 2" } + + it { is_expected.to match("revision should not decrease (from 2 to 0)") } + end + + context "should not decrease with the same, uncommitted version" do + before { formula_gsub "revision 2", "revision 1" } + + it { is_expected.to match("revision should not decrease (from 2 to 1)") } + end + + context "should be removed with a newer version" do + before { formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" } + + it { is_expected.to match("'revision 2' should be removed") } + end + + context "should not warn on an newer version revision removal" do + before do + formula_gsub_commit "revision 2", "" + formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" + end + + it { is_expected.to be_nil } + end + + context "should only increment by 1 with an uncommitted version" do + before do + formula_gsub "foo-1.0.tar.gz", "foo-1.1.tar.gz" + formula_gsub "revision 2", "revision 4" + end + + it { is_expected.to match("revisions should only increment by 1") } + end + + context "should not warn on past increment by more than 1" do + before do + formula_gsub_commit "revision 2", "# no revision" + formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" + formula_gsub_commit "# no revision", "revision 3" + end + + it { is_expected.to be_nil } + end + end + + context "version_schemes" do + context "should not decrease with the same version" do + before { formula_gsub_commit "version_scheme 1" } + + it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") } + end + + context "should not decrease with a new version" do + before do + formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" + formula_gsub_commit "version_scheme 1", "" + formula_gsub_commit "revision 2", "" + end + + it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") } + end + + context "should only increment by 1" do + before do + formula_gsub_commit "version_scheme 1", "# no version_scheme" + formula_gsub_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" + formula_gsub_commit "revision 2", "" + formula_gsub_commit "# no version_scheme", "version_scheme 3" + end + + it { is_expected.to match("version_schemes should only increment by 1") } + end + end + + context "versions" do + context "uncommitted should not decrease" do + before { formula_gsub "foo-1.0.tar.gz", "foo-0.9.tar.gz" } + + it { is_expected.to match("stable version should not decrease (from 1.0 to 0.9)") } + end + + context "committed can decrease" do + before do + formula_gsub_commit "revision 2" + formula_gsub_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz" + end + + it { is_expected.to be_nil } + end + + context "can decrease with version_scheme increased" do + before do + formula_gsub "revision 2" + formula_gsub "foo-1.0.tar.gz", "foo-0.9.tar.gz" + formula_gsub "version_scheme 1", "version_scheme 2" + end + + it { is_expected.to be_nil } + end + end end end diff --git a/Library/Homebrew/test/dev-cmd/pull_spec.rb b/Library/Homebrew/test/dev-cmd/pull_spec.rb index 3c0108df2..2ebe144bb 100644 --- a/Library/Homebrew/test/dev-cmd/pull_spec.rb +++ b/Library/Homebrew/test/dev-cmd/pull_spec.rb @@ -6,9 +6,7 @@ describe "brew pull", :integration_test do .and be_a_failure end - it "fetches a patch from a GitHub commit or pull request and applies it" do - skip "Requires network connection." if ENV["HOMEBREW_NO_GITHUB_API"] - + it "fetches a patch from a GitHub commit or pull request and applies it", :needs_network do CoreTap.instance.path.cd do shutup do system "git", "init" diff --git a/Library/Homebrew/test/diagnostic_spec.rb b/Library/Homebrew/test/diagnostic_spec.rb index 59560127c..c4373671e 100644 --- a/Library/Homebrew/test/diagnostic_spec.rb +++ b/Library/Homebrew/test/diagnostic_spec.rb @@ -7,7 +7,7 @@ describe Homebrew::Diagnostic::Checks do end specify "#check_path_for_trailing_slashes" do - ENV["PATH"] += File::PATH_SEPARATOR + "/foo/bar/" + ENV["HOMEBREW_PATH"] += File::PATH_SEPARATOR + "/foo/bar/" expect(subject.check_path_for_trailing_slashes) .to match("Some directories in your path end in a slash") end @@ -41,6 +41,20 @@ describe Homebrew::Diagnostic::Checks do end end + specify "#check_access_lock_dir" do + begin + prev_mode = HOMEBREW_LOCK_DIR.stat.mode + mode = HOMEBREW_LOCK_DIR.stat.mode & 0777 + HOMEBREW_LOCK_DIR.chmod 0555 + expect(HOMEBREW_LOCK_DIR.stat.mode).not_to eq(prev_mode) + + expect(subject.check_access_lock_dir) + .to match("#{HOMEBREW_LOCK_DIR} isn't writable.") + ensure + HOMEBREW_LOCK_DIR.chmod mode + end + end + specify "#check_access_logs" do begin mode = HOMEBREW_LOGS.stat.mode & 0777 @@ -110,8 +124,9 @@ describe Homebrew::Diagnostic::Checks do specify "#check_user_path_3" do begin sbin = HOMEBREW_PREFIX/"sbin" - ENV["PATH"] = "#{HOMEBREW_PREFIX}/bin#{File::PATH_SEPARATOR}" + - ENV["PATH"].gsub(/(?:^|#{Regexp.escape(File::PATH_SEPARATOR)})#{Regexp.escape(sbin)}/, "") + ENV["HOMEBREW_PATH"] = + "#{HOMEBREW_PREFIX}/bin#{File::PATH_SEPARATOR}" + + ENV["HOMEBREW_PATH"].gsub(/(?:^|#{Regexp.escape(File::PATH_SEPARATOR)})#{Regexp.escape(sbin)}/, "") (sbin/"something").mkpath expect(subject.check_user_path_1).to be nil @@ -137,7 +152,9 @@ describe Homebrew::Diagnostic::Checks do file = "#{path}/foo-config" FileUtils.touch file FileUtils.chmod 0755, file - ENV["PATH"] = "#{path}#{File::PATH_SEPARATOR}#{ENV["PATH"]}" + ENV["HOMEBREW_PATH"] = + ENV["PATH"] = + "#{path}#{File::PATH_SEPARATOR}#{ENV["PATH"]}" expect(subject.check_for_config_scripts) .to match('"config" scripts exist') diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 2309c36fb..1f98ca525 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -10,7 +10,6 @@ RSpec::Matchers.alias_matcher :supersede_an_installed_formula, :be_supersedes_an RSpec::Matchers.alias_matcher :have_changed_alias, :be_alias_changed RSpec::Matchers.alias_matcher :have_option_defined, :be_option_defined -RSpec::Matchers.alias_matcher :have_post_install_defined, :be_post_install_defined RSpec::Matchers.alias_matcher :have_test_defined, :be_test_defined RSpec::Matchers.alias_matcher :pour_bottle, :be_pour_bottle @@ -624,23 +623,6 @@ describe Formula do expect(f.desc).to eq("a formula") end - specify "#post_install_defined?" do - f1 = formula do - url "foo-1.0" - - def post_install - # do nothing - end - end - - f2 = formula do - url "foo-1.0" - end - - expect(f1).to have_post_install_defined - expect(f2).not_to have_post_install_defined - end - specify "#test_defined?" do f1 = formula do url "foo-1.0" diff --git a/Library/Homebrew/test/gpg2_requirement_spec.rb b/Library/Homebrew/test/gpg2_requirement_spec.rb index d7767abd3..a5501c84e 100644 --- a/Library/Homebrew/test/gpg2_requirement_spec.rb +++ b/Library/Homebrew/test/gpg2_requirement_spec.rb @@ -9,7 +9,7 @@ describe GPG2Requirement do ENV["PATH"] = dir/"bin" (dir/"bin/gpg").write <<-EOS.undent #!/bin/bash - echo 2.0.30 + echo 2.1.20 EOS FileUtils.chmod 0755, dir/"bin/gpg" diff --git a/Library/Homebrew/test/gpg_spec.rb b/Library/Homebrew/test/gpg_spec.rb index aa00d79f5..160e55379 100644 --- a/Library/Homebrew/test/gpg_spec.rb +++ b/Library/Homebrew/test/gpg_spec.rb @@ -13,7 +13,12 @@ describe Gpg do shutup do subject.create_test_key(dir) end - expect(dir/".gnupg/secring.gpg").to exist + + if subject.version == Version.create("2.0") + expect(dir/".gnupg/secring.gpg").to be_a_file + else + expect(dir/".gnupg/pubring.kbx").to be_a_file + end end end end diff --git a/Library/Homebrew/test/hardware_spec.rb b/Library/Homebrew/test/hardware_spec.rb index c5f8daf4e..de8d77e68 100644 --- a/Library/Homebrew/test/hardware_spec.rb +++ b/Library/Homebrew/test/hardware_spec.rb @@ -36,5 +36,52 @@ module Hardware ).to include(described_class.family) end end + + describe "::can_run?" do + it "reports that Intel machines can run Intel executables" do + allow(Hardware::CPU).to receive(:type).and_return :intel + allow(Hardware::CPU).to receive(:bits).and_return 64 + expect(Hardware::CPU.can_run?(:i386)).to be true + expect(Hardware::CPU.can_run?(:x86_64)).to be true + end + + it "reports that PowerPC machines can run PowerPC executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + allow(Hardware::CPU).to receive(:bits).and_return 64 + expect(Hardware::CPU.can_run?(:ppc)).to be true + expect(Hardware::CPU.can_run?(:ppc64)).to be true + end + + it "reports that 32-bit Intel machines can't run x86_64 executables" do + allow(Hardware::CPU).to receive(:type).and_return :intel + allow(Hardware::CPU).to receive(:bits).and_return 32 + expect(Hardware::CPU.can_run?(:x86_64)).to be false + end + + it "reports that 32-bit PowerPC machines can't run ppc64 executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + allow(Hardware::CPU).to receive(:bits).and_return 32 + expect(Hardware::CPU.can_run?(:ppc64)).to be false + end + + it "identifies that Intel and PowerPC machines can't run each others' executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + expect(Hardware::CPU.can_run?(:i386)).to be false + expect(Hardware::CPU.can_run?(:x86_64)).to be false + + allow(Hardware::CPU).to receive(:type).and_return :intel + expect(Hardware::CPU.can_run?(:ppc)).to be false + expect(Hardware::CPU.can_run?(:ppc64)).to be false + end + + it "returns false for unknown CPU types" do + allow(Hardware::CPU).to receive(:type).and_return :dunno + expect(Hardware::CPU.can_run?(:i386)).to be false + end + + it "returns false for unknown arches" do + expect(Hardware::CPU.can_run?(:blah)).to be false + end + end end end diff --git a/Library/Homebrew/test/missing_formula_spec.rb b/Library/Homebrew/test/missing_formula_spec.rb index 215cf17f7..69bb3e70d 100644 --- a/Library/Homebrew/test/missing_formula_spec.rb +++ b/Library/Homebrew/test/missing_formula_spec.rb @@ -139,7 +139,7 @@ describe Homebrew::MissingFormula do end context "::deleted_reason" do - subject { described_class.deleted_reason(formula) } + subject { described_class.deleted_reason(formula, silent: true) } before do Tap.clear_cache diff --git a/Library/Homebrew/test/os/linux/osxfuse_requirement_spec.rb b/Library/Homebrew/test/os/linux/osxfuse_requirement_spec.rb new file mode 100644 index 000000000..c45af2fa7 --- /dev/null +++ b/Library/Homebrew/test/os/linux/osxfuse_requirement_spec.rb @@ -0,0 +1,9 @@ +require "requirements/osxfuse_requirement" + +describe OsxfuseRequirement do + subject { described_class.new([]) } + + describe "#message" do + its(:message) { is_expected.to match("libfuse is required to install this formula") } + end +end diff --git a/Library/Homebrew/test/os/mac/hardware_spec.rb b/Library/Homebrew/test/os/mac/hardware_spec.rb new file mode 100644 index 000000000..fa577ba7d --- /dev/null +++ b/Library/Homebrew/test/os/mac/hardware_spec.rb @@ -0,0 +1,56 @@ +require "hardware" +require "extend/os/mac/hardware/cpu" + +describe Hardware::CPU do + describe "::can_run?" do + it "reports that Intel Macs can run Intel executables" do + allow(Hardware::CPU).to receive(:type).and_return :intel + allow(Hardware::CPU).to receive(:bits).and_return 64 + expect(Hardware::CPU.can_run?(:i386)).to be true + expect(Hardware::CPU.can_run?(:x86_64)).to be true + end + + it "reports that PowerPC Macs can run PowerPC executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + allow(Hardware::CPU).to receive(:bits).and_return 64 + expect(Hardware::CPU.can_run?(:ppc)).to be true + expect(Hardware::CPU.can_run?(:ppc64)).to be true + end + + it "reports that 32-bit Intel Macs can't run x86_64 executables" do + allow(Hardware::CPU).to receive(:type).and_return :intel + allow(Hardware::CPU).to receive(:bits).and_return 32 + expect(Hardware::CPU.can_run?(:x86_64)).to be false + end + + it "reports that 32-bit PowerPC Macs can't run ppc64 executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + allow(Hardware::CPU).to receive(:bits).and_return 32 + expect(Hardware::CPU.can_run?(:ppc64)).to be false + end + + it "reports that Intel Macs can only run 32-bit PowerPC executables on 10.6 and older" do + allow(Hardware::CPU).to receive(:type).and_return :intel + allow(OS::Mac).to receive(:version).and_return OS::Mac::Version.new "10.6" + expect(Hardware::CPU.can_run?(:ppc)).to be true + + allow(OS::Mac).to receive(:version).and_return OS::Mac::Version.new "10.7" + expect(Hardware::CPU.can_run?(:ppc)).to be false + end + + it "reports that PowerPC Macs can't run Intel executables" do + allow(Hardware::CPU).to receive(:type).and_return :ppc + expect(Hardware::CPU.can_run?(:i386)).to be false + expect(Hardware::CPU.can_run?(:x86_64)).to be false + end + + it "returns false for unknown CPU types" do + allow(Hardware::CPU).to receive(:type).and_return :dunno + expect(Hardware::CPU.can_run?(:i386)).to be false + end + + it "returns false for unknown arches" do + expect(Hardware::CPU.can_run?(:blah)).to be false + end + end +end diff --git a/Library/Homebrew/test/os/mac/osxfuse_requirement_spec.rb b/Library/Homebrew/test/os/mac/osxfuse_requirement_spec.rb new file mode 100644 index 000000000..06d3d885e --- /dev/null +++ b/Library/Homebrew/test/os/mac/osxfuse_requirement_spec.rb @@ -0,0 +1,36 @@ +require "requirements/osxfuse_requirement" + +describe OsxfuseRequirement do + subject { described_class.new([]) } + + describe "::binary_osxfuse_installed?" do + it "returns false if fuse.h does not exist" do + allow(File).to receive(:exist?).and_return(false) + expect(described_class).not_to be_binary_osxfuse_installed + end + + it "returns false if osxfuse include directory is a symlink" do + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:symlink?).and_return(true) + expect(described_class).not_to be_binary_osxfuse_installed + end + end + + describe "environment" do + it "adds the fuse directories to the appropriate paths" do + expect(ENV).to receive(:append_path).with("PKG_CONFIG_PATH", any_args) + expect(ENV).to receive(:append_path).with("HOMEBREW_LIBRARY_PATHS", any_args) + expect(ENV).to receive(:append_path).with("HOMEBREW_INCLUDE_PATHS", any_args) + subject.modify_build_environment + end + end +end + +describe NonBinaryOsxfuseRequirement do + subject { described_class.new([]) } + + describe "#message" do + msg = /osxfuse is already installed from the binary distribution/ + its(:message) { is_expected.to match(msg) } + end +end diff --git a/Library/Homebrew/test/requirement_spec.rb b/Library/Homebrew/test/requirement_spec.rb index 110a7ac4f..959041cf4 100644 --- a/Library/Homebrew/test/requirement_spec.rb +++ b/Library/Homebrew/test/requirement_spec.rb @@ -146,17 +146,13 @@ describe Requirement do end describe "#build?" do - context "#build true is specified" do - let(:klass) do - Class.new(described_class) do - build true - end - end + context ":build tag is specified" do + subject { described_class.new([:build]) } it { is_expected.to be_a_build_requirement } end - context "#build ommitted" do + context "#build omitted" do it { is_expected.not_to be_a_build_requirement } end end diff --git a/Library/Homebrew/test/rubocops/bottle_block_cop_spec.rb b/Library/Homebrew/test/rubocops/bottle_block_cop_spec.rb index 5be2d6cf5..a775b0b17 100644 --- a/Library/Homebrew/test/rubocops/bottle_block_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/bottle_block_cop_spec.rb @@ -3,7 +3,7 @@ require "rubocop/rspec/support" require_relative "../../extend/string" require_relative "../../rubocops/bottle_block_cop" -describe RuboCop::Cop::Homebrew::CorrectBottleBlock do +describe RuboCop::Cop::FormulaAuditStrict::BottleBlock do subject(:cop) { described_class.new } context "When auditing Bottle Block" do diff --git a/Library/Homebrew/test/rubocops/components_order_cop_spec.rb b/Library/Homebrew/test/rubocops/components_order_cop_spec.rb new file mode 100644 index 000000000..25467c635 --- /dev/null +++ b/Library/Homebrew/test/rubocops/components_order_cop_spec.rb @@ -0,0 +1,163 @@ +require "rubocop" +require "rubocop/rspec/support" +require_relative "../../extend/string" +require_relative "../../rubocops/components_order_cop" + +describe RuboCop::Cop::FormulaAuditStrict::ComponentsOrder do + subject(:cop) { described_class.new } + + context "When auditing formula components order" do + it "When url precedes homepage" do + source = <<-EOS.undent + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + homepage "http://example.com" + end + EOS + + expected_offenses = [{ message: "`homepage` (line 3) should be put before `url` (line 2)", + severity: :convention, + line: 3, + 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 `resource` precedes `depends_on`" do + source = <<-EOS.undent + class Foo < Formula + url "https://example.com/foo-1.0.tgz" + + resource "foo2" do + url "https://example.com/foo-2.0.tgz" + end + + depends_on "openssl" + end + EOS + + expected_offenses = [{ message: "`depends_on` (line 8) should be put before `resource` (line 4)", + severity: :convention, + line: 8, + 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 `test` precedes `plist`" do + source = <<-EOS.undent + class Foo < Formula + url "https://example.com/foo-1.0.tgz" + + test do + expect(shell_output("./dogs")).to match("Dogs are terrific") + end + + def plist + end + end + EOS + + expected_offenses = [{ message: "`plist` (line 8) should be put before `test` (line 4)", + severity: :convention, + line: 8, + 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 only one of many `depends_on` precedes `conflicts_with`" do + source = <<-EOS.undent + class Foo < Formula + depends_on "autoconf" => :build + conflicts_with "visionmedia-watch" + depends_on "automake" => :build + depends_on "libtool" => :build + depends_on "pkg-config" => :build + depends_on "gettext" + end + EOS + + expected_offenses = [{ message: "`depends_on` (line 4) should be put before `conflicts_with` (line 3)", + severity: :convention, + line: 4, + column: 2, + source: source }] + + inspect_source(cop, source) + + expected_offenses.zip(cop.offenses).each do |expected, actual| + expect_offense(expected, actual) + end + end + + def expect_offense(expected, actual) + expect(actual.message).to eq(expected[:message]) + expect(actual.severity).to eq(expected[:severity]) + expect(actual.line).to eq(expected[:line]) + expect(actual.column).to eq(expected[:column]) + end + end + + context "When auditing formula components order with autocorrect" do + it "When url precedes homepage" do + source = <<-EOS.undent + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + homepage "http://example.com" + end + EOS + correct_source = <<-EOS.undent + class Foo < Formula + homepage "http://example.com" + url "http://example.com/foo-1.0.tgz" + end + EOS + + corrected_source = autocorrect_source(cop, source) + expect(corrected_source).to eq(correct_source) + end + + it "When `resource` precedes `depends_on`" do + source = <<-EOS.undent + class Foo < Formula + url "https://example.com/foo-1.0.tgz" + + resource "foo2" do + url "https://example.com/foo-2.0.tgz" + end + + depends_on "openssl" + end + EOS + correct_source = <<-EOS.undent + class Foo < Formula + url "https://example.com/foo-1.0.tgz" + + depends_on "openssl" + + resource "foo2" do + url "https://example.com/foo-2.0.tgz" + end + end + EOS + corrected_source = autocorrect_source(cop, source) + expect(corrected_source).to eq(correct_source) + end + end +end diff --git a/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb b/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb new file mode 100644 index 000000000..fd635a126 --- /dev/null +++ b/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb @@ -0,0 +1,87 @@ +require "rubocop" +require "rubocop/rspec/support" +require_relative "../../extend/string" +require_relative "../../rubocops/components_redundancy_cop" + +describe RuboCop::Cop::FormulaAuditStrict::ComponentsRedundancy do + subject(:cop) { described_class.new } + + context "When auditing formula components common errors" do + it "When url outside stable block" do + source = <<-EOS.undent + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + stable do + # stuff + end + end + EOS + + expected_offenses = [{ message: "`url` should be put inside `stable` block", + severity: :convention, + line: 2, + 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 both `head` and `head do` are present" do + source = <<-EOS.undent + class Foo < Formula + head "http://example.com/foo.git" + head do + # stuff + end + end + EOS + + expected_offenses = [{ message: "`head` and `head do` should not be simultaneously present", + severity: :convention, + line: 3, + 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 both `bottle :modifier` and `bottle do` are present" do + source = <<-EOS.undent + class Foo < Formula + url "http://example.com/foo-1.0.tgz" + bottle do + # bottles go here + end + bottle :unneeded + end + EOS + + expected_offenses = [{ message: "`bottle :modifier` and `bottle do` should not be simultaneously present", + severity: :convention, + line: 3, + column: 2, + source: source }] + + inspect_source(cop, source) + + expected_offenses.zip(cop.offenses).each do |expected, actual| + expect_offense(expected, actual) + end + end + + def expect_offense(expected, actual) + expect(actual.message).to eq(expected[:message]) + expect(actual.severity).to eq(expected[:severity]) + expect(actual.line).to eq(expected[:line]) + expect(actual.column).to eq(expected[:column]) + end + end +end diff --git a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb index 04c4c27da..432b15e3c 100644 --- a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb @@ -3,7 +3,7 @@ require "rubocop/rspec/support" require_relative "../../extend/string" require_relative "../../rubocops/formula_desc_cop" -describe RuboCop::Cop::Homebrew::FormulaDesc do +describe RuboCop::Cop::FormulaAuditStrict::Desc do subject(:cop) { described_class.new } context "When auditing formula desc" do @@ -51,6 +51,31 @@ describe RuboCop::Cop::Homebrew::FormulaDesc do end end + it "When desc is multiline string" do + source = <<-EOS.undent + class Foo < Formula + url 'http://example.com/foo-1.0.tgz' + desc '#{"bar"*10}'\ + '#{"foo"*21}' + end + EOS + + msg = <<-EOS.undent + Description is too long. "name: desc" should be less than 80 characters. + Length is calculated as Foo + desc. (currently 98) + EOS + expected_offenses = [{ message: msg, + severity: :convention, + line: 3, + 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 wrong \"command-line\" usage in desc" do source = <<-EOS.undent class Foo < Formula diff --git a/Library/Homebrew/test/rubocops/homepage_cop_spec.rb b/Library/Homebrew/test/rubocops/homepage_cop_spec.rb new file mode 100644 index 000000000..c03efd825 --- /dev/null +++ b/Library/Homebrew/test/rubocops/homepage_cop_spec.rb @@ -0,0 +1,124 @@ +require "rubocop" +require "rubocop/rspec/support" +require_relative "../../extend/string" +require_relative "../../rubocops/homepage_cop" + +describe RuboCop::Cop::FormulaAudit::Homepage do + subject(:cop) { described_class.new } + + context "When auditing homepage" do + it "When there is no homepage" do + source = <<-EOS.undent + class Foo < Formula + url 'http://example.com/foo-1.0.tgz' + end + EOS + + expected_offenses = [{ message: "Formula should have a homepage.", + severity: :convention, + line: 1, + column: 0, + source: source }] + + inspect_source(cop, source) + + expected_offenses.zip(cop.offenses).each do |expected, actual| + expect_offense(expected, actual) + end + end + + it "Homepage with ftp" do + source = <<-EOS.undent + class Foo < Formula + homepage "ftp://example.com/foo" + url "http://example.com/foo-1.0.tgz" + end + EOS + + expected_offenses = [{ message: "The homepage should start with http or https (URL is ftp://example.com/foo).", + severity: :convention, + line: 2, + column: 2, + source: source }] + + inspect_source(cop, source) + + expected_offenses.zip(cop.offenses).each do |expected, actual| + expect_offense(expected, actual) + end + end + + it "Homepage URLs" do + formula_homepages = { + "bar" => "http://www.freedesktop.org/wiki/bar", + "baz" => "http://www.freedesktop.org/wiki/Software/baz", + "qux" => "https://code.google.com/p/qux", + "quux" => "http://github.com/quux", + "corge" => "http://savannah.nongnu.org/corge", + "grault" => "http://grault.github.io/", + "garply" => "http://www.gnome.org/garply", + "sf1" => "http://foo.sourceforge.net/", + "sf2" => "http://foo.sourceforge.net", + "sf3" => "http://foo.sf.net/", + "sf4" => "http://foo.sourceforge.io/", + "waldo" => "http://www.gnu.org/waldo", + } + + formula_homepages.each do |name, homepage| + source = <<-EOS.undent + class #{name.capitalize} < Formula + homepage "#{homepage}" + url "http://example.com/#{name}-1.0.tgz" + end + EOS + + inspect_source(cop, source) + if homepage =~ %r{http:\/\/www\.freedesktop\.org} + if homepage =~ /Software/ + expected_offenses = [{ message: "#{homepage} should be styled " \ + "`https://wiki.freedesktop.org/www/Software/project_name`", + severity: :convention, + line: 2, + column: 2, + source: source }] + else + expected_offenses = [{ message: "#{homepage} should be styled " \ + "`https://wiki.freedesktop.org/project_name`", + severity: :convention, + line: 2, + column: 2, + source: source }] + end + elsif homepage =~ %r{https:\/\/code\.google\.com} + expected_offenses = [{ message: "#{homepage} should end with a slash", + severity: :convention, + line: 2, + column: 2, + source: source }] + elsif homepage =~ /foo\.(sf|sourceforge)\.net/ + expected_offenses = [{ message: "#{homepage} should be `https://foo.sourceforge.io/`", + severity: :convention, + line: 2, + column: 2, + source: source }] + else + expected_offenses = [{ message: "Please use https:// for #{homepage}", + severity: :convention, + line: 2, + column: 2, + source: source }] + end + expected_offenses.zip([cop.offenses.last]).each do |expected, actual| + expect_offense(expected, actual) + end + end + end + + def expect_offense(expected, actual) + expect(actual.message).to eq(expected[:message]) + expect(actual.severity).to eq(expected[:severity]) + expect(actual.line).to eq(expected[:line]) + expect(actual.column).to eq(expected[:column]) + end + end +end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 2f6274fd1..e193b91a0 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -61,6 +61,10 @@ RSpec.configure do |config| skip "Python not installed." unless which("python") end + config.before(:each, :needs_network) do + skip "Requires network connection." unless ENV["HOMEBREW_TEST_ONLINE"] + end + config.around(:each) do |example| begin TEST_DIRECTORIES.each(&:mkpath) @@ -89,6 +93,7 @@ RSpec.configure do |config| HOMEBREW_PREFIX/"opt", HOMEBREW_PREFIX/"Caskroom", HOMEBREW_LIBRARY/"Taps/caskroom", + HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bar", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-bundle", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-foo", HOMEBREW_LIBRARY/"Taps/homebrew/homebrew-services", diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb new file mode 100644 index 000000000..4bd2f0882 --- /dev/null +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-non-executable-binary.rb @@ -0,0 +1,9 @@ +cask 'with-non-executable-binary' do + version '1.2.3' + sha256 'd5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2' + + url "file://#{TEST_FIXTURE_DIR}/cask/naked_non_executable" + homepage 'http://example.com/with-binary' + + binary "naked_non_executable" +end diff --git a/Library/Homebrew/test/support/fixtures/cask/naked_non_executable b/Library/Homebrew/test/support/fixtures/cask/naked_non_executable new file mode 100644 index 000000000..039e4d006 --- /dev/null +++ b/Library/Homebrew/test/support/fixtures/cask/naked_non_executable @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 diff --git a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb index b037068d2..ae1854f58 100644 --- a/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb +++ b/Library/Homebrew/test/support/helper/spec/shared_context/integration_test.rb @@ -72,6 +72,7 @@ RSpec.shared_context "integration test" do env.merge!( "PATH" => path, + "HOMEBREW_PATH" => path, "HOMEBREW_BREW_FILE" => HOMEBREW_PREFIX/"bin/brew", "HOMEBREW_INTEGRATION_TEST" => command_id_from_args(args), "HOMEBREW_TEST_TMPDIR" => TEST_TMPDIR, diff --git a/Library/Homebrew/test/utils/github_spec.rb b/Library/Homebrew/test/utils/github_spec.rb new file mode 100644 index 000000000..9b539262f --- /dev/null +++ b/Library/Homebrew/test/utils/github_spec.rb @@ -0,0 +1,13 @@ +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") + + expect(results.count).to eq(1) + expect(results.first["name"]).to eq("README.md") + expect(results.first["path"]).to eq("README.md") + end + end +end diff --git a/Library/Homebrew/test/utils/shell_spec.rb b/Library/Homebrew/test/utils/shell_spec.rb index c44bd8253..d32f9928f 100644 --- a/Library/Homebrew/test/utils/shell_spec.rb +++ b/Library/Homebrew/test/utils/shell_spec.rb @@ -1,92 +1,92 @@ require "utils/shell" describe Utils::Shell do - describe "::shell_profile" do + describe "::profile" do it "returns ~/.bash_profile by default" do ENV["SHELL"] = "/bin/another_shell" - expect(subject.shell_profile).to eq("~/.bash_profile") + expect(subject.profile).to eq("~/.bash_profile") end it "returns ~/.bash_profile for Sh" do ENV["SHELL"] = "/bin/another_shell" - expect(subject.shell_profile).to eq("~/.bash_profile") + expect(subject.profile).to eq("~/.bash_profile") end it "returns ~/.bash_profile for Bash" do ENV["SHELL"] = "/bin/bash" - expect(subject.shell_profile).to eq("~/.bash_profile") + expect(subject.profile).to eq("~/.bash_profile") end it "returns ~/.zshrc for Zsh" do ENV["SHELL"] = "/bin/zsh" - expect(subject.shell_profile).to eq("~/.zshrc") + expect(subject.profile).to eq("~/.zshrc") end it "returns ~/.kshrc for Ksh" do ENV["SHELL"] = "/bin/ksh" - expect(subject.shell_profile).to eq("~/.kshrc") + expect(subject.profile).to eq("~/.kshrc") end end - describe "::path_to_shell" do + describe "::from_path" do it "supports a raw command name" do - expect(subject.path_to_shell("bash")).to eq(:bash) + expect(subject.from_path("bash")).to eq(:bash) end it "supports full paths" do - expect(subject.path_to_shell("/bin/bash")).to eq(:bash) + expect(subject.from_path("/bin/bash")).to eq(:bash) end it "supports versions" do - expect(subject.path_to_shell("zsh-5.2")).to eq(:zsh) + expect(subject.from_path("zsh-5.2")).to eq(:zsh) end it "strips newlines" do - expect(subject.path_to_shell("zsh-5.2\n")).to eq(:zsh) + expect(subject.from_path("zsh-5.2\n")).to eq(:zsh) end it "returns nil when input is invalid" do - expect(subject.path_to_shell("")).to be nil - expect(subject.path_to_shell("@@@@@@")).to be nil - expect(subject.path_to_shell("invalid_shell-4.2")).to be nil + expect(subject.from_path("")).to be nil + expect(subject.from_path("@@@@@@")).to be nil + expect(subject.from_path("invalid_shell-4.2")).to be nil end end specify "::sh_quote" do - expect(subject.sh_quote("")).to eq("''") - expect(subject.sh_quote("\\")).to eq("\\\\") - expect(subject.sh_quote("\n")).to eq("'\n'") - expect(subject.sh_quote("$")).to eq("\\$") - expect(subject.sh_quote("word")).to eq("word") + expect(subject.send(:sh_quote, "")).to eq("''") + expect(subject.send(:sh_quote, "\\")).to eq("\\\\") + expect(subject.send(:sh_quote, "\n")).to eq("'\n'") + expect(subject.send(:sh_quote, "$")).to eq("\\$") + expect(subject.send(:sh_quote, "word")).to eq("word") end specify "::csh_quote" do - expect(subject.csh_quote("")).to eq("''") - expect(subject.csh_quote("\\")).to eq("\\\\") + expect(subject.send(:csh_quote, "")).to eq("''") + expect(subject.send(:csh_quote, "\\")).to eq("\\\\") # note this test is different than for sh - expect(subject.csh_quote("\n")).to eq("'\\\n'") - expect(subject.csh_quote("$")).to eq("\\$") - expect(subject.csh_quote("word")).to eq("word") + expect(subject.send(:csh_quote, "\n")).to eq("'\\\n'") + expect(subject.send(:csh_quote, "$")).to eq("\\$") + expect(subject.send(:csh_quote, "word")).to eq("word") end - describe "::prepend_path_in_shell_profile" do + describe "::prepend_path_in_profile" do let(:path) { "/my/path" } it "supports Tcsh" do ENV["SHELL"] = "/bin/tcsh" - expect(subject.prepend_path_in_shell_profile(path)) + expect(subject.prepend_path_in_profile(path)) .to start_with("echo 'setenv PATH #{path}:$") end it "supports Bash" do ENV["SHELL"] = "/bin/bash" - expect(subject.prepend_path_in_shell_profile(path)) + expect(subject.prepend_path_in_profile(path)) .to start_with("echo 'export PATH=\"#{path}:$") end it "supports Fish" do ENV["SHELL"] = "/usr/local/bin/fish" - expect(subject.prepend_path_in_shell_profile(path)) + expect(subject.prepend_path_in_profile(path)) .to start_with("echo 'set -g fish_user_paths \"#{path}\" $fish_user_paths' >>") end end diff --git a/Library/Homebrew/test/utils_spec.rb b/Library/Homebrew/test/utils_spec.rb index 314c299a8..be224990a 100644 --- a/Library/Homebrew/test/utils_spec.rb +++ b/Library/Homebrew/test/utils_spec.rb @@ -189,7 +189,13 @@ describe "globally-scoped helper methods" do specify "#which_editor" do ENV["HOMEBREW_EDITOR"] = "vemate" - expect(which_editor).to eq("vemate") + ENV["HOMEBREW_PATH"] = dir + + editor = "#{dir}/vemate" + FileUtils.touch editor + FileUtils.chmod 0755, editor + + expect(which_editor).to eql editor end specify "#gzip" do diff --git a/Library/Homebrew/test/version_spec.rb b/Library/Homebrew/test/version_spec.rb index d3d63a25c..41e05019c 100644 --- a/Library/Homebrew/test/version_spec.rb +++ b/Library/Homebrew/test/version_spec.rb @@ -433,6 +433,11 @@ describe Version do .to be_detected_from("https://homebrew.bintray.com/bottles/imagemagick-6.7.5-7.lion.bottle.1.tar.gz") end + specify "date-based version style" do + expect(Version.create("2017-04-17")) + .to be_detected_from("https://example.com/dada-v2017-04-17.tar.gz") + end + specify "dash version style" do expect(Version.create("3.4")) .to be_detected_from("http://www.antlr.org/download/antlr-3.4-complete.jar") diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index 46a8cc68e..28d7fc283 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -190,10 +190,10 @@ module Homebrew Gem::Specification.reset # Add Gem binary directory and (if missing) Ruby binary directory to PATH. - path = ENV["PATH"].split(File::PATH_SEPARATOR) - path.unshift(RUBY_BIN) if which("ruby") != RUBY_PATH - path.unshift(Gem.bindir) - ENV["PATH"] = path.join(File::PATH_SEPARATOR) + path = PATH.new(ENV["PATH"]) + path.prepend(RUBY_BIN) if which("ruby") != RUBY_PATH + path.prepend(Gem.bindir) + ENV["PATH"] = path if Gem::Specification.find_all_by_name(name, version).empty? ohai "Installing or updating '#{name}' gem" @@ -293,7 +293,7 @@ def quiet_system(cmd, *args) end def which(cmd, path = ENV["PATH"]) - path.split(File::PATH_SEPARATOR).each do |p| + PATH.new(path).each do |p| begin pcmd = File.expand_path(cmd, p) rescue ArgumentError @@ -307,7 +307,7 @@ def which(cmd, path = ENV["PATH"]) end def which_all(cmd, path = ENV["PATH"]) - path.to_s.split(File::PATH_SEPARATOR).map do |p| + PATH.new(path).map do |p| begin pcmd = File.expand_path(cmd, p) rescue ArgumentError @@ -320,25 +320,34 @@ def which_all(cmd, path = ENV["PATH"]) end def which_editor - editor = ENV.values_at("HOMEBREW_EDITOR", "VISUAL", "EDITOR").compact.first - return editor unless editor.nil? + editor = ENV.values_at("HOMEBREW_EDITOR", "HOMEBREW_VISUAL").compact.reject(&:empty?).first + if editor + editor_name, _, editor_args = editor.partition " " + editor_path = which(editor_name, ENV["HOMEBREW_PATH"]) + editor = if editor_args.to_s.empty? + editor_path.to_s + else + "#{editor_path} #{editor_args}" + end + return editor + end # Find Textmate - editor = "mate" if which "mate" + editor = which("mate", ENV["HOMEBREW_PATH"]) # Find BBEdit / TextWrangler - editor ||= "edit" if which "edit" + editor ||= which("edit", ENV["HOMEBREW_PATH"]) # Find vim - editor ||= "vim" if which "vim" + editor ||= which("vim", ENV["HOMEBREW_PATH"]) # Default to standard vim editor ||= "/usr/bin/vim" opoo <<-EOS.undent Using #{editor} because no editor was set in the environment. - This may change in the future, so we recommend setting EDITOR, VISUAL, + This may change in the future, so we recommend setting EDITOR, or HOMEBREW_EDITOR to your preferred text editor. EOS - editor + editor.to_s end def exec_editor(*args) @@ -347,7 +356,7 @@ def exec_editor(*args) end def exec_browser(*args) - browser = ENV["HOMEBREW_BROWSER"] || ENV["BROWSER"] + browser = ENV["HOMEBREW_BROWSER"] browser ||= OS::PATH_OPEN if defined?(OS::PATH_OPEN) return unless browser safe_exec(browser, *args) @@ -406,8 +415,8 @@ def nostdout end end -def paths - @paths ||= ENV["PATH"].split(File::PATH_SEPARATOR).collect do |p| +def paths(env_path = ENV["PATH"]) + @paths ||= PATH.new(env_path).collect do |p| begin File.expand_path(p).chomp("/") rescue ArgumentError diff --git a/Library/Homebrew/utils/analytics.sh b/Library/Homebrew/utils/analytics.sh index 35f91eabc..8d5cf2ff7 100644 --- a/Library/Homebrew/utils/analytics.sh +++ b/Library/Homebrew/utils/analytics.sh @@ -85,8 +85,6 @@ report-analytics-screenview-command() { fi # Don't report commands used mostly by our scripts and not users. - # TODO: list more e.g. shell completion things here perhaps using a single - # script as a shell-completion entry point. case "$HOMEBREW_COMMAND" in --prefix|analytics|command|commands) return diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index a5ed5394a..88c5199c2 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -4,7 +4,7 @@ require "tempfile" module GitHub module_function - ISSUES_URI = URI.parse("https://api.github.com/search/issues") + API_URL = "https://api.github.com".freeze CREATE_GIST_SCOPES = ["gist"].freeze CREATE_ISSUE_SCOPES = ["public_repo"].freeze @@ -228,13 +228,19 @@ module GitHub end def issues_matching(query, qualifiers = {}) - uri = ISSUES_URI.dup + uri = URI.parse("#{API_URL}/search/issues") uri.query = build_query_string(query, qualifiers) open(uri) { |json| json["items"] } end def repository(user, repo) - open(URI.parse("https://api.github.com/repos/#{user}/#{repo}")) { |j| j } + open(URI.parse("#{API_URL}/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) @@ -286,7 +292,7 @@ module GitHub end def private_repo?(user, repo) - uri = URI.parse("https://api.github.com/repos/#{user}/#{repo}") + uri = URI.parse("#{API_URL}/repos/#{user}/#{repo}") open(uri) { |json| json["private"] } end end diff --git a/Library/Homebrew/utils/shell.rb b/Library/Homebrew/utils/shell.rb index 302167d47..5327f6ecf 100644 --- a/Library/Homebrew/utils/shell.rb +++ b/Library/Homebrew/utils/shell.rb @@ -1,62 +1,24 @@ module Utils - SHELL_PROFILE_MAP = { - bash: "~/.bash_profile", - csh: "~/.cshrc", - fish: "~/.config/fish/config.fish", - ksh: "~/.kshrc", - sh: "~/.bash_profile", - tcsh: "~/.tcshrc", - zsh: "~/.zshrc", - }.freeze - module Shell - UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@\n])} + module_function # take a path and heuristically convert it # to a shell name, return nil if there's no match - def path_to_shell(path) + def from_path(path) # we only care about the basename shell_name = File.basename(path) # handle possible version suffix like `zsh-5.2` shell_name.sub!(/-.*\z/m, "") shell_name.to_sym if %w[bash csh fish ksh sh tcsh zsh].include?(shell_name) end - module_function :path_to_shell - - def preferred_shell - path_to_shell(ENV.fetch("SHELL", "")) - end - module_function :preferred_shell - def parent_shell - path_to_shell(`ps -p #{Process.ppid} -o ucomm=`.strip) + def preferred + from_path(ENV.fetch("SHELL", "")) end - module_function :parent_shell - def csh_quote(str) - # ruby's implementation of shell_escape - str = str.to_s - return "''" if str.empty? - str = str.dup - # anything that isn't a known safe character is padded - str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1") - # newlines have to be specially quoted in csh - str.gsub!(/\n/, "'\\\n'") - str + def parent + from_path(`ps -p #{Process.ppid} -o ucomm=`.strip) end - module_function :csh_quote - - def sh_quote(str) - # ruby's implementation of shell_escape - str = str.to_s - return "''" if str.empty? - str = str.dup - # anything that isn't a known safe character is padded - str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1") - str.gsub!(/\n/, "'\n'") - str - end - module_function :sh_quote # quote values. quoting keys is overkill def export_value(shell, key, value) @@ -72,24 +34,60 @@ module Utils "setenv #{key} #{csh_quote(value)};" end end - module_function :export_value # return the shell profile file based on users' preferred shell - def shell_profile - SHELL_PROFILE_MAP.fetch(preferred_shell, "~/.bash_profile") + def profile + SHELL_PROFILE_MAP.fetch(preferred, "~/.bash_profile") end - module_function :shell_profile - def prepend_path_in_shell_profile(path) - case preferred_shell + def prepend_path_in_profile(path) + case preferred when :bash, :ksh, :sh, :zsh, nil - "echo 'export PATH=\"#{sh_quote(path)}:$PATH\"' >> #{shell_profile}" + "echo 'export PATH=\"#{sh_quote(path)}:$PATH\"' >> #{profile}" when :csh, :tcsh - "echo 'setenv PATH #{csh_quote(path)}:$PATH' >> #{shell_profile}" + "echo 'setenv PATH #{csh_quote(path)}:$PATH' >> #{profile}" when :fish - "echo 'set -g fish_user_paths \"#{sh_quote(path)}\" $fish_user_paths' >> #{shell_profile}" + "echo 'set -g fish_user_paths \"#{sh_quote(path)}\" $fish_user_paths' >> #{profile}" end end - module_function :prepend_path_in_shell_profile + + private + + SHELL_PROFILE_MAP = { + bash: "~/.bash_profile", + csh: "~/.cshrc", + fish: "~/.config/fish/config.fish", + ksh: "~/.kshrc", + sh: "~/.bash_profile", + tcsh: "~/.tcshrc", + zsh: "~/.zshrc", + }.freeze + + UNSAFE_SHELL_CHAR = %r{([^A-Za-z0-9_\-.,:/@\n])} + + module_function + + def csh_quote(str) + # ruby's implementation of shell_escape + str = str.to_s + return "''" if str.empty? + str = str.dup + # anything that isn't a known safe character is padded + str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1") + # newlines have to be specially quoted in csh + str.gsub!(/\n/, "'\\\n'") + str + end + + def sh_quote(str) + # ruby's implementation of shell_escape + str = str.to_s + return "''" if str.empty? + str = str.dup + # anything that isn't a known safe character is padded + str.gsub!(UNSAFE_SHELL_CHAR, "\\\\" + "\\1") + str.gsub!(/\n/, "'\n'") + str + end end end diff --git a/Library/Homebrew/vendor/README.md b/Library/Homebrew/vendor/README.md index 906d42918..b408631c7 100644 --- a/Library/Homebrew/vendor/README.md +++ b/Library/Homebrew/vendor/README.md @@ -1,7 +1,7 @@ Vendored Dependencies ===================== -* [plist](https://github.com/bleything/plist), version 3.1.0 +* [plist](https://github.com/patsplat/plist), version 3.3.0 * [ruby-macho](https://github.com/Homebrew/ruby-macho), version 1.1.0 diff --git a/Library/Homebrew/vendor/plist/plist.rb b/Library/Homebrew/vendor/plist/plist.rb index 0b828afc6..82ecb27d2 100755 --- a/Library/Homebrew/vendor/plist/plist.rb +++ b/Library/Homebrew/vendor/plist/plist.rb @@ -1,5 +1,5 @@ -#!/usr/bin/env ruby -# +# encoding: utf-8 + # = plist # # This is the main file for plist. Everything interesting happens in @@ -15,7 +15,7 @@ require 'stringio' require_relative 'plist/generator' require_relative 'plist/parser' +require_relative 'plist/version' module Plist - VERSION = '3.1.0' end diff --git a/Library/Homebrew/vendor/plist/plist/generator.rb b/Library/Homebrew/vendor/plist/plist/generator.rb index 3b84c301f..84bef3aaf 100644..100755 --- a/Library/Homebrew/vendor/plist/plist/generator.rb +++ b/Library/Homebrew/vendor/plist/plist/generator.rb @@ -1,12 +1,12 @@ -#!/usr/bin/env ruby -# +# encoding: utf-8 + # = plist # # Copyright 2006-2010 Ben Bleything and Patrick May # Distributed under the MIT License # -module Plist ; end +module Plist; end # === Create a plist # You can dump an object to a plist in one of two ways: @@ -94,7 +94,7 @@ module Plist::Emit output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) when Date # also catches DateTime output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) - when String, Symbol, Fixnum, Bignum, Integer, Float + when String, Symbol, Integer, Float output << tag(element_type(element), CGI::escapeHTML(element.to_s)) when IO, StringIO element.rewind @@ -159,7 +159,7 @@ module Plist::Emit when String, Symbol 'string' - when Fixnum, Bignum, Integer + when Integer 'integer' when Float diff --git a/Library/Homebrew/vendor/plist/plist/parser.rb b/Library/Homebrew/vendor/plist/plist/parser.rb index de441fcc5..4de13f881 100644..100755 --- a/Library/Homebrew/vendor/plist/plist/parser.rb +++ b/Library/Homebrew/vendor/plist/plist/parser.rb @@ -1,5 +1,5 @@ -#!/usr/bin/env ruby -# +# encoding: utf-8 + # = plist # # Copyright 2006-2010 Ben Bleything and Patrick May @@ -73,10 +73,10 @@ module Plist end TEXT = /([^<]+)/ - XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um - DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/um - COMMENT_START = /\A<!--/u - COMMENT_END = /.*?-->/um + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/m + DOCTYPE_PATTERN = /\s*<!DOCTYPE\s+(.*?)(\[|>)/m + COMMENT_START = /\A<!--/ + COMMENT_END = /.*?-->/m def parse @@ -91,7 +91,14 @@ module Plist if @scanner.scan(COMMENT_START) @scanner.scan(COMMENT_END) elsif @scanner.scan(XMLDECL_PATTERN) + encoding = parse_encoding_from_xml_declaration(@scanner[1]) + next if encoding.nil? + + # use the specified encoding for the rest of the file + next unless String.method_defined?(:force_encoding) + @scanner.string = @scanner.rest.force_encoding(encoding) elsif @scanner.scan(DOCTYPE_PATTERN) + next elsif @scanner.scan(start_tag) @listener.tag_start(@scanner[1], nil) if (@scanner[2] =~ /\/$/) @@ -106,6 +113,22 @@ module Plist end end end + + private + + def parse_encoding_from_xml_declaration(xml_declaration) + return unless defined?(Encoding) + + xml_encoding = xml_declaration.match(/(?:\A|\s)encoding=(?:"(.*?)"|'(.*?)')(?:\s|\Z)/) + + return if xml_encoding.nil? + + begin + Encoding.find(xml_encoding[1]) + rescue ArgumentError + nil + end + end end class PTag @@ -213,7 +236,7 @@ module Plist data = Base64.decode64(text.gsub(/\s+/, '')) unless text.nil? begin return Marshal.load(data) - rescue Exception => e + rescue Exception io = StringIO.new io.write data io.rewind diff --git a/Library/Homebrew/vendor/plist/plist/version.rb b/Library/Homebrew/vendor/plist/plist/version.rb new file mode 100755 index 000000000..80b1f73dd --- /dev/null +++ b/Library/Homebrew/vendor/plist/plist/version.rb @@ -0,0 +1,5 @@ +# encoding: utf-8 + +module Plist + VERSION = '3.3.0'.freeze +end diff --git a/Library/Homebrew/version.rb b/Library/Homebrew/version.rb index b9f512a50..da239f788 100644 --- a/Library/Homebrew/version.rb +++ b/Library/Homebrew/version.rb @@ -316,6 +316,11 @@ class Version spec.stem end + # date-based versioning + # e.g. ltopers-v2017-04-14.tar.gz + m = /-v?(\d{4}-\d{2}-\d{2})/.match(stem) + return m.captures.first unless m.nil? + # GitHub tarballs # e.g. https://github.com/foo/bar/tarball/v1.2.3 # e.g. https://github.com/sam-github/libnet/tarball/libnet-1.1.4 |
