Module: Homebrew

Extended by:
Context, DependenciesHelpers, FileUtils, Fetch, Install, SystemCommand::Mixin
Includes:
SystemCommand::Mixin
Defined in:
extend/os/mac/cleanup.rb,
extend/os/linux/parser.rb,
extend/os/linux/cleanup.rb,
extend/os/linux/install.rb,
extend/os/mac/diagnostic.rb,
extend/os/linux/diagnostic.rb,
extend/os/mac/dev-cmd/bottle.rb,
extend/os/mac/missing_formula.rb,
extend/os/mac/simulate_system.rb,
extend/os/linux/simulate_system.rb,
extend/os/linux/cmd/update-report.rb,
extend/os/linux/dev-cmd/update-test.rb,
api.rb,
help.rb,
fetch.rb,
style.rb,
utils.rb,
global.rb,
search.rb,
unlink.rb,
cleanup.rb,
cmd/log.rb,
cmd/pin.rb,
cmd/tap.rb,
install.rb,
service.rb,
upgrade.rb,
api/cask.rb,
cli/args.rb,
cmd/deps.rb,
cmd/desc.rb,
cmd/docs.rb,
cmd/help.rb,
cmd/home.rb,
cmd/info.rb,
cmd/link.rb,
cmd/list.rb,
cmd/uses.rb,
manpages.rb,
settings.rb,
cmd/--env.rb,
cmd/fetch.rb,
cmd/unpin.rb,
cmd/untap.rb,
reinstall.rb,
uninstall.rb,
cli/parser.rb,
cmd/config.rb,
cmd/doctor.rb,
cmd/leaves.rb,
cmd/search.rb,
cmd/unlink.rb,
dev-cmd/sh.rb,
diagnostic.rb,
env_config.rb,
utils/gems.rb,
api/formula.rb,
cmd/--cache.rb,
cmd/cleanup.rb,
cmd/install.rb,
cmd/migrate.rb,
cmd/missing.rb,
cmd/options.rb,
cmd/readall.rb,
cmd/upgrade.rb,
completions.rb,
dev-cmd/cat.rb,
dev-cmd/irb.rb,
tap_auditor.rb,
api/download.rb,
cmd/--cellar.rb,
cmd/--prefix.rb,
cmd/commands.rb,
cmd/outdated.rb,
cmd/tap-info.rb,
dev-cmd/bump.rb,
dev-cmd/edit.rb,
dev-cmd/prof.rb,
dev-cmd/ruby.rb,
dev-cmd/test.rb,
api/analytics.rb,
cmd/analytics.rb,
cmd/developer.rb,
cmd/gist-logs.rb,
cmd/reinstall.rb,
cmd/uninstall.rb,
dev-cmd/audit.rb,
dev-cmd/style.rb,
dev-cmd/tests.rb,
bundle_version.rb,
cli/named_args.rb,
cmd/--caskroom.rb,
cmd/autoremove.rb,
cmd/pyenv-sync.rb,
cmd/rbenv-sync.rb,
default_prefix.rb,
dev-cmd/bottle.rb,
dev-cmd/create.rb,
dev-cmd/unpack.rb,
sorbet/parlour.rb,
cmd/completions.rb,
cmd/nodenv-sync.rb,
cmd/postinstall.rb,
dev-cmd/command.rb,
dev-cmd/extract.rb,
dev-cmd/formula.rb,
dev-cmd/linkage.rb,
dev-cmd/pr-pull.rb,
dev-cmd/release.rb,
dev-cmd/tap-new.rb,
formula_auditor.rb,
formula_creator.rb,
livecheck/error.rb,
missing_formula.rb,
simulate_system.rb,
source_location.rb,
cmd/--repository.rb,
resource_auditor.rb,
cmd/update-report.rb,
dev-cmd/livecheck.rb,
dev-cmd/pr-upload.rb,
dev-cmd/typecheck.rb,
dev-cmd/unbottled.rb,
formula_free_port.rb,
dev-cmd/pr-publish.rb,
formula_assertions.rb,
livecheck/strategy.rb,
bump_version_parser.rb,
dev-cmd/update-test.rb,
dev-cmd/vendor-gems.rb,
livecheck/constants.rb,
livecheck/livecheck.rb,
dev-cmd/bump-cask-pr.rb,
dev-cmd/pr-automerge.rb,
formula_text_auditor.rb,
dev-cmd/bump-revision.rb,
dev-cmd/contributions.rb,
livecheck/strategy/git.rb,
livecheck/strategy/gnu.rb,
livecheck/strategy/npm.rb,
livecheck/strategy/xml.rb,
dev-cmd/bump-formula-pr.rb,
dev-cmd/update-sponsors.rb,
livecheck/strategy/cpan.rb,
livecheck/strategy/json.rb,
livecheck/strategy/pypi.rb,
livecheck/strategy/xorg.rb,
livecheck/strategy/yaml.rb,
livecheck/strategy/gnome.rb,
unversioned_cask_checker.rb,
dev-cmd/generate-cask-api.rb,
livecheck/skip_conditions.rb,
livecheck/strategy/apache.rb,
dev-cmd/update-maintainers.rb,
livecheck/strategy/hackage.rb,
livecheck/strategy/sparkle.rb,
dev-cmd/update-license-data.rb,
livecheck/livecheck_version.rb,
dev-cmd/generate-formula-api.rb,
dev-cmd/install-bundler-gems.rb,
livecheck/strategy/bitbucket.rb,
livecheck/strategy/launchpad.rb,
dev-cmd/dispatch-build-bottle.rb,
livecheck/strategy/page_match.rb,
dev-cmd/bump-unversioned-casks.rb,
dev-cmd/determine-test-runners.rb,
livecheck/strategy/sourceforge.rb,
cmd/postgresql-upgrade-database.rb,
dev-cmd/update-python-resources.rb,
livecheck/strategy/header_match.rb,
dev-cmd/generate-man-completions.rb,
livecheck/strategy/extract_plist.rb,
livecheck/strategy/github_latest.rb,
livecheck/strategy/github_releases.rb,
livecheck/strategy/electron_builder.rb

Defined Under Namespace

Modules: API, Assertions, CLI, Completions, Diagnostic, EnvConfig, Fetch, FreePort, Help, Install, Livecheck, Manpages, MissingFormula, Parlour, Search, Settings, Style, Uninstall, Unlink, Upgrade Classes: BumpVersionParser, BundleVersion, Cleanup, FormulaAuditor, FormulaCreator, FormulaTextAuditor, ResourceAuditor, Service, SimulateSystem, SourceLocation, TapAuditor, UnversionedCaskChecker, VersionBumpInfo

Constant Summary collapse

