Merge branch 'master' into master

This commit is contained in:
Mike McQuaid 2025-06-11 13:24:46 +01:00 committed by GitHub
commit dffa42839c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
209 changed files with 2710 additions and 1287 deletions

128
.github/dependabot.yml vendored
View File

@ -1,90 +1,44 @@
# This file is synced from the `.github` repository, do not modify it directly.
---
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
day: friday
time: '08:00'
timezone: Etc/UTC
allow:
- dependency-type: all
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: docker
directory: "/"
schedule:
interval: weekly
day: friday
time: '08:00'
timezone: Etc/UTC
allow:
- dependency-type: all
groups:
docker:
patterns:
- "*"
- package-ecosystem: devcontainers
directory: "/"
schedule:
interval: weekly
day: friday
time: '08:00'
timezone: Etc/UTC
allow:
- dependency-type: all
groups:
devcontainers:
patterns:
- "*"
- package-ecosystem: bundler
directories:
- /
- /Library/Homebrew
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
bundler:
patterns:
- "*"
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
npm:
patterns:
- "*"
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
docker:
patterns:
- "*"
- package-ecosystem: devcontainers
directory: /
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
devcontainers:
patterns:
- "*"
- package-ecosystem: pip
directories:
- /
- /Library/Homebrew/formula-analytics/
schedule:
interval: weekly
day: "friday"
time: "08:00"
timezone: "Etc/UTC"
allow:
- dependency-type: all
groups:
pip:
patterns:
- "*"

View File

@ -77,7 +77,7 @@ jobs:
path: results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
sarif_file: results.sarif
category: zizmor

View File

@ -28,7 +28,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
languages: ruby
config: |
@ -36,4 +36,4 @@ jobs:
- Library/Homebrew/vendor
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19

View File

@ -80,7 +80,7 @@ jobs:
name: tap syntax
needs: syntax
if: github.repository_owner == 'Homebrew'
runs-on: macos-14
runs-on: macos-15
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -141,7 +141,7 @@ jobs:
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
core: true
core: false
cask: false
test-bot: false
@ -164,7 +164,7 @@ jobs:
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
core: true
core: false
cask: true
test-bot: false
@ -213,10 +213,10 @@ jobs:
strategy:
matrix:
include:
- name: update-test (Ubuntu)
- name: update-test (Linux)
runs-on: ubuntu-latest
- name: update-test (macOS)
runs-on: macos-15
runs-on: macos-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -237,7 +237,6 @@ jobs:
name: ${{ matrix.name }}
needs: syntax
runs-on: ${{ matrix.runs-on }}
container: ${{ matrix.container }}
strategy:
matrix:
include:
@ -247,17 +246,10 @@ jobs:
- name: tests (generic OS)
test-flags: --generic --coverage
runs-on: ubuntu-latest
- name: tests (Ubuntu 24.04)
- name: tests (Linux)
test-flags: --coverage
runs-on: ubuntu-24.04
- name: tests (Ubuntu 22.04)
test-flags: --coverage
runs-on: ubuntu-22.04
- name: tests (Ubuntu 20.04)
test-flags: --coverage
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest
- name: tests (macOS 15 arm64)
- name: tests (macOS)
test-flags: --coverage
runs-on: macos-15
steps:
@ -349,7 +341,7 @@ jobs:
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
test-default-formula:
test-bot:
name: ${{ matrix.name }}
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -358,36 +350,76 @@ jobs:
strategy:
matrix:
include:
- name: test default formula (Ubuntu 24.04)
runs-on: ubuntu-latest
- name: test-bot (Linux arm64)
runs-on: ubuntu-24.04-arm
container: ghcr.io/homebrew/ubuntu24.04:latest
- name: test default formula (Ubuntu 22.04)
- name: test-bot (Linux x86_64)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu22.04:master
- name: test default formula (Ubuntu 20.04)
# Use Debian Old Stable for testing Homebrew's glibc support.
- name: test-bot (Linux Homebrew glibc)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest
- name: test default formula (macOS 13 x86_64)
container: debian:oldstable
- name: test-bot (macOS x86_64)
runs-on: macos-13
- name: test default formula (macOS 15 arm64)
- name: test-bot (macOS arm64)
runs-on: macos-15
env:
HOMEBREW_TEST_BOT_ANALYTICS: 1
HOMEBREW_ENFORCE_SBOM: 1
steps:
- name: Install Homebrew and Homebrew's dependencies
# All other images are built from our Homebrew Dockerfile.
# This is the only one that needs to be set up manually.
if: matrix.container == 'debian:oldstable'
run: |
# Slimmed down version from the Homebrew Dockerfile
apt-get update
apt-get install -y --no-install-recommends \
bzip2 \
ca-certificates \
curl \
file \
g++ \
git-core \
less \
locales \
make \
netbase \
patch \
procps \
sudo \
uuid-runtime \
tzdata
# Install Homebrew
mkdir -p /home/linuxbrew/.linuxbrew/bin
# Don't do shallow clone or it's unshallowed by "Set up Homebrew"
git clone https://github.com/Homebrew/brew.git /home/linuxbrew/.linuxbrew/Homebrew
cd /home/linuxbrew/.linuxbrew/bin
ln -s ../Homebrew/bin/brew brew
echo "/home/linuxbrew/.linuxbrew/bin" >>"$GITHUB_PATH"
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
core: true
core: false
cask: false
test-bot: true
- run: brew test-bot --only-cleanup-before
- name: Setup environment variables
if: matrix.container == 'ghcr.io/homebrew/ubuntu20.04:latest'
run: echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV"
run: |
# Set enviroment variables to bypass `brew doctor` failures on Tier >=2 configurations
if [[ "${MATRIX_NAME}" == "test-bot (Linux arm64)" ]]; then
echo "HOMEBREW_ARM64_TESTING=1" >> "$GITHUB_ENV"
elif [[ "${MATRIX_NAME}" == "test-bot (Linux Homebrew glibc)" ]]; then
echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV"
fi
env:
MATRIX_NAME: ${{ matrix.name }}
- run: brew test-bot --only-setup
@ -395,7 +427,7 @@ jobs:
- run: brew test-bot --only-formulae --only-json-tab --test-default-formula
test-brew-bundle-services:
bundle-and-services:
name: ${{ matrix.name }}
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -403,16 +435,16 @@ jobs:
strategy:
matrix:
include:
- name: test brew bundle and brew services (Ubuntu)
- name: bundle and services (Linux)
runs-on: ubuntu-latest
- name: test brew bundle and brew services (macOS)
runs-on: macos-15
- name: bundle and services (macOS)
runs-on: macos-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
with:
core: true
core: false
cask: false
test-bot: false
@ -449,13 +481,16 @@ jobs:
brew services cleanup
brew bundle cleanup --force
test-analytics:
runs-on: ${{ matrix.os }}
analytics:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
include:
- name: analytics (Linux)
runs-on: ubuntu-latest
- name: analytics (macOS)
runs-on: macos-latest
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
steps:

View File

@ -122,14 +122,14 @@ module Homebrew
end
end
sig { params(json: Hash).returns(Hash) }
def self.merge_variations(json)
sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) }
def self.merge_variations(json, bottle_tag: nil)
return json unless json.key?("variations")
bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os,
arch: Homebrew::SimulateSystem.current_arch)
bottle_tag ||= Homebrew::SimulateSystem.current_tag
if (variation = json.dig("variations", bottle_tag.to_s).presence)
if (variation = json.dig("variations", bottle_tag.to_s).presence) ||
(variation = json.dig("variations", bottle_tag.to_sym).presence)
json = json.merge(variation)
end

View File

@ -1,7 +1,7 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "extend/cachable"
require "cachable"
require "api/download"
module Homebrew
@ -12,6 +12,13 @@ module Homebrew
DEFAULT_API_FILENAME = "cask.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"cask.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(token: String).returns(Hash) }
@ -41,12 +48,12 @@ module Homebrew
end
def self.cached_json_file_path
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
HOMEBREW_CACHE_API/api_filename
end
sig { returns(T::Boolean) }
def self.download_and_cache_data!
json_casks, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME
json_casks, updated = Homebrew::API.fetch_json_api_file api_filename
cache["renames"] = {}
cache["casks"] = json_casks.to_h do |json_cask|

View File

@ -1,7 +1,7 @@
# typed: strict
# frozen_string_literal: true
require "extend/cachable"
require "cachable"
require "api/download"
module Homebrew
@ -12,6 +12,13 @@ module Homebrew
DEFAULT_API_FILENAME = "formula.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"internal/formula.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(name: String).returns(T::Hash[String, T.untyped]) }
@ -42,12 +49,12 @@ module Homebrew
sig { returns(Pathname) }
def self.cached_json_file_path
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
HOMEBREW_CACHE_API/api_filename
end
sig { returns(T::Boolean) }
def self.download_and_cache_data!
json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME
json_formulae, updated = Homebrew::API.fetch_json_api_file api_filename
cache["aliases"] = {}
cache["renames"] = {}

View File

@ -600,6 +600,11 @@ case "$1" in
homebrew-version
exit 0
;;
mcp-server)
source "${HOMEBREW_LIBRARY}/Homebrew/cmd/mcp-server.sh"
homebrew-mcp-server "$@"
exit 0
;;
esac
# TODO: bump version when new macOS is released or announced and update references in:
@ -1078,6 +1083,22 @@ else
export HOMEBREW_GITHUB_PACKAGES_AUTH="Bearer QQ=="
fi
# Avoid picking up any random `sudo` in `PATH`.
if [[ -x /usr/bin/sudo ]]
then
SUDO=/usr/bin/sudo
else
# Do this after ensuring we're using default Bash builtins.
SUDO="$(command -v sudo 2>/dev/null)"
fi
# Reset sudo timestamp to avoid running unauthorized sudo commands
if [[ -n "${SUDO}" ]]
then
"${SUDO}" --reset-timestamp 2>/dev/null || true
fi
unset SUDO
if [[ -n "${HOMEBREW_BASH_COMMAND}" ]]
then
# source rather than executing directly to ensure the entire file is read into

View File

@ -130,6 +130,9 @@ module Homebrew
@formula_versions_from_env[formula_env_name]
end
sig { void }
def prepend_pkgconf_path_if_needed!; end
sig { void }
def reset!
@mas_installed = T.let(nil, T.nilable(T::Boolean))

View File

@ -64,14 +64,8 @@ module Homebrew
ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims"
end
# Setup pkg-config, if present, to help locate packages
# Only need this on Linux as Homebrew provides a shim on macOS
# TODO: use extend/OS here
# rubocop:todo Homebrew/MoveToExtendOS
if OS.linux? && (pkgconf = Formulary.factory("pkgconf")) && pkgconf.any_version_installed?
ENV.prepend_path "PATH", pkgconf.opt_bin.to_s
end
# rubocop:enable Homebrew/MoveToExtendOS
# Setup pkgconf, if needed, to help locate packages
Bundle.prepend_pkgconf_path_if_needed!
# For commands which aren't either absolute or relative
# Add the command directory to PATH, since it may get blown away by superenv
@ -170,6 +164,18 @@ module Homebrew
end
end
return
elsif subcommand == "sh"
preferred_path = Utils::Shell.preferred_path(default: "/bin/bash")
notice = unless Homebrew::EnvConfig.no_env_hints?
<<~EOS
Your shell has been configured to use a build environment from your `Brewfile`.
This should help you build stuff.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
When done, type `exit`.
EOS
end
ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil
args = [Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)]
end
if services

View File

@ -11,19 +11,6 @@ module Homebrew
def skip?(entry, silent: false)
require "bundle/brew_dumper"
# TODO: use extend/OS here
# rubocop:todo Homebrew/MoveToExtendOS
if (Hardware::CPU.arm? || OS.linux?) &&
Homebrew.default_prefix? &&
entry.type == :brew && entry.name.exclude?("/") &&
(formula = BrewDumper.formulae_by_full_name(entry.name)) &&
formula[:official_tap] &&
!formula[:bottled]
reason = Hardware::CPU.arm? ? "Apple Silicon" : "Linux"
puts Formatter.warning "Skipping #{entry.name} (no bottle for #{reason})" unless silent
return true
end
# rubocop:enable Homebrew/MoveToExtendOS
return true if @failed_taps&.any? do |tap|
prefix = "#{tap}/"
entry.name.start_with?(prefix) || entry.options[:full_name]&.start_with?(prefix)

View File

