Merge branch 'master' into add-dry-run-option-to-brew-install

This commit is contained in:
Mike McQuaid 2022-10-03 10:10:58 +01:00 committed by GitHub
commit 9c0708f2b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
858 changed files with 10723 additions and 8296 deletions

View File

@ -11,7 +11,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
actions: read
contents: read

View File

@ -14,7 +14,7 @@ permissions:
jobs:
ubuntu:
if: startsWith(github.repository, 'Homebrew/')
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:

View File

@ -16,7 +16,7 @@ permissions:
jobs:
tapioca:
if: github.repository == 'Homebrew/brew'
runs-on: macos-latest
runs-on: macos-12
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -46,12 +46,13 @@ jobs:
if git ls-remote --exit-code --heads origin "${BRANCH}"
then
git checkout "${BRANCH}"
git reset --hard origin/master
git checkout "${GITHUB_WORKSPACE}/Library/Homebrew/sorbet"
else
git checkout --no-track -B "${BRANCH}" origin/master
fi
if brew typecheck --update --fail-if-not-changed
brew typecheck --update
if ! git diff --stat --exit-code "${GITHUB_WORKSPACE}/Library/Homebrew/sorbet"
then
git add "${GITHUB_WORKSPACE}/Library/Homebrew/sorbet"
git commit -m "sorbet: Update RBI files." \

View File

@ -7,12 +7,15 @@ on:
- master
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: read
jobs:
spdx:
if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -42,12 +45,12 @@ jobs:
if git ls-remote --exit-code --heads origin "${BRANCH}"
then
git checkout "${BRANCH}"
git reset --hard origin/master
git checkout "${GITHUB_WORKSPACE}/Library/Homebrew/data/spdx"
else
git checkout --no-track -B "${BRANCH}" origin/master
fi
if brew update-license-data --fail-if-not-changed
if brew update-license-data
then
git add "${GITHUB_WORKSPACE}/Library/Homebrew/data/spdx"
git commit -m "spdx: update license data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/master/.github/workflows/spdx.yml)."

View File