DEFAULT_CELLAR =
"#{DEFAULT_PREFIX}/Cellar"
DEFAULT_MACOS_CELLAR =
"#{HOMEBREW_DEFAULT_PREFIX}/Cellar"
DEFAULT_MACOS_ARM_CELLAR =
"#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}/Cellar"
DEFAULT_LINUX_CELLAR =
"#{HOMEBREW_LINUX_DEFAULT_PREFIX}/Cellar"
VALID_DAYS =
%w[30 90 365].freeze
VALID_FORMULA_CATEGORIES =
%w[install install-on-request build-error].freeze
VALID_CATEGORIES =
(VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze
FETCH_MAX_TRIES =
5
PACKAGE_MANAGERS =
{
  repology:  ->(query) { "https://repology.org/projects/?search=#{query}" },
  macports:  ->(query) { "https://ports.macports.org/search/?q=#{query}" },
  fink:      ->(query) { "https://pdb.finkproject.org/pdb/browse.php?summary=#{query}" },
  opensuse:  ->(query) { "https://software.opensuse.org/search?q=#{query}" },
  fedora:    ->(query) { "https://packages.fedoraproject.org/search?query=#{query}" },
  archlinux: ->(query) { "https://archlinux.org/packages/?q=#{query}" },
  debian:    lambda { |query|
    "https://packages.debian.org/search?keywords=#{query}&searchon=names&suite=all&section=all"
  },
  ubuntu:    lambda { |query|
    "https://packages.ubuntu.com/search?keywords=#{query}&searchon=names&suite=all&section=all"
  },
}.freeze
HOMEBREW_BUNDLER_VERSION =

Keep in sync with the Gemfile.lock’s BUNDLED WITH. After updating this, run brew vendor-gems --update=--bundler.

"2.4.18"
UNBREWED_EXCLUDE_FILES =
%w[.DS_Store].freeze
UNBREWED_EXCLUDE_PATHS =
%w[
  */.keepme
  .github/*
  bin/brew
  completions/zsh/_brew
  docs/*
  lib/gdk-pixbuf-2.0/*
  lib/gio/*
  lib/node_modules/*
  lib/python[23].[0-9]/*
  lib/python3.[0-9][0-9]/*
  lib/pypy/*
  lib/pypy3/*
  lib/ruby/gems/[12].*
  lib/ruby/site_ruby/[12].*
  lib/ruby/vendor_ruby/[12].*
  manpages/brew.1
  share/pypy/*
  share/pypy3/*
  share/info/dir
  share/man/whatis
  share/mime/*
  texlive/*
].freeze
DEFAULT_PREFIX =
T.let(ENV.fetch("HOMEBREW_DEFAULT_PREFIX").freeze, String)
DEFAULT_REPOSITORY =
T.let(ENV.fetch("HOMEBREW_DEFAULT_REPOSITORY").freeze, String)
BOTTLE_BLOCK_REGEX =
/  bottle (?:do.+?end|:[a-z]+)\n\n/m.freeze
PRIMARY_REPOS =
%w[brew core cask].freeze
SUPPORTED_REPOS =
[
  PRIMARY_REPOS,
  OFFICIAL_CMD_TAPS.keys.map { |t| t.delete_prefix("homebrew/") },
  OFFICIAL_CASK_TAPS.reject { |t| t == "cask" },
].flatten.freeze
MAX_REPO_COMMITS =
1000
NAMED_MONTHLY_AMOUNT =
100
URL_MONTHLY_AMOUNT =
1000
CASK_JSON_TEMPLATE =
<<~EOS
  ---
  layout: cask_json
  ---
  {{ content }}
EOS
FORMULA_JSON_TEMPLATE =
<<~EOS
  ---
  layout: formula_json
  ---
  {{ content }}
EOS

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Context

current, current=, debug?, quiet?, verbose?, with_context

Methods included from DependenciesHelpers

args_includes_ignores, dependents, recursive_includes, select_includes

Methods included from Fetch

fetch_bottle?

Methods included from Install

check_prefix, global_post_install, install_formula?, install_formulae, perform_build_from_source_checks, perform_preinstall_checks, print_dry_run_dependencies

Methods included from SystemCommand::Mixin

system_command, system_command!

Class Attribute Details

.auditing=(value) ⇒ Object (writeonly)

Sets the attribute auditing

Parameters:

  • value

    the value to set the attribute auditing to.



93
94
95
# File 'global.rb', line 93

def auditing=(value)
  @auditing = value
end

.failed=(value) ⇒ Object (writeonly)

Sets the attribute failed

Parameters:

  • value

    the value to set the attribute failed to.



93
94
95
# File 'global.rb', line 93

def failed=(value)
  @failed = value
end

.raise_deprecation_exceptions=(value) ⇒ Object (writeonly)

Sets the attribute raise_deprecation_exceptions

Parameters:

  • value

    the value to set the attribute raise_deprecation_exceptions to.



93
94
95
# File 'global.rb', line 93

def raise_deprecation_exceptions=(value)
  @raise_deprecation_exceptions = value
end

Class Method Details

.__cachevoid

This method returns an undefined value.



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
75
76
77
78
79
80
81
82
83
84
85
86
# File 'cmd/--cache.rb', line 48

def self.__cache
  args = __cache_args.parse

  if args.no_named?
    puts HOMEBREW_CACHE
    return
  end

  formulae_or_casks = args.named.to_formulae_and_casks
  os_arch_combinations = args.os_arch_combinations

  formulae_or_casks.each do |formula_or_cask|
    case formula_or_cask
    when Formula
      formula = formula_or_cask
      ref = formula.loaded_from_api? ? formula.full_name : formula.path

      os_arch_combinations.each do |os, arch|
        SimulateSystem.with os: os, arch: arch do
          Formulary.clear_cache
          formula = Formulary.factory(ref)
          print_formula_cache(formula, os: os, arch: arch, args: args)
        end
      end
    else
      cask = formula_or_cask
      ref = cask.loaded_from_api? ? cask.full_name : cask.sourcefile_path

      os_arch_combinations.each do |os, arch|
        next if os == :linux

        SimulateSystem.with os: os, arch: arch do
          cask = Cask::CaskLoader.load(ref)
          print_cask_cache(cask)
        end
      end
    end
  end
end

.__cache_argsCLI::Parser

Returns:



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
# File 'cmd/--cache.rb', line 12

def self.__cache_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display Homebrew's download cache. See also `HOMEBREW_CACHE`.

      If <formula> is provided, display the file or directory used to cache <formula>.
    EOS
    flag   "--os=",
           description: "Show cache file for the given operating system. " \
                        "(Pass `all` to show cache files for all operating systems.)"
    flag   "--arch=",
           description: "Show cache file for the given CPU architecture. " \
                        "(Pass `all` to show cache files for all architectures.)"
    switch "-s", "--build-from-source",
           description: "Show the cache file used when building from source."
    switch "--force-bottle",
           description: "Show the cache file used when pouring a bottle."
    flag "--bottle-tag=",
         description: "Show the cache file used when pouring a bottle for the given tag."
    switch "--HEAD",
           description: "Show the cache file used when building from HEAD."
    switch "--formula", "--formulae",
           description: "Only show cache files for formulae."
    switch "--cask", "--casks",
           description: "Only show cache files for casks."

    conflicts "--build-from-source", "--force-bottle", "--bottle-tag", "--HEAD", "--cask"
    conflicts "--formula", "--cask"
    conflicts "--os", "--bottle-tag"
    conflicts "--arch", "--bottle-tag"

    named_args [:formula, :cask]
  end
end

.__caskroomvoid

This method returns an undefined value.



22
23
24
25
26
27
28
29
30
31
32
# File 'cmd/--caskroom.rb', line 22

def __caskroom
  args = __caskroom_args.parse

  if args.named.to_casks.blank?
    puts Cask::Caskroom.path
  else
    args.named.to_casks.each do |cask|
      puts "#{Cask::Caskroom.path}/#{cask.token}"
    end
  end
end

.__caskroom_argsCLI::Parser

Returns:



8
9
10
11
12
13
14
15
16
17
18
19
# File 'cmd/--caskroom.rb', line 8

def __caskroom_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display Homebrew's Caskroom path.

      If <cask> is provided, display the location in the Caskroom where <cask>
      would be installed, without any sort of versioned directory as the last path.
    EOS

    named_args :cask
  end
end

.__cellarObject



23
24
25
26
27
28
29
30
31
# File 'cmd/--cellar.rb', line 23

def __cellar
  args = __cellar_args.parse

  if args.no_named?
    puts HOMEBREW_CELLAR
  else
    puts args.named.to_resolved_formulae.map(&:rack)
  end
end

.__cellar_argsObject



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'cmd/--cellar.rb', line 9

def __cellar_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display Homebrew's Cellar path. *Default:* `$(brew --prefix)/Cellar`, or if
      that directory doesn't exist, `$(brew --repository)/Cellar`.

      If <formula> is provided, display the location in the Cellar where <formula>
      would be installed, without any sort of versioned directory as the last path.
    EOS

    named_args :formula
  end
end

.__envvoid

This method returns an undefined value.



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
# File 'cmd/--env.rb', line 32

def __env
  args = __env_args.parse

  ENV.activate_extensions!
  ENV.deps = args.named.to_formulae if superenv?(nil)
  ENV.setup_build_environment

  shell = if args.plain?
    nil
  elsif args.shell.nil?
    :bash unless $stdout.tty?
  elsif args.shell == "auto"
    Utils::Shell.parent || Utils::Shell.preferred
  elsif args.shell
    Utils::Shell.from_path(args.shell)
  end

  if shell.nil?
    BuildEnvironment.dump ENV.to_h
  else
    BuildEnvironment.keys(ENV.to_h).each do |key|
      puts Utils::Shell.export_value(key, ENV.fetch(key), shell)
    end
  end
end

.__env_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'cmd/--env.rb', line 13

def __env_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Summarise Homebrew's build environment as a plain list.

      If the command's output is sent through a pipe and no shell is specified,
      the list is formatted for export to `bash`(1) unless `--plain` is passed.
    EOS
    flag   "--shell=",
           description: "Generate a list of environment variables for the specified shell, " \
                        "or `--shell=auto` to detect the current shell."
    switch "--plain",
           description: "Generate plain output even when piped."

    named_args :formula
  end
end

.__getsObject



218
219
220
221
# File 'dev-cmd/create.rb', line 218

def __gets
  gots = $stdin.gets.chomp
  gots.empty? ? nil : gots
end

.__prefixObject

Raises:



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
# File 'cmd/--prefix.rb', line 29

def self.__prefix
  args = __prefix_args.parse

  raise UsageError, "`--installed` requires a formula argument." if args.installed? && args.no_named?

  if args.unbrewed?
    raise UsageError, "`--unbrewed` does not take a formula argument." unless args.no_named?

    list_unbrewed
  elsif args.no_named?
    puts HOMEBREW_PREFIX
  else
    formulae = args.named.to_resolved_formulae
    prefixes = formulae.map do |f|
      next nil if args.installed? && !f.opt_prefix.exist?

      # this case will be short-circuited by brew.sh logic for a single formula
      f.opt_prefix
    end.compact
    puts prefixes
    if args.installed?
      missing_formulae = formulae.reject(&:optlinked?)
                                 .map(&:name)
      return if missing_formulae.blank?

      raise NotAKegError, <<~EOS
        The following formulae are not installed:
        #{missing_formulae.join(" ")}
      EOS
    end
  end
end

.__prefix_argsCLI::Parser

Returns:



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'cmd/--prefix.rb', line 8

def self.__prefix_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display Homebrew's install path. *Default:*

        - macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}`
        - macOS Intel: `#{HOMEBREW_DEFAULT_PREFIX}`
        - Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}`

      If <formula> is provided, display the location where <formula> is or would be installed.
    EOS
    switch "--unbrewed",
           description: "List files in Homebrew's prefix not installed by Homebrew."
    switch "--installed",
           description: "Outputs nothing and returns a failing status code if <formula> is not installed."
    conflicts "--unbrewed", "--installed"

    named_args :formula
  end
end

.__repositoryvoid

This method returns an undefined value.



23
24
25
26
27
28
29
30
31
# File 'cmd/--repository.rb', line 23

def __repository
  args = __repository_args.parse

  if args.no_named?
    puts HOMEBREW_REPOSITORY
  else
    puts args.named.to_taps.map(&:path)
  end
end

.__repository_argsCLI::Parser

Returns:



10
11
12
13
14
15
16
17
18
19
20
# File 'cmd/--repository.rb', line 10

def __repository_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display where Homebrew's Git repository is located.

      If <user>`/`<repo> are provided, display where tap <user>`/`<repo>'s directory is located.
    EOS

    named_args :tap
  end
end

._system(cmd, *args, **options) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'utils.rb', line 29

def self._system(cmd, *args, **options)
  pid = fork do
    yield if block_given?
    args.map!(&:to_s)
    begin
      exec(cmd, *args, **options)
    rescue
      nil
    end
    exit! 1 # never gets here unless exec failed
  end
  Process.wait(T.must(pid))
  $CHILD_STATUS.success?
end

.alias_update_pair(formula, new_formula_version) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
# File 'dev-cmd/bump-formula-pr.rb', line 470

def alias_update_pair(formula, new_formula_version)
  versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first
  return if versioned_alias.nil?

  name, old_alias_version = versioned_alias.split("@")
  new_alias_regex = (old_alias_version.split(".").length == 1) ? /^\d+/ : /^\d+\.\d+/
  new_alias_version, = *new_formula_version.to_s.match(new_alias_regex)
  return if Version.new(new_alias_version) <= Version.new(old_alias_version)

  [versioned_alias, "#{name}@#{new_alias_version}"]
end

.analyticsvoid

This method returns an undefined value.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'cmd/analytics.rb', line 28

def analytics
  args = analytics_args.parse

  case args.named.first
  when nil, "state"
    if Utils::Analytics.disabled?
      puts "InfluxDB analytics are disabled."
    else
      puts "InfluxDB analytics are enabled."
    end
    puts "Google Analytics were destroyed."
  when "on"
    Utils::Analytics.enable!
  when "off"
    Utils::Analytics.disable!
  when "regenerate-uuid"
    Utils::Analytics.delete_uuid!
    opoo "Homebrew no longer uses an analytics UUID so this has been deleted!"
    puts "brew analytics regenerate-uuid is no longer necessary."
  else
    raise UsageError, "unknown subcommand: #{args.named.first}"
  end
end

.analytics_argsCLI::Parser

Returns:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'cmd/analytics.rb', line 10

def analytics_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Control Homebrew's anonymous aggregate user behaviour analytics.
      Read more at <https://docs.brew.sh/Analytics>.

      `brew analytics` [`state`]:
      Display the current state of Homebrew's analytics.

      `brew analytics` (`on`|`off`):
      Turn Homebrew's analytics on or off respectively.
    EOS

    named_args %w[state on off regenerate-uuid], max: 1
  end
end

.analytics_messageObject



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'cmd/update-report.rb', line 354

def analytics_message
  return if Utils::Analytics.messages_displayed?
  return if Utils::Analytics.no_message_output?

  if Utils::Analytics.disabled? && !Utils::Analytics.influx_message_displayed?
    ohai "Homebrew's analytics have entirely moved to our InfluxDB instance in the EU."
    puts "We gather less data than before and have destroyed all Google Analytics data:"
    puts "  #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}"
    puts "Please reconsider re-enabling analytics to help our volunteer maintainers with:"
    puts "  brew analytics on"
  elsif !Utils::Analytics.disabled?
    ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"
    # Use the shell's audible bell.
    print "\a"

    # Use an extra newline and bold to avoid this being missed.
    ohai "Homebrew collects anonymous analytics."
    puts <<~EOS
      #{Tty.bold}Read the analytics documentation (and how to opt-out) here:
        #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}
      No analytics have been recorded yet (nor will be during this `brew` run).

    EOS
  end

  # Consider the messages possibly missed if not a TTY.
  Utils::Analytics.messages_displayed! if $stdout.tty?
end

.auditvoid

This method returns an undefined value.



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
164
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'dev-cmd/audit.rb', line 113

def self.audit
  args = audit_args.parse

  new_cask = args.new? || args.new_cask?
  new_formula = args.new? || args.new_formula?

  Formulary.enable_factory_cache!

  os_arch_combinations = args.os_arch_combinations

  Homebrew.auditing = true
  inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug?

  strict = new_formula || args.strict?
  online = new_formula || args.online?
  tap_audit = args.tap.present?
  skip_style = args.skip_style? || args.no_named? || tap_audit
  no_named_args = T.let(false, T::Boolean)

  ENV.activate_extensions!
  ENV.setup_build_environment

  audit_formulae, audit_casks = Homebrew.with_no_api_env do # audit requires full Ruby source
    if args.tap
      Tap.fetch(args.tap).then do |tap|
        [
          tap.formula_files.map { |path| Formulary.factory(path) },
          tap.cask_files.map { |path| Cask::CaskLoader.load(path) },
        ]
      end
    elsif args.installed?
      no_named_args = true
      [Formula.installed, Cask::Caskroom.casks]
    elsif args.no_named?
      if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
        # This odisabled should probably stick around indefinitely.
        odisabled "brew audit",
                  "brew audit --eval-all or HOMEBREW_EVAL_ALL"
      end
      no_named_args = true
      [Formula.all(eval_all: args.eval_all?), Cask::Cask.all]
    else
      if args.named.any? { |named_arg| named_arg.end_with?(".rb") }
        odisabled "brew audit [path ...]",
                  "brew audit [name ...]"
      end

      args.named.to_formulae_and_casks
          .partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
    end
  end

  if audit_formulae.empty? && audit_casks.empty? && !args.tap
    ofail "No matching formulae or casks to audit!"
    return
  end

  gem_groups = ["audit"]
  gem_groups << "style" unless skip_style
  Homebrew.install_bundler_gems!(groups: gem_groups)

  style_files = args.named.to_paths unless skip_style

  only_cops = args.only_cops
  except_cops = args.except_cops
  style_options = { fix: args.fix?, debug: args.debug?, verbose: args.verbose? }

  if only_cops
    style_options[:only_cops] = only_cops
  elsif new_formula || new_cask
    nil
  elsif except_cops
    style_options[:except_cops] = except_cops
  elsif !strict
    style_options[:except_cops] = [:FormulaAuditStrict]
  end

  # Run tap audits first
  named_arg_taps = [*audit_formulae, *audit_casks].map(&:tap).uniq if !args.tap && !no_named_args
  tap_problems = Tap.each_with_object({}) do |tap, problems|
    next if args.tap && tap != args.tap
    next if named_arg_taps&.exclude?(tap)

    ta = TapAuditor.new(tap, strict: args.strict?)
    ta.audit

    problems[[tap.name, tap.path]] = ta.problems if ta.problems.any?
  end

  # Check style in a single batch run up front for performance
  style_offenses = Style.check_style_json(style_files, **style_options) if style_files
  # load licenses
  spdx_license_data = SPDX.license_data
  spdx_exception_data = SPDX.exception_data

  clear_formulary_cache = [args.os, args.arch].any?

  formula_problems = audit_formulae.sort.each_with_object({}) do |f, problems|
    path = f.path

    only = only_cops ? ["style"] : args.only
    options = {
      new_formula:         new_formula,
      strict:              strict,
      online:              online,
      git:                 args.git?,
      only:                only,
      except:              args.except,
      spdx_license_data:   spdx_license_data,
      spdx_exception_data: spdx_exception_data,
      style_offenses:      style_offenses&.for_path(f.path),
      tap_audit:           tap_audit,
    }.compact

    errors = os_arch_combinations.flat_map do |os, arch|
      SimulateSystem.with os: os, arch: arch do
        odebug "Auditing Formula #{f} on os #{os} and arch #{arch}"

        Formulary.clear_cache if clear_formulary_cache

        audit_proc = proc { FormulaAuditor.new(Formulary.factory(path), **options).tap(&:audit) }

        # Audit requires full Ruby source so disable API.
        # We shouldn't do this for taps however so that we don't unnecessarily require a full Homebrew/core clone.
        fa = if f.core_formula?
          Homebrew.with_no_api_env(&audit_proc)
        else
          audit_proc.call
        end

        fa.problems + fa.new_formula_problems
      end
    end.uniq

    problems[[f.full_name, path]] = errors if errors.any?
  end

  if audit_casks.any?
    require "cask/auditor"

    if args.display_failures_only?
      odisabled "`brew audit <cask> --display-failures-only`", "`brew audit <cask>` without the argument"
    end
  end

  cask_problems = audit_casks.each_with_object({}) do |cask, problems|
    path = cask.sourcefile_path

    errors = os_arch_combinations.flat_map do |os, arch|
      next [] if os == :linux

      SimulateSystem.with os: os, arch: arch do
        odebug "Auditing Cask #{cask} on os #{os} and arch #{arch}"

        Cask::Auditor.audit(
          Cask::CaskLoader.load(path),
          # For switches, we add `|| nil` so that `nil` will be passed
          # instead of `false` if they aren't set.
          # This way, we can distinguish between "not set" and "set to false".
          audit_online:          (args.online? || nil),
          audit_strict:          (args.strict? || nil),

          # No need for `|| nil` for `--[no-]signing`
          # because boolean switches are already `nil` if not passed
          audit_signing:         args.signing?,
          audit_new_cask:        (new_cask || nil),
          audit_token_conflicts: (args.token_conflicts? || nil),
          quarantine:            true,
          any_named_args:        !no_named_args,
          only:                  args.only,
          except:                args.except,
        ).to_a
      end
    end.uniq

    problems[[cask.full_name, path]] = errors if errors.any?
  end

  print_problems(tap_problems, display_filename: args.display_filename?)
  print_problems(formula_problems, display_filename: args.display_filename?)
  print_problems(cask_problems, display_filename: args.display_filename?)

  tap_count = tap_problems.keys.count
  formula_count = formula_problems.keys.count
  cask_count = cask_problems.keys.count

  corrected_problem_count = (formula_problems.values + cask_problems.values)
                            .sum { |problems| problems.count { |problem| problem.fetch(:corrected) } }

  tap_problem_count = tap_problems.sum { |_, problems| problems.count }
  formula_problem_count = formula_problems.sum { |_, problems| problems.count }
  cask_problem_count = cask_problems.sum { |_, problems| problems.count }
  total_problems_count = formula_problem_count + cask_problem_count + tap_problem_count

  if total_problems_count.positive?
    errors_summary = Utils.pluralize("problem", total_problems_count, include_count: true)

    error_sources = []
    if formula_count.positive?
      error_sources << Utils.pluralize("formula", formula_count, plural: "e", include_count: true)
    end
    error_sources << Utils.pluralize("cask", cask_count, include_count: true) if cask_count.positive?
    error_sources << Utils.pluralize("tap", tap_count, include_count: true) if tap_count.positive?

    errors_summary += " in #{error_sources.to_sentence}" if error_sources.any?

    errors_summary += " detected"

    if corrected_problem_count.positive?
      errors_summary += ", #{Utils.pluralize("problem", corrected_problem_count, include_count: true)} corrected"
    end

    ofail "#{errors_summary}."
  end

  return unless ENV["GITHUB_ACTIONS"]

  annotations = formula_problems.merge(cask_problems).flat_map do |(_, path), problems|
    problems.map do |problem|
      GitHub::Actions::Annotation.new(
        :error,
        problem[:message],
        file:   path,
        line:   problem[:location]&.line,
        column: problem[:location]&.column,
      )
    end
  end.compact

  annotations.each do |annotation|
    puts annotation if annotation.relevant?
  end
end

.audit_argsCLI::Parser

Returns:



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
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
# File 'dev-cmd/audit.rb', line 24

def self.audit_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Check <formula> for Homebrew coding style violations. This should be run before
      submitting a new formula or cask. If no <formula>|<cask> are provided, check all
      locally available formulae and casks and skip style checks. Will exit with a
      non-zero status if any errors are found.
    EOS
    flag   "--os=",
           description: "Audit the given operating system. (Pass `all` to audit all operating systems.)"
    flag   "--arch=",
           description: "Audit the given CPU architecture. (Pass `all` to audit all architectures.)"
    switch "--strict",
           description: "Run additional, stricter style checks."
    switch "--git",
           description: "Run additional, slower style checks that navigate the Git repository."
    switch "--online",
           description: "Run additional, slower style checks that require a network connection."
    switch "--installed",
           description: "Only check formulae and casks that are currently installed."
    switch "--eval-all",
           description: "Evaluate all available formulae and casks, whether installed or not, to audit them. " \
                        "Implied if `HOMEBREW_EVAL_ALL` is set."
    switch "--new",
           description: "Run various additional style checks to determine if a new formula or cask is eligible " \
                        "for Homebrew. This should be used when creating new formulae or casks and implies " \
                        "`--strict` and `--online`."
    switch "--new-formula",
           # odeprecated: uncomment `replacement` to enable the `odeprecated` handling in `CLI::Parser`
           # replacement: "--new",
           # odeprecated: remove `args.new_formula?` calls once this is `true`
           disable: false,
           hidden:  true
    switch "--new-cask",
           # odeprecated: uncomment `replacement` to enable the `odeprecated` handling in `CLI::Parser`
           # replacement: "--new",
           # odeprecated: remove `args.new_cask?` calls once this is `true`
           disable: false,
           hidden:  true
    switch "--[no-]signing",
           description: "Audit for signed apps, which are required on ARM"
    switch "--token-conflicts",
           description: "Audit for token conflicts."
    flag   "--tap=",
           description: "Check the formulae within the given tap, specified as <user>`/`<repo>."
    switch "--fix",
           description: "Fix style violations automatically using RuboCop's auto-correct feature."
    switch "--display-cop-names",
           description: "Include the RuboCop cop name for each violation in the output. This is the default.",
           hidden:      true
    switch "--display-filename",
           description: "Prefix every line of output with the file or formula name being audited, to " \
                        "make output easy to grep."
    switch "--display-failures-only",
           description: "Only display casks that fail the audit. This is the default for formulae and casks.",
           hidden:      true
    switch "--skip-style",
           description: "Skip running non-RuboCop style checks. Useful if you plan on running " \
                        "`brew style` separately. Enabled by default unless a formula is specified by name."
    switch "-D", "--audit-debug",
           description: "Enable debugging and profiling of audit methods."
    comma_array "--only",
                description: "Specify a comma-separated <method> list to only run the methods named " \
                             "`audit_`<method>."
    comma_array "--except",
                description: "Specify a comma-separated <method> list to skip running the methods named " \
                             "`audit_`<method>."
    comma_array "--only-cops",
                description: "Specify a comma-separated <cops> list to check for violations of only the listed " \
                             "RuboCop cops."
    comma_array "--except-cops",
                description: "Specify a comma-separated <cops> list to skip checking for violations of the " \
                             "listed RuboCop cops."
    switch "--formula", "--formulae",
           description: "Treat all named arguments as formulae."
    switch "--cask", "--casks",
           description: "Treat all named arguments as casks."

    conflicts "--only", "--except"
    conflicts "--only-cops", "--except-cops", "--strict"
    conflicts "--only-cops", "--except-cops", "--only"
    conflicts "--formula", "--cask"
    conflicts "--installed", "--all"

    named_args [:formula, :cask], without_api: true
  end
end

.auditing?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'global.rb', line 112

def auditing?
  @auditing == true
end

.auto_update_command?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'global.rb', line 129

def auto_update_command?
  ENV.fetch("HOMEBREW_AUTO_UPDATE_COMMAND", false).present?
end

.auto_update_header(args:) ⇒ Object



18
19
20
21
22
23
# File 'cmd/update-report.rb', line 18

def auto_update_header(args:)
  @auto_update_header ||= begin
    ohai "Auto-updated Homebrew!" if args.auto_update?
    true
  end
end

.autoremovevoid

This method returns an undefined value.



22
23
24
25
26
# File 'cmd/autoremove.rb', line 22

def self.autoremove
  args = autoremove_args.parse

  Cleanup.autoremove(dry_run: args.dry_run?)
end

.autoremove_argsCLI::Parser

Returns:



9
10
11
12
13
14
15
16
17
18
19
# File 'cmd/autoremove.rb', line 9

def self.autoremove_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed.
    EOS
    switch "-n", "--dry-run",
           description: "List what would be uninstalled, but do not actually uninstall anything."

    named_args :none
  end
end

.autosquash!(original_commit, tap:, reason: "", verbose: false, resolve: false, cherry_picked: true) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'dev-cmd/pr-pull.rb', line 229

def self.autosquash!(original_commit, tap:, reason: "", verbose: false, resolve: false, cherry_picked: true)
  git_repo = tap.git_repo
  original_head = git_repo.head_ref

  commits = Utils.safe_popen_read("git", "-C", tap.path, "rev-list",
                                  "--reverse", "#{original_commit}..HEAD").lines.map(&:strip)

  # Generate a bidirectional mapping of commits <=> formula/cask files.
  files_to_commits = {}
  commits_to_files = commits.to_h do |commit|
    files = Utils.safe_popen_read("git", "-C", tap.path, "diff-tree", "--diff-filter=AMD",
                                  "-r", "--name-only", "#{commit}^", commit).lines.map(&:strip)
    files.each do |file|
      files_to_commits[file] ||= []
      files_to_commits[file] << commit
      tap_file = (tap.path/file).to_s
      if (tap_file.start_with?("#{tap.formula_dir}/") || tap_file.start_with?("#{tap.cask_dir}/")) &&
         File.extname(file) == ".rb"
        next
      end

      odie <<~EOS
        Autosquash can only squash commits that modify formula or cask files.
          File:   #{file}
          Commit: #{commit}
      EOS
    end
    [commit, files]
  end

  # Reset to state before cherry-picking.
  safe_system "git", "-C", tap.path, "reset", "--hard", original_commit

  # Iterate over every commit in the pull request series, but if we have to squash
  # multiple commits into one, ensure that we skip over commits we've already squashed.
  processed_commits = T.let([], T::Array[String])
  commits.each do |commit|
    next if processed_commits.include? commit

    files = commits_to_files[commit]
    if files.length == 1 && files_to_commits[files.first].length == 1
      # If there's a 1:1 mapping of commits to files, just cherry pick and (maybe) reword.
      reword_package_commit(
        commit, files.first, git_repo: git_repo, reason: reason, verbose: verbose, resolve: resolve
      )
      processed_commits << commit
    elsif files.length == 1 && files_to_commits[files.first].length > 1
      # If multiple commits modify a single file, squash them down into a single commit.
      file = files.first
      commits = files_to_commits[file]
      squash_package_commits(commits, file, git_repo: git_repo, reason: reason, verbose: verbose, resolve: resolve)
      processed_commits += commits
    else
      # We can't split commits (yet) so just raise an error.
      odie <<~EOS
        Autosquash can't split commits that modify multiple files.
          Commit: #{commit}
          Files:  #{files.join " "}
      EOS
    end
  end
rescue
  opoo "Autosquash encountered an error; resetting to original state at #{original_head}"
  system "git", "-C", tap.path, "reset", "--hard", original_head
  system "git", "-C", tap.path, "cherry-pick", "--abort" if cherry_picked
  raise
end

.backup(keg) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
# File 'reinstall.rb', line 82

def backup(keg)
  keg.unlink
  begin
    keg.rename backup_path(keg)
  rescue Errno::EACCES, Errno::ENOTEMPTY
    odie <<~EOS
      Could not rename #{keg.name} keg! Check/fix its permissions:
        sudo chown -R #{ENV.fetch("USER", "$(whoami)")} #{keg}
    EOS
  end
end

.backup_path(path) ⇒ Object



105
106
107
# File 'reinstall.rb', line 105

def backup_path(path)
  Pathname.new "#{path}.reinstall"
end

.bottleObject



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'dev-cmd/bottle.rb', line 93

def self.bottle
  args = bottle_args.parse

  if args.merge?
    Homebrew.install_bundler_gems!(groups: ["ast"])
    return merge(args: args)
  end

  gnu_tar_formula_ensure_installed_if_needed!(only_json_tab: args.only_json_tab?)

  args.named.to_resolved_formulae(uniq: false).each do |formula|
    bottle_formula formula, args: args
  end
end

.bottle_argsCLI::Parser

Returns:



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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'dev-cmd/bottle.rb', line 40

def self.bottle_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Generate a bottle (binary package) from a formula that was installed with
      `--build-bottle`.
      If the formula specifies a rebuild version, it will be incremented in the
      generated DSL. Passing `--keep-old` will attempt to keep it at its original
      value, while `--no-rebuild` will remove it.
    EOS
    switch "--skip-relocation",
           description: "Do not check if the bottle can be marked as relocatable."
    switch "--force-core-tap",
           description: "Build a bottle even if <formula> is not in `homebrew/core` or any installed taps."
    switch "--no-rebuild",
           description: "If the formula specifies a rebuild version, remove it from the generated DSL."
    switch "--keep-old",
           description: "If the formula specifies a rebuild version, attempt to preserve its value in the " \
                        "generated DSL."
    switch "--json",
           description: "Write bottle information to a JSON file, which can be used as the value for " \
                        "`--merge`."
    switch "--merge",
           description: "Generate an updated bottle block for a formula and optionally merge it into the " \
                        "formula file. Instead of a formula name, requires the path to a JSON file generated " \
                        "with `brew bottle --json` <formula>."
    switch "--write",
           depends_on:  "--merge",
           description: "Write changes to the formula file. A new commit will be generated unless " \
                        "`--no-commit` is passed."
    switch "--no-commit",
           depends_on:  "--write",
           description: "When passed with `--write`, a new commit will not generated after writing changes " \
                        "to the formula file."
    switch "--only-json-tab",
           depends_on:  "--json",
           description: "When passed with `--json`, the tab will be written to the JSON file but not the bottle."
    switch "--no-all-checks",
           depends_on:  "--merge",
           description: "Don't try to create an `all` bottle or stop a no-change upload."
    flag   "--committer=",
           description: "Specify a committer name and email in `git`'s standard author format."
    flag   "--root-url=",
           description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
    flag   "--root-url-using=",
           description: "Use the specified download strategy class for downloading the bottle's URL instead of " \
                        "Homebrew's default."

    conflicts "--no-rebuild", "--keep-old"

    named_args [:installed_formula, :file], min: 1, without_api: true
  end
end

.bottle_formula(formula, args:) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'dev-cmd/bottle.rb', line 320

def self.bottle_formula(formula, args:)
  local_bottle_json = args.json? && formula.local_bottle_path.present?

  unless local_bottle_json
    unless formula.latest_version_installed?
      return ofail "Formula not installed or up-to-date: #{formula.full_name}"
    end
    unless Utils::Bottles.built_as? formula
      return ofail "Formula was not installed with --build-bottle: #{formula.full_name}"
    end
  end

  tap = formula.tap
  if tap.nil?
    return ofail "Formula not from core or any installed taps: #{formula.full_name}" unless args.force_core_tap?

    tap = CoreTap.instance
  end
  raise TapUnavailableError, tap.name unless tap.installed?

  return ofail "Formula has no stable version: #{formula.full_name}" unless formula.stable

  bottle_tag, rebuild = if local_bottle_json
    _, tag_string, rebuild_string = Utils::Bottles.extname_tag_rebuild(formula.local_bottle_path.to_s)
    [tag_string.to_sym, rebuild_string.to_i]
  end

  bottle_tag = if bottle_tag
    Utils::Bottles::Tag.from_symbol(bottle_tag)
  else
    Utils::Bottles.tag
  end

  rebuild ||= if args.no_rebuild? || !tap
    0
  elsif args.keep_old?
    formula.bottle_specification.rebuild
  else
    ohai "Determining #{formula.full_name} bottle rebuild..."
    FormulaVersions.new(formula).formula_at_revision("origin/HEAD") do |upstream_formula|
      if formula.pkg_version == upstream_formula.pkg_version
        upstream_formula.bottle_specification.rebuild + 1
      else
        0
      end
    end || 0
  end

  filename = Bottle::Filename.create(formula, bottle_tag.to_sym, rebuild)
  local_filename = filename.to_s
  bottle_path = Pathname.pwd/filename

  tab = nil
  keg = nil

  tap_path = tap.path
  tap_git_revision = tap.git_head
  tap_git_remote = tap.remote

  root_url = args.root_url

  relocatable = T.let(false, T::Boolean)
  skip_relocation = T.let(false, T::Boolean)

  prefix = HOMEBREW_PREFIX.to_s
  cellar = HOMEBREW_CELLAR.to_s

  if local_bottle_json
    bottle_path = formula.local_bottle_path
    local_filename = bottle_path.basename.to_s

    tab_path = Utils::Bottles.receipt_path(bottle_path)
    raise "This bottle does not contain the file INSTALL_RECEIPT.json: #{bottle_path}" unless tab_path

    tab_json = Utils::Bottles.file_from_bottle(bottle_path, tab_path)
    tab = Tab.from_file_content(tab_json, tab_path)

    tag_spec = Formula[formula.name].bottle_specification.tag_specification_for(bottle_tag, no_older_versions: true)
    relocatable = [:any, :any_skip_relocation].include?(tag_spec.cellar)
    skip_relocation = tag_spec.cellar == :any_skip_relocation

    prefix = bottle_tag.default_prefix
    cellar = bottle_tag.default_cellar
  else
    tar_filename = filename.to_s.sub(/.gz$/, "")
    tar_path = Pathname.pwd/tar_filename

    keg = Keg.new(formula.prefix)
  end

  ohai "Bottling #{local_filename}..."

  formula_and_runtime_deps_names = [formula.name] + formula.runtime_dependencies.map(&:name)

  # this will be nil when using a local bottle
  keg&.lock do
    original_tab = nil
    changed_files = nil

    begin
      keg.delete_pyc_files!

      changed_files = keg.replace_locations_with_placeholders unless args.skip_relocation?

      Formula.clear_cache
      Keg.clear_cache
      Tab.clear_cache
      Dependency.clear_cache
      Requirement.clear_cache
      tab = Tab.for_keg(keg)
      original_tab = tab.dup
      tab.poured_from_bottle = false
      tab.time = nil
      tab.changed_files = changed_files.dup
      if args.only_json_tab?
        tab.changed_files.delete(Pathname.new(Tab::FILENAME))
        tab.tabfile.unlink
      else
        tab.write
      end

      keg.consistent_reproducible_symlink_permissions!

      cd cellar do
        sudo_purge
        # Tar then gzip for reproducible bottles.
        tar_mtime = tab.source_modified_time.strftime("%Y-%m-%d %H:%M:%S")
        tar, tar_args = setup_tar_and_args!(args, tar_mtime)
        safe_system tar, "--create", "--numeric-owner",
                    *tar_args,
                    "--file", tar_path, "#{formula.name}/#{formula.pkg_version}"
        sudo_purge
        # Set filename as it affects the tarball checksum.
        relocatable_tar_path = "#{formula}-bottle.tar"
        mv T.must(tar_path), relocatable_tar_path
        # Use gzip, faster to compress than bzip2, faster to uncompress than bzip2
        # or an uncompressed tarball (and more bandwidth friendly).
        Utils::Gzip.compress_with_options(relocatable_tar_path,
                                          mtime:     tab.source_modified_time,
                                          orig_name: relocatable_tar_path,
                                          output:    bottle_path)
        sudo_purge
      end

      ohai "Detecting if #{local_filename} is relocatable..." if bottle_path.size > 1 * 1024 * 1024

      prefix_check = if Homebrew.default_prefix?(prefix)
        File.join(prefix, "opt")
      else
        prefix
      end

      # Ignore matches to source code, which is not required at run time.
      # These matches may be caused by debugging symbols.
      ignores = [%r{/include/|\.(c|cc|cpp|h|hpp)$}]

      # Add additional workarounds to ignore
      ignores += formula_ignores(formula)

      repository_reference = if HOMEBREW_PREFIX == HOMEBREW_REPOSITORY
        HOMEBREW_LIBRARY
      else
        HOMEBREW_REPOSITORY
      end.to_s
      if keg_contain?(repository_reference, keg, ignores + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS, args: args)
        odie "Bottle contains non-relocatable reference to #{repository_reference}!"
      end

      relocatable = true
      if args.skip_relocation?
        skip_relocation = true
      else
        relocatable = false if keg_contain?(prefix_check, keg, ignores, formula_and_runtime_deps_names, args: args)
        relocatable = false if keg_contain?(cellar, keg, ignores, formula_and_runtime_deps_names, args: args)
        if keg_contain?(HOMEBREW_LIBRARY.to_s, keg, ignores, formula_and_runtime_deps_names, args: args)
          relocatable = false
        end
        if prefix != prefix_check
          relocatable = false if keg_contain_absolute_symlink_starting_with?(prefix, keg, args: args)
          relocatable = false if keg_contain?("#{prefix}/etc", keg, ignores, args: args)
          relocatable = false if keg_contain?("#{prefix}/var", keg, ignores, args: args)
          relocatable = false if keg_contain?("#{prefix}/share/vim", keg, ignores, args: args)
        end
        skip_relocation = relocatable && !keg.require_relocation?
      end
      puts if !relocatable && args.verbose?
    rescue Interrupt
      ignore_interrupts { bottle_path.unlink if bottle_path.exist? }
      raise
    ensure
      ignore_interrupts do
        original_tab&.write
        keg.replace_placeholders_with_locations changed_files unless args.skip_relocation?
      end
    end
  end

  bottle = BottleSpecification.new
  bottle.tap = tap
  bottle.root_url(root_url) if root_url
  bottle_cellar = if relocatable
    if skip_relocation
      :any_skip_relocation
    else
      :any
    end
  else
    cellar
  end
  bottle.rebuild rebuild
  sha256 = bottle_path.sha256
  bottle.sha256 cellar: bottle_cellar, bottle_tag.to_sym => sha256

  old_spec = formula.bottle_specification
  if args.keep_old? && !old_spec.checksums.empty?
    mismatches = [:root_url, :rebuild].reject do |key|
      old_spec.send(key) == bottle.send(key)
    end
    unless mismatches.empty?
      bottle_path.unlink if bottle_path.exist?

      mismatches.map! do |key|
        old_value = old_spec.send(key).inspect
        value = bottle.send(key).inspect
        "#{key}: old: #{old_value}, new: #{value}"
      end

      odie <<~EOS
        `--keep-old` was passed but there are changes in:
        #{mismatches.join("\n")}
      EOS
    end
  end

  output = bottle_output(bottle, args.root_url_using)

  puts "./#{local_filename}"
  puts output

  return unless args.json?

  json = {
    formula.full_name => {
      "formula" => {
        "name"             => formula.name,
        "pkg_version"      => formula.pkg_version.to_s,
        "path"             => formula.path.to_s.delete_prefix("#{HOMEBREW_REPOSITORY}/"),
        "tap_git_path"     => formula.path.to_s.delete_prefix("#{tap_path}/"),
        "tap_git_revision" => tap_git_revision,
        "tap_git_remote"   => tap_git_remote,
        # descriptions can contain emoji. sigh.
        "desc"             => formula.desc.to_s.encode(
          Encoding.find("ASCII"),
          invalid: :replace, undef: :replace, replace: "",
        ).strip,
        "license"          => SPDX.license_expression_to_string(formula.license),
        "homepage"         => formula.homepage,
      },
      "bottle"  => {
        "root_url" => bottle.root_url,
        "cellar"   => bottle_cellar.to_s,
        "rebuild"  => bottle.rebuild,
        "date"     => Pathname(filename.to_s).mtime.strftime("%F"),
        "tags"     => {
          bottle_tag.to_s => {
            "filename"       => filename.url_encode,
            "local_filename" => filename.to_s,
            "sha256"         => sha256,
            "tab"            => tab.to_bottle_hash,
          },
        },
      },
    },
  }

  puts "Writing #{filename.json}" if args.verbose?
  json_path = Pathname(filename.json)
  json_path.unlink if json_path.exist?
  json_path.write(JSON.pretty_generate(json))
end

.bottle_output(bottle, root_url_using) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'dev-cmd/bottle.rb', line 200

def self.bottle_output(bottle, root_url_using)
  cellars = bottle.checksums.map do |checksum|
    cellar = checksum["cellar"]
    next unless cellar_parameter_needed? cellar

    case cellar
    when String
      %Q("#{cellar}")
    when Symbol
      ":#{cellar}"
    end
  end.compact
  tag_column = cellars.empty? ? 0 : "cellar: #{cellars.max_by(&:length)}, ".length

  tags = bottle.checksums.map { |checksum| checksum["tag"] }
  # Start where the tag ends, add the max length of the tag, add two for the `: `
  digest_column = tag_column + tags.max_by(&:length).length + 2

  sha256_lines = bottle.checksums.map do |checksum|
    generate_sha256_line(checksum["tag"], checksum["digest"], checksum["cellar"], tag_column, digest_column)
  end
  erb_binding = bottle.instance_eval { binding }
  erb_binding.local_variable_set(:sha256_lines, sha256_lines)
  erb_binding.local_variable_set(:root_url_using, root_url_using)
  erb = ERB.new BOTTLE_ERB
  erb.result(erb_binding).gsub(/^\s*$\n/, "")
end

.bottles_hash_from_json_files(json_files, args) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'dev-cmd/pr-upload.rb', line 72

def bottles_hash_from_json_files(json_files, args)
  puts "Reading JSON files: #{json_files.join(", ")}" if args.verbose?

  bottles_hash = json_files.reduce({}) do |hash, json_file|
    hash.deep_merge(JSON.parse(File.read(json_file)))
  end

  if args.root_url
    bottles_hash.each_value do |bottle_hash|
      bottle_hash["bottle"]["root_url"] = args.root_url
    end
  end

  bottles_hash
end

.brief_build_info(formula, with_hostname:) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
# File 'cmd/gist-logs.rb', line 81

def brief_build_info(formula, with_hostname:)
  build_time_string = formula.logs.ctime.strftime("%Y-%m-%d %H:%M:%S")
  string = +<<~EOS
    Homebrew build logs for #{formula.full_name} on #{OS_VERSION}
  EOS
  if with_hostname
    hostname = Socket.gethostname
    string << "Host: #{hostname}\n"
  end
  string << "Build date: #{build_time_string}\n"
  string.freeze
end

.bumpvoid

This method returns an undefined value.



58
59
60
61
62
63
64
65
66
67
68
69
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
# File 'dev-cmd/bump.rb', line 58

def bump
  args = bump_args.parse

  Homebrew.install_bundler_gems!(groups: ["livecheck"])

  if args.limit.present? && !args.formula? && !args.cask?
    raise UsageError, "`--limit` must be used with either `--formula` or `--cask`."
  end

  Homebrew.with_no_api_env do
    formulae_and_casks = if args.installed?
      formulae = args.cask? ? [] : Formula.installed
      casks = args.formula? ? [] : Cask::Caskroom.casks
      formulae + casks
    elsif args.named.present?
      if args.formula?
        args.named.to_formulae
      elsif args.cask?
        args.named.to_casks
      else
        args.named.to_formulae_and_casks
      end
    end

    formulae_and_casks = formulae_and_casks&.sort_by do |formula_or_cask|
      formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
    end

    unless Utils::Curl.curl_supports_tls13?
      begin
        ensure_formula_installed!("curl", reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist?
      rescue FormulaUnavailableError
        opoo "A newer `curl` is required for Repology queries."
      end
    end

    if formulae_and_casks.present?
      handle_formula_and_casks(formulae_and_casks, args)
    else
      handle_api_response(args)
    end
  end
end

.bump_argsCLI::Parser

Returns:



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
# File 'dev-cmd/bump.rb', line 23

def bump_args
  CLI::Parser.new do
    description <<~EOS
      Display out-of-date brew formulae and the latest version available. If the
      returned current and livecheck versions differ or when querying specific
      formulae, also displays whether a pull request has been opened with the URL.
    EOS
    switch "--full-name",
           description: "Print formulae/casks with fully-qualified names."
    switch "--no-pull-requests",
           description: "Do not retrieve pull requests from GitHub."
    switch "--formula", "--formulae",
           description: "Check only formulae."
    switch "--cask", "--casks",
           description: "Check only casks."
    switch "--installed",
           description: "Check formulae and casks that are currently installed."
    switch "--open-pr",
           description: "Open a pull request for the new version if none have been opened yet."
    flag   "--limit=",
           description: "Limit number of package results returned."
    flag   "--start-with=",
           description: "Letter or word that the list of package results should alphabetically follow."
    switch "-f", "--force",
           description: "Ignore duplicate open PRs.",
           hidden:      true

    conflicts "--cask", "--formula"
    conflicts "--no-pull-requests", "--open-pr"

    named_args [:formula, :cask], without_api: true
  end
end

.bump_cask_prvoid

This method returns an undefined value.



67
68
69
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
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
164
165
166
167
168
169
170
171
# File 'dev-cmd/bump-cask-pr.rb', line 67

def bump_cask_pr
  args = bump_cask_pr_args.parse

  # odeprecated "brew bump-cask-pr --online" if args.online?
  # This will be run by `brew audit` or `brew style` later so run it first to
  # not start spamming during normal output.
  gem_groups = []
  gem_groups << "style" if !args.no_audit? || !args.no_style?
  gem_groups << "audit" unless args.no_audit?
  Homebrew.install_bundler_gems!(groups: gem_groups) unless gem_groups.empty?

  # As this command is simplifying user-run commands then let's just use a
  # user path, too.
  ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s

  # Use the user's browser, too.
  ENV["BROWSER"] = EnvConfig.browser

  cask = args.named.to_casks.first

  odie "This cask is not in a tap!" if cask.tap.blank?
  odie "This cask's tap is not a Git repository!" unless cask.tap.git?

  new_version = BumpVersionParser.new(
    general: args.version,
    intel:   args.version_intel,
    arm:     args.version_arm,
  )

  new_hash = unless (new_hash = args.sha256).nil?
    raise UsageError, "`--sha256` must not be empty." if new_hash.blank?

    ["no_check", ":no_check"].include?(new_hash) ? :no_check : new_hash
  end

  new_base_url = unless (new_base_url = args.url).nil?
    raise UsageError, "`--url` must not be empty." if new_base_url.blank?

    begin
      URI(new_base_url)
    rescue URI::InvalidURIError
      raise UsageError, "`--url` is not valid."
    end
  end

  if new_version.blank? && new_base_url.nil? && new_hash.nil?
    raise UsageError, "No `--version`, `--url` or `--sha256` argument specified!"
  end

  check_pull_requests(cask, args: args, new_version: new_version)

  replacement_pairs ||= []
  branch_name = "bump-#{cask.token}"
  commit_message = nil

  old_contents = File.read(cask.sourcefile_path)

  if new_base_url
    commit_message ||= "#{cask.token}: update URL"

    m = /^ +url "(.+?)"\n/m.match(old_contents)
    odie "Could not find old URL in cask!" if m.nil?

    old_base_url = m.captures.fetch(0)

    replacement_pairs << [
      /#{Regexp.escape(old_base_url)}/,
      new_base_url.to_s,
    ]
  end

  if new_version.present?
    # For simplicity, our naming defers to the arm version if we multiple architectures are specified
    branch_version = new_version.arm || new_version.general
    if branch_version.is_a?(Cask::DSL::Version)
      commit_version = shortened_version(branch_version, cask: cask)
      branch_name = "bump-#{cask.token}-#{branch_version.tr(",:", "-")}"
      commit_message ||= "#{cask.token} #{commit_version}"
    end
    replacement_pairs = replace_version_and_checksum(cask, new_hash, new_version, replacement_pairs)
  end
  # Now that we have all replacement pairs, we will replace them further down

  commit_message ||= "#{cask.token}: update checksum" if new_hash

  # Remove nested arrays where elements are identical
  replacement_pairs = replacement_pairs.reject { |pair| pair[0] == pair[1] }.uniq.compact
  Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
                                   replacement_pairs,
                                   read_only_run: args.dry_run?,
                                   silent:        args.quiet?)

  run_cask_audit(cask, old_contents, args: args)
  run_cask_style(cask, old_contents, args: args)

  pr_info = {
    branch_name:     branch_name,
    commit_message:  commit_message,
    old_contents:    old_contents,
    pr_message:      "Created with `brew bump-cask-pr`.",
    sourcefile_path: cask.sourcefile_path,
    tap:             cask.tap,
  }
  GitHub.create_bump_pr(pr_info, args: args)
end

.bump_cask_pr_argsCLI::Parser

Returns:



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
# File 'dev-cmd/bump-cask-pr.rb', line 14

def bump_cask_pr_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Create a pull request to update <cask> with a new version.

      A best effort to determine the <SHA-256> will be made if the value is not
      supplied by the user.
    EOS
    switch "-n", "--dry-run",
           description: "Print what would be done rather than doing it."
    switch "--write-only",
           description: "Make the expected file modifications without taking any Git actions."
    switch "--commit",
           depends_on:  "--write-only",
           description: "When passed with `--write-only`, generate a new commit after writing changes " \
                        "to the cask file."
    switch "--no-audit",
           description: "Don't run `brew audit` before opening the PR."
    switch "--online",
           hidden:      true
    switch "--no-style",
           description: "Don't run `brew style --fix` before opening the PR."
    switch "--no-browse",
           description: "Print the pull request URL instead of opening in a browser."
    switch "--no-fork",
           description: "Don't try to fork the repository."
    flag   "--version=",
           description: "Specify the new <version> for the cask."
    flag   "--version-arm=",
           description: "Specify the new cask <version> for the ARM architecture."
    flag   "--version-intel=",
           description: "Specify the new cask <version> for the Intel architecture."
    flag   "--message=",
           description: "Prepend <message> to the default pull request message."
    flag   "--url=",
           description: "Specify the <URL> for the new download."
    flag   "--sha256=",
           description: "Specify the <SHA-256> checksum of the new download."
    flag   "--fork-org=",
           description: "Use the specified GitHub organization for forking."
    switch "-f", "--force",
           description: "Ignore duplicate open PRs."

    conflicts "--dry-run", "--write"
    conflicts "--no-audit", "--online"
    conflicts "--version=", "--version-arm="
    conflicts "--version=", "--version-intel="

    named_args :cask, number: 1, without_api: true
  end
end

.bump_formula_prObject



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
164
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'dev-cmd/bump-formula-pr.rb', line 92

def bump_formula_pr
  args = bump_formula_pr_args.parse

  if args.revision.present? && args.tag.nil? && args.version.nil?
    raise UsageError, "`--revision` must be passed with either `--tag` or `--version`!"
  end

  # As this command is simplifying user-run commands then let's just use a
  # user path, too.
  ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s

  # Use the user's browser, too.
  ENV["BROWSER"] = Homebrew::EnvConfig.browser

  formula = args.named.to_formulae.first
  new_url = args.url
  raise FormulaUnspecifiedError if formula.blank?

  odie "This formula is disabled!" if formula.disabled?
  odie "This formula is deprecated and does not build!" if formula.deprecation_reason == :does_not_build
  odie "This formula is not in a tap!" if formula.tap.blank?
  odie "This formula's tap is not a Git repository!" unless formula.tap.git?

  formula_spec = formula.stable
  odie "#{formula}: no stable specification found!" if formula_spec.blank?

  # This will be run by `brew audit` later so run it first to not start
  # spamming during normal output.
  Homebrew.install_bundler_gems!(groups: ["audit", "style"]) unless args.no_audit?

  tap_remote_repo = formula.tap.full_name || formula.tap.remote_repo
  remote = "origin"
  remote_branch = formula.tap.git_repo.origin_branch_name
  previous_branch = "-"

  check_open_pull_requests(formula, tap_remote_repo, args: args)

  new_version = args.version
  check_new_version(formula, tap_remote_repo, version: new_version, args: args) if new_version.present?

  opoo "This formula has patches that may be resolved upstream." if formula.patchlist.present?
  if formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") }
    opoo "This formula has resources that may need to be updated."
  end

  old_mirrors = formula_spec.mirrors
  new_mirrors ||= args.mirror
  new_mirror ||= determine_mirror(new_url)
  new_mirrors ||= [new_mirror] if new_mirror.present?

  check_for_mirrors(formula, old_mirrors, new_mirrors, args: args) if new_url.present?

  old_hash = formula_spec.checksum&.hexdigest
  new_hash = args.sha256
  new_tag = args.tag
  new_revision = args.revision
  old_url = formula_spec.url
  old_tag = formula_spec.specs[:tag]
  old_formula_version = formula_version(formula)
  old_version = old_formula_version.to_s
  forced_version = new_version.present?
  new_url_hash = if new_url.present? && new_hash.present?
    check_new_version(formula, tap_remote_repo, url: new_url, args: args) if new_version.blank?
    true
  elsif new_tag.present? && new_revision.present?
    check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag, args: args) if new_version.blank?
    false
  elsif old_hash.blank?
    if new_tag.blank? && new_version.blank? && new_revision.blank?
      raise UsageError, "#{formula}: no `--tag` or `--version` argument specified!"
    end

    if old_tag.present?
      new_tag ||= old_tag.gsub(old_version, new_version)
      if new_tag == old_tag
        odie <<~EOS
          You need to bump this formula manually since the new tag
          and old tag are both #{new_tag}.
        EOS
      end
      check_new_version(formula, tap_remote_repo, url: old_url, tag: new_tag, args: args) if new_version.blank?
      resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, old_url, tag: new_tag)
      new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD")
      new_revision = new_revision.strip
    elsif new_revision.blank?
      odie "#{formula}: the current URL requires specifying a `--revision=` argument."
    end
    false
  elsif new_url.blank? && new_version.blank?
    raise UsageError, "#{formula}: no `--url` or `--version` argument specified!"
  else
    new_url ||= PyPI.update_pypi_url(old_url, new_version)
    if new_url.blank?
      new_url = old_url.gsub(old_version, new_version)
      if new_mirrors.blank? && old_mirrors.present?
        new_mirrors = old_mirrors.map do |old_mirror|
          old_mirror.gsub(old_version, new_version)
        end
      end
    end
    if new_url == old_url
      odie <<~EOS
        You need to bump this formula manually since the new URL
        and old URL are both:
          #{new_url}
      EOS
    end
    check_new_version(formula, tap_remote_repo, url: new_url, args: args) if new_version.blank?
    resource_path, forced_version = fetch_resource_and_forced_version(formula, new_version, new_url)
    Utils::Tar.validate_file(resource_path)
    new_hash = resource_path.sha256
  end

  replacement_pairs = []
  if formula.revision.nonzero?
    replacement_pairs << [
      /^  revision \d+\n(\n(  head "))?/m,
      "\\2",
    ]
  end

  replacement_pairs += formula_spec.mirrors.map do |mirror|
    [
      / +mirror "#{Regexp.escape(mirror)}"\n/m,
      "",
    ]
  end

  replacement_pairs += if new_url_hash.present?
    [
      [
        /#{Regexp.escape(formula_spec.url)}/,
        new_url,
      ],
      [
        old_hash,
        new_hash,
      ],
    ]
  elsif new_tag.present?
    [
      [
        /tag:(\s+")#{formula_spec.specs[:tag]}(?=")/,
        "tag:\\1#{new_tag}\\2",
      ],
      [
        formula_spec.specs[:revision],
        new_revision,
      ],
    ]
  elsif new_url.present?
    [
      [
        /#{Regexp.escape(formula_spec.url)}/,
        new_url,
      ],
      [
        formula_spec.specs[:revision],
        new_revision,
      ],
    ]
  else
    [
      [
        formula_spec.specs[:revision],
        new_revision,
      ],
    ]
  end

  old_contents = formula.path.read

  if new_mirrors.present?
    replacement_pairs << [
      /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m,
      "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n",
    ]
  end

  if forced_version && new_version != "0"
    replacement_pairs << if old_contents.include?("version \"#{old_formula_version}\"")
      [
        "version \"#{old_formula_version}\"",
        "version \"#{new_version}\"",
      ]
    elsif new_mirrors.present?
      [
        /^( +)(mirror "#{Regexp.escape(new_mirrors.last)}"\n)/m,
        "\\1\\2\\1version \"#{new_version}\"\n",
      ]
    elsif new_url.present?
      [
        /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m,
        "\\1\\2\\1version \"#{new_version}\"\n",
      ]
    elsif new_revision.present?
      [
        /^( {2})( +)(:revision => "#{new_revision}"\n)/m,
        "\\1\\2\\3\\1version \"#{new_version}\"\n",
      ]
    end
  elsif forced_version && new_version == "0"
    replacement_pairs << [
      /^  version "[\w.\-+]+"\n/m,
      "",
    ]
  end
  new_contents = Utils::Inreplace.inreplace_pairs(formula.path,
                                                  replacement_pairs.uniq.compact,
                                                  read_only_run: args.dry_run?,
                                                  silent:        args.quiet?)

  new_formula_version = formula_version(formula, new_contents)

  if new_formula_version < old_formula_version
    formula.path.atomic_write(old_contents) unless args.dry_run?
    odie <<~EOS
      You need to bump this formula manually since changing the version
      from #{old_formula_version} to #{new_formula_version} would be a downgrade.
    EOS
  elsif new_formula_version == old_formula_version
    formula.path.atomic_write(old_contents) unless args.dry_run?
    odie <<~EOS
      You need to bump this formula manually since the new version
      and old version are both #{new_formula_version}.
    EOS
  end

  alias_rename = alias_update_pair(formula, new_formula_version)
  if alias_rename.present?
    ohai "Renaming alias #{alias_rename.first} to #{alias_rename.last}"
    alias_rename.map! { |a| formula.tap.alias_dir/a }
  end

  unless args.dry_run?
    resources_checked = PyPI.update_python_resources! formula,
                                                      version:                  new_formula_version.to_s,
                                                      package_name:             args.python_package_name,
                                                      extra_packages:           args.python_extra_packages,
                                                      exclude_packages:         args.python_exclude_packages,
                                                      silent:                   args.quiet?,
                                                      ignore_non_pypi_packages: true
  end

  run_audit(formula, alias_rename, old_contents, args: args)

  pr_message = "Created with `brew bump-formula-pr`."
  if resources_checked.nil? && formula.resources.any? { |resource| !resource.name.start_with?("homebrew-") }
    pr_message += <<~EOS


      - [ ] `resource` blocks have been checked for updates.
    EOS
  end

  if new_url =~ %r{^https://github\.com/([\w-]+)/([\w-]+)/archive/refs/tags/(v?[.0-9]+)\.tar\.}
    owner = Regexp.last_match(1)
    repo = Regexp.last_match(2)
    tag = Regexp.last_match(3)
    github_release_data = begin
      GitHub::API.open_rest("#{GitHub::API_URL}/repos/#{owner}/#{repo}/releases/tags/#{tag}")
    rescue GitHub::API::HTTPNotFoundError
      # If this is a 404: we can't do anything.
      nil
    end

    if github_release_data.present?
      pre = "pre" if github_release_data["prerelease"].present?
      pr_message += <<~XML
        <details>
          <summary>#{pre}release notes</summary>
          <pre>#{github_release_data["body"]}</pre>
        </details>
      XML
    end
  end

  pr_info = {
    sourcefile_path:  formula.path,
    old_contents:     old_contents,
    additional_files: alias_rename,
    remote:           remote,
    remote_branch:    remote_branch,
    branch_name:      "bump-#{formula.name}-#{new_formula_version}",
    commit_message:   "#{formula.name} #{new_formula_version}",
    previous_branch:  previous_branch,
    tap:              formula.tap,
    tap_remote_repo:  tap_remote_repo,
    pr_message:       pr_message,
  }
  GitHub.create_bump_pr(pr_info, args: args)
end

.bump_formula_pr_argsCLI::Parser

Returns:



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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'dev-cmd/bump-formula-pr.rb', line 13

def bump_formula_pr_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Create a pull request to update <formula> with a new URL or a new tag.

      If a <URL> is specified, the <SHA-256> checksum of the new download should also
      be specified. A best effort to determine the <SHA-256> will be made if not supplied
      by the user.

      If a <tag> is specified, the Git commit <revision> corresponding to that tag
      should also be specified. A best effort to determine the <revision> will be made
      if the value is not supplied by the user.

      If a <version> is specified, a best effort to determine the <URL> and <SHA-256> or
      the <tag> and <revision> will be made if both values are not supplied by the user.

      *Note:* this command cannot be used to transition a formula from a
      URL-and-SHA-256 style specification into a tag-and-revision style specification,
      nor vice versa. It must use whichever style specification the formula already uses.
    EOS
    switch "-n", "--dry-run",
           description: "Print what would be done rather than doing it."
    switch "--write-only",
           description: "Make the expected file modifications without taking any Git actions."
    switch "--commit",
           depends_on:  "--write-only",
           description: "When passed with `--write-only`, generate a new commit after writing changes " \
                        "to the formula file."
    switch "--no-audit",
           description: "Don't run `brew audit` before opening the PR."
    switch "--strict",
           description: "Run `brew audit --strict` before opening the PR."
    switch "--online",
           description: "Run `brew audit --online` before opening the PR."
    switch "--no-browse",
           description: "Print the pull request URL instead of opening in a browser."
    switch "--no-fork",
           description: "Don't try to fork the repository."
    comma_array "--mirror",
                description: "Use the specified <URL> as a mirror URL. If <URL> is a comma-separated list " \
                             "of URLs, multiple mirrors will be added."
    flag   "--fork-org=",
           description: "Use the specified GitHub organization for forking."
    flag   "--version=",
           description: "Use the specified <version> to override the value parsed from the URL or tag. Note " \
                        "that `--version=0` can be used to delete an existing version override from a " \
                        "formula if it has become redundant."
    flag   "--message=",
           description: "Prepend <message> to the default pull request message."
    flag   "--url=",
           description: "Specify the <URL> for the new download. If a <URL> is specified, the <SHA-256> " \
                        "checksum of the new download should also be specified."
    flag   "--sha256=",
           depends_on:  "--url=",
           description: "Specify the <SHA-256> checksum of the new download."
    flag   "--tag=",
           description: "Specify the new git commit <tag> for the formula."
    flag   "--revision=",
           description: "Specify the new commit <revision> corresponding to the specified git <tag> " \
                        "or specified <version>."
    switch "-f", "--force",
           description: "Ignore duplicate open PRs. Remove all mirrors if `--mirror` was not specified."
    flag   "--python-package-name=",
           description: "Use the specified <package-name> when finding Python resources for <formula>. " \
                        "If no package name is specified, it will be inferred from the formula's stable URL."
    comma_array "--python-extra-packages=",
                description: "Include these additional Python packages when finding resources."
    comma_array "--python-exclude-packages=",
                description: "Exclude these Python packages when finding resources."

    conflicts "--dry-run", "--write-only"
    conflicts "--no-audit", "--strict"
    conflicts "--no-audit", "--online"
    conflicts "--url", "--tag"

    named_args :formula, max: 1, without_api: true
  end
end

.bump_revisionObject



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
75
76
77
# File 'dev-cmd/bump-revision.rb', line 32

def bump_revision
  args = bump_revision_args.parse

  # As this command is simplifying user-run commands then let's just use a
  # user path, too.
  ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s

  args.named.to_formulae.each do |formula|
    current_revision = formula.revision
    new_revision = current_revision + 1

    if args.dry_run?
      unless args.quiet?
        old_text = "revision #{current_revision}"
        new_text = "revision #{new_revision}"
        if current_revision.zero?
          ohai "add #{new_text.inspect}"
        else
          ohai "replace #{old_text.inspect} with #{new_text.inspect}"
        end
      end
    else
      Homebrew.install_bundler_gems!(groups: ["ast"])
      require "utils/ast"

      formula_ast = Utils::AST::FormulaAST.new(formula.path.read)
      if current_revision.zero?
        formula_ast.add_stanza(:revision, new_revision)
      else
        formula_ast.replace_stanza(:revision, new_revision)
      end
      formula_ast.remove_stanza(:bottle) if args.remove_bottle_block?
      formula.path.atomic_write(formula_ast.process)
    end

    message = "#{formula.name}: revision bump #{args.message}"
    if args.dry_run?
      ohai "git commit --no-edit --verbose --message=#{message} -- #{formula.path}"
    elsif !args.write_only?
      formula.path.parent.cd do
        safe_system "git", "commit", "--no-edit", "--verbose",
                    "--message=#{message}", "--", formula.path
      end
    end
  end
end

.bump_revision_argsCLI::Parser

Returns:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'dev-cmd/bump-revision.rb', line 11

def bump_revision_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Create a commit to increment the revision of <formula>. If no revision is
      present, "revision 1" will be added.
    EOS
    switch "-n", "--dry-run",
           description: "Print what would be done rather than doing it."
    switch "--remove-bottle-block",
           description: "Remove the bottle block in addition to bumping the revision."
    switch "--write-only",
           description: "Make the expected file modifications without taking any Git actions."
    flag   "--message=",
           description: "Append <message> to the default commit message."

    conflicts "--dry-run", "--write-only"

    named_args :formula, min: 1, without_api: true
  end
end

.bump_unversioned_cask(cask, state:, dry_run:) ⇒ Hash{String => T.untyped}?

Parameters:

Returns:



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
164
165
166
167
168
169
170
171
172
173
174
175
# File 'dev-cmd/bump-unversioned-casks.rb', line 91

def self.bump_unversioned_cask(cask, state:, dry_run:)
  ohai "Checking #{cask.full_name}"

  unversioned_cask_checker = UnversionedCaskChecker.new(cask)

  if !unversioned_cask_checker.single_app_cask? &&
     !unversioned_cask_checker.single_pkg_cask? &&
     !unversioned_cask_checker.single_qlplugin_cask?
    opoo "Skipping, not a single-app or PKG cask."
    return
  end

  last_check_time = state["check_time"]&.then { |t| Time.parse(t) }

  check_time = Time.now
  if last_check_time && (check_time - last_check_time) / 3600 < 24
    opoo "Skipping, already checked within the last 24 hours."
    return
  end

  last_sha256 = state["sha256"]
  last_time = state["time"]&.then { |t| Time.parse(t) }
  last_file_size = state["file_size"]

  download = Cask::Download.new(cask)
  time, file_size = begin
    download.time_file_size
  rescue
    [nil, nil]
  end

  if last_time != time || last_file_size != file_size
    sha256 = begin
      Timeout.timeout(5 * 60) do
        unversioned_cask_checker.installer.download.sha256
      end
    rescue => e
      onoe e
    end

    if sha256.present? && last_sha256 != sha256
      version = begin
        Timeout.timeout(60) do
          unversioned_cask_checker.guess_cask_version
        end
      rescue Timeout::Error
        onoe "Timed out guessing version for cask '#{cask}'."
      end

      if version
        if cask.version == version
          oh1 "Cask #{cask} is up-to-date at #{version}"
        else
          bump_cask_pr_args = [
            "bump-cask-pr",
            "--version", version.to_s,
            "--sha256", ":no_check",
            "--message", "Automatic update via `brew bump-unversioned-casks`.",
            cask.sourcefile_path
          ]

          if dry_run
            bump_cask_pr_args << "--dry-run"
            oh1 "Would bump #{cask} from #{cask.version} to #{version}"
          else
            oh1 "Bumping #{cask} from #{cask.version} to #{version}"
          end

          begin
            system_command! HOMEBREW_BREW_FILE, args: bump_cask_pr_args
          rescue ErrorDuringExecution => e
            onoe e
          end
        end
      end
    end
  end

  {
    "sha256"     => sha256,
    "check_time" => check_time.iso8601,
    "time"       => time&.iso8601,
    "file_size"  => file_size,
  }
end

.bump_unversioned_casksvoid

This method returns an undefined value.



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
75
76
77
78
79
80
81
82
83
84
85
# File 'dev-cmd/bump-unversioned-casks.rb', line 33

def self.bump_unversioned_casks
  args = bump_unversioned_casks_args.parse

  Homebrew.install_bundler_gems!(groups: ["bump_unversioned_casks"])

  state_file = if args.state_file.present?
    Pathname(args.state_file).expand_path
  else
    HOMEBREW_CACHE/"bump_unversioned_casks.json"
  end
  state_file.dirname.mkpath

  state = state_file.exist? ? JSON.parse(state_file.read) : {}

  casks = args.named.to_paths(only: :cask, recurse_tap: true).map { |path| Cask::CaskLoader.load(path) }

  unversioned_casks = casks.select do |cask|
    cask.url&.unversioned? && !cask.livecheckable? && !cask.discontinued?
  end

  ohai "Unversioned Casks: #{unversioned_casks.count} (#{state.size} cached)"

  checked, unchecked = unversioned_casks.partition { |c| state.key?(c.full_name) }

  queue = Queue.new

  # Start with random casks which have not been checked.
  unchecked.shuffle.each do |c|
    queue.enq c
  end

  # Continue with previously checked casks, ordered by when they were last checked.
  checked.sort_by { |c| state.dig(c.full_name, "check_time") }.each do |c|
    queue.enq c
  end

  limit = args.limit.presence&.to_i
  end_time = Time.now + (limit * 60) if limit

  until queue.empty? || (end_time && end_time < Time.now)
    cask = queue.deq

    key = cask.full_name

    new_state = bump_unversioned_cask(cask, state: state.fetch(key, {}), dry_run: args.dry_run?)

    next unless new_state

    state[key] = new_state

    state_file.atomic_write JSON.pretty_generate(state) unless args.dry_run?
  end
end

.bump_unversioned_casks_argsCLI::Parser

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'dev-cmd/bump-unversioned-casks.rb', line 16

def self.bump_unversioned_casks_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Check all casks with unversioned URLs in a given <tap> for updates.
    EOS
    switch "-n", "--dry-run",
           description: "Do everything except caching state and opening pull requests."
    flag   "--limit=",
           description: "Maximum runtime in minutes."
    flag   "--state-file=",
           description: "File for caching state."

    named_args [:cask, :tap], min: 1, without_api: true
  end
end

.bundler_definitionObject

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



41
42
43
# File 'utils/gems.rb', line 41

def bundler_definition
  @bundler_definition ||= Bundler::Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, false)
end

.catObject



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
# File 'dev-cmd/cat.rb', line 25

def self.cat
  args = cat_args.parse

  cd HOMEBREW_REPOSITORY do
    pager = if Homebrew::EnvConfig.bat?
      ENV["BAT_CONFIG_PATH"] = Homebrew::EnvConfig.bat_config_path
      ENV["BAT_THEME"] = Homebrew::EnvConfig.bat_theme
      ensure_formula_installed!(
        "bat",
        reason:           "displaying <formula>/<cask> source",
        # The user might want to capture the output of `brew cat ...`
        # Redirect stdout to stderr
        output_to_stderr: true,
      ).opt_bin/"bat"
    else
      "cat"
    end

    args.named.to_paths.each do |path|
      next path if path.exist?

      path = path.basename(".rb") if args.cask?

      ofail "#{path}'s source doesn't exist on disk."
    end

    if Homebrew.failed?
      $stderr.puts "The name may be wrong, or the tap hasn't been tapped. Instead try:"
      treat_as = "--cask " if args.cask?
      treat_as = "--formula " if args.formula?
      $stderr.puts "  brew info --github #{treat_as}#{args.named.join(" ")}"
      return
    end

    safe_system pager, *args.named.to_paths
  end
end

.cat_argsCLI::Parser

Returns:



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'dev-cmd/cat.rb', line 8

def self.cat_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display the source of a <formula> or <cask>.
    EOS

    switch "--formula", "--formulae",
           description: "Treat all named arguments as formulae."
    switch "--cask", "--casks",
           description: "Treat all named arguments as casks."

    conflicts "--formula", "--cask"

    named_args [:formula, :cask], min: 1, without_api: true
  end
end

.cellar_parameter_needed?(cellar) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
179
180
181
182
183
# File 'dev-cmd/bottle.rb', line 176

def self.cellar_parameter_needed?(cellar)
  default_cellars = [
    Homebrew::DEFAULT_MACOS_CELLAR,
    Homebrew::DEFAULT_MACOS_ARM_CELLAR,
    Homebrew::DEFAULT_LINUX_CELLAR,
  ]
  cellar.present? && default_cellars.exclude?(cellar)
end

.changed_packages(tap, original_commit) ⇒ Object



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'dev-cmd/pr-pull.rb', line 323

def self.changed_packages(tap, original_commit)
  formulae = Utils.popen_read("git", "-C", tap.path, "diff-tree",
                              "-r", "--name-only", "--diff-filter=AM",
                              original_commit, "HEAD", "--", tap.formula_dir)
                  .lines
                  .map do |line|
    next unless line.end_with? ".rb\n"

    name = "#{tap.name}/#{File.basename(line.chomp, ".rb")}"
    if Homebrew::EnvConfig.disable_load_formula?
      opoo "Can't check if updated bottles are necessary as HOMEBREW_DISABLE_LOAD_FORMULA is set!"
      break
    end
    begin
      Formulary.resolve(name)
    rescue FormulaUnavailableError
      nil
    end
  end.compact
  casks = Utils.popen_read("git", "-C", tap.path, "diff-tree",
                           "-r", "--name-only", "--diff-filter=AM",
                           original_commit, "HEAD", "--", tap.cask_dir)
               .lines
               .map do |line|
    next unless line.end_with? ".rb\n"

    name = "#{tap.name}/#{File.basename(line.chomp, ".rb")}"
    begin
      Cask::CaskLoader.load(name)
    rescue Cask::CaskUnavailableError
      nil
    end
  end.compact
  formulae + casks
end

.changed_test_filesObject

Raises:



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'dev-cmd/tests.rb', line 71

def changed_test_files
  changed_files = Utils.popen_read("git", "diff", "--name-only", "master")

  raise UsageError, "No files have been changed from the master branch!" if changed_files.blank?

  filestub_regex = %r{Library/Homebrew/([\w/-]+).rb}
  changed_files.scan(filestub_regex).map(&:last).map do |filestub|
    if filestub.start_with?("test/")
      # Only run tests on *_spec.rb files in test/ folder
      filestub.end_with?("_spec") ? Pathname("#{filestub}.rb") : nil
    else
      # For all other changed .rb files guess the associated test file name
      Pathname("test/#{filestub}_spec.rb")
    end
  end.compact.select(&:exist?)
end

.check_bottled_formulae!(bottles_hash) ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'dev-cmd/pr-upload.rb', line 45

def check_bottled_formulae!(bottles_hash)
  bottles_hash.each do |name, bottle_hash|
    formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]
    formula_version = Formulary.factory(formula_path).pkg_version
    bottle_version = PkgVersion.parse bottle_hash["formula"]["pkg_version"]
    next if formula_version == bottle_version

    odie "Bottles are for #{name} #{bottle_version} but formula is version #{formula_version}!"
  end
end

.check_closed_pull_requests(formula, tap_remote_repo, args:, version:) ⇒ Object



461
462
463
464
465
466
467
468
# File 'dev-cmd/bump-formula-pr.rb', line 461

def check_closed_pull_requests(formula, tap_remote_repo, args:, version:)
  # if we haven't already found open requests, try for an exact match across closed requests
  GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo,
                                           version: version,
                                           state:   "closed",
                                           file:    formula.path.relative_path_from(formula.tap.path).to_s,
                                           args:    args)
end

.check_for_mirrors(formula, old_mirrors, new_mirrors, args:) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
# File 'dev-cmd/bump-formula-pr.rb', line 398

def check_for_mirrors(formula, old_mirrors, new_mirrors, args:)
  return if new_mirrors.present? || old_mirrors.empty?

  if args.force?
    opoo "#{formula}: Removing all mirrors because a `--mirror=` argument was not specified."
  else
    odie <<~EOS
      #{formula}: a `--mirror=` argument for updating the mirror URL(s) was not specified.
      Use `--force` to remove all mirrors.
    EOS
  end
end

.check_head_spec(dependents) ⇒ Object



223
224
225
226
227
# File 'cmd/deps.rb', line 223

def self.check_head_spec(dependents)
  headless = dependents.select { |d| d.is_a?(Formula) && d.active_spec_sym != :head }
                       .to_sentence two_words_connector: " or ", last_word_connector: " or "
  opoo "No head spec for #{headless}, using stable spec instead" unless headless.empty?
end

.check_new_version(formula, tap_remote_repo, args:, version: nil, url: nil, tag: nil) ⇒ Object



439
440
441
442
443
444
445
446
447
448
449
# File 'dev-cmd/bump-formula-pr.rb', line 439

def check_new_version(formula, tap_remote_repo, args:, version: nil, url: nil, tag: nil)
  if version.nil?
    specs = {}
    specs[:tag] = tag if tag.present?
    version = Version.detect(url, **specs)
    return if version.null?
  end

  check_throttle(formula, version)
  check_closed_pull_requests(formula, tap_remote_repo, args: args, version: version)
end

.check_open_pull_requests(formula, tap_remote_repo, args:) ⇒ Object



432
433
434
435
436
437
# File 'dev-cmd/bump-formula-pr.rb', line 432

def check_open_pull_requests(formula, tap_remote_repo, args:)
  GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo,
                                           state: "open",
                                           file:  formula.path.relative_path_from(formula.tap.path).to_s,
                                           args:  args)
end

.check_pull_requests(cask, args:, new_version:) ⇒ void

This method returns an undefined value.

Parameters:



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'dev-cmd/bump-cask-pr.rb', line 247

def check_pull_requests(cask, args:, new_version:)
  tap_remote_repo = cask.tap.full_name || cask.tap.remote_repo

  GitHub.check_for_duplicate_pull_requests(cask.token, tap_remote_repo,
                                           state:   "open",
                                           version: nil,
                                           file:    cask.sourcefile_path.relative_path_from(cask.tap.path).to_s,
                                           args:    args)

  # if we haven't already found open requests, try for an exact match across closed requests
  new_version.instance_variables.each do |version_type|
    version = new_version.instance_variable_get(version_type)
    next if version.blank?

    GitHub.check_for_duplicate_pull_requests(
      cask.token,
      tap_remote_repo,
      state:   "closed",
      version: shortened_version(version, cask: cask),
      file:    cask.sourcefile_path.relative_path_from(cask.tap.path).to_s,
      args:    args,
    )
  end
end

.check_throttle(formula, new_version) ⇒ Object



451
452
453
454
455
456
457
458
459
# File 'dev-cmd/bump-formula-pr.rb', line 451

def check_throttle(formula, new_version)
  throttled_rate = formula.tap.audit_exceptions.dig(:throttled_formulae, formula.name)
  return if throttled_rate.blank?

  formula_suffix = Version.new(new_version).patch.to_i
  return if formula_suffix.modulo(throttled_rate).zero?

  odie "#{formula} should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}"
end

.cherry_pick_pr!(user, repo, pull_request, args:, path: ".") ⇒ Object



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'dev-cmd/pr-pull.rb', line 297

def self.cherry_pick_pr!(user, repo, pull_request, args:, path: ".")
  if args.dry_run?
    puts <<~EOS
      git fetch --force origin +refs/pull/#{pull_request}/head
      git merge-base HEAD FETCH_HEAD
      git cherry-pick --ff --allow-empty $merge_base..FETCH_HEAD
    EOS
    return
  end

  commits = GitHub.pull_request_commits(user, repo, pull_request)
  safe_system "git", "-C", path, "fetch", "--quiet", "--force", "origin", commits.last
  ohai "Using #{commits.count} commit#{"s" if commits.count != 1} from ##{pull_request}"
  Utils::Git.cherry_pick!(path, "--ff", "--allow-empty", *commits, verbose: args.verbose?, resolve: args.resolve?)
end

.clean_argvObject

Remove the --debug, --verbose and --quiet options which cause problems for IRB and have already been parsed by the CLI::Parser.



93
94
95
96
97
98
# File 'dev-cmd/irb.rb', line 93

def clean_argv
  global_options = Homebrew::CLI::Parser
                   .global_options
                   .flat_map { |options| options[0..1] }
  ARGV.reject! { |arg| global_options.include?(arg) }
end

.cleanupvoid

This method returns an undefined value.



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 'cmd/cleanup.rb', line 37

def cleanup
  args = cleanup_args.parse

  days = args.prune.presence&.then do |prune|
    case prune
    when /\A\d+\Z/
      prune.to_i
    when "all"
      0
    else
      raise UsageError, "`--prune` expects an integer or `all`."
    end
  end

  cleanup = Cleanup.new(*args.named, dry_run: args.dry_run?, scrub: args.s?, days: days)
  if args.prune_prefix?
    cleanup.prune_prefix_symlinks_and_directories
    return
  end

  cleanup.clean!(quiet: args.quiet?, periodic: false)

  unless cleanup.disk_cleanup_size.zero?
    disk_space = disk_usage_readable(cleanup.disk_cleanup_size)
    if args.dry_run?
      ohai "This operation would free approximately #{disk_space} of disk space."
    else
      ohai "This operation has freed approximately #{disk_space} of disk space."
    end
  end

  return if cleanup.unremovable_kegs.empty?

  ofail <<~EOS
    Could not cleanup old kegs! Fix your permissions on:
      #{cleanup.unremovable_kegs.join "\n  "}
  EOS
end

.cleanup_argsCLI::Parser

Returns:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'cmd/cleanup.rb', line 11

def cleanup_args
  Homebrew::CLI::Parser.new do
    days = Homebrew::EnvConfig::ENVS[:HOMEBREW_CLEANUP_MAX_AGE_DAYS][:default]
    description <<~EOS
      Remove stale lock files and outdated downloads for all formulae and casks,
      and remove old versions of installed formulae. If arguments are specified,
      only do this for the given formulae and casks. Removes all downloads more than
      #{days} days old. This can be adjusted with `HOMEBREW_CLEANUP_MAX_AGE_DAYS`.
    EOS
    flag   "--prune=",
           description: "Remove all cache files older than specified <days>. " \
                        "If you want to remove everything, use `--prune=all`."
    switch "-n", "--dry-run",
           description: "Show what would be removed, but do not actually remove anything."
    switch "-s",
           description: "Scrub the cache, including downloads for even the latest versions. " \
                        "Note that downloads for any installed formulae or casks will still not be deleted. " \
                        "If you want to delete those too: `rm -rf \"$(brew --cache)\"`"
    switch "--prune-prefix",
           description: "Only prune the symlinks and directories from the prefix and remove no other files."

    named_args [:formula, :cask]
  end
end

.commandObject



21
22
23
24
25
26
27
28
29
# File 'dev-cmd/command.rb', line 21

def command
  args = command_args.parse

  args.named.each do |cmd|
    path = Commands.path(cmd)
    odie "Unknown command: #{cmd}" unless path
    puts path
  end
end

.command_argsCLI::Parser

Returns:



11
12
13
14
15
16
17
18
19
# File 'dev-cmd/command.rb', line 11

def command_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display the path to the file being used when invoking `brew` <cmd>.
    EOS

    named_args :command, min: 1
  end
end

.commandsvoid

This method returns an undefined value.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'cmd/commands.rb', line 26

def commands
  args = commands_args.parse

  if args.quiet?
    puts Formatter.columns(Commands.commands(aliases: args.include_aliases?))
    return
  end

  prepend_separator = T.let(false, T::Boolean)

  {
    "Built-in commands"           => Commands.internal_commands,
    "Built-in developer commands" => Commands.internal_developer_commands,
    "External commands"           => Commands.external_commands,
  }.each do |title, commands|
    next if commands.blank?

    puts if prepend_separator
    ohai title, Formatter.columns(commands)

    prepend_separator ||= true
  end
end

.commands_argsCLI::Parser

Returns:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'cmd/commands.rb', line 10

def commands_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Show lists of built-in and external commands.
    EOS
    switch "-q", "--quiet",
           description: "List only the names of commands without category headers."
    switch "--include-aliases",
           depends_on:  "--quiet",
           description: "Include aliases of internal commands."

    named_args :none
  end
end

.completionsvoid

This method returns an undefined value.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'cmd/completions.rb', line 29

def completions
  args = completions_args.parse

  case args.named.first
  when nil, "state"
    if Completions.link_completions?
      puts "Completions are linked."
    else
      puts "Completions are not linked."
    end
  when "link"
    Completions.link!
    puts "Completions are now linked."
  when "unlink"
    Completions.unlink!
    puts "Completions are no longer linked."
  else
    raise UsageError, "unknown subcommand: #{args.named.first}"
  end
end

.completions_argsCLI::Parser

Returns:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'cmd/completions.rb', line 11

def completions_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Control whether Homebrew automatically links external tap shell completion files.
      Read more at <https://docs.brew.sh/Shell-Completion>.

      `brew completions` [`state`]:
      Display the current state of Homebrew's completions.

      `brew completions` (`link`|`unlink`):
      Link or unlink Homebrew's completions.
    EOS

    named_args %w[state link unlink], max: 1
  end
end

.condense_requirements(deps, args:) ⇒ Object



172
173
174
175
# File 'cmd/deps.rb', line 172

def self.condense_requirements(deps, args:)
  deps.select! { |dep| dep.is_a?(Dependency) } unless args.include_requirements?
  deps.select! { |dep| dep.is_a?(Requirement) || dep.installed? } if args.installed?
end

.configvoid

This method returns an undefined value.



23
24
25
26
27
# File 'cmd/config.rb', line 23

def config
  config_args.parse

  SystemConfig.dump_verbose_config
end

.config_argsCLI::Parser

Returns:



11
12
13
14
15
16
17
18
19
20
# File 'cmd/config.rb', line 11

def config_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Show Homebrew and system configuration info useful for debugging. If you file
      a bug report, you will be required to provide this information.
    EOS

    named_args :none
  end
end

.contributionsvoid

This method returns an undefined value.



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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'dev-cmd/contributions.rb', line 49

def contributions
  args = contributions_args.parse

  results = {}
  grand_totals = {}

  repos = if args.repositories.blank? || args.repositories.include?("primary")
    PRIMARY_REPOS
  elsif args.repositories.include?("all")
    SUPPORTED_REPOS
  else
    args.repositories
  end

  from = args.from.presence || Date.today.prev_year.iso8601

  contribution_types = [:author, :committer, :coauthorship, :review]

  users = args.user.presence || GitHub.members_by_team("Homebrew", "maintainers")
  users.each do |username, _|
    # TODO: Using the GitHub username to scan the `git log` undercounts some
    # contributions as people might not always have configured their Git
    # committer details to match the ones on GitHub.
    # TODO: Switch to using the GitHub APIs instead of `git log` if
    # they ever support trailers.
    results[username] = scan_repositories(repos, username, args, from: from)
    grand_totals[username] = total(results[username])

    contributions = contribution_types.map do |type|
      type_count = grand_totals[username][type]
      next if type_count.to_i.zero?

      "#{Utils.pluralize("time", type_count, include_count: true)} (#{type})"
    end.compact
    contributions << "#{Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)} (total)"

    puts [
      "#{username} contributed",
      *contributions.to_sentence,
      "#{time_period(from: from, to: args.to)}.",
    ].join(" ")
  end

  return unless args.csv?

  puts
  puts generate_csv(grand_totals)
end

.contributions_argsCLI::Parser

Returns:



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
# File 'dev-cmd/contributions.rb', line 19

def contributions_args
  Homebrew::CLI::Parser.new do
    usage_banner "`contributions` [--user=<email|username>] [<--repositories>`=`] [<--csv>]"
    description <<~EOS
      Summarise contributions to Homebrew repositories.
    EOS

    comma_array "--repositories",
                description: "Specify a comma-separated list of repositories to search. " \
                             "Supported repositories: #{SUPPORTED_REPOS.map { |t| "`#{t}`" }.to_sentence}. " \
                             "Omitting this flag, or specifying `--repositories=primary`, searches only the " \
                             "main repositories: brew,core,cask. " \
                             "Specifying `--repositories=all`, searches all repositories. "
    flag "--from=",
         description: "Date (ISO-8601 format) to start searching contributions. " \
                      "Omitting this flag searches the last year."

    flag "--to=",
         description: "Date (ISO-8601 format) to stop searching contributions."

    comma_array "--user=",
                description: "Specify a comma-separated list of GitHub usernames or email addresses to find " \
                             "contributions from. Omitting this flag searches maintainers."

    switch "--csv",
           description: "Print a CSV of contributions across repositories over the time period."
  end
end

.core_cask_path?(path) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


37
38
39
# File 'dev-cmd/edit.rb', line 37

def core_cask_path?(path)
  path.fnmatch?("**/homebrew-cask/Casks/**.rb", File::FNM_DOTMATCH)
end

.core_cask_tap?(path) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


47
48
49
# File 'dev-cmd/edit.rb', line 47

def core_cask_tap?(path)
  path == CoreCaskTap.instance.path
end

.core_formula_path?(path) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


32
33
34
# File 'dev-cmd/edit.rb', line 32

def core_formula_path?(path)
  path.fnmatch?("**/homebrew-core/Formula/**.rb", File::FNM_DOTMATCH)
end

.core_formula_tap?(path) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


42
43
44
# File 'dev-cmd/edit.rb', line 42

def core_formula_tap?(path)
  path == CoreTap.instance.path
end

.count_reviews(repo_full_name, person, args) ⇒ Integer

Parameters:

Returns:

  • (Integer)


205
206
207
208
209
210
211
212
# File 'dev-cmd/contributions.rb', line 205

def count_reviews(repo_full_name, person, args)
  GitHub.count_issues("", is: "pr", repo: repo_full_name, reviewed_by: person, review: "approved", args: args)
rescue GitHub::API::ValidationFailedError
  if args.verbose?
    onoe "Couldn't search GitHub for PRs by #{person}. Their profile might be private. Defaulting to 0."
  end
  0 # Users who have made their contributions private are not searchable to determine counts.
end

.createObject

Create a formula from a tarball URL.



73
74
75
76
77
78
79
80
81
82
83
# File 'dev-cmd/create.rb', line 73

def create
  args = create_args.parse

  path = if args.cask?
    create_cask(args: args)
  else
    create_formula(args: args)
  end

  exec_editor path
end

.create_argsCLI::Parser

Returns:



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
# File 'dev-cmd/create.rb', line 15

def create_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Generate a formula or, with `--cask`, a cask for the downloadable file at <URL>
      and open it in the editor. Homebrew will attempt to automatically derive the
      formula name and version, but if it fails, you'll have to make your own template.
      The `wget` formula serves as a simple example. For the complete API, see:
      <https://rubydoc.brew.sh/Formula>
    EOS
    switch "--autotools",
           description: "Create a basic template for an Autotools-style build."
    switch "--cask",
           description: "Create a basic template for a cask."
    switch "--cmake",
           description: "Create a basic template for a CMake-style build."
    switch "--crystal",
           description: "Create a basic template for a Crystal build."
    switch "--go",
           description: "Create a basic template for a Go build."
    switch "--meson",
           description: "Create a basic template for a Meson-style build."
    switch "--node",
           description: "Create a basic template for a Node build."
    switch "--perl",
           description: "Create a basic template for a Perl build."
    switch "--python",
           description: "Create a basic template for a Python build."
    switch "--ruby",
           description: "Create a basic template for a Ruby build."
    switch "--rust",
           description: "Create a basic template for a Rust build."
    switch "--no-fetch",
           description: "Homebrew will not download <URL> to the cache and will thus not add its SHA-256 " \
                        "to the formula for you, nor will it check the GitHub API for GitHub projects " \
                        "(to fill out its description and homepage)."
    switch "--HEAD",
           description: "Indicate that <URL> points to the package's repository rather than a file."
    flag   "--set-name=",
           description: "Explicitly set the <name> of the new formula or cask."
    flag   "--set-version=",
           description: "Explicitly set the <version> of the new formula or cask."
    flag   "--set-license=",
           description: "Explicitly set the <license> of the new formula."
    flag   "--tap=",
           description: "Generate the new formula within the given tap, specified as <user>`/`<repo>."
    switch "-f", "--force",
           description: "Ignore errors for disallowed formula names and names that shadow aliases."

    conflicts "--autotools", "--cmake", "--crystal", "--go", "--meson", "--node",
              "--perl", "--python", "--ruby", "--rust", "--cask"
    conflicts "--cask", "--HEAD"
    conflicts "--cask", "--set-license"

    named_args :url, number: 1
  end
end

.create_cask(args:) ⇒ Object



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
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'dev-cmd/create.rb', line 85

def create_cask(args:)
  url = args.named.first
  name = if args.set_name.blank?
    stem = Pathname.new(url).stem.rpartition("=").last
    print "Cask name [#{stem}]: "
    __gets || stem
  else
    args.set_name
  end
  token = Cask::Utils.token_from(name)

  cask_tap = Tap.fetch(args.tap || "homebrew/cask")
  raise TapUnavailableError, cask_tap.name unless cask_tap.installed?

  cask_path = cask_tap.new_cask_path(token)
  cask_path.dirname.mkpath unless cask_path.dirname.exist?
  raise Cask::CaskAlreadyCreatedError, token if cask_path.exist?

  version = if args.set_version
    Version.new(args.set_version)
  else
    Version.detect(url.gsub(token, "").gsub(/x86(_64)?/, ""))
  end

  interpolated_url, sha256 = if version.null?
    [url, ""]
  else
    sha256 = if args.no_fetch?
      ""
    else
      strategy = DownloadStrategyDetector.detect(url)
      downloader = strategy.new(url, token, version.to_s, cache: Cask::Cache.path)
      downloader.fetch
      downloader.cached_location.sha256
    end

    [url.gsub(version.to_s, "\#{version}"), sha256]
  end

  cask_path.atomic_write <<~RUBY
    cask "#{token}" do
      version "#{version}"
      sha256 "#{sha256}"

      url "#{interpolated_url}"
      name "#{name}"
      desc ""
      homepage ""

      app ""
    end
  RUBY

  puts "Please run `brew audit --cask --new #{token}` before submitting, thanks."
  cask_path
end

.create_formula(args:) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'dev-cmd/create.rb', line 142

def create_formula(args:)
  mode = if args.autotools?
    :autotools
  elsif args.cmake?
    :cmake
  elsif args.crystal?
    :crystal
  elsif args.go?
    :go
  elsif args.meson?
    :meson
  elsif args.node?
    :node
  elsif args.perl?
    :perl
  elsif args.python?
    :python
  elsif args.ruby?
    :ruby
  elsif args.rust?
    :rust
  end

  fc = FormulaCreator.new(
    args.set_name,
    args.set_version,
    tap:     args.tap,
    url:     args.named.first,
    mode:    mode,
    license: args.set_license,
    fetch:   !args.no_fetch?,
    head:    args.HEAD?,
  )
  fc.parse_url
  # ask for confirmation if name wasn't passed explicitly
  if args.set_name.blank?
    print "Formula name [#{fc.name}]: "
    fc.name = __gets || fc.name
  end

  fc.verify

  # Check for disallowed formula, or names that shadow aliases,
  # unless --force is specified.
  unless args.force?
    if (reason = MissingFormula.disallowed_reason(fc.name))
      odie <<~EOS
        The formula '#{fc.name}' is not allowed to be created.
        #{reason}
        If you really want to create this formula use `--force`.
      EOS
    end

    Homebrew.with_no_api_env do
      if Formula.aliases.include? fc.name
        realname = Formulary.canonical_name(fc.name)
        odie <<~EOS
          The formula '#{realname}' is already aliased to '#{fc.name}'.
          Please check that you are not creating a duplicate.
          To force creation use `--force`.
        EOS
      end
    end
  end

  path = fc.write_formula!

  formula = Homebrew.with_no_api_env do
    Formula[fc.name]
  end
  PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?

  puts "Please run `brew audit --new #{fc.name}` before submitting, thanks."
  path
end

.decorate_dependencies(dependencies) ⇒ Object



350
351
352
353
354
355
356
357
358
359
# File 'cmd/info.rb', line 350

def decorate_dependencies(dependencies)
  deps_status = dependencies.map do |dep|
    if dep.satisfied?([])
      pretty_installed(dep_display_s(dep))
    else
      pretty_uninstalled(dep_display_s(dep))
    end
  end
  deps_status.join(", ")
end

.decorate_requirements(requirements) ⇒ Object



361
362
363
364
365
366
367
# File 'cmd/info.rb', line 361

def decorate_requirements(requirements)
  req_status = requirements.map do |req|
    req_s = req.display_s
    req.satisfied? ? pretty_installed(req_s) : pretty_uninstalled(req_s)
  end
  req_status.join(", ")
end

.default_prefix?(prefix = HOMEBREW_PREFIX) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
# File 'global.rb', line 95

def default_prefix?(prefix = HOMEBREW_PREFIX)
  prefix.to_s == DEFAULT_PREFIX
end

.dep_display_name(dep, args:) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'cmd/deps.rb', line 177

def self.dep_display_name(dep, args:)
  str = if dep.is_a? Requirement
    if args.include_requirements?
      ":#{dep.display_s}"
    else
      # This shouldn't happen, but we'll put something here to help debugging
      "::#{dep.name}"
    end
  elsif args.full_name?
    dep.to_formula.full_name
  else
    dep.name
  end

  if args.annotate?
    str = "#{str} " if args.tree?
    str = "#{str} [build]" if dep.build?
    str = "#{str} [test]" if dep.test?
    str = "#{str} [optional]" if dep.optional?
    str = "#{str} [recommended]" if dep.recommended?
    str = "#{str} [implicit]" if dep.implicit?
  end

  str
end

.dep_display_s(dep) ⇒ Object



369
370
371
372
373
# File 'cmd/info.rb', line 369

def dep_display_s(dep)
  return dep.name if dep.option_tags.empty?

  "#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}"
end

.dependables(formula, args:) ⇒ Object



289
290
291
292
293
294
295
296
# File 'cmd/deps.rb', line 289

def self.dependables(formula, args:)
  includes, ignores = args_includes_ignores(args)
  deps = @use_runtime_dependencies ? formula.runtime_dependencies : formula.deps
  deps = select_includes(deps, ignores, includes)
  reqs = select_includes(formula.requirements, ignores, includes) if args.include_requirements?
  reqs ||= []
  reqs + deps
end

.depsObject



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
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
164
165
166
# File 'cmd/deps.rb', line 85

def self.deps
  args = deps_args.parse

  all = args.eval_all?

  Formulary.enable_factory_cache!

  recursive = !args.direct?
  installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?)

  @use_runtime_dependencies = installed && recursive &&
                              !args.tree? &&
                              !args.graph? &&
                              !args.HEAD? &&
                              !args.include_build? &&
                              !args.include_test? &&
                              !args.include_optional? &&
                              !args.skip_recommended? &&
                              !args.missing?

  if args.tree? || args.graph?
    dependents = if args.named.present?
      sorted_dependents(args.named.to_formulae_and_casks)
    elsif args.installed?
      case args.only_formula_or_cask
      when :formula
        sorted_dependents(Formula.installed)
      when :cask
        sorted_dependents(Cask::Caskroom.casks)
      else
        sorted_dependents(Formula.installed + Cask::Caskroom.casks)
      end
    else
      raise FormulaUnspecifiedError
    end

    if args.graph?
      dot_code = dot_code(dependents, recursive: recursive, args: args)
      if args.dot?
        puts dot_code
      else
        exec_browser "https://dreampuf.github.io/GraphvizOnline/##{ERB::Util.url_encode(dot_code)}"
      end
      return
    end

    puts_deps_tree dependents, recursive: recursive, args: args
    return
  elsif all
    puts_deps sorted_dependents(Formula.all(eval_all: args.eval_all?) + Cask::Cask.all), recursive: recursive,
                                                                                         args:      args
    return
  elsif !args.no_named? && args.for_each?
    puts_deps sorted_dependents(args.named.to_formulae_and_casks), recursive: recursive, args: args
    return
  end

  if args.no_named?
    raise FormulaUnspecifiedError unless args.installed?

    sorted_dependents_formulae_and_casks = case args.only_formula_or_cask
    when :formula
      sorted_dependents(Formula.installed)
    when :cask
      sorted_dependents(Cask::Caskroom.casks)
    else
      sorted_dependents(Formula.installed + Cask::Caskroom.casks)
    end
    puts_deps sorted_dependents_formulae_and_casks, recursive: recursive, args: args
    return
  end

  dependents = dependents(args.named.to_formulae_and_casks)
  check_head_spec(dependents) if args.HEAD?

  all_deps = deps_for_dependents(dependents, recursive: recursive, args: args, &(args.union? ? :| : :&))
  condense_requirements(all_deps, args: args)
  all_deps.map! { |d| dep_display_name(d, args: args) }
  all_deps.uniq!
  all_deps.sort! unless args.topological?
  puts all_deps
end

.deps_argsCLI::Parser

Returns:



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
75
76
77
78
79
80
81
82
83
# File 'cmd/deps.rb', line 13

def self.deps_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Show dependencies for <formula>. When given multiple formula arguments,
      show the intersection of dependencies for each formula. By default, `deps`
      shows all required and recommended dependencies.

      If any version of each formula argument is installed and no other options
      are passed, this command displays their actual runtime dependencies (similar
      to `brew linkage`), which may differ from the current versons' stated
      dependencies if the installed versions are outdated.

      *Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`.
    EOS
    switch "-n", "--topological",
           description: "Sort dependencies in topological order."
    switch "-1", "--direct", "--declared", "--1",
           description: "Show only the direct dependencies declared in the formula."
    switch "--union",
           description: "Show the union of dependencies for multiple <formula>, instead of the intersection."
    switch "--full-name",
           description: "List dependencies by their full name."
    switch "--include-build",
           description: "Include `:build` dependencies for <formula>."
    switch "--include-optional",
           description: "Include `:optional` dependencies for <formula>."
    switch "--include-test",
           description: "Include `:test` dependencies for <formula> (non-recursive)."
    switch "--skip-recommended",
           description: "Skip `:recommended` dependencies for <formula>."
    switch "--include-requirements",
           description: "Include requirements in addition to dependencies for <formula>."
    switch "--tree",
           description: "Show dependencies as a tree. When given multiple formula arguments, " \
                        "show individual trees for each formula."
    switch "--graph",
           description: "Show dependencies as a directed graph."
    switch "--dot",
           depends_on:  "--graph",
           description: "Show text-based graph description in DOT format."
    switch "--annotate",
           description: "Mark any build, test, implicit, optional, or recommended dependencies as " \
                        "such in the output."
    switch "--installed",
           description: "List dependencies for formulae that are currently installed. If <formula> is " \
                        "specified, list only its dependencies that are currently installed."
    switch "--missing",
           description: "Show only missing dependencies."
    switch "--eval-all",
           description: "Evaluate all available formulae and casks, whether installed or not, to list " \
                        "their dependencies."
    switch "--for-each",
           description: "Switch into the mode used by the `--eval-all` option, but only list dependencies " \
                        "for each provided <formula>, one formula per line. This is used for " \
                        "debugging the `--installed`/`--eval-all` display mode."
    switch "--HEAD",
           description: "Show dependencies for HEAD version instead of stable version."
    switch "--formula", "--formulae",
           description: "Treat all named arguments as formulae."
    switch "--cask", "--casks",
           description: "Treat all named arguments as casks."

    conflicts "--tree", "--graph"
    conflicts "--installed", "--missing"
    conflicts "--installed", "--eval-all"
    conflicts "--formula", "--cask"
    formula_options

    named_args [:formula, :cask]
  end
end

.deps_for_dependent(dependency, args:, recursive: false) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'cmd/deps.rb', line 203

def self.deps_for_dependent(dependency, args:, recursive: false)
  includes, ignores = args_includes_ignores(args)

  deps = dependency.runtime_dependencies if @use_runtime_dependencies

  if recursive
    deps ||= recursive_includes(Dependency, dependency, includes, ignores)
    reqs   = recursive_includes(Requirement, dependency, includes, ignores)
  else
    deps ||= select_includes(dependency.deps, ignores, includes)
    reqs   = select_includes(dependency.requirements, ignores, includes)
  end

  deps + reqs.to_a
end

.deps_for_dependents(dependents, args:, recursive: false, &block) ⇒ Object



219
220
221
# File 'cmd/deps.rb', line 219

def self.deps_for_dependents(dependents, args:, recursive: false, &block)
  dependents.map { |d| deps_for_dependent(d, recursive: recursive, args: args) }.reduce(&block)
end

.deps_uses_from_formulae(all_formulae) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'dev-cmd/unbottled.rb', line 160

def deps_uses_from_formulae(all_formulae)
  ohai "Populating dependency tree..."

  deps_hash = {}
  uses_hash = {}

  all_formulae.each do |f|
    deps = Dependency.expand(f, cache_key: "unbottled") do |_, dep|
      Dependency.prune if dep.optional?
    end.map(&:to_formula)
    deps_hash[f.name] = deps

    deps.each do |dep|
      uses_hash[dep.name] ||= []
      uses_hash[dep.name] << f
    end
  end

  [deps_hash, uses_hash]
end

.descObject



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
# File 'cmd/desc.rb', line 42

def desc
  args = desc_args.parse

  if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
    raise UsageError, "`brew desc` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
  end

  search_type = if args.search?
    :either
  elsif args.name?
    :name
  elsif args.description?
    :desc
  end

  if search_type.blank?
    desc = {}
    args.named.to_formulae_and_casks.each do |formula_or_cask|
      if formula_or_cask.is_a? Formula
        desc[formula_or_cask.full_name] = formula_or_cask.desc
      else
        description = formula_or_cask.desc.presence || Formatter.warning("[no description]")
        desc[formula_or_cask.full_name] = "(#{formula_or_cask.name.join(", ")}) #{description}"
      end
    end
    Descriptions.new(desc).print
  else
    query = args.named.join(" ")
    string_or_regex = Search.query_regexp(query)
    Search.search_descriptions(string_or_regex, args, search_type: search_type)
  end
end

.desc_argsCLI::Parser

Returns:



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
# File 'cmd/desc.rb', line 13

def desc_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display <formula>'s name and one-line description.
      The cache is created on the first search, making that search slower than subsequent ones.
    EOS
    switch "-s", "--search",
           description: "Search both names and descriptions for <text>. If <text> is flanked by " \
                        "slashes, it is interpreted as a regular expression."
    switch "-n", "--name",
           description: "Search just names for <text>. If <text> is flanked by slashes, it is " \
                        "interpreted as a regular expression."
    switch "-d", "--description",
           description: "Search just descriptions for <text>. If <text> is flanked by slashes, " \
                        "it is interpreted as a regular expression."
    switch "--eval-all",
           description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
                        "descriptions. Implied if `HOMEBREW_EVAL_ALL` is set."
    switch "--formula", "--formulae",
           description: "Treat all named arguments as formulae."
    switch "--cask", "--casks",
           description: "Treat all named arguments as casks."

    conflicts "--search", "--name", "--description"

    named_args [:formula, :cask, :text_or_regex], min: 1
  end
end

.determine_bump_subject(old_contents, new_contents, subject_path, reason: nil) ⇒ Object



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
# File 'dev-cmd/pr-pull.rb', line 129

def self.determine_bump_subject(old_contents, new_contents, subject_path, reason: nil)
  subject_path = Pathname(subject_path)
  tap          = Tap.from_path(subject_path)
  subject_name = subject_path.basename.to_s.chomp(".rb")
  is_cask      = subject_path.to_s.start_with?("#{tap.cask_dir}/")
  name         = is_cask ? "cask" : "formula"

  new_package = get_package(tap, subject_name, subject_path, new_contents)

  return "#{subject_name}: delete #{reason}".strip if new_package.blank?

  old_package = get_package(tap, subject_name, subject_path, old_contents)

  if old_package.blank?
    "#{subject_name} #{new_package.version} (new #{name})"
  elsif old_package.version != new_package.version
    "#{subject_name} #{new_package.version}"
  elsif !is_cask && old_package.revision != new_package.revision
    "#{subject_name}: revision #{reason}".strip
  elsif is_cask && old_package.sha256 != new_package.sha256
    "#{subject_name}: checksum update #{reason}".strip
  else
    "#{subject_name}: #{reason || "rebuild"}".strip
  end
end

.determine_mirror(url) ⇒ Object



385
386
387
388
389
390
391
392
393
394
395
396
# File 'dev-cmd/bump-formula-pr.rb', line 385

def determine_mirror(url)
  case url
  when %r{.*ftp\.gnu\.org/gnu.*}
    url.sub "ftp.gnu.org/gnu", "ftpmirror.gnu.org"
  when %r{.*download\.savannah\.gnu\.org/*}
    url.sub "download.savannah.gnu.org", "download-mirror.savannah.gnu.org"
  when %r{.*www\.apache\.org/dyn/closer\.lua\?path=.*}
    url.sub "www.apache.org/dyn/closer.lua?path=", "archive.apache.org/dist/"
  when %r{.*mirrors\.ocf\.berkeley\.edu/debian.*}
    url.sub "mirrors.ocf.berkeley.edu/debian", "mirrorservice.org/sites/ftp.debian.org/debian"
  end
end

.determine_test_runnersvoid

This method returns an undefined value.



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
# File 'dev-cmd/determine-test-runners.rb', line 36

def self.determine_test_runners
  args = determine_test_runners_args.parse

  if args.no_named? && !args.all_supported?
    raise Homebrew::CLI::MinNamedArgumentsError, 1
  elsif args.all_supported? && !args.no_named?
    raise UsageError, "`--all-supported` is mutually exclusive to other arguments."
  end

  testing_formulae = args.named.first&.split(",").to_a
  testing_formulae.map! { |name| TestRunnerFormula.new(Formulary.factory(name), eval_all: args.eval_all?) }
                  .freeze
  deleted_formulae = args.named.second&.split(",").to_a.freeze
  runner_matrix = GitHubRunnerMatrix.new(testing_formulae, deleted_formulae,
                                         all_supported:    args.all_supported?,
                                         dependent_matrix: args.dependents?)
  runners = runner_matrix.active_runner_specs_hash

  ohai "Runners", JSON.pretty_generate(runners)

  github_output = ENV.fetch("GITHUB_OUTPUT")
  File.open(github_output, "a") do |f|
    f.puts("runners=#{runners.to_json}")
    f.puts("runners_present=#{runners.present?}")
  end
end

.determine_test_runners_argsHomebrew::CLI::Parser



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'dev-cmd/determine-test-runners.rb', line 10

def self.determine_test_runners_args
  Homebrew::CLI::Parser.new do
    usage_banner <<~EOS
      `determine-test-runners` {<testing-formulae> [<deleted-formulae>]|--all-supported}

      Determines the runners used to test formulae or their dependents. For internal use in Homebrew taps.
    EOS
    switch "--all-supported",
           description: "Instead of selecting runners based on the chosen formula, return all supported runners."
    switch "--eval-all",
           description: "Evaluate all available formulae, whether installed or not, to determine testing " \
                        "dependents.",
           env:         :eval_all
    switch "--dependents",
           description: "Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`.",
           depends_on:  "--eval-all"

    named_args max: 2

    conflicts "--all-supported", "--dependents"

    hide_from_man_page!
  end
end

.developerObject



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
# File 'cmd/developer.rb', line 28

def developer
  args = developer_args.parse

  env_vars = []
  env_vars << "HOMEBREW_DEVELOPER" if Homebrew::EnvConfig.developer?
  env_vars << "HOMEBREW_UPDATE_TO_TAG" if Homebrew::EnvConfig.update_to_tag?
  env_vars.map! do |var|
    "#{Tty.bold}#{var}#{Tty.reset}"
  end

  case args.named.first
  when nil, "state"
    if env_vars.any?
      puts "Developer mode is enabled because #{env_vars.to_sentence} #{(env_vars.count == 1) ? "is" : "are"} set."
    elsif Homebrew::Settings.read("devcmdrun") == "true"
      puts "Developer mode is enabled."
    else
      puts "Developer mode is disabled."
    end
  when "on"
    Homebrew::Settings.write "devcmdrun", true
  when "off"
    Homebrew::Settings.delete "devcmdrun"
    puts "To fully disable developer mode, you must unset #{env_vars.to_sentence}." if env_vars.any?
  else
    raise UsageError, "unknown subcommand: #{args.named.first}"
  end
end

.developer_argsCLI::Parser

Returns:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'cmd/developer.rb', line 10

def developer_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Control Homebrew's developer mode. When developer mode is enabled,
      `brew update` will update Homebrew to the latest commit on the `master`
      branch instead of the latest stable version along with some other behaviour changes.

      `brew developer` [`state`]:
      Display the current state of Homebrew's developer mode.

      `brew developer` (`on`|`off`):
      Turn Homebrew's developer mode on or off respectively.
    EOS

    named_args %w[state on off], max: 1
  end
end

.dispatch_build_bottleObject

Raises:



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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'dev-cmd/dispatch-build-bottle.rb', line 40

def dispatch_build_bottle
  args = dispatch_build_bottle_args.parse

  tap = Tap.fetch(args.tap || CoreTap.instance.name)
  user, repo = tap.full_name.split("/")
  ref = "master"
  workflow = args.workflow || "dispatch-build-bottle.yml"

  runners = []

  if (macos = args.macos&.compact_blank) && macos.present?
    runners += macos.map do |element|
      # We accept runner name syntax (11-arm64) or bottle syntax (arm64_big_sur)
      os, arch = element.then do |s|
        tag = Utils::Bottles::Tag.from_symbol(s.to_sym)
        [tag.to_macos_version, tag.arch]
      rescue ArgumentError, MacOSVersion::Error
        os, arch = s.split("-", 2)
        [MacOSVersion.new(os), arch&.to_sym]
      end

      if arch.present? && arch != :x86_64
        "#{os}-#{arch}"
      else
        os.to_s
      end
    end
  end

  if args.linux?
    runners << "ubuntu-22.04"
  elsif args.linux_self_hosted?
    runners << "linux-self-hosted-1"
  end

  raise UsageError, "Must specify `--macos`, `--linux` or `--linux-self-hosted` option." if runners.empty?

  args.named.to_resolved_formulae.each do |formula|
    # Required inputs
    inputs = {
      runner:  runners.join(","),
      formula: formula.name,
    }

    # Optional inputs
    # These cannot be passed as nil to GitHub API
    inputs[:timeout] = args.timeout if args.timeout
    inputs[:issue] = args.issue if args.issue
    inputs[:upload] = args.upload?

    ohai "Dispatching #{tap} bottling request of formula \"#{formula.name}\" for #{runners.join(", ")}"
    GitHub.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
  end
end

.dispatch_build_bottle_argsCLI::Parser

Returns:



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
# File 'dev-cmd/dispatch-build-bottle.rb', line 11

def dispatch_build_bottle_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Build bottles for these formulae with GitHub Actions.
    EOS
    flag   "--tap=",
           description: "Target tap repository (default: `homebrew/core`)."
    flag   "--timeout=",
           description: "Build timeout (in minutes, default: 60)."
    flag   "--issue=",
           description: "If specified, post a comment to this issue number if the job fails."
    comma_array "--macos",
                description: "macOS version (or comma-separated list of versions) the bottle should be built for."
    flag   "--workflow=",
           description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
    switch "--upload",
           description: "Upload built bottles."
    switch "--linux",
           description: "Dispatch bottle for Linux (using GitHub runners)."
    switch "--linux-self-hosted",
           description: "Dispatch bottle for Linux (using self-hosted runner)."
    switch "--linux-wheezy",
           description: "Use Debian Wheezy container for building the bottle on Linux."

    conflicts "--linux", "--linux-self-hosted"
    named_args :formula, min: 1
  end
end

.docsvoid

This method returns an undefined value.



19
20
21
# File 'cmd/docs.rb', line 19

def docs
  exec_browser HOMEBREW_DOCS_WWW
end

.docs_argsCLI::Parser

Returns:



10
11
12
13
14
15
16
# File 'cmd/docs.rb', line 10

def docs_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Open Homebrew's online documentation at <#{HOMEBREW_DOCS_WWW}> in a browser.
    EOS
  end
end

.doctorObject



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
75
76
77
78
79
# File 'cmd/doctor.rb', line 30

def self.doctor
  args = doctor_args.parse

  inject_dump_stats!(Diagnostic::Checks, /^check_*/) if args.audit_debug?

  checks = Diagnostic::Checks.new(verbose: args.verbose?)

  if args.list_checks?
    puts checks.all
    return
  end

  if args.no_named?
    slow_checks = %w[
      check_for_broken_symlinks
      check_missing_deps
    ]
    methods = (checks.all - slow_checks) + slow_checks
    methods -= checks.cask_checks unless Cask::Caskroom.any_casks_installed?
  else
    methods = args.named
  end

  first_warning = T.let(true, T::Boolean)
  methods.each do |method|
    $stderr.puts Formatter.headline("Checking #{method}", color: :magenta) if args.debug?
    unless checks.respond_to?(method)
      ofail "No check available by the name: #{method}"
      next
    end

    out = checks.send(method)
    next if out.blank?

    if first_warning
      $stderr.puts <<~EOS
        #{Tty.bold}Please note that these warnings are just used to help the Homebrew maintainers
        with debugging if you file an issue. If everything you use Homebrew for is
        working fine: please don't worry or file an issue; just ignore this. Thanks!#{Tty.reset}
      EOS
    end

    $stderr.puts
    opoo out
    Homebrew.failed = true
    first_warning = false
  end

  puts "Your system is ready to brew." if !Homebrew.failed? && !args.quiet?
end

.doctor_argsCLI::Parser

Returns: