Module: Homebrew

Extended by:
Context, DependenciesHelpers, FileUtils, Fetch, Install, Search, SystemCommand::Mixin, T::Sig
Defined in:
brew/Library/Homebrew/extend/os/linux/install.rb,
brew/Library/Homebrew/extend/os/mac/search.rb,
brew/Library/Homebrew/extend/os/mac/diagnostic.rb,
brew/Library/Homebrew/extend/os/linux/diagnostic.rb,
brew/Library/Homebrew/extend/os/mac/missing_formula.rb,
brew/Library/Homebrew/help.rb,
brew/Library/Homebrew/fetch.rb,
brew/Library/Homebrew/style.rb,
brew/Library/Homebrew/utils.rb,
brew/Library/Homebrew/global.rb,
brew/Library/Homebrew/search.rb,
brew/Library/Homebrew/unlink.rb,
brew/Library/Homebrew/cleanup.rb,
brew/Library/Homebrew/cmd/log.rb,
brew/Library/Homebrew/cmd/pin.rb,
brew/Library/Homebrew/cmd/tap.rb,
brew/Library/Homebrew/install.rb,
brew/Library/Homebrew/upgrade.rb,
brew/Library/Homebrew/cli/args.rb,
brew/Library/Homebrew/cmd/cask.rb,
brew/Library/Homebrew/cmd/deps.rb,
brew/Library/Homebrew/cmd/desc.rb,
brew/Library/Homebrew/cmd/help.rb,
brew/Library/Homebrew/cmd/home.rb,
brew/Library/Homebrew/cmd/info.rb,
brew/Library/Homebrew/cmd/link.rb,
brew/Library/Homebrew/cmd/list.rb,
brew/Library/Homebrew/cmd/uses.rb,
brew/Library/Homebrew/settings.rb,
brew/Library/Homebrew/cmd/--env.rb,
brew/Library/Homebrew/cmd/fetch.rb,
brew/Library/Homebrew/cmd/unpin.rb,
brew/Library/Homebrew/cmd/untap.rb,
brew/Library/Homebrew/reinstall.rb,
brew/Library/Homebrew/uninstall.rb,
brew/Library/Homebrew/cli/parser.rb,
brew/Library/Homebrew/cmd/config.rb,
brew/Library/Homebrew/cmd/doctor.rb,
brew/Library/Homebrew/cmd/leaves.rb,
brew/Library/Homebrew/cmd/search.rb,
brew/Library/Homebrew/cmd/switch.rb,
brew/Library/Homebrew/cmd/unlink.rb,
brew/Library/Homebrew/dev-cmd/sh.rb,
brew/Library/Homebrew/diagnostic.rb,
brew/Library/Homebrew/env_config.rb,
brew/Library/Homebrew/utils/gems.rb,
brew/Library/Homebrew/cmd/--cache.rb,
brew/Library/Homebrew/cmd/cleanup.rb,
brew/Library/Homebrew/cmd/install.rb,
brew/Library/Homebrew/cmd/migrate.rb,
brew/Library/Homebrew/cmd/missing.rb,
brew/Library/Homebrew/cmd/options.rb,
brew/Library/Homebrew/cmd/readall.rb,
brew/Library/Homebrew/cmd/upgrade.rb,
brew/Library/Homebrew/completions.rb,
brew/Library/Homebrew/dev-cmd/cat.rb,
brew/Library/Homebrew/dev-cmd/diy.rb,
brew/Library/Homebrew/dev-cmd/irb.rb,
brew/Library/Homebrew/dev-cmd/man.rb,
brew/Library/Homebrew/tap_auditor.rb,
brew/Library/Homebrew/cmd/--cellar.rb,
brew/Library/Homebrew/cmd/--prefix.rb,
brew/Library/Homebrew/cmd/commands.rb,
brew/Library/Homebrew/cmd/outdated.rb,
brew/Library/Homebrew/cmd/tap-info.rb,
brew/Library/Homebrew/dev-cmd/bump.rb,
brew/Library/Homebrew/dev-cmd/edit.rb,
brew/Library/Homebrew/dev-cmd/prof.rb,
brew/Library/Homebrew/dev-cmd/ruby.rb,
brew/Library/Homebrew/dev-cmd/test.rb,
brew/Library/Homebrew/cmd/--version.rb,
brew/Library/Homebrew/cmd/analytics.rb,
brew/Library/Homebrew/cmd/gist-logs.rb,
brew/Library/Homebrew/cmd/reinstall.rb,
brew/Library/Homebrew/cmd/uninstall.rb,
brew/Library/Homebrew/dev-cmd/audit.rb,
brew/Library/Homebrew/dev-cmd/style.rb,
brew/Library/Homebrew/dev-cmd/tests.rb,
brew/Library/Homebrew/os/mac/global.rb,
brew/Library/Homebrew/bundle_version.rb,
brew/Library/Homebrew/cli/named_args.rb,
brew/Library/Homebrew/cmd/--caskroom.rb,
brew/Library/Homebrew/cmd/autoremove.rb,
brew/Library/Homebrew/dev-cmd/bottle.rb,
brew/Library/Homebrew/dev-cmd/create.rb,
brew/Library/Homebrew/dev-cmd/mirror.rb,
brew/Library/Homebrew/dev-cmd/unpack.rb,
brew/Library/Homebrew/cmd/completions.rb,
brew/Library/Homebrew/cmd/postinstall.rb,
brew/Library/Homebrew/dev-cmd/command.rb,
brew/Library/Homebrew/dev-cmd/extract.rb,
brew/Library/Homebrew/dev-cmd/formula.rb,
brew/Library/Homebrew/dev-cmd/linkage.rb,
brew/Library/Homebrew/dev-cmd/pr-pull.rb,
brew/Library/Homebrew/dev-cmd/release.rb,
brew/Library/Homebrew/dev-cmd/tap-new.rb,
brew/Library/Homebrew/formula_auditor.rb,
brew/Library/Homebrew/formula_creator.rb,
brew/Library/Homebrew/livecheck/error.rb,
brew/Library/Homebrew/missing_formula.rb,
brew/Library/Homebrew/os/linux/global.rb,
brew/Library/Homebrew/cmd/--repository.rb,
brew/Library/Homebrew/dev-cmd/sponsors.rb,
brew/Library/Homebrew/resource_auditor.rb,
brew/Library/Homebrew/cmd/update-report.rb,
brew/Library/Homebrew/dev-cmd/livecheck.rb,
brew/Library/Homebrew/dev-cmd/pr-upload.rb,
brew/Library/Homebrew/dev-cmd/typecheck.rb,
brew/Library/Homebrew/dev-cmd/unbottled.rb,
brew/Library/Homebrew/formula_free_port.rb,
brew/Library/Homebrew/dev-cmd/pr-publish.rb,
brew/Library/Homebrew/formula_assertions.rb,
brew/Library/Homebrew/livecheck/strategy.rb,
brew/Library/Homebrew/dev-cmd/update-test.rb,
brew/Library/Homebrew/dev-cmd/vendor-gems.rb,
brew/Library/Homebrew/livecheck/livecheck.rb,
brew/Library/Homebrew/dev-cmd/bump-cask-pr.rb,
brew/Library/Homebrew/dev-cmd/pr-automerge.rb,
brew/Library/Homebrew/formula_text_auditor.rb,
brew/Library/Homebrew/dev-cmd/bump-revision.rb,
brew/Library/Homebrew/dev-cmd/release-notes.rb,
brew/Library/Homebrew/livecheck/strategy/git.rb,
brew/Library/Homebrew/livecheck/strategy/gnu.rb,
brew/Library/Homebrew/livecheck/strategy/npm.rb,
brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb,
brew/Library/Homebrew/livecheck/strategy/cpan.rb,
brew/Library/Homebrew/livecheck/strategy/pypi.rb,
brew/Library/Homebrew/livecheck/strategy/xorg.rb,
brew/Library/Homebrew/livecheck/strategy/gnome.rb,
brew/Library/Homebrew/unversioned_cask_checker.rb,
brew/Library/Homebrew/livecheck/skip_conditions.rb,
brew/Library/Homebrew/livecheck/strategy/apache.rb,
brew/Library/Homebrew/livecheck/strategy/hackage.rb,
brew/Library/Homebrew/livecheck/strategy/sparkle.rb,
brew/Library/Homebrew/dev-cmd/update-license-data.rb,
brew/Library/Homebrew/dev-cmd/install-bundler-gems.rb,
brew/Library/Homebrew/livecheck/strategy/bitbucket.rb,
brew/Library/Homebrew/livecheck/strategy/launchpad.rb,
brew/Library/Homebrew/dev-cmd/dispatch-build-bottle.rb,
brew/Library/Homebrew/livecheck/strategy/page_match.rb,
brew/Library/Homebrew/dev-cmd/bump-unversioned-casks.rb,
brew/Library/Homebrew/livecheck/strategy/sourceforge.rb,
brew/Library/Homebrew/dev-cmd/update-python-resources.rb,
brew/Library/Homebrew/livecheck/strategy/header_match.rb,
brew/Library/Homebrew/livecheck/strategy/github_latest.rb

Defined Under Namespace

