Top Level Namespace

Defined Under Namespace

Modules: ArchitectureListExtension, Cachable, Cask, CleanupRefinement, Commands, Debrew, Dependable, DiskUsageExtension, ELFShim, Emoji, EnvActivation, Formatter, FormulaCellarChecks, FormulaClassUnavailableErrorModule, FormulaUnreadableErrorModule, Formulary, Git, GitHub, GitRepositoryExtension, Hardware, HashValidator, Homebrew, HomebrewArgvExtension, IRB, InstallRenamed, Kernel, Language, MachOShim, Metafiles, OS, Patch, Predicable, Readall, RuboCop, Searchable, SharedEnvExtension, StringInreplaceExtension, Superenv, Tty, UnpackStrategy, Utils Classes: AbstractDownloadStrategy, AbstractFileDownloadStrategy, ArchRequirement, BazaarDownloadStrategy, Bottle, BottleDisableReason, BottleFormulaUnavailableError, BottleSpecification, Build, BuildEnvironment, BuildError, BuildFlagsError, BuildOptions, BuildToolsError, CVSDownloadStrategy, CacheStore, CacheStoreDatabase, CannotInstallFormulaError, CaskLock, Caveats, Checksum, ChecksumMismatchError, ChecksumMissingError, ChildProcessError, Cleaner, CodesignRequirement, CompilerFailure, CompilerSelectionError, CompilerSelector, CoreTap, CurlApacheMirrorDownloadStrategy, CurlDownloadStrategy, CurlDownloadStrategyError, CurlPostDownloadStrategy, CxxStdlib, DATAPatch, Dependencies, Dependency, DependencyCollector, DeprecatedOption, DescriptionCacheStore, Descriptions, DownloadError, DownloadStrategyDetector, DuplicateResourceError, EmbeddedPatch, ErrorDuringExecution, ExternalPatch, Formula, FormulaAmbiguousPythonError, FormulaClassUnavailableError, FormulaConflict, FormulaConflictError, FormulaInstallationAlreadyAttemptedError, FormulaInstaller, FormulaLock, FormulaPin, FormulaSpecificationError, FormulaUnavailableError, FormulaUnreadableError, FormulaUnspecifiedError, FormulaValidationError, FormulaVersions, FossilDownloadStrategy, GitDownloadStrategy, GitHubGitDownloadStrategy, HeadSoftwareSpec, HeadVersion, IO, JavaRequirement, Keg, KegOnlyReason, KegUnspecifiedError, LazyObject, LegacyPatch, LinkageCacheStore, LinkageChecker, LinuxRequirement, LocalBottleDownloadStrategy, Locale, LockFile, MacOSRequirement, MaximumMacOSRequirement, MercurialDownloadStrategy, Messages, MethodDeprecatedError, Migrator, MissingApplyError, MissingEnvironmentVariables, Mktemp, Module, MultipleVersionsInstalledError, NilClass, NoSuchKegError, NoUnzipCurlDownloadStrategy, NotAKegError, OperationInProgressError, Option, Options, OsxfuseRequirement, PATH, Pathname, PkgVersion, PourBottleCheck, PrettyListing, Reporter, ReporterHub, Requirement, Requirements, Resource, ResourceMissingError, ResourceStageContext, Sandbox, SoftwareSpec, String, StringPatch, SubversionDownloadStrategy, Symbol, SystemCommand, SystemConfig, Tab, Tap, TapAlreadyTappedError, TapAlreadyUnshallowError, TapConfig, TapDependency, TapFormulaAmbiguityError, TapFormulaClassUnavailableError, TapFormulaUnavailableError, TapFormulaUnreadableError, TapFormulaWithOldnameAmbiguityError, TapPinStatusError, TapRemoteMismatchError, TapUnavailableError, TuntapRequirement, URL, UnsatisfiedRequirements, UsageError, User, VCSDownloadStrategy, Version, X11Requirement, XcodeRequirement

Constant Summary collapse

HOMEBREW_HELP =
<<~EOS
  Example usage:
    brew search [TEXT|/REGEX/]
    brew info [FORMULA...]
    brew install FORMULA...
    brew update
    brew upgrade [FORMULA...]
    brew uninstall FORMULA...
    brew list [FORMULA...]

  Troubleshooting:
    brew config
    brew doctor
    brew install --verbose --debug FORMULA

  Contributing:
    brew create [URL [--no-fetch]]
    brew edit [FORMULA...]

  Further help:
    brew commands
    brew help [COMMAND]
    man brew
    https://docs.brew.sh
EOS
TEST_TIMEOUT_SECONDS =
5 * 60
HOMEBREW_BREW_FILE =

Path to bin/brew main executable in HOMEBREW_PREFIX

Pathname.new(ENV["HOMEBREW_BREW_FILE"]).freeze
HOMEBREW_PREFIX =

Where we link under