@ -1,9 +1,9 @@
name: Update maintainers, manpage and completions
name: Update sponsors, maintainers, manpage and completions
on:
push:
paths:
- .github/workflows/update-man-completions.yml
- .github/workflows/sponsors-maintainers-man-completions.yml
- README.md
- Library/Homebrew/cmd/**
- Library/Homebrew/dev-cmd/**
@ -12,8 +12,6 @@ on:
- Library/Homebrew/cli/parser.rb
- Library/Homebrew/completions.rb
- Library/Homebrew/env_config.rb
branches:
- master
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
@ -22,11 +20,12 @@ permissions:
contents: read
jobs:
update-manpage:
runs-on: ubuntu-latest
updates:
runs-on: ubuntu-22.04
if: github.repository == 'Homebrew/brew'
steps:
- name: Setup Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Configure Git user
@ -39,35 +38,68 @@ jobs:
with:
signing_key: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY }}
- name: Update maintainers, manpage and completions
- name: Cache Bundler RubyGems
uses: actions/cache@v1
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
restore-keys: ${{ runner.os }}-rubygems-
- name: Update sponsors, maintainers, manpage and completions
id: update
run: |
git fetch origin
BRANCH=update-man-completions
if [[ -n "$GITHUB_REF_NAME" && "$GITHUB_REF_NAME" != "master" ]]
then
BRANCH="$GITHUB_REF_NAME"
else
BRANCH=sponsors-maintainers-man-completions
fi
echo "::set-output name=branch::${BRANCH}"
if git ls-remote --exit-code --heads origin "${BRANCH}"
then
git checkout "${BRANCH}"
git reset --hard origin/master
git checkout "${GITHUB_WORKSPACE}/README.md" \
"${GITHUB_WORKSPACE}/docs/Manpage.md" \
"${GITHUB_WORKSPACE}/manpages/brew.1" \
"${GITHUB_WORKSPACE}/completions"
else
git checkout --no-track -B "${BRANCH}" origin/master
fi
if [[ "${{github.event_name}}" != "push" ]]
if brew update-sponsors
then
brew update-maintainers
git add "${GITHUB_WORKSPACE}/README.md"
git commit -m "Update sponsors." \
-m "Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow."
COMMITTED=true
fi
if brew generate-man-completions --fail-if-not-changed
if brew update-maintainers
then
git add "${GITHUB_WORKSPACE}/README.md" \
"${GITHUB_WORKSPACE}/docs/Manpage.md" \
"${GITHUB_WORKSPACE}/manpages/brew.1"
git commit -m "Update maintainers." \
-m "Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow."
COMMITTED=true
fi
if brew generate-man-completions
then
git add "${GITHUB_WORKSPACE}/README.md" \
"${GITHUB_WORKSPACE}/docs/Manpage.md" \
"${GITHUB_WORKSPACE}/manpages/brew.1" \
"${GITHUB_WORKSPACE}/completions"
git commit -m "Update maintainers, manpage and completions." \
-m "Autogenerated by the [update-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/update-man-completions.yml) workflow."
git commit -m "Update manpage and completions." \
-m "Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow."
COMMITTED=true
fi
if [[ -n "$COMMITTED" ]]
then
echo "::set-output name=committed::true"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")"
if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
@ -77,7 +109,7 @@ jobs:
fi
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_BREW_UPDATE_MAINTAINERS_TOKEN }}
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_BREW_UPDATE_SPONSORS_MAINTAINERS_TOKEN }}
HOMEBREW_GPG_PASSPHRASE: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY_PASSPHRASE }}
- name: Push commits

View File

@ -20,7 +20,7 @@ concurrency:
jobs:
syntax:
if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -49,10 +49,10 @@ jobs:
vale docs/
tap-syntax:
name: tap syntax (Linux)
name: tap syntax
needs: syntax
if: startsWith(github.repository, 'Homebrew/')
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -82,13 +82,13 @@ jobs:
brew update-test --commit=HEAD
- name: Run brew readall on all taps
run: brew readall --aliases
run: brew readall --eval-all --aliases
- name: Run brew style on homebrew-core for Linux
run: brew style --display-cop-names homebrew/core
- name: Run brew audit --skip-style on all taps
run: brew audit --skip-style --except=version --display-failures-only
run: brew audit --eval-all --skip-style --except=version --display-failures-only
- name: Set up all Homebrew taps
run: |
@ -145,8 +145,8 @@ jobs:
homebrew/cask-versions
vendored-gems:
name: vendored gems (Linux)
runs-on: ubuntu-latest
name: vendored gems
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -167,40 +167,78 @@ jobs:
docker:
needs: syntax
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Build Docker image
run: docker build -t brew --build-arg=version=16.04 .
run: |
docker build -t brew --build-arg=version=22.04 \
--label org.opencontainers.image.created="$(date --rfc-3339=seconds --utc)" \
--label org.opencontainers.image.url="https://brew.sh" \
--label org.opencontainers.image.documentation="https://docs.brew.sh" \
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
--label org.opencontainers.image.vendor="${GITHUB_REPOSITORY_OWNER}" \
--label org.opencontainers.image.licenses="BSD-2-Clause" \
.
- name: Deploy the Docker image to GitHub Packages and Docker Hub
if: github.ref == 'refs/heads/master'
run: |
echo ${{secrets.HOMEBREW_BREW_GITHUB_PACKAGES_TOKEN}} |
docker login ghcr.io -u BrewTestBot --password-stdin
docker tag brew "ghcr.io/homebrew/ubuntu16.04:master"
docker push "ghcr.io/homebrew/ubuntu16.04:master"
docker tag brew "ghcr.io/homebrew/ubuntu22.04:master"
docker push "ghcr.io/homebrew/ubuntu22.04:master"
echo ${{secrets.HOMEBREW_BREW_DOCKER_TOKEN}} |
docker login -u brewtestbot --password-stdin
docker tag brew "homebrew/ubuntu16.04:master"
docker tag brew "homebrew/ubuntu22.04:master"
docker push "homebrew/ubuntu22.04:master"
- name: Build deprecated 16.04 Docker image
run: |
echo "homebrew/ubuntu16.04:master is deprecated and will soon be retired. Use homebrew/ubuntu22.04:master or homebrew/ubuntu16.04 or homebrew/brew. For CI, homebrew/ubuntu22.04:master is recommended." > .docker-deprecate
docker build -t brew-deprecated --build-arg=version=16.04 \
--label org.opencontainers.image.created="$(date --rfc-3339=seconds --utc)" \
--label org.opencontainers.image.url="https://brew.sh" \
--label org.opencontainers.image.documentation="https://docs.brew.sh" \
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
--label org.opencontainers.image.vendor="${GITHUB_REPOSITORY_OWNER}" \
--label org.opencontainers.image.licenses="BSD-2-Clause" \
--label org.opencontainers.image.support.end-of-support="2022-09-07T00:00:00Z" \
.
- name: Deploy the deprecated 16.04 Docker image to GitHub Packages and Docker Hub
if: github.ref == 'refs/heads/master'
run: |
docker tag brew-deprecated "ghcr.io/homebrew/ubuntu16.04:master"
docker push "ghcr.io/homebrew/ubuntu16.04:master"
docker tag brew-deprecated "homebrew/ubuntu16.04:master"
docker push "homebrew/ubuntu16.04:master"
tests:
name: ${{ matrix.name }}
needs: syntax
runs-on: ubuntu-latest
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
include:
- name: tests (no-compatibility mode)
test-flags: --no-compat --online --coverage
runs-on: ubuntu-22.04
- name: tests (generic OS)
test-flags: --generic --online --coverage
- name: tests (Linux)
runs-on: ubuntu-22.04
- name: tests (Ubuntu 22.04)
test-flags: --online --coverage
runs-on: ubuntu-22.04
# Enable later once this can be fixed.
# - name: tests (Ubuntu 18.04)
# test-flags: --online --coverage
# runs-on: ubuntu-18.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -238,15 +276,24 @@ jobs:
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
with:
files: Library/Homebrew/test/coverage/coverage.xml
test-default-formula-linux:
name: test default formula (Linux)
runs-on: ubuntu-latest
name: ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
env:
HOMEBREW_BOOTSNAP: 1
strategy:
matrix:
include:
- name: test default formula (Ubuntu 22.04)
test-flags: --online --coverage
runs-on: ubuntu-22.04
- name: test default formula (Ubuntu 18.04)
test-flags: --online --coverage
runs-on: ubuntu-18.04
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -260,7 +307,7 @@ jobs:
name: test everything (macOS)
needs: syntax
if: startsWith(github.repository, 'Homebrew/')
runs-on: macos-latest
runs-on: macos-12
env:
HOMEBREW_BOOTSNAP: 1
steps:
@ -301,7 +348,7 @@ jobs:
Library/Taps/homebrew/homebrew-services
- name: Run brew readall on all taps
run: brew readall --aliases
run: brew readall --eval-all --aliases
- name: Install brew tests dependencies
run: brew install subversion curl
@ -339,6 +386,6 @@ jobs:
- run: brew test-bot --only-formulae --test-default-formula
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
with:
files: Library/Homebrew/test/coverage/coverage.xml

View File

@ -32,12 +32,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Mark/Close Stale Issues and Pull Requests
uses: actions/stale@v5
uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
days-before-close: 7
close-issue-reason: "not_planned"
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
@ -58,7 +57,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests
uses: actions/stale@v5
uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 2

View File

@ -18,7 +18,7 @@ concurrency: triage-${{ github.head_ref }}
jobs:
review:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
if: startsWith(github.repository, 'Homebrew/')
steps:
- name: Re-run this workflow

View File

@ -21,7 +21,7 @@ jobs:
contains(github.event.pull_request.title, '/Library/Homebrew')
)
)
runs-on: macos-latest
runs-on: macos-12
steps:
- name: Set up Homebrew
id: set-up-homebrew
@ -60,16 +60,12 @@ jobs:
GEM_NAME: ${{ steps.checkout.outputs.gem_name }}
HOMEBREW_GPG_PASSPHRASE: ${{ secrets.BREWTESTBOT_GPG_SIGNING_SUBKEY_PASSPHRASE }}
run: |
set -u
if brew typecheck --update --fail-if-not-changed
brew typecheck --update
if ! git diff --stat --exit-code "${GITHUB_WORKSPACE}/Library/Homebrew/sorbet"
then
if git add Library/Homebrew/sorbet
then
git commit -m "Update RBI files for ${GEM_NAME}."
fi
git reset --hard
git add "${GITHUB_WORKSPACE}/Library/Homebrew/sorbet"
git commit -m "Update RBI files for ${GEM_NAME}." \
-m "Autogenerated by the [vendor-gems](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/vendor-gemss.yml) workflow."
fi
- name: Push to pull request

View File

@ -21,6 +21,8 @@ RSpec/RepeatedDescription:
Enabled: false
RSpec/StubbedMock:
Enabled: false
RSpec/NoExpectationExample:
Enabled: false
# TODO: try to reduce these
RSpec/ExampleLength:

View File

@ -2,7 +2,11 @@
source "https://rubygems.org"
ruby ">= 2.6.0"
if ENV.fetch("HOMEBREW_DEVELOPER", "").empty? || ENV.fetch("HOMEBREW_USE_RUBY_FROM_PATH", "").empty?
ruby "~> 2.6.0"
else
ruby ">= 2.6.0"
end
# disallowed gems (should not be used)
# * nokogiri - use rexml instead for XML parsing

View File

@ -1,16 +1,16 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.1.6.1)
activesupport (6.1.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
bindata (2.4.10)
bindata (2.4.11)
bootsnap (1.13.0)
msgpack (~> 1.2)
byebug (11.1.3)
@ -18,7 +18,7 @@ GEM
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
connection_pool (2.3.0)
did_you_mean (1.6.1)
diff-lcs (1.5.0)
docile (1.4.0)
@ -57,8 +57,8 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_portile2 (2.8.0)
minitest (5.16.2)
msgpack (1.5.4)
minitest (5.16.3)
msgpack (1.6.0)
mustache (1.1.1)
net-http-digest_auth (1.4.1)
net-http-persistent (4.0.1)
@ -67,7 +67,7 @@ GEM
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
parallel (1.22.1)
parallel_tests (3.11.1)
parallel_tests (3.13.0)
parallel
parlour (8.0.0)
commander (~> 4.5)
@ -82,9 +82,9 @@ GEM
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (4.0.7)
public_suffix (5.0.0)
racc (1.6.0)
rack (2.2.4)
rack (3.0.0)
rainbow (3.1.1)
rbi (0.0.14)
ast
@ -92,7 +92,7 @@ GEM
sorbet-runtime (>= 0.5.9204)
unparser
rdiscount (2.2.0.2)
regexp_parser (2.5.0)
regexp_parser (2.6.0)
rexml (3.2.5)
ronn (0.7.3)
hpricot (>= 0.8.2)
@ -104,7 +104,7 @@ GEM
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
rspec-expectations (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-github (2.3.1)
@ -117,14 +117,14 @@ GEM
rspec-support (~> 3.11.0)
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-sorbet (1.8.3)
rspec-sorbet (1.9.0)
sorbet-runtime
rspec-support (3.11.0)
rspec-support (3.11.1)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
rspec_junit_formatter (0.5.1)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.35.0)
rubocop (1.35.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
@ -136,15 +136,15 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.21.0)
parser (>= 3.1.1.0)
rubocop-performance (1.14.3)
rubocop-performance (1.15.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.15.2)
rubocop-rails (2.16.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-rspec (2.12.1)
rubocop (~> 1.31)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
rubocop-sorbet (0.6.11)
rubocop (>= 0.90.0)
ruby-macho (3.0.0)
@ -170,7 +170,7 @@ GEM
sorbet (>= 0.5.9204)
sorbet-runtime (>= 0.5.9204)
thor (>= 0.19.2)
tapioca (0.7.2)
tapioca (0.7.3)
bundler (>= 1.17.3)
pry (>= 0.12.2)
rbi (~> 0.0.0, >= 0.0.14)
@ -185,7 +185,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.2.0)
unicode-display_width (2.3.0)
unparser (0.6.4)
diff-lcs (~> 1.3)
parser (>= 3.1.0)

View File

@ -26,22 +26,34 @@ module Homebrew
Homebrew::API.fetch "#{formula_api_path}/#{name}.json"
end
sig { returns(Array) }
sig { returns(Hash) }
def all_formulae
@all_formulae ||= begin
curl_args = %w[--compressed --silent https://formulae.brew.sh/api/formula.json]
if cached_formula_json_file.exist? && !cached_formula_json_file.empty?
curl_args.prepend("--time-cond", cached_formula_json_file)
end
curl_download(*curl_args, to: HOMEBREW_CACHE_API/"#{formula_api_path}.json", max_time: 5)
curl_download(*curl_args, to: cached_formula_json_file, max_time: 5)
json_formulae = JSON.parse(cached_formula_json_file.read)
@all_aliases = {}
json_formulae.to_h do |json_formula|
json_formula["aliases"].each do |alias_name|
@all_aliases[alias_name] = json_formula["name"]
end
[json_formula["name"], json_formula.except("name")]
end
end
end
sig { returns(Hash) }
def all_aliases
all_formulae if @all_aliases.blank?
@all_aliases
end
end
end
end

View File

@ -7,6 +7,7 @@ if ENV["HOMEBREW_STACKPROF"]
end
raise "HOMEBREW_BREW_FILE was not exported! Please call bin/brew directly!" unless ENV["HOMEBREW_BREW_FILE"]
raise "#{__FILE__} must not be loaded via `require`." if $PROGRAM_NAME != __FILE__
std_trap = trap("INT") { exit! 130 } # no backtrace thanks
@ -86,7 +87,8 @@ begin
end
if internal_cmd || Commands.external_ruby_v2_cmd_path(cmd)
if Commands::INSTALL_FROM_API_FORBIDDEN_COMMANDS.include?(cmd) && Homebrew::EnvConfig.install_from_api?
if Commands::INSTALL_FROM_API_FORBIDDEN_COMMANDS.include?(cmd) &&
Homebrew::EnvConfig.install_from_api? && !Homebrew::EnvConfig.developer?
odie "This command cannot be run while HOMEBREW_INSTALL_FROM_API is set!"
end
@ -120,8 +122,9 @@ begin
# Unset HOMEBREW_HELP to avoid confusing the tap
with_env HOMEBREW_HELP: nil do
tap_commands = []
cgroup = Utils.popen_read("cat", "/proc/1/cgroup")
if %w[azpl_job actions_job docker garden kubepods].none? { |container| cgroup.include?(container) }
if File.exist?("/.dockerenv") ||
((cgroup = Utils.popen_read("cat", "/proc/1/cgroup").presence) &&
%w[azpl_job actions_job docker garden kubepods].none? { |type| cgroup.include?(type) })
brew_uid = HOMEBREW_BREW_FILE.stat.uid
tap_commands += %W[/usr/bin/sudo -u ##{brew_uid}] if Process.uid.zero? && !brew_uid.zero?
end

View File

@ -197,6 +197,7 @@ check-run-command-as-root() {
[[ "$(id -u)" == 0 ]] || return
# Allow Azure Pipelines/GitHub Actions/Docker/Concourse/Kubernetes to do everything as root (as it's normal there)
[[ -f /.dockerenv ]] && return
[[ -f /proc/1/cgroup ]] && grep -E "azpl_job|actions_job|docker|garden|kubepods" -q /proc/1/cgroup && return
# Homebrew Services may need `sudo` for system-wide daemons.
@ -358,6 +359,18 @@ fi
##### Now, do everything else (that may be a bit slower).
#####
# Docker image deprecation
if [[ -f "${HOMEBREW_REPOSITORY}/.docker-deprecate" ]]
then
DOCKER_DEPRECATION_MESSAGE="$(cat "${HOMEBREW_REPOSITORY}/.docker-deprecate")"
if [[ -n "${GITHUB_ACTIONS}" ]]
then
echo "::warning::${DOCKER_DEPRECATION_MESSAGE}" >&2
else
opoo "${DOCKER_DEPRECATION_MESSAGE}"
fi
fi
# USER isn't always set so provide a fall back for `brew` and subprocesses.
export USER="${USER:-$(id -un)}"
@ -495,18 +508,9 @@ else
: "${HOMEBREW_OS_VERSION:=$(uname -r)}"
HOMEBREW_OS_USER_AGENT_VERSION="${HOMEBREW_OS_VERSION}"
# This is set by the user environment.
# shellcheck disable=SC2154
if [[ -n "${HOMEBREW_ON_DEBIAN7}" ]]
then
# Special version for our debian 7 docker container used to build binutils
HOMEBREW_MINIMUM_CURL_VERSION="7.25.0"
HOMEBREW_SYSTEM_CA_CERTIFICATES_TOO_OLD="1"
HOMEBREW_FORCE_BREWED_CA_CERTIFICATES="1"
else
# Ensure the system Curl is a version that supports modern HTTPS certificates.
HOMEBREW_MINIMUM_CURL_VERSION="7.41.0"
fi
# Ensure the system Curl is a version that supports modern HTTPS certificates.
HOMEBREW_MINIMUM_CURL_VERSION="7.41.0"
curl_version_output="$(${HOMEBREW_CURL} --version 2>/dev/null)"
curl_name_and_version="${curl_version_output%% (*}"
# shellcheck disable=SC2248

View File

@ -4,6 +4,8 @@
# This script is loaded by formula_installer as a separate instance.
# Thrown exceptions are propagated back to the parent process over a pipe
raise "#{__FILE__} must not be loaded via `require`." if $PROGRAM_NAME != __FILE__
old_trap = trap("INT") { exit! 130 }
require_relative "global"

View File

@ -33,9 +33,17 @@ class BuildEnvironment
module DSL
extend T::Sig
# Initialise @env for each class which may use this DSL (e.g. each formula subclass).
# `env` may never be called, and it needs to be initialised before the class is frozen.
def inherited(child)
super
child.instance_eval do
@env = BuildEnvironment.new
end
end
sig { params(settings: Symbol).returns(BuildEnvironment) }
def env(*settings)
@env ||= BuildEnvironment.new
@env.merge(settings)
end
end

View File

@ -1,6 +1,8 @@
# typed: false
# frozen_string_literal: true
require "active_support/core_ext/object/deep_dup"
module Cask
module Artifact
# Abstract superclass for all artifacts.
@ -127,8 +129,9 @@ module Cask
attr_reader :cask
def initialize(cask)
def initialize(cask, *dsl_args)
@cask = cask
@dsl_args = dsl_args.deep_dup
end
def config
@ -139,6 +142,10 @@ module Cask
def to_s
"#{summarize} (#{self.class.english_name})"
end
def to_args
@dsl_args.reject(&:blank?)
end
end
end
end

View File

@ -32,6 +32,10 @@ module Cask
abstract_phase(self.class.uninstall_dsl_key)
end
def summarize
directives.keys.map(&:to_s).join(", ")
end
private
def class_for_dsl_key(dsl_key)
@ -44,10 +48,6 @@ module Cask
class_for_dsl_key(dsl_key).new(cask).instance_eval(&block)
end
def summarize
directives.keys.map(&:to_s).join(", ")
end
end
end
end

View File

@ -40,7 +40,7 @@ module Cask
def initialize(cask, directives)
directives.assert_valid_keys!(*ORDERED_DIRECTIVES)
super(cask)
super(cask, **directives)
directives[:signal] = Array(directives[:signal]).flatten.each_slice(2).to_a
@directives = directives

View File

@ -72,7 +72,7 @@ module Cask
attr_reader :path, :args
def initialize(cask, **args)
super(cask)
super(cask, **args)
if args.key?(:manual)
@path = Pathname(args[:manual])

View File

@ -23,7 +23,7 @@ module Cask
end
def initialize(cask, path, **stanza_options)
super(cask)
super(cask, path, **stanza_options)
@path = cask.staged_path.join(path)
@stanza_options = stanza_options
end

View File

@ -42,12 +42,13 @@ module Cask
attr_reader :source, :target
sig {
params(cask: Cask, source: T.nilable(T.any(String, Pathname)), target: T.nilable(T.any(String, Pathname)))
params(cask: Cask, source: T.nilable(T.any(String, Pathname)), target_hash: T.any(String, Pathname))
.void
}
def initialize(cask, source, target: nil)
super(cask)
def initialize(cask, source, **target_hash)
super(cask, source, **target_hash)
target = target_hash[:target]
@source_string = source.to_s
@target_string = target.to_s
source = cask.staged_path.join(source)

View File

@ -14,7 +14,7 @@ module Cask
def self.from_args(cask, *args)
raise CaskInvalidError.new(cask.token, "'stage_only' takes only a single argument: true") if args != [true]
new(cask)
new(cask, true)
end
sig { returns(T::Array[T::Boolean]) }

View File

@ -24,7 +24,7 @@ module Cask
def initialize(cask, appcast: nil, download: nil, quarantine: nil,
token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil)
new_cask: nil, only: [], except: [])
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
online = new_cask if online.nil?
@ -47,44 +47,22 @@ module Cask
@signing = signing
@new_cask = new_cask
@token_conflicts = token_conflicts
@only = only
@except = except
end
def run!
check_denylist
check_reverse_migration
check_required_stanzas
check_version
check_sha256
check_desc
check_url
check_unnecessary_verified
check_missing_verified
check_no_match
check_generic_artifacts
check_token_valid
check_token_bad_words
check_token_conflicts
check_languages
check_download
check_https_availability
check_single_pre_postflight
check_single_uninstall_zap
check_untrusted_pkg
livecheck_result = check_livecheck_version
check_hosting_with_livecheck(livecheck_result: livecheck_result)
check_appcast_and_livecheck
check_latest_with_appcast_or_livecheck
check_latest_with_auto_updates
check_stanza_requires_uninstall
check_appcast_contains_version
check_gitlab_repository
check_gitlab_repository_archived
check_gitlab_prerelease_version
check_github_repository
check_github_repository_archived
check_github_prerelease_version
check_bitbucket_repository
check_signing
only_audits = @only
except_audits = @except
private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name|
name = audit_method_name.delete_prefix("check_")
next if !only_audits.empty? && only_audits&.exclude?(name)
next if except_audits&.include?(name)
send(audit_method_name)
end
self
rescue => e
odebug e, e.backtrace
@ -100,10 +78,27 @@ module Cask
@warnings ||= []
end
sig { returns(T::Boolean) }
def errors?
errors.any?
end
sig { returns(T::Boolean) }
def warnings?
warnings.any?
end
sig { returns(T::Boolean) }
def success?
!(errors? || warnings?)
end
sig { params(message: T.nilable(String), location: T.nilable(String)).void }
def add_error(message, location: nil)
errors << ({ message: message, location: location })
end
sig { params(message: T.nilable(String), location: T.nilable(String)).void }
def add_warning(message, location: nil)
if strict?
add_error message, location: location
@ -112,14 +107,6 @@ module Cask
end
end
def errors?
errors.any?
end
def warnings?
warnings.any?
end
def result
if errors?
Formatter.error("failed")
@ -150,12 +137,9 @@ module Cask
summary.join("\n")
end
def success?
!(errors? || warnings?)
end
private
sig { void }
def check_untrusted_pkg
odebug "Auditing pkg stanza: allow_untrusted"
@ -170,6 +154,7 @@ module Cask
add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
end
sig { void }
def check_stanza_requires_uninstall
odebug "Auditing stanzas which require an uninstall"
@ -179,6 +164,7 @@ module Cask
add_error "installer and pkg stanzas require an uninstall stanza"
end
sig { void }
def check_single_pre_postflight
odebug "Auditing preflight and postflight stanzas"
@ -195,6 +181,7 @@ module Cask
add_error "only a single postflight stanza is allowed"
end
sig { void }
def check_single_uninstall_zap
odebug "Auditing single uninstall_* and zap stanzas"
@ -221,6 +208,7 @@ module Cask
add_error "only a single zap stanza is allowed"
end
sig { void }
def check_required_stanzas
odebug "Auditing required stanzas"
[:version, :sha256, :url, :homepage].each do |sym|
@ -233,58 +221,66 @@ module Cask
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end
def check_version
return unless cask.version
sig { void }
def check_description_present
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
return if cask.tap == "homebrew/cask-fonts"
check_no_string_version_latest
add_warning "Cask should have a description. Please add a `desc` stanza." if cask.desc.blank?
end
sig { void }
def check_no_string_version_latest
odebug "Verifying version :latest does not appear as a string ('latest')"
return unless cask.version
odebug "Auditing version :latest does not appear as a string ('latest')"
return unless cask.version.raw_version == "latest"
add_error "you should use version :latest instead of version 'latest'"
end
def check_sha256
sig { void }
def check_sha256_no_check_if_latest
return unless cask.sha256
check_sha256_no_check_if_latest
check_sha256_no_check_if_unversioned
check_sha256_actually_256
check_sha256_invalid
end
def check_sha256_no_check_if_latest
odebug "Verifying sha256 :no_check with version :latest"
odebug "Auditing sha256 :no_check with version :latest"
return unless cask.version.latest?
return if cask.sha256 == :no_check
add_error "you should use sha256 :no_check when version is :latest"
end
sig { void }
def check_sha256_no_check_if_unversioned
return unless cask.sha256
return if cask.sha256 == :no_check
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
end
sig { void }
def check_sha256_actually_256
odebug "Verifying sha256 string is a legal SHA-256 digest"
return unless cask.sha256
odebug "Auditing sha256 string is a legal SHA-256 digest"
return unless cask.sha256.is_a?(Checksum)
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
add_error "sha256 string must be of 64 hexadecimal characters"
end
sig { void }
def check_sha256_invalid
odebug "Verifying sha256 is not a known invalid value"
return unless cask.sha256
odebug "Auditing sha256 is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless cask.sha256 == empty_sha256
add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
end
sig { void }
def check_appcast_and_livecheck
return unless cask.appcast
@ -295,6 +291,7 @@ module Cask
end
end
sig { void }
def check_latest_with_appcast_or_livecheck
return unless cask.version.latest?
@ -302,6 +299,7 @@ module Cask
add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
end
sig { void }
def check_latest_with_auto_updates
return unless cask.version.latest?
return unless cask.auto_updates
@ -311,7 +309,8 @@ module Cask
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
def check_hosting_with_livecheck(livecheck_result:)
sig { params(livecheck_result: T::Boolean).void }
def check_hosting_with_livecheck(livecheck_result: check_livecheck_version)
return if cask.discontinued? || cask.version.latest?
return if block_url_offline? || cask.appcast || cask.livecheckable?
return if livecheck_result == :auto_detected
@ -330,24 +329,12 @@ module Cask
end
end
def check_desc
return if cask.desc.present?
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
return if cask.tap == "homebrew/cask-fonts"
add_warning "Cask should have a description. Please add a `desc` stanza."
end
def check_url
return unless cask.url
check_download_url_format
end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
sig { void }
def check_download_url_format
return unless cask.url
odebug "Auditing URL format"
if bad_sourceforge_url?
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
@ -356,82 +343,9 @@ module Cask
end
end
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s.match?(regex)
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def bad_sourceforge_url?
bad_url_format?(/sourceforge/,
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
])
end
def bad_osdn_url?
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def homepage
URI(cask.homepage.to_s).host
end
def domain
URI(cask.url.to_s).host
end
def url_match_homepage?
host = cask.url.to_s
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
def strip_url_scheme(url)
url.sub(%r{^[^:/]+://(www\.)?}, "")
end
def url_from_verified
strip_url_scheme(cask.url.verified)
end
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
def verified_present?
cask.url.verified.present?
end
def file_url?
URI(cask.url.to_s).scheme == "file"
end
def block_url_offline?
return false if online?
cask.url.from_block?
end
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
sig { void }
def check_unnecessary_verified
return if block_url_offline?
return unless verified_present?
@ -443,6 +357,7 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end
sig { void }
def check_missing_verified
return if block_url_offline?
return if file_url?
@ -454,6 +369,7 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end
sig { void }
def check_no_match
return if block_url_offline?
return unless verified_present?
@ -464,6 +380,7 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end
sig { void }
def check_generic_artifacts
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
unless artifact.target.absolute?
@ -472,6 +389,7 @@ module Cask
end
end
sig { void }
def check_languages
@cask.languages.each do |language|
Locale.parse(language)
@ -480,6 +398,7 @@ module Cask
end
end
sig { void }
def check_token_conflicts
return unless token_conflicts?
return unless core_formula_names.include?(cask.token)
@ -488,6 +407,7 @@ module Cask
"#{Formatter.url(core_formula_url)}"
end
sig { void }
def check_token_valid
add_error "cask token contains non-ascii characters" unless cask.token.ascii_only?
add_error "cask token + should be replaced by -plus-" if cask.token.include? "+"
@ -505,6 +425,7 @@ module Cask
add_error "cask token should not have leading or trailing hyphens"
end
sig { void }
def check_token_bad_words
return unless new_cask?
@ -532,19 +453,7 @@ module Cask
add_warning "cask token mentions framework"
end
def core_tap
@core_tap ||= CoreTap.instance
end
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end
sig { void }
def check_download
return if download.blank? || cask.url.blank?
@ -554,11 +463,11 @@ module Cask
add_error "download not possible: #{e}"
end
sig { void }
def check_signing
return if !signing? || download.blank? || cask.url.blank?
odebug "Auditing signing"
odebug cask.artifacts
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
return if artifacts.empty?
@ -571,16 +480,34 @@ module Cask
Dir.mktmpdir do |tmpdir|
tmpdir = Pathname(tmpdir)
primary_container.extract_nestedly(to: tmpdir, basename: downloaded_path.basename, verbose: false)
cask.artifacts.each do |artifact|
result = system_command("codesign", args: [
"--verify",
tmpdir/artifact.source.basename,
], print_stderr: false)
add_warning result.merged_output unless result.success?
artifacts.each do |artifact|
path = case artifact
when Artifact::Moved
tmpdir/artifact.source.basename
when Artifact::Pkg
artifact.path
end
next unless path.exist?
result = system_command("codesign", args: ["--verify", path], print_stderr: false)
next if result.success?
message = "Signature verification failed:\n#{result.merged_output}\nmacOS on ARM requires applications " \
"to be signed. Please contact the upstream developer to let them know they should "
message += if result.stderr.include?("not signed at all")
"sign their app."
else
"fix the signature of their app."
end
add_warning message
end
end
end
sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
def check_livecheck_version
return unless appcast?
@ -618,6 +545,7 @@ module Cask
false
end
sig { void }
def check_appcast_contains_version
return unless appcast?
return if cask.appcast.to_s.empty?
@ -644,6 +572,7 @@ module Cask
EOS
end
sig { void }
def check_github_prerelease_version
return if cask.tap == "homebrew/cask-versions"
@ -657,6 +586,7 @@ module Cask
add_error error if error
end
sig { void }
def check_gitlab_prerelease_version
return if cask.tap == "homebrew/cask-versions"
@ -671,6 +601,7 @@ module Cask
add_error error if error
end
sig { void }
def check_github_repository_archived
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil?
@ -691,6 +622,7 @@ module Cask
end
end
sig { void }
def check_gitlab_repository_archived
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil?
@ -711,6 +643,7 @@ module Cask
end
end
sig { void }
def check_github_repository
return unless new_cask?
@ -723,6 +656,7 @@ module Cask
add_error error if error
end
sig { void }
def check_gitlab_repository
return unless new_cask?
@ -735,6 +669,7 @@ module Cask
add_error error if error
end
sig { void }
def check_bitbucket_repository
return unless new_cask?
@ -747,6 +682,62 @@ module Cask
add_error error if error
end
sig { void }
def check_denylist
return unless cask.tap
return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
add_error "#{cask.token} is not allowed: #{reason}"
end
sig { void }
def check_reverse_migration
return unless new_cask?
return unless cask.tap
return unless cask.tap.official?
return unless cask.tap.tap_migrations.key?(cask.token)
add_error "#{cask.token} is listed in tap_migrations.json"
end
sig { void }
def check_https_availability
return unless download
if cask.url && !cask.url.using
validate_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
user_agents: [cask.url.user_agent])
end
if cask.appcast && appcast?
validate_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
end
return unless cask.homepage
validate_url_for_https_availability(cask.homepage, SharedAudits::URL_TYPE_HOMEPAGE, cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end
# sig {
# params(url_to_check: T.any(String, URL), url_type: String, cask_token: String, tap: Tap,
# options: T.untyped).void
# }
def validate_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
if problem
add_error problem unless exception
elsif exception
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
end
end
sig { params(regex: T.any(String, Regexp)).returns(T.nilable(T::Array[String])) }
def get_repo_data(regex)
return unless online?
@ -760,52 +751,110 @@ module Cask
[user, repo]
end
def check_denylist
return unless cask.tap
return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
sig {
params(regex: T.any(String, Regexp), valid_formats_array: T::Array[T.any(String, Regexp)]).returns(T::Boolean)
}
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s.match?(regex)
add_error "#{cask.token} is not allowed: #{reason}"
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def check_reverse_migration
return unless new_cask?
return unless cask.tap
return unless cask.tap.official?
return unless cask.tap.tap_migrations.key?(cask.token)
add_error "#{cask.token} is listed in tap_migrations.json"
sig { returns(T::Boolean) }
def bad_sourceforge_url?
bad_url_format?(/sourceforge/,
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
])
end
def check_https_availability
return unless download
if cask.url && !cask.url.using
check_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
user_agents: [cask.url.user_agent])
end
if cask.appcast && appcast?
check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
end
return unless cask.homepage
check_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
sig { returns(T::Boolean) }
def bad_osdn_url?
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def check_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
# sig { returns(String) }
def homepage
URI(cask.homepage.to_s).host
end
if problem
add_error problem unless exception
elsif exception
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
# sig { returns(String) }
def domain
URI(cask.url.to_s).host
end
sig { returns(T::Boolean) }
def url_match_homepage?
host = cask.url.to_s
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
return false if homepage.blank?
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
# sig { params(url: String).returns(String) }
def strip_url_scheme(url)
url.sub(%r{^[^:/]+://(www\.)?}, "")
end
# sig { returns(String) }
def url_from_verified
strip_url_scheme(cask.url.verified)
end
sig { returns(T::Boolean) }
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
sig { returns(T::Boolean) }
def verified_present?
cask.url.verified.present?
end
sig { returns(T::Boolean) }
def file_url?
URI(cask.url.to_s).scheme == "file"
end
sig { returns(T::Boolean) }
def block_url_offline?
return false if online?
cask.url.from_block?
end
sig { returns(Tap) }
def core_tap
@core_tap ||= CoreTap.instance
end
# sig { returns(T::Array[String]) }
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end
end
end

View File

@ -144,7 +144,6 @@ module Cask
quarantine: @quarantine,
)
audit.run!
audit
end
def language_blocks

View File

@ -24,6 +24,11 @@ module Cask
attr_accessor :download, :allow_reassignment
def self.all
# TODO: uncomment for 3.7.0 and ideally avoid using ARGV by moving to e.g. CLI::Parser
# if !ARGV.include?("--eval-all") && !Homebrew::EnvConfig.eval_all?
# odeprecated "Cask::Cask#all without --all or HOMEBREW_EVAL_ALL"
# end
Tap.flat_map(&:cask_files).map do |f|
CaskLoader::FromTapPathLoader.new(f).load(config: nil)
rescue CaskUnreadableError => e
@ -235,7 +240,7 @@ module Cask
"installed" => versions.last,
"outdated" => outdated?,
"sha256" => sha256,
"artifacts" => artifacts.map(&method(:to_h_gsubs)),
"artifacts" => artifacts_list,
"caveats" => (to_h_string_gsubs(caveats) unless caveats.empty?),
"depends_on" => depends_on,
"conflicts_with" => conflicts_with,
@ -281,6 +286,18 @@ module Cask
private
def artifacts_list
artifacts.map do |artifact|
key, value = if artifact.is_a? Artifact::AbstractFlightBlock
artifact.summarize
else
[artifact.class.dsl_key, to_h_gsubs(artifact.to_args)]
end
{ key => value }
end
end
def to_h_string_gsubs(string)
string.to_s
.gsub(Dir.home, "$HOME")

View File

@ -230,8 +230,13 @@ module Cask
end
# @api public
def sha256(arg = nil)
set_unique_stanza(:sha256, arg.nil?) do
def sha256(arg = nil, arm: nil, intel: nil)
should_return = arg.nil? && arm.nil? && intel.nil?
set_unique_stanza(:sha256, should_return) do
@on_system_blocks_exist = true if arm.present? || intel.present?
arg ||= on_arch_conditional(arm: arm, intel: intel)
case arg
when :no_check
arg
@ -245,7 +250,7 @@ module Cask
# @api public
def arch(arm: nil, intel: nil)
should_return = arm.blank? && intel.blank?
should_return = arm.nil? && intel.nil?
set_unique_stanza(:arch, should_return) do
@on_system_blocks_exist = true

View File

@ -150,20 +150,6 @@ module Cask
version { split(",", 2).second }
end
# @api public
sig { returns(T.self_type) }
def before_colon
odisabled "Cask::DSL::Version#before_colon", "Cask::DSL::Version#csv"
version { split(":", 2).first }
end
# @api public
sig { returns(T.self_type) }
def after_colon
odisabled "Cask::DSL::Version#after_colon", "Cask::DSL::Version#csv"
version { split(":", 2).second }
end
# @api public
sig { returns(T.self_type) }
def no_dividers

View File

@ -1,8 +1,17 @@
# typed: true
# frozen_string_literal: true
require "requirement"
# An adapter for casks to provide dependency information in a formula-like interface.
class CaskDependent
# Defines a dependency on another cask
class Requirement < ::Requirement
satisfy(build_env: false) do
Cask::CaskLoader.load(cask).installed?
end
end
attr_reader :cask
def initialize(cask)
@ -33,11 +42,21 @@ class CaskDependent
dsl_reqs = @cask.depends_on
dsl_reqs.arch&.each do |arch|
requirements << ArchRequirement.new([:x86_64]) if arch[:bits] == 64
requirements << ArchRequirement.new([arch[:type]])
arch = if arch[:bits] == 64
if arch[:type] == :intel
:x86_64
else
:"#{arch[:type]}64"
end
elsif arch[:type] == :intel && arch[:bits] == 32
:i386
else
arch[:type]
end
requirements << ArchRequirement.new([arch])
end
dsl_reqs.cask.each do |cask_ref|
requirements << Requirement.new([{ cask: cask_ref }])
requirements << CaskDependent::Requirement.new([{ cask: cask_ref }])
end
requirements << dsl_reqs.macos if dsl_reqs.macos

View File

@ -189,8 +189,8 @@ module Homebrew
def self.skip_clean_formula?(f)
return false if Homebrew::EnvConfig.no_cleanup_formulae.blank?
skip_clean_formulae = Homebrew::EnvConfig.no_cleanup_formulae.split(",")
skip_clean_formulae.include?(f.name) || (skip_clean_formulae & f.aliases).present?
@skip_clean_formulae ||= Homebrew::EnvConfig.no_cleanup_formulae.split(",")
@skip_clean_formulae.include?(f.name) || (@skip_clean_formulae & f.aliases).present?
end
def self.periodic_clean_due?
@ -235,6 +235,7 @@ module Homebrew
cleanup_cache
cleanup_logs
cleanup_lockfiles
cleanup_python_site_packages
prune_prefix_symlinks_and_directories
unless dry_run?
@ -428,7 +429,6 @@ module Homebrew
next if !use_system_ruby && portable_ruby_latest_version == path.basename.to_s
portable_rubies_to_remove << path
puts "Would remove: #{path} (#{path.abv})" if dry_run?
end
return if portable_rubies_to_remove.empty?
@ -440,20 +440,16 @@ module Homebrew
puts Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "clean", "-ffqx", bundler_path).chomp
end
return if dry_run?
FileUtils.rm_rf portable_rubies_to_remove
portable_rubies_to_remove.each do |portable_ruby|
cleanup_path(portable_ruby) { portable_ruby.rmtree }
end
end
def cleanup_bootsnap
bootsnap = cache/"bootsnap"
return unless bootsnap.exist?
if dry_run?
puts "Would remove: #{bootsnap} (#{bootsnap.abv})"
else
FileUtils.rm_rf bootsnap
end
cleanup_path(bootsnap) { bootsnap.rmtree }
end
def cleanup_cache_db(rack = nil)
@ -489,6 +485,55 @@ module Homebrew
end
end
def cleanup_python_site_packages
pyc_files = Hash.new { |h, k| h[k] = [] }
seen_non_pyc_file = Hash.new { |h, k| h[k] = false }
unused_pyc_files = []
HOMEBREW_PREFIX.glob("lib/python*/site-packages").each do |site_packages|
site_packages.each_child do |child|
next unless child.directory?
# TODO: Work out a sensible way to clean up pip's, setuptools', and wheel's
# {dist,site}-info directories. Alternatively, consider always removing
# all `-info` directories, because we may not be making use of them.
next if child.basename.to_s.end_with?("-info")
# Clean up old *.pyc files in the top-level __pycache__.
if child.basename.to_s == "__pycache__"
child.find do |path|
next unless path.extname == ".pyc"
next unless path.prune?(days)
unused_pyc_files << path
end
next
end
# Look for directories that contain only *.pyc files.
child.find do |path|
next if path.directory?
if path.extname == ".pyc"
pyc_files[child] << path
else
seen_non_pyc_file[child] = true
break
end
end
end
end
unused_pyc_files += pyc_files.reject { |k,| seen_non_pyc_file[k] }
.values
.flatten
return if unused_pyc_files.blank?
unused_pyc_files.each do |pyc|
cleanup_path(pyc) { pyc.unlink }
end
end
def prune_prefix_symlinks_and_directories
ObserverPathnameExtension.reset_counts!
@ -534,17 +579,22 @@ module Homebrew
end
def self.autoremove(dry_run: false)
require "utils/autoremove"
require "cask/caskroom"
# If this runs after install, uninstall, reinstall or upgrade,
# the cache of installed formulae may no longer be valid.
Formula.clear_cache unless dry_run
# Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE.
formulae = Formula.installed.reject(&method(:skip_clean_formula?))
formulae = Formula.installed
# Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE and their dependencies.
if Homebrew::EnvConfig.no_cleanup_formulae.present?
formulae -= formulae.select(&method(:skip_clean_formula?))
.flat_map { |f| [f, *f.runtime_formula_dependencies] }
end
casks = Cask::Caskroom.casks
removable_formulae = Formula.unused_formulae_with_no_dependents(formulae, casks)
removable_formulae = Utils::Autoremove.removable_formulae(formulae, casks)
return if removable_formulae.blank?