Modules: Assertions, CLI, Completions, Diagnostic, EnvConfig, Fetch, FreePort, Help, Install, Livecheck, MissingFormula, Search, Settings, Style, Uninstall, Unlink, Upgrade Classes: BundleVersion, Cleanup, FormulaAuditor, FormulaCreator, FormulaTextAuditor, ResourceAuditor, TapAuditor, UnversionedCaskChecker

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
PACKAGE_MANAGERS =
{
  macports: ->(query) { "https://www.macports.org/ports.php?by=name&substr=#{query}" },
  fink:     ->(query) { "https://pdb.finkproject.org/pdb/browse.php?summary=#{query}" },
  opensuse: ->(query) { "https://software.opensuse.org/search?q=#{query}" },
  fedora:   ->(query) { "https://apps.fedoraproject.org/packages/s/#{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.

"1.17.3"
SOURCE_PATH =
(HOMEBREW_LIBRARY_PATH/"manpages").freeze
TARGET_MAN_PATH =
(HOMEBREW_REPOSITORY/"manpages").freeze
TARGET_DOC_PATH =
(HOMEBREW_REPOSITORY/"docs").freeze
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/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
].freeze
WATCHLIST_PATH =
(
  ENV["HOMEBREW_LIVECHECK_WATCHLIST"] ||
  "#{Dir.home}/.brew_livecheck_watchlist"
).freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Context

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

Methods included from DependenciesHelpers

args_includes_ignores, dependents, recursive_includes, reject_ignores

Methods included from Search

query_regexp, search_casks, search_descriptions, search_formulae, search_taps

Methods included from Search::Extension

#search_casks, #search_descriptions

Methods included from Fetch

fetch_bottle?

Methods included from Install

check_prefix, perform_build_from_source_checks, perform_preinstall_checks

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.



95
96
97
# File 'brew/Library/Homebrew/global.rb', line 95

def auditing=(value)
  @auditing = value
end

.failed=(value) ⇒ Object (writeonly)

Sets the attribute failed

Parameters:

  • value

    the value to set the attribute failed to.



95
96
97
# File 'brew/Library/Homebrew/global.rb', line 95

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.



95
96
97
# File 'brew/Library/Homebrew/global.rb', line 95

def raise_deprecation_exceptions=(value)
  @raise_deprecation_exceptions = value
end

Class Method Details

.__cachevoid

This method returns an undefined value.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'brew/Library/Homebrew/cmd/--cache.rb', line 39

def __cache
  args = __cache_args.parse

  if args.no_named?
    puts HOMEBREW_CACHE
    return
  end

  formulae_or_casks = args.named.to_formulae_and_casks

  formulae_or_casks.each do |formula_or_cask|
    if formula_or_cask.is_a? Formula
      print_formula_cache formula_or_cask, args: args
    else
      print_cask_cache formula_or_cask
    end
  end
end

.__cache_argsCLI::Parser

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'brew/Library/Homebrew/cmd/--cache.rb', line 16

def __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
    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."
    switch "--formula",
           description: "Only show cache files for formulae."
    switch "--cask",
           description: "Only show cache files for casks."
    conflicts "--build-from-source", "--force-bottle"
    conflicts "--formula", "--cask"

    named_args [:formula, :cask]
  end
end

.__caskroomvoid

This method returns an undefined value.



24
25
26
27
28
29
30
31
32
33
34
# File 'brew/Library/Homebrew/cmd/--caskroom.rb', line 24

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:



10
11
12
13
14
15
16
17
18
19
20
21
# File 'brew/Library/Homebrew/cmd/--caskroom.rb', line 10

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 'brew/Library/Homebrew/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 'brew/Library/Homebrew/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.



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
# File 'brew/Library/Homebrew/cmd/--env.rb', line 34

def __env
  args = __env_args.parse

  ENV.activate_extensions!(env: args.env)
  ENV.deps = args.named.to_formulae if superenv?(args.env)
  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
  else
    BuildEnvironment.keys(ENV).each do |key|
      puts Utils::Shell.export_value(key, ENV.fetch(key), shell)
    end
  end
end

.__env_argsCLI::Parser

Returns:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'brew/Library/Homebrew/cmd/--env.rb', line 15

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



208
209
210
211
# File 'brew/Library/Homebrew/dev-cmd/create.rb', line 208

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

.__prefixObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'brew/Library/Homebrew/cmd/--prefix.rb', line 31

def __prefix
  args = __prefix_args.parse

  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
    puts args.named.to_resolved_formulae.map { |f|
      f.opt_prefix.exist? ? f.opt_prefix : f.latest_installed_prefix
    }
  end
end

.__prefix_argsCLI::Parser

Returns:



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

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

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

      If <formula> is provided, display the location in the Cellar where <formula>
      is or would be installed.
    EOS
    switch "--unbrewed",
           description: "List files in Homebrew's prefix not installed by Homebrew."

    named_args :formula
  end
end

.__repositoryObject



24
25
26
27
28
29
30
31
32
# File 'brew/Library/Homebrew/cmd/--repository.rb', line 24

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:



12
13
14
15
16
17
18
19
20
21
22
# File 'brew/Library/Homebrew/cmd/--repository.rb', line 12

def __repository_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display where Homebrew's `.git` directory is located.

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

    named_args :tap
  end
end

.__versionObject



23
24
25
26
27
28
29
# File 'brew/Library/Homebrew/cmd/--version.rb', line 23

def __version
  __version_args.parse

  puts "Homebrew #{HOMEBREW_VERSION}"
  puts "#{CoreTap.instance.full_name} #{CoreTap.instance.version_string}"
  puts "#{Tap.default_cask_tap.full_name} #{Tap.default_cask_tap.version_string}" if Tap.default_cask_tap.installed?
end

.__version_argsCLI::Parser

Returns:



12
13
14
15
16
17
18
19
20
21
# File 'brew/Library/Homebrew/cmd/--version.rb', line 12

def __version_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Print the version numbers of Homebrew, Homebrew/homebrew-core and Homebrew/homebrew-cask
      (if tapped) to standard output.
    EOS

    named_args :none
  end
end

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



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'brew/Library/Homebrew/utils.rb', line 27

def _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



478
479
480
481
482
483
484
485
486
487
488
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 478

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.create(new_alias_version) <= Version.create(old_alias_version)

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

.analyticsObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'brew/Library/Homebrew/cmd/analytics.rb', line 32

def analytics
  args = analytics_args.parse

  case args.named.first
  when nil, "state"
    if Utils::Analytics.disabled?
      puts "Analytics are disabled."
    else
      puts "Analytics are enabled."
      puts "UUID: #{Utils::Analytics.uuid}" if Utils::Analytics.uuid.present?
    end
  when "on"
    Utils::Analytics.enable!
  when "off"
    Utils::Analytics.disable!
  when "regenerate-uuid"
    Utils::Analytics.regenerate_uuid!
  else
    raise UsageError, "unknown subcommand: #{args.named.first}"
  end
end

.analytics_argsCLI::Parser

Returns:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'brew/Library/Homebrew/cmd/analytics.rb', line 12

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.

      `brew analytics regenerate-uuid`:
      Regenerate the UUID used for Homebrew's analytics.
    EOS

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

.auditvoid

This method returns an undefined value.



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
# File 'brew/Library/Homebrew/dev-cmd/audit.rb', line 97

def audit
  args = audit_args.parse

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

  formula_count = 0
  problem_count = 0
  corrected_problem_count = 0
  new_formula_problem_count = 0
  new_formula = args.new_formula?
  strict = new_formula || args.strict?
  online = new_formula || args.online?
  git = args.git?
  skip_style = args.skip_style? || args.no_named? || args.tap
  no_named_args = false

  ENV.activate_extensions!
  ENV.setup_build_environment

  audit_formulae, audit_casks = if args.tap
    Tap.fetch(args.tap).yield_self do |tap|
      [
        tap.formula_names.map { |name| Formula[name] },
        tap.cask_files.map { |path| Cask::CaskLoader.load(path) },
      ]
    end
  elsif args.no_named?
    no_named_args = true
    [Formula, Cask::Cask.to_a]
  else
    args.named.to_formulae_and_casks
        .partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
  end
  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 args.new_formula?
    nil
  elsif except_cops
    style_options[:except_cops] = except_cops
  elsif !strict
    style_options[:except_cops] = [:FormulaAuditStrict]
  end

  # Run tap audits first
  tap_problem_count = 0
  tap_count = 0
  Tap.each do |tap|
    next if args.tap && tap != args.tap

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

    next if ta.problems.blank?

    tap_count += 1
    tap_problem_count += ta.problems.size
    tap_problem_lines = format_problem_lines(ta.problems)

    puts "#{tap.name}:", tap_problem_lines.map { |s| "  #{s}" }
  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
  new_formula_problem_lines = []
  audit_formulae.sort.each do |f|
    only = only_cops ? ["style"] : args.only
    options = {
      new_formula:          new_formula,
      strict:               strict,
      online:               online,
      git:                  git,
      only:                 only,
      except:               args.except,
      spdx_license_data:    spdx_license_data,
      spdx_exception_data:  spdx_exception_data,
      tap_audit_exceptions: f.tap&.audit_exceptions,
      style_offenses:       style_offenses ? style_offenses.for_path(f.path) : nil,
      display_cop_names:    args.display_cop_names?,
      build_stable:         args.build_stable?,
    }.compact

    fa = FormulaAuditor.new(f, **options)
    fa.audit
    next if fa.problems.empty? && fa.new_formula_problems.empty?

    formula_count += 1
    problem_count += fa.problems.size
    problem_lines = format_problem_lines(fa.problems)
    corrected_problem_count += options.fetch(:style_offenses, []).count(&:corrected?)
    new_formula_problem_lines += format_problem_lines(fa.new_formula_problems)
    if args.display_filename?
      puts problem_lines.map { |s| "#{f.path}: #{s}" }
    else
      puts "#{f.full_name}:", problem_lines.map { |s| "  #{s}" }
    end

    next unless ENV["GITHUB_ACTIONS"]

    (fa.problems + fa.new_formula_problems).each do |message:, location:|
      annotation = GitHub::Actions::Annotation.new(
        :error, message, file: f.path, line: location&.line, column: location&.column
      )
      puts annotation if annotation.relevant?
    end
  end

  casks_results = if audit_casks.empty?
    []
  else
    require "cask/cmd/audit"

    Cask::Cmd::Audit.audit_casks(
      *audit_casks,
      download:        nil,
      appcast:         args.appcast?,
      online:          args.online?,
      strict:          args.strict?,
      new_cask:        args.new_cask?,
      token_conflicts: args.token_conflicts?,
      quarantine:      nil,
      any_named_args:  !no_named_args,
      language:        nil,
    )
  end

  failed_casks = casks_results.reject { |_, result| result[:errors].empty? }

  cask_count = failed_casks.count

  cask_problem_count = failed_casks.sum { |_, result| result[:warnings].count + result[:errors].count }
  new_formula_problem_count += new_formula_problem_lines.count
  total_problems_count = problem_count + new_formula_problem_count + cask_problem_count + tap_problem_count
  return unless total_problems_count.positive?

  puts new_formula_problem_lines.map { |s| "  #{s}" }

  errors_summary = "#{total_problems_count} #{"problem".pluralize(total_problems_count)}"

  error_sources = []
  error_sources << "#{formula_count} #{"formula".pluralize(formula_count)}" if formula_count.positive?
  error_sources << "#{cask_count} #{"cask".pluralize(cask_count)}" if cask_count.positive?
  error_sources << "#{tap_count} #{"tap".pluralize(tap_count)}" 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 += ", #{corrected_problem_count} #{"problem".pluralize(corrected_problem_count)} corrected"
  end

  ofail errors_summary
end

.audit_argsCLI::Parser

Returns:



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
# File 'brew/Library/Homebrew/dev-cmd/audit.rb', line 28

def 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
    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 "--new", "--new-formula", "--new-cask",
           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 formula and implies "\
                        "`--strict` and `--online`."
    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."
    switch "--display-filename",
           description: "Prefix every line of output with the file or formula name being audited, to "\
                        "make output easy to grep."
    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."

    switch "--[no-]appcast",
           description: "Audit the appcast"
    switch "--token-conflicts",
           description: "Audit for token conflicts"

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

    conflicts "--only", "--except"
    conflicts "--only-cops", "--except-cops", "--strict"
    conflicts "--only-cops", "--except-cops", "--only"
    conflicts "--display-cop-names", "--skip-style"
    conflicts "--display-cop-names", "--only-cops"
    conflicts "--display-cop-names", "--except-cops"

    named_args [:formula, :cask]
  end
end

.auditing?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'brew/Library/Homebrew/global.rb', line 114

def auditing?
  @auditing == true
end

.autoremoveObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'brew/Library/Homebrew/cmd/autoremove.rb', line 33

def autoremove
  args = autoremove_args.parse

  removable_formulae = get_removable_formulae(Formula.installed)
  return if removable_formulae.blank?

  formulae_names = removable_formulae.map(&:full_name).sort

  verb = args.dry_run? ? "Would uninstall" : "Uninstalling"
  oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:"
  puts formulae_names.join("\n")
  return if args.dry_run?

  kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack)
  Uninstall.uninstall_kegs(kegs_by_rack)