Pathname.new(get_env_or_raise("HOMEBREW_PREFIX")).freeze
HOMEBREW_REPOSITORY =

Where .git is found

Pathname.new(get_env_or_raise("HOMEBREW_REPOSITORY"))
.extend(GitRepositoryExtension)
.freeze
HOMEBREW_LIBRARY =

Where we store most of Homebrew, taps, and various metadata

Pathname.new(get_env_or_raise("HOMEBREW_LIBRARY")).freeze
HOMEBREW_SHIMS_PATH =

Where shim scripts for various build and SCM tools are stored

(HOMEBREW_LIBRARY/"Homebrew/shims").freeze
HOMEBREW_LINKED_KEGS =

Where we store symlinks to currently linked kegs

(HOMEBREW_PREFIX/"var/homebrew/linked").freeze
HOMEBREW_PINNED_KEGS =

Where we store symlinks to currently version-pinned kegs

(HOMEBREW_PREFIX/"var/homebrew/pinned").freeze
HOMEBREW_LOCKS =

Where we store lock files

(HOMEBREW_PREFIX/"var/homebrew/locks").freeze
HOMEBREW_CELLAR =

Where we store built products

Pathname.new(get_env_or_raise("HOMEBREW_CELLAR")).freeze
HOMEBREW_CACHE =

Where downloads (bottles, source tarballs, etc.) are cached

Pathname.new(get_env_or_raise("HOMEBREW_CACHE")).freeze
HOMEBREW_CACHE_FORMULA =

Where brews installed via URL are cached

(HOMEBREW_CACHE/"Formula").freeze
HOMEBREW_LOGS =

Where build, postinstall, and test logs of formulae are written to

Pathname.new(get_env_or_raise("HOMEBREW_LOGS")).expand_path.freeze
HOMEBREW_TEMP =

Must use /tmp instead of TMPDIR because long paths break Unix domain sockets

begin
  tmp = Pathname.new(get_env_or_raise("HOMEBREW_TEMP"))
  tmp.mkpath unless tmp.exist?
  tmp.realpath
end.freeze
ARGV_WITHOUT_MONKEY_PATCHING =
ARGV.dup.freeze
HOMEBREW_PRODUCT =
ENV["HOMEBREW_PRODUCT"]
HOMEBREW_VERSION =
ENV["HOMEBREW_VERSION"]
HOMEBREW_WWW =
"https://brew.sh"
RUBY_PATH =
Pathname.new(RbConfig.ruby).freeze
RUBY_BIN =
RUBY_PATH.dirname.freeze
HOMEBREW_USER_AGENT_CURL =
ENV["HOMEBREW_USER_AGENT_CURL"]
HOMEBREW_USER_AGENT_RUBY =
"#{ENV["HOMEBREW_USER_AGENT"]} ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
HOMEBREW_USER_AGENT_FAKE_SAFARI =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 " \
"(KHTML, like Gecko) Version/10.0.3 Safari/602.4.8"
HOMEBREW_BOTTLE_DEFAULT_DOMAIN =
ENV["HOMEBREW_BOTTLE_DEFAULT_DOMAIN"]
HOMEBREW_BOTTLE_DOMAIN =
ENV["HOMEBREW_BOTTLE_DOMAIN"]
HOMEBREW_PULL_API_REGEX =
%r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze
HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
%r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze
ORIGINAL_PATHS =
PATH.new(ENV["HOMEBREW_PATH"]).map do |p|
  begin
    Pathname.new(p).expand_path
  rescue
    nil
  end
end.compact.freeze
HOMEBREW_INTERNAL_COMMAND_ALIASES =
{
  "ls"          => "list",
  "homepage"    => "home",
  "-S"          => "search",
  "up"          => "update",
  "ln"          => "link",
  "instal"      => "install", # gem does the same
  "uninstal"    => "uninstall",
  "rm"          => "uninstall",
  "remove"      => "uninstall",
  "configure"   => "diy",
  "abv"         => "info",
  "dr"          => "doctor",
  "--repo"      => "--repository",
  "environment" => "--env",
  "--config"    => "config",
  "-v"          => "--version",
}.freeze
CLEANUP_DEFAULT_DAYS =
30
CLEANUP_MAX_AGE_DAYS =
120
HOMEBREW_LIBRARY_PATH =
Pathname(__dir__).realpath.freeze
BUG_REPORTS_URL =
"https://github.com/Homebrew/homebrew-cask#reporting-bugs"
COMMAND_DESC_WIDTH =
80
OPTION_DESC_WIDTH =
43
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[
  apache
  binary
  completions
  devel-only
  dupes
  emacs
  fuse
  games
  gui
  head-only
  nginx
  php
  python
  science
  tex
  versions
  x11
].freeze
HOMEBREW_TAP_FORMULA_REGEX =

match taps’ formulae, e.g. someuser/sometap/someformula

%r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}.freeze
HOMEBREW_TAP_CASK_REGEX =