View File

@ -30,6 +30,9 @@ module Homebrew
sig { returns(T::Boolean) }
def newer_only?; end
sig { returns(T::Boolean) }
def resources?; end
sig { returns(T::Boolean) }
def full_name?; end
@ -132,6 +135,9 @@ module Homebrew
sig { returns(T::Boolean) }
def all?; end
sig { returns(T::Boolean) }
def eval_all?; end
sig { returns(T::Boolean) }
def full?; end

View File

@ -22,10 +22,10 @@ module Homebrew
may be appended to the command. When given multiple formula arguments,
show the intersection of dependencies for each formula.
EOS
switch "-n",
switch "-n", "--topological",
description: "Sort dependencies in topological order."
switch "--1",
description: "Only show dependencies one level down, instead of recursing."
switch "-1", "--direct", "--declared", "--1",
description: "Show only the direct dependencies declared in the formula."
switch "--union",
description: "Show the union of dependencies for multiple <formula>, instead of the intersection."
switch "--full-name",
@ -54,20 +54,22 @@ module Homebrew
switch "--installed",
description: "List dependencies for formulae that are currently installed. If <formula> is " \
"specified, list only its dependencies that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to list " \
"their dependencies."
switch "--all",
description: "List dependencies for all available formulae."
hidden: true
switch "--for-each",
description: "Switch into the mode used by the `--all` option, but only list dependencies " \
"for each provided <formula>, one formula per line. This is used for " \
"debugging the `--installed`/`--all` display mode."
switch "--formula", "--formulae",
depends_on: "--installed",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
depends_on: "--installed",
description: "Treat all named arguments as casks."
conflicts "--tree", "--graph"
conflicts "--installed", "--eval-all"
conflicts "--installed", "--all"
conflicts "--formula", "--cask"
formula_options
@ -79,9 +81,18 @@ module Homebrew
def deps
args = deps_args.parse
all = args.eval_all?
if args.all?
unless all
odeprecated "brew deps --all",
"brew deps --eval-all or HOMEBREW_EVAL_ALL"
end
all = true
end
Formulary.enable_factory_cache!
recursive = !args.send(:"1?")
recursive = !args.direct?
installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?)
@use_runtime_dependencies = installed && recursive &&
@ -120,7 +131,7 @@ module Homebrew
puts_deps_tree dependents, recursive: recursive, args: args
return
elsif args.all?
elsif all
puts_deps sorted_dependents(Formula.all + Cask::Cask.all), recursive: recursive, args: args
return
elsif !args.no_named? && args.for_each?
@ -149,7 +160,7 @@ module Homebrew
condense_requirements(all_deps, args: args)
all_deps.map! { |d| dep_display_name(d, args: args) }
all_deps.uniq!
all_deps.sort! unless args.n?
all_deps.sort! unless args.topological?
puts all_deps
end
@ -286,8 +297,14 @@ module Homebrew
end
display_s = "#{tree_lines} #{dep_display_name(dep, args: args)}"
# Detect circular dependencies and consider them a failure if present.
is_circular = dep_stack.include?(dep.name)
display_s = "#{display_s} (CIRCULAR DEPENDENCY)" if is_circular
if is_circular
display_s = "#{display_s} (CIRCULAR DEPENDENCY)"
Homebrew.failed = true
end
puts "#{prefix}#{display_s}"
next if !recursive || is_circular

View File

@ -18,8 +18,7 @@ module Homebrew
Homebrew::CLI::Parser.new do
description <<~EOS
Display <formula>'s name and one-line description.
Formula descriptions are cached; the cache is created on the
first search, making that search slower than subsequent ones.
The cache is created on the first search, making that search slower than subsequent ones.
EOS
switch "-s", "--search",
description: "Search both names and descriptions for <text>. If <text> is flanked by " \
@ -30,6 +29,9 @@ module Homebrew
switch "-d", "--description",
description: "Search just descriptions for <text>. If <text> is flanked by slashes, " \
"it is interpreted as a regular expression."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if HOMEBREW_EVAL_ALL is set."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
@ -44,6 +46,10 @@ module Homebrew
def desc
args = desc_args.parse
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew desc", "brew desc --eval-all or HOMEBREW_EVAL_ALL"
end
search_type = if args.search?
:either
elsif args.name?

View File

@ -0,0 +1,24 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
module Homebrew
extend T::Sig
module_function
sig { returns(CLI::Parser) }
def docs_args
Homebrew::CLI::Parser.new do
description <<~EOS
Open Homebrew's online documentation (#{HOMEBREW_DOCS_WWW}) in a browser.
EOS
end
end
sig { void }
def docs
exec_browser HOMEBREW_DOCS_WWW
end
end

View File

@ -13,6 +13,8 @@ module Homebrew
module_function
FETCH_MAX_TRIES = 5
sig { returns(CLI::Parser) }
def fetch_args
Homebrew::CLI::Parser.new do
@ -31,7 +33,8 @@ module Homebrew
"seeing if an existing VCS cache has been updated."
switch "--retry",
description: "Retry if downloading fails or re-download if the checksum of a previously cached " \
"version no longer matches."
"version no longer matches. Tries at most #{FETCH_MAX_TRIES} times with " \
"exponential backoff."
switch "--deps",
description: "Also download dependencies for any listed <formula>."
switch "-s", "--build-from-source",
@ -159,10 +162,17 @@ module Homebrew
end
def retry_fetch?(f, args:)
@fetch_failed ||= Set.new
if args.retry? && @fetch_failed.add?(f)
ohai "Retrying download"
@fetch_tries ||= Hash.new { |h, k| h[k] = 1 }
if args.retry? && (@fetch_tries[f] < FETCH_MAX_TRIES)
wait = 2 ** @fetch_tries[f]
remaining = FETCH_MAX_TRIES - @fetch_tries[f]
what = "try".pluralize(remaining)
ohai "Retrying download in #{wait}s... (#{remaining} #{what} left)"
sleep wait
f.clear_cache
@fetch_tries[f] += 1
true
else
Homebrew.failed = true

View File

@ -58,9 +58,13 @@ module Homebrew
switch "--installed",
depends_on: "--json",
description: "Print JSON of formulae that are currently installed."
switch "--all",
switch "--eval-all",
depends_on: "--json",
description: "Print JSON of all available formulae."
description: "Evaluate all available formulae and casks, whether installed or not, to print their " \
"JSON. Implied if HOMEBREW_EVAL_ALL is set."
switch "--all",
hidden: true,
depends_on: "--json"
switch "--variations",
depends_on: "--json",
description: "Include the variations hash in each formula's JSON output."
@ -71,6 +75,7 @@ module Homebrew
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--installed", "--eval-all"
conflicts "--installed", "--all"
conflicts "--formula", "--cask"
@ -103,7 +108,13 @@ module Homebrew
print_analytics(args: args)
elsif args.json
print_json(args: args)
all = args.eval_all?
if !all && args.all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew info --all", "brew info --eval-all or HOMEBREW_EVAL_ALL"
all = true
end
print_json(all, args: args)
elsif args.github?
raise FormulaOrCaskUnspecifiedError if args.no_named?
@ -187,15 +198,15 @@ module Homebrew
version_hash[version]
end
sig { params(args: CLI::Args).void }
def print_json(args:)
raise FormulaOrCaskUnspecifiedError if !(args.all? || args.installed?) && args.no_named?
sig { params(all: T::Boolean, args: CLI::Args).void }
def print_json(all, args:)
raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named?
json = case json_version(args.json)
when :v1, :default
raise UsageError, "cannot specify --cask with --json=v1!" if args.cask?
formulae = if args.all?
formulae = if all
Formula.all.sort
elsif args.installed?
Formula.installed.sort
@ -211,7 +222,7 @@ module Homebrew
formulae.map(&:to_hash)
end
when :v2
formulae, casks = if args.all?
formulae, casks = if all
[Formula.all.sort, Cask::Cask.all.sort_by(&:full_name)]
elsif args.installed?
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]

View File

@ -37,7 +37,7 @@ module Homebrew
def leaves
args = leaves_args.parse
leaves_list = Formula.formulae_with_no_formula_dependents(Formula.installed)
leaves_list = Formula.installed - Formula.installed.flat_map(&:runtime_formula_dependencies)
leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request?
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?

View File

@ -70,14 +70,6 @@ module Homebrew
def list
args = list_args.parse
# Unbrewed uses the PREFIX, which will exist
# Things below use the CELLAR, which doesn't until the first formula is installed.
unless HOMEBREW_CELLAR.exist?
raise NoSuchKegError, args.named.first if args.named.present? && !args.cask?
return
end
if args.full_name?
unless args.cask?
formula_names = args.no_named? ? Formula.installed : args.named.to_resolved_formulae
@ -112,12 +104,10 @@ module Homebrew
if !args.cask? && HOMEBREW_CELLAR.exist? && HOMEBREW_CELLAR.children.any?
ohai "Formulae" if $stdout.tty? && !args.formula?
safe_system "ls", *ls_args, HOMEBREW_CELLAR
puts if $stdout.tty? && !args.formula?
end
if !args.formula? && Cask::Caskroom.any_casks_installed?
if $stdout.tty? && !args.cask?
puts
ohai "Casks"
end
ohai "Casks" if $stdout.tty? && !args.cask?
safe_system "ls", *ls_args, Cask::Caskroom.path
end
else

View File

@ -19,8 +19,11 @@ module Homebrew
description: "Show all options on a single line separated by spaces."
switch "--installed",
description: "Show options for formulae that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show their " \
"options."
switch "--all",
description: "Show options for all available formulae."
hidden: true
flag "--command=",
description: "Show options for the specified <command>."
@ -33,7 +36,13 @@ module Homebrew
def options
args = options_args.parse
all = args.eval_all?
if args.all?
odeprecated "brew info --all", "brew info --eval-all" if !all && !Homebrew::EnvConfig.eval_all?
all = true
end
if all
puts_options Formula.all.sort, args: args
elsif args.installed?
puts_options Formula.installed.sort, args: args

View File

@ -3,6 +3,7 @@
require "readall"
require "cli/parser"
require "env_config"
module Homebrew
extend T::Sig
@ -22,6 +23,9 @@ module Homebrew
description: "Verify any alias symlinks in each tap."
switch "--syntax",
description: "Syntax-check all of Homebrew's Ruby files (if no `<tap>` is passed)."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not. " \
"Implied if HOMEBREW_EVAL_ALL is set."
named_args :tap
end
@ -39,6 +43,9 @@ module Homebrew
options = { aliases: args.aliases? }
taps = if args.no_named?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew readall", "brew readall --eval-all or HOMEBREW_EVAL_ALL"
end
Tap
else
args.named.to_installed_taps

View File

@ -44,6 +44,10 @@ module Homebrew
switch "--desc",
description: "Search for formulae with a description matching <text> and casks with " \
"a name or description matching <text>."
switch "--eval-all",
depends_on: "--desc",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if HOMEBREW_EVAL_ALL is set."
switch "--pull-request",
description: "Search for GitHub pull requests containing <text>."
switch "--open",
@ -75,6 +79,9 @@ module Homebrew
string_or_regex = query_regexp(query)
if args.desc?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew search --desc", "brew search --desc --eval-all or HOMEBREW_EVAL_ALL"
end
search_descriptions(string_or_regex, args)
elsif args.pull_request?
search_pull_requests(query, args)

View File