@ -8,7 +8,7 @@ require "cask/dsl"
require "cask/metadata"
require "cask/tab"
require "utils/bottles"
require "extend/api_hashable"
require "api_hashable"
module Cask
# An instance of a cask.
@ -416,16 +416,14 @@ module Cask
if @dsl.on_system_blocks_exist?
begin
OnSystem::ALL_OS_ARCH_COMBINATIONS.each do |os, arch|
bottle_tag = ::Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
next if bottle_tag.linux? && @dsl.os.nil?
next if bottle_tag.macos? &&
depends_on.macos &&
!@dsl.depends_on_set_in_block? &&
!depends_on.macos.allows?(bottle_tag.to_macos_version)
Homebrew::SimulateSystem.with(os:, arch:) do
Homebrew::SimulateSystem.with_tag(bottle_tag) do
refresh
to_h.each do |key, value|

View File

@ -6,6 +6,7 @@ require "cask/cask"
require "uri"
require "utils/curl"
require "extend/hash/keys"
require "api"
module Cask
# Loads a cask from various sources.
@ -104,8 +105,8 @@ module Cask
return
end
return if %w[.rb .json].exclude?(path.extname)
return unless path.expand_path.exist?
return if invalid_path?(path)
return if Homebrew::EnvConfig.forbid_packages_from_paths? &&
!path.realpath.to_s.start_with?("#{Caskroom.path}/", "#{HOMEBREW_LIBRARY}/Taps/")
@ -113,6 +114,14 @@ module Cask
new(path)
end
sig { params(pathname: Pathname, valid_extnames: T::Array[String]).returns(T::Boolean) }
def self.invalid_path?(pathname, valid_extnames: %w[.rb .json])
return true if valid_extnames.exclude?(pathname.extname)
@invalid_basenames ||= %w[INSTALL_RECEIPT.json sbom.spdx.json].freeze
@invalid_basenames.include?(pathname.basename.to_s)
end
attr_reader :token, :path
sig { params(path: T.any(Pathname, String), token: String).void }
@ -135,8 +144,10 @@ module Cask
@content = path.read(encoding: "UTF-8")
@config = config
if path.extname == ".json"
return FromAPILoader.new(token, from_json: JSON.parse(@content), path:).load(config:)
if !self.class.invalid_path?(path, valid_extnames: %w[.json]) &&
(from_json = JSON.parse(@content).presence) &&
from_json.is_a?(Hash)
return FromAPILoader.new(token, from_json:, path:).load(config:)
end
begin
@ -284,7 +295,7 @@ module Cask
sig { returns(Pathname) }
attr_reader :path
sig { returns(T.nilable(Hash)) }
sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.anything])) }
attr_reader :from_json
sig {
@ -306,7 +317,13 @@ module Cask
new("#{tap}/#{token}")
end
sig { params(token: String, from_json: Hash, path: T.nilable(Pathname)).void }
sig {
params(
token: String,
from_json: T.nilable(T::Hash[T.any(String, Symbol), T.anything]),
path: T.nilable(Pathname),
).void
}
def initialize(token, from_json: T.unsafe(nil), path: nil)
@token = token.sub(%r{^homebrew/(?:homebrew-)?cask/}i, "")
@sourcefile_path = path || Homebrew::API::Cask.cached_json_file_path
@ -400,7 +417,7 @@ module Cask
container(**container_hash)
end
json_cask[:artifacts].each do |artifact|
json_cask[:artifacts]&.each do |artifact|
# convert generic string replacements into actual ones
artifact = cask.loader.from_h_gsubs(artifact, appdir)
key = artifact.keys.first

View File

@ -26,7 +26,7 @@ require "cask/dsl/version"
require "cask/url"
require "cask/utils"
require "extend/on_system"
require "on_system"
module Cask
# Class representing the domain-specific language used for casks.

View File

@ -2,7 +2,7 @@
# frozen_string_literal: true
require "cask/utils"
require "extend/on_system"
require "on_system"
module Cask
class DSL

View File

@ -149,7 +149,7 @@ module Cask
oh1 "Installing Cask #{Formatter.identifier(@cask)}"
# GitHub Actions globally disables Gatekeeper.
opoo "macOS's Gatekeeper has been disabled for this Cask" if !quarantine? && !GitHub::Actions.env_set?
opoo_outside_github_actions "macOS's Gatekeeper has been disabled for this Cask" unless quarantine?
stage
@cask.config = @cask.default_config.merge(old_config)

View File

@ -14,40 +14,54 @@ class Caveats
sig { params(formula: Formula).void }
def initialize(formula)
@formula = formula
@caveats = T.let(nil, T.nilable(String))
@completions_and_elisp = T.let(nil, T.nilable(T::Array[String]))
end
sig { returns(String) }
def caveats
caveats = []
build = formula.build
begin
formula.build = Tab.for_formula(formula)
string = formula.caveats.to_s
caveats << "#{string.chomp}\n" unless string.empty?
ensure
formula.build = build
@caveats ||= begin
caveats = []
build = formula.build
begin
formula.build = Tab.for_formula(formula)
string = formula.caveats.to_s
caveats << "#{string.chomp}\n" unless string.empty?
ensure
formula.build = build
end
caveats << keg_only_text
caveats << service_caveats
caveats.compact.join("\n")
end
caveats << keg_only_text
valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? &&
(shell_sym = current_shell.to_sym) &&
valid_shells.include?(shell_sym)
[shell_sym]
else
valid_shells
end
shells.each do |shell|
caveats << function_completion_caveats(shell)
end
caveats << service_caveats
caveats << elisp_caveats
caveats.compact.join("\n")
end
delegate [:empty?, :to_s] => :caveats
sig { returns(T::Boolean) }
def empty?
caveats.blank? && completions_and_elisp.blank?
end
delegate [:to_s] => :caveats
sig { returns(T::Array[String]) }
def completions_and_elisp
@completions_and_elisp ||= begin
valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? &&
(shell_sym = current_shell.to_sym) &&
valid_shells.include?(shell_sym)
[shell_sym]
else
valid_shells
end
completions_and_elisp = shells.map do |shell|
function_completion_caveats(shell)
end
completions_and_elisp << elisp_caveats
completions_and_elisp.compact
end
end
sig { params(skip_reason: T::Boolean).returns(T.nilable(String)) }
def keg_only_text(skip_reason: false)

View File

@ -208,11 +208,15 @@ module Homebrew
return if global_switch
description = option_description(description, *names, hidden:)
process_option(*names, description, type: :switch, hidden:) unless disable
env, counterpart = env
if env && @non_global_processed_options.any?
affix = counterpart ? " and `#{counterpart}` is passed." : "."
description += " Enabled by default if `$HOMEBREW_#{env.upcase}` is set#{affix}"
end
if replacement || disable
description += " (#{disable ? "disabled" : "deprecated"}#{"; replaced by #{replacement}" if replacement})"
end
process_option(*names, description, type: :switch, hidden:) unless disable
@parser.public_send(method, *names, *wrap_option_desc(description)) do |value|
# This odeprecated should stick around indefinitely.

View File

@ -73,11 +73,10 @@ module Homebrew
description: "`install` prints output from commands as they are run. " \
"`check` lists all missing dependencies."
switch "--no-upgrade",
env: :bundle_no_upgrade,
description: "`install` does not run `brew upgrade` on outdated dependencies. " \
"`check` does not check for outdated dependencies. " \
"Note they may still be upgraded by `brew install` if needed. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set."
"Note they may still be upgraded by `brew install` if needed.",
env: :bundle_no_upgrade
switch "--upgrade",
description: "`install` runs `brew upgrade` on outdated dependencies, " \
"even if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set."
@ -87,18 +86,15 @@ module Homebrew
switch "--install",
description: "Run `install` before continuing to other operations e.g. `exec`."
switch "--services",
env: :bundle_services,
description: "Temporarily start services while running the `exec` or `sh` command. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_SERVICES` is set."
description: "Temporarily start services while running the `exec` or `sh` command.",
env: :bundle_services
switch "-f", "--force",
description: "`install` runs with `--force`/`--overwrite`. " \
"`dump` overwrites an existing `Brewfile`. " \
"`cleanup` actually performs its cleanup operations."
switch "--cleanup",
env: :bundle_install_cleanup,
description: "`install` performs cleanup operation, same as running `cleanup --force`. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_INSTALL_CLEANUP` is set and " \
"`--global` is passed."
description: "`install` performs cleanup operation, same as running `cleanup --force`.",
env: [:bundle_install_cleanup, "--global"]
switch "--all",
description: "`list` all dependencies."
switch "--formula", "--brews",
@ -114,14 +110,12 @@ module Homebrew
switch "--vscode",
description: "`list`, `dump` or `cleanup` VSCode (and forks/variants) extensions."
switch "--no-vscode",
env: :bundle_dump_no_vscode,
description: "`dump` without VSCode (and forks/variants) extensions. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_NO_VSCODE` is set."
description: "`dump` without VSCode (and forks/variants) extensions.",
env: :bundle_dump_no_vscode
switch "--describe",
env: :bundle_dump_describe,
description: "`dump` adds a description comment above each line, unless the " \
"dependency does not have a description. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_DESCRIBE` is set."
"dependency does not have a description.",
env: :bundle_dump_describe
switch "--no-restart",
description: "`dump` does not add `restart_service` to formula lines."
switch "--zap",
@ -282,17 +276,7 @@ module Homebrew
_subcommand, *named_args = args.named
named_args
when "sh"
preferred_path = Utils::Shell.preferred_path(default: "/bin/bash")
notice = unless Homebrew::EnvConfig.no_env_hints?
<<~EOS
Your shell has been configured to use a build environment from your `Brewfile`.
This should help you build stuff.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
When done, type `exit`.
EOS
end
ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil
[Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)]
["sh"]
when "env"
["env"]
end

View File