match taps’ casks, e.g. someuser/sometap/somecask

%r{^([\w-]+)/([\w-]+)/([a-z0-9\-]+)$}.freeze
HOMEBREW_TAP_DIR_REGEX =

match taps’ directory paths, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap

%r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}.freeze
HOMEBREW_TAP_PATH_REGEX =

match taps’ formula paths, e.g. HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula

Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{(?:/.*)?$}.source).freeze
HOMEBREW_CASK_TAP_CASK_REGEX =

match official taps’ casks, e.g. homebrew/cask/somecask or homebrew/cask-versions/somecask

%r{^(?:([Cc]askroom)/(cask|versions)|(homebrew)/(cask|cask-[\w-]+))/([\w+-.]+)$}.freeze
HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX =
/^(home|linux)brew-/.freeze
BOTTLE_ERB =
<<-EOS.freeze
  bottle do
    <% if !root_url.start_with?(HOMEBREW_BOTTLE_DEFAULT_DOMAIN) %>
    root_url "<%= root_url %>"
    <% end %>
    <% if ![Homebrew::DEFAULT_PREFIX, "/usr/local"].include?(prefix) %>
    prefix "<%= prefix %>"
    <% end %>
    <% if cellar.is_a? Symbol %>
    cellar :<%= cellar %>
    <% elsif ![Homebrew::DEFAULT_CELLAR, "/usr/local/Cellar"].include?(cellar) %>
    cellar "<%= cellar %>"
    <% end %>
    <% if rebuild.positive? %>
    rebuild <%= rebuild %>
    <% end %>
    <% checksums.each do |checksum_type, checksum_values| %>
    <% checksum_values.each do |checksum_value| %>
    <% checksum, macos = checksum_value.shift %>
    <%= checksum_type %> "<%= checksum %>" => :<%= macos %><%= "_or_later" if Homebrew.args.or_later? %>
    <% end %>
    <% end %>
  end
EOS
MAXIMUM_STRING_MATCHES =
100
PYTHON_VIRTUALENV_URL =
"https://files.pythonhosted.org/packages/53/c0" \
"/c7819f0bb2cf83e1b4b0d96c901b85191f598a7b534d297c2ef6dc80e2d3" \
"/virtualenv-16.6.0.tar.gz"
PYTHON_VIRTUALENV_SHA256 =
"99acaf1e35c7ccf9763db9ba2accbca2f4254d61d1912c5ee364f9cc4a8942a0"

Instance Method Summary collapse

Methods included from EnvActivation

activate_extensions!, clear_sensitive_environment!, with_build_environment

Methods included from HomebrewArgvExtension

bottle_arch, build_bottle?, build_formula_from_source?, build_from_source?, build_stable?, build_universal?, casks, cc, collect_build_flags, debug?, env, fetch_head?, flag?, flags_only, force?, force_bottle?, formulae, git?, homebrew_developer?, ignore_deps?, interactive?, keep_tmp?, kegs, named, no_sandbox?, options_only, quieter?, resolved_formulae, value, verbose?

Instance Method Details

#archs_for_command(cmd) ⇒ Object

Returns array of architectures that the given command or library is built for.



350
351
352
353
# File 'brew/Library/Homebrew/utils.rb', line 350

def archs_for_command(cmd)
  cmd = which(cmd) unless Pathname.new(cmd).absolute?
  Pathname.new(cmd).archs
end

#capture_stderrObject



364
365
366
367
368
369
370
371
# File 'brew/Library/Homebrew/utils.rb', line 364

def capture_stderr
  old = $stderr
  $stderr = StringIO.new
  yield
  $stderr.string
ensure
  $stderr = old
end

#command_help_lines(path) ⇒ Object



500
501
502
# File 'brew/Library/Homebrew/utils.rb', line 500