@ -9,6 +9,8 @@
# HOMEBREW_CELLAR and HOMEBREW_PREFIX are set by extend/ENV/super.rb
# HOMEBREW_REPOSITORY is set by bin/brew
# Trailing colon in MANPATH adds default man dirs to search path in Linux, does no harm in macOS.
# Please do not submit PRs to remove it!
# shellcheck disable=SC2154
homebrew-shellenv() {
if [[ "${HOMEBREW_PATH%%:"${HOMEBREW_PREFIX}"/sbin*}" == "${HOMEBREW_PREFIX}/bin" ]]

View File

@ -43,6 +43,9 @@ module Homebrew
description: "Migrate tapped formulae from symlink-based to directory-based structure."
switch "--list-pinned",
description: "List all pinned taps."
switch "--eval-all",
description: "Evaluate all the formulae, casks and aliases in the new tap to check validity. " \
"Implied if HOMEBREW_EVAL_ALL is set."
named_args :tap, max: 2
end
@ -65,7 +68,8 @@ module Homebrew
tap.install clone_target: args.named.second,
force_auto_update: args.force_auto_update?,
custom_remote: args.custom_remote?,
quiet: args.quiet?
quiet: args.quiet?,
verify: args.eval_all? || Homebrew::EnvConfig.eval_all?
rescue TapRemoteMismatchError, TapNoCustomRemoteError => e
odie e
rescue TapAlreadyTappedError

View File

@ -547,7 +547,7 @@ EOS
for DIR in "${HOMEBREW_REPOSITORY}" "${HOMEBREW_LIBRARY}"/Taps/*/*
do
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" ]] &&
[[ -n "${HOMEBREW_UPDATE_AUTO}" ]] &&
[[ -z "${HOMEBREW_DEVELOPER}" || -n "${HOMEBREW_UPDATE_AUTO}" ]] &&
[[ "${DIR}" == "${HOMEBREW_CORE_REPOSITORY}" ]]
then
continue
@ -705,6 +705,7 @@ EOS
for DIR in "${HOMEBREW_REPOSITORY}" "${HOMEBREW_LIBRARY}"/Taps/*/*
do
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" ]] &&
[[ -z "${HOMEBREW_DEVELOPER}" || -n "${HOMEBREW_UPDATE_AUTO}" ]] &&
[[ "${DIR}" == "${HOMEBREW_CORE_REPOSITORY}" ||
"${DIR}" == "${HOMEBREW_LIBRARY}/Taps/homebrew/homebrew-cask" ]]
then
@ -748,11 +749,11 @@ EOS
if [[ -n "${HOMEBREW_INSTALL_FROM_API}" ]]
then
mkdir -p "${HOMEBREW_CACHE}/api"
# TODO: use --header If-Modified-Since
curl \
"${CURL_DISABLE_CURLRC_ARGS[@]}" \
--fail --compressed --silent --max-time 5 \
--location --remote-time --output "${HOMEBREW_CACHE}/api/formula.json" \
--time-cond "${HOMEBREW_CACHE}/api/formula.json" \
--user-agent "${HOMEBREW_USER_AGENT_CURL}" \
"https://formulae.brew.sh/api/formula.json"
# TODO: we probably want to print an error if this fails.

View File

@ -30,9 +30,11 @@ module Homebrew
description: "Resolve more than one level of dependencies."
switch "--installed",
description: "Only list formulae and casks that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show " \
"their dependents."
switch "--all",
description: "List all formulae and casks whether installed or not.",
hidden: true
hidden: true
switch "--include-build",
description: "Include all formulae that specify <formula> as `:build` type dependency."
switch "--include-test",
@ -88,8 +90,6 @@ module Homebrew
show_formulae_and_casks = !args.formula? && !args.cask?
includes, ignores = args_includes_ignores(args)
# TODO: 3.6.0: odeprecate not specifying args.all?, require args.installed?
deps = []
if use_runtime_dependents
if show_formulae_and_casks || args.formula?
@ -106,6 +106,18 @@ module Homebrew
deps
else
all = args.eval_all?
if args.all?
unless all
odeprecated "brew uses --all",
"brew uses --eval-all or HOMEBREW_EVAL_ALL"
end
all = true
end
if !args.installed? && !(all || Homebrew::EnvConfig.eval_all?)
odeprecated "brew uses", "brew uses --eval-all or HOMEBREW_EVAL_ALL"
end
if show_formulae_and_casks || args.formula?
deps += args.installed? ? Formula.installed : Formula.all
end

View File

@ -1,21 +1,2 @@
# typed: true
# frozen_string_literal: true
module Cask
class Cask
extend Enumerable
def self.each(&block)
odisabled "`Enumerable` methods on `Cask::Cask`",
"`Cask::Cask.all` (but avoid looping over all casks, it's slow and insecure)"
return to_enum unless block
Tap.flat_map(&:cask_files).each do |f|
yield CaskLoader::FromTapPathLoader.new(f).load(config: nil)
rescue CaskUnreadableError => e
opoo e.message
end
end
end
end

View File

@ -1,20 +1,2 @@
# typed: true
# frozen_string_literal: true
class Formula
extend Enumerable
def self.each(&_block)
odisabled "`Enumerable` methods on `Formula`",
"`Formula.all` (but avoid looping over all formulae, it's slow and insecure)"
files.each do |file|
yield Formulary.factory(file)
rescue FormulaUnavailableError, FormulaUnreadableError => e
# Don't let one broken formula break commands. But do complain.
onoe "Failed to import: #{file}"
$stderr.puts e
next
end
end
end

View File

@ -172,7 +172,7 @@ class CompilerSelector
def compiler_version(name)
case name.to_s
when "gcc", GNU_GCC_REGEXP
versions.non_apple_gcc_version(name.to_s)
versions.gcc_version(name.to_s)
else
versions.send("#{name}_build_version")
end

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true
require "delegate"
require "cask_dependent"
# A collection of dependencies.
#

View File

@ -26,8 +26,23 @@ class DependencyCollector
sig { void }
def initialize
# Ensure this is synced with `initialize_dup` and `freeze` (excluding simple objects like integers and booleans)
@deps = Dependencies.new
@requirements = Requirements.new
init_global_dep_tree_if_needed!
end
def initialize_dup(other)
super
@deps = @deps.dup
@requirements = @requirements.dup
end
def freeze
@deps.freeze
@requirements.freeze
super
end
def add(spec)
@ -36,6 +51,11 @@ class DependencyCollector
@deps << dep
when Requirement
@requirements << dep
when nil
# no-op when we have a nil value
nil
else
raise ArgumentError, "DependencyCollector#add passed something that isn't a Dependency or Requirement!"
end
dep
end
@ -57,13 +77,19 @@ class DependencyCollector
parse_spec(spec, Array(tags))
end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
def gcc_dep_if_needed(related_formula_names); end
sig { params(related_formula_names: T::Array[String]).returns(T.nilable(Dependency)) }
def glibc_dep_if_needed(related_formula_names); end
def git_dep_if_needed(tags)
return if Utils::Git.available?
Dependency.new("git", tags)
end
def brewed_curl_dep_if_needed(tags)
def curl_dep_if_needed(tags)
Dependency.new("curl", tags)
end
@ -99,6 +125,9 @@ class DependencyCollector
private
sig { void }
def init_global_dep_tree_if_needed!; end
def parse_spec(spec, tags)
case spec
when String
@ -148,7 +177,7 @@ class DependencyCollector
strategy = spec.download_strategy
if strategy <= HomebrewCurlDownloadStrategy
@deps << brewed_curl_dep_if_needed(tags)
@deps << curl_dep_if_needed(tags)
parse_url_spec(spec.url, tags)
elsif strategy <= CurlDownloadStrategy
parse_url_spec(spec.url, tags)

View File

@ -34,9 +34,9 @@ class DescriptionCacheStore < CacheStore
#
# @return [nil]
def populate_if_empty!
return unless Homebrew::EnvConfig.eval_all?
return unless database.empty?
# TODO: 3.6.0: consider if we want to actually read all contents of all formulae or odeprecate.
Formula.all.each { |f| update!(f.full_name, f.desc) }
end
@ -45,6 +45,7 @@ class DescriptionCacheStore < CacheStore
# @param report [Report] an update report generated by cmd/update.rb
# @return [nil]
def update_from_report!(report)
return unless Homebrew::EnvConfig.eval_all?
return populate_if_empty! if database.empty?
return if report.empty?
@ -63,6 +64,7 @@ class DescriptionCacheStore < CacheStore
# @param formula_names [Array] the formulae to update
# @return [nil]
def update_from_formula_names!(formula_names)
return unless Homebrew::EnvConfig.eval_all?
return populate_if_empty! if database.empty?
formula_names.each do |name|
@ -100,9 +102,9 @@ class CaskDescriptionCacheStore < DescriptionCacheStore
#
# @return [nil]
def populate_if_empty!
return unless Homebrew::EnvConfig.eval_all?
return unless database.empty?
# TODO: 3.6.0: consider if we want to actually read all contents of all casks or odeprecate.
Cask::Cask.all.each { |c| update!(c.full_name, [c.name.join(", "), c.desc.presence]) }
end
@ -111,6 +113,7 @@ class CaskDescriptionCacheStore < DescriptionCacheStore
# @param report [Report] an update report generated by cmd/update.rb
# @return [nil]
def update_from_report!(report)
return unless Homebrew::EnvConfig.eval_all?
return populate_if_empty! if database.empty?
return if report.empty?
@ -126,6 +129,7 @@ class CaskDescriptionCacheStore < DescriptionCacheStore
# @param cask_tokens [Array] the casks to update
# @return [nil]
def update_from_cask_tokens!(cask_tokens)
return unless Homebrew::EnvConfig.eval_all?
return populate_if_empty! if database.empty?
cask_tokens.each do |token|

View File

@ -41,9 +41,11 @@ module Homebrew
description: "Run additional, slower style checks that require a network connection."
switch "--installed",
description: "Only check formulae and casks that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to audit them. " \
"Implied if HOMEBREW_EVAL_ALL is set."
switch "--all",
description: "Check all formulae and casks whether installed or not.",
hidden: true
hidden: true
switch "--new", "--new-formula", "--new-cask",
description: "Run various additional style checks to determine if a new formula or cask is eligible " \
"for Homebrew. This should be used when creating new formula and implies " \
@ -118,8 +120,6 @@ module Homebrew
ENV.activate_extensions!
ENV.setup_build_environment
# TODO: 3.6.0: odeprecate not specifying args.all?, require args.installed?
audit_formulae, audit_casks = if args.tap
Tap.fetch(args.tap).then do |tap|
[
@ -131,12 +131,22 @@ module Homebrew
no_named_args = true
[Formula.installed, Cask::Caskroom.casks]
elsif args.no_named?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew audit",
"brew audit --eval-all or HOMEBREW_EVAL_ALL"
end
no_named_args = true
[Formula.all, Cask::Cask.all]
else
args.named.to_formulae_and_casks
.partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
end
if audit_formulae.empty? && audit_casks.empty?
ofail "No matching formulae or casks to audit!"
return
end
style_files = args.named.to_paths unless skip_style
only_cops = args.only_cops

View File

@ -121,8 +121,8 @@ module Homebrew
if new_version.present?
if new_version.latest?
opoo "Ignoring specified `--sha256=` argument." if new_hash.present?
new_hash = :no_check
elsif new_hash.nil? || cask.languages.present?
replacement_pairs << [old_hash, ":no_check"]
elsif old_hash != :no_check && (new_hash.nil? || cask.languages.present?)
tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
replacement_pairs.uniq.compact,
read_only_run: true,
@ -131,27 +131,27 @@ module Homebrew
tmp_cask = Cask::CaskLoader.load(tmp_contents)
tmp_config = tmp_cask.config
new_hash = fetch_cask(tmp_contents)[1] if old_hash != :no_check && new_hash.nil?
[:arm, :intel].each do |arch|
Homebrew::SimulateSystem.arch = arch
cask.languages.each do |language|
lang_config = tmp_config.merge(Cask::Config.new(explicit: { languages: [language] }))
replacement_pairs << fetch_cask(tmp_contents, config: lang_config)
end
languages = cask.languages
languages = [nil] if languages.empty?
languages.each do |language|
new_hash_config = if language.blank?
tmp_config
else
tmp_config.merge(Cask::Config.new(explicit: { languages: [language] }))
end
# TODO: Use SimulateSystem once all casks use on_system blocks
if tmp_contents.include?("Hardware::CPU.intel?")
other_intel = !Hardware::CPU.intel?
Homebrew::SimulateSystem.arch = other_intel ? :intel : :arm
other_contents = tmp_contents.gsub("Hardware::CPU.intel?", other_intel.to_s)
other_cask = Cask::CaskLoader.load(other_contents)
new_hash_cask = Cask::CaskLoader.load(tmp_contents)
new_hash_cask.config = new_hash_config
old_hash = new_hash_cask.sha256.to_s
if other_cask.sha256 != :no_check && other_cask.language.blank?
replacement_pairs << fetch_cask(other_contents)
end
cask_download = Cask::Download.new(new_hash_cask, quarantine: true)
download = cask_download.fetch(verify_download_integrity: false)
Utils::Tar.validate_file(download)
other_cask.languages.each do |language|
lang_config = other_cask.config.merge(Cask::Config.new(explicit: { languages: [language] }))
replacement_pairs << fetch_cask(other_contents, config: lang_config)
replacement_pairs << [new_hash_cask.sha256.to_s, download.sha256]
end
Homebrew::SimulateSystem.clear
@ -159,15 +159,6 @@ module Homebrew
end
end
if new_hash.present? && cask.language.blank? # avoid repeated replacement for multilanguage cask
hash_regex = (old_hash == :no_check) ? ":no_check" : "[\"']#{Regexp.escape(old_hash.to_s)}[\"']"
replacement_pairs << [
/sha256\s+#{hash_regex}/m,
"sha256 #{(new_hash == :no_check) ? ":no_check" : "\"#{new_hash}\""}",
]
end
Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
replacement_pairs.uniq.compact,
read_only_run: args.dry_run?,
@ -197,19 +188,6 @@ module Homebrew
GitHub.create_bump_pr(pr_info, args: args)
end
def fetch_cask(contents, config: nil)
cask = Cask::CaskLoader.load(contents)
cask.config = config if config.present?
old_hash = cask.sha256.to_s
cask_download = Cask::Download.new(cask, quarantine: true)
download = cask_download.fetch(verify_download_integrity: false)
Utils::Tar.validate_file(download)
new_hash = download.sha256
[old_hash, new_hash]
end
def check_open_pull_requests(cask, args:)
tap_remote_repo = cask.tap.remote_repo || cask.tap.full_name
GitHub.check_for_duplicate_pull_requests(cask.token, tap_remote_repo,

View File

@ -18,8 +18,8 @@ module Homebrew
Create a pull request to update <formula> with a new URL or a new tag.
If a <URL> is specified, the <SHA-256> checksum of the new download should also
be specified. A best effort to determine the <SHA-256> and <formula> name will
be made if either or both values are not supplied by the user.
be specified. A best effort to determine the <SHA-256> will be made if not supplied
by the user.
If a <tag> is specified, the Git commit <revision> corresponding to that tag
should also be specified. A best effort to determine the <revision> will be made
@ -34,12 +34,8 @@ module Homebrew
EOS
switch "-n", "--dry-run",
description: "Print what would be done rather than doing it."
switch "--all",
description: "Read all formulae if necessary to determine URL.",
hidden: true
switch "--write-only",
description: "Make the expected file modifications without taking any Git actions."
switch "--write", hidden: true
switch "--commit",
depends_on: "--write-only",
description: "When passed with `--write-only`, generate a new commit after writing changes " \
@ -87,11 +83,9 @@ module Homebrew
description: "Exclude these Python packages when finding resources."
conflicts "--dry-run", "--write-only"
conflicts "--dry-run", "--write"
conflicts "--no-audit", "--strict"
conflicts "--no-audit", "--online"
conflicts "--url", "--tag"
conflicts "--installed", "--all"
named_args :formula, max: 1
end
@ -100,8 +94,6 @@ module Homebrew
def bump_formula_pr
args = bump_formula_pr_args.parse
odisabled "`brew bump-formula-pr --write`", "`brew bump-formula-pr --write-only`" if args.write?
if args.revision.present? && args.tag.nil? && args.version.nil?
raise UsageError, "`--revision` must be passed with either `--tag` or `--version`!"
end
@ -114,9 +106,7 @@ module Homebrew
ENV["BROWSER"] = Homebrew::EnvConfig.browser
formula = args.named.to_formulae.first
new_url = args.url
formula ||= determine_formula_from_url(new_url) if new_url.present?
raise FormulaUnspecifiedError if formula.blank?
odie "This formula is disabled!" if formula.disabled?
@ -372,27 +362,6 @@ module Homebrew
GitHub.create_bump_pr(pr_info, args: args)
end
def determine_formula_from_url(url)
# Split the new URL on / and find any formulae that have the same URL
# except for the last component, but don't try to match any more than the
# first five components since sometimes the last component isn't the only
# one to change.
url_split = url.split("/")
maximum_url_components_to_match = 5
components_to_match = [url_split.count - 1, maximum_url_components_to_match].min
base_url = url_split.first(components_to_match).join("/")
base_url = /#{Regexp.escape(base_url)}/
guesses = []
# TODO: 3.6.0: odeprecate not specifying args.all?
Formula.all.each do |f|
guesses << f if f.stable&.url&.match(base_url)
end
return guesses.shift if guesses.count == 1
return if guesses.count <= 1
odie "Couldn't guess formula for sure; could be one of these:\n#{guesses.map(&:name).join(", ")}"
end
def determine_mirror(url)
case url
when %r{.*ftp\.gnu\.org/gnu.*}

View File

@ -72,17 +72,18 @@ module Homebrew
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks.group_by { |item| Livecheck.formula_or_cask_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
ambiguous_casks = formulae_and_casks \
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
end
ambiguous_names = []
unless args.full_name?
ambiguous_names =
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.formula_or_cask_name(item) }
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
@ -92,7 +93,7 @@ module Homebrew
puts if i.positive?
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.formula_or_cask_name(formula_or_cask, full_name: use_full_name)
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula)
if formula_or_cask.head_only?
ohai name
@ -157,7 +158,7 @@ module Homebrew
rescue
next
end
name = Livecheck.formula_or_cask_name(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
ambiguous_cask = begin
formula_or_cask.is_a?(Cask::Cask) && !args.cask? && Formula[name]
rescue FormulaUnavailableError
@ -178,7 +179,7 @@ module Homebrew
end
def livecheck_result(formula_or_cask)
name = Livecheck.formula_or_cask_name(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, =
Livecheck.resolve_livecheck_reference(formula_or_cask, full_name: false, debug: false)

View File

@ -69,7 +69,7 @@ module Homebrew
end
if args.linux?
runners << "ubuntu-latest"
runners << "ubuntu-22.04"
elsif args.linux_self_hosted?
runners << "linux-self-hosted-1"
end

View File

@ -23,6 +23,7 @@ module Homebrew
Generate Homebrew's manpages and shell completions.
EOS
switch "--fail-if-not-changed",
hidden: true,
description: "Return a failing status code if no changes are detected in the manpage outputs. " \
"This can be used to notify CI when the manpages are out of date. Additionally, " \
"the date used in new manpages will match those in the existing manpages (to allow " \
@ -34,27 +35,30 @@ module Homebrew
def generate_man_completions
args = generate_man_completions_args.parse
odeprecated "brew generate-man-completions --fail-if-not-changed" if args.fail_if_not_changed?
Commands.rebuild_internal_commands_completion_list
regenerate_man_pages(preserve_date: args.fail_if_not_changed?, quiet: args.quiet?)
regenerate_man_pages(quiet: args.quiet?)
Completions.update_shell_completions!
diff = system_command "git", args: [
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
]
return unless diff.status.success?
puts "No changes to manpage or completions output detected."
Homebrew.failed = true if args.fail_if_not_changed?
if diff.status.success?
ofail "No changes to manpage or completions."
else
puts "Manpage and completions updated."
end
end
def regenerate_man_pages(preserve_date:, quiet:)
# TODO: move this method and all called functions to manpages.rb
def regenerate_man_pages(quiet:)
Homebrew.install_bundler_gems!
markup = build_man_page(quiet: quiet)
convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md", preserve_date: preserve_date)
convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md")
markup = I18n.transliterate(markup, locale: :en)
convert_man_page(markup, TARGET_MAN_PATH/"brew.1", preserve_date: preserve_date)
convert_man_page(markup, TARGET_MAN_PATH/"brew.1")
end
def build_man_page(quiet:)
@ -94,13 +98,13 @@ module Homebrew
path.basename.to_s.sub(/\.(rb|sh)$/, "").sub(/^--/, "~~")
end
def convert_man_page(markup, target, preserve_date:)
def convert_man_page(markup, target)
manual = target.basename(".1")
organisation = "Homebrew"
# Set the manpage date to the existing one if we're checking for changes.
# Set the manpage date to the existing one if we're updating.
# This avoids the only change being e.g. a new date.
date = if preserve_date && target.extname == ".1" && target.exist?
date = if target.extname == ".1" && target.exist?
/"(\d{1,2})" "([A-Z][a-z]+) (\d{4})" "#{organisation}" "#{manual}"/ =~ target.read
Date.parse("#{Regexp.last_match(1)} #{Regexp.last_match(2)} #{Regexp.last_match(3)}")
else

View File

@ -50,7 +50,7 @@ module Homebrew
if args.test?
result.display_test_output(strict: args.strict?)
Homebrew.failed = true if result.broken_library_linkage?(strict: args.strict?)
Homebrew.failed = true if result.broken_library_linkage?(test: true, strict: args.strict?)
elsif args.reverse?
result.display_reverse_output
else

View File

@ -24,17 +24,21 @@ module Homebrew
`~/.brew_livecheck_watchlist`.
EOS
switch "--full-name",
description: "Print formulae/casks with fully-qualified names."
description: "Print formulae and casks with fully-qualified names."
flag "--tap=",
description: "Check formulae/casks within the given tap, specified as <user>`/`<repo>."
description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to check them."
switch "--all",
description: "Check all available formulae/casks."
hidden: true
switch "--installed",
description: "Check formulae/casks that are currently installed."
description: "Check formulae and casks that are currently installed."
switch "--newer-only",
description: "Show the latest version only if it's newer than the formula/cask."
switch "--json",
description: "Output information in JSON format."
switch "-r", "--resources",
description: "Also check resources for formulae."
switch "-q", "--quiet",
description: "Suppress warnings, don't print a progress bar for JSON output."
switch "--formula", "--formulae",
@ -43,7 +47,7 @@ module Homebrew
description: "Only check casks."
conflicts "--debug", "--json"
conflicts "--tap=", "--all", "--installed"
conflicts "--tap=", "--eval-all", "--installed"
conflicts "--cask", "--formula"
named_args [:formula, :cask]
@ -53,6 +57,12 @@ module Homebrew
def livecheck
args = livecheck_args.parse
all = args.eval_all?
if args.all?
odeprecated "brew livecheck --all", "brew livecheck --eval-all" if !all && !Homebrew::EnvConfig.eval_all?
all = true
end
if args.debug? && args.verbose?
puts args
puts Homebrew::EnvConfig.livecheck_watchlist if Homebrew::EnvConfig.livecheck_watchlist.present?
@ -67,7 +77,7 @@ module Homebrew
formulae = args.cask? ? [] : Formula.installed
casks = args.formula? ? [] : Cask::Caskroom.casks
formulae + casks
elsif args.all?
elsif all
formulae = args.cask? ? [] : Formula.all
casks = args.formula? ? [] : Cask::Cask.all
formulae + casks
@ -93,6 +103,7 @@ module Homebrew
else
raise UsageError, "A watchlist file is required when no arguments are given."
end
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
@ -103,6 +114,7 @@ module Homebrew
json: args.json?,
full_name: args.full_name?,
handle_name_conflict: !args.formula? && !args.cask?,
check_resources: args.resources?,
newer_only: args.newer_only?,
quiet: args.quiet?,
debug: args.debug?,

View File

@ -368,28 +368,32 @@ module Homebrew
end
end
def pr_check_conflicts(user, repo, pr)
long_build_pr_files = GitHub.search_issues(
"org:#{user}", repo: repo, state: "open", label: "\"no long build conflict\""
def pr_check_conflicts(repo, pr)
long_build_pr_files = GitHub.issues(
repo: repo, state: "open", labels: "no long build conflict",
).each_with_object({}) do |long_build_pr, hash|
next unless long_build_pr.key?("pull_request")
number = long_build_pr["number"]
next if number == pr.to_i
GitHub.get_pull_request_changed_files("#{user}/#{repo}", number).each do |file|
GitHub.get_pull_request_changed_files(repo, number).each do |file|
key = file["filename"]
hash[key] ||= []
hash[key] << number
end
end
this_pr_files = GitHub.get_pull_request_changed_files("#{user}/#{repo}", pr)
return if long_build_pr_files.blank?
this_pr_files = GitHub.get_pull_request_changed_files(repo, pr)
conflicts = this_pr_files.each_with_object({}) do |file, hash|
filename = file["filename"]
next unless long_build_pr_files.key?(filename)
long_build_pr_files[filename].each do |pr_number|
key = "#{user}/#{repo}/pull/#{pr_number}"
key = "#{repo}/pull/#{pr_number}"
hash[key] ||= []
hash[key] << filename
end
@ -440,7 +444,7 @@ module Homebrew
opoo "Current branch is #{tap.path.git_branch}: do you need to pull inside #{tap.path.git_origin_branch}?"
end
pr_check_conflicts(user, repo, pr)
pr_check_conflicts("#{user}/#{repo}", pr)
ohai "Fetching #{tap} pull request ##{pr}"
Dir.mktmpdir pr do |dir|

View File

@ -30,7 +30,6 @@ module Homebrew
description: "Skip running `brew bottle` before uploading."
flag "--committer=",
description: "Specify a committer name and email in `git`'s standard author format."
flag "--github-org=", hidden: true
flag "--root-url=",
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
flag "--root-url-using=",
@ -90,8 +89,6 @@ module Homebrew
def pr_upload
args = pr_upload_args.parse
odisabled "`brew pr-upload --github-org`", "`brew pr-upload` without `--github-org`" if args.github_org
json_files = Dir["*.bottle.json"]
odie "No bottle JSON files found in the current working directory" if json_files.blank?
bottles_hash = bottles_hash_from_json_files(json_files, args)

View File

@ -74,7 +74,7 @@ module Homebrew
test-bot:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-22.04, macos-12]
runs-on: ${{ matrix.os }}
steps:
- name: Set up Homebrew
@ -119,7 +119,7 @@ module Homebrew
jobs:
pr-pull:
if: contains(github.event.pull_request.labels.*.name, '#{label}')
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master

View File

@ -119,6 +119,7 @@ module Homebrew
if args.retry? && @test_failed.add?(f)
oh1 "Testing #{f.full_name} (again)"
f.clear_cache
ENV["RUST_BACKTRACE"] = "full"
true
else
Homebrew.failed = true

View File

@ -20,13 +20,13 @@ module Homebrew
description: "Silence all non-critical errors."
switch "--update",
description: "Update RBI files."
switch "--all",
depends_on: "--update",
description: "Regenerate all RBI files rather than just updated gems."
switch "--update-all",
description: "Update all RBI files rather than just updated gems."
switch "--suggest-typed",
depends_on: "--update",
description: "Try upgrading `typed` sigils."
switch "--fail-if-not-changed",
hidden: true,
description: "Return a failing status code if all gems are up to date " \
"and gem definitions do not need a tapioca update."
flag "--dir=",
@ -50,7 +50,9 @@ module Homebrew
Homebrew.install_bundler_gems!(groups: ["sorbet"])
HOMEBREW_LIBRARY_PATH.cd do
if args.update?
if args.update? || args.update_all?
odeprecated "brew typecheck --update --fail-if-not-changed" if args.fail_if_not_changed?
excluded_gems = [
"did_you_mean", # RBI file is already provided by Sorbet
"webrobots", # RBI file is bugged
@ -60,7 +62,7 @@ module Homebrew
"msgpack:false", # Investigate removing this with Tapioca 0.8
]
tapioca_args = ["--exclude", *excluded_gems, "--typed-overrides", *typed_overrides]
tapioca_args << "--all" if args.all?
tapioca_args << "--all" if args.update_all?
ohai "Updating Tapioca RBI files..."
safe_system "bundle", "exec", "tapioca", "gem", *tapioca_args
@ -100,8 +102,6 @@ module Homebrew
end
end
Homebrew.failed = system("git", "diff", "--stat", "--exit-code") if args.fail_if_not_changed?
return
end

View File

@ -20,11 +20,13 @@ module Homebrew
description: "Use the specified bottle tag (e.g. `big_sur`) instead of the current OS."
switch "--dependents",
description: "Skip getting analytics data and sort by number of dependents instead."
switch "--all", "--total",
switch "--total",
description: "Print the number of unbottled and total formulae."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to check them. " \
"Implied if HOMEBREW_EVAL_ALL is set."
conflicts "--dependents", "--all"
conflicts "--installed", "--all"
conflicts "--dependents", "--total"
named_args :formula
end
@ -42,16 +44,22 @@ module Homebrew
Utils::Bottles.tag
end
# TODO: 3.6.0: odeprecate args.total?
all = args.eval_all?
if args.total?
if !all && !Homebrew::EnvConfig.eval_all?
odeprecated "brew unbottled --total", "brew unbottled --total --eval-all or HOMEBREW_EVAL_ALL"
end
all = true
end
if args.named.blank?
ohai "Getting formulae..."
elsif args.all?
raise UsageError, "cannot specify `<formula>` and `--all`/`--total`."
elsif all
raise UsageError, "cannot specify `<formula>` and `--eval-all`/`--total`."
end
formulae, all_formulae, formula_installs =
formulae_all_installs_from_args(args)
formulae_all_installs_from_args(args, all)
deps_hash, uses_hash = deps_uses_from_formulae(all_formulae)
if args.dependents?
@ -60,9 +68,7 @@ module Homebrew
dependents = uses_hash[f.name]&.length || 0
formula_dependents[f.name] ||= dependents
end.reverse
end
if args.all?
elsif all
output_total(formulae)
return
end
@ -78,16 +84,18 @@ module Homebrew
output_unbottled(formulae, deps_hash, noun, hash, args.named.present?)
end
def formulae_all_installs_from_args(args)
def formulae_all_installs_from_args(args, all)
if args.named.present?
formulae = all_formulae = args.named.to_formulae
elsif args.all?
formulae = all_formulae = Formula.all
elsif args.dependents?
# TODO: 3.6.0: odeprecate not specifying args.all? for args.dependents?
if !args.all? && !Homebrew::EnvConfig.eval_all?
odeprecated "brew unbottled --dependents", "brew unbottled --all --dependents or HOMEBREW_EVAL_ALL"
end
formulae = all_formulae = Formula.all
@sort = " (sorted by number of dependents)"
elsif all
formulae = all_formulae = Formula.all
else
formula_installs = {}

View File

@ -3,9 +3,11 @@
require "cli/parser"
require "utils/spdx"
require "system_command"
module Homebrew
extend T::Sig
include SystemCommand::Mixin
module_function
@ -16,6 +18,7 @@ module Homebrew
Update SPDX license data in the Homebrew repository.
EOS
switch "--fail-if-not-changed",
hidden: true,
description: "Return a failing status code if current license data's version is the same as " \
"the upstream. This can be used to notify CI when the SPDX license data is out of date."
@ -25,11 +28,16 @@ module Homebrew
def update_license_data
args = update_license_data_args.parse
ohai "Updating SPDX license data..."
odeprecated "brew update-license-data --fail-if-not-changed" if args.fail_if_not_changed?
SPDX.download_latest_license_data!
return unless args.fail_if_not_changed?
Homebrew.failed = system("git", "diff", "--stat", "--exit-code", SPDX::DATA_PATH)
diff = system_command "git", args: [
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", SPDX::DATA_PATH
]
if diff.status.success?
ofail "No changes to SPDX license data."
else
puts "SPDX license data updated."
end
end
end

View File

@ -3,6 +3,8 @@
require "cli/parser"
require "utils/github"
# TODO: move function to manpages.rb and require that instead
require "dev-cmd/generate-man-completions"
module Homebrew
@ -57,9 +59,10 @@ module Homebrew
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "README.md"
]
if diff.status.success?
puts "No changes to list of maintainers."
ofail "No changes to list of maintainers."
else
Homebrew.regenerate_man_pages(preserve_date: true, quiet: true)
# TODO: move function to manpages.rb and call that instead
Homebrew.regenerate_man_pages(quiet: true)
puts "List of maintainers updated in the README and the generated man pages."
end
end

View File

@ -9,11 +9,11 @@ module Homebrew
module_function
NAMED_TIER_AMOUNT = 100
URL_TIER_AMOUNT = 1000
NAMED_MONTHLY_AMOUNT = 100
URL_MONTHLY_AMOUNT = 1000
sig { returns(CLI::Parser) }
def sponsors_args
def update_sponsors_args
Homebrew::CLI::Parser.new do
description <<~EOS
Update the list of GitHub Sponsors in the `Homebrew/brew` README.
@ -23,38 +23,36 @@ module Homebrew
end
end
def sponsor_name(s)
s["name"] || s["login"]
def sponsor_name(sponsor)
sponsor[:name] || sponsor[:login]
end
def sponsor_logo(s)
"https://github.com/#{s["login"]}.png?size=64"
def sponsor_logo(sponsor)
"https://github.com/#{sponsor[:login]}.png?size=64"
end
def sponsor_url(s)
"https://github.com/#{s["login"]}"
def sponsor_url(sponsor)
"https://github.com/#{sponsor[:login]}"
end
def sponsors
sponsors_args.parse
def update_sponsors
update_sponsors_args.parse
named_sponsors = []
logo_sponsors = []
largest_monthly_amount = 0
GitHub.sponsors_by_tier("Homebrew").each do |tier|
if tier["tier"] >= NAMED_TIER_AMOUNT
named_sponsors += tier["sponsors"].map do |s|
"[#{sponsor_name(s)}](#{sponsor_url(s)})"
end
end
GitHub.sponsorships("Homebrew").each do |s|
largest_monthly_amount = [s[:monthly_amount], s[:closest_tier_monthly_amount]].max
named_sponsors << "[#{sponsor_name(s)}](#{sponsor_url(s)})" if largest_monthly_amount >= NAMED_MONTHLY_AMOUNT
next if tier["tier"] < URL_TIER_AMOUNT
next if largest_monthly_amount < URL_MONTHLY_AMOUNT
logo_sponsors += tier["sponsors"].map do |s|
"[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
end
logo_sponsors << "[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
end
odie "No sponsorships amounts found! Ensure you have sufficient permissions!" if largest_monthly_amount.zero?
named_sponsors << "many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew)"
readme = HOMEBREW_REPOSITORY/"README.md"
@ -68,7 +66,7 @@ module Homebrew
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "README.md"
]
if diff.status.success?
puts "No changes to list of sponsors."
ofail "No changes to list of sponsors."
else
puts "List of sponsors updated in the README."
end

View File

@ -78,8 +78,8 @@ class DevelopmentTools
end
sig { params(cc: String).returns(Version) }
def non_apple_gcc_version(cc)
(@non_apple_gcc_version ||= {}).fetch(cc) do
def gcc_version(cc)
(@gcc_version ||= {}).fetch(cc) do
path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc
path = locate(cc) unless path.exist?
version = if path &&
@ -88,14 +88,24 @@ class DevelopmentTools
else
Version::NULL
end
@non_apple_gcc_version[cc] = version
@gcc_version[cc] = version
end
end
sig { void }
def clear_version_cache
@clang_version = @clang_build_version = nil
@non_apple_gcc_version = {}
@gcc_version = {}
end
sig { returns(T::Boolean) }
def build_system_too_old?
false
end
sig { returns(T::Boolean) }
def system_gcc_too_old?
false
end
sig { returns(T::Boolean) }

View File

@ -591,7 +591,11 @@ module Homebrew
def check_coretap_integrity
coretap = CoreTap.instance
return if !coretap.installed? && EnvConfig.install_from_api?
unless coretap.installed?
return if EnvConfig.install_from_api?
CoreTap.ensure_installed!
end
broken_tap(coretap) || examine_git_origin(coretap.path, Homebrew::EnvConfig.core_git_remote)
end
@ -1045,11 +1049,11 @@ module Homebrew
when :quarantine_available
nil
when :xattr_broken
"There's no working version of `xattr` on this system."
"No Cask quarantine support available: there's no working version of `xattr` on this system."
when :no_swift
"Swift is not available on this system."
"No Cask quarantine support available: there's no available version of `swift` on this system."
else
"Unknown support status"
"No Cask quarantine support available: unknown reason."
end
end

View File

@ -153,6 +153,11 @@ module Homebrew
"editors will do strange things in this case.",
default_text: "`$EDITOR` or `$VISUAL`.",
},
HOMEBREW_EVAL_ALL: {
description: "If set, `brew` commands evaluate all formulae and casks, executing their arbitrary code, by " \
"default without requiring --eval-all. Required to cache formula and cask descriptions.",
boolean: true,
},
HOMEBREW_FAIL_LOG_LINES: {
description: "Output this many lines of output on formula `system` failures.",
default: 15,
@ -313,6 +318,10 @@ module Homebrew
"outdated.",
boolean: true,
},
HOMEBREW_PIP_INDEX_URL: {
description: "If set, `brew install <formula>` will use this URL to download PyPI package resources.",
default_text: "`https://pypi.org/simple`.",
},
HOMEBREW_PRY: {
description: "If set, use Pry for the `brew irb` command.",
boolean: true,

View File

@ -57,7 +57,14 @@ module Stdenv
# Os is the default Apple uses for all its stuff so let's trust them
define_cflags "-Os #{SAFE_CFLAGS_FLAGS}"
send(compiler)
begin
send(compiler)
rescue CompilerSelectionError
# We don't care if our compiler fails to build the formula during `brew test`.
raise unless testing_formula
send(DevelopmentTools.default_compiler)
end
return unless cc&.match?(GNU_GCC_REGEXP)

View File

@ -5,11 +5,17 @@ class Module
def attr_rw(*attrs)
attrs.each do |attr|
module_eval <<-EOS, __FILE__, __LINE__+1
def #{attr}(val=nil) # def prefix(val=nil)
@#{attr} ||= nil # @prefix ||= nil
return @#{attr} if val.nil? # return @prefix if val.nil?
@#{attr} = val # @prefix = val
end # end
def #{attr}(val=nil) # def prefix(val=nil)
if val.nil? # if val.nil?
if instance_variable_defined?(:@#{attr}) # if instance_variable_defined?(:@prefix)
return @#{attr} # return @prefix
else # else
return nil # return nil
end # end
end # end
#
@#{attr} = val # @prefix = val
end # end
EOS
end
end

View File

@ -1,4 +1,8 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/dependency_collector" if OS.mac?
if OS.mac?
require "extend/os/mac/dependency_collector"
elsif OS.linux?
require "extend/os/linux/dependency_collector"
end

View File

@ -4,8 +4,6 @@
class CompilerSelector
sig { returns(String) }
def self.preferred_gcc
# gcc-5 is the lowest gcc version we support on Linux.
# gcc-5 is the default gcc in Ubuntu 16.04 (used for our CI)
"gcc@5"
OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA
end
end

View File

@ -0,0 +1,89 @@
# typed: true
# frozen_string_literal: true
require "os/linux/glibc"
class DependencyCollector
extend T::Sig
undef gcc_dep_if_needed
undef glibc_dep_if_needed
undef init_global_dep_tree_if_needed!
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def gcc_dep_if_needed(related_formula_names)
# gcc is required for libgcc_s.so.1 if glibc or gcc are too old
return unless DevelopmentTools.build_system_too_old?
return if building_global_dep_tree?
return if related_formula_names.include?(GCC)
return if global_dep_tree[GCC]&.intersect?(related_formula_names)
Dependency.new(GCC)
end
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def glibc_dep_if_needed(related_formula_names)
return unless OS::Linux::Glibc.below_ci_version?
return if building_global_dep_tree?
return if related_formula_names.include?(GLIBC)
return if global_dep_tree[GLIBC]&.intersect?(related_formula_names)
Dependency.new(GLIBC)
end
private
GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
sig { void }
def init_global_dep_tree_if_needed!
return unless DevelopmentTools.build_system_too_old?
return if building_global_dep_tree?
return unless global_dep_tree.empty?
building_global_dep_tree!
global_dep_tree[GLIBC] = Set.new(global_deps_for(GLIBC))
# gcc depends on glibc
global_dep_tree[GCC] = Set.new([*global_deps_for(GCC), GLIBC, *@@global_dep_tree[GLIBC]])
built_global_dep_tree!
end
sig { params(name: String).returns(T::Array[String]) }
def global_deps_for(name)
@global_deps_for ||= {}
# Always strip out glibc and gcc from all parts of dependency tree when
# we're calculating their dependency trees. Other parts of Homebrew will
# catch any circular dependencies.
@global_deps_for[name] ||= Formula[name].deps.map(&:name).flat_map do |dep|
[dep, *global_deps_for(dep)].compact
end.uniq
end
# Use class variables to avoid this expensive logic needing to be done more
# than once.
# rubocop:disable Style/ClassVars
@@global_dep_tree = {}
@@building_global_dep_tree = false
sig { returns(T::Hash[String, T::Set[String]]) }
def global_dep_tree
@@global_dep_tree
end
sig { void }
def building_global_dep_tree!
@@building_global_dep_tree = true
end
sig { void }
def built_global_dep_tree!
@@building_global_dep_tree = false
end
sig { returns(T::Boolean) }
def building_global_dep_tree?
@@building_global_dep_tree.present?
end
# rubocop:enable Style/ClassVars
end

View File

@ -8,10 +8,15 @@ class DevelopmentTools
sig { params(tool: String).returns(T.nilable(Pathname)) }
def locate(tool)
(@locate ||= {}).fetch(tool) do |key|
@locate[key] = if (path = HOMEBREW_PREFIX/"bin/#{tool}").executable?
path
elsif File.executable?(path = "/usr/bin/#{tool}")
Pathname.new path
@locate[key] = if build_system_too_old? &&
(binutils_path = HOMEBREW_PREFIX/"opt/binutils/bin/#{tool}").executable?
binutils_path
elsif build_system_too_old? && (glibc_path = HOMEBREW_PREFIX/"opt/glibc/bin/#{tool}").executable?
glibc_path
elsif (homebrew_path = HOMEBREW_PREFIX/"bin/#{tool}").executable?
homebrew_path
elsif File.executable?(system_path = "/usr/bin/#{tool}")
Pathname.new system_path
end
end
end
@ -21,6 +26,21 @@ class DevelopmentTools
:gcc
end
sig { returns(T::Boolean) }
def build_system_too_old?
return @build_system_too_old if defined? @build_system_too_old
@build_system_too_old = (system_gcc_too_old? || OS::Linux::Glibc.below_ci_version?)
end
sig { returns(T::Boolean) }
def system_gcc_too_old?
gcc = "/usr/bin/gcc"
return true unless File.exist?(gcc)
gcc_version(gcc) < OS::LINUX_GCC_CI_VERSION
end
sig { returns(T::Hash[String, T.nilable(String)]) }
def build_system_info
generic_build_system_info.merge({

View File

@ -139,6 +139,35 @@ module Homebrew
e.g. by using homebrew instead).
EOS
end
def check_gcc_dependent_linkage
gcc_dependents = Formula.installed.select do |formula|
next false unless formula.tap&.core_tap?
# FIXME: This includes formulae that have no runtime dependency on GCC.
formula.recursive_dependencies.map(&:name).include? "gcc"
rescue TapFormulaUnavailableError
false
end
return if gcc_dependents.empty?
badly_linked = gcc_dependents.select do |dependent|
keg = Keg.new(dependent.prefix)
keg.binary_executable_or_library_files.any? do |binary|
paths = binary.rpaths
versioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/\d+$}) }
unversioned_linkage = paths.any? { |path| path.match?(%r{lib/gcc/current$}) }
versioned_linkage && !unversioned_linkage
end
end
return if badly_linked.empty?
inject_file_list badly_linked, <<~EOS
Formulae which link to GCC through a versioned path were found. These formulae
are prone to breaking when GCC is updated. You should `brew reinstall` these formulae:
EOS
end
end
end
end