@ -18,8 +18,7 @@ module Homebrew
If any version of each formula argument is installed and no other options
are passed, this command displays their actual runtime dependencies (similar
to `brew linkage`), which may differ from the current versions' stated
dependencies if the installed versions are outdated.
to `brew linkage`), which may differ from a formula's declared dependencies.
*Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`.
EOS
@ -97,21 +96,45 @@ module Homebrew
Formulary.enable_factory_cache!
SimulateSystem.with(os:, arch:) do
recursive = !args.direct?
installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?)
@use_runtime_dependencies = true
@use_runtime_dependencies = installed && recursive &&
!args.tree? &&
!args.graph? &&
!args.HEAD? &&
!args.include_implicit? &&
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!args.skip_recommended? &&
!args.missing? &&
args.os.nil? &&
args.arch.nil?
installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?)
unless installed
not_using_runtime_dependencies_reason = if args.installed?
"not all the named formulae were installed"
else
"`--installed` was not passed"
end
@use_runtime_dependencies = false
end
%w[direct tree graph HEAD skip_recommended missing
include_implicit include_build include_test include_optional].each do |arg|
next unless args.public_send("#{arg}?")
not_using_runtime_dependencies_reason = "--#{arg.tr("_", "-")} was passed"
@use_runtime_dependencies = false
end
%w[os arch].each do |arg|
next if args.public_send(arg).nil?
not_using_runtime_dependencies_reason = "--#{arg.tr("_", "-")} was passed"
@use_runtime_dependencies = false
end
if !@use_runtime_dependencies && !Homebrew::EnvConfig.no_env_hints?
opoo <<~EOS
`brew deps` is not the actual runtime dependencies because #{not_using_runtime_dependencies_reason}!
This means dependencies may differ from a formula's declared dependencies.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
EOS
end
recursive = !args.direct?
if args.tree? || args.graph?
dependents = if args.named.present?

View File

@ -33,8 +33,8 @@ module Homebrew
description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each package at the end of the run."
description: "Print install times for each package at the end of the run.",
env: :display_install_times
switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \
@ -44,9 +44,9 @@ module Homebrew
switch "-n", "--dry-run",
description: "Show what would be installed, but do not actually install anything."
switch "--ask",
env: :ask,
description: "Ask for confirmation before downloading and installing formulae. " \
"Print bottles and dependencies download size and install size."
"Print download and install sizes of bottles and dependencies.",
env: :ask
[
[:switch, "--formula", "--formulae", {
description: "Treat all named arguments as formulae.",

View File

@ -0,0 +1,23 @@
# typed: strong
# frozen_string_literal: true
require "abstract_command"
require "shell_command"
module Homebrew
module Cmd
class McpServerCmd < AbstractCommand
# This is a shell command as MCP servers need a faster startup time
# than a normal Homebrew Ruby command allows.
include ShellCommand
cmd_args do
description <<~EOS
Starts the Homebrew MCP (Model Context Protocol) server.
EOS
switch "-d", "--debug", description: "Enable debug logging to stderr."
switch "--ping", description: "Start the server, act as if receiving a ping and then exit.", hidden: true
end
end
end
end

View File

@ -0,0 +1,14 @@
# Documentation defined in Library/Homebrew/cmd/mcp-server.rb
# This is a shell command as MCP servers need a faster startup time
# than a normal Homebrew Ruby command allows.
# HOMEBREW_LIBRARY is set by brew.sh
# HOMEBREW_BREW_FILE is set by extend/ENV/super.rb
# shellcheck disable=SC2154
homebrew-mcp-server() {
source "${HOMEBREW_LIBRARY}/Homebrew/utils/ruby.sh"
setup-ruby-path
export HOMEBREW_VERSION
"${HOMEBREW_RUBY_PATH}" "-r${HOMEBREW_LIBRARY}/Homebrew/mcp_server.rb" -e "Homebrew::McpServer.new.run" "$@"
}

View File

@ -32,12 +32,10 @@ module Homebrew
"formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
"updates when a new stable or development version has been released."
switch "-g", "--greedy",
env: :upgrade_greedy,
description: "Also include outdated casks with `auto_updates true` or `version :latest`."
description: "Also include outdated casks with `auto_updates true` or `version :latest`.",
env: :upgrade_greedy
switch "--greedy-latest",
description: "Also include outdated casks including those with `version :latest`."
switch "--greedy-auto-updates",
description: "Also include outdated casks including those with `auto_updates true`."

View File

@ -32,8 +32,8 @@ module Homebrew
description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each package at the end of the run."
description: "Print install times for each package at the end of the run.",
env: :display_install_times
switch "-f", "--force",
description: "Install without checking for previously installed keg-only or " \
"non-migrated versions."
@ -41,7 +41,7 @@ module Homebrew
description: "Print the verification and post-install steps."
switch "--ask",
description: "Ask for confirmation before downloading and upgrading formulae. " \
"Print bottles and dependencies download size, install and net install size.",
"Print download, install and net install sizes of bottles and dependencies.",
env: :ask
[
[:switch, "--formula", "--formulae", { description: "Treat all named arguments as formulae." }],

View File

@ -13,18 +13,20 @@ module Homebrew
description <<~EOS
Valid shells: bash|csh|fish|pwsh|sh|tcsh|zsh
Print export statements. When run in a shell, this installation of Homebrew will be added to your `PATH`, `MANPATH`, and `INFOPATH`.
Print export statements. When run in a shell, this installation of Homebrew will be added to your
`$PATH`, `$MANPATH`, and `$INFOPATH`.
The variables `$HOMEBREW_PREFIX`, `$HOMEBREW_CELLAR` and `$HOMEBREW_REPOSITORY` are also exported to avoid
querying them multiple times.
To help guarantee idempotence, this command produces no output when Homebrew's `bin` and `sbin` directories
are first and second respectively in your `PATH`. Consider adding evaluation of this command's output to
are first and second respectively in your `$PATH`. Consider adding evaluation of this command's output to
your dotfiles (e.g. `~/.bash_profile` or ~/.zprofile` on macOS and ~/.bashrc` or ~/.zshrc` on Linux) with:
`eval "$(brew shellenv)"`
The shell can be specified explicitly with a supported shell name parameter. Unknown shells will output
POSIX exports.
EOS
named_args :shell
end
end

View File

@ -725,31 +725,53 @@ EOS
local tmp_failure_file="${DIR}/.git/TMP_FETCH_FAILURES"
rm -f "${tmp_failure_file}"
if [[ -n "${HOMEBREW_UPDATE_AUTO}" ]]
if ! git fetch --tags --force "${QUIET_ARGS[@]}" origin \
"refs/heads/${UPSTREAM_BRANCH_DIR}:refs/remotes/origin/${UPSTREAM_BRANCH_DIR}" 2>>"${tmp_failure_file}"
then
git fetch --tags --force "${QUIET_ARGS[@]}" origin \
"refs/heads/${UPSTREAM_BRANCH_DIR}:refs/remotes/origin/${UPSTREAM_BRANCH_DIR}" 2>/dev/null
else
# Capture stderr to tmp_failure_file
if ! git fetch --tags --force "${QUIET_ARGS[@]}" origin \
"refs/heads/${UPSTREAM_BRANCH_DIR}:refs/remotes/origin/${UPSTREAM_BRANCH_DIR}" 2>>"${tmp_failure_file}"
if [[ -f "${tmp_failure_file}" ]]
then
# Reprint fetch errors to stderr
[[ -f "${tmp_failure_file}" ]] && cat "${tmp_failure_file}" 1>&2
if [[ "${UPSTREAM_SHA_HTTP_CODE}" == "404" ]]
local git_errors
git_errors="$(cat "${tmp_failure_file}")"
if [[ "${git_errors}" == "fatal: couldn't find remote ref refs/heads/master" ]]
then
TAP="${DIR#"${HOMEBREW_LIBRARY}"/Taps/}"
echo "${TAP} does not exist! Run \`brew untap ${TAP}\` to remove it." >>"${update_failed_file}"
else
echo "Fetching ${DIR} failed!" >>"${update_failed_file}"
if [[ -f "${tmp_failure_file}" ]] &&
[[ "$(cat "${tmp_failure_file}")" == "fatal: couldn't find remote ref refs/heads/${UPSTREAM_BRANCH_DIR}" ]]
# Attempt migration from master to main branch.
if git fetch --tags --force "${QUIET_ARGS[@]}" origin \
"refs/heads/main:refs/remotes/origin/main" 2>>"${tmp_failure_file}"
then
echo "${DIR}" >>"${missing_remote_ref_dirs_file}"
rm -f "${DIR}/.git/refs/remotes/origin/HEAD" "${DIR}/.git/refs/remotes/origin/master"
UPSTREAM_BRANCH_DIR="$(upstream_branch)"
declare UPSTREAM_BRANCH"${TAP_VAR}"="${UPSTREAM_BRANCH_DIR}"
git branch -m master main "${QUIET_ARGS[@]}"
git branch -u origin/main main "${QUIET_ARGS[@]}"
rm -f "${tmp_failure_file}"
exit
fi
fi
rm -f "${tmp_failure_file}"
fi
# Don't output errors if HOMEBREW_UPDATE_AUTO is set.
if [[ -n "${HOMEBREW_UPDATE_AUTO}" ]]
then
exit
fi
# Reprint fetch errors to stderr
[[ -n "${git_errors}" ]] && echo "${git_errors}" 1>&2
if [[ "${UPSTREAM_SHA_HTTP_CODE}" == "404" ]]
then
TAP="${DIR#"${HOMEBREW_LIBRARY}"/Taps/}"
echo "${TAP} does not exist! Run \`brew untap ${TAP}\` to remove it." >>"${update_failed_file}"
else
echo "Fetching ${DIR} failed!" >>"${update_failed_file}"
if [[ -f "${tmp_failure_file}" ]] &&
[[ "$(cat "${tmp_failure_file}")" == "fatal: couldn't find remote ref refs/heads/${UPSTREAM_BRANCH_DIR}" ]]
then
echo "${DIR}" >>"${missing_remote_ref_dirs_file}"
fi
fi
fi

View File