def command_help_lines(path)
  path.read.lines.grep(/^#:/).map { |line| line.slice(2..-1) }
end

#curl(*args) ⇒ Object



45
46
47
48
49
50
51
52
# File 'brew/Library/Homebrew/utils/curl.rb', line 45

def curl(*args)
  # SSL_CERT_FILE can be incorrectly set by users or portable-ruby and screw
  # with SSL downloads so unset it here.
  system_command! curl_executable,
                  args:         curl_args(*args),
                  print_stdout: true,
                  env:          { "SSL_CERT_FILE" => nil }
end

#curl_args(*extra_args, show_output: false, user_agent: :default) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'brew/Library/Homebrew/utils/curl.rb', line 16

def curl_args(*extra_args, show_output: false, user_agent: :default)
  args = []

  # do not load .curlrc unless requested (must be the first argument)
  args << "-q" unless ENV["HOMEBREW_CURLRC"]

  args << "--show-error"

  args << "--user-agent" << case user_agent
  when :browser, :fake
    HOMEBREW_USER_AGENT_FAKE_SAFARI
  when :default
    HOMEBREW_USER_AGENT_CURL
  else
    user_agent
  end

  unless show_output
    args << "--fail"
    args << "--progress-bar" unless ARGV.verbose?
    args << "--verbose" if ENV["HOMEBREW_CURL_VERBOSE"]
    args << "--silent" unless $stdout.tty?
  end

  args << "--retry" << ENV["HOMEBREW_CURL_RETRIES"] if ENV["HOMEBREW_CURL_RETRIES"]

  args + extra_args
end

#curl_check_http_content(url, user_agents: [:default], check_content: false, strict: false) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'brew/Library/Homebrew/utils/curl.rb', line 86

def curl_check_http_content(url, user_agents: [:default], check_content: false, strict: false)
  return unless url.start_with? "http"

  details = nil
  user_agent = nil
  hash_needed = url.start_with?("http:")
  user_agents.each do |ua|
    details = curl_http_content_headers_and_checksum(url, hash_needed: hash_needed, user_agent: ua)
    user_agent = ua
    break if details[:status].to_s.start_with?("2")
  end

  unless details[:status]
    # Hack around https://github.com/Homebrew/brew/issues/3199
    return if MacOS.version == :el_capitan

    return "The URL #{url} is not reachable"
  end

  unless details[:status].start_with? "2"
    return "The URL #{url} is not reachable (HTTP status code #{details[:status]})"
  end

  if url.start_with?("https://") && ENV["HOMEBREW_NO_INSECURE_REDIRECT"] &&
     !details[:final_url].start_with?("https://")
    return "The URL #{url} redirects back to HTTP"
  end

  return unless hash_needed

  secure_url = url.sub "http", "https"
  secure_details =
    curl_http_content_headers_and_checksum(secure_url, hash_needed: true, user_agent: user_agent)

  if !details[:status].to_s.start_with?("2") ||
     !secure_details[:status].to_s.start_with?("2")
    return
  end

  etag_match = details[:etag] &&
               details[:etag] == secure_details[:etag]
  content_length_match =
    details[:content_length] &&
    details[:content_length] == secure_details[:content_length]
  file_match = details[:file_hash] == secure_details[:file_hash]

  if (etag_match || content_length_match || file_match) &&
     secure_details[:final_url].start_with?("https://") &&
     url.start_with?("http://")
    return "The URL #{url} should use HTTPS rather than HTTP"
  end

  return unless check_content

  no_protocol_file_contents = %r{https?:\\?/\\?/}
  details[:file] = details[:file].gsub(no_protocol_file_contents, "/")
  secure_details[:file] = secure_details[:file].gsub(no_protocol_file_contents, "/")

  # Check for the same content after removing all protocols
  if (details[:file] == secure_details[:file]) &&
     secure_details[:final_url].start_with?("https://") &&
     url.start_with?("http://")
    return "The URL #{url} should use HTTPS rather than HTTP"
  end

  return unless strict

  # Same size, different content after normalization
  # (typical causes: Generated ID, Timestamp, Unix time)
  if details[:file].length == secure_details[:file].length
    return "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser."
  end

  lenratio = (100 * secure_details[:file].length / details[:file].length).to_i
  return unless (90..110).cover?(lenratio)

  "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser."
end

#curl_download(*args, to: nil, **options) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'brew/Library/Homebrew/utils/curl.rb', line 54

def curl_download(*args, to: nil, **options)
  destination = Pathname(to)
  destination.dirname.mkpath

  range_stdout = curl_output("--location", "--range", "0-1",
                             "--dump-header", "-",
                             "--write-out", "%{http_code}",
                             "--output", "/dev/null", *args, **options).stdout
  headers, _, http_status = range_stdout.partition("\r\n\r\n")

  supports_partial_download = http_status.to_i == 206 # Partial Content
  if supports_partial_download &&
     destination.exist? &&
     destination.size == %r{^.*Content-Range: bytes \d+-\d+/(\d+)\r\n.*$}m.match(headers)&.[](1)&.to_i
    return # We've already downloaded all the bytes
  end

  continue_at = if destination.exist? && supports_partial_download
    "-"
  else
    0
  end

  curl("--location", "--remote-time", "--continue-at", continue_at.to_s, "--output", destination, *args, **options)
end

#curl_executableObject



5
6
7
8
9
10
11
12
13
14
# File 'brew/Library/Homebrew/utils/curl.rb', line 5

def curl_executable
  @curl ||= [
    ENV["HOMEBREW_CURL"],
    which("curl"),
    "/usr/bin/curl",
  ].compact.map { |c| Pathname(c) }.find(&:executable?)
  raise "no executable curl was found" unless @curl

  @curl
end

#curl_http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'brew/Library/Homebrew/utils/curl.rb', line 165

def curl_http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default)
  max_time = hash_needed ? "600" : "25"
  output, = curl_output(
    "--connect-timeout", "15", "--include", "--max-time", max_time, "--location", url,
    user_agent: user_agent
  )

  status_code = :unknown
  while status_code == :unknown || status_code.to_s.start_with?("3")
    headers, _, output = output.partition("\r\n\r\n")
    status_code = headers[%r{HTTP\/.* (\d+)}, 1]
    final_url = headers[/^Location:\s*(.*)$/i, 1]&.chomp
  end

  output_hash = Digest::SHA256.digest(output) if hash_needed

  final_url ||= url

  {
    url:            url,
    final_url:      final_url,
    status:         status_code,
    etag:           headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2],
    content_length: headers[/Content-Length: (\d+)/, 1],
    file_hash:      output_hash,
    file:           output,
  }