View File

@ -0,0 +1,5 @@
# typed: strict
class Pathname
include ELFShim
end

View File

@ -3,8 +3,9 @@
class Formula
undef shared_library
undef rpath
undef loader_path
undef deuniversalize_machos
undef add_global_deps_to_spec
sig { params(name: String, version: T.nilable(T.any(String, Integer))).returns(String) }
def shared_library(name, version = nil)
@ -17,10 +18,29 @@ class Formula
end
sig { returns(String) }
def rpath
"'$ORIGIN/../lib'"
def loader_path
"$ORIGIN"
end
sig { params(targets: T.nilable(T.any(Pathname, String))).void }
def deuniversalize_machos(*targets); end
sig { params(spec: SoftwareSpec).void }
def add_global_deps_to_spec(spec)
return unless DevelopmentTools.build_system_too_old?
@global_deps ||= begin
dependency_collector = spec.dependency_collector
related_formula_names = Set.new([
name,
*aliases,
*versioned_formulae_names,
])
[
dependency_collector.gcc_dep_if_needed(related_formula_names),
dependency_collector.glibc_dep_if_needed(related_formula_names),
].compact.freeze
end
@global_deps.each { |dep| spec.dependency_collector.add(dep) }
end
end

View File

@ -5,21 +5,43 @@ module Homebrew
module Install
module_function
DYNAMIC_LINKERS = [
"/lib64/ld-linux-x86-64.so.2",
"/lib64/ld64.so.2",
"/lib/ld-linux.so.3",
"/lib/ld-linux.so.2",
"/lib/ld-linux-aarch64.so.1",
"/lib/ld-linux-armhf.so.3",
"/system/bin/linker64",
"/system/bin/linker",
# This is a list of known paths to the host dynamic linker on Linux if
# the host glibc is new enough. The symlink_ld_so method will fail if
# the host linker cannot be found in this list.
DYNAMIC_LINKERS = %w[
/lib64/ld-linux-x86-64.so.2
/lib64/ld64.so.2
/lib/ld-linux.so.3
/lib/ld-linux.so.2
/lib/ld-linux-aarch64.so.1
/lib/ld-linux-armhf.so.3
/system/bin/linker64
/system/bin/linker
].freeze
private_constant :DYNAMIC_LINKERS
# We link GCC runtime libraries that are not specificaly used for Fortran,
# which are linked by the GCC formula. We only use the versioned shared libraries
# as the other shared and static libraries are only used at build time where
# GCC can find its own libraries.
GCC_RUNTIME_LIBS = %w[
libatomic.so.1
libgcc_s.so.1
libgomp.so.1
libstdc++.so.6
].freeze
private_constant :GCC_RUNTIME_LIBS
def perform_preinstall_checks(all_fatal: false, cc: nil)
generic_perform_preinstall_checks(all_fatal: all_fatal, cc: cc)
symlink_ld_so
setup_preferred_gcc_libs
end
def global_post_install
generic_global_post_install
symlink_ld_so
setup_preferred_gcc_libs
end
def check_cpu
@ -39,17 +61,73 @@ module Homebrew
def symlink_ld_so
brew_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
return if brew_ld_so.readable?
ld_so = HOMEBREW_PREFIX/"opt/glibc/lib/ld-linux-x86-64.so.2"
ld_so = HOMEBREW_PREFIX/"opt/glibc/bin/ld.so"
unless ld_so.readable?
ld_so = DYNAMIC_LINKERS.find { |s| File.executable? s }
raise "Unable to locate the system's dynamic linker" unless ld_so
if ld_so.blank?
raise "Unable to locate the system's dynamic linker" unless brew_ld_so.readable?
return
end
end
return if brew_ld_so.readable? && (brew_ld_so.readlink == ld_so)
FileUtils.mkdir_p HOMEBREW_PREFIX/"lib"
FileUtils.ln_sf ld_so, brew_ld_so
end
private_class_method :symlink_ld_so
def setup_preferred_gcc_libs
gcc_opt_prefix = HOMEBREW_PREFIX/"opt/#{OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA}"
glibc_installed = (HOMEBREW_PREFIX/"opt/glibc/bin/ld.so").readable?
return unless gcc_opt_prefix.readable?
if glibc_installed
ld_so_conf_d = HOMEBREW_PREFIX/"etc/ld.so.conf.d"
unless ld_so_conf_d.exist?
ld_so_conf_d.mkpath
FileUtils.chmod "go-w", ld_so_conf_d
end
# Add gcc to ld search paths
ld_gcc_conf = ld_so_conf_d/"50-homebrew-preferred-gcc.conf"
ld_gcc_conf_content = <<~EOS
# This file is generated by Homebrew. Do not modify.
#{gcc_opt_prefix}/lib/gcc/current
EOS
if !ld_gcc_conf.exist? || (ld_gcc_conf.read != ld_gcc_conf_content)
ld_gcc_conf.atomic_write ld_gcc_conf_content
FileUtils.chmod "u=rw,go-wx", ld_gcc_conf
FileUtils.rm_f HOMEBREW_PREFIX/"etc/ld.so.cache"
system HOMEBREW_PREFIX/"opt/glibc/sbin/ldconfig"
end
else
odie "#{HOMEBREW_PREFIX}/lib does not exist!" unless (HOMEBREW_PREFIX/"lib").readable?
end
GCC_RUNTIME_LIBS.each do |library|
gcc_library_symlink = HOMEBREW_PREFIX/"lib/#{library}"
if glibc_installed
# Remove legacy symlinks
FileUtils.rm gcc_library_symlink if gcc_library_symlink.symlink?
else
gcc_library = gcc_opt_prefix/"lib/gcc/current/#{library}"
# Skip if the link target doesn't exist.
next unless gcc_library.readable?
# Also skip if the symlink already exists.
next if gcc_library_symlink.readable? && (gcc_library_symlink.readlink == gcc_library)
FileUtils.ln_sf gcc_library, gcc_library_symlink
end
end
end
private_class_method :setup_preferred_gcc_libs
end
end

View File

@ -30,16 +30,10 @@ class Keg
lib_path = "#{new_prefix}/lib"
rpath << lib_path unless rpath.include? lib_path
# Add GCC's lib directory (as of GCC 12+) to RPATH when there is existing linkage.
# This fixes linkage for newly-poured bottles.
if !name.match?(Version.formula_optionally_versioned_regex(:gcc)) &&
rpath.any? { |rp| rp.match?(%r{lib/gcc/\d+$}) }
# TODO: Replace with
# rpath.map! { |path| path = path.sub(%r{lib/gcc/\d+$}, "lib/gcc/current") }
# when
# 1. Homebrew/homebrew-core#106755 is merged
# 2. No formula has a runtime dependency on a versioned GCC (see `envoy.rb`)
rpath.prepend HOMEBREW_PREFIX/"opt/gcc/lib/gcc/current"
# Add GCC's lib directory (as of GCC 12+) to RPATH when there is existing versioned linkage.
# This prevents broken linkage when pouring bottles built with an old GCC formula.
unless name.match?(Version.formula_optionally_versioned_regex(:gcc))
rpath.map! { |rp| rp.sub(%r{lib/gcc/\d+$}, "lib/gcc/current") }
end
rpath.join(":")

View File

@ -7,10 +7,10 @@ class LinkageChecker
# Libraries provided by glibc and gcc.
SYSTEM_LIBRARY_ALLOWLIST = %w[
ld-linux-x86-64.so.2
ld-linux-aarch64.so.1
libanl.so.1
libatomic.so.1
libc.so.6
libcrypt.so.1
libdl.so.2
libm.so.6
libmvec.so.1
@ -27,18 +27,26 @@ class LinkageChecker
].freeze
def display_deprecated_warning(strict: false)
return unless @libcrypt_found
# Steps when moving this to `odisabled`:
# - Remove `libcrypt.so.1` from SYSTEM_LIBRARY_ALLOWLIST above.
# Steps when moving these to `odisabled`:
# - Remove the old library from SYSTEM_LIBRARY_ALLOWLIST above.
# - Remove the `disable` and `disable_for_developer` kwargs here.
# - Remove `broken_library_linkage?` override below and the generic alias in HOMEBREW_LIBRARY/linkage_checker.rb.
# - Remove `fail_on_libcrypt1?`.
# Steps when removing this entirely (assuming the above has already been done):
# - Adjust the `broken_library_linkage?` override below to not check for the library.
# - Remove the relevant `fail_on_lib*?`.
# If there's no more deprecations left:
# - Remove the `broken_library_linkage?` override and the generic alias in HOMEBREW_LIBRARY/linkage_checker.rb.
#
# Steps when removing handling for a library entirely (assuming the steps to `odisabled` has already been done):
# - Remove the relevant setting of `@lib*_found` in `check_dylibs` below.
# - Remove the `odisabled` line
# If there's no library deprecated/disabled handling left:
# - Remove the `display_` overrides here and the associated generic aliases in HOMEBREW_LIBRARY/linkage_checker.rb
# - Remove the setting of `@libcrypt_found` in `check_dylibs` below.
odeprecated "linkage to libcrypt.so.1", "libcrypt.so.2 in the libxcrypt formula",
disable: fail_on_libcrypt1?(strict: strict),
odisabled "linkage to libcrypt.so.1", "libcrypt.so.2 in the libxcrypt formula" if @libcrypt_found
return unless @libnsl_found
odeprecated "linkage to libnsl.so.1", "libnsl.so.3 in the libnsl formula",
disable: fail_on_libnsl1?(strict: strict),
disable_for_developers: false
end
@ -52,29 +60,35 @@ class LinkageChecker
display_deprecated_warning(strict: strict)
end
def broken_library_linkage?(strict: false)
generic_broken_library_linkage?(strict: strict) || (fail_on_libcrypt1?(strict: strict) && @libcrypt_found)
def broken_library_linkage?(test: false, strict: false)
generic_broken_library_linkage?(test: test, strict: strict) || (fail_on_libnsl1?(strict: strict) && @libnsl_found)
end
private
def fail_on_libcrypt1?(strict:)
strict || ENV["HOMEBREW_DISALLOW_LIBCRYPT1"].present?
def fail_on_libnsl1?(strict:)
strict || ENV["HOMEBREW_DISALLOW_LIBNSL1"].present?
end
def check_dylibs(rebuild_cache:)
generic_check_dylibs(rebuild_cache: rebuild_cache)
@libcrypt_found = true if @system_dylibs.any? { |s| File.basename(s) == "libcrypt.so.1" }
@libnsl_found = true if @system_dylibs.any? { |s| File.basename(s) == "libnsl.so.1" }
# glibc and gcc are implicit dependencies.
# No other linkage to system libraries is expected or desired.
@unwanted_system_dylibs = @system_dylibs.reject do |s|
SYSTEM_LIBRARY_ALLOWLIST.include? File.basename(s)
end
# FIXME: Remove this when these dependencies are injected correctly (e.g. through `DependencyCollector`)
# See discussion at
# https://github.com/Homebrew/brew/pull/13577
@undeclared_deps -= [CompilerSelector.preferred_gcc, "glibc", "gcc"]
# We build all formulae with an RPATH that includes the gcc formula's runtime lib directory.
# See: https://github.com/Homebrew/brew/blob/e689cc07/Library/Homebrew/extend/os/linux/extend/ENV/super.rb#L53
# This results in formulae showing linkage with gcc whenever it is installed, even if no dependency is declared.
# See discussions at:
# https://github.com/Homebrew/brew/pull/13659
# https://github.com/Homebrew/brew/pull/13796
# TODO: Find a nicer way to handle this. (e.g. examining the ELF file to determine the required libstdc++.)
@undeclared_deps.delete("gcc")
end
end

View File

@ -1,9 +1,9 @@
# typed: strict
# typed: true
# frozen_string_literal: true
module Readall
class << self
def valid_casks?(*)
def valid_casks?(_casks)
true
end
end

View File

@ -28,7 +28,7 @@ module SystemConfig
def formula_linked_version(formula)
return "N/A" unless CoreTap.instance.installed?
Formulary.factory(formula).linked_version || "N/A"
Formulary.factory(formula).any_installed_version || "N/A"
rescue FormulaUnavailableError
"N/A"
end
@ -47,7 +47,7 @@ module SystemConfig
out.puts "Host glibc: #{host_glibc_version}"
out.puts "/usr/bin/gcc: #{host_gcc_version}"
out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH
["glibc", CompilerSelector.preferred_gcc, "xorg"].each do |f|
["glibc", CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f|
out.puts "#{f}: #{formula_linked_version(f)}"
end
end

View File

@ -0,0 +1,5 @@
# typed: strict
class Pathname
include MachOShim
end

View File

@ -181,6 +181,14 @@ class Formula
# @private
def initialize(name, path, spec, alias_path: nil, force_bottle: false)
# Only allow instances of subclasses. The base class does not hold any spec information (URLs etc).
raise "Do not call `Formula.new' directly without a subclass." unless self.class < Formula
# Stop any subsequent modification of a formula's definition.
# Changes do not propagate to existing instances of formulae.
# Now that we have an instance, it's too late to make any changes to the class-level definition.
self.class.freeze
@name = name
@path = path
@alias_path = alias_path
@ -262,9 +270,13 @@ class Formula
return unless spec.url
spec.owner = self
add_global_deps_to_spec(spec)
instance_variable_set("@#{name}", spec)
end
sig { params(spec: SoftwareSpec).void }
def add_global_deps_to_spec(spec); end
def determine_active_spec(requested)
spec = send(requested) || stable || head
spec || raise(FormulaSpecificationError, "formulae require at least a URL")
@ -435,16 +447,36 @@ class Formula
end
# If this is a `@`-versioned formula.
sig { returns(T::Boolean) }
def versioned_formula?
name.include?("@")
end
# Returns any `@`-versioned formulae for any formula (including versioned formulae).
def versioned_formulae
Pathname.glob(path.to_s.gsub(/(@[\d.]+)?\.rb$/, "@*.rb")).map do |versioned_path|
# Returns any `@`-versioned formulae names for any formula (including versioned formulae).
sig { returns(T::Array[String]) }
def versioned_formulae_names
versioned_paths = if tap
# Faster path, due to `tap.versioned_formula_files` caching.
name_prefix = "#{name.gsub(/(@[\d.]+)?$/, "")}@"
tap.versioned_formula_files.select do |file|
file.basename.to_s.start_with?(name_prefix)
end
else
Pathname.glob(path.to_s.gsub(/(@[\d.]+)?\.rb$/, "@*.rb"))
end
versioned_paths.map do |versioned_path|
next if versioned_path == path
Formula[versioned_path.basename(".rb").to_s]
versioned_path.basename(".rb").to_s
end.compact.sort
end
# Returns any `@`-versioned Formula objects for any Formula (including versioned formulae).
sig { returns(T::Array[Formula]) }
def versioned_formulae
versioned_formulae_names.map do |name|
Formula[name]
rescue FormulaUnavailableError
nil
end.compact.sort_by(&:version).reverse
@ -1572,9 +1604,23 @@ class Formula
end
# Executable/Library RPATH according to platform conventions.
#
# Optionally specify a `source` or `target` depending on the location
# of the file containing the RPATH command and where its target is located.
#
# <pre>
# rpath #=> "@loader_path/../lib"
# rpath(target: frameworks) #=> "@loader_path/../Frameworks"
# rpath(source: libexec/"bin") #=> "@loader_path/../../lib"
# </pre>
sig { params(source: Pathname, target: Pathname).returns(String) }
def rpath(source: bin, target: lib)
"#{loader_path}/#{target.relative_path_from(source)}"
end
sig { returns(String) }
def rpath
"@loader_path/../lib"
def loader_path
"@loader_path"
end
# Creates a new `Time` object for use in the formula as the build time.
@ -1699,14 +1745,14 @@ class Formula
elsif shell_parameter_format == :arg
"--shell=#{shell}"
elsif shell_parameter_format == :none
""
nil
else
"#{shell_parameter_format}#{shell}"
end
popen_read_args = %w[]
popen_read_args << commands
popen_read_args << shell_parameter
popen_read_args << shell_parameter if shell_parameter.present?
popen_read_args.flatten!
script_path.dirname.mkpath
@ -1760,7 +1806,10 @@ class Formula
# this should only be used when users specify `--all` to a command
# @private
def self.all
# TODO: 3.6.0: consider checking ARGV for --all
# TODO: uncomment for 3.7.0 and ideally avoid using ARGV by moving to e.g. CLI::Parser
# if !ARGV.include?("--eval-all") && !Homebrew::EnvConfig.eval_all?
# odeprecated "Formula#all without --all or HOMEBREW_EVAL_ALL"
# end
files.map do |file|
Formulary.factory(file)
@ -1795,46 +1844,6 @@ class Formula
end.uniq(&:name)
end
# An array of all installed {Formula} with {Cask} dependents.
# @private
def self.formulae_with_cask_dependents(casks)
casks.flat_map { |cask| cask.depends_on[:formula] }
.compact
.map { |f| Formula[f] }
.flat_map { |f| [f, *f.runtime_formula_dependencies].compact }
end
# An array of all installed {Formula} without {Formula} dependents
# @private
def self.formulae_with_no_formula_dependents(formulae)
return [] if formulae.blank?
formulae - formulae.flat_map(&:runtime_formula_dependencies)
end
# Recursive function that returns an array of {Formula} without
# {Formula} dependents that weren't installed on request.
# @private
def self.unused_formulae_with_no_formula_dependents(formulae)
unused_formulae = formulae_with_no_formula_dependents(formulae).reject do |f|
Tab.for_keg(f.any_installed_keg).installed_on_request
end
if unused_formulae.present?
unused_formulae += unused_formulae_with_no_formula_dependents(formulae - unused_formulae)
end
unused_formulae
end
# An array of {Formula} without {Formula} or {Cask}
# dependents that weren't installed on request.
# @private
def self.unused_formulae_with_no_dependents(formulae, casks)
unused_formulae = unused_formulae_with_no_formula_dependents(formulae)
unused_formulae - formulae_with_cask_dependents(casks)
end
def self.installed_with_alias_path(alias_path)
return [] if alias_path.nil?
@ -2003,7 +2012,7 @@ class Formula
# `any_installed_keg` and `runtime_dependencies` `select`s ensure
# that we don't end up with something `Formula#runtime_dependencies` can't
# read from a `Tab`.
Formula.cache[:runtime_installed_formula_dependents] = {}
Formula.cache[:runtime_installed_formula_dependents] ||= {}
Formula.cache[:runtime_installed_formula_dependents][full_name] ||= Formula.installed
.select(&:any_installed_keg)
.select(&:runtime_dependencies)
@ -2061,6 +2070,10 @@ class Formula
"dependencies" => dependencies.reject(&:optional?)
.reject(&:recommended?)
.reject(&:build?)
.reject(&:test?)
.map(&:name)
.uniq,
"test_dependencies" => dependencies.select(&:test?)
.map(&:name)
.uniq,
"recommended_dependencies" => dependencies.select(&:recommended?)
@ -2654,9 +2667,26 @@ class Formula
# The methods below define the formula DSL.
class << self
extend Predicable
extend T::Sig
include BuildEnvironment::DSL
include OnSystem::MacOSAndLinux
# Initialise instance variables for each subclass. These need to be initialised before the class is frozen,
# and some DSL may never be called so it can't be done lazily.
def inherited(child)
super
child.instance_eval do
# Ensure this is synced with `freeze`
@stable = SoftwareSpec.new(flags: build_flags)
@head = HeadSoftwareSpec.new(flags: build_flags)
@livecheck = Livecheck.new(self)
@conflicts = []
@skip_clean_paths = Set.new
@link_overwrite_paths = Set.new
@allowed_missing_libraries = Set.new
end
end
def method_added(method)
super
@ -2668,6 +2698,20 @@ class Formula
end
end
def freeze
specs.each(&:freeze)
@livecheck.freeze
@conflicts.freeze
@skip_clean_paths.freeze
@link_overwrite_paths.freeze
@allowed_missing_libraries.freeze
super
end
# Whether this formula was loaded using the formulae.brew.sh API
# @private
attr_accessor :loaded_from_api
# Whether this formula contains OS/arch-specific blocks
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
# @private
@ -2748,6 +2792,18 @@ class Formula
# @private
attr_reader :plist_manual
# @private
attr_reader :conflicts
# @private
attr_reader :skip_clean_paths
# @private
attr_reader :link_overwrite_paths
# @private
attr_reader :allowed_missing_libraries
# If `pour_bottle?` returns `false` the user-visible reason to display for
# why they cannot use the bottle.
# @private
@ -2778,7 +2834,7 @@ class Formula
# A list of the {.stable} and {.head} {SoftwareSpec}s.
# @private
def specs
@specs ||= [stable, head].freeze
[stable, head].freeze
end
# @!attribute [w] url
@ -2862,8 +2918,9 @@ class Formula
#
# Formulae which should not be bottled should be tagged with:
# <pre>bottle :disable, "reasons"</pre>
def bottle(*args, &block)
stable.bottle(*args, &block)
sig { params(block: T.proc.bind(BottleSpecification).void).void }
def bottle(&block)
stable.bottle(&block)
end
# @private
@ -2894,7 +2951,6 @@ class Formula
# depends_on "libffi"
# end</pre>
def stable(&block)
@stable ||= SoftwareSpec.new(flags: build_flags)
return @stable unless block
@stable.instance_eval(&block)
@ -2913,7 +2969,6 @@ class Formula
# or (if autodetect fails):
# <pre>head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg</pre>
def head(val = nil, specs = {}, &block)
@head ||= HeadSoftwareSpec.new(flags: build_flags)
if block
@head.instance_eval(&block)
elsif val
@ -3064,11 +3119,6 @@ class Formula
@plist_manual = options[:manual]
end
# @private
def conflicts
@conflicts ||= []
end
# One or more formulae that conflict with this one and why.
# <pre>conflicts_with "imagemagick", because: "both install `convert` binaries"</pre>
def conflicts_with(*names)
@ -3089,11 +3139,6 @@ class Formula
skip_clean_paths.merge(paths)
end
# @private
def skip_clean_paths
@skip_clean_paths ||= Set.new
end
# Software that will not be symlinked into the `brew --prefix` and will
# only live in its Cellar. Other formulae can depend on it and Homebrew
# will add the necessary includes, libraries, and other paths while
@ -3197,7 +3242,6 @@ class Formula
# regex /foo-(\d+(?:\.\d+)+)\.tar/
# end</pre>
def livecheck(&block)
@livecheck ||= Livecheck.new(self)
return @livecheck unless block
@livecheckable = true
@ -3353,11 +3397,6 @@ class Formula
link_overwrite_paths.merge(paths)
end
# @private
def link_overwrite_paths
@link_overwrite_paths ||= Set.new
end
# Permit links to certain libraries that don't exist. Available on Linux only.
def ignore_missing_libraries(*libs)
unless Homebrew::SimulateSystem.simulating_or_running_on_linux?
@ -3371,11 +3410,6 @@ class Formula
allowed_missing_libraries.merge(libraries)
end
# @private
def allowed_missing_libraries
@allowed_missing_libraries ||= Set.new
end
end
end

View File

@ -52,14 +52,16 @@ module Homebrew
def audit_file
if formula.core_formula? && @versioned_formula
unversioned_name = formula.name.gsub(/@.*$/, "")
# ignore when an unversioned formula doesn't exist after an explicit rename
return if formula.tap.formula_renames.key?(unversioned_name)
# build this ourselves as we want e.g. homebrew/core to be present
full_name = "#{formula.tap}/#{unversioned_name}"
unversioned_formula = begin
# build this ourselves as we want e.g. homebrew/core to be present
full_name = if formula.tap
"#{formula.tap}/#{formula.name}"
else
formula.name
end
Formulary.factory(full_name.gsub(/@.*$/, "")).path
Formulary.factory(full_name).path
rescue FormulaUnavailableError, TapFormulaAmbiguityError,
TapFormulaWithOldnameAmbiguityError
Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb")
@ -194,6 +196,13 @@ module Homebrew
if formula.license.present?
licenses, exceptions = SPDX.parse_license_expression formula.license
sspl_licensed = licenses.any? { |license| license.to_s.start_with?("SSPL") }
if sspl_licensed && @core_tap
problem <<~EOS
Formula #{formula.name} is SSPL-licensed. Software under the SSPL must not be packaged in homebrew/core.
EOS
end
non_standard_licenses = licenses.reject { |license| SPDX.valid_license? license }
if non_standard_licenses.present?
problem <<~EOS
@ -344,6 +353,10 @@ module Homebrew
recursive_runtime_formulae.each do |f|
name = f.name
unversioned_name, = name.split("@")
# Allow use of the full versioned name (e.g. `python@3.99`) or an unversioned alias (`python`).
next if formula.tap&.audit_exception :versioned_formula_dependent_conflicts_allowlist, name
next if formula.tap&.audit_exception :versioned_formula_dependent_conflicts_allowlist, unversioned_name
version_hash[unversioned_name] ||= Set.new
version_hash[unversioned_name] << name
next if version_hash[unversioned_name].length < 2
@ -401,6 +414,21 @@ module Homebrew
end
end
def audit_gcc_dependency
return unless @core_tap
return if !@strict && !(@git && formula.tap.git?) # git log is required for non-strict audit
return unless Homebrew::SimulateSystem.simulating_or_running_on_linux?
return unless linux_only_gcc_dep?(formula)
bad_gcc_dep = @strict || begin
fv = FormulaVersions.new(formula)
fv.formula_at_revision("origin/HEAD") { |prev_f| !linux_only_gcc_dep?(prev_f) }
end
return unless bad_gcc_dep
problem "Formulae in homebrew/core should not have a Linux-only dependency on GCC."
end
def audit_postgresql
return unless formula.name == "postgresql"
return unless @core_tap
@ -419,11 +447,12 @@ module Homebrew
def audit_glibc
return unless @core_tap
return if formula.name != "glibc"
return if [OS::CI_GLIBC_VERSION, "2.27", "2.31", "2.35"].include?(formula.version.to_s)
# Also allow LINUX_GLIBC_NEXT_CI_VERSION for when we're upgrading.
return if [OS::LINUX_GLIBC_CI_VERSION, OS::LINUX_GLIBC_NEXT_CI_VERSION].include?(formula.version.to_s)
problem "The glibc version must be #{OS::CI_GLIBC_VERSION}, as this is the version used by our CI on Linux. " \
"Glibc is for users who have a system Glibc with a lower version, " \
"which allows them to use our Linux bottles, which were compiled against system Glibc on CI."
problem "The glibc version must be #{OS::LINUX_GLIBC_CI_VERSION}, as needed by our CI on Linux. " \
"The glibc formula is for users who have a system glibc with a lower version, " \
"which allows them to use our Linux bottles, which were compiled against system glibc on CI."
end
ELASTICSEARCH_KIBANA_RELICENSED_VERSION = "7.11"
@ -462,17 +491,14 @@ module Homebrew
return unless DevelopmentTools.curl_handles_most_https_certificates?
use_homebrew_curl = false
%w[Stable HEAD].each do |name|
spec_name = name.downcase.to_sym
next unless (spec = formula.send(spec_name))
use_homebrew_curl = [:stable, :head].any? do |spec_name|
next false unless (spec = formula.send(spec_name))
use_homebrew_curl = spec.using == :homebrew_curl
break if use_homebrew_curl
spec.using == :homebrew_curl
end
if (http_content_problem = curl_check_http_content(homepage,
"homepage URL",
SharedAudits::URL_TYPE_HOMEPAGE,
user_agents: [:browser, :default],
check_content: true,
strict: @strict,
@ -848,5 +874,33 @@ module Homebrew
def head_only?(formula)
formula.head && formula.stable.nil?
end
def linux_only_gcc_dep?(formula)
odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos?
return false if formula.deps.map(&:name).exclude?("gcc")
variations = formula.to_hash_with_variations["variations"]
# The formula has no variations, so all OS-version-arch triples depend on GCC.
return false if variations.blank?
MacOSVersions::SYMBOLS.each_key do |macos_version|
[:arm, :intel].each do |arch|
bottle_tag = Utils::Bottles::Tag.new(system: macos_version, arch: arch)
next unless bottle_tag.valid_combination?
variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies")
# This variation either:
# 1. does not exist
# 2. has no variation-specific dependencies
# In either case, it matches Linux. We must check for `nil` because an empty
# array indicates that this variation does not depend on GCC.
return false if variation_dependencies.nil?
# We found a non-Linux variation that depends on GCC.
return false if variation_dependencies.include?("gcc")
end
end
true
end
end
end

View File

@ -581,11 +581,9 @@ class FormulaInstaller
end
def expand_dependencies_for_formula(formula, inherited_options)
any_bottle_used = false
# Cache for this expansion only. FormulaInstaller has a lot of inputs which can alter expansion.
cache_key = "FormulaInstaller-#{formula.full_name}-#{Time.now.to_f}"
expanded_deps = Dependency.expand(formula, cache_key: cache_key) do |dependent, dep|
Dependency.expand(formula, cache_key: cache_key) do |dependent, dep|
inherited_options[dep.name] |= inherited_options_for(dep)
build = effective_build_options_for(
dependent,
@ -601,36 +599,14 @@ class FormulaInstaller
Dependency.prune
elsif dep.satisfied?(inherited_options[dep.name])
Dependency.skip
else
any_bottle_used ||= install_bottle_for?(dep.to_formula, build)
end
end
[expanded_deps, any_bottle_used]
end
def expand_dependencies
inherited_options = Hash.new { |hash, key| hash[key] = Options.new }
any_bottle_used = pour_bottle?
expanded_deps, any_dep_bottle_used = expand_dependencies_for_formula(formula, inherited_options)
any_bottle_used ||= any_dep_bottle_used
# We require some dependencies (glibc, GCC 5, etc.) if binaries were built.
# Native binaries shouldn't exist in cross-platform `all` bottles.
if any_bottle_used && !formula.bottled?(:all) && !Keg.bottle_dependencies.empty?
all_bottle_deps = Keg.bottle_dependencies.flat_map do |bottle_dep|
bottle_dep.recursive_dependencies.map(&:name) + [bottle_dep.name]
end
if all_bottle_deps.exclude?(formula.name)
bottle_deps = Keg.bottle_dependencies.flat_map do |bottle_dep|
expanded_bottle_deps, = expand_dependencies_for_formula(bottle_dep, inherited_options)
expanded_bottle_deps
end
expanded_deps = Dependency.merge_repeats(bottle_deps + expanded_deps)
end
end
expanded_deps = expand_dependencies_for_formula(formula, inherited_options)
expanded_deps.map { |dep| [dep, inherited_options[dep.name]] }
end
@ -799,6 +775,8 @@ class FormulaInstaller
fix_dynamic_linkage(keg) if !@poured_bottle || !formula.bottle_specification.skip_relocation?
Homebrew::Install.global_post_install
if build_bottle?
ohai "Not running 'post_install' as we're building a bottle"
puts "You can run it manually using:"
@ -1187,6 +1165,8 @@ class FormulaInstaller
if pour_bottle?(output_warning: true)
formula.fetch_bottle_tab
elsif formula.core_formula? && Homebrew::EnvConfig.install_from_api?
odie "Unable to build #{formula.name} from source with HOMEBREW_INSTALL_FROM_API."
else
formula.fetch_patches
formula.resources.each(&:fetch)
@ -1222,6 +1202,7 @@ class FormulaInstaller
tab.unused_options = []
tab.built_as_bottle = true
tab.poured_from_bottle = true
tab.loaded_from_api = formula.class.loaded_from_api
tab.installed_as_dependency = installed_as_dependency?
tab.installed_on_request = installed_on_request?
tab.time = Time.now.to_i

View File

@ -194,7 +194,7 @@ module Formulary
depends_on dep
end
[:build, :recommended, :optional].each do |type|
[:build, :test, :recommended, :optional].each do |type|
json_formula["#{type}_dependencies"].each do |dep|
next if uses_from_macos_names.include? dep
@ -217,6 +217,7 @@ module Formulary
end
end
klass.loaded_from_api = true
mod.const_set(class_s, klass)
cache[:api] ||= {}
@ -529,6 +530,14 @@ module Formulary
end
end
# Load aliases from the API.
class AliasAPILoader < FormulaAPILoader
def initialize(alias_name)
super Homebrew::API::Formula.all_aliases[alias_name]
@alias_path = Formulary.core_alias_path(alias_name).to_s
end
end
# Return a {Formula} instance for the given reference.
# `ref` is a string containing:
#
@ -655,22 +664,29 @@ module Formulary
if ref.start_with?("homebrew/core/") && Homebrew::EnvConfig.install_from_api?
name = ref.split("/", 3).last
return FormulaAPILoader.new(name) if Homebrew::API::Formula.all_formulae.key?(name)
return AliasAPILoader.new(name) if Homebrew::API::Formula.all_aliases.key?(name)
end
return TapLoader.new(ref, from: from)
end
return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && Pathname.new(ref).expand_path.exist?
pathname_ref = Pathname.new(ref)
return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && pathname_ref.expand_path.exist?
if Homebrew::EnvConfig.install_from_api? && Homebrew::API::Formula.all_formulae.key?(ref)
return FormulaAPILoader.new(ref)
if Homebrew::EnvConfig.install_from_api?
return FormulaAPILoader.new(ref) if Homebrew::API::Formula.all_formulae.key?(ref)
return AliasAPILoader.new(ref) if Homebrew::API::Formula.all_aliases.key?(ref)
end
formula_with_that_name = core_path(ref)
return FormulaLoader.new(ref, formula_with_that_name) if formula_with_that_name.file?
possible_alias = CoreTap.instance.alias_dir/ref
return AliasLoader.new(possible_alias) if possible_alias.file?
possible_alias = if pathname_ref.absolute?
pathname_ref
else
core_alias_path(ref)
end
return AliasLoader.new(possible_alias) if possible_alias.symlink?
possible_tap_formulae = tap_paths(ref)
raise TapFormulaAmbiguityError.new(ref, possible_tap_formulae) if possible_tap_formulae.size > 1
@ -705,6 +721,10 @@ module Formulary
CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb"
end
def self.core_alias_path(name)
CoreTap.instance.alias_dir/name.to_s.downcase
end
def self.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"])
name = name.to_s.downcase
taps.map do |tap|

View File

@ -333,9 +333,9 @@ class GitHubPackages
os_version ||= "macOS #{bottle_tag.to_macos_version}"
when "linux"
os_version&.delete_suffix!(" LTS")
os_version ||= OS::CI_OS_VERSION
os_version ||= OS::LINUX_CI_OS_VERSION
glibc_version = tab["built_on"]["glibc_version"].presence if tab["built_on"].present?
glibc_version ||= OS::CI_GLIBC_VERSION
glibc_version ||= OS::LINUX_GLIBC_CI_VERSION
cpu_variant = tab["oldest_cpu_family"] || Hardware::CPU::INTEL_64BIT_OLDEST_CPU.to_s
end

View File

@ -45,6 +45,7 @@ HOMEBREW_REQUIRED_RUBY_VERSION = ENV.fetch("HOMEBREW_REQUIRED_RUBY_VERSION").fre
HOMEBREW_PRODUCT = ENV.fetch("HOMEBREW_PRODUCT").freeze
HOMEBREW_VERSION = ENV.fetch("HOMEBREW_VERSION").freeze
HOMEBREW_WWW = "https://brew.sh"
HOMEBREW_DOCS_WWW = "https://docs.brew.sh"
HOMEBREW_SYSTEM = ENV.fetch("HOMEBREW_SYSTEM").freeze
HOMEBREW_PROCESSOR = ENV.fetch("HOMEBREW_PROCESSOR").freeze

View File

@ -11,7 +11,7 @@ module Hardware
INTEL_64BIT_ARCHS = [:x86_64].freeze
PPC_32BIT_ARCHS = [:ppc, :ppc32, :ppc7400, :ppc7450, :ppc970].freeze
PPC_64BIT_ARCHS = [:ppc64, :ppc64le, :ppc970].freeze
ARM_64BIT_ARCHS = [:arm64].freeze
ARM_64BIT_ARCHS = [:arm64, :aarch64].freeze
ALL_ARCHS = [
*INTEL_32BIT_ARCHS,
*INTEL_64BIT_ARCHS,

View File

@ -30,6 +30,10 @@ module Homebrew
Diagnostic.checks(:build_from_source_checks, fatal: all_fatal)
end
def global_post_install; end
alias generic_global_post_install global_post_install
module_function :generic_global_post_install
def check_prefix
if (Hardware::CPU.intel? || Hardware::CPU.in_rosetta2?) &&
HOMEBREW_PREFIX.to_s == HOMEBREW_MACOS_ARM_DEFAULT_PREFIX

View File

@ -366,17 +366,6 @@ class Keg
def self.file_linked_libraries(_file, _string)
[]
end
def self.bottle_dependencies
return [] unless Homebrew::SimulateSystem.simulating_or_running_on_linux?
@bottle_dependencies ||= begin
formulae = []
gcc = Formulary.factory(CompilerSelector.preferred_gcc)
formulae << gcc if DevelopmentTools.non_apple_gcc_version("gcc") < gcc.version.to_i
formulae
end
end
end
require "extend/os/keg_relocate"

View File

@ -80,11 +80,16 @@ class LinkageChecker
alias generic_display_test_output display_test_output
private :generic_display_test_output
sig { params(strict: T::Boolean).returns(T::Boolean) }
def broken_library_linkage?(strict: false)
issues = [@broken_deps, @unwanted_system_dylibs, @version_conflict_deps]
issues += [@undeclared_deps, @files_missing_rpaths] if strict
[issues, unexpected_broken_dylibs, unexpected_present_dylibs].flatten.any?(&:present?)
sig { params(test: T::Boolean, strict: T::Boolean).returns(T::Boolean) }
def broken_library_linkage?(test: false, strict: false)
raise ArgumentError, "Strict linkage checking requires test mode to be enabled." if strict && !test
issues = [@broken_deps, unexpected_broken_dylibs]
if test
issues += [@unwanted_system_dylibs, @version_conflict_deps, unexpected_present_dylibs]
issues += [@undeclared_deps, @files_missing_rpaths] if strict
end
issues.any?(&:present?)
end
alias generic_broken_library_linkage? broken_library_linkage?
private :generic_broken_library_linkage?
@ -306,9 +311,12 @@ class LinkageChecker
def harmless_broken_link?(dylib)
# libgcc_s_* is referenced by programs that use the Java Service Wrapper,
# and is harmless on x86(_64) machines
# dyld will fall back to Apple libc++ if LLVM's is not available.
[
"/usr/lib/libgcc_s_ppc64.1.dylib",
"/opt/local/lib/libgcc/libgcc_s.1.dylib",
# TODO: Report linkage with `/usr/lib/libc++.1.dylib` when this link is broken.
"#{HOMEBREW_PREFIX}/opt/llvm/lib/libc++.1.dylib",
].include?(dylib)
end

View File

@ -126,11 +126,11 @@ module Homebrew
if debug
# Print the chain of references for debugging
puts "Reference Chain:"
puts formula_or_cask_name(first_formula_or_cask, full_name: full_name)
puts package_or_resource_name(first_formula_or_cask, full_name: full_name)
references << referenced_formula_or_cask
references.each do |ref_formula_or_cask|
puts formula_or_cask_name(ref_formula_or_cask, full_name: full_name)
puts package_or_resource_name(ref_formula_or_cask, full_name: full_name)
end
end
@ -157,11 +157,14 @@ module Homebrew
# Executes the livecheck logic for each formula/cask in the
# `formulae_and_casks_to_check` array and prints the results.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
sig {
params(
formulae_and_casks_to_check: T::Array[T.any(Formula, Cask::Cask)],
full_name: T::Boolean,
handle_name_conflict: T::Boolean,
check_resources: T::Boolean,
json: T::Boolean,
newer_only: T::Boolean,
debug: T::Boolean,
@ -171,24 +174,25 @@ module Homebrew
}
def run_checks(
formulae_and_casks_to_check,
full_name: false, handle_name_conflict: false, json: false, newer_only: false,
full_name: false, handle_name_conflict: false, check_resources: false, json: false, newer_only: false,
debug: false, quiet: false, verbose: false
)
load_other_tap_strategies(formulae_and_casks_to_check)
ambiguous_casks = []
if handle_name_conflict
ambiguous_casks = formulae_and_casks_to_check.group_by { |item| formula_or_cask_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
ambiguous_casks = formulae_and_casks_to_check \
.group_by { |item| package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
end
ambiguous_names = []
unless full_name
ambiguous_names =
(formulae_and_casks_to_check - ambiguous_casks).group_by { |item| formula_or_cask_name(item) }
(formulae_and_casks_to_check - ambiguous_casks).group_by { |item| package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
@ -218,7 +222,7 @@ module Homebrew
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
use_full_name = full_name || ambiguous_names.include?(formula_or_cask)
name = formula_or_cask_name(formula_or_cask, full_name: use_full_name)
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
referenced_formula_or_cask, livecheck_references =
resolve_livecheck_reference(formula_or_cask, full_name: use_full_name, debug: debug)
@ -282,6 +286,30 @@ module Homebrew
version_info[:latest] if version_info.present?
end
check_for_resources = check_resources && formula_or_cask.is_a?(Formula) && formula_or_cask.resources.present?
if check_for_resources
resource_version_info = formula_or_cask.resources.map do |resource|
res_skip_info ||= SkipConditions.skip_information(resource, verbose: verbose)
if res_skip_info.present?
res_skip_info
else
res_version_info = resource_version(
resource,
json: json,
debug: debug,
quiet: quiet,
verbose: verbose,
)
if res_version_info.empty?
status_hash(resource, "error", ["Unable to get versions"], verbose: verbose)
else
res_version_info
end
end
end.compact_blank
Homebrew.failed = true if resource_version_info.any? { |info| info[:status] == "error" }
end
if latest.blank?
no_versions_msg = "Unable to get versions"
raise Livecheck::Error, no_versions_msg unless json
@ -289,7 +317,14 @@ module Homebrew
next version_info if version_info.is_a?(Hash) && version_info[:status] && version_info[:messages]
next status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name, verbose: verbose)
latest_info = status_hash(formula_or_cask, "error", [no_versions_msg], full_name: use_full_name,
verbose: verbose)
if check_for_resources
resource_version_info.map! { |r| r.except!(:meta) } unless verbose
latest_info[:resources] = resource_version_info
end
next latest_info
end
if (m = latest.to_s.match(/(.*)-release$/)) && !current.to_s.match(/.*-release$/)
@ -324,6 +359,8 @@ module Homebrew
info[:meta][:head_only] = true if formula&.head_only?
info[:meta].merge!(version_info[:meta]) if version_info.present? && version_info.key?(:meta)
info[:resources] = resource_version_info if check_for_resources
next if newer_only && !info[:version][:outdated]
has_a_newer_upstream_version ||= true
@ -331,10 +368,12 @@ module Homebrew
if json
progress&.increment
info.except!(:meta) unless verbose
resource_version_info.map! { |r| r.except!(:meta) } if check_for_resources && !verbose
next info
end
puts if debug
print_latest_version(info, verbose: verbose, ambiguous_cask: ambiguous_casks.include?(formula_or_cask))
print_resources_info(resource_version_info, verbose: verbose) if check_for_resources
nil
rescue => e
Homebrew.failed = true
@ -344,11 +383,12 @@ module Homebrew
progress&.increment
status_hash(formula_or_cask, "error", [e.to_s], full_name: use_full_name, verbose: verbose) unless quiet
elsif !quiet
name = formula_or_cask_name(formula_or_cask, full_name: use_full_name)
name = package_or_resource_name(formula_or_cask, full_name: use_full_name)
name += " (cask)" if ambiguous_casks.include?(formula_or_cask)
onoe "#{Tty.blue}#{name}#{Tty.reset}: #{e}"
$stderr.puts e.backtrace if debug && !e.is_a?(Livecheck::Error)
print_resources_info(resource_version_info, verbose: verbose) if check_for_resources
nil
end
end
@ -367,16 +407,20 @@ module Homebrew
puts JSON.pretty_generate(formulae_checked.compact)
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), full_name: T::Boolean).returns(String) }
def formula_or_cask_name(formula_or_cask, full_name: false)
case formula_or_cask
sig { params(package_or_resource: T.any(Formula, Cask::Cask, Resource), full_name: T::Boolean).returns(String) }
def package_or_resource_name(package_or_resource, full_name: false)
case package_or_resource
when Formula
formula_name(formula_or_cask, full_name: full_name)
formula_name(package_or_resource, full_name: full_name)
when Cask::Cask
cask_name(formula_or_cask, full_name: full_name)
cask_name(package_or_resource, full_name: full_name)
when Resource
package_or_resource.name
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
end
@ -396,40 +440,44 @@ module Homebrew
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
status_str: String,
messages: T.nilable(T::Array[String]),
full_name: T::Boolean,
verbose: T::Boolean,
package_or_resource: T.any(Formula, Cask::Cask, Resource),
status_str: String,
messages: T.nilable(T::Array[String]),
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def status_hash(formula_or_cask, status_str, messages = nil, full_name: false, verbose: false)
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
cask = formula_or_cask if formula_or_cask.is_a?(Cask::Cask)
def status_hash(package_or_resource, status_str, messages = nil, full_name: false, verbose: false)
formula = package_or_resource if package_or_resource.is_a?(Formula)
cask = package_or_resource if package_or_resource.is_a?(Cask::Cask)
resource = package_or_resource if package_or_resource.is_a?(Resource)
status_hash = {}
if formula
status_hash[:formula] = formula_name(formula, full_name: full_name)
elsif cask
status_hash[:cask] = cask_name(formula_or_cask, full_name: full_name)
status_hash[:cask] = cask_name(cask, full_name: full_name)
elsif resource
status_hash[:resource] = resource.name
end
status_hash[:status] = status_str
status_hash[:messages] = messages if messages.is_a?(Array)
status_hash[:meta] = {
livecheckable: formula_or_cask.livecheckable?,
livecheckable: package_or_resource.livecheckable?,
}
status_hash[:meta][:head_only] = true if formula&.head_only?
status_hash
end
# Formats and prints the livecheck result for a formula.
# Formats and prints the livecheck result for a formula/cask/resource.
sig { params(info: Hash, verbose: T::Boolean, ambiguous_cask: T::Boolean).void }
def print_latest_version(info, verbose:, ambiguous_cask: false)
formula_or_cask_s = "#{Tty.blue}#{info[:formula] || info[:cask]}#{Tty.reset}"
formula_or_cask_s += " (cask)" if ambiguous_cask
formula_or_cask_s += " (guessed)" if !info[:meta][:livecheckable] && verbose
def print_latest_version(info, verbose: false, ambiguous_cask: false)
package_or_resource_s = info[:resource].present? ? " " : ""
package_or_resource_s += "#{Tty.blue}#{info[:formula] || info[:cask] || info[:resource]}#{Tty.reset}"
package_or_resource_s += " (cask)" if ambiguous_cask
package_or_resource_s += " (guessed)" if verbose && !info[:meta][:livecheckable]
current_s = if info[:version][:newer_than_upstream]
"#{Tty.red}#{info[:version][:current]}#{Tty.reset}"
@ -443,47 +491,61 @@ module Homebrew
info[:version][:latest]
end
puts "#{formula_or_cask_s}: #{current_s} ==> #{latest_s}"
puts "#{package_or_resource_s}: #{current_s} ==> #{latest_s}"
end
# Prints the livecheck result for the resources of a given Formula.
sig { params(info: T::Array[Hash], verbose: T::Boolean).void }
def print_resources_info(info, verbose: false)
info.each do |r_info|
if r_info[:status] && r_info[:messages]
SkipConditions.print_skip_information(r_info)
else
print_latest_version(r_info, verbose: verbose)
end
end
end
sig {
params(
livecheck_url: T.any(String, Symbol),
formula_or_cask: T.any(Formula, Cask::Cask),
livecheck_url: T.any(String, Symbol),
package_or_resource: T.any(Formula, Cask::Cask, Resource),
).returns(T.nilable(String))
}
def livecheck_url_to_string(livecheck_url, formula_or_cask)
def livecheck_url_to_string(livecheck_url, package_or_resource)
case livecheck_url
when String
livecheck_url
when :url
formula_or_cask.url&.to_s if formula_or_cask.is_a?(Cask::Cask)
package_or_resource.url&.to_s if package_or_resource.is_a?(Cask::Cask) || package_or_resource.is_a?(Resource)
when :head, :stable
formula_or_cask.send(livecheck_url)&.url if formula_or_cask.is_a?(Formula)
package_or_resource.send(livecheck_url)&.url if package_or_resource.is_a?(Formula)
when :homepage
formula_or_cask.homepage
package_or_resource.homepage unless package_or_resource.is_a?(Resource)
end
end
# Returns an Array containing the formula/cask URLs that can be used by livecheck.
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(T::Array[String]) }
def checkable_urls(formula_or_cask)
# Returns an Array containing the formula/cask/resource URLs that can be used by livecheck.
sig { params(package_or_resource: T.any(Formula, Cask::Cask, Resource)).returns(T::Array[String]) }
def checkable_urls(package_or_resource)
urls = []
case formula_or_cask
case package_or_resource
when Formula
if formula_or_cask.stable
urls << formula_or_cask.stable.url
urls.concat(formula_or_cask.stable.mirrors)
if package_or_resource.stable
urls << package_or_resource.stable.url
urls.concat(package_or_resource.stable.mirrors)
end
urls << formula_or_cask.head.url if formula_or_cask.head
urls << formula_or_cask.homepage if formula_or_cask.homepage
urls << package_or_resource.head.url if package_or_resource.head
urls << package_or_resource.homepage if package_or_resource.homepage
when Cask::Cask
urls << formula_or_cask.appcast.to_s if formula_or_cask.appcast
urls << formula_or_cask.url.to_s if formula_or_cask.url
urls << formula_or_cask.homepage if formula_or_cask.homepage
urls << package_or_resource.appcast.to_s if package_or_resource.appcast
urls << package_or_resource.url.to_s if package_or_resource.url
urls << package_or_resource.homepage if package_or_resource.homepage
when Resource
urls << package_or_resource.url
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
urls.compact.uniq
@ -561,7 +623,7 @@ module Homebrew
homebrew_curl_root_domains.include?(url_root_domain)
end
# Identifies the latest version of the formula and returns a Hash containing
# Identifies the latest version of the formula/cask and returns a Hash containing
# the version information. Returns nil if a latest version couldn't be found.
# rubocop:disable Metrics/CyclomaticComplexity
sig {
@ -713,7 +775,6 @@ module Homebrew
version.to_s.include?(rejection)
end
end
next if match_version_map.blank?
if debug
@ -770,6 +831,195 @@ module Homebrew
nil
end
# rubocop:enable Metrics/CyclomaticComplexity
# Identifies the latest version of a resource and returns a Hash containing the
# version information. Returns nil if a latest version couldn't be found.
sig {
params(
resource: Resource,
json: T::Boolean,
debug: T::Boolean,
quiet: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def resource_version(
resource,
json: false,
debug: false,
quiet: false,
verbose: false
)
has_livecheckable = resource.livecheckable?
if debug
puts "\n\n"
puts "Resource: #{resource.name}"
puts "Livecheckable?: #{has_livecheckable ? "Yes" : "No"}"
end
resource_version_info = {}
livecheck = resource.livecheck
livecheck_url = livecheck.url
livecheck_regex = livecheck.regex
livecheck_strategy = livecheck.strategy
livecheck_strategy_block = livecheck.strategy_block
livecheck_url_string = livecheck_url_to_string(livecheck_url, resource)
urls = [livecheck_url_string] if livecheck_url_string
urls ||= checkable_urls(resource)
checked_urls = []
# rubocop:disable Metrics/BlockLength
urls.each_with_index do |original_url, i|
# Only preprocess the URL when it's appropriate
url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
original_url
else
preprocess_url(original_url)
end
next if checked_urls.include?(url)
strategies = Strategy.from_url(
url,
livecheck_strategy: livecheck_strategy,
url_provided: livecheck_url.present?,
regex_provided: livecheck_regex.present?,
block_provided: livecheck_strategy_block.present?,
)
strategy = Strategy.from_symbol(livecheck_strategy) || strategies.first
strategy_name = livecheck_strategy_names[strategy]
if debug
puts
if livecheck_url.is_a?(Symbol)
# This assumes the URL symbol will fit within the available space
puts "URL (#{livecheck_url}):".ljust(18, " ") + original_url
else
puts "URL: #{original_url}"
end
puts "URL (processed): #{url}" if url != original_url
if strategies.present? && verbose
puts "Strategies: #{strategies.map { |s| livecheck_strategy_names[s] }.join(", ")}"
end
puts "Strategy: #{strategy.blank? ? "None" : strategy_name}"
puts "Regex: #{livecheck_regex.inspect}" if livecheck_regex.present?
end
if livecheck_strategy.present?
if livecheck_url.blank? && strategy.method(:find_versions).parameters.include?([:keyreq, :url])
odebug "#{strategy_name} strategy requires a URL"
next
elsif livecheck_strategy != :page_match && strategies.exclude?(strategy)
odebug "#{strategy_name} strategy does not apply to this URL"
next
end
end
puts if debug && strategy.blank?
next if strategy.blank?
strategy_data = strategy.find_versions(
url: url,
regex: livecheck_regex,
homebrew_curl: false,
&livecheck_strategy_block
)
match_version_map = strategy_data[:matches]
regex = strategy_data[:regex]
messages = strategy_data[:messages]
checked_urls << url
if messages.is_a?(Array) && match_version_map.blank?
puts messages unless json
next if i + 1 < urls.length
return status_hash(resource, "error", messages, verbose: verbose)
end
if debug
if strategy_data[:url].present? && strategy_data[:url] != url
puts "URL (strategy): #{strategy_data[:url]}"
end
puts "URL (final): #{strategy_data[:final_url]}" if strategy_data[:final_url].present?
if strategy_data[:regex].present? && strategy_data[:regex] != livecheck_regex
puts "Regex (strategy): #{strategy_data[:regex].inspect}"
end
puts "Cached?: Yes" if strategy_data[:cached] == true
end
match_version_map.delete_if do |_match, version|
next true if version.blank?
next false if has_livecheckable
UNSTABLE_VERSION_KEYWORDS.any? do |rejection|
version.to_s.include?(rejection)
end
end
next if match_version_map.blank?
if debug
puts
puts "Matched Versions:"
if verbose
match_version_map.each do |match, version|
puts "#{match} => #{version.inspect}"
end
else
puts match_version_map.values.join(", ")
end
end
res_current = resource.version
res_latest = Version.new(match_version_map.values.max_by { |v| LivecheckVersion.create(resource, v) })
return status_hash(resource, "error", ["Unable to get versions"], verbose: verbose) if res_latest.blank?
is_outdated = res_current < res_latest
is_newer_than_upstream = res_current > res_latest
resource_version_info = {
resource: resource.name,
version: {
current: res_current.to_s,
latest: res_latest.to_s,
outdated: is_outdated,
newer_than_upstream: is_newer_than_upstream,
},
}
resource_version_info[:meta] = { livecheckable: has_livecheckable, url: {} }
if livecheck_url.is_a?(Symbol) && livecheck_url_string
resource_version_info[:meta][:url][:symbol] = livecheck_url
end
resource_version_info[:meta][:url][:original] = original_url
resource_version_info[:meta][:url][:processed] = url if url != original_url
if strategy_data[:url].present? && strategy_data[:url] != url
resource_version_info[:meta][:url][:strategy] = strategy_data[:url]
end
resource_version_info[:meta][:url][:final] = strategy_data[:final_url] if strategy_data[:final_url]
resource_version_info[:meta][:strategy] = strategy.present? ? strategy_name : nil
if strategies.present?
resource_version_info[:meta][:strategies] = strategies.map { |s| livecheck_strategy_names[s] }
end
resource_version_info[:meta][:regex] = regex.inspect if regex.present?
resource_version_info[:meta][:cached] = true if strategy_data[:cached] == true
rescue => e
Homebrew.failed = true
if json
status_hash(resource, "error", [e.to_s], verbose: verbose)
elsif !quiet
onoe "#{Tty.blue}#{resource.name}#{Tty.reset}: #{e}"
$stderr.puts e.backtrace if debug && !e.is_a?(Livecheck::Error)
nil
end
end
# rubocop:enable Metrics/BlockLength
resource_version_info
end
end
# rubocop:enable Metrics/ModuleLength
end

View File

@ -11,15 +11,17 @@ module Homebrew
include Comparable
sig { params(formula_or_cask: T.any(Formula, Cask::Cask), version: Version).returns(LivecheckVersion) }
def self.create(formula_or_cask, version)
versions = case formula_or_cask
when Formula
sig {
params(package_or_resource: T.any(Formula, Cask::Cask, Resource), version: Version).returns(LivecheckVersion)
}
def self.create(package_or_resource, version)
versions = case package_or_resource
when Formula, Resource
[version]
when Cask::Cask
version.to_s.split(/[,:]/).map { |s| Version.new(s) }
else
T.absurd(formula_or_cask)
T.absurd(package_or_resource)
end
new(versions)
end

View File

@ -6,7 +6,7 @@ require "livecheck/livecheck"
module Homebrew
module Livecheck
# The `Livecheck::SkipConditions` module primarily contains methods that
# check for various formula/cask conditions where a check should be skipped.
# check for various formula/cask/resource conditions where a check should be skipped.
#
# @api private
module SkipConditions
@ -16,14 +16,14 @@ module Homebrew
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
livecheckable: T::Boolean,
full_name: T::Boolean,
verbose: T::Boolean,
package_or_resource: T.any(Formula, Cask::Cask, Resource),
livecheckable: T::Boolean,
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def formula_or_cask_skip(formula_or_cask, livecheckable, full_name: false, verbose: false)
formula = formula_or_cask if formula_or_cask.is_a?(Formula)
def package_or_resource_skip(package_or_resource, livecheckable, full_name: false, verbose: false)
formula = package_or_resource if package_or_resource.is_a?(Formula)
if (stable_url = formula&.stable&.url)
stable_is_gist = stable_url.match?(%r{https?://gist\.github(?:usercontent)?\.com/}i)
@ -33,8 +33,8 @@ module Homebrew
stable_from_internet_archive = stable_url.match?(%r{https?://web\.archive\.org/}i)
end
skip_message = if formula_or_cask.livecheck.skip_msg.present?
formula_or_cask.livecheck.skip_msg
skip_message = if package_or_resource.livecheck.skip_msg.present?
package_or_resource.livecheck.skip_msg
elsif !livecheckable
if stable_from_google_code_archive
"Stable URL is from Google Code Archive"
@ -45,10 +45,10 @@ module Homebrew
end
end
return {} if !formula_or_cask.livecheck.skip? && skip_message.blank?
return {} if !package_or_resource.livecheck.skip? && skip_message.blank?
skip_messages = skip_message ? [skip_message] : nil
Livecheck.status_hash(formula_or_cask, "skipped", skip_messages, full_name: full_name, verbose: verbose)
Livecheck.status_hash(package_or_resource, "skipped", skip_messages, full_name: full_name, verbose: verbose)
end
sig {
@ -157,7 +157,7 @@ module Homebrew
# Skip conditions for formulae.
FORMULA_CHECKS = [
:formula_or_cask_skip,
:package_or_resource_skip,
:formula_head_only,
:formula_deprecated,
:formula_disabled,
@ -166,76 +166,85 @@ module Homebrew
# Skip conditions for casks.
CASK_CHECKS = [
:formula_or_cask_skip,
:package_or_resource_skip,
:cask_discontinued,
:cask_version_latest,
:cask_url_unversioned,
].freeze
# If a formula/cask should be skipped, we return a hash from
# Skip conditions for resources.
RESOURCE_CHECKS = [
:package_or_resource_skip,
].freeze
# If a formula/cask/resource should be skipped, we return a hash from
# `Livecheck#status_hash`, which contains a `status` type and sometimes
# error `messages`.
sig {
params(
formula_or_cask: T.any(Formula, Cask::Cask),
full_name: T::Boolean,
verbose: T::Boolean,
package_or_resource: T.any(Formula, Cask::Cask, Resource),
full_name: T::Boolean,
verbose: T::Boolean,
).returns(Hash)
}
def skip_information(formula_or_cask, full_name: false, verbose: false)
livecheckable = formula_or_cask.livecheckable?
def skip_information(package_or_resource, full_name: false, verbose: false)
livecheckable = package_or_resource.livecheckable?
checks = case formula_or_cask
checks = case package_or_resource
when Formula
FORMULA_CHECKS
when Cask::Cask
CASK_CHECKS
when Resource
RESOURCE_CHECKS
end
return {} unless checks
checks.each do |method_name|
skip_hash = send(method_name, formula_or_cask, livecheckable, full_name: full_name, verbose: verbose)
skip_hash = send(method_name, package_or_resource, livecheckable, full_name: full_name, verbose: verbose)
return skip_hash if skip_hash.present?
end
{}
end
# Skip conditions for formulae/casks referenced in a `livecheck` block
# Skip conditions for formulae/casks/resources referenced in a `livecheck` block
# are treated differently than normal. We only respect certain skip
# conditions (returning the related hash) and others are treated as
# errors.
sig {
params(
livecheck_formula_or_cask: T.any(Formula, Cask::Cask),
original_formula_or_cask_name: String,
full_name: T::Boolean,
verbose: T::Boolean,
livecheck_package_or_resource: T.any(Formula, Cask::Cask, Resource),
original_package_or_resource_name: String,
full_name: T::Boolean,
verbose: T::Boolean,
).returns(T.nilable(Hash))
}
def referenced_skip_information(
livecheck_formula_or_cask,
original_formula_or_cask_name,
livecheck_package_or_resource,
original_package_or_resource_name,
full_name: false,
verbose: false
)
skip_info = SkipConditions.skip_information(
livecheck_formula_or_cask,
livecheck_package_or_resource,
full_name: full_name,
verbose: verbose,
)
return if skip_info.blank?
referenced_name = Livecheck.formula_or_cask_name(livecheck_formula_or_cask, full_name: full_name)
referenced_type = case livecheck_formula_or_cask
referenced_name = Livecheck.package_or_resource_name(livecheck_package_or_resource, full_name: full_name)
referenced_type = case livecheck_package_or_resource
when Formula
:formula
when Cask::Cask
:cask
when Resource
:resource
end
if skip_info[:status] != "error" &&
!(skip_info[:status] == "skipped" && livecheck_formula_or_cask.livecheck.skip?)
!(skip_info[:status] == "skipped" && livecheck_package_or_resource.livecheck.skip?)
error_msg_end = if skip_info[:status] == "skipped"
"automatically skipped"
else
@ -245,7 +254,7 @@ module Homebrew
raise "Referenced #{referenced_type} (#{referenced_name}) is #{error_msg_end}"
end
skip_info[referenced_type] = original_formula_or_cask_name
skip_info[referenced_type] = original_package_or_resource_name
skip_info
end
@ -258,6 +267,8 @@ module Homebrew
skip_hash[:formula]
elsif skip_hash[:cask].is_a?(String)
skip_hash[:cask]
elsif skip_hash[:resource].is_a?(String)
" #{skip_hash[:resource]}"
end
return unless name

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