@ -29,8 +29,8 @@ module Homebrew
description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each package at the end of the run."
description: "Print install times for each package at the end of the run.",
env: :display_install_times
switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \
@ -41,7 +41,7 @@ module Homebrew
description: "Show what would be upgraded, but do not actually upgrade anything."
switch "--ask",
description: "Ask for confirmation before downloading and upgrading formulae. " \
"Print bottles and dependencies download size, install and net install size.",
"Print download, install and net install sizes of bottles and dependencies.",
env: :ask
[
[:switch, "--formula", "--formulae", {

View File

@ -101,6 +101,7 @@ fetch() {
local first_try=1
local vendor_locations
local temporary_path
local curl_exit_code=0
curl_args=()
@ -149,19 +150,27 @@ fetch() {
# HOMEBREW_CURL is set by brew.sh (and isn't misspelt here)
# shellcheck disable=SC2153
"${HOMEBREW_CURL}" "${curl_args[@]}" -C - "${url}" -o "${temporary_path}"
if [[ $? -eq 33 ]]
curl_exit_code="$?"
if [[ "${curl_exit_code}" -eq 33 ]]
then
[[ -n "${HOMEBREW_QUIET}" ]] || echo "Trying a full download" >&2
rm -f "${temporary_path}"
"${HOMEBREW_CURL}" "${curl_args[@]}" "${url}" -o "${temporary_path}"
curl_exit_code="$?"
fi
else
"${HOMEBREW_CURL}" "${curl_args[@]}" "${url}" -o "${temporary_path}"
curl_exit_code="$?"
fi
[[ -f "${temporary_path}" ]] && break
done
if [[ "${curl_exit_code}" -ne 0 ]]
then
rm -f "${temporary_path}"
fi
if [[ ! -f "${temporary_path}" ]]
then
vendor_locations="$(printf " - %s\n" "${VENDOR_URLs[@]}")"

View File

@ -5,7 +5,7 @@ require "dependency"
require "dependencies"
require "requirement"
require "requirements"
require "extend/cachable"
require "cachable"
# A dependency is a formula that another formula needs to install.
# A requirement is something other than a formula that another formula

View File

@ -43,7 +43,7 @@ module Homebrew
description: "Only check formulae and casks that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to audit them. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
"Implied if `$HOMEBREW_EVAL_ALL` is set."
switch "--new",
description: "Run various additional style checks to determine if a new formula or cask is eligible " \
"for Homebrew. This should be used when creating new formulae or casks and implies " \

View File

@ -227,7 +227,7 @@ module Homebrew
private
sig {
params(string: String, keg: Keg, ignores: T::Array[String],
params(string: String, keg: Keg, ignores: T::Array[Regexp],
formula_and_runtime_deps_names: T.nilable(T::Array[String])).returns(T::Boolean)
}
def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil)
@ -373,35 +373,17 @@ module Homebrew
[gnu_tar(gnu_tar_formula), reproducible_gnutar_args(mtime)].freeze
end
sig { params(formula: T.untyped).returns(T::Array[T.untyped]) }
sig { params(formula: Formula).returns(T::Array[Regexp]) }
def formula_ignores(formula)
ignores = []
cellar_regex = Regexp.escape(HOMEBREW_CELLAR)
prefix_regex = Regexp.escape(HOMEBREW_PREFIX)
# Ignore matches to go keg, because all go binaries are statically linked.
any_go_deps = formula.deps.any? do |dep|
Version.formula_optionally_versioned_regex(:go).match?(dep.name)
end
if any_go_deps
go_regex = Version.formula_optionally_versioned_regex(:go, full: false)
ignores << %r{#{cellar_regex}/#{go_regex}/[\d.]+/libexec}
end
return [] unless any_go_deps
# TODO: Refactor and move to extend/os
# rubocop:disable Homebrew/MoveToExtendOS
ignores << case formula.name
# On Linux, GCC installation can be moved so long as the whole directory tree is moved together:
# https://gcc-help.gcc.gnu.narkive.com/GnwuCA7l/moving-gcc-from-the-installation-path-is-it-allowed.
when Version.formula_optionally_versioned_regex(:gcc)
Regexp.union(%r{#{cellar_regex}/gcc}, %r{#{prefix_regex}/opt/gcc}) if OS.linux?
# binutils is relocatable for the same reason: https://github.com/Homebrew/brew/pull/11899#issuecomment-906804451.
when Version.formula_optionally_versioned_regex(:binutils)
%r{#{cellar_regex}/binutils} if OS.linux?
end
# rubocop:enable Homebrew/MoveToExtendOS
ignores.compact
cellar_regex = Regexp.escape(HOMEBREW_CELLAR)
go_regex = Version.formula_optionally_versioned_regex(:go, full: false)
Array(%r{#{cellar_regex}/#{go_regex}/[\d.]+/libexec})
end
sig { params(formula: Formula).void }

View File

@ -118,7 +118,7 @@ module Homebrew
else
raise UsageError,
"`brew bump` without named arguments needs `--installed` or `--eval-all` passed or " \
"`HOMEBREW_EVAL_ALL` set!"
"`$HOMEBREW_EVAL_ALL` set!"
end
if args.start_with

View File

@ -88,16 +88,21 @@ module Homebrew
contributions <<
"#{Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)} (total)"
puts [
contributions_string = [
"#{username} contributed",
*contributions.to_sentence,
"#{time_period(from:, to: args.to)}.",
].join(" ")
if args.csv?
$stderr.puts contributions_string
else
puts contributions_string
end
end
return unless args.csv?
puts
$stderr.puts
puts generate_csv(grand_totals)
end

View File

@ -21,7 +21,8 @@ module Homebrew
"dependents.",
env: :eval_all
switch "--dependents",
description: "Determine runners for testing dependents. Requires `--eval-all` or `HOMEBREW_EVAL_ALL`.",
description: "Determine runners for testing dependents. " \
"Requires `--eval-all` or `$HOMEBREW_EVAL_ALL` to be set.",
depends_on: "--eval-all"
named_args max: 2

View File

@ -9,10 +9,9 @@ module Homebrew
class Edit < AbstractCommand
cmd_args do
description <<~EOS
Open a <formula>, <cask> or <tap> in the editor set by `EDITOR` or `HOMEBREW_EDITOR`,
Open a <formula>, <cask> or <tap> in the editor set by `$EDITOR` or `$HOMEBREW_EDITOR`,
or open the Homebrew repository for editing if no argument is provided.
EOS
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",

View File

@ -77,7 +77,7 @@ module Homebrew
analytics_data_dir = root_dir/"_data/analytics"
analytics_api_dir = root_dir/"api/analytics"
threads = []
analytics_output_queue = Queue.new
CATEGORIES.each do |category|
formula_analytics_args = []
@ -124,15 +124,40 @@ module Homebrew
DAYS.each do |days|
next if days != "30" && category_name == "build-error" && !data_source.nil?
threads << Thread.new do
args = %W[--days-ago=#{days}]
(analytics_data_path/"#{days}d.json").write run_formula_analytics(*formula_analytics_args, *args)
(analytics_api_path/"#{days}d.json").write analytics_json_template(category_name, data_source:)
end
analytics_output_queue << {
formula_analytics_args: formula_analytics_args.dup,
days: days,
analytics_data_path: analytics_data_path,
analytics_api_path: analytics_api_path,
category_name: category_name,
data_source: data_source,
}
end
end
threads.each(&:join)
workers = []
4.times do
workers << Thread.new do
until analytics_output_queue.empty?
analytics_output_type = begin
analytics_output_queue.pop(true)
rescue ThreadError
break
end
days = analytics_output_type[:days]
args = ["--days-ago=#{days}"]
(analytics_output_type[:analytics_data_path]/"#{days}d.json").write \
run_formula_analytics(*analytics_output_type[:formula_analytics_args], *args)
data_source = analytics_output_type[:data_source]
(analytics_output_type[:analytics_api_path]/"#{days}d.json").write \
analytics_json_template(analytics_output_type[:category_name], data_source:)
end
end
end
workers.each(&:join)
end
end
end

View File

@ -33,7 +33,7 @@ module Homebrew
raise TapUnavailableError, tap.name unless tap.installed?
unless args.dry_run?
directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal/v3"].freeze
directories = ["_data/cask", "api/cask", "api/cask-source", "cask", "api/internal"].freeze
FileUtils.rm_rf directories
FileUtils.mkdir_p directories
end
@ -44,12 +44,14 @@ module Homebrew
Cask::Cask.generating_hash!
all_casks = {}
latest_macos = MacOSVersion.new((HOMEBREW_MACOS_NEWEST_UNSUPPORTED.to_i - 1).to_s).to_sym
Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do
tap.cask_files.each do |path|
cask = Cask::CaskLoader.load(path)
name = cask.token
json = JSON.pretty_generate(cask.to_hash_with_variations)
all_casks[name] = cask.to_hash_with_variations
json = JSON.pretty_generate(all_casks[name])
cask_source = path.read
html_template_name = html_template(name)
@ -67,6 +69,14 @@ module Homebrew
canonical_json = JSON.pretty_generate(tap.cask_renames)
File.write("_data/cask_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
variation_casks = all_casks.map do |_, cask|
Homebrew::API.merge_variations(cask, bottle_tag:)
end
File.write("api/internal/cask.#{bottle_tag}.json", JSON.generate(variation_casks)) unless args.dry_run?
end
end
end

View File

@ -32,7 +32,7 @@ module Homebrew
raise TapUnavailableError, tap.name unless tap.installed?
unless args.dry_run?
directories = ["_data/formula", "api/formula", "formula", "api/internal/v3"]
directories = ["_data/formula", "api/formula", "formula", "api/internal"]
FileUtils.rm_rf directories + ["_data/formula_canonical.json"]
FileUtils.mkdir_p directories
end
@ -44,12 +44,14 @@ module Homebrew
Formulary.enable_factory_cache!
Formula.generating_hash!
all_formulae = {}
latest_macos = MacOSVersion.new((HOMEBREW_MACOS_NEWEST_UNSUPPORTED.to_i - 1).to_s).to_sym
Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do
tap.formula_names.each do |name|
formula = Formulary.factory(name)
name = formula.name
json = JSON.pretty_generate(formula.to_hash_with_variations)
all_formulae[name] = formula.to_hash_with_variations
json = JSON.pretty_generate(all_formulae[name])
html_template_name = html_template(name)
unless args.dry_run?
@ -65,6 +67,30 @@ module Homebrew
canonical_json = JSON.pretty_generate(tap.formula_renames.merge(tap.alias_table))
File.write("_data/formula_canonical.json", "#{canonical_json}\n") unless args.dry_run?
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
variation_formulae = all_formulae.to_h do |name, formula|
formula = Homebrew::API.merge_variations(formula, bottle_tag:)
version = Version.new(formula.dig("versions", "stable"))
pkg_version = PkgVersion.new(version, formula["revision"])
rebuild = formula.dig("bottle", "stable", "rebuild") || 0
bottle_collector = Utils::Bottles::Collector.new
formula.dig("bottle", "stable", "files")&.each do |tag, data|
tag = Utils::Bottles::Tag.from_symbol(tag)
bottle_collector.add tag, checksum: Checksum.new(data["sha256"]), cellar: :any
end
sha256 = bottle_collector.specification_for(bottle_tag)&.checksum&.to_s
[name, [pkg_version.to_s, rebuild, sha256]]
end
unless args.dry_run?
File.write("api/internal/formula.#{bottle_tag}.json", JSON.generate(variation_formulae))
end
end
end
end

View File

@ -44,8 +44,8 @@ module Homebrew
switch "--examples",
description: "Show several examples."
switch "--pry",
env: :pry,
description: "Use Pry instead of IRB. Implied if `HOMEBREW_PRY` is set."
description: "Use Pry instead of IRB.",
env: :pry
end
# work around IRB modifying ARGV.

View File

@ -23,7 +23,7 @@ module Homebrew
description: "For every library that a keg references, print its dylib path followed by the " \
"binaries that link to it."
switch "--cached",
description: "Print the cached linkage values stored in `HOMEBREW_CACHE`, set by a previous " \
description: "Print the cached linkage values stored in `$HOMEBREW_CACHE`, set by a previous " \
"`brew linkage` run."
named_args :installed_formula

View File

@ -13,7 +13,7 @@ module Homebrew
description <<~EOS
Check for newer versions of formulae and/or casks from upstream.
If no formula or cask argument is passed, the list of formulae and
casks to check is taken from `HOMEBREW_LIVECHECK_WATCHLIST` or
casks to check is taken from `$HOMEBREW_LIVECHECK_WATCHLIST` or
`~/.homebrew/livecheck_watchlist.txt`.
EOS
switch "--full-name",

View File

@ -13,11 +13,11 @@ module Homebrew
Enter an interactive shell for Homebrew's build environment. Use years-battle-hardened
build logic to help your `./configure && make && make install`
and even your `gem install` succeed. Especially handy if you run Homebrew
in an Xcode-only configuration since it adds tools like `make` to your `PATH`
in an Xcode-only configuration since it adds tools like `make` to your `$PATH`
which build systems would not find otherwise.
EOS
flag "--env=",
description: "Use the standard `PATH` instead of superenv's when `std` is passed."
description: "Use the standard `$PATH` instead of superenv's when `std` is passed."
flag "-c=", "--cmd=",
description: "Execute commands in a non-interactive shell."

View File

@ -28,6 +28,8 @@ module Homebrew
description: "Only runs tests on files that were changed from the master branch."
switch "--fail-fast",
description: "Exit early on the first failing test."
switch "--no-parallel",
description: "Run tests serially."
flag "--only=",
description: "Run only `<test_script>_spec.rb`. Appending `:<line_number>` will start at a " \
"specific line."
@ -49,7 +51,7 @@ module Homebrew
HOMEBREW_LIBRARY_PATH.cd do
setup_environment!
parallel = true
parallel = !args.no_parallel?
only = args.only
files = if only
@ -120,29 +122,13 @@ module Homebrew
]
bundle_args << "--fail-fast" if args.fail_fast?
bundle_args << "--profile" << args.profile if args.profile
# TODO: Refactor and move to extend/os
# rubocop:disable Homebrew/MoveToExtendOS
unless OS.mac?
bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask"
files = files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$})
end
unless OS.linux?
bundle_args << "--tag" << "~needs_linux"
files = files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$})
end
# rubocop:enable Homebrew/MoveToExtendOS
bundle_args << "--tag" << "~needs_arm" unless Hardware::CPU.arm?
bundle_args << "--tag" << "~needs_intel" unless Hardware::CPU.intel?
bundle_args << "--tag" << "~needs_network" unless args.online?
unless ENV["CI"]
bundle_args << "--tag" << "~needs_ci" \
<< "--tag" << "~needs_svn"
end
bundle_args << "--tag" << "~needs_ci" unless ENV["CI"]
bundle_args = os_bundle_args(bundle_args)
files = os_files(files)
puts "Randomized with seed #{seed}"
@ -170,6 +156,41 @@ module Homebrew
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
# for generic tests, remove macOS or Linux specific tests
non_linux_bundle_args(non_macos_bundle_args(bundle_args))
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_macos_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_homebrew_core" if ENV["CI"]
bundle_args << "--tag" << "~needs_svn" unless args.online?
bundle_args << "--tag" << "~needs_macos" << "--tag" << "~cask"
end
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def non_linux_bundle_args(bundle_args)
bundle_args << "--tag" << "~needs_linux" << "--tag" << "~needs_systemd"
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
# for generic tests, remove macOS or Linux specific files
non_linux_files(non_macos_files(files))
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_macos_files(files)
files.grep_v(%r{^test/(os/mac|cask)(/.*|_spec\.rb)$})
end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def non_linux_files(files)
files.grep_v(%r{^test/os/linux(/.*|_spec\.rb)$})
end
sig { returns(T::Array[String]) }
def changed_test_files
changed_files = Utils.popen_read("git", "diff", "--name-only", "master")
@ -221,9 +242,6 @@ module Homebrew
ENV["HOMEBREW_SORBET_RUNTIME"] = "1"
ENV["HOMEBREW_NO_FORCE_BREW_WRAPPER"] = "1"
# TODO: remove this and fix tests when possible.
ENV["HOMEBREW_NO_INSTALL_FROM_API"] = "1"
ENV["USER"] ||= system_command!("id", args: ["-nu"]).stdout.chomp
# Avoid local configuration messing with tests, e.g. git being configured
@ -249,3 +267,5 @@ module Homebrew
end
end
end
require "extend/os/dev-cmd/tests"

View File

@ -23,7 +23,7 @@ module Homebrew
description: "Print the `homebrew/core` commits where bottles were lost in the last week."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to check them. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
"Implied if `$HOMEBREW_EVAL_ALL` is set."
conflicts "--dependents", "--total", "--lost"
@ -68,7 +68,7 @@ module Homebrew
all = args.eval_all?
if args.total?
if !all && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew unbottled --total` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
raise UsageError, "`brew unbottled --total` needs `--eval-all` passed or `$HOMEBREW_EVAL_ALL` set!"
end
all = true
@ -119,7 +119,7 @@ module Homebrew
elsif args.dependents?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError,
"`brew unbottled --dependents` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
"`brew unbottled --dependents` needs `--eval-all` passed or `$HOMEBREW_EVAL_ALL` set!"
end
formulae = all_formulae = Formula.all(eval_all: args.eval_all?)
@ -136,7 +136,7 @@ module Homebrew
if analytics.blank?
raise UsageError,
"default sort by analytics data requires " \
"`HOMEBREW_NO_GITHUB_API` and `HOMEBREW_NO_ANALYTICS` to be unset"
"`$HOMEBREW_NO_GITHUB_API` and `$HOMEBREW_NO_ANALYTICS` to be unset."
end
formulae = analytics["items"].filter_map do |i|

View File

@ -15,7 +15,7 @@ module Homebrew
If no options are passed, use `origin/master` as the start commit.
EOS
switch "--to-tag",
description: "Set `HOMEBREW_UPDATE_TO_TAG` to test updating between tags."
description: "Set `$HOMEBREW_UPDATE_TO_TAG` to test updating between tags."
switch "--keep-tmp",
description: "Retain the temporary directory containing the new repository clone."
flag "--commit=",

View File

@ -463,7 +463,7 @@ module Homebrew
default_text: "`~/.ssh/config`",
},
HOMEBREW_SUDO_THROUGH_SUDO_USER: {
description: "If set, Homebrew will use the `SUDO_USER` environment variable to define the user to " \
description: "If set, Homebrew will use the `$SUDO_USER` environment variable to define the user to " \
"`sudo`(8) through when running `sudo`(8).",
boolean: true,
},

View File

@ -7,16 +7,6 @@ require "extend/ENV/shared"
require "extend/ENV/std"
require "extend/ENV/super"
module Kernel
sig { params(env: T.nilable(String)).returns(T::Boolean) }
def superenv?(env)
return false if env == "std"
!Superenv.bin.nil?
end
private :superenv?
end
# <!-- vale off -->
# @!parse
# # `ENV` is not actually a class, but this makes YARD happy

View File

@ -5,6 +5,14 @@
# TODO: move these out of `Kernel`.
module Kernel
sig { params(env: T.nilable(String)).returns(T::Boolean) }
def superenv?(env)
return false if env == "std"
!Superenv.bin.nil?
end
private :superenv?
def require?(path)
return false if path.nil?
@ -82,6 +90,17 @@ module Kernel
end
end
# Print a warning message only if not running in GitHub Actions.
#
# @api public
sig { params(message: T.any(String, Exception)).void }
def opoo_outside_github_actions(message)
require "utils/github/actions"
return if GitHub::Actions.env_set?
opoo(message)
end
# Print an error message.
#
# @api public

View File

@ -0,0 +1,5 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/linux/dev-cmd/tests" if OS.linux?
require "extend/os/mac/dev-cmd/tests" if OS.mac?

View File

@ -9,6 +9,16 @@ module OS
def mas_installed?
false
end
# Setup pkg-config, if present, to help locate packages
# Only need this on Linux as Homebrew provides a shim on macOS
sig { void }
def prepend_pkgconf_path_if_needed!
pkgconf = Formulary.factory("pkgconf")
return unless pkgconf.any_version_installed?
ENV.prepend_path "PATH", pkgconf.opt_bin.to_s
end
end
end
end

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
module OS
module Linux
module DevCmd
module Tests
extend T::Helpers
requires_ancestor { Homebrew::DevCmd::Tests }
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
non_macos_bundle_args(bundle_args)
end
end
end
end
end
Homebrew::DevCmd::Tests.prepend(OS::Linux::DevCmd::Tests)

View File

@ -18,6 +18,26 @@ module OS
def gnu_tar(gnu_tar_formula)
"#{gnu_tar_formula.opt_bin}/gtar"
end
sig { params(formula: Formula).returns(T::Array[Regexp]) }
def formula_ignores(formula)
ignores = super
cellar_regex = Regexp.escape(HOMEBREW_CELLAR)
prefix_regex = Regexp.escape(HOMEBREW_PREFIX)
ignores << case formula.name
# On Linux, GCC installation can be moved so long as the whole directory tree is moved together:
# https://gcc-help.gcc.gnu.narkive.com/GnwuCA7l/moving-gcc-from-the-installation-path-is-it-allowed.
when Version.formula_optionally_versioned_regex(:gcc)
Regexp.union(%r{#{cellar_regex}/gcc}, %r{#{prefix_regex}/opt/gcc}) if OS.linux?
# binutils is relocatable for the same reason: https://github.com/Homebrew/brew/pull/11899#issuecomment-906804451.
when Version.formula_optionally_versioned_regex(:binutils)
%r{#{cellar_regex}/binutils} if OS.linux?
end
ignores.compact
end
end
end
end

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
module OS
module Mac
module DevCmd
module Tests
extend T::Helpers
requires_ancestor { Homebrew::DevCmd::Tests }
private
sig { params(bundle_args: T::Array[String]).returns(T::Array[String]) }
def os_bundle_args(bundle_args)
non_linux_bundle_args(bundle_args)
end
end
end
end
end
Homebrew::DevCmd::Tests.prepend(OS::Mac::DevCmd::Tests)

View File

@ -425,8 +425,8 @@ module OS
end
def check_deprecated_caskroom_taps
tapped_caskroom_taps = Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" }
.map(&:name)
tapped_caskroom_taps = ::Tap.select { |t| t.user == "caskroom" || t.name == "phinze/cask" }
.map(&:name)
return if tapped_caskroom_taps.empty?
<<~EOS

View File

@ -8,7 +8,7 @@ module OS
requires_ancestor { Kernel }
sig { params(tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
sig { params(tap: ::Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type)
return true if os_name == :linux

View File

@ -0,0 +1,17 @@
# typed: strict
# frozen_string_literal: true
module OS
module Mac
module Tap
module ClassMethods
sig { returns(T::Array[::Tap]) }
def core_taps
[CoreTap.instance, CoreCaskTap.instance].freeze
end
end
end
end
end
Tap.singleton_class.prepend(OS::Mac::Tap::ClassMethods)

View File

@ -47,7 +47,7 @@ module Utils
return if tag_version.blank?
tags.find do |candidate|
next if candidate.arch != tag.arch
next if candidate.standardized_arch != tag.standardized_arch
candidate.to_macos_version <= tag_version
rescue MacOSVersion::Error

View File

@ -0,0 +1,4 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/tap" if OS.mac?

View File

@ -1,79 +1,9 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
module DiskUsageExtension
extend T::Helpers
requires_ancestor { Pathname }
sig { returns(Integer) }
def disk_usage
return @disk_usage if defined?(@disk_usage)
compute_disk_usage
@disk_usage
end
sig { returns(Integer) }
def file_count
return @file_count if defined?(@file_count)
compute_disk_usage
@file_count
end
sig { returns(String) }
def abv
out = +""
compute_disk_usage
out << "#{number_readable(@file_count)} files, " if @file_count > 1
out << disk_usage_readable(@disk_usage).to_s
out.freeze
end
private
sig { void }
def compute_disk_usage
if symlink? && !exist?
@file_count = 1
@disk_usage = 0
return
end
path = if symlink?
resolved_path
else
self
end
if path.directory?
scanned_files = Set.new
@file_count = 0
@disk_usage = 0
path.find do |f|
if f.directory?
@disk_usage += f.lstat.size
else
@file_count += 1 if f.basename.to_s != ".DS_Store"
# use Pathname#lstat instead of Pathname#stat to get info of symlink itself.
stat = f.lstat
file_id = [stat.dev, stat.ino]
# count hardlinks only once.
unless scanned_files.include?(file_id)
@disk_usage += stat.size
scanned_files.add(file_id)
end
end
end
else
@file_count = 1
@disk_usage = path.lstat.size
end
end
end
require "system_command"
require "extend/pathname/disk_usage_extension"
require "extend/pathname/observer_pathname_extension"
# Homebrew extends Ruby's `Pathname` to make our code more readable.
# @see https://ruby-doc.org/stdlib-2.6.3/libdoc/pathname/rdoc/Pathname.html Ruby's Pathname API
@ -524,92 +454,3 @@ class Pathname
end
end
require "extend/os/pathname"
require "context"
module ObserverPathnameExtension
extend T::Helpers
requires_ancestor { Pathname }
class << self
include Context
sig { returns(Integer) }
attr_accessor :n, :d
sig { void }
def reset_counts!
@n = @d = 0
@put_verbose_trimmed_warning = false
end
sig { returns(Integer) }
def total
n + d
end
sig { returns([Integer, Integer]) }
def counts
[n, d]
end
MAXIMUM_VERBOSE_OUTPUT = 100
private_constant :MAXIMUM_VERBOSE_OUTPUT
sig { returns(T::Boolean) }
def verbose?
return super unless ENV["CI"]
return false unless super
if total < MAXIMUM_VERBOSE_OUTPUT
true
else
unless @put_verbose_trimmed_warning
puts "Only the first #{MAXIMUM_VERBOSE_OUTPUT} operations were output."
@put_verbose_trimmed_warning = true
end
false
end
end
end
sig { void }
def unlink
super
puts "rm #{self}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.n += 1
end
sig { void }
def mkpath
super
puts "mkdir -p #{self}" if ObserverPathnameExtension.verbose?
end
sig { void }
def rmdir
super
puts "rmdir #{self}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.d += 1
end
sig { params(src: Pathname).void }
def make_relative_symlink(src)
super
puts "ln -s #{src.relative_path_from(dirname)} #{basename}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.n += 1
end
sig { void }
def install_info
super
puts "info #{self}" if ObserverPathnameExtension.verbose?
end
sig { void }
def uninstall_info
super
puts "uninfo #{self}" if ObserverPathnameExtension.verbose?
end
end

View File

@ -0,0 +1,74 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
module DiskUsageExtension
extend T::Helpers
requires_ancestor { Pathname }
sig { returns(Integer) }
def disk_usage
return @disk_usage if defined?(@disk_usage)
compute_disk_usage
@disk_usage
end
sig { returns(Integer) }
def file_count
return @file_count if defined?(@file_count)
compute_disk_usage
@file_count
end
sig { returns(String) }
def abv
out = +""
compute_disk_usage
out << "#{number_readable(@file_count)} files, " if @file_count > 1
out << disk_usage_readable(@disk_usage).to_s
out.freeze
end
private
sig { void }
def compute_disk_usage
if symlink? && !exist?
@file_count = 1
@disk_usage = 0
return
end
path = if symlink?
resolved_path
else
self
end
if path.directory?
scanned_files = Set.new
@file_count = 0
@disk_usage = 0
path.find do |f|
if f.directory?
@disk_usage += f.lstat.size
else
@file_count += 1 if f.basename.to_s != ".DS_Store"
# use Pathname#lstat instead of Pathname#stat to get info of symlink itself.
stat = f.lstat
file_id = [stat.dev, stat.ino]
# count hardlinks only once.
unless scanned_files.include?(file_id)
@disk_usage += stat.size
scanned_files.add(file_id)
end
end
end
else
@file_count = 1
@disk_usage = path.lstat.size
end
end
end

View File

@ -0,0 +1,91 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "context"
module ObserverPathnameExtension
extend T::Helpers
requires_ancestor { Pathname }
class << self
include Context
sig { returns(Integer) }
attr_accessor :n, :d
sig { void }
def reset_counts!
@n = @d = 0
@put_verbose_trimmed_warning = false
end
sig { returns(Integer) }
def total
n + d
end
sig { returns([Integer, Integer]) }
def counts
[n, d]
end
MAXIMUM_VERBOSE_OUTPUT = 100
private_constant :MAXIMUM_VERBOSE_OUTPUT
sig { returns(T::Boolean) }
def verbose?
return super unless ENV["CI"]
return false unless super
if total < MAXIMUM_VERBOSE_OUTPUT
true
else
unless @put_verbose_trimmed_warning
puts "Only the first #{MAXIMUM_VERBOSE_OUTPUT} operations were output."
@put_verbose_trimmed_warning = true
end
false
end
end
end
sig { void }
def unlink
super
puts "rm #{self}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.n += 1
end
sig { void }
def mkpath
super
puts "mkdir -p #{self}" if ObserverPathnameExtension.verbose?
end
sig { void }
def rmdir
super
puts "rmdir #{self}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.d += 1
end
sig { params(src: Pathname).void }
def make_relative_symlink(src)
super
puts "ln -s #{src.relative_path_from(dirname)} #{basename}" if ObserverPathnameExtension.verbose?
ObserverPathnameExtension.n += 1
end
sig { void }
def install_info
super
puts "info #{self}" if ObserverPathnameExtension.verbose?
end
sig { void }
def uninstall_info
super
puts "uninfo #{self}" if ObserverPathnameExtension.verbose?
end
end

View File

@ -5,5 +5,9 @@ require "time"
class Time
# Backwards compatibility for formulae that used this ActiveSupport extension
alias rfc3339 xmlschema
sig { returns(String) }
def rfc3339
odeprecated "Time#rfc3339", "Time#xmlschema"
xmlschema
end
end

View File

@ -38,9 +38,9 @@ require "tab"
require "mktemp"
require "find"
require "utils/spdx"
require "extend/on_system"
require "on_system"
require "api"
require "extend/api_hashable"
require "api_hashable"
# A formula provides instructions and metadata for Homebrew to install a piece
# of software. Every Homebrew formula is a {Formula}.
@ -2593,11 +2593,8 @@ class Formula
if path.exist? && on_system_blocks_exist?
formula_contents = path.read
OnSystem::ALL_OS_ARCH_COMBINATIONS.each do |os, arch|
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
Homebrew::SimulateSystem.with(os:, arch:) do
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
Homebrew::SimulateSystem.with_tag(bottle_tag) do
variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}")
variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace,
flags: self.class.build_flags, ignore_errors: true)

View File

@ -114,7 +114,7 @@ module Homebrew
ln -s #{formula.path.to_s.gsub(formula.tap.path, "..")} #{alias_name}
EOS
else
problem "Formula has other versions so create an alias named #{alias_name}."
problem "Formula has other versions so create an alias named '#{alias_name}'."
end
end
@ -153,7 +153,7 @@ module Homebrew
next if synced_formula == name
if (synced_version = Formulary.factory(synced_formula).version) != version
problem "Version of `#{synced_formula}` (#{synced_version}) should match version of `#{name}` (#{version})"
problem "Version of #{synced_formula} (#{synced_version}) should match version of #{name} (#{version})"
end
end
@ -188,7 +188,7 @@ module Homebrew
return if formula.core_formula?
return unless Formula.core_names.include?(name)
problem "Formula name conflicts with existing core formula."
problem "Formula name conflicts with an existing formula in homebrew/core."
end
PERMITTED_LICENSE_MISMATCHES = {
@ -226,7 +226,7 @@ module Homebrew
problem <<~EOS
Formula #{formula.name} contains incompatible licenses: #{incompatible_licenses}.
Formulae in homebrew/core must either use a Debian Free Software Guidelines license
or be released into the public domain. See #{Formatter.url("https://docs.brew.sh/License-Guidelines")}
or be released into the public domain: #{Formatter.url("https://docs.brew.sh/License-Guidelines")}
EOS
end
@ -327,7 +327,7 @@ module Homebrew
end
end
problem "Dependency '#{dep}' does not define option #{opt.name.inspect}"
problem "Dependency '#{dep}' does not define option: #{opt.name.inspect}"
end
problem "Don't use 'git' as a dependency (it's always available)" if @new_formula && dep.name == "git"
@ -448,7 +448,7 @@ module Homebrew
if T.must(tap).formula_renames.key?(conflict.name) || T.must(tap).aliases.include?(conflict.name)
problem "Formula conflict should be declared using " \
"canonical name (#{conflicting_formula.name}) instead of #{conflict.name}"
"canonical name (#{conflicting_formula.name}) instead of '#{conflict.name}'"
end
reverse_conflict_found = T.let(false, T::Boolean)
@ -457,7 +457,7 @@ module Homebrew
if T.must(tap).formula_renames.key?(reverse_conflict.name) ||
T.must(tap).aliases.include?(reverse_conflict.name)
problem "Formula #{conflicting_formula.name} conflict should be declared using " \
"canonical name (#{reverse_conflict_formula.name}) instead of #{reverse_conflict.name}"
"canonical name (#{reverse_conflict_formula.name}) instead of '#{reverse_conflict.name}'"
end
reverse_conflict_found ||= reverse_conflict_formula == formula
@ -651,7 +651,7 @@ module Homebrew
metadata = SharedAudits.github_repo_data(user, repo)
return if metadata.nil?
problem "GitHub repo is archived" if metadata["archived"]
problem "GitHub repository is archived" if metadata["archived"]
end
def audit_gitlab_repository_archived
@ -663,7 +663,7 @@ module Homebrew
metadata = SharedAudits.gitlab_repo_data(user, repo)
return if metadata.nil?
problem "GitLab repo is archived" if metadata["archived"]
problem "GitLab repository is archived" if metadata["archived"]
end
def audit_github_repository
@ -712,7 +712,7 @@ module Homebrew
end
def audit_specs
problem "Head-only (no stable download)" if head_only?(formula)
problem "HEAD-only (no stable download)" if head_only?(formula)
%w[Stable HEAD].each do |name|
spec_name = name.downcase.to_sym
@ -759,7 +759,7 @@ module Homebrew
if formula.head && @versioned_formula &&
!formula.tap&.audit_exception(:versioned_head_spec_allowlist, formula.name)
problem "Versioned formulae should not have a `HEAD` spec"
problem "Versioned formulae should not have a `head` spec"
end
stable = formula.stable
@ -771,7 +771,7 @@ module Homebrew
stable_version_string = version.to_s
if stable_version_string.start_with?("HEAD")
problem "Stable: non-HEAD version name (#{stable_version_string}) should not begin with HEAD"
problem "Stable: non-HEAD version (#{stable_version_string}) should not begin with `HEAD`"
end
stable_url_version = Version.parse(stable.url)
@ -790,7 +790,7 @@ module Homebrew
return if formula.tap&.audit_exception :unstable_allowlist, formula.name, version_prefix
return if formula.tap&.audit_exception :unstable_devel_allowlist, formula.name, version_prefix
problem "Stable version URLs should not contain #{matched}"
problem "Stable: version URLs should not contain `#{matched}`"
when %r{download\.gnome\.org/sources}, %r{ftp\.gnome\.org/pub/GNOME/sources}i
version_prefix = stable.version.major_minor
return if formula.tap&.audit_exception :gnome_devel_allowlist, formula.name, version_prefix
@ -800,11 +800,11 @@ module Homebrew
return if stable_url_version >= Version.new("40.0")
return if stable_url_minor_version.even?
problem "#{stable.version} is a development release"
problem "Stable: version (#{stable.version}) is a development release"
when %r{isc.org/isc/bind\d*/}i
return if stable_url_minor_version.even?
problem "#{stable.version} is a development release"
problem "Stable: version (#{stable.version}) is a development release"
when %r{https?://gitlab\.com/([\w-]+)/([\w-]+)}
owner = T.must(Regexp.last_match(1))
@ -845,7 +845,7 @@ module Homebrew
if !newest_committed[:version].nil? &&
current_version < newest_committed[:version] &&
current_version_scheme == previous_committed[:version_scheme]
problem "stable version should not decrease (from #{newest_committed[:version]} to #{current_version})"
problem "Stable: version should not decrease (from #{newest_committed[:version]} to #{current_version})"
end
end
@ -867,14 +867,14 @@ module Homebrew
!current_revision.zero? &&
current_revision == newest_committed[:revision] &&
current_revision == previous_committed[:revision]
problem "'revision #{current_revision}' should be removed"
problem "`revision #{current_revision}` should be removed"
elsif current_version == previous_committed[:version] &&
!previous_committed[:revision].nil? &&
current_revision < previous_committed[:revision]
problem "revision should not decrease (from #{previous_committed[:revision]} to #{current_revision})"
problem "`revision` should not decrease (from #{previous_committed[:revision]} to #{current_revision})"
elsif newest_committed[:revision] &&
current_revision > (newest_committed[:revision] + 1)
problem "revisions should only increment by 1"
problem "`revision` should only increment by 1"
end
end
@ -891,10 +891,10 @@ module Homebrew
return if previous_committed[:version_scheme].nil?
if current_version_scheme < previous_committed[:version_scheme]
problem "version_scheme should not decrease (from #{previous_committed[:version_scheme]} " \
problem "`version_scheme` should not decrease (from #{previous_committed[:version_scheme]} " \
"to #{current_version_scheme})"
elsif current_version_scheme > (previous_committed[:version_scheme] + 1)
problem "version_schemes should only increment by 1"
problem "`version_scheme` should only increment by 1"
end
end
@ -935,7 +935,7 @@ module Homebrew
bin_names.each do |name|
shell_commands.each do |cmd|
if text.to_s.match?(/test do.*#{cmd}[(\s]+['"]#{Regexp.escape(name)}[\s'"]/m)
problem %Q(fully scope test #{cmd} calls, e.g. #{cmd} "\#{bin}/#{name}")
problem %Q(Fully scope test `#{cmd}` calls, e.g.: #{cmd} "\#{bin}/#{name}")
end
end
end

View File

@ -520,6 +520,7 @@ class FormulaInstaller
oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip if show_header?
if (tap = formula.tap) && tap.should_report_analytics?
require "utils/analytics"
Utils::Analytics.report_package_event(:formula_install, package_name: formula.name, tap_name: tap.name,
on_request: installed_on_request?, options:)
end
@ -892,9 +893,11 @@ on_request: installed_on_request?, options:)
return if quiet?
caveats = Caveats.new(formula)
return if caveats.empty?
Homebrew.messages.record_completions_and_elisp(caveats.completions_and_elisp)
return if caveats.caveats.empty?
@show_summary_heading = true
ohai "Caveats", caveats.to_s
Homebrew.messages.record_caveats(formula.name, caveats)

View File

@ -2,7 +2,7 @@
# frozen_string_literal: true
require "digest/sha2"
require "extend/cachable"
require "cachable"
require "tab"
require "utils"
require "utils/bottles"

View File

@ -138,7 +138,7 @@ require "extend/kernel"
require "os"
require "extend/array"
require "extend/cachable"
require "cachable"
require "extend/enumerable"
require "extend/string"
require "extend/pathname"

View File

@ -4,7 +4,7 @@
require "keg_relocate"
require "language/python"
require "lock_file"
require "extend/cachable"
require "cachable"
# Installation prefix of a formula.
class Keg

View File

@ -19,6 +19,7 @@ class MacOSVersion < Version
# NOTE: When removing symbols here, ensure that they are added
# to `DEPRECATED_MACOS_VERSIONS` in `MacOSRequirement`.
SYMBOLS = {
tahoe: "26",
sequoia: "15",
sonoma: "14",
ventura: "13",
@ -34,7 +35,9 @@ class MacOSVersion < Version
sig { params(macos_version: MacOSVersion).returns(Version) }
def self.kernel_major_version(macos_version)
version_major = macos_version.major.to_i
if version_major > 10
if version_major >= 26
Version.new((version_major - 1).to_s)
elsif version_major > 10
Version.new((version_major + 9).to_s)
else
version_minor = macos_version.minor.to_i
@ -50,7 +53,7 @@ class MacOSVersion < Version
sig { params(version: T.nilable(String)).void }
def initialize(version)
raise MacOSVersion::Error, version unless /\A1\d+(?:\.\d+){0,2}\Z/.match?(version)
raise MacOSVersion::Error, version unless /\A\d{2,}(?:\.\d+){0,2}\z/.match?(version)
super(T.must(version))

View File

@ -133,7 +133,7 @@ If no search term is provided, all locally available formulae are listed.
## CUSTOM EXTERNAL COMMANDS
Homebrew, like `git`(1), supports external commands. These are executable
scripts that reside somewhere in the `PATH`, named `brew-`<cmdname> or
scripts that reside somewhere in the `$PATH`, named `brew-`<cmdname> or
`brew-`<cmdname>`.rb`, which can be invoked like `brew` <cmdname>. This
allows you to create your own commands without modifying Homebrew's internals.
@ -184,7 +184,7 @@ files:
User-specific environment files take precedence over prefix-specific files and
prefix-specific files take precedence over system-wide files (unless
`HOMEBREW_SYSTEM_ENV_TAKES_PRIORITY` is set, see below).
`$HOMEBREW_SYSTEM_ENV_TAKES_PRIORITY` is set, see below).
Note that these files do not support shell variable expansion e.g. `$HOME` or
command execution e.g. `$(cat file)`.

View File

@ -0,0 +1,272 @@
# typed: strict
# frozen_string_literal: true
# This is a standalone Ruby script as MCP servers need a faster startup time
# than a normal Homebrew Ruby command allows.
require_relative "standalone"
require "json"
require "stringio"
module Homebrew
# Provides a Model Context Protocol (MCP) server for Homebrew.
# See https://modelcontextprotocol.io/introduction for more information.
#
# https://modelcontextprotocol.io/docs/tools/inspector is useful for testing.
class McpServer
HOMEBREW_BREW_FILE = T.let(ENV.fetch("HOMEBREW_BREW_FILE").freeze, String)
HOMEBREW_VERSION = T.let(ENV.fetch("HOMEBREW_VERSION").freeze, String)
JSON_RPC_VERSION = T.let("2.0", String)
MCP_PROTOCOL_VERSION = T.let("2025-03-26", String)
ERROR_CODE = T.let(-32601, Integer)
SERVER_INFO = T.let({
name: "brew-mcp-server",
version: HOMEBREW_VERSION,
}.freeze, T::Hash[Symbol, String])
FORMULA_OR_CASK_PROPERTIES = T.let({
formula_or_cask: {
type: "string",
description: "Formula or cask name",
},
}.freeze, T::Hash[Symbol, T.anything])
# NOTE: Cursor (as of June 2025) will only query/use a maximum of 40 tools.
TOOLS = T.let({
search: {
name: "search",
description: "Perform a substring search of cask tokens and formula names for <text>. " \
"If <text> is flanked by slashes, it is interpreted as a regular expression.",
command: "brew search",
inputSchema: {
type: "object",
properties: {
text_or_regex: {
type: "string",
description: "Text or regex to search for",
},
},
},
required: ["text_or_regex"],
},
info: {
name: "info",
description: "Display brief statistics for your Homebrew installation. " \
"If a <formula> or <cask> is provided, show summary of information about it.",
command: "brew info",
inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES },
},
install: {
name: "install",
description: "Install a <formula> or <cask>.",
command: "brew install",
inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES },
required: ["formula_or_cask"],
},
update: {
name: "update",
description: "Fetch the newest version of Homebrew and all formulae from GitHub using `git` and " \
"perform any necessary migrations.",
command: "brew update",
inputSchema: { type: "object", properties: {} },
},
upgrade: {
name: "upgrade",
description: "Upgrade outdated casks and outdated, unpinned formulae using the same options they were " \
"originally installed with, plus any appended brew formula options. If <cask> or <formula> " \
"are specified, upgrade only the given <cask> or <formula> kegs (unless they are pinned).",
command: "brew upgrade",
inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES },
},
uninstall: {
name: "uninstall",
description: "Uninstall a <formula> or <cask>.",
command: "brew uninstall",
inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES },
required: ["formula_or_cask"],
},
list: {
name: "list",
description: "List all installed formulae and casks. " \
"If <formula> is provided, summarise the paths within its current keg. " \
"If <cask> is provided, list its artifacts.",
command: "brew list",
inputSchema: { type: "object", properties: FORMULA_OR_CASK_PROPERTIES },
},
config: {
name: "config",
description: "Show Homebrew and system configuration info useful for debugging. " \
"If you file a bug report, you will be required to provide this information.",
command: "brew config",
inputSchema: { type: "object", properties: {} },
},
doctor: {
name: "doctor",
description: "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.",
command: "brew doctor",
inputSchema: { type: "object", properties: {} },
},
commands: {
name: "commands",
description: "Show lists of built-in and external commands.",
command: "brew commands",
inputSchema: { type: "object", properties: {} },
},
help: {
name: "help",
description: "Outputs the usage instructions for `brew` <command>.",
command: "brew help",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description: "Command to get help for",
},
},
},
},
}.freeze, T::Hash[Symbol, T::Hash[Symbol, T.anything]])
sig { params(stdin: T.any(IO, StringIO), stdout: T.any(IO, StringIO), stderr: T.any(IO, StringIO)).void }
def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr)
@debug_logging = T.let(ARGV.include?("--debug") || ARGV.include?("-d"), T::Boolean)
@ping_switch = T.let(ARGV.include?("--ping"), T::Boolean)
@stdin = T.let(stdin, T.any(IO, StringIO))
@stdout = T.let(stdout, T.any(IO, StringIO))
@stderr = T.let(stderr, T.any(IO, StringIO))
end
sig { returns(T::Boolean) }
def debug_logging? = @debug_logging
sig { returns(T::Boolean) }
def ping_switch? = @ping_switch
sig { void }
def run
@stderr.puts "==> Started Homebrew MCP server..."
loop do
input = if ping_switch?
{ jsonrpc: JSON_RPC_VERSION, id: 1, method: "ping" }.to_json
else
@stdin.gets
end
next if input.nil? || input.strip.empty?
request = JSON.parse(input)
debug("Request: #{JSON.pretty_generate(request)}")
response = handle_request(request)
if response.nil?
debug("Response: nil")
next
end
debug("Response: #{JSON.pretty_generate(response)}")
output = JSON.dump(response).strip
@stdout.puts(output)
@stdout.flush
break if ping_switch?
end
rescue Interrupt
exit 0
rescue => e
log("Error: #{e.message}")
exit 1
end
sig { params(text: String).void }
def debug(text)
return unless debug_logging?
log(text)
end
sig { params(text: String).void }
def log(text)
@stderr.puts(text)
@stderr.flush
end
sig { params(request: T::Hash[String, T.untyped]).returns(T.nilable(T::Hash[Symbol, T.anything])) }
def handle_request(request)
id = request["id"]
return if id.nil?
case request["method"]
when "initialize"
respond_result(id, {
protocolVersion: MCP_PROTOCOL_VERSION,
capabilities: {
tools: { listChanged: false },
prompts: {},
resources: {},
logging: {},
roots: {},
},
serverInfo: SERVER_INFO,
})
when "resources/list"
respond_result(id, { resources: [] })
when "resources/templates/list"
respond_result(id, { resourceTemplates: [] })
when "prompts/list"
respond_result(id, { prompts: [] })
when "ping"
respond_result(id)
when "get_server_info"
respond_result(id, SERVER_INFO)
when "logging/setLevel"
@debug_logging = request["params"]["level"] == "debug"
respond_result(id)
when "notifications/initialized", "notifications/cancelled"
respond_result
when "tools/list"
respond_result(id, { tools: TOOLS.values })
when "tools/call"
if (tool = TOOLS.fetch(request["params"]["name"].to_sym, nil))
require "shellwords"
arguments = request["params"]["arguments"]
argument = arguments.fetch("formula_or_cask", "")
argument = arguments.fetch("text_or_regex", "") if argument.strip.empty?
argument = arguments.fetch("command", "") if argument.strip.empty?
argument = nil if argument.strip.empty?
brew_command = T.cast(tool.fetch(:command), String)
.delete_prefix("brew ")
full_command = [HOMEBREW_BREW_FILE, brew_command, argument].compact
.map { |arg| Shellwords.escape(arg) }
.join(" ")
output = `#{full_command} 2>&1`.strip
respond_result(id, { content: [{ type: "text", text: output }] })
else
respond_error(id, "Unknown tool")
end
else
respond_error(id, "Method not found")
end
end
sig {
params(id: T.nilable(Integer),
result: T::Hash[Symbol, T.anything]).returns(T.nilable(T::Hash[Symbol, T.anything]))
}
def respond_result(id = nil, result = {})
return if id.nil?
{ jsonrpc: JSON_RPC_VERSION, id:, result: }
end
sig { params(id: T.nilable(Integer), message: String).returns(T::Hash[Symbol, T.anything]) }
def respond_error(id, message)
{ jsonrpc: JSON_RPC_VERSION, id:, error: { code: ERROR_CODE, message: } }
end
end
end

View File

@ -16,6 +16,7 @@ class Messages
sig { void }
def initialize
@caveats = T.let([], T::Array[T::Hash[Symbol, Symbol]])
@completions_and_elisp = T.let(Set.new, T::Set[String])
@package_count = T.let(0, Integer)
@install_times = T.let([], T::Array[T::Hash[String, Float]])
end
@ -25,6 +26,11 @@ class Messages
@caveats.push(package:, caveats:)
end
sig { params(completions_and_elisp: T::Array[String]).void }
def record_completions_and_elisp(completions_and_elisp)
@completions_and_elisp.merge(completions_and_elisp)
end
sig { params(package: String, elapsed_time: Float).void }
def package_installed(package, elapsed_time)
@package_count += 1
@ -40,13 +46,14 @@ class Messages
sig { params(force: T::Boolean).void }
def display_caveats(force: false)
return if @package_count.zero?
return if @package_count == 1 && !force
return if @caveats.empty?
return if @caveats.empty? && @completions_and_elisp.empty?
oh1 "Caveats"
@caveats.each do |c|
ohai c[:package], c[:caveats]
end
oh1 "Caveats" unless @completions_and_elisp.empty?
@completions_and_elisp.each { |c| puts c }
return if @package_count == 1 && !force
oh1 "Caveats" if @completions_and_elisp.empty?
@caveats.each { |c| ohai c[:package], c[:caveats] }
end
sig { void }

View File

@ -9,6 +9,13 @@ module OnSystem
ALL_OS_OPTIONS = [*MacOSVersion::SYMBOLS.keys, :linux].freeze
ALL_OS_ARCH_COMBINATIONS = ALL_OS_OPTIONS.product(ARCH_OPTIONS).freeze
VALID_OS_ARCH_TAGS = ALL_OS_ARCH_COMBINATIONS.filter_map do |os, arch|
tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless tag.valid_combination?
tag
end.freeze
sig { params(arch: Symbol).returns(T::Boolean) }
def self.arch_condition_met?(arch)
raise ArgumentError, "Invalid arch condition: #{arch.inspect}" if ARCH_OPTIONS.exclude?(arch)

View File

@ -5,7 +5,7 @@ libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: expat
Version: 2.6.3
Version: 2.7.1
Description: expat XML parser
URL: https://libexpat.github.io/
Libs: -L${libdir} -lexpat

View File

@ -0,0 +1,12 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
bindir=${exec_prefix}/bin
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: bzip2
Description: Lossless, block-sorting data compression
Version: 1.0.8
Libs: -L${libdir} -lbz2
Cflags:

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: expat
Version: 2.7.1
Description: expat XML parser
URL: https://libexpat.github.io/
Libs: -L${libdir} -lexpat
Libs.private:
Cflags:
Cflags.private: -DXML_STATIC

View File

@ -0,0 +1,42 @@
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
# This should most probably benefit from getting a "Requires:" field added
# dynamically by configure.
#
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
supported_protocols="DICT FILE FTP FTPS GOPHER GOPHERS HTTP HTTPS IMAP IMAPS IPFS IPNS LDAP LDAPS MQTT POP3 POP3S RTSP SMB SMBS SMTP SMTPS TELNET TFTP"
supported_features="alt-svc AsynchDNS GSS-API HSTS HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM SPNEGO SSL threadsafe UnixSockets"
Name: libcurl
URL: https://curl.se/
Description: Library to transfer files with ftp, http, etc.
Version: 8.7.1
Libs: -L${libdir} -lcurl
Libs.private: -lldap -lz
Cflags:

View File

@ -0,0 +1,12 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libedit
Description: command line editor library provides generic line editing, history, and tokenization functions.
Version: 3.0
Requires:
Libs: -L${libdir} -ledit
Cflags: -I${includedir}/editline

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libexslt
Version: 0.8.20
Description: EXSLT Extension library
Requires: libxml-2.0, libxslt
Cflags:
Libs: -L${libdir} -lexslt
Libs.private:

View File

@ -0,0 +1,12 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
toolexeclibdir=${libdir}
includedir=${prefix}/include/ffi
Name: libffi
Description: Library supporting Foreign Function Interfaces
Version: 3.4-rc1
Libs: -L${toolexeclibdir} -lffi
Cflags: -I${includedir}

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
modules=1
Name: libXML
Version: 2.9.13
Description: libXML library version2.
Requires:
Libs: -L${libdir} -lxml2
Libs.private: -lz -lpthread -licucore -lm
Cflags:

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libxslt
Version: 1.1.35
Description: XSLT library version 2.
Requires: libxml-2.0
Cflags:
Libs: -L${libdir} -lxslt
Libs.private:

View File

@ -0,0 +1,17 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
abi_version=5.4
major_version=6
version=6.0.20150808
Name: ncurses
Description: ncurses 6.0 library
Version: ${version}
URL: http://invisible-island.net/ncurses
Requires.private:
Libs: -L${libdir} -lncurses
Libs.private:
Cflags: -D_DARWIN_C_SOURCE

View File

@ -0,0 +1,17 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
abi_version=5.4
major_version=6
version=6.0.20150808
Name: ncursesw
Description: ncurses 6.0 library
Version: ${version}
URL: http://invisible-island.net/ncurses
Requires.private:
Libs: -L${libdir} -lncurses
Libs.private:
Cflags: -D_DARWIN_C_SOURCE

View File

@ -0,0 +1,12 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: SQLite
Description: SQL database engine
Version: 3.48.0
Libs: -L${libdir} -lsqlite3
Libs.private:
Cflags:

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
sharedlibdir=${libdir}
includedir=${prefix}/include/uuid
Name: uuid
Description: Universally unique id library
Version: 1.0
Requires:
Libs:
Cflags: -I${includedir}

View File

@ -0,0 +1,14 @@
homebrew_sdkroot=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk
prefix=${homebrew_sdkroot}/usr
exec_prefix=/usr
libdir=${exec_prefix}/lib
sharedlibdir=${libdir}
includedir=${prefix}/include
Name: zlib
Description: zlib compression library
Version: 1.2.12
Requires:
Libs: -L${libdir} -L${sharedlibdir} -lz
Cflags:

View File

@ -15,10 +15,10 @@ module OS
# This may be a beta version for a beta macOS.
sig { params(macos: MacOSVersion).returns(String) }
def self.latest_version(macos: MacOS.version)
latest_stable = "15.4"
macos = macos.strip_patch
case macos
when "15" then "16.0"
when "14" then latest_stable
when "15" then "16.4"
when "14" then "16.2"
when "13" then "15.2"
when "12" then "14.2"
when "11" then "13.2.1"
@ -28,10 +28,10 @@ module OS
when "10.12" then "9.2"
when "10.11" then "8.2.1"
else
raise "macOS '#{MacOS.version}' is invalid" unless OS::Mac.version.prerelease?
raise "macOS '#{macos}' is invalid" unless macos.prerelease?
# Default to newest known version of Xcode for unreleased macOS versions.
latest_stable
# Assume matching yearly Xcode release
"#{macos}.0"
end
end
@ -41,7 +41,8 @@ module OS
# also in beta).
sig { returns(String) }
def self.minimum_version
case MacOS.version
macos = MacOS.version
case macos
when "15" then "16.0"
when "14" then "15.0"
when "13" then "14.1"
@ -51,7 +52,9 @@ module OS
when "10.14" then "10.2"
when "10.13" then "9.0"
when "10.12" then "8.0"
else "7.3"
when "10.11" then "7.3"
else
"#{macos}.0"
end
end
@ -225,10 +228,8 @@ module OS
detect_version_from_clang_version
end
sig { returns(String) }
def self.detect_version_from_clang_version
version = ::DevelopmentTools.clang_version
sig { params(version: ::Version).returns(String) }
def self.detect_version_from_clang_version(version = ::DevelopmentTools.clang_version)
return "dunno" if version.null?
# This logic provides a fake Xcode version based on the
@ -255,8 +256,9 @@ module OS
when "13.1.6" then "13.4.1"
when "14.0.0" then "14.2"
when "14.0.3" then "14.3.1"
when "16.0.0" then "16.0"
else "15.4"
when "15.0.0" then "15.4"
when "16.0.0" then "16.2"
else "26.0"
end
end
@ -355,8 +357,9 @@ module OS
sig { returns(String) }
def self.latest_clang_version
case MacOS.version
when "15" then "1600.0.20.10"
when "14" then "1500.3.9.4"
when "26" then "1700.3.9.908"
when "15" then "1700.0.13.5"
when "14" then "1600.0.26.6"
when "13" then "1500.1.0.2.5"
when "12" then "1400.0.29.202"
when "11" then "1300.0.29.30"
@ -373,7 +376,8 @@ module OS
# that macOS version.
sig { returns(String) }
def self.minimum_version
case MacOS.version
macos = MacOS.version
case macos
when "15" then "16.0.0"
when "14" then "15.0.0"
when "13" then "14.0.0"
@ -383,7 +387,9 @@ module OS
when "10.14" then "10.0.0"
when "10.13" then "9.0.0"
when "10.12" then "8.0.0"
else "7.3.0"
when "10.11" then "7.3.0"
else
"#{macos}.0.0"
end
end
@ -410,7 +416,10 @@ module OS
sig { returns(T.nilable(String)) }
def self.detect_version_from_clang_version
detect_clang_version&.sub(/^(\d+)0(\d)\./, "\\1.\\2.")
clang_version = detect_clang_version
return if clang_version.nil?
MacOS::Xcode.detect_version_from_clang_version(Version.new(clang_version))
end
# Version string (a pretty long one) of the CLT package.

View File

@ -1,17 +1,17 @@
# typed: strict
# frozen_string_literal: true
class IO
sig { params(sep: String).returns(String) }
def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
class ReadlineNonblock
sig { params(io: IO).returns(String) }
def self.read(io)
line = +""
buffer = +""
begin
loop do
break if buffer == sep
break if buffer == $INPUT_RECORD_SEPARATOR
read_nonblock(1, buffer)
io.read_nonblock(1, buffer)
line.concat(buffer)
end

View File

@ -4,7 +4,7 @@
require "downloadable"
require "mktemp"
require "livecheck"
require "extend/on_system"
require "on_system"
# Resource is the fundamental representation of an external resource. The
# primary formula download, along with other declared resources, are instances

View File

@ -63,7 +63,7 @@ module Homebrew
url_strategy = DownloadStrategyDetector.detect(url)
if (using == :git || url_strategy == GitDownloadStrategy) && specs[:tag] && !specs[:revision]
problem "Git should specify :revision when a :tag is specified."
problem "Git should specify `revision:` when a `tag:` is specified."
end
return unless using
@ -71,7 +71,7 @@ module Homebrew
if using == :cvs
mod = specs[:module]
problem "Redundant :module value in URL" if mod == name
problem "Redundant `module:` value in URL" if mod == name
if url.match?(%r{:[^/]+$})
mod = url.split(":").last
@ -79,14 +79,14 @@ module Homebrew
if mod == name
problem "Redundant CVS module appended to URL"
else
problem "Specify CVS module as `:module => \"#{mod}\"` instead of appending it to the URL"
problem "Specify CVS module as `module: \"#{mod}\"` instead of appending it to the URL"
end
end
end
return if url_strategy != DownloadStrategyDetector.detect("", using)
problem "Redundant :using value in URL"
problem "Redundant `using:` value in URL"
end
def audit_checksum
@ -125,7 +125,7 @@ module Homebrew
return if name.casecmp(pypi_package_name).zero?
problem "resource name should be `#{pypi_package_name}` to match the PyPI package name"
problem "`resource` name should be '#{pypi_package_name}' to match the PyPI package name"
end
def audit_urls
@ -166,12 +166,12 @@ module Homebrew
remote_exists = Utils::Git.remote_exists?(url)
attempts += 1
end
problem "The URL #{url} is not a valid git URL" unless remote_exists
problem "The URL #{url} is not a valid Git URL" unless remote_exists
elsif strategy <= SubversionDownloadStrategy
next unless DevelopmentTools.subversion_handles_most_https_certificates?
next unless Utils::Svn.available?
problem "The URL #{url} is not a valid svn URL" unless Utils::Svn.remote_exists? url
problem "The URL #{url} is not a valid SVN URL" unless Utils::Svn.remote_exists? url
end
end
end
@ -188,7 +188,7 @@ module Homebrew
.match(%r{ref: refs/heads/(.*?)\s+HEAD})&.to_a&.second
return if branch.blank? || branch == specs[:branch]
problem "Use `branch: \"#{branch}\"` to specify the default branch"
problem "Specify the default branch as `branch: \"#{branch}\"`"
end
def problem(text)

View File

@ -11,14 +11,14 @@ module RuboCop
# TODO: Update this list if new stanzas are added to `Cask::DSL` that call `set_unique_stanza`.
OVERRIDABLE_METHODS = [
:appcast, :arch, :auto_updates, :conflicts_with, :container,
:desc, :homepage, :sha256, :url, :version
:desc, :homepage, :os, :sha256, :url, :version
].freeze
MESSAGE = "Do not use a top-level `%<stanza>s` stanza as the default. " \
"Add it to an `on_{system}` block instead. " \
"Use `:or_older` or `:or_newer` to specify a range of macOS versions."
sig { override.params(cask_block: RuboCop::Cask::AST::CaskBlock).void }
def on_cask(cask_block)
message = "Do not use a top-level `%<stanza>s` stanza as the default. " \
"Add it to an `on_{system}` block instead. " \
"Use `:or_older` or `:or_newer` to specify a range of macOS versions."
cask_stanzas = cask_block.toplevel_stanzas
return if (on_blocks = on_system_methods(cask_stanzas)).none?
@ -31,12 +31,14 @@ module RuboCop
# Skip if the stanza outside of a block is not also in an `on_*` block.
next unless stanzas_in_blocks.include?(stanza.stanza_name)
add_offense(stanza.source_range, message: format(MESSAGE, stanza: stanza.stanza_name))
add_offense(stanza.source_range, message: format(message, stanza: stanza.stanza_name))
end
end
sig { params(on_system: T::Array[RuboCop::Cask::AST::Stanza]).returns(T::Set[Symbol]) }
def on_system_stanzas(on_system)
message = "Do not use a `depends_on macos:` stanza inside an `on_{system}` block. " \
"Add it once to specify the oldest macOS supported by any version in the cask."
names = T.let(Set.new, T::Set[Symbol])
method_nodes = on_system.map(&:method_node)
method_nodes.select(&:block_type?).each do |node|
@ -51,6 +53,14 @@ module RuboCop
end
next if RuboCop::Cask::Constants::ON_SYSTEM_METHODS.include?(send_node.method_name)
if send_node.method_name == :depends_on &&
send_node.arguments.first.pairs.any? { |a| a.key.value == :macos } &&
OnSystemConditionalsHelper::ON_SYSTEM_OPTIONS.map do |m|
:"on_#{m}"
end.include?(T.cast(node, RuboCop::AST::BlockNode).method_name)
add_offense(send_node.source_range, message:)
end
names.add(send_node.method_name)
end
end

View File

@ -41,6 +41,15 @@ module RuboCop
return unless hash_node.hash_type?
unless stanza_node.source.match?(/",\n *\w+:/)
add_offense(
stanza_node.source_range,
message: "Keyword URL parameter should be on a new indented line.",
) do |corrector|
corrector.replace(stanza_node.source_range, stanza_node.source.gsub(/",\s*/, "\",\n "))
end
end
hash_node.each_pair do |key_node, value_node|
next if key_node.source != "verified"
next unless value_node.str_type?

View File

@ -30,7 +30,7 @@ module RuboCop
def audit_formula(_formula_nodes)
caveats_strings.each do |n|
if regex_match_group(n, /\bsetuid\b/i)
problem "Don't recommend `setuid` in the caveats, suggest `sudo` instead."
problem "Instead of recommending `setuid` in the caveats, suggest `sudo`."
end
problem "Don't use ANSI escape codes in the caveats." if regex_match_group(n, /\e/)

View File

@ -28,17 +28,17 @@ module RuboCop
return if checksum.nil?
if regex_match_group(checksum, /^$/)
problem "sha256 is empty"
problem "`sha256` is empty"
return
end
if string_content(checksum).size != 64 && regex_match_group(checksum, /^\w*$/)
problem "sha256 should be 64 characters"
problem "`sha256` should be 64 characters"
end
return unless regex_match_group(checksum, /[^a-f0-9]+/i)
add_offense(T.must(@offensive_source_range), message: "sha256 contains invalid characters")
add_offense(T.must(@offensive_source_range), message: "`sha256` contains invalid characters")
end
end
@ -54,7 +54,7 @@ module RuboCop
next if checksum.nil?
next unless regex_match_group(checksum, /[A-F]+/)
add_offense(@offensive_source_range, message: "sha256 should be lowercase") do |corrector|
add_offense(@offensive_source_range, message: "`sha256` should be lowercase") do |corrector|
correction = T.must(@offensive_node).source.downcase
corrector.insert_before(T.must(@offensive_node).source_range, correction)
corrector.remove(T.must(@offensive_node).source_range)

View File

@ -23,7 +23,7 @@ module RuboCop
parent_class = class_name(parent_class_node)
return unless DEPRECATED_CLASSES.include?(parent_class)
problem "#{parent_class} is deprecated, use Formula instead" do |corrector|
problem "`#{parent_class}` is deprecated, use `Formula` instead" do |corrector|
corrector.replace(parent_class_node.source_range, "Formula")
end
end
@ -49,14 +49,14 @@ module RuboCop
p1, p2 = params
if (match = string_content(p1).match(%r{(/usr/local/(s?bin))}))
offending_node(p1)
problem "use \#{#{match[2]}} instead of #{match[1]} in #{node}" do |corrector|
problem "Use `\#{#{match[2]}}` instead of `#{match[1]}` in `#{node}`" do |corrector|
corrector.replace(p1.source_range, p1.source.sub(match[1], "\#{#{match[2]}}"))
end
end
if node == :shell_output && node_equals?(p2, 0)
offending_node(p2)
problem "Passing 0 to shell_output() is redundant" do |corrector|
problem "Passing 0 to `shell_output` is redundant" do |corrector|
corrector.remove(range_with_surrounding_comma(range_with_surrounding_space(range: p2.source_range,
side: :left)))
end

Some files were not shown because too many files have changed in this diff Show More