end

#curl_output(*args, **options) ⇒ Object



80
81
82
83
84
# File 'brew/Library/Homebrew/utils/curl.rb', line 80

def curl_output(*args, **options)
  system_command(curl_executable,
                 args:         curl_args(*args, show_output: true, **options),
                 print_stderr: false)
end

#disk_usage_readable(size_in_bytes) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'brew/Library/Homebrew/utils.rb', line 398

def disk_usage_readable(size_in_bytes)
  if size_in_bytes >= 1_073_741_824
    size = size_in_bytes.to_f / 1_073_741_824
    unit = "GB"
  elsif size_in_bytes >= 1_048_576
    size = size_in_bytes.to_f / 1_048_576
    unit = "MB"
  elsif size_in_bytes >= 1_024
    size = size_in_bytes.to_f / 1_024
    unit = "KB"
  else
    size = size_in_bytes
    unit = "B"
  end

  # avoid trailing zero after decimal point
  if ((size * 10).to_i % 10).zero?
    "#{size.to_i}#{unit}"
  else
    "#{format("%.1f", size)}#{unit}"
  end
end

#exec_browser(*args) ⇒ Object



331
332
333
334
335
336
337
338
339
# File 'brew/Library/Homebrew/utils.rb', line 331

def exec_browser(*args)
  browser = ENV["HOMEBREW_BROWSER"]
  browser ||= OS::PATH_OPEN if defined?(OS::PATH_OPEN)
  return unless browser

  ENV["DISPLAY"] = ENV["HOMEBREW_DISPLAY"]

  safe_system(browser, *args)
end

#exec_editor(*args) ⇒ Object



326
327
328
329
# File 'brew/Library/Homebrew/utils.rb', line 326

def exec_editor(*args)
  puts "Editing #{args.join "\n"}"
  with_homebrew_path { safe_system(*which_editor.shellsplit, *args) }
end

#get_env_or_raise(env) ⇒ Object



10
11
12
13
14
# File 'brew/Library/Homebrew/config.rb', line 10

def get_env_or_raise(env)
  raise MissingEnvironmentVariables, "#{env} was not exported!" unless ENV[env]

  ENV[env]
end

#gzip(*paths) ⇒ Object

GZips the given paths, and returns the gzipped paths



342
343
344
345
346
347
# File 'brew/Library/Homebrew/utils.rb', line 342

def gzip(*paths)
  paths.map do |path|
    safe_system "gzip", path
    Pathname.new("#{path}.gz")
  end
end

#ignore_interrupts(opt = nil) ⇒ Object



355
356
357
358
359
360
361
362
# File 'brew/Library/Homebrew/utils.rb', line 355

def ignore_interrupts(opt = nil)
  std_trap = trap("INT") do
    puts "One sec, just cleaning up" unless opt == :quietly
  end
  yield
ensure
  trap("INT", std_trap)
end

#interactive_shell(f = nil) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'brew/Library/Homebrew/utils.rb', line 171

def interactive_shell(f = nil)
  unless f.nil?
    ENV["HOMEBREW_DEBUG_PREFIX"] = f.prefix
    ENV["HOMEBREW_DEBUG_INSTALL"] = f.full_name
  end

  if ENV["SHELL"].include?("zsh") && ENV["HOME"].start_with?(HOMEBREW_TEMP.resolved_path.to_s)
    FileUtils.mkdir_p ENV["HOME"]
    FileUtils.touch "#{ENV["HOME"]}/.zshrc"
  end

  Process.wait fork { exec ENV["SHELL"] }

  return if $CHILD_STATUS.success?
  raise "Aborted due to non-zero exit status (#{$CHILD_STATUS.exitstatus})" if $CHILD_STATUS.exited?

  raise $CHILD_STATUS.inspect
end

#nostdoutObject



373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'brew/Library/Homebrew/utils.rb', line 373

def nostdout
  if ARGV.verbose?
    yield
  else
    begin
      out = $stdout.dup
      $stdout.reopen("/dev/null")
      yield
    ensure
      $stdout.reopen(out)
      out.close
    end
  end
end

#number_readable(number) ⇒ Object