end

.autoremove_argsObject



11
12
13
14
15
16
17
18
19
20
21
# File 'brew/Library/Homebrew/cmd/autoremove.rb', line 11

def 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, path: ".", reason: "", verbose: false, resolve: false) ⇒ Object



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

def autosquash!(original_commit, path: ".", reason: "", verbose: false, resolve: false)
  path = Pathname(path).extend(GitRepositoryExtension)
  original_head = path.git_head

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

  # Generate a bidirectional mapping of commits <=> formula files.
  files_to_commits = {}
  commits_to_files = commits.map do |commit|
    files = Utils.safe_popen_read("git", "-C", 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
      next if %r{^Formula/.*\.rb$}.match?(file)

      odie <<~EOS
        Autosquash can't squash commits that modify non-formula files.
          File:   #{file}
          Commit: #{commit}
      EOS
    end
    [commit, files]
  end.to_h

  # Reset to state before cherry-picking.
  safe_system "git", "-C", 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 = []
  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_formula_commit(commit, files.first, path: path, 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_formula_commits(commits, file, path: path, 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 cherry-picked state at #{original_head}"
  system "git", "-C", path, "reset", "--hard", original_head
  system "git", "-C", path, "cherry-pick", "--abort"
  raise
end

.backup(keg) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
# File 'brew/Library/Homebrew/reinstall.rb', line 72

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 $(whoami) #{keg}
    EOS
  end
end

.backup_path(path) ⇒ Object



95
96
97
# File 'brew/Library/Homebrew/reinstall.rb', line 95

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

.bottleObject



90
91
92
93
94
95
96
97
98
99
# File 'brew/Library/Homebrew/dev-cmd/bottle.rb', line 90

def bottle
  args = bottle_args.parse

  return merge(args: args) if args.merge?

  ensure_relocation_formulae_installed! unless args.skip_relocation?
  args.named.to_resolved_formulae.each do |f|
    bottle_formula f, args: args
  end
end

.bottle_argsCLI::Parser

Returns:



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
# File 'brew/Library/Homebrew/dev-cmd/bottle.rb', line 48

def 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."
    flag   "--root-url=",
           description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."

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

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

.bottle_formula(f, args:) ⇒ Object



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
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
# File 'brew/Library/Homebrew/dev-cmd/bottle.rb', line 216

def bottle_formula(f, args:)
  return ofail "Formula not installed or up-to-date: #{f.full_name}" unless f.latest_version_installed?

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

    tap = CoreTap.instance
  end

  if f.bottle_disabled?
    ofail "Formula has disabled bottle: #{f.full_name}"
    puts f.bottle_disable_reason
    return
  end

  return ofail "Formula was not installed with --build-bottle: #{f.full_name}" unless Utils::Bottles.built_as? f

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

  if args.no_rebuild? || !f.tap
    rebuild = 0
  elsif args.keep_old?
    rebuild = f.bottle_specification.rebuild
  else
    ohai "Determining #{f.full_name} bottle rebuild..."
    versions = FormulaVersions.new(f)
    rebuilds = versions.bottle_version_map("origin/master")[f.pkg_version]
    rebuilds.pop if rebuilds.last.to_i.positive?
    rebuild = rebuilds.empty? ? 0 : rebuilds.max.to_i + 1
  end

  filename = Bottle::Filename.create(f, Utils::Bottles.tag, rebuild)
  bottle_path = Pathname.pwd/filename

  tar_filename = filename.to_s.sub(/.gz$/, "")
  tar_path = Pathname.pwd/tar_filename

  prefix = HOMEBREW_PREFIX.to_s
  cellar = HOMEBREW_CELLAR.to_s

  ohai "Bottling #{filename}..."

  formula_and_runtime_deps_names = [f.name] + f.runtime_dependencies.map(&:name)
  keg = Keg.new(f.prefix)
  relocatable = T.let(false, T::Boolean)
  skip_relocation = T.let(false, T::Boolean)

  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
      tab = Tab.for_keg(keg)
      original_tab = tab.dup
      tab.poured_from_bottle = false
      tab.HEAD = nil
      tab.time = nil
      tab.changed_files = changed_files
      tab.write

      keg.find do |file|
        if file.symlink?
          File.lutime(tab.source_modified_time, tab.source_modified_time, file)
        else
          file.utime(tab.source_modified_time, tab.source_modified_time)
        end
      end

      cd cellar do
        sudo_purge
        safe_system "tar", "cf", tar_path, "#{f.name}/#{f.pkg_version}"
        sudo_purge
        tar_path.utime(tab.source_modified_time, tab.source_modified_time)
        relocatable_tar_path = "#{f}-bottle.tar"
        mv 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).
        safe_system "gzip", "-f", relocatable_tar_path
        sudo_purge
        mv "#{relocatable_tar_path}.gz", bottle_path
      end

      ohai "Detecting if #{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)$}]
      any_go_deps = f.deps.any? do |dep|
        dep.name =~ Version.formula_optionally_versioned_regex(:go)
      end
      if any_go_deps
        go_regex =
          Version.formula_optionally_versioned_regex(:go, full: false)
        ignores << %r{#{Regexp.escape(HOMEBREW_CELLAR)}/#{go_regex}/[\d.]+/libexec}
      end

      repository_reference = if HOMEBREW_PREFIX == HOMEBREW_REPOSITORY
        HOMEBREW_LIBRARY
      else
        HOMEBREW_REPOSITORY
      end.to_s
      if keg_contain?(repository_reference, keg, ignores, 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

  root_url = args.root_url

  bottle = BottleSpecification.new
  bottle.tap = tap
  bottle.root_url(root_url) if root_url
  if relocatable
    if skip_relocation
      bottle.cellar :any_skip_relocation
    else
      bottle.cellar :any
    end
  else
    bottle.cellar cellar
    bottle.prefix prefix
  end
  bottle.rebuild rebuild
  sha256 = bottle_path.sha256
  bottle.sha256 sha256 => Utils::Bottles.tag

  old_spec = f.bottle_specification
  if args.keep_old? && !old_spec.checksums.empty?
    mismatches = [:root_url, :prefix, :cellar, :rebuild].reject do |key|
      old_spec.send(key) == bottle.send(key)
    end
    if (old_spec.cellar == :any && bottle.cellar == :any_skip_relocation) ||
       (old_spec.cellar == cellar &&
        [:any, :any_skip_relocation].include?(bottle.cellar))
      mismatches.delete(:cellar)
      bottle.cellar old_spec.cellar
    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

  puts "./#{filename}"
  puts output

  return unless args.json?

  json = {
    f.full_name => {
      "formula" => {
        "pkg_version" => f.pkg_version.to_s,
        "path"        => f.path.to_s.delete_prefix("#{HOMEBREW_REPOSITORY}/"),
      },
      "bottle"  => {
        "root_url" => bottle.root_url,
        "prefix"   => bottle.prefix,
        "cellar"   => bottle.cellar.to_s,
        "rebuild"  => bottle.rebuild,
        "tags"     => {
          Utils::Bottles.tag.to_s => {
            "filename"       => filename.bintray,
            "local_filename" => filename.to_s,
            "sha256"         => sha256,
          },
        },
      },
      "bintray" => {
        "package"    => Utils::Bottles::Bintray.package(f.name),
        "repository" => Utils::Bottles::Bintray.repository(tap),
      },
    },
  }
  File.open(filename.json, "w") do |file|
    file.write JSON.generate json
  end
end

.bottle_output(bottle) ⇒ Object



205
206
207
208
# File 'brew/Library/Homebrew/dev-cmd/bottle.rb', line 205

def bottle_output(bottle)
  erb = ERB.new BOTTLE_ERB
  erb.result(bottle.instance_eval { binding }).gsub(/^\s*$\n/, "")
end

.brief_build_info(f, with_hostname:) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 73

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

.build_man_page(quiet:) ⇒ Object



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
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 66

def build_man_page(quiet:)
  template = (SOURCE_PATH/"brew.1.md.erb").read
  variables = OpenStruct.new

  variables[:commands] = generate_cmd_manpages(Commands.internal_commands_paths(cask: false))
  variables[:developer_commands] = generate_cmd_manpages(Commands.internal_developer_commands_paths)
  variables[:official_external_commands] =
    generate_cmd_manpages(Commands.official_external_commands_paths(quiet: quiet))
  variables[:global_cask_options] = global_cask_options_manpage
  variables[:global_options] = global_options_manpage
  variables[:environment_variables] = env_vars_manpage

  readme = HOMEBREW_REPOSITORY/"README.md"
  variables[:lead] =
    readme.read[/(Homebrew's \[Project Leader.*\.)/, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
  variables[:plc] =
    readme.read[/(Homebrew's \[Project Leadership Committee.*\.)/, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
  variables[:tsc] =
    readme.read[/(Homebrew's \[Technical Steering Committee.*\.)/, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
  variables[:linux] =
    readme.read[%r{(Homebrew/brew's Linux maintainers .*\.)}, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
  variables[:maintainers] =
    readme.read[/(Homebrew's other current maintainers .*\.)/, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
  variables[:alumni] =
    readme.read[/(Former maintainers .*\.)/, 1]
          .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')

  ERB.new(template, trim_mode: ">").result(variables.instance_eval { binding })
end

.bumpObject



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
# File 'brew/Library/Homebrew/dev-cmd/bump.rb', line 27

def bump
  args = bump_args.parse

  requested_formulae = args.named.to_formulae.map(&:name) if args.named.to_formulae.present?

  requested_limit = args.limit.to_i if args.limit.present?

  repology_data = if requested_formulae
    response = {}
    requested_formulae.each do |formula|
      raise FormulaUnavailableError, formula unless validate_formula(formula)

      package_data = Repology.single_package_query(formula)
      response[package_data.keys.first] = package_data.values.first if package_data
    end

    response
  else
    Repology.parse_api_response(requested_limit)
  end

  validated_formulae = {}

  validated_formulae = Repology.validate_and_format_packages(repology_data, requested_limit) if repology_data

  if requested_formulae
    repology_excluded_formulae = requested_formulae.reject do |formula|
      repology_data[formula]
    end

    formulae = {}
    repology_excluded_formulae.each do |formula|
      formulae[formula] = Repology.format_package(formula, nil)
    end

    formulae.each { |formula, data| validated_formulae[formula] = data }
  end

  display(validated_formulae)
end

.bump_argsCLI::Parser

Returns:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'brew/Library/Homebrew/dev-cmd/bump.rb', line 12

def bump_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display out-of-date brew formulae and the latest version available.
      Also displays whether a pull request has been opened with the URL.
    EOS
    flag   "--limit=",
           description: "Limit number of package results returned."
    switch :verbose
    switch :debug

    named_args :formula
  end
end

.bump_cask_prObject



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

def bump_cask_pr
  args = bump_cask_pr_args.parse

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

  # Use the user's browser, too.
  ENV["BROWSER"] = Homebrew::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 = args.version
  new_version = :latest if ["latest", ":latest"].include?(new_version)
  new_version = Cask::DSL::Version.new(new_version) if new_version.present?
  new_base_url = args.url
  new_hash = args.sha256
  new_hash = :no_check if ["no_check", ":no_check"].include? new_hash

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

  old_version = cask.version
  old_hash = cask.sha256

  check_open_pull_requests(cask, args: args)

  old_contents = File.read(cask.sourcefile_path)

  replacement_pairs = []

  if new_version.present?
    old_version_regex = old_version.latest? ? ":latest" : "[\"']#{Regexp.escape(old_version.to_s)}[\"']"

    replacement_pairs << [
      /version\s+#{old_version_regex}/m,
      "version #{new_version.latest? ? ":latest" : "\"#{new_version}\""}",
    ]
  end

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

    old_base_url = m.captures.first

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

  if new_version.present?
    if new_version.latest?
      opoo "Ignoring specified --sha256= argument." if new_hash.present?
      new_hash = :no_check
    elsif new_hash.nil? || cask.languages.present?
      tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
                                                      replacement_pairs.uniq.compact,
                                                      read_only_run: true,
                                                      silent:        true)

      tmp_cask = Cask::CaskLoader.load(tmp_contents)
      tmp_config = cask.config
      tmp_url = tmp_cask.url.to_s

      if new_hash.nil? && old_hash != :no_check
        resource_path = fetch_resource(cask, new_version, tmp_url)
        Utils::Tar.validate_file(resource_path)
        new_hash = resource_path.sha256
      end

      cask.languages.each do |language|
        next if language == cask.language

        lang_config = tmp_config.merge(Cask::Config.new(explicit: { languages: [language] }))
        lang_cask = Cask::CaskLoader.load(tmp_contents)
        lang_cask.config = lang_config
        lang_url = lang_cask.url.to_s
        lang_old_hash = lang_cask.sha256.to_s

        resource_path = fetch_resource(cask, new_version, lang_url)
        Utils::Tar.validate_file(resource_path)
        lang_new_hash = resource_path.sha256

        replacement_pairs << [
          lang_old_hash,
          lang_new_hash,
        ]
      end
    end
  end

  if new_hash.present?
    hash_regex = old_hash == :no_check ? ":no_check" : "[\"']#{Regexp.escape(old_hash.to_s)}[\"']"

    replacement_pairs << [
      /sha256\s+#{hash_regex}/m,
      "sha256 #{new_hash == :no_check ? ":no_check" : "\"#{new_hash}\""}",
    ]
  end

  Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
                                   replacement_pairs.uniq.compact,
                                   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)

  branch_name = "bump-#{cask.token}"
  commit_message = "Update #{cask.token}"
  if new_version.present?
    branch_name += "-#{new_version.tr(",:", "-")}"
    commit_message += " from #{old_version} to #{new_version}"
  end
  pr_info = {
    sourcefile_path: cask.sourcefile_path,
    old_contents:    old_contents,
    branch_name:     branch_name,
    commit_message:  commit_message,
    tap:             cask.tap,
    pr_message:      "Created with `brew bump-cask-pr`.",
  }
  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
# File 'brew/Library/Homebrew/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",
           description: "Make the expected file modifications without taking any Git actions."
    switch "--commit",
           depends_on:  "--write",
           description: "When passed with `--write`, 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",
           description: "Run `brew audit --online` before opening the PR."
    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   "--message=",
           description: "Append <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."
    switch "-f", "--force",
           description: "Ignore duplicate open PRs."

    conflicts "--dry-run", "--write"
    conflicts "--no-audit", "--online"

    named_args :cask, number: 1
  end
end

.bump_formula_prObject



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
384
385
386
387
388
389
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 120

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"] = ENV["HOMEBREW_PATH"]

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

  formula = args.named.to_formulae.first

  new_url = args.url
  formula ||= determine_formula_from_url(new_url) if new_url.present?
  raise FormulaUnspecifiedError if formula.blank?

  odie "This formula is disabled!" if formula.disabled?
  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?

  tap_full_name, remote, remote_branch, previous_branch = use_correct_linux_tap(formula, args: args)
  check_open_pull_requests(formula, tap_full_name, args: args)

  new_version = args.version
  check_closed_pull_requests(formula, tap_full_name, 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_closed_pull_requests(formula, tap_full_name, url: new_url, args: args) if new_version.blank?
    true
  elsif new_tag.present? && new_revision.present?
    check_closed_pull_requests(formula, tap_full_name, 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
      if new_version.blank?
        check_closed_pull_requests(formula, tap_full_name, url: old_url, tag: new_tag, args: args)
      end
      resource_path, forced_version = fetch_resource(formula, new_version, old_url, tag: new_tag)
      new_revision = Utils.popen_read("git -C \"#{resource_path}\" 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_closed_pull_requests(formula, tap_full_name, url: new_url, args: args) if new_version.blank?
    resource_path, forced_version = fetch_resource(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?
    [
      [
        formula_spec.specs[:tag],
        new_tag,
      ],
      [
        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)/m,
      "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n",
    ]
  end

  # When bumping a linux-only formula, one needs to also delete the
  # sha256 linux bottle line if it exists. That's because of running
  # test-bot with --keep-old option in linuxbrew-core.
  if old_contents.include?("depends_on :linux") && old_contents.include?("=> :x86_64_linux")
    replacement_pairs << [
      /^    sha256 ".+" => :x86_64_linux\n/m,
      "\\2",
    ]
  end

  if forced_version && new_version != "0"
    replacement_pairs << if old_contents.include?("version \"#{old_formula_version}\"")
      [
        old_formula_version.to_s,
        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)/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,
                                                      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 may require updates.
    EOS
  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_full_name:    tap_full_name,
    pr_message:       pr_message,
  }
  GitHub.create_bump_pr(pr_info, args: args)
end

.bump_formula_pr_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
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 15

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> and <formula> name will
      be made if either or both values are 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",
           description: "Make the expected file modifications without taking any Git actions."
    switch "--commit",
           depends_on:  "--write",
           description: "When passed with `--write`, 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   "--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: "Append <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."

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

    named_args :formula, max: 1
  end
end

.bump_revisionObject



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
# File 'brew/Library/Homebrew/dev-cmd/bump-revision.rb', line 28

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"] = ENV["HOMEBREW_PATH"]

  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!
      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.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}"
    else
      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:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'brew/Library/Homebrew/dev-cmd/bump-revision.rb', line 13

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."
    flag   "--message=",
           description: "Append <message> to the default commit message."

    named_args :formula, min: 1
  end
end

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

Parameters:

Returns:

  • (Hash{String => T.untyped}, nil)


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 'brew/Library/Homebrew/dev-cmd/bump-unversioned-casks.rb', line 89

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?
    opoo "Skipping, not a single-app or PKG cask."
    return
  end

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

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

  last_sha256 = state["sha256"]
  last_time = state["time"]&.yield_self { |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.minutes) do
        unversioned_cask_checker.installer.download.sha256
      end
    rescue => e
      onoe e
    end

    if sha256.present? && last_sha256 != sha256
      version = begin
        Timeout.timeout(1.minute) 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.



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 'brew/Library/Homebrew/dev-cmd/bump-unversioned-casks.rb', line 35

def self.bump_unversioned_casks
  args = bump_unversioned_casks_args.parse

  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 { |cask| cask.url&.unversioned? }

  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.minutes 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.generate(state) unless args.dry_run?
  end
end

.bump_unversioned_casks_argsCLI::Parser

Returns:



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'brew/Library/Homebrew/dev-cmd/bump-unversioned-casks.rb', line 18

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
  end
end

.caskObject



13
14
15
16
# File 'brew/Library/Homebrew/cmd/cask.rb', line 13

def cask
  ARGV.freeze
  Cask::Cmd.run(*ARGV)
end

.cask_argsObject



9
10
11
# File 'brew/Library/Homebrew/cmd/cask.rb', line 9

def cask_args
  Cask::Cmd.parser
end

.catObject



28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'brew/Library/Homebrew/dev-cmd/cat.rb', line 28

def cat
  args = cat_args.parse

  cd HOMEBREW_REPOSITORY
  pager = if Homebrew::EnvConfig.bat?
    ENV["BAT_CONFIG_PATH"] = Homebrew::EnvConfig.bat_config_path
    "#{HOMEBREW_PREFIX}/bin/bat"
  else
    "cat"
  end

  safe_system pager, args.named.to_paths.first
end

.cat_argsCLI::Parser

Returns:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'brew/Library/Homebrew/dev-cmd/cat.rb', line 12

def 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], number: 1
  end
end

.changed_formulae(tap, original_commit) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'brew/Library/Homebrew/dev-cmd/pr-pull.rb', line 324

def changed_formulae(tap, original_commit)
  if Homebrew::EnvConfig.disable_load_formula?
    opoo "Can't check if updated bottles are necessary as formula loading is disabled!"
    return
  end

  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")}"
    Formula[name]
  end.compact
end

.check_bottled_formulae(bottles_hash) ⇒ Object



39
40
41
42
43
44
45
46
47
48
# File 'brew/Library/Homebrew/dev-cmd/pr-upload.rb', line 39

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_full_name, args:, version: nil, url: nil, tag: nil) ⇒ Object



465
466
467
468
469
470
471
472
473
474
475
476
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 465

def check_closed_pull_requests(formula, tap_full_name, args:, version: nil, url: nil, tag: nil)
  if version.nil?
    specs = {}
    specs[:tag] = tag if tag.present?
    version = Version.detect(url, **specs)
  end
  # if we haven't already found open requests, try for an exact match across closed requests
  GitHub.check_for_duplicate_pull_requests("#{formula.name} #{version}", tap_full_name,
                                           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



424
425
426
427
428
429
430
431
432
433
434
435
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 424

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_open_pull_requests(formula, tap_full_name, args:) ⇒ Object



197
198
199
200
201
202
# File 'brew/Library/Homebrew/dev-cmd/bump-cask-pr.rb', line 197

def check_open_pull_requests(cask, args:)
  GitHub.check_for_duplicate_pull_requests(cask.token, cask.tap.full_name,
                                           state: "open",
                                           file:  cask.sourcefile_path.relative_path_from(cask.tap.path).to_s,
                                           args:  args)
end

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



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'brew/Library/Homebrew/dev-cmd/pr-pull.rb', line 279

def cherry_pick_pr!(user, repo, pr, args:, path: ".")
  if args.dry_run?
    puts <<~EOS
      git fetch --force origin +refs/pull/#{pr}/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, pr)
  safe_system "git", "-C", path, "fetch", "--quiet", "--force", "origin", commits.last
  ohai "Using #{commits.count} commit#{"s" unless commits.count == 1} from ##{pr}"
  Utils::Git.cherry_pick!(path, "--ff", "--allow-empty", *commits, verbose: args.verbose?, resolve: args.resolve?)
end

.cleanupObject



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
# File 'brew/Library/Homebrew/cmd/cleanup.rb', line 38

def cleanup
  args = cleanup_args.parse

  days = args.prune.presence&.yield_self 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!

  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:



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

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 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

.cmd_comment_manpage_lines(cmd_path) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 194

def cmd_comment_manpage_lines(cmd_path)
  comment_lines = cmd_path.read.lines.grep(/^#:/)
  return if comment_lines.empty?
  return if comment_lines.first.include?("@hide_from_man_page")

  lines = [format_usage_banner(comment_lines.first).chomp]
  comment_lines.slice(1..-1)
               .each do |line|
    line = line.slice(4..-2)
    unless line
      lines.last << "\n"
      next
    end

    # Omit the common global_options documented separately in the man page.
    next if line.match?(/--(debug|help|quiet|verbose) /)

    # Format one option or a comma-separated pair of short and long options.
    lines << line.gsub(/^ +(-+[a-z-]+), (-+[a-z-]+) +/, "* `\\1`, `\\2`:\n  ")
                 .gsub(/^ +(-+[a-z-]+) +/, "* `\\1`:\n  ")
  end
  lines.last << "\n"
  lines
end

.cmd_parser_manpage_lines(cmd_parser) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 179

def cmd_parser_manpage_lines(cmd_parser)
  lines = [format_usage_banner(cmd_parser.usage_banner_text)]
  lines += cmd_parser.processed_options.map do |short, long, _, desc|
    if long.present?
      next if Homebrew::CLI::Parser.global_options.include?([short, long, desc])
      next if Homebrew::CLI::Parser.global_cask_options.any? do |_, option, description:, **|
                [long, "#{long}="].include?(option) && description == desc
              end
    end

    generate_option_doc(short, long, desc)
  end.reject(&:blank?)
  lines
end

.commandObject



23
24
25
26
27
28
29
30
31
# File 'brew/Library/Homebrew/dev-cmd/command.rb', line 23

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:



13
14
15
16
17
18
19
20
21
# File 'brew/Library/Homebrew/dev-cmd/command.rb', line 13

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

.commandsObject



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
# File 'brew/Library/Homebrew/cmd/commands.rb', line 27

def commands
  args = commands_args.parse

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

  prepend_separator = false

  {
    "Built-in commands"           => Commands.internal_commands,
    "Built-in developer commands" => Commands.internal_developer_commands,
    "External commands"           => Commands.external_commands,
    "Cask commands"               => Commands.cask_internal_commands,
    "External cask commands"      => Commands.cask_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:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'brew/Library/Homebrew/cmd/commands.rb', line 12

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

.completionsObject



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

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:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'brew/Library/Homebrew/cmd/completions.rb', line 13

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



142
143
144
145
# File 'brew/Library/Homebrew/cmd/deps.rb', line 142

def 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

.configObject



24
25
26
27
28
# File 'brew/Library/Homebrew/cmd/config.rb', line 24

def config
  config_args.parse

  SystemConfig.dump_verbose_config
end

.config_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
22
# File 'brew/Library/Homebrew/cmd/config.rb', line 13

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

.convert_man_page(markup, target, preserve_date:) ⇒ Object



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
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 106

def convert_man_page(markup, target, preserve_date:)
  manual = target.basename(".1")
  organisation = "Homebrew"

  # Set the manpage date to the existing one if we're checking for changes.
  # This avoids the only change being e.g. a new date.
  date = if preserve_date && target.extname == ".1" && target.exist?
    /"(\d{1,2})" "([A-Z][a-z]+) (\d{4})" "#{organisation}" "#{manual}"/ =~ target.read
    Date.parse("#{Regexp.last_match(1)} #{Regexp.last_match(2)} #{Regexp.last_match(3)}")
  else
    Date.today
  end
  date = date.strftime("%Y-%m-%d")

  shared_args = %W[
    --pipe
    --organization=#{organisation}
    --manual=#{target.basename(".1")}
    --date=#{date}
  ]

  format_flag, format_desc = target_path_to_format(target)

  puts "Writing #{format_desc} to #{target}"
  Utils.popen(["ronn", format_flag] + shared_args, "rb+") do |ronn|
    ronn.write markup
    ronn.close_write
    ronn_output = ronn.read
    odie "Got no output from ronn!" if ronn_output.blank?
    case format_flag
    when "--markdown"
      ronn_output = ronn_output.gsub(%r{<var>(.*?)</var>}, "*`\\1`*")
                               .gsub(/\n\n\n+/, "\n\n")
                               .gsub(/^(- `[^`]+`):/, "\\1") # drop trailing colons from definition lists
                               .gsub(/(?<=\n\n)([\[`].+):\n/, "\\1\n<br>") # replace colons with <br> on subcommands
    when "--roff"
      ronn_output = ronn_output.gsub(%r{<code>(.*?)</code>}, "\\fB\\1\\fR")
                               .gsub(%r{<var>(.*?)</var>}, "\\fI\\1\\fR")
                               .gsub(/(^\[?\\fB.+): /, "\\1\n    ")
    end
    target.atomic_write ronn_output
  end
end

.createObject

Create a formula from a tarball URL.



75
76
77
78
79
80
81
82
83
84
85
# File 'brew/Library/Homebrew/dev-cmd/create.rb', line 75

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:



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
# File 'brew/Library/Homebrew/dev-cmd/create.rb', line 17

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



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
# File 'brew/Library/Homebrew/dev-cmd/create.rb', line 87

def create_cask(args:)
  url = args.named.first

  if (token = args.set_name).nil?
    raise UsageError, "The `--set-name` flag is required for creating casks."
  end

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

  cask_path = Cask::CaskLoader.path("#{cask_tap}/#{token}")
  cask_path.dirname.mkpath unless cask_path.dirname.exist?
  raise Cask::CaskAlreadyCreatedError, token if cask_path.exist?

  version = if args.set_version
    Version.create(args.set_version)
  else
    Version.detect(url.gsub(token, ""))
  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 ""
      desc ""
      homepage ""

      app ""
    end
  RUBY

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

.create_formula(args:) ⇒ Object



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
# File 'brew/Library/Homebrew/dev-cmd/create.rb', line 140

def create_formula(args:)
  fc = FormulaCreator.new(args)
  fc.name = args.set_name
  fc.version = args.set_version
  fc.license = args.set_license
  fc.tap = Tap.fetch(args.tap || "homebrew/core")
  raise TapUnavailableError, args.tap unless fc.tap.installed?

  fc.url = args.named.first # Pull the first (and only) url from ARGV

  fc.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

  if fc.name.nil? || fc.name.strip.empty?
    stem = Pathname.new(fc.url).stem.rpartition("=").last
    print "Formula name [#{stem}]: "
    fc.name = __gets || stem
    fc.update_path
  end

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

    if Formula.aliases.include? fc.name
      realname = Formulary.canonical_name(fc.name)
      raise <<~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

  fc.generate!

  PyPI.update_python_resources! Formula[fc.name], ignore_non_pypi_packages: true if args.python?

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

.create_gist(files, description, private:) ⇒ Object



111
112
113
114
115
116
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 111

def create_gist(files, description, private:)
  url = "https://api.github.com/gists"
  data = { "public" => !private, "files" => files, "description" => description }
  scopes = GitHub::CREATE_GIST_SCOPES
  GitHub.open_api(url, data: data, scopes: scopes)["html_url"]
end

.create_issue(repo, title, body) ⇒ Object



118
119
120
121
122
123
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 118

def create_issue(repo, title, body)
  url = "https://api.github.com/repos/#{repo}/issues"
  data = { "title" => title, "body" => body }
  scopes = GitHub::CREATE_ISSUE_FORK_OR_PR_SCOPES
  GitHub.open_api(url, data: data, scopes: scopes)["html_url"]
end

.decorate_dependencies(dependencies) ⇒ Object



316
317
318
319
320
321
322
323
324
325
# File 'brew/Library/Homebrew/cmd/info.rb', line 316

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



327
328
329
330
331
332
333
# File 'brew/Library/Homebrew/cmd/info.rb', line 327

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)


97
98
99
# File 'brew/Library/Homebrew/global.rb', line 97

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

.dep_display_name(dep, args:) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'brew/Library/Homebrew/cmd/deps.rb', line 147

def 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?
  end

  str
end

.dep_display_s(dep) ⇒ Object



335
336
337
338
339
# File 'brew/Library/Homebrew/cmd/info.rb', line 335

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

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

.depsObject



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
# File 'brew/Library/Homebrew/cmd/deps.rb', line 72

def deps
  args = deps_args.parse

  Formulary.enable_factory_cache!

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

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

  if args.tree?
    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(config: Cask::Config.from_args(args)))
      else
        sorted_dependents(Formula.installed + Cask::Caskroom.casks(config: Cask::Config.from_args(args)))
      end
    else
      raise FormulaUnspecifiedError
    end

    puts_deps_tree dependents, recursive: recursive, args: args
    return
  elsif args.all?
    puts_deps sorted_dependents(Formula.to_a + Cask::Cask.to_a), 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(config: Cask::Config.from_args(args)))
    else
      sorted_dependents(Formula.installed + Cask::Caskroom.casks(config: Cask::Config.from_args(args)))
    end
    puts_deps sorted_dependents_formulae_and_casks, recursive: recursive, args: args
    return
  end

  dependents = dependents(args.named.to_formulae_and_casks)

  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.n?
  puts all_deps
end

.deps_argsCLI::Parser

Returns:



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 'brew/Library/Homebrew/cmd/deps.rb', line 18

def deps_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Show dependencies for <formula>. Additional options specific to <formula>
      may be appended to the command. When given multiple formula arguments,
      show the intersection of dependencies for each formula.
    EOS
    switch "-n",
           description: "Sort dependencies in topological order."
    switch "--1",
           description: "Only show dependencies one level down, instead of recursing."
    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 "--annotate",
           description: "Mark any build, test, 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 "--all",
           description: "List dependencies for all available formulae."
    switch "--for-each",
           description: "Switch into the mode used by the `--all` option, but only list dependencies "\
                        "for each provided <formula>, one formula per line. This is used for "\
                        "debugging the `--installed`/`--all` display mode."
    switch "--formula", "--formulae",
           depends_on:  "--installed",
           description: "Treat all named arguments as formulae."
    switch "--cask", "--casks",
           depends_on:  "--installed",
           description: "Treat all named arguments as casks."
    conflicts "--installed", "--all"
    conflicts "--formula", "--cask"
    formula_options

    named_args [:formula, :cask]
  end
end

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



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

def deps_for_dependent(d, args:, recursive: false)
  includes, ignores = args_includes_ignores(args)

  deps = d.runtime_dependencies if @use_runtime_dependencies

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

  deps + reqs.to_a
end

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



188
189
190
# File 'brew/Library/Homebrew/cmd/deps.rb', line 188

def 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



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'brew/Library/Homebrew/dev-cmd/unbottled.rb', line 113

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

  deps_hash = {}
  uses_hash = {}

  all_formulae.each do |f|
    next unless f.core_formula?

    deps = f.recursive_dependencies 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



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
# File 'brew/Library/Homebrew/cmd/desc.rb', line 40

def desc
  args = desc_args.parse

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

  results = if search_type.nil?
    desc = {}
    args.named.to_formulae.each { |f| desc[f.full_name] = f.desc }
    Descriptions.new(desc)
  else
    query = args.named.join(" ")
    string_or_regex = query_regexp(query)
    CacheStoreDatabase.use(:descriptions) do |db|
      cache_store = DescriptionCacheStore.new(db)
      Descriptions.search(string_or_regex, search_type, cache_store)
    end
  end

  results.print
end

.desc_argsCLI::Parser

Returns:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'brew/Library/Homebrew/cmd/desc.rb', line 17

def desc_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display <formula>'s name and one-line description.
      Formula descriptions are cached; 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."

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

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

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



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

def determine_bump_subject(old_contents, new_contents, formula_path, reason: nil)
  formula_path = Pathname(formula_path)
  formula_name = formula_path.basename.to_s.chomp(".rb")

  new_formula = begin
    Formulary.from_contents(formula_name, formula_path, new_contents, :stable)
  rescue FormulaUnavailableError
    nil
  end

  return "#{formula_name}: delete #{reason}".strip if new_formula.blank?

  old_formula = begin
    Formulary.from_contents(formula_name, formula_path, old_contents, :stable)
  rescue FormulaUnavailableError
    nil
  end

  return "#{formula_name} #{new_formula.stable.version} (new formula)" if old_formula.blank?

  if old_formula.stable.version != new_formula.stable.version
    "#{formula_name} #{new_formula.stable.version}"
  elsif old_formula.revision != new_formula.revision
    "#{formula_name}: revision #{reason}".strip
  else
    "#{formula_name}: #{reason || "rebuild"}".strip
  end
end

.determine_formula_from_url(url) ⇒ Object



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 391

def determine_formula_from_url(url)
  # Split the new URL on / and find any formulae that have the same URL
  # except for the last component, but don't try to match any more than the
  # first five components since sometimes the last component isn't the only
  # one to change.
  url_split = url.split("/")
  maximum_url_components_to_match = 5
  components_to_match = [url_split.count - 1, maximum_url_components_to_match].min
  base_url = url_split.first(components_to_match).join("/")
  base_url = /#{Regexp.escape(base_url)}/
  guesses = []
  Formula.each do |f|
    guesses << f if f.stable&.url&.match(base_url)
  end
  return guesses.shift if guesses.count == 1
  return if guesses.count <= 1

  odie "Couldn't guess formula for sure; could be one of these:\n#{guesses.map(&:name).join(", ")}"
end

.determine_mirror(url) ⇒ Object



411
412
413
414
415
416
417
418
419
420
421
422
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 411

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

.dispatch_build_bottleObject

Raises:



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 'brew/Library/Homebrew/dev-cmd/dispatch-build-bottle.rb', line 33

def dispatch_build_bottle
  args = dispatch_build_bottle_args.parse

  # Fixup version for ARM/Apple Silicon
  # TODO: fix label name to be 11-arm64 instead and remove this.
  args.macos&.gsub!(/^11-arm$/, "11-arm64")

  macos = args.macos&.yield_self do |s|
    MacOS::Version.from_symbol(s.to_sym)
  rescue MacOSVersionError
    MacOS::Version.new(s)
  end

  raise UsageError, "Must specify --macos option" if macos.blank?

  # Fixup label for ARM/Apple Silicon
  macos_label = if macos.arch == :arm64
    # TODO: fix label name to be 11-arm64 instead.
    "#{macos}-arm"
  else
    macos.to_s
  end

  tap = Tap.fetch(args.tap || CoreTap.instance.name)
  user, repo = tap.full_name.split("/")

  workflow = args.workflow || "dispatch-build-bottle.yml"
  ref = "master"

  args.named.to_resolved_formulae.each do |formula|
    # Required inputs
    inputs = {
      formula: formula.name,
      macos:   macos_label,
    }

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

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

.dispatch_build_bottle_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'brew/Library/Homebrew/dev-cmd/dispatch-build-bottle.rb', line 13

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   "--issue=",
           description: "If specified, post a comment to this issue number if the job fails."
    flag   "--macos=",
           description: "Version of macOS the bottle should be built for."
    flag   "--workflow=",
           description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
    switch "--upload",
           description: "Upload built bottles to Bintray."

    named_args :formula, min: 1
  end
end

.display(formulae) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'brew/Library/Homebrew/dev-cmd/bump.rb', line 80

def display(formulae)
  formulae.each do |formula, package_details|
    title = (up_to_date?(package_details) ? "#{formula} is up to date!" : formula).to_s
    ohai title
    puts "Current formula version:  #{package_details[:current_formula_version]}"
    puts "Latest Repology version:  #{package_details[:repology_latest_version]}"
    puts "Latest livecheck version: #{package_details[:livecheck_latest_version]}"
    puts "Open pull requests:       #{package_details[:open_pull_requests]}"
  end
end

.diyObject



30
31
32
33
34
# File 'brew/Library/Homebrew/dev-cmd/diy.rb', line 30

def diy
  diy_args.parse

  odisabled "`brew diy`"
end

.diy_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'brew/Library/Homebrew/dev-cmd/diy.rb', line 13

def diy_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Automatically determine the installation prefix for non-Homebrew software.
      Using the output from this command, you can install your own software into
      the Cellar and then link it into Homebrew's prefix with `brew link`.
    EOS
    flag   "--name=",
           description: "Explicitly set the <name> of the package being installed."
    flag   "--version=",
           description: "Explicitly set the <version> of the package being installed."

    max_named 0
    hide_from_man_page!
  end
end

.doctorObject



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 'brew/Library/Homebrew/cmd/doctor.rb', line 33

def 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.sort
    return
  end

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

  first_warning = true
  methods.each do |method|
    $stderr.puts Formatter.headline("Checking #{method}", color: :magenta) if args.debug?
    unless checks.respond_to?(method)
      Homebrew.failed = true
      puts "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." unless Homebrew.failed?
end

.doctor_argsCLI::Parser

Returns:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'brew/Library/Homebrew/cmd/doctor.rb', line 14

def doctor_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Check your system for potential problems. Will exit with a non-zero status
      if any potential problems are found. 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.
    EOS
    switch "--list-checks",
           description: "List all audit methods, which can be run individually "\
                        "if provided as arguments."
    switch "-D", "--audit-debug",
           description: "Enable debugging and profiling of audit methods."

    named_args :diagnostic_check
  end
end

.download_artifact(url, dir, pr) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'brew/Library/Homebrew/dev-cmd/pr-pull.rb', line 342

def download_artifact(url, dir, pr)
  raise "Credentials must be set to access the Artifacts API" if GitHub.api_credentials_type == :none

  token = GitHub.api_credentials
  curl_args = ["--header", "Authorization: token #{token}"]

  # Download the artifact as a zip file and unpack it into `dir`. This is
  # preferred over system `curl` and `tar` as this leverages the Homebrew
  # cache to avoid repeated downloads of (possibly large) bottles.
  FileUtils.chdir dir do
    downloader = GitHubArtifactDownloadStrategy.new(url, "artifact", pr, curl_args: curl_args, secrets: [token])
    downloader.fetch
    downloader.stage
  end
end

.editvoid

This method returns an undefined value.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'brew/Library/Homebrew/dev-cmd/edit.rb', line 31

def edit
  args = edit_args.parse

  unless (HOMEBREW_REPOSITORY/".git").directory?
    raise <<~EOS
      Changes will be lost!
      The first time you `brew update`, all local changes will be lost; you should
      thus `brew update` before you `brew edit`!
    EOS
  end

  paths = args.named.to_paths.select do |path|
    next path if path.exist?

    raise UsageError, "#{path} doesn't exist on disk. " \
                      "Run #{Formatter.identifier("brew create --set-name #{path.basename} $URL")} " \
                      "to create a new Formula!"
  end.presence

  # If no brews are listed, open the project root in an editor.
  paths ||= [HOMEBREW_REPOSITORY]

  exec_editor(*paths)
end

.edit_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'brew/Library/Homebrew/dev-cmd/edit.rb', line 13

def edit_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Open a <formula> or <cask> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
      or open the Homebrew repository for editing if no formula is provided.
    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]
  end
end

.ensure_relocation_formulae_installed!Object



101
102
103
104
105
106
107
108
# File 'brew/Library/Homebrew/dev-cmd/bottle.rb', line 101

def ensure_relocation_formulae_installed!
  Keg.relocation_formulae.each do |f|
    next if Formula[f].latest_version_installed?

    ohai "Installing #{f}..."
    safe_system HOMEBREW_BREW_FILE, "install", f
  end
end

.env_vars_manpageString

Returns:



239
240
241
242
243
244
245
246
247
248
249
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 239

def env_vars_manpage
  lines = Homebrew::EnvConfig::ENVS.flat_map do |env, hash|
    entry = "- `#{env}`:\n  <br>#{hash[:description]}\n"
    default = hash[:default_text]
    default ||= "`#{hash[:default]}`." if hash[:default]
    entry += "\n\n  *Default:* #{default}\n" if default

    entry
  end
  lines.join("\n")
end

.extractObject



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
# File 'brew/Library/Homebrew/dev-cmd/extract.rb', line 102

def extract
  args = extract_args.parse

  if (match = args.named.first.match(HOMEBREW_TAP_FORMULA_REGEX))
    name = match[3].downcase
    source_tap = Tap.fetch(match[1], match[2])
    raise TapFormulaUnavailableError.new(source_tap, name) unless source_tap.installed?
  else
    name = args.named.first.downcase
    source_tap = CoreTap.instance
  end

  destination_tap = Tap.fetch(args.named.second)
  unless Homebrew::EnvConfig.developer?
    odie "Cannot extract formula to homebrew/core!" if destination_tap.core_tap?
    odie "Cannot extract formula to the same tap!" if destination_tap == source_tap
  end
  destination_tap.install unless destination_tap.installed?

  repo = source_tap.path
  pattern = if source_tap.core_tap?
    [repo/"Formula/#{name}.rb"]
  else
    # A formula can technically live in the root directory of a tap or in any of its subdirectories
    [repo/"#{name}.rb", repo/"**/#{name}.rb"]
  end

  if args.version
    ohai "Searching repository history"
    version = args.version
    version_segments = Gem::Version.new(version).segments if Gem::Version.correct?(version)
    rev = nil
    test_formula = nil
    result = ""
    loop do
      rev = rev.nil? ? "HEAD" : "#{rev}~1"
      rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern, before_commit: rev)
      if rev.nil? && source_tap.shallow?
        odie <<~EOS
          Could not find #{name} but #{source_tap} is a shallow clone!
          Try again after running:
            git -C "#{source_tap.path}" fetch --unshallow
        EOS
      elsif rev.nil?
        odie "Could not find #{name}! The formula or version may not have existed."
      end

      file = repo/path
      result = Utils::Git.last_revision_of_file(repo, file, before_commit: rev)
      if result.empty?
        odebug "Skipping revision #{rev} - file is empty at this revision"
        next
      end

      test_formula = formula_at_revision(repo, name, file, rev)
      break if test_formula.nil? || test_formula.version == version

      if version_segments && Gem::Version.correct?(test_formula.version)
        test_formula_version_segments = Gem::Version.new(test_formula.version).segments
        if version_segments.length < test_formula_version_segments.length
          odebug "Apply semantic versioning with #{test_formula_version_segments}"
          break if version_segments == test_formula_version_segments.first(version_segments.length)
        end
      end

      odebug "Trying #{test_formula.version} from revision #{rev} against desired #{version}"
    end
    odie "Could not find #{name}! The formula or version may not have existed." if test_formula.nil?
  else
    # Search in the root directory of <repo> as well as recursively in all of its subdirectories
    files = Dir[repo/"{,**/}"].map do |dir|
      Pathname.glob(["#{dir}/#{name}.rb"]).find(&:file?)
    end.compact

    if files.empty?
      ohai "Searching repository history"
      rev, (path,) = Utils::Git.last_revision_commit_of_files(repo, pattern)
      odie "Could not find #{name}! The formula or version may not have existed." if rev.nil?
      file = repo/path
      version = formula_at_revision(repo, name, file, rev).version
      result = Utils::Git.last_revision_of_file(repo, file)
    else
      file = files.first.realpath
      rev = "HEAD"
      version = Formulary.factory(file).version
      result = File.read(file)
    end
  end

  # The class name has to be renamed to match the new filename,
  # e.g. Foo version 1.2.3 becomes FooAT123 and resides in Foo@1.2.3.rb.
  class_name = Formulary.class_s(name)

  # Remove any existing version suffixes, as a new one will be added later
  name.sub!(/\b@(.*)\z\b/i, "")
  versioned_name = Formulary.class_s("#{name}@#{version}")
  result.sub!("class #{class_name} < Formula", "class #{versioned_name} < Formula")

  # Remove bottle blocks, they won't work.
  result.sub!(/  bottle do.+?end\n\n/m, "") if destination_tap != source_tap

  path = destination_tap.path/"Formula/#{name}@#{version}.rb"
  if path.exist?
    unless args.force?
      odie <<~EOS
        Destination formula already exists: #{path}
        To overwrite it and continue anyways, run:
          brew extract --force --version=#{version} #{name} #{destination_tap.name}
      EOS
    end
    odebug "Overwriting existing formula at #{path}"
    path.delete
  end
  ohai "Writing formula for #{name} from revision #{rev} to:"
  puts path
  path.write result
end

.extract_argsCLI::Parser

Returns:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'brew/Library/Homebrew/dev-cmd/extract.rb', line 84

def extract_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Look through repository history to find the most recent version of <formula> and
      create a copy in <tap>`/Formula/`<formula>`@`<version>`.rb`. If the tap is not
      installed yet, attempt to install/clone the tap before continuing. To extract
      a formula from a tap that is not `homebrew/core` use its fully-qualified form of
      <user>`/`<repo>`/`<formula>.
    EOS
    flag   "--version=",
           description: "Extract the specified <version> of <formula> instead of the most recent."
    switch "-f", "--force",
           description: "Overwrite the destination formula if it already exists."

    named_args :formula, number: 2
  end
end

.failed?Boolean

Returns:

  • (Boolean)


101
102
103
104
# File 'brew/Library/Homebrew/global.rb', line 101

def failed?
  @failed ||= false
  @failed == true
end

.fetchObject



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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 63

def fetch
  args = fetch_args.parse

  bucket = if args.deps?
    args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
      case formula_or_cask
      when Formula
        f = formula_or_cask

        [f, *f.recursive_dependencies.map(&:to_formula)]
      else
        formula_or_cask
      end
    end
  else
    args.named.to_formulae_and_casks
  end.uniq

  puts "Fetching: #{bucket * ", "}" if bucket.size > 1
  bucket.each do |formula_or_cask|
    case formula_or_cask
    when Formula
      f = formula_or_cask

      f.print_tap_action verb: "Fetching"

      fetched_bottle = false
      if fetch_bottle?(f, args: args)
        begin
          fetch_formula(f.bottle, args: args)
        rescue Interrupt
          raise
        rescue => e
          raise if Homebrew::EnvConfig.developer?

          fetched_bottle = false
          onoe e.message
          opoo "Bottle fetch failed: fetching the source."
        else
          fetched_bottle = true
        end
      end

      next if fetched_bottle

      fetch_formula(f, args: args)

      f.resources.each do |r|
        fetch_resource(r, args: args)
        r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
      end

      f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
    else
      cask = formula_or_cask

      quarantine = args.quarantine?
      quarantine = true if quarantine.nil?

      download = Cask::Download.new(cask, quarantine: quarantine)
      fetch_cask(download, args: args)
    end
  end
end

.fetch_argsCLI::Parser

Returns:



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
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 17

def fetch_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Download a bottle (if available) or source packages for <formula>e
      and binaries for <cask>s. For files, also print SHA-256 checksums.
    EOS
    switch "--HEAD",
           description: "Fetch HEAD version instead of stable version."
    switch "-f", "--force",
           description: "Remove a previously cached version and re-fetch."
    switch "-v", "--verbose",
           description: "Do a verbose VCS checkout, if the URL represents a VCS. This is useful for "\
                        "seeing if an existing VCS cache has been updated."
    switch "--retry",
           description: "Retry if downloading fails or re-download if the checksum of a previously cached "\
                        "version no longer matches."
    switch "--deps",
           description: "Also download dependencies for any listed <formula>."
    switch "-s", "--build-from-source",
           description: "Download source packages rather than a bottle."
    switch "--build-bottle",
           description: "Download source packages (for eventual bottling) rather than a bottle."
    switch "--force-bottle",
           description: "Download a bottle if it exists for the current or newest version of macOS, "\
                        "even if it would not be used during installation."
    switch "--[no-]quarantine",
           description: "Disable/enable quarantining of downloads (default: enabled).",
           env:         :cask_opts_quarantine

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

    conflicts "--build-from-source", "--build-bottle", "--force-bottle"
    conflicts "--cask", "--HEAD"
    conflicts "--cask", "--deps"
    conflicts "--cask", "-s"
    conflicts "--cask", "--build-bottle"
    conflicts "--cask", "--force-bottle"

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

.fetch_cask(cask_download, args:) ⇒ Object



143
144
145
146
147
148
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 143

def fetch_cask(cask_download, args:)
  fetch_fetchable cask_download, args: args
rescue ChecksumMismatchError => e
  retry if retry_fetch?(cask_download, args: args)
  opoo "Cask reports different sha256: #{e.expected}"
end

.fetch_fetchable(f, args:) ⇒ Object



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

def fetch_fetchable(f, args:)
  f.clear_cache if args.force?

  already_fetched = f.cached_download.exist?

  begin
    download = f.fetch(verify_download_integrity: false)
  rescue DownloadError
    retry if retry_fetch?(f, args: args)
    raise
  end

  return unless download.file?

  puts "Downloaded to: #{download}" unless already_fetched
  puts "SHA256: #{download.sha256}"

  f.verify_download_integrity(download)
end

.fetch_formula(f, args:) ⇒ Object



136
137
138
139
140
141
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 136

def fetch_formula(f, args:)
  fetch_fetchable f, args: args
rescue ChecksumMismatchError => e
  retry if retry_fetch?(f, args: args)
  opoo "Formula reports different sha256: #{e.expected}"
end

.fetch_patch(p, args:) ⇒ Object



150
151
152
153
154
155
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 150

def fetch_patch(p, args:)
  fetch_fetchable p, args: args
rescue ChecksumMismatchError => e
  Homebrew.failed = true
  opoo "Patch reports different sha256: #{e.expected}"
end

.fetch_resource(formula, new_version, url, **specs) ⇒ Object



128
129
130
131
132
133
134
# File 'brew/Library/Homebrew/cmd/fetch.rb', line 128

def fetch_resource(r, args:)
  puts "Resource: #{r.name}"
  fetch_fetchable r, args: args
rescue ChecksumMismatchError => e
  retry if retry_fetch?(r, args: args)
  opoo "Resource #{r.name} reports different sha256: #{e.expected}"
end

.filtered_list(args:) ⇒ Object



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
# File 'brew/Library/Homebrew/cmd/list.rb', line 132

def filtered_list(args:)
  names = if args.no_named?
    Formula.racks
  else
    racks = args.named.map { |n| Formulary.to_rack(n) }
    racks.select do |rack|
      Homebrew.failed = true unless rack.exist?
      rack.exist?
    end
  end
  if args.pinned?
    pinned_versions = {}
    names.sort.each do |d|
      keg_pin = (HOMEBREW_PINNED_KEGS/d.basename.to_s)
      pinned_versions[d] = keg_pin.readlink.basename.to_s if keg_pin.exist? || keg_pin.symlink?
    end
    pinned_versions.each do |d, version|
      puts d.basename.to_s.concat(args.versions? ? " #{version}" : "")
    end
  else # --versions without --pinned
    names.sort.each do |d|
      versions = d.subdirs.map { |pn| pn.basename.to_s }
      next if args.multiple? && versions.length < 2

      puts "#{d.basename} #{versions * " "}"
    end
  end
end

.find_in_path(executable) ⇒ Object



91
92
93
94
95
# File 'brew/Library/Homebrew/utils/gems.rb', line 91

def find_in_path(executable)
  ENV.fetch("PATH").split(":").find do |path|
    File.executable?(File.join(path, executable))
  end
end

.force_auto_update?(args:) ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
81
# File 'brew/Library/Homebrew/cmd/tap.rb', line 78

def force_auto_update?(args:)
  # if no relevant flag is present, return nil, meaning "no change"
  true if args.force_auto_update?
end

.format_long_opt(opt) ⇒ Object



263
264
265
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 263

def format_long_opt(opt)
  "`#{opt}`" unless opt.nil?
end

.format_problem(message, location) ⇒ Object



266
267
268
# File 'brew/Library/Homebrew/dev-cmd/audit.rb', line 266

def format_problem(message, location)
  "* #{location&.to_s&.dup&.concat(": ")}#{message.chomp.gsub("\n", "\n    ")}"
end

.format_problem_lines(problems) ⇒ Object



261
262
263
264
# File 'brew/Library/Homebrew/dev-cmd/audit.rb', line 261

def format_problem_lines(problems)
  problems.uniq
          .map { |message:, location:| format_problem(message, location) }
end

.format_short_opt(opt) ⇒ Object



259
260
261
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 259

def format_short_opt(opt)
  "`#{opt}`" unless opt.nil?
end

.format_usage_banner(usage_banner) ⇒ Object



267
268
269
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 267

def format_usage_banner(usage_banner)
  usage_banner&.sub(/^(#: *\* )?/, "### ")
end

.formulaObject



23
24
25
26
27
# File 'brew/Library/Homebrew/dev-cmd/formula.rb', line 23

def formula
  args = formula_args.parse

  args.named.to_formulae_paths.each(&method(:puts))
end

.formula_argsCLI::Parser

Returns:



13
14
15
16
17
18
19
20
21
# File 'brew/Library/Homebrew/dev-cmd/formula.rb', line 13

def formula_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Display the path where <formula> is located.
    EOS

    named_args :formula, min: 1
  end
end

.formula_version(formula, contents = nil) ⇒ Object



447
448
449
450
451
452
453
454
455
456
# File 'brew/Library/Homebrew/dev-cmd/bump-formula-pr.rb', line 447

def formula_version(formula, contents = nil)
  spec = :stable
  name = formula.name
  path = formula.path
  if contents.present?
    Formulary.from_contents(name, path, contents, spec).version
  else
    Formulary::FormulaLoader.new(name, path).get_formula(spec).version
  end
end

.formulae_all_installs_from_args(args) ⇒ Object



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
# File 'brew/Library/Homebrew/dev-cmd/unbottled.rb', line 72

def formulae_all_installs_from_args(args)
  if args.named.present?
    formulae = all_formulae = args.named.to_formulae
  elsif args.total?
    formulae = all_formulae = Formula.to_a
  elsif args.dependents?
    formulae = all_formulae = Formula.to_a

    @sort = " (sorted by installs in the last 90 days)"
  else
    formula_installs = {}

    ohai "Getting analytics data..."
    analytics = Utils::Analytics.formulae_brew_sh_json("analytics/install/90d.json")

    if analytics.blank?
      raise UsageError,
            "default sort by analytics data requires " \
            "`HOMEBREW_NO_GITHUB_API` and `HOMEBREW_NO_ANALYTICS` to be unset"
    end

    formulae = analytics["items"].map do |i|
      f = i["formula"].split.first
      next if f.include?("/")
      next if formula_installs[f].present?

      formula_installs[f] = i["count"]
      begin
        Formula[f]
      rescue FormulaUnavailableError
        nil
      end
    end.compact
    @sort = " (sorted by installs in the last 90 days)"

    all_formulae = Formula
  end

  [formulae, all_formulae, formula_installs]
end

.formulae_need_bottles?(tap, original_commit, user, repo, pr, args:) ⇒ Boolean

Returns:

  • (Boolean)


295
296
297
298
299
300
301
302
# File 'brew/Library/Homebrew/dev-cmd/pr-pull.rb', line 295

def formulae_need_bottles?(tap, original_commit, user, repo, pr, args:)
  return if args.dry_run?
  return false if GitHub.pull_request_labels(user, repo, pr).include? "CI-syntax-only"

  changed_formulae(tap, original_commit).any? do |f|
    !f.bottle_unneeded? && !f.bottle_disabled?
  end
end

.gem_user_bindirObject



24
25
26
27
# File 'brew/Library/Homebrew/utils/gems.rb', line 24

def gem_user_bindir
  require "rubygems"
  "#{gem_user_dir}/bin"
end

.gem_user_dirObject



20
21
22
# File 'brew/Library/Homebrew/utils/gems.rb', line 20

def gem_user_dir
  ENV["HOMEBREW_TESTS_GEM_USER_DIR"] || Gem.user_dir
end

.generate_cmd_manpages(cmd_paths) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 159

def generate_cmd_manpages(cmd_paths)
  man_page_lines = []

  # preserve existing manpage order
  cmd_paths.sort_by(&method(:sort_key_for_path))
           .each do |cmd_path|
    cmd_man_page_lines = if cmd_parser = CLI::Parser.from_cmd_path(cmd_path)
      next if cmd_parser.hide_from_man_page

      cmd_parser_manpage_lines(cmd_parser).join
    else
      cmd_comment_manpage_lines(cmd_path)
    end

    man_page_lines << cmd_man_page_lines
  end

  man_page_lines.compact.join("\n")
end

.generate_option_doc(short, long, desc) ⇒ Object



251
252
253
254
255
256
257
# File 'brew/Library/Homebrew/dev-cmd/man.rb', line 251

def generate_option_doc(short, long, desc)
  comma = (short && long) ? ", " : ""
  <<~EOS
    * #{format_short_opt(short)}#{comma}#{format_long_opt(long)}:
      #{desc}
  EOS
end

.get_removable_formulae(formulae) ⇒ Object



23
24
25
26
27
28
29
30
31
# File 'brew/Library/Homebrew/cmd/autoremove.rb', line 23

def get_removable_formulae(formulae)
  removable_formulae = Formula.installed_formulae_with_no_dependents(formulae).reject do |f|
    Tab.for_keg(f.any_installed_keg).installed_on_request
  end

  removable_formulae += get_removable_formulae(formulae - removable_formulae) if removable_formulae.present?

  removable_formulae
end

.gist_logsObject



125
126
127
128
129
130
131
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 125

def gist_logs
  args = gist_logs_args.parse

  Install.perform_preinstall_checks(all_fatal: true)
  Install.perform_build_from_source_checks(all_fatal: true)
  gistify_logs(args.named.to_resolved_formulae.first, args: args)
end

.gist_logs_argsCLI::Parser

Returns:



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 19

def gist_logs_args
  Homebrew::CLI::Parser.new do
    description <<~EOS
      Upload logs for a failed build of <formula> to a new Gist. Presents an
      error message if no logs are found.
    EOS
    switch "--with-hostname",
           description: "Include the hostname in the Gist."
    switch "-n", "--new-issue",
           description: "Automatically create a new issue in the appropriate GitHub repository "\
                        "after creating the Gist."
    switch "-p", "--private",
           description: "The Gist will be marked private and will not appear in listings but will "\
                        "be accessible with its link."

    named_args :formula, number: 1
  end
end

.gistify_logs(f, args:) ⇒ Object



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
# File 'brew/Library/Homebrew/cmd/gist-logs.rb', line 38

def gistify_logs(f, args:)
  files = load_logs(f.logs)
  build_time = f.logs.ctime
  timestamp = build_time.strftime("%Y-%m-%d_%H-%M-%S")

  s = StringIO.new
  SystemConfig.dump_verbose_config s
  # Dummy summary file, asciibetically first, to control display title of gist
  files["# #{f.name} - #{timestamp}.txt"] = { content: brief_build_info(f, with_hostname: args.with_hostname?) }
  files["00.config.out"] = { content: s.string }
  files["00.doctor.out"] = { content: Utils.popen_read("#{HOMEBREW_PREFIX}/bin/brew", "doctor", err: :out) }
  unless f.core_formula?
    tap = <<~EOS
      Formula: #{f.name}
          Tap: #{f.tap}
         Path: #{f.path}
    EOS
    files["00.tap.out"] = { content: tap }
  end

  odie "`brew gist-logs` requires HOMEBREW_GITHUB_API_TOKEN to be set!" if GitHub.api_credentials_type == :none

  # Description formatted to work well as page title when viewing gist
  descr = if f.core_formula?
    "#{f.name} on #{OS_VERSION} - Homebrew build logs"
  else
    "#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs"
  end
  url = create_gist(files, descr, private: args.private?)

  url = create_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url) if args.new_issue?

  puts url if url
end

.git_log(cd_dir, path = nil, tap = nil, args:) ⇒ Object



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
# File 'brew/Library/Homebrew/cmd/log.rb', line 52

def git_log(cd_dir, path = nil, tap = nil, args:)
  cd cd_dir
  repo = Utils.popen_read("git rev-parse --show-toplevel").chomp
  if tap
    name = tap.to_s
    git_cd = "$(brew --repo #{tap})"
  elsif cd_dir == HOMEBREW_REPOSITORY
    name = "Homebrew/brew"
    git_cd = "$(brew --repo)"
  else
    name, git_cd = cd_dir
  end

  if File.exist? "#{repo}/.git/shallow"
    opoo <<~EOS
      #{name} is a shallow clone so only partial output will be shown.
      To get a full clone run:
        git -C "#{git_cd}" fetch --unshallow
    EOS
  end

  git_args = []
  git_args << "--patch" if args.patch?
  git_args << "--stat" if args.stat?
  git_args << "--oneline" if args.oneline?
  git_args << "-1" if args.public_send(:'1?')
  git_args << "--max-count" << args.max_count if args.max_count
  git_args += ["--follow", "--", path] if path.present?
  system "git", "log", *git_args
end

.github_info(f) ⇒ Object



217
218
219
220
221
222
223
224
225
226
# File 'brew/Library/Homebrew/cmd/info.rb', line 217

def github_info(f)
  return f.path if f.tap.blank? || f.tap.remote.blank?

  path = if f.class.superclass == Formula
    f.path.relative_path_from(f.tap.path)
  elsif f.is_a?(