Merge branch 'main' into remove-nilable-booleans

This commit is contained in:
Anton Melnikov
2026-08-07 18:49:17 +02:00
committed by GitHub
277 changed files with 3959 additions and 3302 deletions
+2
View File
@@ -65,6 +65,8 @@ updates:
- dependency-type: all
cooldown:
default-days: 7
exclude:
- Homebrew/actions/*
- package-ecosystem: uv
directories:
- "/Library/Homebrew/formula-analytics/"
+3 -3
View File
@@ -34,12 +34,12 @@ jobs:
steps:
- name: Set up Homebrew
id: setup-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
- uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
# Don't cache the prefix for the formulae.brew.sh repository as it won't download its own JSON API files
if: github.repository != 'Homebrew/formulae.brew.sh'
with:
@@ -101,7 +101,7 @@ jobs:
path: results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
sarif_file: results.sarif
category: zizmor
+6 -1
View File
@@ -8,6 +8,11 @@ on:
- completions/**
- docs/Manpage.md
- manpages/brew.1
- Library/Homebrew/cmd/**
- Library/Homebrew/dev-cmd/**
- Library/Homebrew/cli/parser.rb
- Library/Homebrew/completions.rb
- Library/Homebrew/env_config.rb
permissions:
contents: read
@@ -27,7 +32,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
+6 -10
View File
@@ -37,7 +37,6 @@ jobs:
pull-requests: write
env:
GH_TOKEN: ${{ github.token }}
PR_AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TEMPLATE_URL: ${{ github.server_url }}/${{ github.repository }}/blob/main/.github/PULL_REQUEST_TEMPLATE.md
steps:
@@ -78,14 +77,9 @@ jobs:
- name: Check pull request template
id: template
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
case "${PR_AUTHOR_ASSOCIATION}" in
OWNER | MEMBER | COLLABORATOR | CONTRIBUTOR)
echo "complete_template=true" >>"${GITHUB_OUTPUT:?}"
exit 0
;;
esac
# homebrew-core and homebrew-cask allow condensed PR bodies from recognised bump tools.
if [[ "${GITHUB_REPOSITORY}" == "Homebrew/homebrew-core" ||
"${GITHUB_REPOSITORY}" == "Homebrew/homebrew-cask" ]]
@@ -101,7 +95,9 @@ jobs:
complete_template="$(
ruby "${RUNNER_TEMP:?}/check_template.rb" pull-request \
"${RUNNER_TEMP}/check-prs/body" \
"${RUNNER_TEMP}/check-prs/template"
"${RUNNER_TEMP}/check-prs/template" \
"${PR_TITLE}" \
"${GITHUB_REPOSITORY:?}"
)"
case "${complete_template}" in
true | false) ;;
@@ -161,7 +157,7 @@ jobs:
--raw-field body="$(
cat <<COMMENT
<!-- incomplete-pr-template -->
Thanks for your pull request. This has been closed because it appears to use an incomplete or outdated pull request template.
Thanks for your pull request. This has been closed because it appears to be missing the pull request template, perhaps because this was written by an AI not a human. We require humans to read and fill in these templates.
Please edit this pull request to fill in the current [pull request template](${PR_TEMPLATE_URL:?}). This workflow will reopen this pull request automatically once the template is complete. **Do not open a new pull request for this.**
COMMENT
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
with:
languages: ruby
config: |
@@ -35,4 +35,4 @@ jobs:
- Library/Homebrew/vendor
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: true
cask: true
@@ -25,7 +25,7 @@ jobs:
- run: brew install-bundler-gems --groups=all
# install Homebrew formulae we might need
- uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
- uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: shellcheck shfmt gh gnu-tar subversion curl
workflow-key: copilot-setup-steps
+3 -3
View File
@@ -178,7 +178,7 @@ jobs:
echo "push=$(jq --raw-output "${filter}" <<<"${PUSH}")" >>"${GITHUB_OUTPUT}"
- name: Log in to GitHub Packages (github-actions[bot])
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: github-actions[bot]
@@ -206,7 +206,7 @@ jobs:
- name: Log in to GitHub Packages (BrewTestBot)
if: fromJSON(steps.attributes.outputs.push)
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: BrewTestBot
@@ -265,7 +265,7 @@ jobs:
merge-multiple: true
- name: Log in to GitHub Packages (BrewTestBot)
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: BrewTestBot
+3 -3
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
@@ -54,7 +54,7 @@ jobs:
run: vale docs/
- name: Setup Ruby
uses: Homebrew/actions/setup-ruby@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-ruby@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
bundler-cache: true
portable-ruby: true
@@ -128,7 +128,7 @@ jobs:
issues: write # for Homebrew/actions/create-or-update-issue
steps:
- name: Open, update, or close deploy issue
uses: Homebrew/actions/create-or-update-issue@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/create-or-update-issue@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ github.token }}
repository: ${{ github.repository }}
+2 -2
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
persist-credentials: false
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
- name: Install git-pkgs
run: brew install git-pkgs
+3 -3
View File
@@ -77,13 +77,13 @@ jobs:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Install Pandoc
uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: pandoc
workflow-key: release
@@ -212,7 +212,7 @@ jobs:
fi
- name: Generate build provenance
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
with:
subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg
+5 -5
View File
@@ -24,19 +24,19 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: BrewTestBot
- name: Set up commit signing
if: github.actor != 'dependabot[bot]'
uses: Homebrew/actions/setup-commit-signing@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-commit-signing@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@@ -80,7 +80,7 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true' && github.actor != 'dependabot[bot]'
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@@ -106,7 +106,7 @@ jobs:
issues: write
steps:
- name: Open, update, or close schema issue
uses: Homebrew/actions/create-or-update-issue@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/create-or-update-issue@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
title: Failed to update SBOM schema
body: >
+5 -5
View File
@@ -30,20 +30,20 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
if: github.event_name != 'pull_request'
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: BrewTestBot
- name: Set up commit signing
if: github.event_name != 'pull_request' && github.actor != 'dependabot[bot]'
uses: Homebrew/actions/setup-commit-signing@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-commit-signing@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@@ -99,7 +99,7 @@ jobs:
- name: Push commits
if: steps.commit.outputs.committed == 'true' && github.actor != 'dependabot[bot]'
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@@ -125,7 +125,7 @@ jobs:
issues: write
steps:
- name: Open, update, or close Sorbet issue
uses: Homebrew/actions/create-or-update-issue@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/create-or-update-issue@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
title: Failed to update RBI files
body: >
+5 -5
View File
@@ -24,19 +24,19 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: BrewTestBot
- name: Set up commit signing
if: github.actor != 'dependabot[bot]'
uses: Homebrew/actions/setup-commit-signing@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-commit-signing@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@@ -74,7 +74,7 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true' && github.actor != 'dependabot[bot]'
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@@ -100,7 +100,7 @@ jobs:
issues: write
steps:
- name: Open, update, or close SPDX issue
uses: Homebrew/actions/create-or-update-issue@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/create-or-update-issue@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
title: Failed to update SPDX license data
body: >
@@ -33,18 +33,18 @@ jobs:
steps:
- name: Setup Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: BrewTestBot
- name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-commit-signing@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@@ -124,7 +124,7 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@@ -149,7 +149,7 @@ jobs:
issues: write
steps:
- name: Open, update, or close sponsors, maintainers, manpage and completions issue
uses: Homebrew/actions/create-or-update-issue@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/create-or-update-issue@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
title: Failed to update sponsors, maintainers, manpage and completions
body: >
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale Issues and Pull Requests
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
@@ -69,7 +69,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 2
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
contents: write
steps:
- name: Configure Git user
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: github-actions[bot]
+2 -2
View File
@@ -27,11 +27,11 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@5037c6f1012cc5ea0b9f03fedbaf9955ed9e51e0 # 2026.08.03.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
- name: Set up git
id: git_setup
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
- name: Bump formulae
env:
+3 -3
View File
@@ -31,13 +31,13 @@ jobs:
pull-requests: write
steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up git
id: git_setup
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
- name: Pull bottles
env:
@@ -59,6 +59,6 @@ jobs:
fi
- name: Push commits
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
branch: TAP_NEW_BRANCH
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
+15 -17
View File
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
@@ -50,7 +50,7 @@ jobs:
run: brew install-bundler-gems --groups=style,typecheck
- name: Install actionlint, shellcheck and shfmt
uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: actionlint shellcheck shfmt
workflow-key: tests-syntax
@@ -74,7 +74,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: true
cask: true
@@ -112,7 +112,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: true
cask: false
@@ -134,7 +134,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: true
cask: true
@@ -156,13 +156,13 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: BrewTestBot
@@ -197,7 +197,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
@@ -239,7 +239,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
# We only test needs_homebrew_core tests on macOS because
# homebrew/core is not available by default on GitHub-hosted Ubuntu
@@ -271,14 +271,14 @@ jobs:
- name: Install brew tests --online dependencies
if: matrix.name == 'tests (online)'
uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: curl subversion
workflow-key: tests-tests-online
- name: Install brew tests macOS dependencies
if: runner.os != 'Linux'
uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: subversion gnupg
workflow-key: tests-tests-macos
@@ -298,7 +298,6 @@ jobs:
run: brew tests ${{ matrix.test-flags }}
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_SANDBOX_LINUX_LANDLOCK: 1
# These cannot be queried at the macOS level on GitHub Actions.
HOMEBREW_LANGUAGES: en-GB
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -359,7 +358,6 @@ jobs:
- name: test-bot (macOS arm64)
runs-on: macos-26
env:
HOMEBREW_SANDBOX_LINUX_LANDLOCK: 1
HOMEBREW_TEST_BOT_ANALYTICS: 1
steps:
- name: Install Homebrew and Homebrew's dependencies
@@ -399,12 +397,12 @@ jobs:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: true
cask: false
- uses: Homebrew/actions/cache-homebrew-prefix@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
- uses: Homebrew/actions/cache-homebrew-prefix@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
install: gnu-tar
workflow-key: test-bot
@@ -444,7 +442,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
@@ -496,7 +494,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
- name: Cache Homebrew Bundler RubyGems
id: cache
+3 -3
View File
@@ -36,14 +36,14 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
- name: Configure Git user
if: github.event_name != 'pull_request'
uses: Homebrew/actions/git-user-config@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-user-config@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
username: github-actions[bot]
@@ -96,7 +96,7 @@ jobs:
- name: Push to pull request
if: github.event_name != 'pull_request'
uses: Homebrew/actions/git-try-push@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/git-try-push@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
token: ${{ github.token }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@df4b09108a1de9d6f995fe68f302b3f68bd6d2ef # 2026.07.20.1
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
with:
core: false
cask: false
+1
View File
@@ -77,6 +77,7 @@
# Ignore dependencies we don't wish to vendor
**/vendor/bundle/ruby/*/gems/ast-*/
**/vendor/bundle/ruby/*/gems/base64-*/
**/vendor/bundle/ruby/*/gems/benchmark-*/
**/vendor/bundle/ruby/*/gems/bigdecimal-*/
**/vendor/bundle/ruby/*/gems/bootsnap-*/
-1
View File
@@ -29,7 +29,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
&& retry apt-get update --error-on=any \
&& apt-get install -y --no-install-recommends \
acl \
bubblewrap \
bzip2 \
ca-certificates \
curl \
+49 -6
View File
@@ -24,6 +24,7 @@ AllCops:
# otherwise taps will be prompted to use newer Ruby syntax too early.
TargetRubyVersion: 4.0
NewCops: enable
UseProjectIndex: true
Include:
- "**/*.rbi"
Exclude:
@@ -71,6 +72,14 @@ Homebrew/CompactBlank:
# `blank?` is not necessarily available here:
- "Homebrew/extend/enumerable.rb"
Homebrew/NoBase64:
Description: "Use `String#unpack1` or `Array#pack` instead of the `base64` gem."
Include:
- "/**/{Formula,Casks}/**/*.rb"
- "**/{Formula,Casks}/**/*.rb"
Exclude:
- "Homebrew/test/**/*.rb"
Homebrew/NoFileutilsRmrf:
Include:
- "/**/{Formula,Casks}/**/*.rb"
@@ -198,6 +207,35 @@ Lint/DuplicateBranch:
- "/**/{Formula,Casks}/**/*.rb"
- "**/{Formula,Casks}/**/*.rb"
# Formula files are loaded independently, so constants with the same names do
# not reassign each other. RBI constants declare their Ruby definitions.
Lint/ConstantReassignment:
Exclude:
- "**/*.rbi"
- "Taps/*/*/*.rb"
- "/**/Formula/**/*.rb"
- "**/Formula/**/*.rb"
# Detect references to project APIs marked with a YARD `@deprecated` tag.
Lint/DeprecatedReference:
Enabled: true
# Formula files from separate taps can define the same class without being
# loaded together. RBI methods declare their Ruby implementations.
Lint/DuplicateMethods:
Exclude:
- "**/*.rbi"
- "Taps/*/*/*.rb"
- "/**/Formula/**/*.rb"
- "**/Formula/**/*.rb"
# The project-only index does not include Ruby core or gem APIs.
Lint/NameTypo:
AllowedNames:
- "IfNode" # Defined by rubocop-ast.
- "instance_method" # Defined by Ruby core.
- "send" # Defined by Ruby core.
# so many of these in formulae and can't be autocorrected
Lint/ParenthesesAsGroupedExpression:
Exclude:
@@ -346,12 +384,6 @@ Style/AndOr:
Style/ArgumentsForwarding:
Enabled: false
# Autocorrects `include?`/`member?` block patterns to `intersect?` even when the second operand
# isn't actually an `Array` (e.g. `String`, `Set`, `Enumerator`), which can raise at runtime.
# Re-enable once a rubocop version with https://github.com/rubocop/rubocop/pull/15442 is released.
Style/ArrayIntersect:
Enabled: false
# Avoid leaking resources.
Style/AutoResourceCleanup:
Enabled: true
@@ -438,6 +470,11 @@ Style/HashAsLastArrayItem:
- "/**/Formula/**/*.rb"
- "**/Formula/**/*.rb"
# Restore when the fix for https://github.com/rubocop/rubocop/issues/15531
# is released.
Style/IfUnlessModifier:
Enabled: false
Style/InverseMethods:
InverseMethods:
:blank?: :present?
@@ -483,6 +520,12 @@ Style/OptionalBooleanParameter:
Style/RescueStandardError:
EnforcedStyle: implicit
# The project index compares lexical resolution but Ruby also searches class
# ancestors, including formula subclasses. Removing `::` can therefore resolve
# a different constant.
Style/RedundantConstantBase:
Enabled: false
# Returning `nil` is unnecessary.
Style/ReturnNil:
Enabled: true
+29
View File
@@ -4,6 +4,35 @@ inherit_from:
Homebrew/MoveToExtendOS:
Enabled: true
# The project index does not model explicit removal or isolated entry points.
Lint/ConstantReassignment:
Exclude:
# Replaced or bypassed by isolated test environments.
- "cask/config.rb"
- "startup/config.rb"
- "test/support/helper/spec/shared_context/homebrew_cask.rb"
- "test/support/lib/startup/config.rb"
# Defined by mutually exclusive application entry points.
- "global.rb"
- "utils/ruby_check_version_script.rb"
Lint/DuplicateMethods:
Exclude:
# OS-specific implementations intentionally override shared fallbacks. The
# project index reports both definitions, so both files must be excluded.
- "formula_installer.rb"
- "extend/os/linux/formula_installer.rb"
- "requirements/xcode_requirement.rb"
- "extend/os/linux/requirements/xcode_requirement.rb"
- "hardware.rb"
- "extend/os/mac/hardware/cpu/hardware.rb"
- "keg_only_reason.rb"
- "extend/os/mac/keg_only_reason.rb"
# Detect unreferenced private methods across Homebrew's project index.
Lint/UnusedPrivateMethod:
Enabled: true
# Want to preserve our own API for these methods for now.
Naming/PredicatePrefix:
inherit_mode:
+1 -1
View File
@@ -45,6 +45,7 @@ group :style, optional: true do
gem "rubocop-performance", require: false
gem "rubocop-rspec", require: false
gem "rubocop-sorbet", require: false
gem "rubydex", require: false
end
group :style, :tests, optional: true do
gem "test-prof", require: false
@@ -79,7 +80,6 @@ group :audit, :bump_unversioned_casks, :livecheck, optional: true do
end
# vendored gems (no group)
gem "base64"
gem "concurrent-ruby"
gem "patchelf"
gem "plist"
+30 -30
View File
@@ -7,14 +7,14 @@ GEM
bigdecimal (4.1.2)
bindata (2.5.1)
concurrent-ruby (1.3.8)
csv (3.3.5)
csv (3.3.6)
diff-lcs (1.6.2)
drb (2.2.3)
elftools (1.3.1)
bindata (~> 2)
erubi (1.13.1)
hana (1.3.7)
json (2.21.1)
json (2.21.2)
json_schemer (2.5.0)
bigdecimal
hana (~> 1.3)
@@ -36,7 +36,7 @@ GEM
parser (3.3.12.0)
ast (~> 2.4.1)
racc
patchelf (1.5.2)
patchelf (1.6.2)
elftools (>= 1.3)
logger (~> 1)
plist (3.7.2)
@@ -46,7 +46,7 @@ GEM
rbi (0.4.1)
prism (~> 1.0)
rbs (>= 4.0.1)
rbs (4.0.3)
rbs (4.1.2)
logger
prism (>= 1.6.0)
tsort
@@ -74,7 +74,7 @@ GEM
rspec-support (3.13.7)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.88.2)
rubocop (1.89.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -99,7 +99,7 @@ GEM
lint_roller (~> 1.1)
regexp_parser (>= 2.0)
rubocop (~> 1.86, >= 1.86.2)
rubocop-sorbet (0.13.2)
rubocop-sorbet (0.14.0)
lint_roller
rubocop (>= 1.75.2)
ruby-lsp (0.26.10)
@@ -115,20 +115,20 @@ GEM
rubydex (0.3.0-arm64-darwin)
rubydex (0.3.0-x86_64-darwin)
rubydex (0.3.0-x86_64-linux)
simplecov (1.0.2)
simplecov (1.0.3)
simplecov-cobertura (4.0.0)
rexml
simplecov (~> 1.0)
simpleidn (0.2.3)
sorbet (0.6.13359)
sorbet-static (= 0.6.13359)
sorbet-runtime (0.6.13359)
sorbet-static (0.6.13359-aarch64-linux)
sorbet-static (0.6.13359-universal-darwin)
sorbet-static (0.6.13359-x86_64-linux)
sorbet-static-and-runtime (0.6.13359)
sorbet (= 0.6.13359)
sorbet-runtime (= 0.6.13359)
sorbet (0.6.13386)
sorbet-static (= 0.6.13386)
sorbet-runtime (0.6.13386)
sorbet-static (0.6.13386-aarch64-linux)
sorbet-static (0.6.13386-universal-darwin)
sorbet-static (0.6.13386-x86_64-linux)
sorbet-static-and-runtime (0.6.13386)
sorbet (= 0.6.13386)
sorbet-runtime (= 0.6.13386)
spoom (1.8.6)
erubi (>= 1.10.0)
prism (>= 0.28.0)
@@ -170,7 +170,6 @@ PLATFORMS
x86_64-linux-gnu
DEPENDENCIES
base64
concurrent-ruby
csv
json_schemer
@@ -197,6 +196,7 @@ DEPENDENCIES
ruby-macho
ruby-prof
ruby-progressbar
rubydex
simplecov
simplecov-cobertura
sorbet-runtime
@@ -216,13 +216,13 @@ CHECKSUMS
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
elftools (1.3.1) sha256=9d46fca49a7d5e821b9c80127b19d59e8ce578bfaa17b99c8ff259222686aaf8
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
@@ -234,13 +234,13 @@ CHECKSUMS
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
parallel_tests (5.7.0) sha256=3f1762c46ca2c223b8af8ef877217f9d76974e191bfa934f2580b58bcf1d005c
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
patchelf (1.5.2) sha256=7efd746a52344be18c44b2fafeb80517c2c12e18dc24ac65d328b7c0609bc386
patchelf (1.6.2) sha256=b3867281dd5f4089bc656311e5de774515e01f97aa44a27a1536ec1ad4a77bda
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rbi (0.4.1) sha256=66611ca331b0b47d98607a7afda12ab44e0a98297d5393d4b93b846b9786d44d
rbs (4.0.3) sha256=5a7bf70e2628549d9a1f44eae447b2cfe55968a9c60cfff52693a4bdcc020e14
rbs (4.1.2) sha256=050eb1d8b508f1233bed929c0f2c7052302f7adf295230d9cb314e9024078f48
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
require-hooks (0.4.1) sha256=3a4cd3ed0131b9ec5c2e68f64cb0c5ba56daec6fc363d56e8eef74816c374d7d
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
@@ -253,12 +253,12 @@ CHECKSUMS
rspec-sorbet (1.9.2) sha256=28bf3969fa136ed22edd05b00d23aab52c276f2fcc89fccbb16b8fd0c48931c8
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
rspec_junit_formatter (0.6.0) sha256=40dde674e6ae4e6cc0ff560da25497677e34fefd2338cc467a8972f602b62b15
rubocop (1.88.2) sha256=8def251c90cd955feb4daa3edc0ab56893250c4ce90ef81e6c80c03f9a939bbf
rubocop (1.89.0) sha256=4dee8e3ee9c45e474834efd9e8d6fd031e8331c8dacdff0de4ad65ae0a6faae7
rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
rubocop-md (2.0.4) sha256=0d076b6b5e99dea2ddc928c4bd702497decb9a8400da7808091e02ebcafcfb93
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
rubocop-rspec (3.10.2) sha256=0b3e2ecc592cd10ecbf0095bb58d1e357905276e069643523cc19eb7495f65e2
rubocop-sorbet (0.13.2) sha256=7901e57b6b8e9e9b970d941dbdbb89f2c9de0183bfd2551460468f8aa9548655
rubocop-sorbet (0.14.0) sha256=30899556c9d02ab291d8bb761bae1cc025de56f72d909286b6960c1620d67bda
ruby-lsp (0.26.10) sha256=e67284af94423531f6b9a583350596421b5a6a4dd93083f1c2ba03da7c23bbed
ruby-macho (6.0.0) sha256=7bae15ff5564d06342a045523e47049cbf09f16e80b5ceb5bbc5fd8d8ec00c8e
ruby-prof (2.0.5) sha256=dbc3b5112089538ac33303145db0cbd77cbdcdbc05b09e44a967ff72a36d6c0e
@@ -267,15 +267,15 @@ CHECKSUMS
rubydex (0.3.0-arm64-darwin) sha256=19f685610216e4e7f488722ac90dfbc33113a384dd16db309cf939606f4c32dd
rubydex (0.3.0-x86_64-darwin) sha256=59d20c98b0bf2226f5db5bee14cf57075aa93aa0c3fa0176f496a217691ff7b6
rubydex (0.3.0-x86_64-linux) sha256=dfe4026591f226b4a1b53f82f86dc14fad2e7c221ee8a5be99a0a46bf2d58b04
simplecov (1.0.2) sha256=c6459434efe4b948b46477cc2df2faa73ab365f83a33c7c17f81262f4f7f1244
simplecov (1.0.3) sha256=38ef0514f16ae7562f0d0f4df02610071115103d301b6de7dacbcc000082e39b
simplecov-cobertura (4.0.0) sha256=e4fb3159b1ecea545b44f5452a8611305323e78ad23eae8aed35924d072e01ea
simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29
sorbet (0.6.13359) sha256=772eabba1cadbfdba4cd8416d342e4ad5f195b8e57bf08467cf1fb103fc5a415
sorbet-runtime (0.6.13359) sha256=5edcd198c678d69f07ec2758b9d0f277bce2e322efd7d5bd902a34c1056a2e5a
sorbet-static (0.6.13359-aarch64-linux) sha256=d6ffbc62c2189d6d02ae2d7b054e78565f404a5d254b34c876dd2adade621f63
sorbet-static (0.6.13359-universal-darwin) sha256=ebad32899f8cf929589a1932f48670fb4e522ea75a9f4cdfa1395475377282f5
sorbet-static (0.6.13359-x86_64-linux) sha256=08bb9cc0db2d0a6f9e181af0259c54455bfa66577b1581a4f11e928ad58190bd
sorbet-static-and-runtime (0.6.13359) sha256=cea6c6c1e930becdaadf6bbe732c119441ef206bf16c74e8bc11d11975dcb181
sorbet (0.6.13386) sha256=8f9cb0771c0843f1f2610847bf38a976716459fbec87b040ba8897414a286aa3
sorbet-runtime (0.6.13386) sha256=716aa0a47e75bba1167205d408d59b622db3648109854e9fd309fb901564df48
sorbet-static (0.6.13386-aarch64-linux) sha256=ebe41ccfbe9659d318cf6c5e28cede1069d4141bdc2f6524d4ba4d7b18440519
sorbet-static (0.6.13386-universal-darwin) sha256=1d3cfdfd1c57a6108b322fe5d2657da2b4021aa1fed413634762d9c44d4ffea4
sorbet-static (0.6.13386-x86_64-linux) sha256=29123908d23ca2bfc135c94c2da698934c74e9fbf02d5855274dc91d71071a93
sorbet-static-and-runtime (0.6.13386) sha256=ca4450fa2ef490fd17526eac9e0067fbf2c2b657b83637a9957135658e6b8207
spoom (1.8.6) sha256=b59ea53b8e258b044c08ef48ef819dd6f12aa386c47a745aa9d8e7fb1ae34a29
stackprof (0.2.28) sha256=4ec2ace02f386012b40ca20ef80c030ad711831f59511da12e83b34efb0f9a04
tapioca (0.19.2) sha256=938731b07811aee8d23871b1aee8861d464fbaf2cfffbf79a62b0c869a5120ec
+1 -1
View File
@@ -179,7 +179,7 @@ class Bottle
sig { returns(T::Boolean) }
def skip_relocation?
attrs = tab_attributes
tab = Tab.new(attrs) unless attrs.empty?
tab = Tab.new(**attrs.transform_keys(&:to_sym)) unless attrs.empty?
@spec.skip_relocation?(tag: @tag, tab:)
end
-7
View File
@@ -241,13 +241,6 @@ then
export HOMEBREW_COLOR="1"
fi
# This is set by Homebrew's self-hosted runner environment.
# shellcheck disable=SC2154
if [[ -n "${HOMEBREW_LINUX}" && -n "${GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED}" ]]
then
export HOMEBREW_SANDBOX_LINUX_LANDLOCK="1"
fi
setup-locale
#####
+205 -9
View File
@@ -6,6 +6,20 @@ require "bundle/extensions/extension"
module Homebrew
module Bundle
class Cargo < Extension
SourceOptions = T.type_alias { T::Hash[Symbol, String] }
Crate = T.type_alias { { name: String, source: T.nilable(String) } }
Checkable = T.type_alias { { name: String, options: SourceOptions } }
CrateEntry = T.type_alias { T.any(Crate, Checkable) }
# `cargo install --list` reports the origin of anything not installed from a
# registry, as either a git URL or a local path. Only a git URL that resolves
# from another machine is supported here, so neither a path nor the `file://`
# scheme is accepted. `--git` rejects an scp-style remote, so a scheme is
# required too.
GIT_SOURCE_REGEX = %r{\A(?:ssh|git|https?)://}
GIT_REFERENCE_KEYS = %w[branch tag rev].freeze
PACKAGE_LIST_REGEX = /\A(?<name>[^\s:]+)\s+v[0-9A-Za-z.+-]+(?:\s+\((?<origin>[^)]+)\))?/
class << self
sig { override.returns(Symbol) }
def type = :cargo
@@ -16,10 +30,36 @@ module Homebrew
sig { override.returns(String) }
def banner_name = "Cargo packages"
sig { override.params(name: String, options: Homebrew::Bundle::EntryInputOptions).returns(Dsl::Entry) }
def entry(name, options = {})
unknown_options = options.keys - [:source]
raise "unknown options(#{unknown_options.inspect}) for cargo" if unknown_options.present?
source = options.fetch(:source, nil)
if !source.nil? && !source.is_a?(String)
raise "options[:source](#{source.inspect}) should be a String object"
end
normalized_options = {}
if source.present?
normalized_source = normalize_source(source)
raise "options[:source](#{source.inspect}) should be a git URL" if normalized_source.nil?
selector = normalized_source.partition("?").last
if selector.present? && GIT_REFERENCE_KEYS.exclude?(selector.partition("=").first)
raise "options[:source](#{source.inspect}) should select a branch, tag or rev"
end
normalized_options[:source] = normalized_source
end
Dsl::Entry.new(:cargo, name, normalized_options)
end
sig { override.void }
def reset!
@packages = T.let(nil, T.nilable(T::Array[String]))
@installed_packages = T.let(nil, T.nilable(T::Array[String]))
@packages = T.let(nil, T.nilable(T::Array[Crate]))
@installed_packages = T.let(nil, T.nilable(T::Array[Crate]))
end
sig { override.returns(T.nilable(String)) }
@@ -37,7 +77,7 @@ module Homebrew
which("cargo", ORIGINAL_PATHS)
end
sig { override.returns(T::Array[String]) }
sig { override.returns(T::Array[Crate]) }
def packages
packages = @packages
return packages if packages
@@ -53,24 +93,47 @@ module Homebrew
@packages
end
sig { override.params(package: Object).returns(String) }
def dump_name(package)
T.cast(package, CrateEntry)[:name]
end
sig { params(package: Object).returns(T.nilable(String)) }
def dump_source(package)
package = T.cast(package, CrateEntry)
return package[:source] if package.key?(:source)
package[:options].fetch(:source, nil)
end
sig { override.params(package: Object).returns(String) }
def dump_entry(package)
line = super
source = dump_source(package)
line = "#{line}, source: #{quote(source)}" if source.present?
line
end
sig {
override.params(
name: String,
with: T.nilable(T::Array[String]),
source: T.nilable(String),
verbose: T::Boolean,
).returns(T::Boolean)
}
def install_package!(name, with: nil, verbose: false)
def install_package!(name, with: nil, source: nil, verbose: false)
_ = with
cargo = package_manager_executable!
with_env(cargo_env(cargo)) do
Bundle.system(cargo.to_s, "install", "--locked", name, verbose:)
Bundle.system(cargo.to_s, "install", "--locked", *source_args(source), name, verbose:)
end
end
sig { override.returns(T::Array[String]) }
sig { override.returns(T::Array[Crate]) }
def installed_packages
installed_packages = @installed_packages
return installed_packages if installed_packages
@@ -88,17 +151,135 @@ module Homebrew
cargo_env(executable)
end
sig { params(output: String).returns(T::Array[String]) }
sig {
override.params(
name: String,
with: T.nilable(T::Array[String]),
source: T.nilable(String),
).returns(Object)
}
def package_record(name, with: nil, source: nil)
_ = with
crate_record(name, source:)
end
sig { params(name: String, source: T.nilable(String)).returns(Crate) }
def crate_record(name, source: nil)
{ name: name.strip, source: normalize_source(source) }
end
private :crate_record
sig {
override.params(
name: String,
with: T.nilable(T::Array[String]),
source: T.nilable(String),
).returns(T::Boolean)
}
def package_installed?(name, with: nil, source: nil)
installed_packages.include?(package_record(name, with:, source:))
end
sig {
override.params(
name: String,
with: T.nilable(T::Array[String]),
source: T.nilable(String),
no_upgrade: T::Boolean,
verbose: T::Boolean,
_options: Homebrew::Bundle::EntryOption,
).returns(T::Boolean)
}
def preinstall!(name, with: nil, source: nil, no_upgrade: false, verbose: false, **_options)
_ = no_upgrade
ensure_package_manager_installed!(name, verbose:)
if package_installed?(name, with:, source:)
puts "Skipping install of #{name} #{package_description}. It is already installed." if verbose
return false
end
true
end
sig {
override.params(
name: String,
with: T.nilable(T::Array[String]),
source: T.nilable(String),
preinstall: T::Boolean,
no_upgrade: T::Boolean,
verbose: T::Boolean,
force: T::Boolean,
_options: Homebrew::Bundle::EntryOption,
).returns(T::Boolean)
}
def install!(name, with: nil, source: nil, preinstall: true, no_upgrade: false, verbose: false, force: false,
**_options)
_ = no_upgrade
_ = force
return true unless preinstall
puts "Installing #{name} #{package_description}. It is not currently installed." if verbose
return false unless install_package!(name, with:, source:, verbose:)
package = crate_record(name, source:)
installed_packages << package unless installed_packages.include?(package)
packages << package unless packages.include?(package)
true
end
# A branch, tag or revision selected at install time is reported by
# `cargo install --list` as a URL query, and never more than one of them,
# but `--git` rejects a query: it has to be passed as the matching flag.
sig { params(source: T.nilable(String)).returns(T::Array[String]) }
def source_args(source)
source = normalize_source(source)
return [] if source.nil?
url, _, query = source.partition("?")
key, _, value = query.partition("=")
args = ["--git", url]
args.push("--#{key}", value) if GIT_REFERENCE_KEYS.include?(key)
args
end
private :source_args
sig { params(output: String).returns(T::Array[Crate]) }
def parse_package_list(output)
output.lines.filter_map do |line|
next if line.match?(/^\s/)
match = line.match(/\A(?<name>[^\s:]+)\s+v[0-9A-Za-z.+-]+/)
match[:name] if match
match = line.match(PACKAGE_LIST_REGEX)
next if match.nil?
name = match[:name]
next if name.nil?
{ name:, source: normalize_source(match[:origin]) }
end.uniq
end
private :parse_package_list
# The resolved revision that `cargo install --list` appends to a git origin
# is deliberately dropped: a dumped Brewfile has to compare equal to a
# hand-written one, which a pinned revision would prevent. Anything that is
# not a git URL has no `source:` to dump, whether it is a registry crate or
# a local path.
sig { params(source: T.nilable(String)).returns(T.nilable(String)) }
def normalize_source(source)
source = source.presence&.strip&.sub(/#[^#]*\z/, "")
return if source.blank?
return source if source.match?(GIT_SOURCE_REGEX)
nil
end
private :normalize_source
sig { params(cargo: Pathname).returns(T::Hash[String, String]) }
def cargo_env(cargo)
{
@@ -110,6 +291,21 @@ module Homebrew
end
private :cargo_env
end
sig { override.params(entries: T::Array[Dsl::Entry]).returns(T::Array[Object]) }
def format_checkable(entries)
checkable_entries(entries).map do |entry|
{ name: entry.name, options: entry.options }
end
end
sig { override.params(package: Object, no_upgrade: T::Boolean).returns(T::Boolean) }
def installed_and_up_to_date?(package, no_upgrade: false)
self.class.package_installed?(
self.class.dump_name(package),
source: self.class.dump_source(package),
)
end
end
end
end
@@ -203,7 +203,6 @@ module Cask
sig { returns(T.nilable(Sandbox)) }
def cask_sandbox
Sandbox.ensure_sandbox_installed!
return unless Sandbox.available?
Sandbox.new.tap do |sandbox|
@@ -137,7 +137,7 @@ module Cask
next
end
if resolved_path.each_filename.any? { |part| [".", ".."].include?(part) }
if resolved_path.each_filename.to_a.intersect?([".", ".."])
opoo "Skipping #{Formatter.identifier(action)} for path with relative segments '#{path}'."
next
end
@@ -5,6 +5,7 @@ require "cask/artifact/symlinked"
module Cask
module Artifact
# Artifact corresponding to the `app_image` stanza.
class AppImage < Symlinked
sig { override.params(target: T.any(String, Pathname), base_dir: T.nilable(Pathname)).returns(Pathname) }
def resolve_target(target, base_dir: nil)
@@ -6,6 +6,7 @@ require "install_steps"
module Cask
module Artifact
# Abstract superclass for install steps artifacts.
class AbstractInstallSteps < AbstractArtifact
abstract!
@@ -34,6 +35,7 @@ module Cask
end
end
# Artifact corresponding to the `preflight_steps` stanza.
class PreflightSteps < AbstractInstallSteps
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
def install_phase(command: SystemCommand, **_options)
@@ -46,6 +48,7 @@ module Cask
end
end
# Artifact corresponding to the `postflight_steps` stanza.
class PostflightSteps < AbstractInstallSteps
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
def install_phase(command: SystemCommand, **_options)
@@ -58,6 +61,7 @@ module Cask
end
end
# Artifact corresponding to the `uninstall_preflight_steps` stanza.
class UninstallPreflightSteps < AbstractInstallSteps
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
def uninstall_phase(command: SystemCommand, **_options)
@@ -65,6 +69,7 @@ module Cask
end
end
# Artifact corresponding to the `uninstall_postflight_steps` stanza.
class UninstallPostflightSteps < AbstractInstallSteps
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
def uninstall_phase(command: SystemCommand, **_options)
@@ -5,6 +5,7 @@ require "cask/artifact/symlinked"
module Cask
module Artifact
# Superclass for all artifacts that are installed as shell completions.
class ShellCompletion < Symlinked
sig { override.overridable.params(_: T.any(String, Pathname), base_dir: T.nilable(Pathname)).returns(Pathname) }
def resolve_target(_, base_dir: nil)
@@ -79,6 +79,9 @@ module Cask
(target.realpath == source.realpath || target.realpath.to_s.start_with?("#{cask.caskroom_path}/"))
opoo "#{message}; overwriting."
Utils.gain_permissions_remove(target, command:)
elsif target_links_to_source?
ohai "#{self.class.english_name} '#{source.basename}' is already linked to '#{target}'"
return
elsif (formula = conflicting_formula)
opoo "#{message} from formula #{formula}; skipping link."
return
@@ -113,6 +116,14 @@ module Cask
sudo: !target.dirname.writable?
end
sig { returns(T::Boolean) }
def target_links_to_source?
target.symlink? && target.realpath == source.realpath
rescue => e
odebug "Error checking whether #{target} links to #{source}: #{e}"
false
end
# Check if the target file is a symlink that originates from a formula
# with the same name as this cask, indicating a potential conflict
sig { returns(T.nilable(String)) }
+153 -139
View File
@@ -89,12 +89,12 @@ module Cask
only_audits = @only
except_audits = @except
private_methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name|
public_methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name|
name = audit_method_name.delete_prefix("audit_")
next if !only_audits.empty? && only_audits.exclude?(name)
next if except_audits.include?(name)
send(audit_method_name)
public_send(audit_method_name)
end
self
@@ -272,8 +272,6 @@ module Cask
nil
end
private
sig { void }
def audit_untrusted_pkg
odebug "Auditing pkg stanza: allow_untrusted"
@@ -491,6 +489,15 @@ module Cask
add_error "OSDN download urls are disabled.", location: url.location, strict_only: true
end
sig { void }
def audit_unnecessary_verified
return unless new_cask?
return unless cask.url
return unless verified_present?
add_error "the `verified` parameter has been deprecated; use the `url` stanza without it"
end
sig { void }
def audit_generic_artifacts
cask.artifacts.grep(Artifact::Artifact).each do |artifact|
@@ -909,141 +916,6 @@ module Cask
"but the cask declared #{min_os_definition}"
end
sig { returns(T.nilable(MacOSVersion)) }
def cask_sparkle_min_os
return unless online?
return unless cask.livecheck_defined?
return if cask.livecheck.strategy != :sparkle
# `Sparkle` strategy blocks that use the `items` argument (instead of
# `item`) contain arbitrary logic that ignores/overrides the strategy's
# sorting, so we can't identify which item would be first/newest here.
return if cask.livecheck.strategy_block.present? &&
cask.livecheck.strategy_block.parameters[0] == [:opt, :items]
content = Homebrew::Livecheck::Strategy.page_content(cask.livecheck.url)[:content]
return if content.blank?
begin
items = Homebrew::Livecheck::Strategy::Sparkle.sort_items(
Homebrew::Livecheck::Strategy::Sparkle.filter_items(
Homebrew::Livecheck::Strategy::Sparkle.items_from_content(content),
),
)
rescue
return
end
return if items.blank?
normalize_min_os(items[0]&.minimum_system_version)
end
sig { returns(T.nilable(MacOSVersion)) }
def cask_bundle_min_os
return unless online?
min_os = T.let(nil, T.untyped)
@staged_path ||= T.let(cask.staged_path, T.nilable(Pathname))
extract_artifacts do |artifacts, tmpdir|
artifacts.each do |artifact|
next if artifact.is_a?(Artifact::Installer)
artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source
path = tmpdir/artifact_path.relative_path_from(cask.staged_path)
# Handle .pkg artifacts by expanding and checking Distribution file
if artifact.is_a?(Artifact::Pkg)
pkg_expanded_dir = tmpdir/"pkg-expanded"
begin
system_command!("pkgutil", args: ["--expand", path.to_s, pkg_expanded_dir.to_s])
distribution_file = pkg_expanded_dir/"Distribution"
if File.exist?(distribution_file)
distribution_content = File.read(distribution_file)
if (match = distribution_content.match(/<os-version\s+min="(?<version>[^"]+)"/))
min_os = match[:version]
break if min_os
end
end
rescue
break
end
end
info_plist_paths = Dir.glob("#{path}/**/Contents/Info.plist")
# Ensure the main `Info.plist` file is checked first, as this can
# sometimes use the min_os version from a framework instead
if info_plist_paths.delete("#{path}/Contents/Info.plist")
info_plist_paths.insert(0, "#{path}/Contents/Info.plist")
end
info_plist_paths.each do |plist_path|
next unless File.exist?(plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist
min_os = plist["LSMinimumSystemVersion"].presence
break if min_os
# Get the app bundle path from the plist path
app_bundle_path = Pathname(plist_path).dirname.dirname
next unless (main_binary = get_plist_main_binary(app_bundle_path))
next if !File.exist?(main_binary) || File.open(main_binary, "rb") { |f| f.read(2) == "#!" }
require "macho"
macho = MachO.open(main_binary)
min_os = case macho
when MachO::MachOFile
[
macho[:LC_VERSION_MIN_MACOSX].first&.version_string,
macho[:LC_BUILD_VERSION].first&.minos_string,
]
when MachO::FatFile
# Collect requirements by architecture
arch_min_os = { arm: [], intel: [] }
macho.machos.each do |slice|
macos_reqs = [
slice[:LC_VERSION_MIN_MACOSX].first&.version_string,
slice[:LC_BUILD_VERSION].first&.minos_string,
]
case slice.cputype
when *Hardware::CPU::ARM_ARCHS
arch_min_os[:arm].concat(macos_reqs)
when *Hardware::CPU::INTEL_ARCHS
arch_min_os[:intel].concat(macos_reqs)
end
end
# Only use the requirements for the current architecture
arch_min_os.fetch(Homebrew::SimulateSystem.current_arch, [])
end.compact.max
break if min_os
end
break if min_os
end
end
normalize_min_os(min_os)
end
sig { params(path: Pathname).returns(T.nilable(String)) }
def get_plist_main_binary(path)
return unless online?
plist_path = "#{path}/Contents/Info.plist"
return unless File.exist?(plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist
binary = plist["CFBundleExecutable"].presence
return unless binary
binary_path = "#{path}/Contents/MacOS/#{binary}"
binary_path if File.exist?(binary_path) && File.executable?(binary_path)
end
sig { void }
def audit_github_prerelease_version
return if (url = cask.url).nil?
@@ -1301,6 +1173,143 @@ module Cask
add_error error if error
end
private
sig { returns(T.nilable(MacOSVersion)) }
def cask_sparkle_min_os
return unless online?
return unless cask.livecheck_defined?
return if cask.livecheck.strategy != :sparkle
# `Sparkle` strategy blocks that use the `items` argument (instead of
# `item`) contain arbitrary logic that ignores/overrides the strategy's
# sorting, so we can't identify which item would be first/newest here.
return if cask.livecheck.strategy_block.present? &&
cask.livecheck.strategy_block.parameters[0] == [:opt, :items]
content = Homebrew::Livecheck::Strategy.page_content(cask.livecheck.url)[:content]
return if content.blank?
begin
items = Homebrew::Livecheck::Strategy::Sparkle.sort_items(
Homebrew::Livecheck::Strategy::Sparkle.filter_items(
Homebrew::Livecheck::Strategy::Sparkle.items_from_content(content),
),
)
rescue
return
end
return if items.blank?
normalize_min_os(items[0]&.minimum_system_version)
end
sig { returns(T.nilable(MacOSVersion)) }
def cask_bundle_min_os
return unless online?
min_os = T.let(nil, T.untyped)
@staged_path ||= T.let(cask.staged_path, T.nilable(Pathname))
extract_artifacts do |artifacts, tmpdir|
artifacts.each do |artifact|
next if artifact.is_a?(Artifact::Installer)
artifact_path = artifact.is_a?(Artifact::Pkg) ? artifact.path : artifact.source
path = tmpdir/artifact_path.relative_path_from(cask.staged_path)
# Handle .pkg artifacts by expanding and checking Distribution file
if artifact.is_a?(Artifact::Pkg)
pkg_expanded_dir = tmpdir/"pkg-expanded"
begin
system_command!("pkgutil", args: ["--expand", path.to_s, pkg_expanded_dir.to_s])
distribution_file = pkg_expanded_dir/"Distribution"
if File.exist?(distribution_file)
distribution_content = File.read(distribution_file)
if (match = distribution_content.match(/<os-version\s+min="(?<version>[^"]+)"/))
min_os = match[:version]
break if min_os
end
end
rescue
break
end
end
info_plist_paths = Dir.glob("#{path}/**/Contents/Info.plist")
# Ensure the main `Info.plist` file is checked first, as this can
# sometimes use the min_os version from a framework instead
if info_plist_paths.delete("#{path}/Contents/Info.plist")
info_plist_paths.insert(0, "#{path}/Contents/Info.plist")
end
info_plist_paths.each do |plist_path|
next unless File.exist?(plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist
min_os = plist["LSMinimumSystemVersion"].presence
break if min_os
# Get the app bundle path from the plist path
app_bundle_path = Pathname(plist_path).dirname.dirname
next unless (main_binary = get_plist_main_binary(app_bundle_path))
next if !File.exist?(main_binary) || File.open(main_binary, "rb") { |f| f.read(2) == "#!" }
require "macho"
macho = MachO.open(main_binary)
min_os = case macho
when MachO::MachOFile
[
macho[:LC_VERSION_MIN_MACOSX].first&.version_string,
macho[:LC_BUILD_VERSION].first&.minos_string,
]
when MachO::FatFile
# Collect requirements by architecture
arch_min_os = { arm: [], intel: [] }
macho.machos.each do |slice|
macos_reqs = [
slice[:LC_VERSION_MIN_MACOSX].first&.version_string,
slice[:LC_BUILD_VERSION].first&.minos_string,
]
case slice.cputype
when *Hardware::CPU::ARM_ARCHS
arch_min_os[:arm].concat(macos_reqs)
when *Hardware::CPU::INTEL_ARCHS
arch_min_os[:intel].concat(macos_reqs)
end
end
# Only use the requirements for the current architecture
arch_min_os.fetch(Homebrew::SimulateSystem.current_arch, [])
end.compact.max
break if min_os
end
break if min_os
end
end
normalize_min_os(min_os)
end
sig { params(path: Pathname).returns(T.nilable(String)) }
def get_plist_main_binary(path)
return unless online?
plist_path = "#{path}/Contents/Info.plist"
return unless File.exist?(plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist
binary = plist["CFBundleExecutable"].presence
return unless binary
binary_path = "#{path}/Contents/MacOS/#{binary}"
binary_path if File.exist?(binary_path) && File.executable?(binary_path)
end
sig {
params(
url_to_check: T.any(String, URL),
@@ -1369,6 +1378,11 @@ module Cask
URI(cask.url.to_s).host
end
sig { returns(T::Boolean) }
def verified_present?
cask.url&.verified.present?
end
sig { returns(Tap) }
def core_tap
@core_tap ||= T.let(CoreTap.instance, T.nilable(Tap))
+8 -7
View File
@@ -121,11 +121,14 @@ module Cask
@default_config = T.let(config || Config.new, Config)
@config = T.let(if config_path.exist?
Config.from_json(File.read(config_path), ignore_invalid_keys: true)
else
@default_config
end, Config)
@config = T.let(
if config_path.exist?
Config.from_json(File.read(config_path), ignore_invalid_keys: true)
else
@default_config
end,
Config,
)
refresh
end
@@ -613,8 +616,6 @@ module Cask
if dsl!.on_system_blocks_exist?
begin
OnSystem::VALID_OS_ARCH_TAGS.each do |bottle_tag|
next if bottle_tag.linux? && dsl!.os.nil? && !dsl!.sha256_set_for_linux?
macos_requirements = [depends_on.macos, depends_on.maximum_macos].compact
next if bottle_tag.macos? &&
macos_requirements.present? &&
+11 -8
View File
@@ -197,6 +197,7 @@ module Cask
@auto_updates_set_in_block = T.let(false, T::Boolean)
@autobump = T.let(true, T::Boolean)
@called_in_on_system_block = T.let(false, T::Boolean)
@called_in_on_os_block = T.let(false, T::Boolean)
@cask = cask
@caveats = T.let(DSL::Caveats.new(cask), DSL::Caveats)
@conflicts_with = T.let(nil, T.nilable(DSL::ConflictsWith))
@@ -236,7 +237,6 @@ module Cask
@os_set_in_block = T.let(false, T::Boolean)
@rename = T.let([], T::Array[DSL::Rename])
@sha256 = T.let(nil, T.nilable(T.any(Checksum, Symbol)))
@sha256_set_for_linux = T.let(false, T::Boolean)
@sha256_set_in_block = T.let(false, T::Boolean)
@staged_path = T.let(nil, T.nilable(Pathname))
@token = T.let(cask.token, String)
@@ -264,9 +264,6 @@ module Cask
sig { returns(T::Boolean) }
def on_os_blocks_exist? = @on_os_blocks_exist
sig { returns(T::Boolean) }
def sha256_set_for_linux? = @sha256_set_for_linux
# Specifies the cask's name.
#
# NOTE: Multiple names can be specified.
@@ -554,7 +551,6 @@ module Cask
if arm.present? || x86_64.present? || x86_64_linux.present? || arm64_linux.present?
@on_system_blocks_exist = true
end
@sha256_set_for_linux = true if x86_64_linux.present? || arm64_linux.present?
val = arg || on_system_conditional(
macos: on_arch_conditional(arm:, intel: x86_64),
@@ -570,13 +566,16 @@ module Cask
# running OS, matching `sha256` inside an `on_macos`/`on_linux` block;
# `depends_on` governs whether the cask is usable there. A checksum
# declared for the running OS but missing the running architecture
# still raises.
# still raises on the real system but is nil under simulation so
# API variations can be generated for the missing architecture.
running_os_checksums = if OnSystem.os_condition_met?(:linux)
[x86_64_linux, arm64_linux]
else
[arm, x86_64]
end
raise CaskInvalidError.new(cask, "invalid 'sha256' value: nil") if running_os_checksums.any?(&:present?)
if running_os_checksums.any?(&:present?) && !Homebrew::SimulateSystem.simulating?
raise CaskInvalidError.new(cask, "invalid 'sha256' value: nil")
end
nil
else
@@ -653,7 +652,10 @@ module Cask
return @depends_on if kwargs.empty?
begin
@depends_on.load(kwargs, set_in_block: @called_in_on_system_block)
# Only OS blocks scope a dependency to one OS: `on_arm`/`on_intel`
# blocks are evaluated on every OS, so a macOS dependency inside one
# applies everywhere and marks the cask macOS-only.
@depends_on.load(kwargs, set_in_block: @called_in_on_system_block, os_scoped: @called_in_on_os_block)
rescue RuntimeError => e
raise CaskInvalidError.new(cask, e)
end
@@ -858,6 +860,7 @@ module Cask
[klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key|
define_method(dsl_key) do |&block|
T.bind(self, DSL)
# odeprecated "`#{dsl_key}`", "`#{dsl_key}_steps`"
artifacts.add(klass.new(cask, dsl_key => block))
end
end
+24 -18
View File
@@ -50,6 +50,7 @@ module Cask
@macos = T.let(nil, T.nilable(MacOSRequirement))
@maximum_macos = T.let(nil, T.nilable(MacOSRequirement))
@linux = T.let(nil, T.nilable(LinuxRequirement))
@macos_required = T.let(false, T::Boolean)
@macos_bare_set_top_level = T.let(false, T::Boolean)
@macos_version_set_top_level = T.let(false, T::Boolean)
@maximum_macos_set_top_level = T.let(false, T::Boolean)
@@ -70,20 +71,22 @@ module Cask
params(
pairs: T::Hash[Symbol, T.any(String, Symbol, T::Array[T.any(String, Symbol)])],
set_in_block: T::Boolean,
os_scoped: T::Boolean,
).void
}
def load(pairs, set_in_block: false)
def load(pairs, set_in_block: false, os_scoped: false)
pairs.each do |key, value|
raise "invalid depends_on key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
previous_macos = @macos if key == :macos
__getobj__[key] = case key
case key
when :macos, :maximum_macos
send(:"#{key}=", *value, set_in_block:)
else
send(:"#{key}=", *value)
end
record_os_requirement(key, set_in_block:)
__getobj__[key] = public_send(key)
record_os_requirement(key, set_in_block:, os_scoped:)
next if key != :macos
next if value != :any
next unless previous_macos&.version_specified?
@@ -93,24 +96,24 @@ module Cask
end
end
sig { params(args: String).returns(T::Array[String]) }
sig { params(args: String).void }
def formula=(*args)
formula.concat(args)
end
sig { params(args: String).returns(T::Array[String]) }
sig { params(args: String).void }
def cask=(*args)
cask.concat(args)
end
sig { params(args: T.any(String, Symbol), set_in_block: T::Boolean).returns(T.nilable(MacOSRequirement)) }
sig { params(args: T.any(String, Symbol), set_in_block: T::Boolean).void }
def macos=(*args, set_in_block: false)
@macos = MacOSRequirement.parse(args, comparator: ">=")
rescue MacOSVersion::Error, TypeError => e
raise "invalid 'depends_on macos' value: #{e}"
end
sig { params(args: T.any(String, Symbol), set_in_block: T::Boolean).returns(T.nilable(MacOSRequirement)) }
sig { params(args: T.any(String, Symbol), set_in_block: T::Boolean).void }
def maximum_macos=(*args, set_in_block: false)
raise "invalid 'depends_on maximum_macos' value: only a single macOS version is allowed" if args.count != 1
@@ -126,7 +129,7 @@ module Cask
@maximum_macos = maximum_macos
end
sig { params(args: T.any(String, Symbol)).returns(T.nilable(LinuxRequirement)) }
sig { params(args: T.any(String, Symbol)).void }
def linux=(*args)
raise "Only a single 'depends_on linux' is allowed." if @linux
raise "invalid 'depends_on linux' value: #{args.first.inspect}" if args.first != :any
@@ -134,7 +137,7 @@ module Cask
@linux = LinuxRequirement.new
end
sig { params(args: Symbol).returns(T::Array[T::Hash[Symbol, T.any(Symbol, Integer)]]) }
sig { params(args: Symbol).void }
def arch=(*args)
@arch ||= []
arches = args.map do |elt|
@@ -153,26 +156,24 @@ module Cask
def present? = !empty?
sig { returns(T::Boolean) }
def requires_macos?
@macos_bare_set_top_level || @macos_version_set_top_level || @maximum_macos_set_top_level
end
def requires_macos? = @macos_required
sig { returns(T::Boolean) }
def requires_linux? = @linux_set_top_level
sig { params(key: Symbol, set_in_block: T::Boolean).void }
def record_os_requirement(key, set_in_block:)
sig { params(key: Symbol, set_in_block: T::Boolean, os_scoped: T::Boolean).void }
def record_os_requirement(key, set_in_block:, os_scoped:)
case key
when :macos
macos = @macos
raise "invalid 'depends_on macos' value" unless macos
record_macos_requirement(macos, set_in_block:)
record_macos_requirement(macos, set_in_block:, os_scoped:)
when :maximum_macos
maximum_macos = @maximum_macos
raise "invalid 'depends_on maximum_macos' value" unless maximum_macos
record_macos_requirement(maximum_macos, set_in_block:)
record_macos_requirement(maximum_macos, set_in_block:, os_scoped:)
when :linux
return if set_in_block
raise "`depends_on :linux` cannot be combined with `depends_on macos:`" if requires_macos?
@@ -181,8 +182,13 @@ module Cask
end
end
sig { params(requirement: MacOSRequirement, set_in_block: T::Boolean).void }
def record_macos_requirement(requirement, set_in_block:)
sig { params(requirement: MacOSRequirement, set_in_block: T::Boolean, os_scoped: T::Boolean).void }
def record_macos_requirement(requirement, set_in_block:, os_scoped:)
# `on_arm`/`on_intel` blocks are evaluated on every OS, so a macOS
# dependency inside one applies everywhere; only an OS block scopes a
# dependency to macOS alone.
@macos_required = true unless os_scoped
return if set_in_block
raise "`depends_on :linux` cannot be combined with `depends_on macos:`" if requires_linux?
+15
View File
@@ -358,6 +358,7 @@ on_request: true)
sig { void }
def check_requirements
check_stanza_os_requirements
check_supported_system
check_macos_requirements
check_arch_requirements
end
@@ -369,6 +370,20 @@ on_request: true)
raise CaskError, "#{@cask}: This cask requires Linux."
end
sig { void }
def check_supported_system
# Audited casks always have an activatable artifact for the systems they
# support, so API data without one means this system is unsupported.
# Source loads keep working for unaudited casks, e.g. naked containers.
return unless @cask.loaded_from_api?
return if @cask.artifacts.any? do |artifact|
artifact.respond_to?(:install_phase) || artifact.is_a?(Artifact::StageOnly)
end
os_name = Homebrew::SimulateSystem.simulating_or_running_on_macos? ? "macOS" : "Linux"
raise CaskError, "#{@cask}: This cask is not available on #{os_name}."
end
sig { void }
def check_macos_requirements
macos_requirement = [@cask.depends_on.macos, @cask.depends_on.maximum_macos].compact.find { !it.satisfied? }
+9 -5
View File
@@ -14,12 +14,16 @@ module Cask
sig { returns(T.nilable(T::Array[T.untyped])) }
attr_accessor :uninstall_artifacts
sig { params(attributes: T.any(T::Hash[String, T.untyped], T::Hash[Symbol, T.untyped])).void }
def initialize(attributes = {})
@uninstall_flight_blocks = T.let(nil, T.nilable(T::Boolean))
@uninstall_artifacts = T.let(nil, T.nilable(T::Array[T.untyped]))
sig {
params(uninstall_flight_blocks: T.nilable(T::Boolean),
uninstall_artifacts: T.nilable(T::Array[T.untyped]),
rest: T.untyped).void
}
def initialize(uninstall_flight_blocks: nil, uninstall_artifacts: nil, **rest)
@uninstall_flight_blocks = uninstall_flight_blocks
@uninstall_artifacts = uninstall_artifacts
super
super(**rest)
end
# Instantiates a {Tab} for a new installation of a cask.
+2 -1
View File
@@ -35,7 +35,7 @@ module Cask
attr_reader :using
sig { returns(T.nilable(String)) }
attr_reader :tag, :branch, :revision, :only_path
attr_reader :tag, :branch, :revision, :only_path, :verified
extend Forwardable
@@ -73,6 +73,7 @@ module Cask
header = Array(header) unless header.nil?
specs = {}
specs[:verified] = @verified = T.let(verified, T.nilable(String))
specs[:using] = @using = T.let(using, T.nilable(T.any(T::Class[AbstractDownloadStrategy], Symbol)))
specs[:tag] = @tag = T.let(tag, T.nilable(String))
specs[:branch] = @branch = T.let(branch, T.nilable(String))
+1 -1
View File
@@ -552,7 +552,7 @@ module Homebrew
(formula.requirements.any? { |requirement| self.class.requirement_for_other_os?(requirement) } ||
(stable.present? ? stable.bottled? && formula.pour_bottle? : formula.head.blank?))))
deps = formula.deps.send(type).uniq
deps = formula.deps.public_send(type).uniq
next if deps.empty?
tab_deps = (kegs.any? && type != "build") ? tab_runtime_deps : nil
+1 -1
View File
@@ -38,7 +38,7 @@ module Homebrew
Homebrew.with_no_api_env do
if args.syntax? && args.no_named?
scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb"
ruby_files = Dir.glob(scan_files).grep_v(%r{/(vendor)/})
ruby_files = Dir.glob(scan_files).grep_v(%r{/(vendor)/}).map { Pathname(it) }
Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files)
end
-20
View File
@@ -1,20 +0,0 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "shell_command"
module Homebrew
module Cmd
class SetupSandbox < AbstractCommand
include ShellCommand
cmd_args do
description <<~EOS
Run any necessary commands to setup the Homebrew sandbox.
Must be run with `sudo`. Currently a no-op on non-Linux.
EOS
end
end
end
end
-67
View File
@@ -1,67 +0,0 @@
# Documentation defined in Library/Homebrew/cmd/setup-sandbox.rb
# This Bubblewrap installation mirrors the package manager approaches in
# https://github.com/Homebrew/install and the Homebrew formula fallback in
# `ensure_sandbox_installed!` in Library/Homebrew/extend/os/linux/sandbox.rb.
# `sudo` strips `GITHUB_ACTIONS`, so also detect the runner via `/proc/1/cgroup`
# like `check-run-command-as-root` in Library/Homebrew/brew.sh does.
homebrew-on-github-actions() {
[[ -n "${GITHUB_ACTIONS}" ]] && return 0
grep -q "actions_job" /proc/1/cgroup &>/dev/null
}
homebrew-setup-sandbox() {
# The sandbox sysctls and Bubblewrap are Linux-only.
[[ -z "${HOMEBREW_LINUX}" ]] && return 0
if homebrew-on-github-actions && ! command -v bwrap &>/dev/null
then
if command -v apt-get &>/dev/null
then
apt-get install --yes bubblewrap
elif command -v dnf &>/dev/null
then
dnf install --assumeyes bubblewrap
elif command -v yum &>/dev/null
then
yum install --assumeyes bubblewrap
elif command -v pacman &>/dev/null
then
pacman --sync --noconfirm bubblewrap
elif command -v apk &>/dev/null
then
apk add bubblewrap
fi
fi
# These settings mirror SANDBOX_SYSCTL_SETTINGS in
# Library/Homebrew/extend/os/linux/sandbox.rb; keep both in sync.
local proc_sys_root="${HOMEBREW_PROC_SYS:-/proc/sys}"
local sysctl_value
local unprivileged_userns_clone_sysctl="${proc_sys_root}/kernel/unprivileged_userns_clone"
if [[ -e "${unprivileged_userns_clone_sysctl}" ]] &&
sysctl_value="$(sysctl -n "kernel.unprivileged_userns_clone")" &&
[[ "${sysctl_value}" != "1" ]] &&
[[ -w "${unprivileged_userns_clone_sysctl}" ]]
then
sysctl -w kernel.unprivileged_userns_clone=1 || true
fi
local max_user_namespaces_sysctl="${proc_sys_root}/user/max_user_namespaces"
if [[ -e "${max_user_namespaces_sysctl}" ]] &&
sysctl_value="$(sysctl -n "user.max_user_namespaces")" &&
[[ "${sysctl_value}" -lt 28633 ]] &&
[[ -w "${max_user_namespaces_sysctl}" ]]
then
sysctl -w user.max_user_namespaces=28633 || true
fi
local apparmor_restrict_unprivileged_userns_sysctl="${proc_sys_root}/kernel/apparmor_restrict_unprivileged_userns"
if [[ -e "${apparmor_restrict_unprivileged_userns_sysctl}" ]] &&
sysctl_value="$(sysctl -n "kernel.apparmor_restrict_unprivileged_userns")" &&
[[ "${sysctl_value}" != "0" ]] &&
[[ -w "${apparmor_restrict_unprivileged_userns_sysctl}" ]]
then
sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
fi
}
@@ -39,6 +39,8 @@ class ReporterHub
sig { params(auto_update: T::Boolean).void }
def dump(auto_update: false)
return if auto_update && Homebrew::EnvConfig.auto_update_quiet?
unless Homebrew::EnvConfig.no_update_report_new?
dump_new_formula_report
dump_new_cask_report
+14
View File
@@ -22,6 +22,16 @@ module Homebrew
flag "--brewfile",
description: "Check formulae listed in a Brewfile. " \
"Defaults to `./Brewfile`; use `--brewfile=`<path> to specify another."
switch "--fix-available",
description: "Only report vulnerabilities that have a fix available. " \
"Note that this may exclude vulnerabilities with fixes available " \
"if we cannot determine that the fix is included in the version " \
"under consideration."
switch "--no-fix-available",
description: "Only report vulnerabilities that do not have a fix available. " \
"Note that this may include vulnerabilities with fixes available " \
"if we cannot determine that the fix is included in the version " \
"under consideration."
flag "-s", "--severity=",
description: "Only report findings at or above: `low`, `medium`, `high`, `critical`."
flag "-m", "--max-summary=",
@@ -29,6 +39,8 @@ module Homebrew
switch "-j", "--json",
description: "Output JSON."
conflicts "--fix-available", "--no-fix-available"
named_args :formula
end
@@ -43,6 +55,8 @@ module Homebrew
formulae,
ignore_patches: !args.no_ignore_patches?,
min_severity: severity,
only_fixed: args.fix_available?,
except_fixed: args.no_fix_available?,
).scan
if args.json?
-4
View File
@@ -99,15 +99,11 @@ class DependencyCollector
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def glibc_dep_if_needed(related_formula_names); end
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def bubblewrap_dep_if_needed(related_formula_names); end
# Names implicitly added to any formula's deps right now, reusing the same checks
# `Formula#add_global_deps_to_spec` uses to inject them onto a real formula.
sig { returns(T::Set[String]) }
def implicit_dependency_names
[
bubblewrap_dep_if_needed(Set.new),
gcc_dep_if_needed(Set.new),
glibc_dep_if_needed(Set.new),
].compact.to_set(&:name)
+3 -4
View File
@@ -44,8 +44,7 @@ module Homebrew
titleized_repository = tap.repository.dup
titleized_user[0] = T.must(titleized_user[0]).upcase
titleized_repository[0] = T.must(titleized_repository[0]).upcase
# Duplicate assignment to silence `assigned but unused variable` warning
root_url = root_url = GitHubPackages.root_url(tap.user, "homebrew-#{tap.repository}") if args.github_packages?
root_url = GitHubPackages.root_url(tap.user, "homebrew-#{tap.repository}") if args.github_packages?
(tap.path/"Formula").mkpath
@@ -163,8 +162,8 @@ module Homebrew
YAML
end
# Pick a random 5 minute block in which to execute the autobump action to avoid peak GitHub loads
hour = Random.rand(23)
minute = Random.rand(11) * 5
hour = Random.rand(24)
minute = Random.rand(12) * 5
workflow.gsub!("this will be changed later and randomised by brew tap-new") do
"Every day at #{hour}:#{minute} UTC"
end
-1
View File
@@ -82,7 +82,6 @@ module Homebrew
exec_args << "--HEAD" if f.head?
Sandbox.ensure_sandbox_installed!
if Sandbox.available?
sandbox = Sandbox.new
f.logs.mkpath
+8
View File
@@ -559,6 +559,14 @@ module Homebrew
@default_download_queue ||= T.let(DownloadQueue.new, T.nilable(DownloadQueue))
end
sig { void }
def self.reset_default_download_queue
# Skip `shutdown` for a leaked RSpec double, which cannot receive
# messages outside the per-example rspec-mocks lifecycle.
@default_download_queue.shutdown if @default_download_queue.is_a?(DownloadQueue)
@default_download_queue = nil
end
sig { void }
def self.shutdown_default_download_queue
@default_download_queue&.shutdown
+7 -2
View File
@@ -96,6 +96,11 @@ module Homebrew
replacement: "the default behaviour",
odeprecated: true,
},
HOMEBREW_AUTO_UPDATE_QUIET: {
description: "If set, the auto-update run before commands like `brew install`, `brew upgrade` or " \
"`brew tap` will not show information about new, outdated or deleted formulae and casks.",
boolean: true,
},
HOMEBREW_AUTO_UPDATE_SECS: {
description: "Run `brew update` once every `$HOMEBREW_AUTO_UPDATE_SECS` seconds before some commands, " \
"e.g. `brew install`, `brew upgrade` or `brew tap`. Alternatively, " \
@@ -658,7 +663,7 @@ module Homebrew
default: true,
},
HOMEBREW_SANDBOX_LINUX: {
description: "The `bwrap`(1) sandbox is the default for formula installation and testing " \
description: "The Landlock sandbox is the default for formula installation and testing " \
"on Linux unless `$HOMEBREW_NO_SANDBOX_LINUX` is set.",
boolean: :set,
disabled_by: :HOMEBREW_NO_SANDBOX_LINUX,
@@ -968,7 +973,7 @@ module Homebrew
end
sig { returns(T::Boolean) }
def cask_opts_binaries?
def self.cask_opts_binaries?
cask_opts.reverse_each do |opt|
return true if opt == "--binaries"
return false if opt == "--no-binaries"
+5 -5
View File
@@ -121,7 +121,7 @@ module SharedEnvExtension
sig { params(key: String, path: T.any(String, Pathname)).void }
def append_path(key, path)
self[key] = PATH.new(self[key]).append(path)
self[key] = PATH.new(self[key]).append(path).to_s
end
sig { params(rustflags: String).void }
@@ -140,7 +140,7 @@ module SharedEnvExtension
def prepend_path(key, path)
return if %w[/usr/bin /bin /usr/sbin /sbin].include? path.to_s
self[key] = PATH.new(self[key]).prepend(path)
self[key] = PATH.new(self[key]).prepend(path).to_s
end
sig { params(key: String, path: T.any(String, Pathname)).void }
@@ -285,7 +285,7 @@ module SharedEnvExtension
end
if gfortran
puts "This may be changed by setting the `$FC` environment variable."
self["FC"] = self["F77"] = gfortran
self["FC"] = self["F77"] = gfortran.to_s
flags = FC_FLAG_VARS
end
end
@@ -352,12 +352,12 @@ module SharedEnvExtension
sig { params(_flags: T::Array[String], _map: T::Hash[Symbol, String]).void }
def set_cpu_flags(_flags, _map = {}); end
sig { params(val: T.any(String, Pathname)).returns(String) }
sig { params(val: T.any(String, Pathname)).void }
def cc=(val)
self["CC"] = self["OBJC"] = val.to_s
end
sig { params(val: T.any(String, Pathname)).returns(String) }
sig { params(val: T.any(String, Pathname)).void }
def cxx=(val)
self["CXX"] = self["OBJCXX"] = val.to_s
end
-12
View File
@@ -1,12 +0,0 @@
# typed: strict
module SharedEnvExtension
# Overload to allow `PATH` values.
sig {
type_parameters(:U).params(
key: String,
value: T.all(T.type_parameter(:U), T.nilable(T.any(String, Pathname, PATH))),
).returns(T.type_parameter(:U))
}
def []=(key, value); end
end
+1 -1
View File
@@ -31,7 +31,7 @@ module Stdenv
# Set the default pkg-config search path, overriding the built-in paths
# Anything in PKG_CONFIG_PATH is searched before paths in this variable
self["PKG_CONFIG_LIBDIR"] = determine_pkg_config_libdir
self["PKG_CONFIG_LIBDIR"] = determine_pkg_config_libdir&.to_s
self["MAKEFLAGS"] = "-j#{make_jobs}"
self["RUSTC_WRAPPER"] = "#{HOMEBREW_SHIMS_PATH}/shared/rustc_wrapper"
+15 -14
View File
@@ -82,9 +82,9 @@ module Superenv
self["MAKEFLAGS"] ||= "-j#{determine_make_jobs}"
self["RUSTC_WRAPPER"] = "#{HOMEBREW_SHIMS_PATH}/shared/rustc_wrapper"
self["HOMEBREW_RUSTFLAGS"] = Hardware.rustflags_target_cpu(effective_arch)
self["PATH"] = determine_path
self["PKG_CONFIG_PATH"] = determine_pkg_config_path
self["PKG_CONFIG_LIBDIR"] = determine_pkg_config_libdir || ""
self["PATH"] = determine_path&.to_s
self["PKG_CONFIG_PATH"] = determine_pkg_config_path&.to_s
self["PKG_CONFIG_LIBDIR"] = (determine_pkg_config_libdir || "").to_s
self["HOMEBREW_CCCFG"] = determine_cccfg
self["HOMEBREW_OPTIMIZATION_LEVEL"] = compiler.match?(GNU_GCC_REGEXP) ? "O2" : "Os"
self["HOMEBREW_BREW_FILE"] = HOMEBREW_BREW_FILE.to_s
@@ -94,17 +94,17 @@ module Superenv
self["HOMEBREW_TEMP"] = HOMEBREW_TEMP.to_s
self["HOMEBREW_OPTFLAGS"] = determine_optflags
self["HOMEBREW_MAKE_JOBS"] = determine_make_jobs.to_s
self["CMAKE_PREFIX_PATH"] = determine_cmake_prefix_path
self["CMAKE_FRAMEWORK_PATH"] = determine_cmake_frameworks_path
self["CMAKE_INCLUDE_PATH"] = determine_cmake_include_path
self["CMAKE_LIBRARY_PATH"] = determine_cmake_library_path
self["ACLOCAL_PATH"] = determine_aclocal_path
self["CMAKE_PREFIX_PATH"] = determine_cmake_prefix_path&.to_s
self["CMAKE_FRAMEWORK_PATH"] = determine_cmake_frameworks_path&.to_s
self["CMAKE_INCLUDE_PATH"] = determine_cmake_include_path&.to_s
self["CMAKE_LIBRARY_PATH"] = determine_cmake_library_path&.to_s
self["ACLOCAL_PATH"] = determine_aclocal_path&.to_s
self["M4"] = "#{HOMEBREW_PREFIX}/opt/m4/bin/m4" if deps.any? { |d| d.name == "libtool" }
self["HOMEBREW_ISYSTEM_PATHS"] = determine_isystem_paths
self["HOMEBREW_INCLUDE_PATHS"] = determine_include_paths
self["HOMEBREW_LIBRARY_PATHS"] = determine_library_paths
self["HOMEBREW_ISYSTEM_PATHS"] = determine_isystem_paths&.to_s
self["HOMEBREW_INCLUDE_PATHS"] = determine_include_paths&.to_s
self["HOMEBREW_LIBRARY_PATHS"] = determine_library_paths&.to_s
self["HOMEBREW_DEPENDENCIES"] = determine_dependencies
self["HOMEBREW_FORMULA_PREFIX"] = @formula.prefix unless @formula.nil?
self["HOMEBREW_FORMULA_PREFIX"] = @formula.prefix.to_s unless @formula.nil?
# Prevent the OpenSSL rust crate from building a vendored OpenSSL.
# https://github.com/sfackler/rust-openssl/blob/994e5ff8c63557ab2aa85c85cc6956b0b0216ca7/openssl/src/lib.rs#L65
self["OPENSSL_NO_VENDOR"] = "1"
@@ -155,9 +155,10 @@ module Superenv
private
sig { params(val: T.any(String, Pathname)).returns(String) }
sig { params(val: T.any(String, Pathname)).void }
def cc=(val)
self["HOMEBREW_CC"] = super
super
self["HOMEBREW_CC"] = val.to_s
end
sig { returns(String) }
@@ -2,22 +2,10 @@
# frozen_string_literal: true
require "os/linux/glibc"
require "sandbox"
module OS
module Linux
module DependencyCollector
sig { params(related_formula_names: T::Set[String]).returns(T.nilable(Dependency)) }
def bubblewrap_dep_if_needed(related_formula_names)
return unless bubblewrap_dependency_needed?
return if building_global_dep_tree?
return if related_formula_names.include?(BUBBLEWRAP)
return if global_dep_tree[BUBBLEWRAP]&.intersect?(related_formula_names)
return unless formula_for(BUBBLEWRAP)
Dependency.new(BUBBLEWRAP, [:implicit])
end
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
@@ -50,31 +38,18 @@ module OS
GLIBC = "glibc"
GCC = OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA
BUBBLEWRAP = "bubblewrap"
private_constant :GLIBC, :GCC, :BUBBLEWRAP
private_constant :GLIBC, :GCC
sig { void }
def init_global_dep_tree_if_needed!
return if building_global_dep_tree?
sandbox_tree_needed = bubblewrap_dependency_needed?
build_formulae_tree_needed = ::DevelopmentTools.needs_build_formulae?
return if !sandbox_tree_needed && !build_formulae_tree_needed
return if (!sandbox_tree_needed || global_dep_tree.key?(BUBBLEWRAP)) &&
(!build_formulae_tree_needed || (global_dep_tree.key?(GLIBC) && global_dep_tree.key?(GCC)))
return unless ::DevelopmentTools.needs_build_formulae?
return if global_dep_tree.key?(GLIBC) && global_dep_tree.key?(GCC)
building_global_dep_tree!
if sandbox_tree_needed
include_build = OS.not_tier_one_configuration? || build_formulae_tree_needed
global_dep_tree[BUBBLEWRAP] = Set.new(global_deps_for(BUBBLEWRAP, include_build:))
end
if build_formulae_tree_needed
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]])
# bubblewrap depends on gcc
global_dep_tree[BUBBLEWRAP]&.merge([GCC, *@@global_dep_tree[GCC]])
end
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
@@ -86,27 +61,17 @@ module OS
nil
end
sig { returns(T::Boolean) }
def bubblewrap_dependency_needed?
return false unless ::Homebrew::EnvConfig.sandbox_linux?
return false if ENV["HOMEBREW_TESTS"]
return false if OS::Linux::Sandbox.landlock?
::Sandbox.executable.blank?
end
sig { params(name: String, include_build: T::Boolean).returns(T::Array[String]) }
def global_deps_for(name, include_build: true)
sig { params(name: String).returns(T::Array[String]) }
def global_deps_for(name)
@global_deps_for ||= T.let({}, T.nilable(T::Hash[String, T::Array[String]]))
# 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}|#{include_build}"] ||= if (formula = formula_for(name))
@global_deps_for[name] ||= if (formula = formula_for(name))
formula.deps.filter_map do |dep|
next if dep.test? && !dep.build?
next if dep.build? && !include_build
[dep.name, *global_deps_for(dep.name, include_build:)].compact
[dep.name, *global_deps_for(dep.name)].compact
end.flatten.uniq
else
[]
@@ -17,20 +17,8 @@ module OS
return unless Homebrew::EnvConfig.sandbox_linux?
require "sandbox"
return if !::Sandbox.available? && GitHub::Actions.env_set?
if OS::Linux::Sandbox.landlock?
unless ::Sandbox.available?
return if GitHub::Actions.env_set?
::Sandbox.ensure_sandbox_available!
end
::Sandbox.configure!
elsif GitHub::Actions.env_set?
::Sandbox.configure!
else
::Sandbox.ensure_sandbox_installed!(install_from_tests: true)
end
::Sandbox.ensure_sandbox_available!
end
+10 -50
View File
@@ -196,64 +196,24 @@ module OS
def check_linux_sandbox
return unless Homebrew::EnvConfig.sandbox_linux?
inside_docker = OS::Linux.inside_docker?
return if inside_docker && !GitHub::Actions.env_set?
return if OS::Linux.inside_docker? && !GitHub::Actions.env_set?
state = ::Sandbox.state
return if state == :available
reason = ::Sandbox.failure_reason || "The Linux sandbox is not available."
state = :landlock if OS::Linux::Sandbox.landlock?
reason_append = case state
when :setuid
"\n\nHomebrew's Linux sandbox requires a rootless `bwrap` executable."
when :unavailable
"\n\nHomebrew's Linux sandbox requires rootless Bubblewrap and unprivileged user namespaces."
fix = if state == :missing_fiddle
"Run Homebrew with its vendored Ruby, which includes Fiddle."
else
""
end
reason += reason_append
fix_lines = case state
when :missing
missing_lines = [
reason,
"",
"Install Bubblewrap and ensure a rootless `bwrap` executable is available on `PATH`.",
]
if (install_command = ::Sandbox.sandbox_install_command)
missing_lines.push("", "On this system, install it with:", " #{install_command}")
end
missing_lines
when :setuid
[
"Install a non-setuid Bubblewrap or put it earlier on `PATH`.",
]
when :unavailable
[
reason,
"",
"Homebrew's Linux sandbox requires rootless Bubblewrap and unprivileged",
"user namespaces. Run `sudo brew setup-sandbox` or check and update this system configuration:",
*::Sandbox.configuration_command_messages,
]
else
[]
end
if state == :unavailable && inside_docker && GitHub::Actions.env_set?
fix_lines.push("",
"If this is a GitHub Actions container, add `options: --privileged` to the job's " \
"`container` configuration.")
"Homebrew's Linux sandbox requires a kernel with Landlock enabled."
end
::Homebrew::Diagnostic::Finding.new(
reason,
remediation: [
*fix_lines,
"",
"As a final workaround, disable the Linux sandbox:",
" export HOMEBREW_NO_SANDBOX_LINUX=1",
].join("\n").to_s,
::Sandbox.failure_reason || "The Linux sandbox is not available.",
remediation: <<~EOS.chomp,
#{fix}
As a final workaround, disable the Linux sandbox:
export HOMEBREW_NO_SANDBOX_LINUX=1
EOS
)
end
@@ -38,7 +38,7 @@ module OS
self["HOMEBREW_OPTIMIZATION_LEVEL"] = "O2"
self["HOMEBREW_DYNAMIC_LINKER"] = determine_dynamic_linker_path
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(formula)
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(formula).to_s
m4_path_deps = ["libtool", "bison"]
self["M4"] = "#{HOMEBREW_PREFIX}/opt/m4/bin/m4" if deps.any? { m4_path_deps.include?(it.name) }
return unless ::Hardware::CPU.arm64?
@@ -37,7 +37,6 @@ module OS
related_formula_names.merge(versioned_formulae_names)
end
[
dependency_collector.bubblewrap_dep_if_needed(related_formula_names),
dependency_collector.gcc_dep_if_needed(related_formula_names),
dependency_collector.glibc_dep_if_needed(related_formula_names),
].compact.freeze
+10 -125
View File
@@ -1,7 +1,6 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/linux/sandbox/bubblewrap"
require "extend/os/linux/sandbox/landlock"
module OS
@@ -15,31 +14,6 @@ module OS
TIOCSCTTY = 0x540E
private_constant :TIOCSCTTY
sig { returns(::PATH) }
def self.bubblewrap_candidate_paths
::Sandbox::Bubblewrap.executable_candidate_paths
end
sig { returns(T.nilable(::Pathname)) }
def self.bubblewrap_executable
::Sandbox::Bubblewrap.executable
end
sig { returns(::Pathname) }
def self.bubblewrap_executable!
::Sandbox::Bubblewrap.executable!
end
sig { returns(T::Boolean) }
def self.landlock?
ENV.fetch("HOMEBREW_SANDBOX_LINUX_LANDLOCK", nil) == "1"
end
sig { returns(T.any(T.class_of(::Sandbox::Bubblewrap), T.class_of(::Sandbox::Landlock))) }
def self.sandbox_implementation
landlock? ? ::Sandbox::Landlock : ::Sandbox::Bubblewrap
end
sig { void }
def allow_write_temp_and_cache
allow_write_path "/tmp"
@@ -67,101 +41,31 @@ module OS
requires_ancestor { T.class_of(::Sandbox) }
sig { returns(String) }
def executable_name
::Sandbox::Bubblewrap.executable_name
end
sig { params(candidate: ::Pathname).returns(T::Boolean) }
def executable_usable?(candidate)
::Sandbox::Bubblewrap.executable_usable?(candidate)
end
sig { returns(T::Array[String]) }
def system_bubblewrap_paths
::Sandbox::Bubblewrap.system_paths
end
sig { returns(::PATH) }
def executable_candidate_paths
::Sandbox::Bubblewrap.executable_candidate_paths
end
sig { returns(::PATH) }
def bubblewrap_candidate_paths
executable_candidate_paths
end
sig { returns(T.nilable(::Pathname)) }
def bubblewrap_executable
::Sandbox::Bubblewrap.executable
end
sig { returns(::Pathname) }
def bubblewrap_executable!
::Sandbox::Bubblewrap.executable!
end
sig { params(install_from_tests: T::Boolean).void }
def ensure_sandbox_installed!(install_from_tests: false)
OS::Linux::Sandbox.sandbox_implementation.ensure_installed!(install_from_tests:)
end
sig { returns(T::Boolean) }
def available?
OS::Linux::Sandbox.sandbox_implementation.available?
::Sandbox::Landlock.available?
end
sig { returns(T::Boolean) }
def full_write_isolation?
OS::Linux::Sandbox.sandbox_implementation.full_write_isolation?
end
# Bubblewrap reports this specific namespace error when an outer
# Bubblewrap sandbox prevents Homebrew from creating another rootless
# sandbox. The shared `avoid_nested_sandboxing?` only calls this once the
# `$HOMEBREW_AVOID_NESTED_SANDBOXING` opt-in is set.
sig { returns(T::Boolean) }
def nested_sandbox?
OS::Linux::Sandbox.sandbox_implementation.nested_sandbox?
::Sandbox::Landlock.full_write_isolation?
end
sig { returns(Symbol) }
def state
OS::Linux::Sandbox.sandbox_implementation.state
::Sandbox::Landlock.state
end
sig { void }
def reset_state!
::Sandbox::Bubblewrap.reset_state!
::Sandbox::Landlock.reset_state!
end
sig { returns(T::Array[String]) }
def configuration_commands
OS::Linux::Sandbox.sandbox_implementation.configuration_commands
end
sig { returns(T::Array[String]) }
def configuration_command_messages
OS::Linux::Sandbox.sandbox_implementation.configuration_command_messages
end
sig { void }
def configure!
OS::Linux::Sandbox.sandbox_implementation.configure!
end
sig { returns(T.nilable(String)) }
def failure_reason
return super if self != ::Sandbox
OS::Linux::Sandbox.sandbox_implementation.failure_reason
end
sig { returns(T.nilable(String)) }
def sandbox_install_command
OS::Linux::Sandbox.sandbox_implementation.install_command
::Sandbox::Landlock.failure_reason
end
# `ioctl` request used to attach the sandboxed child to a controlling TTY.
@@ -173,43 +77,24 @@ module OS
sig { params(args: T.any(String, ::Pathname)).void }
def run(*args)
implementation.run { super }
end
sig { params(tmpdir: String).returns(T::Array[String]) }
def bubblewrap_args(tmpdir)
bubblewrap.arguments(tmpdir)
end
sig { returns(T::Hash[String, Symbol]) }
def writable_paths
bubblewrap.writable_paths
landlock.run { super }
end
private
sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) }
def sandbox_command(args, tmpdir)
implementation.command(args, tmpdir)
landlock.command(args, tmpdir)
end
sig { void }
def apply_sandbox
sandbox = implementation
sandbox.apply! if sandbox.is_a?(::Sandbox::Landlock)
landlock.apply!
end
sig { returns(T.any(::Sandbox::Bubblewrap, ::Sandbox::Landlock)) }
def implementation
@implementation ||= T.let(
OS::Linux::Sandbox.sandbox_implementation.new(profile),
T.nilable(T.any(::Sandbox::Bubblewrap, ::Sandbox::Landlock)),
)
end
sig { returns(::Sandbox::Bubblewrap) }
def bubblewrap
@bubblewrap ||= T.let(::Sandbox::Bubblewrap.new(profile), T.nilable(::Sandbox::Bubblewrap))
sig { returns(::Sandbox::Landlock) }
def landlock
@landlock ||= T.let(::Sandbox::Landlock.new(profile), T.nilable(::Sandbox::Landlock))
end
end
end
@@ -1,372 +0,0 @@
# typed: strict
# frozen_string_literal: true
require "fileutils"
require "env_config"
require "system_command"
require "utils/popen"
require "utils/github/actions"
require "extend/os/linux/sandbox/backend"
class Sandbox
class Bubblewrap < LinuxBackend
extend SystemCommand::Mixin
extend Utils::Output::Mixin
EXECUTABLE = "bwrap"
TEST_ARGS = [
"--unshare-user",
"--unshare-ipc",
"--unshare-pid",
"--unshare-uts",
"--unshare-cgroup-try",
"--ro-bind", "/", "/",
"--proc", "/proc",
"--dev", "/dev",
"true"
].freeze
SYSTEM_PATHS = %w[
/usr/bin
/bin
].freeze
HOMEBREW_PATHS = [
"#{HOMEBREW_PREFIX}/bin",
].freeze
NESTED_ERROR = "Creating new namespace failed: nesting depth or /proc/sys/user/max_*_namespaces exceeded"
class SysctlSetting < T::Struct
const :assignment, String
const :description, T::Array[String]
const :optional, T::Boolean, default: false
end
# These settings mirror the `sysctl` assignments in
# Library/Homebrew/cmd/setup-sandbox.sh; keep both in sync.
SYSCTL_SETTINGS = T.let([
SysctlSetting.new(
assignment: "kernel.unprivileged_userns_clone=1",
description: [
"Allows unprivileged processes to create user namespaces. Rootless",
"Bubblewrap needs this to isolate builds without elevated privileges.",
],
),
SysctlSetting.new(
assignment: "user.max_user_namespaces=28633",
description: [
"Allows each user to allocate enough user namespaces. A zero or low",
"limit can prevent Bubblewrap from creating its sandbox.",
],
),
SysctlSetting.new(
assignment: "kernel.apparmor_restrict_unprivileged_userns=0",
description: [
"Allows unprivileged user namespaces on AppArmor-enabled systems",
"that restrict them by default. Older kernels may not provide this",
"setting.",
],
optional: true,
),
].freeze, T::Array[SysctlSetting])
# Per-distro Bubblewrap install commands, detected by package manager and
# checked in priority order. Mirrors the build tools instructions in
# `Homebrew/install`'s `install.sh`.
INSTALL_COMMANDS = T.let({
"apt-get" => "sudo apt-get install bubblewrap",
"dnf" => "sudo dnf install bubblewrap",
"yum" => "sudo yum install bubblewrap",
"pacman" => "sudo pacman -S bubblewrap",
"apk" => "sudo apk add bubblewrap",
}.freeze, T::Hash[String, String])
private_constant :EXECUTABLE, :TEST_ARGS, :SYSTEM_PATHS, :HOMEBREW_PATHS, :NESTED_ERROR, :SysctlSetting,
:SYSCTL_SETTINGS, :INSTALL_COMMANDS
class << self
sig { returns(String) }
def executable_name
EXECUTABLE
end
sig { params(candidate: ::Pathname).returns(T::Boolean) }
def executable_usable?(candidate)
!File.stat(candidate).setuid?
end
sig { returns(T::Array[String]) }
def system_paths
SYSTEM_PATHS
end
sig { returns(::PATH) }
def executable_candidate_paths
PATH.new(HOMEBREW_PATHS, system_paths, ORIGINAL_PATHS, ENV.fetch("PATH"), HOMEBREW_ORIGINAL_BREW_FILE.dirname)
end
sig { returns(T.nilable(::Pathname)) }
def executable
executable_candidate_paths.each do |path|
begin
candidate = ::Pathname.new(File.expand_path(executable_name, path))
rescue ArgumentError
next
end
next if !candidate.file? || !candidate.executable?
next unless executable_usable?(candidate)
return candidate
end
nil
end
sig { returns(::Pathname) }
def executable!
executable || raise("Bubblewrap is required to use the Linux sandbox.")
end
sig { params(install_from_tests: T::Boolean).void }
def ensure_installed!(install_from_tests: false)
return unless Homebrew::EnvConfig.sandbox_linux?
return if ENV["HOMEBREW_TESTS"] && !install_from_tests
return if ENV["HOMEBREW_INSTALLING_BUBBLEWRAP"]
return if executable
begin
require "exceptions"
require "formula"
with_env(HOMEBREW_INSTALLING_BUBBLEWRAP: "1") do
::Formula["bubblewrap"].ensure_installed!(reason: "Linux sandboxing")
end
reset_state!
return if executable
rescue ::FormulaUnavailableError
nil
end
return unless GitHub::Actions.env_set?
return unless ENV.fetch("HOMEBREW_GITHUB_HOSTED_RUNNER", nil)
return unless which("apt-get")
ohai "Installing Bubblewrap..."
command = ["apt-get", "install", "--yes", "bubblewrap"]
command.unshift("sudo") unless Process.euid.zero?
system(*command)
reset_state!
end
sig { returns(T::Boolean) }
def available?
state == :available
end
# Bubblewrap reports this specific namespace error when an outer
# Bubblewrap sandbox prevents Homebrew from creating another rootless
# sandbox. The shared `avoid_nested_sandboxing?` only calls this once the
# `$HOMEBREW_AVOID_NESTED_SANDBOXING` opt-in is set.
sig { returns(T::Boolean) }
def nested_sandbox?
return false unless Homebrew::EnvConfig.sandbox_linux?
bubblewrap = executable
return false unless bubblewrap
Utils.popen_read(bubblewrap.to_s, *TEST_ARGS, err: :out).include?(NESTED_ERROR)
end
sig { returns(Symbol) }
def state
return :config_disabled unless Homebrew::EnvConfig.sandbox_linux?
@state ||= T.let(compute_state, T.nilable(Symbol))
end
sig { void }
def reset_state!
@state = T.let(nil, T.nilable(Symbol))
end
sig { returns(T::Array[String]) }
def configuration_commands
SYSCTL_SETTINGS.map do |setting|
command = "sudo sysctl -w #{setting.assignment}"
command += " || true" if setting.optional
command
end
end
sig { returns(T::Array[String]) }
def configuration_command_messages
commands = configuration_commands
SYSCTL_SETTINGS.each_with_index.flat_map do |setting, index|
[
" #{commands.fetch(index)}",
*setting.description.map { |line| " #{line}" },
]
end
end
sig { void }
def configure!
unless executable
ensure_installed!(install_from_tests: true)
unless executable
reset_state!
return
end
end
ohai "Configuring Bubblewrap..."
command = [HOMEBREW_BREW_FILE.to_s, "setup-sandbox"]
command.unshift("sudo") unless Process.euid.zero?
raise ErrorDuringExecution.new(command, status: $CHILD_STATUS || 1) unless system(*command)
reset_state!
end
sig { returns(T.nilable(String)) }
def failure_reason
case state
when :config_disabled, :available
nil
when :missing
"Bubblewrap is required to use the Linux sandbox but was not found."
when :setuid
"A rootless Bubblewrap executable is required to use the Linux sandbox, " \
"but all found `bwrap` executables are setuid."
when :unavailable
"Bubblewrap is installed but cannot create a rootless sandbox."
else
"The Linux sandbox is not available."
end
end
sig { returns(T.nilable(String)) }
def install_command
INSTALL_COMMANDS.find { |package_manager, _| which(package_manager) }&.last
end
private
sig { returns(Symbol) }
def compute_state
bubblewraps = executables
return :missing if bubblewraps.empty?
bubblewraps = bubblewraps.select { |candidate| executable_usable?(candidate) }
return :setuid if bubblewraps.empty?
return :available if bubblewraps.any? { |candidate| sandbox_available?(candidate) }
:unavailable
end
sig { returns(T::Array[::Pathname]) }
def executables
executable_candidate_paths.filter_map do |path|
begin
candidate = ::Pathname.new(File.expand_path(executable_name, path))
rescue ArgumentError
next
end
candidate if candidate.file? && candidate.executable?
end
end
sig { params(bubblewrap: ::Pathname).returns(T::Boolean) }
def sandbox_available?(bubblewrap)
result = system_command(
bubblewrap,
args: TEST_ARGS,
print_stderr: false,
)
return true if result.success?
opoo "bubblewrap test probe failed"
$stderr.print result.merged_output
false
end
end
sig { params(profile: SandboxProfile).void }
def initialize(profile)
super
@masked_read_paths = T.let([], T::Array[::Pathname])
end
sig { params(block: T.proc.void).void }
def run(&block)
old_report_on_exception = T.let(Thread.report_on_exception, T.nilable(T::Boolean))
Thread.report_on_exception = false
super
ensure
Thread.report_on_exception = old_report_on_exception unless old_report_on_exception.nil?
@masked_read_paths.reverse_each { |path| FileUtils.rm_rf(path) }
@masked_read_paths.clear
end
sig { params(args: T::Array[T.any(String, ::Pathname)], tmpdir: String).returns(T::Array[T.any(String, ::Pathname)]) }
def command(args, tmpdir)
[self.class.executable!, *arguments(tmpdir), "--", *args]
end
sig { params(tmpdir: String).returns(T::Array[String]) }
def arguments(tmpdir)
args = T.let([
"--unshare-user",
"--unshare-ipc",
"--unshare-pid",
"--unshare-uts",
"--unshare-cgroup-try",
"--die-with-parent",
"--new-session",
"--ro-bind", "/", "/",
"--dev", "/dev",
"--proc", "/proc"
], T::Array[String])
args << "--unshare-net" if deny_all_network?
writable_paths.each do |path, type|
prepare_writable_path(path, type)
args += ["--bind", path, path]
end
denied_write_paths.each do |path|
next unless File.exist?(path)
args += ["--ro-bind", path, path]
end
denied_read_paths.each do |path|
next unless File.exist?(path)
args += if File.directory?(path)
["--bind", masked_read_path, path]
else
["--ro-bind", File::NULL, path]
end
end
args += ["--bind", tmpdir, tmpdir, "--chdir", tmpdir]
args
end
private
sig { returns(T::Array[String]) }
def denied_write_paths
profile_paths(allow: false, operation: "file-write")
end
sig { returns(T::Array[String]) }
def denied_read_paths
profile_paths(allow: false, operation: "file-read")
end
sig { returns(String) }
def masked_read_path
path = ::Pathname.new(Dir.mktmpdir("homebrew-sandbox-deny-read", HOMEBREW_TEMP))
@masked_read_paths << path
path.to_s
end
end
end
@@ -94,8 +94,8 @@ class Sandbox
class << self
# Landlock cannot restrict chmod, chown, extended attributes or timestamp
# changes. Callers requiring Bubblewrap-equivalent write isolation must
# compensate for these limitations:
# changes. Callers requiring full write isolation must compensate for
# these limitations:
# https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#filesystem-flags
sig { returns(T::Boolean) }
def full_write_isolation? = false
@@ -159,26 +159,6 @@ class Sandbox
@abi_version = T.let(nil, T.nilable(Integer))
end
sig { params(install_from_tests: T::Boolean).void }
def ensure_installed!(install_from_tests: false); end
sig { void }
def configure!
ensure_available!
end
sig { returns(T::Array[String]) }
def configuration_commands = []
sig { returns(T::Array[String]) }
def configuration_command_messages = []
sig { returns(T.nilable(String)) }
def install_command = nil
sig { returns(T::Boolean) }
def nested_sandbox? = false
sig { params(attributes: T.nilable(String), size: Integer, flags: Integer).returns(Integer) }
def landlock_create_ruleset(attributes, size, flags)
@landlock_create_ruleset ||= T.let(
@@ -264,13 +244,6 @@ class Sandbox
private
sig { void }
def ensure_available!
return if available?
raise failure_reason || "Landlock is not available."
end
sig { returns(Symbol) }
def compute_state
return :config_disabled unless Homebrew::EnvConfig.sandbox_linux?
@@ -357,7 +330,7 @@ class Sandbox
# https://github.com/torvalds/linux/blob/master/drivers/char/mem.c
# POSIX shared memory and message queues use `/dev/shm` and
# `/dev/mqueue`. These grants retain normal kernel permissions but do
# not provide Bubblewrap's private IPC namespace:
# not provide a private IPC namespace:
# https://github.com/bminor/glibc/blob/master/sysdeps/posix/shm-directory.c
# https://www.kernel.org/doc/html/latest/filesystems/mqueue.html
device_path_rules = T.let({
@@ -22,10 +22,7 @@ module OS
sig { returns(T::Boolean) }
def configure_sandbox!
require "sandbox"
::Sandbox.configure!
::Sandbox.available?
rescue ::ErrorDuringExecution
false
end
end
+4 -1
View File
@@ -64,7 +64,10 @@ module OS
check_access_directories
]
# Developer tools are checked when building from source.
# We need the developer tools for `codesign` on Intel:
# https://github.com/Homebrew/brew/issues/23418
checks << "check_for_installed_developer_tools" unless ::Hardware::CPU.arm?
checks.freeze
end
+37
View File
@@ -108,6 +108,43 @@ module OS
def codesign_patched_binary(file)
return if MacOS.version < :big_sur
unless ::Hardware::CPU.arm?
# Intel macOS rejects ruby-macho's ad-hoc signatures on larger
# binaries and does not require unsigned binaries to be signed,
# so use `codesign` to re-sign only the binaries whose existing
# signature our modifications have just broken:
# https://github.com/Homebrew/brew/issues/23418
result = system_command("codesign", args: ["--verify", file], print_stderr: false)
return unless result.stderr.match?(/invalid signature/i)
odebug "Codesigning #{file}"
return if quiet_system("codesign", "--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file)
# If the codesigning fails, it may be a bug in Apple's codesign utility.
# A known workaround is to copy the file to another inode, then move it back
# erasing the previous file. Then sign again.
Dir::Tmpname.create("workaround") do |tmppath|
FileUtils.cp file, tmppath
FileUtils.mv tmppath, file, force: true
end
odebug "Codesigning (2nd try) #{file}"
result = system_command("codesign", args: [
"--sign", "-", "--force",
"--preserve-metadata=entitlements,requirements,flags,runtime",
file
], print_stderr: false)
return if result.success?
onoe <<~EOS
Failed applying an ad-hoc signature to #{file}:
#{result.stderr}
EOS
return
end
require "macho"
odebug "Codesigning #{file}"
+10 -3
View File
@@ -12,8 +12,15 @@ module OS
requires_ancestor { Kernel }
sig { params(tap: ::Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type)
sig {
params(
tap: ::Tap,
os_name: T.nilable(Symbol),
arch: T.nilable(Symbol),
files: T.nilable(T::Array[::Pathname]),
).returns(T::Boolean)
}
def valid_casks?(tap, os_name: nil, arch: ::Hardware::CPU.type, files: nil)
return super if os_name == :linux
current_macos_version = if os_name.is_a?(Symbol)
@@ -23,7 +30,7 @@ module OS
end
success = T.let(true, T::Boolean)
tap.cask_files.each do |file|
(files || tap.cask_files).each do |file|
cask = ::Cask::CaskLoader.load(file)
# Fine to have missing URLs for unsupported macOS
@@ -13,16 +13,17 @@ module OS
sig { void }
def initialize
super
@xcode = T.let(nil, T.nilable(String))
@clt = T.let(nil, T.nilable(Version))
end
sig { returns(String) }
def describe_clang
return "N/A" if ::SystemConfig.clang.null?
return "N/A" if clang.null?
clang_build_info = ::SystemConfig.clang_build.null? ? "(parse error)" : ::SystemConfig.clang_build
"#{::SystemConfig.clang} build #{clang_build_info}"
clang_build_info = clang_build.null? ? "(parse error)" : clang_build
"#{clang} build #{clang_build_info}"
end
sig { returns(T.nilable(String)) }
@@ -39,12 +40,6 @@ module OS
@clt ||= MacOS::CLT.version if MacOS::CLT.installed?
end
sig { params(out: T.any(File, StringIO, IO)).void }
def core_tap_config(out = $stdout)
dump_tap_config(CoreTap.instance, out)
dump_tap_config(CoreCaskTap.instance, out)
end
sig { returns(T.nilable(String)) }
def metal_toolchain
return unless ::Hardware::CPU.arm64?
@@ -81,4 +76,5 @@ module OS
end
end
end
SystemConfig.singleton_class.prepend(OS::Mac::SystemConfig::ClassMethods)
+7 -9
View File
@@ -538,8 +538,7 @@ class Formula
!!head && !stable
end
# Stop RuboCop from erroneously indenting hash target
delegate [ # rubocop:disable Layout/HashAlignment
delegate [
:bottle_defined?,
:bottle_tag?,
:bottled?,
@@ -618,8 +617,7 @@ class Formula
# @see .version
delegate version: :active_spec
# Stop RuboCop from erroneously indenting hash target
delegate [ # rubocop:disable Layout/HashAlignment
delegate [
:allow_network_access!,
:deny_network_access!,
:network_access_allowed?,
@@ -1574,7 +1572,7 @@ class Formula
delegate pour_bottle_check_unsatisfied_reason: :"self.class"
# Can be overridden to run commands on both source and bottle installation.
# odeprecated
sig { overridable.void }
def post_install; end
@@ -1646,7 +1644,10 @@ class Formula
with_logging("post_install") do
run_post_install_steps if post_install_steps_defined?
post_install if post_install_defined?
if post_install_defined?
# odeprecated "`post_install`", "`post_install_steps`"
post_install
end
end
end
end
@@ -4083,9 +4084,6 @@ class Formula
current_steps.concat(
if block
Homebrew::InstallSteps::DSL.build(
# TODO: Remove the undocumented `default_base: :var` compatibility default after official taps use
# explicit bases.
default_base: :var,
default_source_base: :prefix,
default_target_base: :prefix,
&block
+5 -5
View File
@@ -69,7 +69,7 @@ module Homebrew
@problems = T.let([], T::Array[T.any(String, T::Hash[Symbol, T.untyped])])
@new_formula_problems = T.let([], T::Array[T.any(String, T::Hash[Symbol, T.untyped])])
@text = T.let(formula.path.open("rb", &:read), String)
@specs = T.let(%w[stable head].filter_map { |s| formula.send(s) }, T::Array[SoftwareSpec])
@specs = T.let(%w[stable head].filter_map { |s| formula.public_send(s) }, T::Array[SoftwareSpec])
@spdx_license_data = spdx_license_data
@spdx_exception_data = spdx_exception_data
@tap_audit = tap_audit
@@ -644,7 +644,7 @@ module Homebrew
return if homepage.match?(%r{^https?://www\.(?:non)?gnu\.org/.+}) && github_runner
use_homebrew_curl = [:stable, :head].any? do |spec_name|
next false unless (spec = formula.send(spec_name))
next false unless (spec = formula.public_send(spec_name))
spec.using == :homebrew_curl
end
@@ -855,7 +855,7 @@ module Homebrew
%w[Stable HEAD].each do |name|
spec_name = name.downcase.to_sym
next unless (spec = formula.send(spec_name))
next unless (spec = formula.public_send(spec_name))
except = @except.to_a
if spec_name == :head &&
@@ -1324,12 +1324,12 @@ module Homebrew
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def problem(message, location: nil, corrected: false)
@problems << ({ message:, location:, corrected: })
@problems << { message:, location:, corrected: }
end
sig { params(message: String, location: T.nilable(Homebrew::SourceLocation), corrected: T::Boolean).void }
def new_formula_problem(message, location: nil, corrected: false)
@new_formula_problems << ({ message:, location:, corrected: })
@new_formula_problems << { message:, location:, corrected: }
end
sig { params(repo_owner: String).returns(T::Boolean) }
+8 -19
View File
@@ -448,20 +448,7 @@ class FormulaInstaller
if Homebrew::EnvConfig.developer?
# `recursive_dependencies` trims cyclic dependencies, so we do one level and take the recursive deps of that.
# Mapping direct dependencies to deeper dependencies in a hash is also useful for the cyclic output below.
recursive_dep_map = formula.deps.to_h do |dep|
# We cheat a bit with bubblewrap. We eagerly add it to build dependencies on tier-one systems.
# But this cyclic dependency check is (intentionally) overly strict and forbids cyclic build dependencies,
# to help prevent cases that would break, for example, mass bottling.
recursive_deps = if dep.name == "bubblewrap" && dep.implicit?
[]
else
dep.to_formula.recursive_dependencies do |_dependent, recursive_dep|
Dependable::PRUNE if recursive_dep.name == "bubblewrap" && recursive_dep.implicit?
end
end
[dep, recursive_deps]
end
recursive_dep_map = formula.deps.to_h { |dep| [dep, dep.to_formula.recursive_dependencies] }
cyclic_dependencies = []
recursive_dep_map.each do |dep, recursive_deps|
@@ -1066,8 +1053,6 @@ on_request: installed_on_request?, options:)
# let's reset Utils::Git.available? if we just installed git
Utils::Git.clear_available_cache if formula.name == "git"
Sandbox.reset_state! if formula.name == "bubblewrap"
# use installed ca-certificates when it's needed and available
if formula.name == "ca-certificates" &&
!DevelopmentTools.ca_file_handles_most_https_certificates?
@@ -1369,10 +1354,10 @@ on_request: installed_on_request?, options:)
@show_summary_heading = true
end
sig { returns(Pathname) }
sig { returns(T.any(String, Pathname)) }
def post_install_formula_path
# Use the formula from the keg when any of the following is true:
# * We're installing from the JSON API
# * We're installing from the JSON API and it has a Ruby post-install hook
# * We're installing a local bottle file
# * We're building from source
# * The formula doesn't exist in the tap (or the tap isn't installed)
@@ -1387,7 +1372,11 @@ on_request: installed_on_request?, options:)
return tap_formula_path if installed_prefix.nil?
keg_formula_path = installed_prefix/".brew/#{formula.name}.rb"
return keg_formula_path if formula.loaded_from_api?
if formula.loaded_from_api?
return formula.full_name unless formula.post_install_defined?
return keg_formula_path
end
return keg_formula_path if formula.local_bottle_path
return keg_formula_path if build_from_source?
+1 -1
View File
@@ -199,7 +199,7 @@ class GitHubRunnerMatrix
unless self_hosted
container = {
image: "ghcr.io/homebrew/brew:main",
options: "--init --user linuxbrew --env HOMEBREW_SANDBOX_LINUX_LANDLOCK=1",
options: "--init --user linuxbrew",
}
workdir = "/github/home"
end
@@ -65,9 +65,11 @@ Once an install step DSL method has shipped in a stable `Homebrew/brew`
release, keep accepting and executing it throughout this migration. A better
replacement may stop being documented and become the target of tap migrations,
but the old method must remain marked with `# odeprecated` until it can be
deprecated in a later release. Each `Homebrew/brew` PR must continue to pass
deprecated in a later release. Each capability PR must continue to pass
tap-wide syntax checks against the default branches of `homebrew/core` and
`homebrew/cask`; paired tap branches cannot provide atomic compatibility.
`homebrew/cask`; paired tap branches cannot provide atomic compatibility. The
final official-hook enforcement PR is the deliberate exception: keep it at the
top of the stack and red until both tap migrations have landed.
RuboCop autocorrection converts the simplest existing `post_install` and
`*flight` Ruby blocks to steps blocks when every statement is a supported file
@@ -105,37 +107,40 @@ The delivery order is now:
in their recorded order. A tap file containing several step types is
assigned to the latest brew capability it needs so every intermediate tap
commit remains loadable.
1. Add sandbox hardening, conservative autocorrection and audit cops after the
taps can consume every new DSL method.
1. Keep the official-tap enforcement PR at the top of the stack. Its tap syntax
job is expected to fail until both tap stacks reach zero legacy hooks, then
it can merge without changing runtime compatibility.
1. Add conflicts or legacy-hook deprecations only after the merged tap heads,
rather than only local branches, pass the zero-hook gate.
The `Homebrew/brew` implementation stack is:
| Commit | Capability |
| Commit, PR or branch | Capability |
| --- | --- |
| `df477ca13b` | scoped path and platform guards |
| `55190efb93` | shared path, token and privilege handling |
| `51093bf43d` | copies |
| `9f4a8c9e19` | removals |
| `8ed34862ff` | `inreplace` steps |
| `7bfe8cf697` | recursive install-step validation |
| `82e7898974` | cask command wrappers |
| `053658e0fb` | generated cask scripts |
| `516d7e209e` | formula permission steps |
| `f4b8adfc25` | constrained commands |
| `996b7168a3` | process termination |
| `b312195625` | scoped warnings |
| `644eae48d6` | GCC runtime configuration |
| `9b45d57aca` | gzipped executable installation |
| `c315929071` | glibc runtime setup |
| `39956d5347` | Clang system configuration |
| `8714d6873d` | PHP configuration |
| `3e52a3efda` | Python bootstrap |
| `3d8f2c5c5c` | compatibility and canonical interface alignment |
| `81180c1ec5` | temporary cask flight migration bridge |
| `50829a2e04` | canonical autocorrection and validation |
| `138ae14570` | canonical cookbook documentation |
| `3f237af118` | scoped path and platform guards |
| `a51ad058fa` | shared path, token and privilege handling |
| `f98b955d42` | copies |
| `af66235c7e` | removals |
| `390c74bd49` | `inreplace` steps |
| `e05c08f986` | recursive install-step validation |
| `c33846e9da` | cask command wrappers with declared names |
| `eeaae4a0dc` | generated cask scripts |
| `677dad25a5` | formula permission steps |
| `cf5bc21c6b` | constrained commands |
| `2c2089fc74` | process termination |
| `46b43d57d0` | scoped warnings |
| `7dc5f2bbaf` | GCC runtime configuration and idempotence |
| `a959228644` | gzipped executable installation |
| `d39b823008` | glibc runtime setup |
| `b312752fc0` | Clang system configuration |
| `Homebrew/brew#23191` | PHP configuration |
| `Homebrew/brew#23192` | Python bootstrap |
| `Homebrew/brew#23193` | compatibility and canonical interface alignment |
| `Homebrew/brew#23194` | temporary cask flight migration bridge |
| `Homebrew/brew#23195` | canonical autocorrection and validation |
| `Homebrew/brew#23196` | canonical cookbook documentation |
| `install-step-25-compact-json` | compact structured step payloads |
| `install-step-26-official-hook-enforcement` | reject new legacy hooks in official taps |
### Compatibility bridge
@@ -159,34 +164,34 @@ The `homebrew/core` branch `install-step-migrations` is split into this stack:
| Commit | Migration | Brew dependency |
| --- | --- | --- |
| `0b9bd868455` | copies | `7bfe8cf697` |
| `051113f88ab` | removals | `7bfe8cf697` |
| `249f4350d30` | `inreplace` steps | `7bfe8cf697` |
| `0f7ceca3561` | formula permissions | `516d7e209e` |
| `c5ef325490e` | commands | `f4b8adfc25` |
| `6a27ffeb425` | process termination | `996b7168a3` |
| `cc9fa35cb13` | config warnings | `b312195625` |
| `b710256aded` | GCC runtimes | `644eae48d6` |
| `ed9c245477f` | gzipped executables | `9b45d57aca` |
| `8de30da8919` | glibc runtime setup | `c315929071` |
| `15220c281ec` | Clang configs | `39956d5347` |
| `ccb800cfe2e` | PHP configuration | `8714d6873d` |
| `424452ca1d2` | Python bootstrap | `3e52a3efda` |
| `c5181e26c9c` | existing rebuild actions | `3d8f2c5c5c` |
| `c63aad69e25` | canonical names | `3d8f2c5c5c` |
| `8b2a3736ad2` | copies | `e05c08f986` |
| `a410f86f0e5` | removals | `e05c08f986` |
| `8fff4cfe6ac` | `inreplace` steps | `e05c08f986` |
| `36a86e4cd3` | formula permissions | `677dad25a5` |
| `b737b6ddfc5` | commands | `cf5bc21c6b` |
| `de01d0cb71f` | process termination | `2c2089fc74` |
| `99f7e7ee196` | config warnings | `46b43d57d0` |
| `954c3eeb0e1` | GCC runtimes | `7dc5f2bbaf` |
| `60b9fd667cb` | gzipped executables | `a959228644` |
| `12494317fea` | glibc runtime setup | `d39b823008` |
| `1ae21b8bacc` | Clang configs | `b312752fc0` |
| `b1363e7f414` | PHP configuration | `Homebrew/brew#23191` |
| `54b3a12d201` | Python bootstrap | `Homebrew/brew#23192` |
| `b6f242bc378` | existing rebuild actions | `Homebrew/brew#23193` |
| `fba7166ace8` | canonical names | `Homebrew/brew#23193` |
The `homebrew/cask` branch `install-step-migrations` is split into this stack:
| Commit | Migration | Brew dependency |
| --- | --- | --- |
| `759984cd350` | removals | `7bfe8cf697` |
| `b970d628f53` | `inreplace` steps | `7bfe8cf697` |
| `6827cc8f6b5` | command wrappers | `82e7898974` |
| `3421d25b25d` | generated scripts | `053658e0fb` |
| `985a8dc24f7` | commands and mixed steps | `f4b8adfc25` |
| `1951737bab1` | process termination | `996b7168a3` |
| `c5d23bfe585` | existing structured flight steps | `3d8f2c5c5c` |
| `8581c70e3a9` | canonical names | `3d8f2c5c5c` |
| `98460ee76f7` | removals | `e05c08f986` |
| `c4600c11d1c` | `inreplace` steps | `e05c08f986` |
| `cc918a9697d` | command wrappers | `c33846e9da` |
| `1010ee37a29` | generated scripts | `eeaae4a0dc` |
| `a6dda33a841` | commands and mixed steps | `cf5bc21c6b` |
| `e6787710ae2` | process termination | `2c2089fc74` |
| `46ac0e3034b` | existing structured flight steps | `Homebrew/brew#23193` |
| `79ac6f5fc5c` | canonical names | `Homebrew/brew#23193` |
`gcloud-cli` is the only cask copy user, but its matching flight blocks also
need removal, linking and command steps. It therefore lands in the command
@@ -206,8 +211,8 @@ formula files and `82` `post_install` methods, and `homebrew/cask` at
`892cff1a33bb`, with `7,701` cask files and `146` legacy flight blocks in `124`
casks.
The refreshed local tap migrations at `homebrew/core` `6e23a533d3e4` and
`homebrew/cask` `215a345b6b40` now pass the gate:
The refreshed local tap migrations at `homebrew/core` `fba7166ace8` and
`homebrew/cask` `79ac6f5fc5c` now pass the gate:
- `homebrew/core` has `0` `post_install` methods after converting all `86`
hook-bearing files.
@@ -219,6 +224,13 @@ The compatibility naming pass also updates structured-step users that did not
have a legacy hook. The complete local stacks therefore differ from their tap
heads in `140` formula files and `137` cask files.
The final brew-only audit on 30 July 2026 found `21` formula hooks at
`homebrew/core` `2927a618306` and `12` cask flight blocks at `homebrew/cask`
`13de0293aa0`. Every remaining file is already covered by the prepared
migration stacks, but those stacks now conflict with their moving tap heads and
must be rebased during PR 29. Until that happens, the official-hook enforcement
PR is expected to fail tap syntax.
This proves that the implemented DSL is sufficient, but it does not authorise
bridge conflicts yet. The tap stacks must first be reviewed and merged against
current heads after a stable `Homebrew/brew` release contains the new DSL. The
@@ -335,11 +347,11 @@ less consistent with Formula syntax without reducing the runner surface.
## API Source Download Gates
Formula JSON API installs need to preserve `post_install` because it is the
only install-time Ruby hook recorded for bottle installs. The hook runs from
the formula stored in the installed keg, while source builds and local patch
handling use `Homebrew::API::Formula.source_download_formula` for build-time
reasons outside this post-install DSL work.
Formula JSON API installs load `post_install_steps` through `FormulaStruct` and
run them without downloading formula Ruby. Bottles retain the formula stored in
the keg only for legacy `post_install` compatibility. Source builds and local
patch handling still use `Homebrew::API::Formula.source_download_formula` for
build-time reasons outside this post-install DSL work.
Cask JSON API installs use `Homebrew::API::Cask.source_download_cask` when
`Cask#caskfile_only?` is true. Legacy `preflight`, `postflight`,
@@ -348,7 +360,15 @@ API data only records that a block exists, not the Ruby body. Current API data
stores each language block's locale group, default marker, return value and
resulting stanza differences, so language-specific URLs can be resolved before
the download is enqueued. Older API data with only the flat `languages` array
continues to download source as a compatibility fallback.
continues to download source as a compatibility fallback. Once the official tap
contains only structured flight steps, those artifacts do not make
`Cask#caskfile_only?` true and cask Ruby is not downloaded.
Formula structs omit empty steps and false legacy-hook markers. Cask structs
store artifacts as compact positional arrays and omit blank fields. Individual
steps omit values supplied by runner defaults while retaining values, such as
the canonical `move` overwrite default, that are required to distinguish them
from already released payloads.
## Installed Cask Metadata Format
@@ -470,7 +490,7 @@ is stripped during metadata serialisation.
- [x] PR 4.1, formula install-step sandboxing.
Commit: `Sandbox formula install steps`.
Scope: run structured formula steps inside the existing post-install child
process so macOS Seatbelt and Linux Bubblewrap apply the same filesystem and
process so macOS Seatbelt and Linux Landlock apply the same filesystem and
network policy as legacy `post_install` hooks. This must land before any tap
migrations use filesystem-mutating steps.
- PR 5, default config and template writes (historical split workflow).
@@ -681,14 +701,20 @@ is stripped during metadata serialisation.
- [ ] PR 29, refresh and merge both tap stacks.
Scope: after the brew DSL ships in a stable release, rebase each stack onto
the current tap head, repeat the zero-hook gate and merge in order.
- [ ] PR 30, sandbox and runner hardening.
Scope: sandbox eligible helpers and commands without adding migration DSL.
- [ ] PR 31, enforcement and migration cops.
Scope: add conservative autocorrection and audits after both taps consume the
complete DSL. Do not introduce conflicts here.
- [x] PR 30, compact structured step payloads.
Scope: omit runner defaults from internal JSON while preserving explicit
values needed to distinguish released compatibility behaviour.
- [ ] PR 31, official-tap enforcement.
Scope: reject `post_install` and legacy flight blocks in taps owned by the
Homebrew organisation, remove their authoring documentation and mark the
runtime call sites with commented `odeprecated` calls. Keep this PR at the
top of the stack and expect tap syntax to fail until PR 29 reaches zero
hooks. Do not introduce runtime conflicts or warnings here; third-party
compatibility remains intact.
- [ ] PR 32, close the bridges and deprecate legacy hooks.
Hard prerequisite: the merged `homebrew/core` head has no `post_install`
methods and the merged `homebrew/cask` head has no legacy flight blocks.
methods and the merged `homebrew/cask` head has no legacy flight blocks. Add
actual `odeprecated` calls in the next major or minor Homebrew release.
- [ ] PR 33, remove the documented formula `var` default while retaining it
temporarily as a runtime compatibility fallback.
- [ ] PR 34, migrate every implicit `var` path in `homebrew/core` to an
+9 -1
View File
@@ -25,7 +25,9 @@ module OnSystem
sig { params(os_name: Symbol, or_condition: T.nilable(Symbol)).returns(T::Boolean) }
def self.os_condition_met?(os_name, or_condition = nil)
return Homebrew::SimulateSystem.send(:"simulating_or_running_on_#{os_name}?") if BASE_OS_OPTIONS.include?(os_name)
if BASE_OS_OPTIONS.include?(os_name)
return Homebrew::SimulateSystem.public_send(:"simulating_or_running_on_#{os_name}?")
end
raise ArgumentError, "Invalid OS condition: #{os_name.inspect}" unless MacOSVersion::SYMBOLS.key?(os_name)
@@ -92,8 +94,10 @@ module OnSystem
return unless OnSystem.os_condition_met? OnSystem.condition_from_method_name(T.must(__method__))
@called_in_on_system_block = true
@called_in_on_os_block = T.let(true, T.nilable(T::Boolean))
result = block.call
@called_in_on_system_block = false
@called_in_on_os_block = false
result
end
@@ -113,8 +117,10 @@ module OnSystem
return if !OnSystem.os_condition_met?(os_version, or_condition) && !OnSystem.os_condition_met?(:linux)
@called_in_on_system_block = true
@called_in_on_os_block = T.let(true, T.nilable(T::Boolean))
result = block.call
@called_in_on_system_block = false
@called_in_on_os_block = false
result
end
@@ -149,8 +155,10 @@ module OnSystem
T.nilable(MacOSVersion),
)
@called_in_on_system_block = T.let(true, T.nilable(T::Boolean))
@called_in_on_os_block = T.let(true, T.nilable(T::Boolean))
result = block.call
@called_in_on_system_block = false
@called_in_on_os_block = false
result
end
+2 -1
View File
@@ -13,6 +13,7 @@ require "utils/popen"
module Patch
CVE_PATTERN = /CVE-?(\d{4})-(\d{4,})/i
GHSA_PATTERN = /\AGHSA(-[23456789cfghjmpqrvwx]{4}){3}\z/
OSV_PATTERN = /\AOSV-\d{4}-\d+\z/
# CycloneDX `pedigree.patches.type` values applicable to source diffs.
# `monkey` is omitted: it describes runtime modification, which `patch do` cannot express.
# Keep in sync with `PATCH_TYPES` in `Library/Homebrew/rubocops/patches.rb`.
@@ -35,7 +36,7 @@ module Patch
sig { params(id: String).returns(String) }
def self.resolves_type(id)
return "security" if id.match?(/\ACVE-\d{4}-\d{4,}\z/) || id.match?(GHSA_PATTERN)
return "security" if id.match?(/\ACVE-\d{4}-\d{4,}\z/) || id.match?(GHSA_PATTERN) || id.match?(OSV_PATTERN)
"defect"
end
+169 -45
View File
@@ -3,34 +3,59 @@
require "formula"
require "cask/cask_loader"
require "system_command"
require "tempfile"
require "utils/output"
# Helper module for validating syntax in taps.
module Readall
extend T::Generic
extend Cachable
extend SystemCommand::Mixin
extend Utils::Output::Mixin
Cache = type_template { { fixed: T::Hash[Symbol, T.untyped] } }
private_class_method :cache
MIN_FILES_PER_WORKER = 4
private_constant :MIN_FILES_PER_WORKER
# Buffers Ruby compile warnings from {.syntax_errors_or_warnings?} so they
# can be filtered before being printed to `$stderr`.
module WarningBuffer
sig { params(message: String, category: T.nilable(Symbol)).void }
def warn(message, category: nil)
buffer = Readall.warning_buffer
buffer ? buffer << message : super
end
end
private_constant :WarningBuffer
Warning.singleton_class.prepend(WarningBuffer)
@warning_buffer = T.let(nil, T.nilable(T::Array[String]))
class << self
sig { returns(T.nilable(T::Array[String])) }
attr_accessor :warning_buffer
end
sig { params(ruby_files: T::Array[Pathname]).returns(T::Boolean) }
def self.valid_ruby_syntax?(ruby_files)
failed = T.let(false, T::Boolean)
ruby_files.each do |ruby_file|
# As a side effect, print syntax errors/warnings to `$stderr`.
failed = true if syntax_errors_or_warnings?(ruby_file)
parallel_slices_valid?(ruby_files) do |files|
failed = T.let(false, T::Boolean)
files.each do |ruby_file|
# As a side effect, print syntax errors/warnings to `$stderr`.
failed = true if syntax_errors_or_warnings?(ruby_file)
end
!failed
end
!failed
end
sig { params(alias_dir: Pathname, formula_dir: Pathname).returns(T::Boolean) }
def self.valid_aliases?(alias_dir, formula_dir)
return true unless alias_dir.directory?
formula_basenames = Set.new(formula_dir.glob("**/*.rb").map { |formula_file| formula_file.basename.to_s })
failed = T.let(false, T::Boolean)
alias_dir.each_child do |f|
if !f.symlink?
@@ -41,7 +66,7 @@ module Readall
failed = true
end
if formula_dir.glob("**/#{f.basename}.rb").any?(&:exist?)
if formula_basenames.include?("#{f.basename}.rb")
onoe "Formula duplicating alias: #{f}"
failed = true
end
@@ -49,12 +74,16 @@ module Readall
!failed
end
sig { params(tap: Tap, bottle_tag: T.nilable(Utils::Bottles::Tag)).returns(T::Boolean) }
def self.valid_formulae?(tap, bottle_tag: nil)
sig {
params(
tap: Tap, bottle_tag: T.nilable(Utils::Bottles::Tag), files: T.nilable(T::Array[Pathname]),
).returns(T::Boolean)
}
def self.valid_formulae?(tap, bottle_tag: nil, files: nil)
cache[:valid_formulae] ||= {}
success = T.let(true, T::Boolean)
tap.formula_files.each do |file|
(files || tap.formula_files).each do |file|
valid = cache[:valid_formulae][file]
next if valid == true || valid&.include?(bottle_tag)
@@ -82,8 +111,12 @@ module Readall
success
end
sig { params(tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) }
def self.valid_casks?(tap, os_name: nil, arch: nil)
sig {
params(
tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol), files: T.nilable(T::Array[Pathname]),
).returns(T::Boolean)
}
def self.valid_casks?(tap, os_name: nil, arch: nil, files: nil)
validating_linux = if os_name.nil?
Homebrew::SimulateSystem.current_os == :linux
else
@@ -95,9 +128,7 @@ module Readall
os_and_arch += " on #{(arch == :intel) ? "Intel x86_64" : "ARM64"}" if arch
success = T.let(true, T::Boolean)
tap.cask_files.each do |file|
next if file.read.match?(/^\s*depends_on(?:\s*\(\s*|\s+)(?::macos\b|macos:)/)
(files || tap.cask_files).each do |file|
cask = if arch
Homebrew::SimulateSystem.with(os: :macos, arch:) do
loaded_cask = Cask::CaskLoader.load(file)
@@ -111,24 +142,28 @@ module Readall
end
next unless cask
linux_sha256 = if arch
Homebrew::SimulateSystem.with(os: :linux, arch:) do
cask.refresh
cask.sha256
end
check_linux_sha256 = lambda do
cask.refresh
arch_types = cask.depends_on.arch&.map { |cask_arch| cask_arch[:type] }
# `depends_on arch:` excludes this architecture, so no Linux
# checksum is expected for it.
next true if arch_types&.exclude?(Homebrew::SimulateSystem.current_arch)
!cask.sha256.nil?
end
linux_sha256_valid = if arch
Homebrew::SimulateSystem.with(os: :linux, arch:, &check_linux_sha256)
else
Homebrew::SimulateSystem.with(os: :linux) do
cask.refresh
cask.sha256
end
Homebrew::SimulateSystem.with(os: :linux, &check_linux_sha256)
end
# No `sha256` matched Linux, so the cask cannot be downloaded there
# despite not being marked macOS-only.
next unless linux_sha256.nil?
next if linux_sha256_valid
onoe "Invalid cask (#{os_and_arch}): #{file}"
$stderr.puts "Missing Linux stanzas can leave Linux `sha256` as nil. " \
"Add `depends_on :macos` if this cask is macOS-only."
"Add `depends_on :macos` if this cask is macOS-only or " \
"`depends_on arch:` if it does not support this architecture."
success = false
rescue Interrupt
raise
@@ -155,40 +190,129 @@ module Readall
success = false unless valid_aliases
end
if no_simulate
success = false unless valid_formulae?(tap)
success = false unless valid_casks?(tap)
else
os_arch_combinations.each do |os, arch|
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
items = tap.formula_files.map { |file| [:formula, file] } +
tap.cask_files.map { |file| [:cask, file] }
Homebrew::SimulateSystem.with(os:, arch:) do
success = false unless valid_formulae?(tap, bottle_tag:)
success = false unless valid_casks?(tap, os_name: os, arch:)
all_files_valid = parallel_slices_valid?(items) do |slice|
formula_files = slice.filter_map { |type, file| file if type == :formula }
cask_files = slice.filter_map { |type, file| file if type == :cask }
slice_success = T.let(true, T::Boolean)
if no_simulate
slice_success = false unless valid_formulae?(tap, files: formula_files)
slice_success = false unless valid_casks?(tap, files: cask_files)
else
os_arch_combinations.each do |os, arch|
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
next unless bottle_tag.valid_combination?
Homebrew::SimulateSystem.with(os:, arch:) do
slice_success = false unless valid_formulae?(tap, bottle_tag:, files: formula_files)
slice_success = false unless valid_casks?(tap, os_name: os, arch:, files: cask_files)
end
end
end
slice_success
end
success = false unless all_files_valid
success
end
sig { params(filename: Pathname).returns(T::Boolean) }
private_class_method def self.syntax_errors_or_warnings?(filename)
# Retrieve messages about syntax errors/warnings printed to `$stderr`.
_, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false).to_a
# Compile in-process (much faster than spawning `ruby -c -w` per file),
# buffering compile warnings so they can be filtered.
error = T.let(nil, T.nilable(String))
warnings = self.warning_buffer = []
old_verbose = $VERBOSE
$VERBOSE = true
begin
RubyVM::InstructionSequence.compile_file(filename.to_s)
rescue ScriptError, ArgumentError => e
error = "#{e.message.chomp}\n"
ensure
$VERBOSE = old_verbose
self.warning_buffer = nil
end
# Ignore unnecessary warning about named capture conflicts.
# See https://bugs.ruby-lang.org/issues/12359.
messages = err.lines
.grep_v(/named capture conflicts a local variable/)
.join
messages = warnings.grep_v(/named capture conflicts a local variable/).join
messages += error if error
$stderr.print messages
# Only syntax errors result in a non-zero status code. To detect syntax
# warnings we also need to inspect the output to `$stderr`.
!status.success? || !messages.chomp.empty?
# Both syntax errors and syntax warnings count as failures.
!messages.chomp.empty?
end
sig {
type_parameters(:U).params(
items: T::Array[T.type_parameter(:U)],
_block: T.proc.params(arg0: T::Array[T.type_parameter(:U)]).returns(T::Boolean),
).returns(T::Boolean)
}
private_class_method def self.parallel_slices_valid?(items, &_block)
require "hardware"
worker_count = [Hardware::CPU.cores, items.length / MIN_FILES_PER_WORKER].min
return yield(items) if worker_count <= 1
workers = items.each_slice((items.length.to_f / worker_count).ceil).map do |slice|
reader, writer = IO.pipe
stdout_file = Tempfile.new("readall-stdout")
stderr_file = Tempfile.new("readall-stderr")
pid = Process.fork do
reader.close
success = begin
# Capture output so parallel workers cannot interleave lines.
$stdout = stdout_file.to_io
$stderr = stderr_file.to_io
yield(slice)
rescue Interrupt
false
# Report any worker exception as a validation failure.
rescue Exception => e # rubocop:disable Lint/RescueException
$stderr.puts e.full_message
false
ensure
$stdout.flush
$stderr.flush
end
writer.write(Marshal.dump(success))
writer.close
exit!(true)
end
writer.close
[pid, reader, stdout_file, stderr_file]
end
success = T.let(true, T::Boolean)
workers.each do |pid, reader, stdout_file, stderr_file|
worker_success = begin
# The data being loaded was written by our own forked child process.
Marshal.load(reader) # rubocop:disable Security/MarshalLoad
rescue EOFError
nil
end
reader.close
Process.wait(pid)
[stdout_file, stderr_file].each(&:rewind)
$stdout.print stdout_file.read
$stderr.print stderr_file.read
[stdout_file, stderr_file].each(&:close!)
case worker_success
when nil
onoe "readall worker exited unexpectedly!"
success = false
when false
success = false
end
end
success
end
end
+1
View File
@@ -13,6 +13,7 @@ require_relative "full_name_split"
require_relative "io_read"
require_relative "move_to_extend_os"
require_relative "negate_include"
require_relative "no_base64"
require_relative "no_fileutils_rmrf"
require_relative "no_instance_variable_access_in_tests"
require_relative "no_send_in_tests"
@@ -42,7 +42,7 @@ module RuboCop
[
:suite,
:app,
:appimage,
:app_image,
:pkg,
:generated_script,
:installer,
@@ -21,6 +21,7 @@ module RuboCop
}.freeze,
T::Hash[Symbol, Symbol],
)
LEGACY_FLIGHT_MSG = "Casks in official Homebrew taps must use `%<steps>s` instead of `%<flight>s`."
KEYCHAIN_HASHES_SOURCE =
'hashes = stdout.lines.grep(/^SHA-256 hash:/) { |l| l.split(":").second.strip }'
KEYCHAIN_DELETE_SOURCE = T.let(
@@ -60,14 +61,27 @@ module RuboCop
next unless (flight_stanza = stanzas.find { |stanza| stanza.stanza_name == flight_block })
steps_stanza = stanzas.find { |stanza| stanza.stanza_name == steps_block }
audit_flight_block(flight_stanza, steps_block) if steps_stanza.nil?
converted_flight = autocorrect_flight_block?(flight_stanza, steps_block) if steps_stanza.nil?
# odeprecated: remove the official-tap scope in the next major or minor release.
next unless official_homebrew_tap?(processed_source.file_path)
next if converted_flight
add_offense(flight_stanza.method_node,
message: format(LEGACY_FLIGHT_MSG, steps: steps_block, flight: flight_block))
end
stanzas.each do |stanza|
next unless INSTALL_STEP_PAIRS.value?(stanza.stanza_name)
next unless stanza.method_node.block_type?
block_node = T.cast(stanza.method_node, RuboCop::AST::BlockNode)
if (offense_node = brew_ruby_step_node(block_node))
add_offense(offense_node, message: BREW_RUBY_STEP_MSG)
next
end
next unless (offense_node = install_step_block_offense_node(
T.cast(stanza.method_node, RuboCop::AST::BlockNode),
block_node,
allowed_methods: CASK_ALLOWED_STEP_METHODS,
))
@@ -77,9 +91,9 @@ module RuboCop
private
sig { params(flight_stanza: RuboCop::Cask::AST::Stanza, steps_block: Symbol).void }
def audit_flight_block(flight_stanza, steps_block)
return unless flight_stanza.method_node.block_type?
sig { params(flight_stanza: RuboCop::Cask::AST::Stanza, steps_block: Symbol).returns(T::Boolean) }
def autocorrect_flight_block?(flight_stanza, steps_block)
return false unless flight_stanza.method_node.block_type?
block_node = T.cast(flight_stanza.method_node, RuboCop::AST::BlockNode)
step_lines = keychain_certificate_step_lines(block_node.body) ||
@@ -89,7 +103,7 @@ module RuboCop
default_target_base: :staged_path,
rebuild_actions: false,
permission_actions: true)
return if step_lines.blank?
return false if step_lines.blank?
add_offense(block_node.source_range,
message: format(SIMPLE_STEP_CONVERSION_MSG, steps_block:)) do |corrector|
@@ -98,6 +112,7 @@ module RuboCop
install_steps_block_source(steps_block, step_lines, block_node.source_range.column),
)
end
true
end
sig { params(body_node: T.nilable(RuboCop::AST::Node)).returns(T.nilable(T::Array[String])) }
+126 -14
View File
@@ -15,7 +15,17 @@ module RuboCop
# TODO: Re-enable when formula `post_install` and `post_install_steps`
# cannot coexist after the incremental conversion bridge is removed.
# CONFLICT_MSG = "`post_install` and `post_install_steps` cannot both be used."
LEGACY_POST_INSTALL_MSG =
"Formulae in official Homebrew taps must use `post_install_steps` instead of `post_install`."
REDUNDANT_SERVICE_PATH_DIRS_MSG = "`%<block>s` only creates directories created by `brew services`."
EXPLICIT_BASE_MSG = "Formula install-step paths must specify their base explicitly."
EXPLICIT_BASE_STEP_METHODS = [:if_path_exists, :unless_path_exists, :mkdir, :mkdir_p, :touch, :remove,
:inreplace, :write, :write_file, :init_data_dir, :set_permissions].freeze
RUN_PATH_KEYWORDS = [:stdin_path, :stdout_path, :chdir].freeze
ABSOLUTE_PATH_TEMPLATE_TOKENS = %w[
HOMEBREW_PREFIX HOMEBREW_CELLAR prefix opt_prefix bin sbin lib libexec share pkgshare var etc pkgetc rack
staged_path appdir caskroom_path temp bash_completion zsh_completion fish_completion pwsh_completion
].freeze
CERTIFICATE_REMOVE_SOURCE = 'rm(pkgetc/"cert.pem") if (pkgetc/"cert.pem").exist?'
CERTIFICATE_INSTALL_SYMLINK_SOURCE =
'pkgetc.install_symlink Formula["ca-certificates"].pkgetc/"cert.pem"'
@@ -89,7 +99,11 @@ module RuboCop
# problem CONFLICT_MSG
# end
audit_step_block(post_install_steps_block)
redundant_post_install_steps = post_install_steps_block.present? &&
redundant_service_path_dirs_block?(post_install_steps_block,
service_path_dirs,
:post_install_steps)
audit_step_block(post_install_steps_block) unless redundant_post_install_steps
add_redundant_service_path_dirs_offense(post_install_steps_block, service_path_dirs, :post_install_steps)
redundant_post_install = post_install_method.present? &&
redundant_service_path_dirs_block?(post_install_method, service_path_dirs,
@@ -97,18 +111,114 @@ module RuboCop
add_redundant_service_path_dirs_offense(post_install_method, service_path_dirs, :post_install)
return if redundant_post_install
audit_post_install_method(post_install_method, post_install_steps_block,
body_node, formula_nodes.class_node.const_name)
converted_post_install = autocorrect_post_install_method?(post_install_method, post_install_steps_block,
body_node, formula_nodes.class_node.const_name)
# odeprecated: remove the official-tap scope in the next major or minor release.
return unless official_homebrew_tap?(processed_source.file_path)
return if post_install_method.nil? || converted_post_install
add_offense(post_install_method, message: LEGACY_POST_INSTALL_MSG)
end
private
sig { params(block_node: T.nilable(RuboCop::AST::BlockNode)).void }
def audit_step_block(block_node)
return unless (offense_node = install_step_block_offense_node(block_node))
return if block_node.nil?
offending_node(offense_node)
problem STEP_BLOCK_MSG
if (offense_node = brew_ruby_step_node(block_node))
offending_node(offense_node)
problem BREW_RUBY_STEP_MSG
return
end
if (offense_node = install_step_block_offense_node(block_node))
offending_node(offense_node)
problem STEP_BLOCK_MSG
return
end
add_implicit_var_path_offenses(block_node)
end
sig { params(block_node: RuboCop::AST::BlockNode).void }
def add_implicit_var_path_offenses(block_node)
block_node.each_descendant(:send) do |node|
send_node = T.cast(node, RuboCop::AST::SendNode)
next if send_node.receiver
if EXPLICIT_BASE_STEP_METHODS.include?(send_node.method_name)
add_implicit_var_base_offense(send_node)
elsif send_node.method_name == :run
add_implicit_var_run_path_offenses(send_node)
end
end
end
sig { params(send_node: RuboCop::AST::SendNode).void }
def add_implicit_var_base_offense(send_node)
path_node = send_node.arguments.first
return if path_node.nil? || explicit_formula_step_path?(path_node)
options = send_node.arguments.last
options = nil unless options&.hash_type?
return if options && T.cast(options, RuboCop::AST::HashNode).pairs.any? do |pair|
pair.key.sym_type? && pair.key.value == :base
end
add_offense(send_node, message: EXPLICIT_BASE_MSG) do |corrector|
if options
options = T.cast(options, RuboCop::AST::HashNode)
pair = options.pairs.last
if pair
corrector.insert_after(pair.source_range, ", base: :var")
else
corrector.replace(options, "base: :var")
end
else
argument = send_node.arguments.last
next if argument.nil?
range = if argument.loc.respond_to?(:heredoc_end) && argument.loc.heredoc_end
argument.loc.expression
else
argument.source_range
end
corrector.insert_after(range, ", base: :var")
end
end
end
sig { params(send_node: RuboCop::AST::SendNode).void }
def add_implicit_var_run_path_offenses(send_node)
options = send_node.arguments.last
return unless options&.hash_type?
T.cast(options, RuboCop::AST::HashNode).pairs.each do |pair|
next if !pair.key.sym_type? || !RUN_PATH_KEYWORDS.include?(pair.key.value)
next if explicit_formula_step_path?(pair.value)
add_offense(pair.value, message: EXPLICIT_BASE_MSG) do |corrector|
next unless pair.value.str_type?
path = T.cast(pair.value, RuboCop::AST::StrNode).str_content
corrector.replace(pair.value, "{{var}}/#{path}".dump)
end
end
end
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def explicit_formula_step_path?(node)
if node.array_type?
paths = node.child_nodes
return paths.present? && paths.all? { |path| explicit_formula_step_path?(path) }
end
return false unless node.str_type?
path = T.cast(node, RuboCop::AST::StrNode).str_content
return true if path.start_with?("/", "~")
ABSOLUTE_PATH_TEMPLATE_TOKENS.any? { |token| path.start_with?("{{#{token}}}") }
end
sig {
@@ -117,14 +227,15 @@ module RuboCop
post_install_steps_block: T.nilable(RuboCop::AST::BlockNode),
formula_body: RuboCop::AST::Node,
formula_class: String,
).void
).returns(T::Boolean)
}
def audit_post_install_method(post_install_method, post_install_steps_block, formula_body, formula_class)
return if post_install_method.nil?
return unless post_install_method.def_type?
def autocorrect_post_install_method?(post_install_method, post_install_steps_block, formula_body,
formula_class)
return false if post_install_method.nil?
return false unless post_install_method.def_type?
post_install_def = T.cast(post_install_method, RuboCop::AST::DefNode)
return if post_install_steps_block && post_install_steps_block.loc.line > post_install_def.loc.line
return false if post_install_steps_block && post_install_steps_block.loc.line > post_install_def.loc.line
step_nodes = T.let({}, T::Hash[RuboCop::AST::Node, T::Array[String]])
removable_methods = T.let([], T::Array[RuboCop::AST::Node])
@@ -139,16 +250,16 @@ module RuboCop
unless step_nodes.empty?
add_formula_step_conversion_offense(post_install_def, post_install_steps_block, direct_nodes, step_nodes,
removable_methods)
return
return true
end
return if post_install_steps_block
return false if post_install_steps_block
step_lines = simple_install_step_lines(post_install_def.body,
default_base: :var,
default_source_base: :prefix,
default_target_base: :prefix)
return if step_lines.blank?
return false if step_lines.blank?
add_offense(post_install_method,
message: format(SIMPLE_STEP_CONVERSION_MSG, steps_block: "post_install_steps")) do |corrector|
@@ -157,6 +268,7 @@ module RuboCop
install_steps_block_source(:post_install_steps, step_lines, post_install_method.source_range.column),
)
end
true
end
sig {
+110
View File
@@ -0,0 +1,110 @@
# typed: strict
# frozen_string_literal: true
module RuboCop
module Cop
module Homebrew
# Enforces the use of `String#unpack1` and `Array#pack` over the
# `base64` gem, which Homebrew no longer includes.
#
# ### Example
#
# ```ruby
# # bad
# require "base64"
# Base64.decode64(encoded)
# Base64.strict_encode64(decoded)
#
# # good
# encoded.unpack1("m")
# [decoded].pack("m0")
# ```
class NoBase64 < Base
include RangeHelp
extend AutoCorrector
MSG = "Homebrew no longer includes the `base64` gem; " \
"use `String#unpack1` or `Array#pack` instead."
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
if require_base64?(node)
add_offense(node) do |corrector|
parent = node.parent
next if parent && !parent.begin_type?
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
end
elsif top_level_const?(node.receiver, :Base64)
add_offense(node) do |corrector|
autocorrect_base64_call(corrector, node)
end
end
end
alias on_csend on_send
sig { params(node: RuboCop::AST::ConstNode).void }
def on_const(node)
return unless top_level_const?(node, :Base64)
parent = node.parent
return if parent.is_a?(RuboCop::AST::SendNode) && parent.receiver == node
# Formulae for base64 tools are legitimately named `Base64`.
return if parent.is_a?(RuboCop::AST::ClassNode) && parent.identifier == node
add_offense(node)
end
private
sig { params(node: RuboCop::AST::SendNode).returns(T::Boolean) }
def require_base64?(node)
return false unless node.method?(:require)
receiver = node.receiver
return false if receiver && !top_level_const?(receiver, :Kernel)
arg = node.first_argument
node.arguments.one? && arg.is_a?(RuboCop::AST::StrNode) && arg.value == "base64"
end
sig { params(node: T.nilable(RuboCop::AST::Node), name: Symbol).returns(T::Boolean) }
def top_level_const?(node, name)
return false unless node.is_a?(RuboCop::AST::ConstNode)
return false if node.short_name != name
namespace = node.namespace
namespace.nil? || namespace.cbase_type?
end
sig { params(corrector: RuboCop::Cop::Corrector, node: RuboCop::AST::SendNode).void }
def autocorrect_base64_call(corrector, node)
return unless node.arguments.one?
arg = node.first_argument
replacement = case node.method_name
when :decode64, :strict_decode64
directive = (node.method_name == :decode64) ? "m" : "m0"
"#{arg.source}.unpack1(\"#{directive}\")" if chainable?(arg)
when :encode64, :strict_encode64
directive = (node.method_name == :encode64) ? "m" : "m0"
"[#{arg.source}].pack(\"#{directive}\")" if !arg.splat_type? && !arg.block_pass_type?
end
return if replacement.nil?
corrector.replace(node, replacement)
end
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def chainable?(node)
if node.is_a?(RuboCop::AST::SendNode)
!node.operator_method? && !node.assignment_method?
else
node.variable? || node.const_type? || node.begin_type? ||
(node.literal? && !node.range_type?)
end
end
end
end
end
end
+54 -20
View File
@@ -28,9 +28,11 @@ module RuboCop
:vst_plugin,
:vst3_plugin,
].freeze
LINUX_ONLY_CASK_STANZAS = [:app_image].freeze
CASK_STANZA_ORDER = T.let(RuboCop::Cask::Constants::STANZA_ORDER, T::Array[Symbol])
MACOS_DEPENDENCY_STANZAS = [:macos, :maximum_macos].freeze
LINUX_DEPENDENCY_STANZAS = [:linux].freeze
RESTRICT_ON_SEND = [:depends_on].freeze
@@ -41,6 +43,7 @@ module RuboCop
return if send_node.method_name != :cask
add_missing_macos_dependency(node)
add_missing_linux_dependency(node)
end
sig { params(node: RuboCop::AST::SendNode).void }
@@ -114,19 +117,14 @@ module RuboCop
return if os_depends_on?(body)
macos_stanza = stanzas.find do |stanza|
case stanza.method_name
when :installer
if stanza.method_name == :installer
stanza.arguments.any? do |argument|
argument.hash_type? && argument.pairs.any? { |pair| symbol_key(pair) == :manual }
argument.hash_type? && argument.pairs.any? do |pair|
symbol_key(pair) == :manual
end
end
when :os
pairs = depends_on_pairs(stanza)
pairs.any? { |pair| symbol_key(pair) == :macos } &&
pairs.none? { |pair| symbol_key(pair) == :linux }
when *MACOS_ONLY_CASK_STANZAS
true
else
false
MACOS_ONLY_CASK_STANZAS.include?(stanza.method_name)
end
end
return unless macos_stanza
@@ -145,14 +143,6 @@ module RuboCop
range_by_whole_lines(following_stanza.source_range, include_final_newline: false),
" depends_on :macos\n\n",
)
elsif (preceding_stanza = stanzas.rfind do |stanza|
stanza_index = CASK_STANZA_ORDER.index(stanza.method_name)
stanza_index && stanza_index <= depends_on_stanza_index
end)
corrector.insert_after(
range_by_whole_lines(preceding_stanza.source_range, include_final_newline: true),
"\n depends_on :macos\n",
)
else
corrector.insert_before(
range_by_whole_lines(macos_stanza.source_range, include_final_newline: false),
@@ -162,6 +152,46 @@ module RuboCop
end
end
sig { params(node: RuboCop::AST::BlockNode).void }
def add_missing_linux_dependency(node)
body = node.body
return unless body
stanzas = (body.begin_type? ? body.child_nodes : [body]).filter_map do |child|
if child.send_type?
T.cast(child, RuboCop::AST::SendNode)
elsif child.block_type?
T.cast(child, RuboCop::AST::BlockNode).send_node
end
end
return if os_depends_on?(body)
linux_stanza = stanzas.find { |stanza| LINUX_ONLY_CASK_STANZAS.include?(stanza.method_name) }
return unless linux_stanza
add_offense(linux_stanza.source_range,
message: "Add `depends_on :linux` for Linux-only casks.") do |corrector|
depends_on_stanza_index = CASK_STANZA_ORDER.index(:depends_on) ||
raise("unexpected nil value for depends_on stanza index")
following_stanza = stanzas.find do |stanza|
stanza_index = CASK_STANZA_ORDER.index(stanza.method_name)
stanza_index && stanza_index > depends_on_stanza_index
end
if following_stanza
corrector.insert_before(
range_by_whole_lines(following_stanza.source_range, include_final_newline: false),
" depends_on :linux\n\n",
)
else
corrector.insert_before(
range_by_whole_lines(linux_stanza.source_range, include_final_newline: false),
" depends_on :linux\n\n",
)
end
end
end
sig { params(node: RuboCop::AST::SendNode).returns(T::Array[RuboCop::AST::PairNode]) }
def depends_on_pairs(node)
node.arguments.filter_map do |argument|
@@ -198,8 +228,7 @@ module RuboCop
next false if send_node.method_name != :depends_on
bare_os_depends_on?(send_node, :macos) || bare_os_depends_on?(send_node, :linux) ||
top_level_macos_depends_on?(send_node) ||
depends_on_pairs(send_node).any? { |pair| symbol_key(pair) == :linux }
top_level_macos_depends_on?(send_node) || top_level_linux_depends_on?(send_node)
end
end
@@ -212,6 +241,11 @@ module RuboCop
def top_level_macos_depends_on?(node)
depends_on_pairs(node).any? { |pair| MACOS_DEPENDENCY_STANZAS.include?(symbol_key(pair)) }
end
sig { params(node: RuboCop::AST::SendNode).returns(T::Boolean) }
def top_level_linux_depends_on?(node)
depends_on_pairs(node).any? { |pair| LINUX_DEPENDENCY_STANZAS.include?(symbol_key(pair)) }
end
end
end
end
+3 -2
View File
@@ -143,13 +143,14 @@ module RuboCop
def resolves_problems(node)
unless node.str_type?
offending_node(node)
problem "`resolves` should be passed identifier strings (CVE/GHSA id or issue URL)"
problem "`resolves` should be passed identifier strings (CVE/GHSA/OSV id or issue URL)"
return
end
value = string_content(node)
return if value.match?(/\ACVE-\d{4}-\d{4,}\z/)
return if value.match?(/\AGHSA(-[23456789cfghjmpqrvwx]{4}){3}\z/)
return if value.match?(/\AOSV-\d{4}-\d+\z/)
return if value.match?(%r{\Ahttps?://})
offending_node(node)
@@ -159,7 +160,7 @@ module RuboCop
corrector.replace(node.source_range, corrected.inspect)
end
else
problem "`resolves` should be a CVE/GHSA identifier or issue URL, got: #{value.inspect}"
problem "`resolves` should be a CVE/GHSA/OSV identifier or issue URL, got: #{value.inspect}"
end
end
@@ -4,6 +4,7 @@
module RuboCop
module Cop
module InstallStepsHelper
OFFICIAL_HOMEBREW_TAP_PATH_REGEX = %r{(?:\A|/)Taps/homebrew/homebrew-[\w-]+/}i
FILE_PREPARATION_STEP_METHODS =
[:mkdir, :mkdir_p, :touch, :move, :mv, :move_children, :move_contents, :copy, :remove, :inreplace,
:symlink,
@@ -50,6 +51,7 @@ module RuboCop
"#{(ALLOWED_STEP_METHODS - COMPATIBILITY_STEP_METHODS).map { |method| "`#{method}`" }.join(", ")}.".freeze,
String,
)
BREW_RUBY_STEP_MSG = "Install steps must not use `brew ruby` because it enables developer mode."
SIMPLE_STEP_CONVERSION_MSG = "Use `%<steps_block>s` for simple file preparation."
REBUILD_ACTION_STEP_LINES = T.let(
T.let([
@@ -82,6 +84,11 @@ module RuboCop
T::Hash[String, String],
)
sig { params(file_path: String).returns(T::Boolean) }
def official_homebrew_tap?(file_path)
file_path.match?(OFFICIAL_HOMEBREW_TAP_PATH_REGEX)
end
sig { params(allowed_methods: T::Array[Symbol]).returns(String) }
def step_block_msg(allowed_methods)
"Steps blocks may only contain install step DSL calls. Prefer canonical calls: " \
@@ -113,6 +120,33 @@ module RuboCop
nil
end
sig { params(block_node: T.nilable(RuboCop::AST::BlockNode)).returns(T.nilable(RuboCop::AST::Node)) }
def brew_ruby_step_node(block_node)
return if block_node.nil?
block_node.each_descendant(:send).each do |node|
send_node = T.cast(node, RuboCop::AST::SendNode)
next if send_node.receiver.present? || send_node.method_name != :run
command = send_node.first_argument
next unless command&.str_type?
next if command.str_content != "{{HOMEBREW_BREW_FILE}}"
options = send_node.arguments.last
next unless options&.hash_type?
args = T.cast(options, RuboCop::AST::HashNode).pairs.find do |pair|
pair.key.sym_type? && pair.key.value == :args
end&.value
next unless args&.array_type?
first_arg = args.child_nodes.first
return command if first_arg&.str_type? && first_arg.str_content == "ruby"
end
nil
end
sig {
params(
body_node: T.nilable(RuboCop::AST::Node),
-19
View File
@@ -120,9 +120,6 @@ class Sandbox
true
end
sig { params(install_from_tests: T::Boolean).void }
def self.ensure_sandbox_installed!(install_from_tests: false); end
sig { void }
def self.ensure_sandbox_available!
return if available?
@@ -145,24 +142,8 @@ class Sandbox
sig { void }
def self.reset_state!; end
sig { returns(T::Array[String]) }
def self.configuration_commands = []
sig { returns(T::Array[String]) }
def self.configuration_command_messages = []
sig { returns(T.nilable(String)) }
def self.sandbox_install_command = nil
sig { void }
def self.configure!
ensure_sandbox_installed!
reset_state!
end
sig { params(command: T.any(String, Pathname), writable_path: T.any(String, Pathname), deny_network: T::Boolean).void }
def self.run_command(*command, writable_path:, deny_network: false)
ensure_sandbox_installed!
ensure_sandbox_available!
writable_path = Pathname(writable_path).expand_path
+5
View File
@@ -76,6 +76,11 @@ module Homebrew
@os = @arch = nil
end
sig { returns(T::Boolean) }
def simulating?
os.present? || arch.present?
end
sig { returns(T::Boolean) }
def simulating_or_running_on_macos?
[:macos, *MacOSVersion::SYMBOLS.keys].include?(os)
@@ -1,13 +0,0 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::SetupSandbox`.
# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::SetupSandbox`.
class Homebrew::Cmd::SetupSandbox
sig { returns(Homebrew::Cmd::SetupSandbox::Args) }
def args; end
end
class Homebrew::Cmd::SetupSandbox::Args < Homebrew::CLI::Args; end
+6
View File
@@ -17,6 +17,9 @@ class Homebrew::Cmd::Vulns::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) }
def deps?; end
sig { returns(T::Boolean) }
def fix_available?; end
sig { returns(T::Boolean) }
def j?; end
@@ -29,6 +32,9 @@ class Homebrew::Cmd::Vulns::Args < Homebrew::CLI::Args
sig { returns(T.nilable(String)) }
def max_summary; end
sig { returns(T::Boolean) }
def no_fix_available?; end
sig { returns(T::Boolean) }
def no_ignore_patches?; end
+3
View File
@@ -31,6 +31,9 @@ module Homebrew::EnvConfig
sig { returns(T::Boolean) }
def ask?; end
sig { returns(T::Boolean) }
def auto_update_quiet?; end
sig { returns(String) }
def auto_update_secs; end
+3
View File
@@ -153,6 +153,9 @@ module RSpec::Matchers
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def be_installed_on_request(*args, &block); end
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def be_installed_on_request_present(*args, &block); end
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def be_latest_version_installed(*args, &block); end
@@ -10,10 +10,10 @@ class RuboCop::Cask::AST::Stanza
def app?; end
sig { returns(T::Boolean) }
def appcast?; end
def app_image?; end
sig { returns(T::Boolean) }
def appimage?; end
def appcast?; end
sig { returns(T::Boolean) }
def arch?; end
@@ -19,12 +19,12 @@ class CSV
def <<(row); end
def add_row(row); end
def binmode(*args, **_arg1, &block); end
def binmode(*_arg0, **_arg1, &_arg2); end
def binmode?; end
def close(*args, **_arg1, &block); end
def close_read(*args, **_arg1, &block); end
def close_write(*args, **_arg1, &block); end
def closed?(*args, **_arg1, &block); end
def close(*_arg0, **_arg1, &_arg2); end
def close_read(*_arg0, **_arg1, &_arg2); end
def close_write(*_arg0, **_arg1, &_arg2); end
def closed?(*_arg0, **_arg1, &_arg2); end
def col_sep; end
def convert(name = T.unsafe(nil), &converter); end
def converters; end
@@ -32,53 +32,53 @@ class CSV
def encoding; end
def eof; end
def eof?; end
def external_encoding(*args, **_arg1, &block); end
def fcntl(*args, **_arg1, &block); end
def external_encoding(*_arg0, **_arg1, &_arg2); end
def fcntl(*_arg0, **_arg1, &_arg2); end
def field_size_limit; end
def fileno(*args, **_arg1, &block); end
def fileno(*_arg0, **_arg1, &_arg2); end
def flock(*args); end
def flush(*args, **_arg1, &block); end
def flush(*_arg0, **_arg1, &_arg2); end
def force_quotes?; end
def fsync(*args, **_arg1, &block); end
def fsync(*_arg0, **_arg1, &_arg2); end
def gets; end
def header_convert(name = T.unsafe(nil), &converter); end
def header_converters; end
def header_row?; end
def headers; end
def inspect; end
def internal_encoding(*args, **_arg1, &block); end
def internal_encoding(*_arg0, **_arg1, &_arg2); end
def ioctl(*args); end
def isatty(*args, **_arg1, &block); end
def isatty(*_arg0, **_arg1, &_arg2); end
def liberal_parsing?; end
def line; end
def lineno; end
def max_field_size; end
def path; end
def pid(*args, **_arg1, &block); end
def pos(*args, **_arg1, &block); end
def pos=(*args, **_arg1, &block); end
def pid(*_arg0, **_arg1, &_arg2); end
def pos(*_arg0, **_arg1, &_arg2); end
def pos=(*_arg0, **_arg1, &_arg2); end
def puts(row); end
def quote_char; end
def read; end
def readline; end
def readlines; end
def reopen(*args, **_arg1, &block); end
def reopen(*_arg0, **_arg1, &_arg2); end
def return_headers?; end
def rewind; end
def row_sep; end
def seek(*args, **_arg1, &block); end
def seek(*_arg0, **_arg1, &_arg2); end
def shift; end
def skip_blanks?; end
def skip_lines; end
def stat(*args); end
def string(*args, **_arg1, &block); end
def sync(*args, **_arg1, &block); end
def sync=(*args, **_arg1, &block); end
def tell(*args, **_arg1, &block); end
def string(*_arg0, **_arg1, &_arg2); end
def sync(*_arg0, **_arg1, &_arg2); end
def sync=(*_arg0, **_arg1, &_arg2); end
def tell(*_arg0, **_arg1, &_arg2); end
def to_i; end
def to_io; end
def truncate(*args, **_arg1, &block); end
def tty?(*args, **_arg1, &block); end
def truncate(*_arg0, **_arg1, &_arg2); end
def tty?(*_arg0, **_arg1, &_arg2); end
def unconverted_fields?; end
def write_headers?; end
@@ -93,7 +93,6 @@ class CSV
def header_fields_converter; end
def normalize_converters(converters); end
def parser; end
def parser_enumerator; end
def parser_fields_converter; end
def parser_options; end
def raw_encoding; end
@@ -103,22 +102,22 @@ class CSV
class << self
def filter(input = T.unsafe(nil), output = T.unsafe(nil), **options); end
def foreach(path, mode = T.unsafe(nil), **options, &block); end
def foreach(path_or_io, mode = T.unsafe(nil), **options, &block); end
def generate(str = T.unsafe(nil), **options); end
def generate_line(row, **options); end
def generate_lines(rows, **options); end
def instance(data = T.unsafe(nil), **options); end
def open(filename_or_io, mode = T.unsafe(nil), **options); end
def open(path_or_io, mode = T.unsafe(nil), **options); end
def parse(str, **options, &block); end
def parse_line(line, **options); end
def read(path, **options); end
def readlines(path, **options); end
def table(path, **options); end
def read(path_or_io, **options); end
def readlines(path_or_io, **options); end
def table(path_or_io, **options); end
private
def create_stringio(str, mode, opts); end
def may_enable_bom_detection_automatically(filename_or_io, mode, options, file_opts); end
def may_enable_bom_detection_automatically(path_or_io, mode, options, file_opts); end
end
end
@@ -187,6 +186,7 @@ class CSV::Parser
def initialize(input, options); end
def column_separator; end
def eof?; end
def field_size_limit; end
def header_row?; end
def headers; end
@@ -310,7 +310,7 @@ class CSV::Row
def dig(index_or_header, *indexes); end
def each(&block); end
def each_pair(&block); end
def empty?(*args, **_arg1, &block); end
def empty?(*_arg0, **_arg1, &_arg2); end
def fetch(header, *varargs); end
def field(header_or_index, minimum_index = T.unsafe(nil)); end
def field?(data); end
@@ -324,10 +324,10 @@ class CSV::Row
def index(header, minimum_index = T.unsafe(nil)); end
def inspect; end
def key?(header); end
def length(*args, **_arg1, &block); end
def length(*_arg0, **_arg1, &_arg2); end
def member?(header); end
def push(*args); end
def size(*args, **_arg1, &block); end
def size(*_arg0, **_arg1, &_arg2); end
def to_ary(*_arg0); end
def to_csv(**options); end
def to_h; end
@@ -368,13 +368,13 @@ class CSV::Table
def delete_if(&block); end
def dig(index_or_header, *index_or_headers); end
def each(&block); end
def empty?(*args, **_arg1, &block); end
def empty?(*_arg0, **_arg1, &_arg2); end
def headers; end
def inspect; end
def length(*args, **_arg1, &block); end
def length(*_arg0, **_arg1, &_arg2); end
def mode; end
def push(*rows); end
def size(*args, **_arg1, &block); end
def size(*_arg0, **_arg1, &_arg2); end
def to_a; end
def to_csv(write_headers: T.unsafe(nil), limit: T.unsafe(nil), **options); end
def to_s(write_headers: T.unsafe(nil), limit: T.unsafe(nil), **options); end
@@ -494,6 +494,21 @@ class RBS::AST::Ruby::Annotations::ModuleAliasAnnotation < ::RBS::AST::Ruby::Ann
def type_fingerprint; end
end
class RBS::AST::Ruby::Annotations::ModuleSelfAnnotation < ::RBS::AST::Ruby::Annotations::Base
def initialize(location:, prefix_location:, keyword_location:, colon_location:, name:, args:, open_bracket_location:, close_bracket_location:, args_comma_locations:, comment_location:); end
def args; end
def args_comma_locations; end
def close_bracket_location; end
def colon_location; end
def comment_location; end
def keyword_location; end
def map_type_name; end
def name; end
def open_bracket_location; end
def type_fingerprint; end
end
class RBS::AST::Ruby::Annotations::NodeTypeAssertion < ::RBS::AST::Ruby::Annotations::Base
def initialize(location:, prefix_location:, type:); end
@@ -723,9 +738,11 @@ class RBS::AST::Ruby::Members::Base
end
class RBS::AST::Ruby::Members::DefMember < ::RBS::AST::Ruby::Members::Base
def initialize(buffer, name, node, method_type, leading_comment); end
def initialize(buffer, name, node, method_type, leading_comment, kind: T.unsafe(nil)); end
def annotations; end
def instance?; end
def kind; end
def leading_comment; end
def location; end
def method_type; end
@@ -734,6 +751,7 @@ class RBS::AST::Ruby::Members::DefMember < ::RBS::AST::Ruby::Members::Base
def node; end
def overloading?; end
def overloads; end
def singleton?; end
def type_fingerprint; end
end
@@ -804,6 +822,16 @@ class RBS::AST::Ruby::Members::MixinMember < ::RBS::AST::Ruby::Members::Base
def type_fingerprint; end
end
class RBS::AST::Ruby::Members::ModuleSelfMember < ::RBS::AST::Ruby::Members::Base
def initialize(buffer, annotation); end
def annotation; end
def args; end
def location; end
def name; end
def type_fingerprint; end
end
class RBS::AST::Ruby::Members::PrependMember < ::RBS::AST::Ruby::Members::MixinMember; end
class RBS::AST::TypeParam
@@ -908,6 +936,7 @@ class RBS::Buffer
def initialize(content:, name: T.unsafe(nil), parent: T.unsafe(nil)); end
def absolute_position(position); end
def character_offset(byte_offset); end
def content; end
def detach; end
def inspect; end
@@ -922,7 +951,7 @@ class RBS::Buffer
def pos_to_loc(pos); end
def ranges; end
def rbs_location(location, loc2 = T.unsafe(nil)); end
def sub_buffer(lines:); end
def sub_buffer(lines:, byte_lines_hint: T.unsafe(nil)); end
def top_buffer; end
end
@@ -1069,6 +1098,7 @@ class RBS::Collection::Config::LockfileGenerator::GemfileLockMismatchError < ::S
def message; end
end
RBS::Collection::Config::LockfileGenerator::NONGEM_STDLIBS = T.let(T.unsafe(nil), Set)
RBS::Collection::Config::PATH = T.let(T.unsafe(nil), Pathname)
class RBS::Collection::Installer
@@ -1863,6 +1893,7 @@ class RBS::InlineParser::Parser < ::Prism::Visitor
def skip_node?(node); end
def visit_call_node(node); end
def visit_class_node(node); end
def visit_class_or_module_body(decl, node); end
def visit_constant_path_write_node(node); end
def visit_constant_write_node(node); end
def visit_def_node(node); end
@@ -2056,12 +2087,15 @@ class RBS::Namespace
def to_type_name; end
class << self
def [](path, absolute); end
def empty; end
def parse(string); end
def root; end
end
end
module RBS::Namespace::INTERN_LEAF; end
class RBS::NoMixinFoundError < ::RBS::DefinitionError
include ::RBS::DetailedMessageable
@@ -2128,9 +2162,12 @@ class RBS::Parser
def _parse_inline_leading_annotation(_arg0, _arg1, _arg2, _arg3); end
def _parse_inline_trailing_annotation(_arg0, _arg1, _arg2, _arg3); end
def _parse_method_type(_arg0, _arg1, _arg2, _arg3, _arg4); end
def _parse_method_type_to_bytes(_arg0, _arg1, _arg2, _arg3, _arg4); end
def _parse_signature(_arg0, _arg1, _arg2); end
def _parse_signature_to_bytes(_arg0, _arg1, _arg2); end
def _parse_type(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7); end
def _parse_type_params(_arg0, _arg1, _arg2, _arg3); end
def _parse_type_to_bytes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7); end
def buffer(source); end
def byte_range(char_range, content); end
def lex(source); end
@@ -2247,32 +2284,47 @@ class RBS::Prototype::RBI
def initialize; end
def append_decl(decl); end
def attribute_type(kind, sigs); end
def call_node?(node, name:, receiver: T.unsafe(nil), args: T.unsafe(nil)); end
def const_to_name(node); end
def current_context; end
def current_context!; end
def current_module; end
def current_module!; end
def current_namespace; end
def decls; end
def each_arg(array, &block); end
def each_child(node); end
def join_comments(nodes, comments); end
def last_sig; end
def member_visibility(context); end
def method_type(args_node, type_node, variables:, overloads:); end
def modules; end
def nested_name(name); end
def node_to_hash(node); end
def parse(string); end
def parse_params(args_node, args, method_type, variables:, overloads:); end
def pop_sig; end
def proc_type?(type_node); end
def process(node, comments:, outer: T.unsafe(nil)); end
def process_attribute(node, comments:); end
def process_visibility(node, outer:, comments:); end
def push_class(name, super_class, comment:); end
def push_module(name, comment:); end
def push_sig(node); end
def sync_visibility(visibility); end
def type_of(type_node, variables:); end
def type_of0(type_node, variables:); end
end
class RBS::Prototype::RBI::Context
def initialize(singleton:, visibility:); end
def singleton; end
def singleton=(_arg0); end
def visibility; end
def visibility=(_arg0); end
end
class RBS::Prototype::Runtime
include ::RBS::Prototype::Helpers
include ::RBS::Prototype::Runtime::Helpers
@@ -2509,13 +2561,28 @@ class RBS::Resolver::TypeNameResolver
def resolve_namespace(type_name, context:); end
def resolve_namespace0(type_name, context, visited); end
def resolve_type_name(type_name, context); end
def try_cache(query); end
def try_cache(type_name, context); end
class << self
def build(env); end
end
end
class RBS::Rewriter
def initialize(buffer); end
def add_comment(*locations, content:); end
def buffer; end
def delete_comment(comment); end
def replace_comment(comment, content:); end
def rewrite(location, string); end
def string; end
private
def format_comment(content, indent); end
end
module RBS::Source; end
class RBS::Source::RBS
@@ -2652,6 +2719,7 @@ class RBS::TypeName
def with_prefix(namespace); end
class << self
def [](namespace, name); end
def parse(string); end
end
end
@@ -299,6 +299,7 @@ class RuboCop::Config
def disabled_new_cops?; end
def each(*_arg0, **_arg1, &_arg2); end
def each_key(*_arg0, **_arg1, &_arg2); end
def enabled_new_cop?(qualified_cop_name); end
def enabled_new_cops?; end
def fetch(*_arg0, **_arg1, &_arg2); end
def file_to_exclude?(file); end
@@ -340,10 +341,14 @@ class RuboCop::Config
private
def comparable_version(value); end
def department_of(qualified_cop_name); end
def enable_cop?(qualified_cop_name, cop_options); end
def gem_version_to_major_minor_float(gem_version); end
def match_relative_or_absolute_path?(pattern, relative_file_path, absolute_file_path); end
def new_cops_covered?(qualified_cop_name); end
def new_cops_setting_for(qualified_cop_name); end
def new_cops_version_covers?(new_cops_version, qualified_cop_name); end
def read_gem_versions_from_target_lockfile; end
def read_rails_version_from_bundler_lock_file; end
def target_rails_version_from_bundler_lock_file; end
@@ -691,11 +696,15 @@ class RuboCop::ConfigValidator
def check_target_ruby; end
def each_invalid_parameter(cop_name); end
def list_unknown_cops(invalid_cop_names); end
def new_cops_value_for_department(name, section); end
def new_cops_version_value?(value); end
def param_error_message(parent, key, value, supposed_values); end
def reject_conflicting_safe_settings; end
def reject_mutually_exclusive_defaults; end
def suggestion(name); end
def target_ruby; end
def validate_all_cops_new_cops_parameter; end
def validate_department_new_cops_parameters; end
def validate_enforced_styles(valid_cop_names); end
def validate_new_cops_parameter; end
def validate_parameter_names(valid_cop_names); end
@@ -716,6 +725,8 @@ RuboCop::ConfigValidator::INTERNAL_PARAMS = T.let(T.unsafe(nil), Array)
RuboCop::ConfigValidator::NEW_COPS_VALUES = T.let(T.unsafe(nil), Array)
RuboCop::ConfigValidator::NEW_COPS_VERSION_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Alignment::SPACE = T.let(T.unsafe(nil), String)
RuboCop::Cop::AllowedIdentifiers::SIGILS = T.let(T.unsafe(nil), String)
@@ -1058,6 +1069,12 @@ RuboCop::Cop::Generator::CONFIGURATION_ADDED_MESSAGE = T.let(T.unsafe(nil), Stri
RuboCop::Cop::Generator::ConfigurationInjector::TEMPLATE = T.let(T.unsafe(nil), String)
RuboCop::Cop::Generator::RegistrationInjector::DEPARTMENT_REQUIRE_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Generator::RegistrationInjector::ENTRY_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Generator::RegistrationInjector::EXTEND_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Generator::RequireFileInjector::REQUIRE_PATH = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Generator::SOURCE_TEMPLATE = T.let(T.unsafe(nil), String)
@@ -1420,10 +1437,12 @@ RuboCop::Cop::Lint::AmbiguousAssignment::MISTAKES = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::Lint::AmbiguousAssignment::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::AmbiguousAssignment::SIMPLE_ASSIGNMENT_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::AmbiguousBlockAssociation::BLOCK_METHODS = T.let(T.unsafe(nil), Set)
RuboCop::Cop::Lint::AmbiguousBlockAssociation::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::AmbiguousBlockAssociation::MSG_DO_END_BLOCK = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::AmbiguousOperator::AMBIGUITIES = T.let(T.unsafe(nil), Hash)
RuboCop::Cop::Lint::AmbiguousOperator::MSG_FORMAT = T.let(T.unsafe(nil), String)
@@ -1514,6 +1533,14 @@ RuboCop::Cop::Lint::DeprecatedOpenSSLConstant::NO_ARG_ALGORITHM = T.let(T.unsafe
RuboCop::Cop::Lint::DeprecatedOpenSSLConstant::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::DeprecatedReference::CONSTANT_MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DeprecatedReference::DEPRECATED_TAG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DeprecatedReference::DEPRECATION_DETAIL = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Lint::DeprecatedReference::METHOD_MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DisjunctiveAssignmentInConstructor::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DuplicateBranch::MSG = T.let(T.unsafe(nil), String)
@@ -1528,9 +1555,9 @@ RuboCop::Cop::Lint::DuplicateMagicComment::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DuplicateMatchPattern::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DuplicateMethods::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DuplicateMethods::INDEXABLE_METHOD_NAME = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Lint::DuplicateMethods::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::DuplicateMethods::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::DuplicateRegexpCharacterClassElement::MSG_REPEATED_ELEMENT = T.let(T.unsafe(nil), String)
@@ -1642,6 +1669,8 @@ RuboCop::Cop::Lint::IneffectiveAccessModifier::ALTERNATIVE_PROTECTED = T.let(T.u
RuboCop::Cop::Lint::IneffectiveAccessModifier::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::InheritException::INDIRECT_MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::InheritException::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::InheritException::PREFERRED_BASE_CLASS = T.let(T.unsafe(nil), Hash)
@@ -1700,6 +1729,14 @@ RuboCop::Cop::Lint::MultipleComparison::RESTRICT_ON_SEND = T.let(T.unsafe(nil),
RuboCop::Cop::Lint::MultipleComparison::SET_OPERATION_OPERATORS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::NameTypo::CONSTANT_MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NameTypo::LITERAL_IDENTIFIER_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Lint::NameTypo::METHOD_MEMBER_REGEXP = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Lint::NameTypo::METHOD_MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NestedMethodDefinition::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::NestedPercentLiteral::MSG = T.let(T.unsafe(nil), String)
@@ -1980,6 +2017,12 @@ RuboCop::Cop::Lint::UnreachablePatternBranch::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::UnreachablePatternBranch::MSG_ELSE = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::UnusedPrivateMethod::IDENTIFIER_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Lint::UnusedPrivateMethod::IMPLICITLY_INVOKED_METHODS = T.let(T.unsafe(nil), Set)
RuboCop::Cop::Lint::UnusedPrivateMethod::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Lint::UriEscapeUnescape::ALTERNATE_METHODS_OF_URI_ESCAPE = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Lint::UriEscapeUnescape::ALTERNATE_METHODS_OF_URI_UNESCAPE = T.let(T.unsafe(nil), Array)
@@ -2308,16 +2351,18 @@ class RuboCop::Cop::Registry
def department_missing?(badge, name); end
def departments; end
def disabled(config); end
def disabled_names(config); end
def dismiss(cop); end
def each(&block); end
def enabled(config); end
def enabled?(cop, config); end
def enabled_pending_cop?(cop_cfg, config); end
def enabled_pending_cop?(cop_cfg, config, cop = T.unsafe(nil)); end
def enlist(cop); end
def filter_by_badge(options = T.unsafe(nil)); end
def find_by_cop_name(cop_name); end
def find_cops_by_directive(directive); end
def freeze; end
def lazy_load(cop_name, constant_name); end
def lazy_load(badge, constant_name); end
def length; end
def names; end
def names_for_department(department); end
@@ -2334,16 +2379,21 @@ class RuboCop::Cop::Registry
def with_department(department); end
def without_department(department); end
protected
def add_entry(badge, cop_or_name); end
private
def clear_enrollment_queue; end
def emit_warning(path, message); end
def enabled_cop_name?(cop_name, config); end
def initialize_copy(reg); end
def load_all_lazy_cops; end
def load_lazy_cop(badge); end
def registered?(badge); end
def registered_badges; end
def resolve_badge(given_badge, real_badge, source_path, warn: T.unsafe(nil)); end
def with(cops); end
class << self
def all; end
@@ -2355,6 +2405,8 @@ class RuboCop::Cop::Registry
end
end
RuboCop::Cop::ReparsedEquivalence::MAX_VERIFICATION_FRAGMENT_SIZE = T.let(T.unsafe(nil), Integer)
RuboCop::Cop::RequireLibrary::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Security::CompoundHash::COMBINATOR_IN_HASH_MSG = T.let(T.unsafe(nil), String)
@@ -2647,6 +2699,8 @@ RuboCop::Cop::Style::DocumentDynamicEvalDefinition::MSG = T.let(T.unsafe(nil), S
RuboCop::Cop::Style::DocumentDynamicEvalDefinition::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::Documentation::DIRECTIVE_COMMENT_REGEXP = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Style::Documentation::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::DocumentationMethod::MSG = T.let(T.unsafe(nil), String)
@@ -2865,6 +2919,8 @@ RuboCop::Cop::Style::IfInsideElse::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::IfUnlessModifier::MSG_USE_MODIFIER = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::IfUnlessModifier::MSG_USE_MODIFIER_PARENS = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::IfUnlessModifier::MSG_USE_NORMAL = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::IfUnlessModifierOfIfUnless::MSG = T.let(T.unsafe(nil), String)
@@ -3317,21 +3373,17 @@ RuboCop::Cop::Style::RedundantInterpolation::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::RedundantInterpolationUnfreeze::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::RedundantLineContinuation::ALLOWED_STRING_TOKENS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::ARGUMENT_TAKING_FLOW_TOKEN_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::ARGUMENT_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::ARITHMETIC_OPERATOR_TOKENS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::LINE_CONTINUATION = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::RedundantLineContinuation::LINE_CONTINUATION_PATTERN = T.let(T.unsafe(nil), Regexp)
RuboCop::Cop::Style::RedundantLineContinuation::MSG = T.let(T.unsafe(nil), String)
RuboCop::Cop::Style::RedundantLineContinuation::STRING_LITERAL_BEGIN_TOKENS = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::STRING_LITERAL_BEGINNING_TOKEN_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::STRING_LITERAL_ENDING_TOKEN_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantLineContinuation::STRING_TOKEN_TYPES = T.let(T.unsafe(nil), Array)
RuboCop::Cop::Style::RedundantMinMaxBy::MSG_BLOCK = T.let(T.unsafe(nil), String)
@@ -3964,6 +4016,8 @@ RuboCop::DirectiveComment::MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP = T.let(T
RuboCop::DirectiveComment::PUSH_POP_ARGS_PATTERN = T.let(T.unsafe(nil), String)
RuboCop::DirectiveComment::STYLE_DISABLE_COPS_DIRECTIVE_COP = T.let(T.unsafe(nil), String)
RuboCop::DirectiveComment::TRAILING_COMMENT_MARKER = T.let(T.unsafe(nil), String)
class RuboCop::Error < ::StandardError; end
@@ -4288,6 +4342,8 @@ class RuboCop::Runner
private
def add_redundant_disables(file, offenses, source); end
def build_project_index(target_files); end
def bundled_gem_files; end
def cached_result(file, team); end
def cached_run?; end
def check_for_infinite_loop(processed_source, offenses_by_iteration); end
@@ -4301,7 +4357,6 @@ class RuboCop::Runner
def file_iterator(files, &block); end
def file_offenses(file); end
def file_started(file); end
def filter_cop_classes(cop_classes, config); end
def find_target_files(paths); end
def finished_report(file, index, offenses); end
def formatter_set; end
@@ -4313,6 +4368,7 @@ class RuboCop::Runner
def list_files(paths); end
def mark_as_safe_by_config?(config); end
def minimum_severity_to_fail; end
def mobilize_cop_badge?(badge, config); end
def mobilize_team(processed_source); end
def mobilized_cop_classes(config); end
def offense_displayed?(offense); end
@@ -4323,6 +4379,7 @@ class RuboCop::Runner
def process_report_queue_entry(index); end
def project_index_disables_parallel?; end
def project_index_enabled?; end
def project_index_files(target_files); end
def qualify_option_cop_names; end
def redundant_cop_disable_directive(file); end
def run_in_parallel?(files); end

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