421
422
423
424
425
# File 'brew/Library/Homebrew/utils.rb', line 421

def number_readable(number)
  numstr = number.to_i.to_s
  (numstr.size - 3).step(1, -3) { |i| numstr.insert(i, ",") }
  numstr
end

#odebug(title, *sput) ⇒ Object



39
40
41
42
43
44
# File 'brew/Library/Homebrew/utils.rb', line 39

def odebug(title, *sput)
  return unless ARGV.debug?

  puts Formatter.headline(title, color: :magenta)
  puts sput unless sput.empty?
end

#odeprecated(method, replacement = nil, disable: false, disable_on: nil, caller: send(:caller)) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'brew/Library/Homebrew/utils.rb', line 70

def odeprecated(method, replacement = nil, disable: false, disable_on: nil, caller: send(:caller))
  replacement_message = if replacement
    "Use #{replacement} instead."
  else
    "There is no replacement."
  end

  unless disable_on.nil?
    if disable_on > Time.now
      will_be_disabled_message = " and will be disabled on #{disable_on.strftime("%Y-%m-%d")}"
    else
      disable = true
    end
  end

  verb = if disable
    "disabled"
  else
    "deprecated#{will_be_disabled_message}"
  end

  # Try to show the most relevant location in message, i.e. (if applicable):
  # - Location in a formula.
  # - Location outside of 'compat/'.
  # - Location of caller of deprecated method (if all else fails).
  backtrace = caller

  # Don't throw deprecations at all for cached, .brew or .metadata files.
  return if backtrace.any? do |line|
    line.include?(HOMEBREW_CACHE) ||
    line.include?("/.brew/") ||
    line.include?("/.metadata/")
  end

  tap_message = nil

  backtrace.each do |line|
    next unless match = line.match(HOMEBREW_TAP_PATH_REGEX)

    tap = Tap.fetch(match[:user], match[:repo])
    tap_message = +"\nPlease report this to the #{tap} tap"
    tap_message += ", or even better, submit a PR to fix it" if replacement
    tap_message << ":\n  #{line.sub(/^(.*\:\d+)\:.*$/, '\1')}\n\n"
    break
  end

  message = +"Calling #{method} is #{verb}! #{replacement_message}"
  message << tap_message if tap_message
  message.freeze

  if ARGV.homebrew_developer? || disable || Homebrew.raise_deprecation_exceptions?
    exception = MethodDeprecatedError.new(message)
    exception.set_backtrace(backtrace)
    raise exception
  elsif !Homebrew.auditing?
    opoo message
  end
end

#odie(error) ⇒ Object



65
66
67
68
# File 'brew/Library/Homebrew/utils.rb', line 65

def odie(error)
  onoe error
  exit 1
end

#odisabled(method, replacement = nil, options = {}) ⇒ Object



129
130
131
132
# File 'brew/Library/Homebrew/utils.rb', line 129

def odisabled(method, replacement = nil, options = {})
  options = { disable: true, caller: caller }.merge(options)
  odeprecated(method, replacement, options)
end

#ofail(error) ⇒ Object



60
61
62
63
# File 'brew/Library/Homebrew/utils.rb', line 60

def ofail(error)
  onoe error
  Homebrew.failed = true
end

#oh1(title, options = {}) ⇒ Object



46
47
48
49
# File 'brew/Library/Homebrew/utils.rb', line 46

def oh1(title, options = {})
  title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose? && options.fetch(:truncate, :auto) == :auto
  puts Formatter.headline(title, color: :green)
end

#ohai(title, *sput) ⇒ Object



34
35
36
37
# File 'brew/Library/Homebrew/utils.rb', line 34

def ohai(title, *sput)
  puts ohai_title(title)
  puts sput
end

#ohai_title(title) ⇒ Object



29
30
31
32
# File 'brew/Library/Homebrew/utils.rb', line 29

def ohai_title(title)
  title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose?
  Formatter.headline(title, color: :blue)
end

#onoe(message) ⇒ Object



56
57
58
# File 'brew/Library/Homebrew/utils.rb', line 56

def onoe(message)
  $stderr.puts Formatter.error(message, label: "Error")
end

#opoo(message) ⇒ Object

Print a warning (do this rarely)



52
53
54
# File 'brew/Library/Homebrew/utils.rb', line 52

def opoo(message)
  $stderr.puts Formatter.warning(message, label: "Warning")
end

#pathsObject



388
389
390
391
392
393
394
395
396
# File 'brew/Library/Homebrew/utils.rb', line 388

def paths
  @paths ||= PATH.new(ENV["HOMEBREW_PATH"]).map do |p|
    begin
      File.expand_path(p).chomp("/")
    rescue ArgumentError
      onoe "The following PATH component is invalid: #{p}"
    end
  end.uniq.compact
end

#pretty_duration(s) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'brew/Library/Homebrew/utils.rb', line 154

def pretty_duration(s)
  s = s.to_i
  res = +""

  if s > 59
    m = s / 60
    s %= 60
    res = +"#{m} #{"minute".pluralize(m)}"
    return res.freeze if s.zero?

    res << " "
  end

  res << "#{s} #{"second".pluralize(s)}"
  res.freeze
end

#pretty_installed(f) ⇒ Object



134
135
136
137
138
139
140
141
142
# File 'brew/Library/Homebrew/utils.rb', line 134

def pretty_installed(f)
  if !$stdout.tty?
    f.to_s
  elsif Emoji.enabled?
    "#{Tty.bold}#{f} #{Formatter.success("")}#{Tty.reset}"
  else
    Formatter.success("#{Tty.bold}#{f} (installed)#{Tty.reset}")
  end
end

#pretty_uninstalled(f) ⇒ Object



144
145
146
147
148
149
150
151
152
# File 'brew/Library/Homebrew/utils.rb', line 144

def pretty_uninstalled(f)
  if !$stdout.tty?
    f.to_s
  elsif Emoji.enabled?
    "#{Tty.bold}#{f} #{Formatter.error("")}#{Tty.reset}"
  else
    Formatter.error("#{Tty.bold}#{f} (uninstalled)#{Tty.reset}")
  end
end

#quiet_system(cmd, *args) ⇒ Object

Prints no output



268
269
270
271
272
273
274
275
# File 'brew/Library/Homebrew/utils.rb', line 268

def quiet_system(cmd, *args)
  Homebrew._system(cmd, *args) do
    # Redirect output streams to `/dev/null` instead of closing as some programs
    # will fail to execute if they can't write to an open stream.
    $stdout.reopen("/dev/null")
    $stderr.reopen("/dev/null")
  end
end

#require?(path) ⇒ Boolean

Returns:

  • (Boolean)


19
20
21
22
23
24
25
26
27
# File 'brew/Library/Homebrew/utils.rb', line 19

def require?(path)
  return false if path.nil?

  require path
  true
rescue LoadError => e
  # we should raise on syntax errors but not if the file doesn't exist.
  raise unless e.message.include?(path)
end

#safe_system(cmd, *args, **options) ⇒ Object

Kernel.system but with exceptions



261
262
263
264
265
# File 'brew/Library/Homebrew/utils.rb', line 261

def safe_system(cmd, *args, **options)
  return if Homebrew.system(cmd, *args, **options)

  raise ErrorDuringExecution.new([cmd, *args], status: $CHILD_STATUS)
end

#shell_profileObject



484
485
486
# File 'brew/Library/Homebrew/utils.rb', line 484

def shell_profile
  Utils::Shell.profile
end

#superenv?Boolean

Returns:

  • (Boolean)


8
9
10
# File 'brew/Library/Homebrew/extend/ENV.rb', line 8

def superenv?
  ARGV.env != "std" && Superenv.bin
end

#tap_and_name_comparisonObject



488
489
490
491
492
493
494
495
496
497
498
# File 'brew/Library/Homebrew/utils.rb', line 488

def tap_and_name_comparison
  proc do |a, b|
    if a.include?("/") && !b.include?("/")
      1
    elsif !a.include?("/") && b.include?("/")
      -1
    else
      a <=> b
    end
  end
end

#truncate_text_to_approximate_size(s, max_bytes, options = {}) ⇒ Object

Truncates a text string to fit within a byte size constraint, preserving character encoding validity. The returned string will be not much longer than the specified max_bytes, though the exact shortfall or overrun may vary.



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'brew/Library/Homebrew/utils.rb', line 431

def truncate_text_to_approximate_size(s, max_bytes, options = {})
  front_weight = options.fetch(:front_weight, 0.5)
  raise "opts[:front_weight] must be between 0.0 and 1.0" if front_weight < 0.0 || front_weight > 1.0
  return s if s.bytesize <= max_bytes

  glue = "\n[...snip...]\n"
  max_bytes_in = [max_bytes - glue.bytesize, 1].max
  bytes = s.dup.force_encoding("BINARY")
  glue_bytes = glue.encode("BINARY")
  n_front_bytes = (max_bytes_in * front_weight).floor
  n_back_bytes = max_bytes_in - n_front_bytes
  if n_front_bytes.zero?
    front = bytes[1..0]
    back = bytes[-max_bytes_in..-1]
  elsif n_back_bytes.zero?
    front = bytes[0..(max_bytes_in - 1)]
    back = bytes[1..0]
  else
    front = bytes[0..(n_front_bytes - 1)]
    back = bytes[-n_back_bytes..-1]
  end
  out = front + glue_bytes + back
  out.force_encoding("UTF-8")
  out.encode!("UTF-16", invalid: :replace)
  out.encode!("UTF-8")
  out
end

#which(cmd, path = ENV["PATH"]) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'brew/Library/Homebrew/utils.rb', line 277

def which(cmd, path = ENV["PATH"])
  PATH.new(path).each do |p|
    begin
      pcmd = File.expand_path(cmd, p)
    rescue ArgumentError
      # File.expand_path will raise an ArgumentError if the path is malformed.
      # See https://github.com/Homebrew/legacy-homebrew/issues/32789
      next
    end
    return Pathname.new(pcmd) if File.file?(pcmd) && File.executable?(pcmd)
  end
  nil
end

#which_all(cmd, path = ENV["PATH"]) ⇒ Object



291
292
293
294
295
296
297
298
299
300
301
302
# File 'brew/Library/Homebrew/utils.rb', line 291

def which_all(cmd, path = ENV["PATH"])
  PATH.new(path).map do |p|
    begin
      pcmd = File.expand_path(cmd, p)
    rescue ArgumentError
      # File.expand_path will raise an ArgumentError if the path is malformed.
      # See https://github.com/Homebrew/legacy-homebrew/issues/32789
      next
    end
    Pathname.new(pcmd) if File.file?(pcmd) && File.executable?(pcmd)
  end.compact.uniq
end

#which_editorObject



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'brew/Library/Homebrew/utils.rb', line 304

def which_editor
  editor = ENV.values_at("HOMEBREW_EDITOR", "HOMEBREW_VISUAL")
              .compact
              .reject(&:empty?)
              .first
  return editor if editor

  # Find Atom, Sublime Text, Textmate, BBEdit / TextWrangler, or vim
  editor = %w[atom subl mate edit vim].find do |candidate|
    candidate if which(candidate, ENV["HOMEBREW_PATH"])
  end
  editor ||= "vim"

  opoo <<~EOS
    Using #{editor} because no editor was set in the environment.
    This may change in the future, so we recommend setting EDITOR,
    or HOMEBREW_EDITOR to your preferred text editor.
  EOS

  editor
end

#with_custom_locale(locale) ⇒ Object



254
255
256
257
258
# File 'brew/Library/Homebrew/utils.rb', line 254

def with_custom_locale(locale)
  with_env(LC_ALL: locale) do
    yield
  end
end

#with_env(hash) ⇒ Object

Calls the given block with the passed environment variables added to ENV, then restores ENV afterwards. Example:

with_env(PATH: "/bin") do
  system "echo $PATH"
end

Note that this method is not thread-safe - other threads which happen to be scheduled during the block will also see these environment variables.



469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'brew/Library/Homebrew/utils.rb', line 469

def with_env(hash)
  old_values = {}
  begin
    hash.each do |key, value|
      key = key.to_s
      old_values[key] = ENV.delete(key)
      ENV[key] = value
    end

    yield if block_given?
  ensure
    ENV.update(old_values)
  end
end

#with_homebrew_pathObject



248
249
250
251
252
# File 'brew/Library/Homebrew/utils.rb', line 248

def with_homebrew_path
  with_env(PATH: PATH.new(ENV["HOMEBREW_PATH"])) do
    yield
  end
end

#with_monkey_patchObject



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'brew/Library/Homebrew/dev-cmd/extract.rb', line 8

def with_monkey_patch
  BottleSpecification.class_eval do
    alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
    define_method(:method_missing) { |*| }
  end

  Module.class_eval do
    alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
    define_method(:method_missing) { |*| }
  end

  Resource.class_eval do
    alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
    define_method(:method_missing) { |*| }
  end

  DependencyCollector.class_eval do
    alias_method :old_parse_symbol_spec, :parse_symbol_spec if method_defined?(:parse_symbol_spec)
    define_method(:parse_symbol_spec) { |*| }
  end

  if defined?(DependencyCollector::Compat)
    DependencyCollector::Compat.class_eval do
      alias_method :old_parse_string_spec, :parse_string_spec if method_defined?(:parse_string_spec)
      define_method(:parse_string_spec) { |*| }
    end
  end

  yield
ensure
  BottleSpecification.class_eval do
    if method_defined?(:old_method_missing)
      alias_method :method_missing, :old_method_missing
      undef :old_method_missing
    end
  end

  Module.class_eval do
    if method_defined?(:old_method_missing)
      alias_method :method_missing, :old_method_missing
      undef :old_method_missing
    end
  end

  Resource.class_eval do
    if method_defined?(:old_method_missing)
      alias_method :method_missing, :old_method_missing
      undef :old_method_missing
    end
  end

  DependencyCollector.class_eval do
    if method_defined?(:old_parse_symbol_spec)
      alias_method :parse_symbol_spec, :old_parse_symbol_spec
      undef :old_parse_symbol_spec
    end
  end

  if defined?(DependencyCollector::Compat)
    DependencyCollector::Compat.class_eval do
      if method_defined?(:old_parse_string_spec)
        alias_method :parse_string_spec, :old_parse_string_spec
        undef :old_parse_string_spec
      end
    end
  end
end