Merge branch 'main' into py3-whl

This commit is contained in:
William Woodruff 2025-07-16 11:32:22 -04:00 committed by GitHub
commit 78f3fdeed9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
603 changed files with 24201 additions and 75417 deletions

View File

@ -1,7 +1,7 @@
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Homebrew/brew",
"image": "ghcr.io/homebrew/brew:master",
"image": "ghcr.io/homebrew/brew:main",
"workspaceFolder": "/home/linuxbrew/.linuxbrew/Homebrew",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/linuxbrew/.linuxbrew/Homebrew,type=bind,consistency=cached",
"onCreateCommand": ".devcontainer/on-create-command.sh",

View File

@ -4,7 +4,7 @@ type: "Bug"
body:
- type: markdown
attributes:
value: Please note we will close your issue without comment if you do not correctly fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again.
value: Please note we may close your issue without comment if you do not fill out the issue checklist below and provide ALL the requested information (even if you consider them irrelevant). If you are unwilling to use the issue template, we may block you from ever submitting future issues to Homebrew.
- type: textarea
attributes:
render: shell
@ -16,10 +16,10 @@ body:
label: Verification
description: Please verify that you've followed these steps. If you cannot truthfully check these boxes, open a discussion at https://github.com/orgs/Homebrew/discussions instead.
options:
- label: My "`brew doctor` output" above says `Your system is ready to brew.` and am still able to reproduce my issue.
required: true
- label: I ran `brew update` twice and am still able to reproduce my issue.
required: true
- label: My "`brew doctor` output" above says `Your system is ready to brew` or a definitely unrelated `Tier` message.
required: true
- label: This issue's title and/or description do not reference a single formula e.g. `brew install wget`. If they do, open an issue at https://github.com/Homebrew/homebrew-core/issues/new/choose instead.
required: true
- type: textarea

View File

@ -0,0 +1,7 @@
# This file is synced from the `.github` repository, do not modify it directly.
extensions:
- addsTo:
pack: codeql/actions-all
extensible: trustedActionsOwnerDataModel
data:
- ["Homebrew"]

113
.github/dependabot.yml vendored
View File

@ -1,72 +1,71 @@
# This file is synced from the `.github` repository, do not modify it directly.
---
version: 2
updates:
- package-ecosystem: github-actions
directory: /
multi-ecosystem-groups:
all:
schedule:
interval: weekly
allow:
- dependency-type: all
# The actions in triage-issues.yml are updated in the Homebrew/.github repo
ignore:
- dependency-name: actions/stale
groups:
artifacts:
day: friday
time: '08:00'
timezone: Etc/UTC
updates:
- package-ecosystem: github-actions
directory: "/"
multi-ecosystem-group: all
patterns:
- actions/*-artifact
open-pull-requests-limit: 10
- package-ecosystem: bundler
directory: /Library/Homebrew
schedule:
interval: daily
- "*"
allow:
- dependency-type: all
groups:
rspec:
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: bundler
directories:
- "/Library/Homebrew"
multi-ecosystem-group: all
patterns:
- "rspec*"
sorbet:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"
- package-ecosystem: docker
directory: "/"
multi-ecosystem-group: all
patterns:
- "sorbet*"
open-pull-requests-limit: 10
- package-ecosystem: npm
directory: /
schedule:
interval: daily
- "*"
allow:
- dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: docker
directory: /
schedule:
interval: daily
- package-ecosystem: devcontainers
directory: "/"
multi-ecosystem-group: all
patterns:
- "*"
allow:
- dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: devcontainers
directory: /
schedule:
interval: daily
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: pip
directories:
- "/Library/Homebrew/formula-analytics/"
multi-ecosystem-group: all
patterns:
- "*"
allow:
- dependency-type: all
open-pull-requests-limit: 10
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"
- package-ecosystem: pip
directory: /
schedule:
interval: daily
allow:
- dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: pip
directory: /Library/Homebrew/formula-analytics/
schedule:
interval: daily
allow:
- dependency-type: all
open-pull-requests-limit: 10

View File

@ -1,18 +1,12 @@
name: actionlint
# This file is synced from the `.github` repository, do not modify it directly.
name: Actionlint
on:
push:
branches:
- main
- master
pull_request:
paths:
- '.github/workflows/*.ya?ml'
- '.github/actionlint.yaml'
env:
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_ENV_HINTS: 1
defaults:
run:
@ -22,16 +16,25 @@ concurrency:
group: "actionlint-${{ github.ref }}"
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_ENV_HINTS: 1
permissions: {}
jobs:
workflow_syntax:
if: github.repository_owner == 'Homebrew'
runs-on: ubuntu-latest
permissions:
contents: read
container:
image: ghcr.io/homebrew/ubuntu22.04:main
steps:
- name: Set up Homebrew
id: setup-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -40,31 +43,44 @@ jobs:
- name: Install tools
run: brew install actionlint shellcheck zizmor
- name: Set up GITHUB_WORKSPACE
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- run: zizmor --format sarif . > results.sarif
env:
HOMEBREW_REPOSITORY: ${{ steps.setup-homebrew.outputs.repository-path }}
run: |
# Annotations work only relative to GITHUB_WORKSPACE
(shopt -s dotglob; rm -rf "${GITHUB_WORKSPACE:?}"/*; mv "${HOMEBREW_REPOSITORY:?}"/* "$GITHUB_WORKSPACE")
rmdir "$HOMEBREW_REPOSITORY"
ln -vs "$GITHUB_WORKSPACE" "$HOMEBREW_REPOSITORY"
echo "::add-matcher::.github/actionlint-matcher.json"
- run: |
# NOTE: exit code intentionally suppressed here
zizmor --format sarif . > results.sarif || true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
# We can't use the SARIF file when triggered by `merge_group` so we don't upload it.
if: always() && github.event_name != 'merge_group'
with:
name: results.sarif
path: results.sarif
- name: Set up actionlint
run: |
# In homebrew-core, setting `shell: /bin/bash` prevents shellcheck from running on
# those steps, so let's change them to `shell: bash` temporarily for better linting.
sed -i 's|shell: /bin/bash -x|shell: bash -x|' .github/workflows/*.y*ml
# In homebrew-core, the JSON matcher needs to be accessible to the container host.
cp "$(brew --repository)/.github/actionlint-matcher.json" "$HOME"
echo "::add-matcher::$HOME/actionlint-matcher.json"
- run: actionlint
upload_sarif:
needs: workflow_syntax
# We want to always upload this even if `actionlint` failed.
# This is only available on public repositories.
if: >
always() &&
!contains(fromJSON('["cancelled", "skipped"]'), needs.workflow_syntax.result) &&
!github.event.repository.private &&
github.event_name != 'merge_group'
runs-on: ubuntu-latest
permissions:
contents: read
@ -77,7 +93,7 @@ jobs:
path: results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif
category: zizmor

View File

@ -27,7 +27,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false

View File

@ -3,10 +3,9 @@ name: "CodeQL"
on:
push:
branches:
- main
- master
pull_request:
branches:
- master
defaults:
run:
@ -28,7 +27,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
languages: ruby
config: |
@ -36,4 +35,4 @@ jobs:
- Library/Homebrew/vendor
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2

View File

@ -4,6 +4,7 @@ on:
pull_request:
push:
branches:
- main
- master
merge_group:
release:
@ -38,8 +39,8 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Fetch origin/master from Git
run: git fetch origin master
- name: Fetch origin/HEAD from Git
run: git fetch origin HEAD
- name: Determine build attributes
id: attributes
@ -83,12 +84,16 @@ jobs:
)
fi
elif [[ "${GITHUB_EVENT_NAME}" == "push" &&
"${GITHUB_REF}" == "refs/heads/master" &&
("${GITHUB_REF}" == "refs/heads/master" || "${GITHUB_REF}" == "refs/heads/main") &&
"${version}" == "22.04" ]]; then
tags+=(
"ghcr.io/homebrew/brew:main"
"ghcr.io/homebrew/brew:master"
"ghcr.io/homebrew/ubuntu${version}:main"
"ghcr.io/homebrew/ubuntu${version}:master"
"homebrew/brew:main"
"homebrew/brew:master"
"homebrew/ubuntu${version}:main"
"homebrew/ubuntu${version}:master"
)
fi
@ -160,11 +165,11 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Fetch origin/master from Git
run: git fetch origin master
- name: Fetch origin/HEAD from Git
run: git fetch origin HEAD
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with:
cache-binary: false
@ -190,7 +195,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
load: true
@ -227,7 +232,7 @@ jobs:
- name: Deploy the Docker image by digest
id: digest
if: fromJSON(steps.attributes.outputs.push)
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
cache-from: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache
@ -263,7 +268,7 @@ jobs:
version: ${{ fromJSON(needs.generate-tags.outputs.matrix) }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with:
cache-binary: false

View File

@ -24,7 +24,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -52,7 +52,7 @@ jobs:
run: vale docs/
- name: Install Ruby
uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0
uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
with:
bundler-cache: true
working-directory: docs
@ -67,7 +67,13 @@ jobs:
- name: Generate formulae.brew.sh API samples
if: github.repository == 'Homebrew/formulae.brew.sh'
working-directory: docs
run: ../script/generate-api-samples.rb
run: ../script/generate-api-samples.rb --template
- name: Cache HTML Proofer
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: tmp/.htmlproofer
key: ${{ runner.os }}-htmlproofer
- name: Build the site and check for broken links
working-directory: docs

View File

@ -28,7 +28,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -55,7 +55,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false

View File

@ -43,7 +43,7 @@ jobs:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -135,7 +135,7 @@ jobs:
fi
- name: Generate build provenance
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg
@ -230,17 +230,15 @@ jobs:
--password "${PKG_APPLE_ID_APP_SPECIFIC_PASSWORD}"
--wait
- name: Install gh
run: brew install gh
- name: Upload installer to GitHub release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INSTALLER_PATH: ${{ needs.build.outputs.installer_path }}
run: gh release upload --repo Homebrew/brew
"${GITHUB_REF//refs\/tags\//}"
"${INSTALLER_PATH}"
run: |
VERSION="${INSTALLER_PATH#Homebrew-}"
VERSION="${VERSION%.pkg}"
gh release upload --repo Homebrew/brew "${VERSION}" "${INSTALLER_PATH}"
issue:
needs: [build, test, upload]
@ -253,7 +251,7 @@ jobs:
issues: write
steps:
- name: Open, update, or close pkg installer issue
uses: Homebrew/actions/create-or-update-issue@master
uses: Homebrew/actions/create-or-update-issue@main
with:
title: Failed to publish pkg installer
body: >
@ -261,7 +259,7 @@ jobs:
${{ github.ref_name }}. No pkg installer was uploaded to the GitHub
release.
labels: bug,release blocker
update-existing: ${{ contains(needs.*.result, 'failure') }}
update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
close-existing: ${{ needs.upload.result == 'success' }}
close-from-author: github-actions[bot]
close-comment: >

View File

@ -3,6 +3,7 @@ name: Ruby Documentation CI
on:
push:
branches:
- main
- master
pull_request:
@ -28,7 +29,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -42,7 +43,7 @@ jobs:
persist-credentials: false
- name: Install Ruby
uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0
uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
with:
bundler-cache: true
working-directory: rubydoc

View File

@ -1,9 +1,10 @@
name: Update schema data
name: Update SBOM schema
on:
push:
paths:
- .github/workflows/schemas.yml
- .github/workflows/sbom.yml
branches-ignore:
- main
- master
schedule:
- cron: "0 0 * * *"
@ -17,25 +18,25 @@ defaults:
shell: bash -xeuo pipefail {0}
jobs:
spdx:
sbom:
if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
test-bot: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
- name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master
uses: Homebrew/actions/setup-commit-signing@main
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -55,7 +56,7 @@ jobs:
git checkout "${BRANCH}"
git checkout "Library/Homebrew/data/schemas"
else
git checkout --no-track -B "${BRANCH}" origin/master
git checkout --no-track -B "${BRANCH}" origin/HEAD
fi
# Intentionally tracking 2.3.x to match what we output in sbom.rb. 3.0 also doesn't have a JSON Schema.
@ -67,9 +68,10 @@ jobs:
if ! git diff --exit-code Library/Homebrew/data/schemas
then
git add "Library/Homebrew/data/schemas"
git commit -m "data/schemas: update schema data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/master/.github/workflows/schemas.yml)."
git commit -m "data/schemas: update schema data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/schemas.yml)."
echo "committed=true" >> "$GITHUB_OUTPUT"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)"
if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then
echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -78,13 +80,13 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }}
force: true
origin_branch: "master"
origin_branch: "HEAD"
- name: Open a pull request
if: steps.update.outputs.pull_request == 'true'
@ -92,3 +94,26 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
issue:
needs: sbom
if: always() && github.event_name == 'schedule'
runs-on: ubuntu-latest
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
permissions:
# To create or update issues
issues: write
steps:
- name: Open, update, or close schema issue
uses: Homebrew/actions/create-or-update-issue@main
with:
title: Failed to update SBOM schema
body: >
The SBOM schema workflow [failed](${{ env.RUN_URL }}). No SBOM schema was updated.
labels: bug
update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
close-existing: ${{ needs.sbom.result == 'success' }}
close-from-author: github-actions[bot]
close-comment: >
The SBOM schema workflow [succeeded](${{ env.RUN_URL }}). Closing this issue.

View File

@ -10,6 +10,7 @@ on:
paths:
- .github/workflows/sorbet.yml
branches-ignore:
- main
- master
schedule:
- cron: "0 0 * * *"
@ -29,7 +30,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -37,13 +38,13 @@ jobs:
- name: Configure Git user
if: github.event_name != 'pull_request'
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
- name: Set up commit signing
if: github.event_name != 'pull_request'
uses: Homebrew/actions/setup-commit-signing@master
uses: Homebrew/actions/setup-commit-signing@main
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -63,7 +64,7 @@ jobs:
git checkout "${BRANCH}"
git checkout "Library/Homebrew/sorbet"
else
git checkout --no-track -B "${BRANCH}" origin/master
git checkout --no-track -B "${BRANCH}" origin/HEAD
fi
fi
@ -80,17 +81,17 @@ jobs:
then
git add "Library/Homebrew/sorbet"
git commit -m "sorbet: Update RBI files." \
-m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/master/.github/workflows/sorbet.yml) workflow."
-m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sorbet.yml) workflow."
if ! git diff --stat --exit-code "Library/Homebrew"
then
git add "Library/Homebrew/"
git commit -m "sorbet: Autobump sigils via Spoom" \
-m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/master/.github/workflows/sorbet.yml) workflow."
-m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sorbet.yml) workflow."
fi
echo "committed=true" >> "$GITHUB_OUTPUT"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)"
if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then
echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -99,13 +100,13 @@ jobs:
- name: Push commits
if: steps.commit.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }}
force: true
origin_branch: "master"
origin_branch: "HEAD"
- name: Open a pull request
if: steps.commit.outputs.pull_request == 'true'
@ -113,3 +114,26 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
issue:
needs: tapioca
if: always() && github.event_name == 'schedule'
runs-on: ubuntu-latest
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
permissions:
# To create or update issues
issues: write
steps:
- name: Open, update, or close Sorbet issue
uses: Homebrew/actions/create-or-update-issue@main
with:
title: Failed to update RBI files
body: >
The Sorbet workflow [failed](${{ env.RUN_URL }}). No RBI files were updated.
labels: bug
update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
close-existing: ${{ needs.tapioca.result == 'success' }}
close-from-author: github-actions[bot]
close-comment: >
The Sorbet workflow [succeeded](${{ env.RUN_URL }}). Closing this issue.

View File

@ -4,6 +4,7 @@ on:
paths:
- .github/workflows/spdx.yml
branches-ignore:
- main
- master
schedule:
- cron: "0 0 * * *"
@ -23,19 +24,19 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
test-bot: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
- name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master
uses: Homebrew/actions/setup-commit-signing@main
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -55,15 +56,16 @@ jobs:
git checkout "${BRANCH}"
git checkout "Library/Homebrew/data/spdx"
else
git checkout --no-track -B "${BRANCH}" origin/master
git checkout --no-track -B "${BRANCH}" origin/HEAD
fi
if brew update-license-data
then
git add "Library/Homebrew/data/spdx"
git commit -m "spdx: update license data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/master/.github/workflows/spdx.yml)."
git commit -m "spdx: update license data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/spdx.yml)."
echo "committed=true" >> "$GITHUB_OUTPUT"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)"
if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then
echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -72,13 +74,13 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }}
force: true
origin_branch: "master"
origin_branch: "HEAD"
- name: Open a pull request
if: steps.update.outputs.pull_request == 'true'
@ -86,3 +88,26 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
issue:
needs: spdx
if: always() && github.event_name == 'schedule'
runs-on: ubuntu-latest
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
permissions:
# To create or update issues
issues: write
steps:
- name: Open, update, or close SPDX issue
uses: Homebrew/actions/create-or-update-issue@main
with:
title: Failed to update SPDX license data
body: >
The SPDX license data workflow [failed](${{ env.RUN_URL }}). No SPDX license data was updated.
labels: bug
update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
close-existing: ${{ needs.spdx.result == 'success' }}
close-from-author: github-actions[bot]
close-comment: >
The SPDX license data workflow [succeeded](${{ env.RUN_URL }}). Closing this issue.

View File

@ -3,6 +3,7 @@ name: Update sponsors, maintainers, manpage and completions
on:
push:
branches:
- main
- master
paths:
- .github/workflows/sponsors-maintainers-man-completions.yml
@ -32,19 +33,19 @@ jobs:
steps:
- name: Setup Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
test-bot: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
- name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master
uses: Homebrew/actions/setup-commit-signing@main
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -60,7 +61,7 @@ jobs:
run: |
git fetch origin
if [[ -n "$GITHUB_REF_NAME" && "$GITHUB_REF_NAME" != "master" ]]
if [[ -n "$GITHUB_REF_NAME" && "$GITHUB_REF_NAME" != "master" && "$GITHUB_REF_NAME" != "main" ]]
then
BRANCH="$GITHUB_REF_NAME"
else
@ -76,7 +77,7 @@ jobs:
"manpages/brew.1" \
"completions"
else
git checkout --force --no-track -B "${BRANCH}" origin/master
git checkout --force --no-track -B "${BRANCH}" origin/HEAD
fi
if brew update-sponsors
@ -111,7 +112,7 @@ jobs:
if [[ -n "${COMMITTED-}" ]]
then
echo "committed=true" >> "$GITHUB_OUTPUT"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")"
PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)"
if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then
echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -124,7 +125,7 @@ jobs:
- name: Push commits
if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@ -137,3 +138,26 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
issue:
needs: updates
if: always() && github.event_name == 'schedule'
runs-on: ubuntu-latest
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
permissions:
# To create or update issues
issues: write
steps:
- name: Open, update, or close sponsors, maintainers, manpage and completions issue
uses: Homebrew/actions/create-or-update-issue@main
with:
title: Failed to update sponsors, maintainers, manpage and completions
body: >
The sponsors, maintainers, manpage and completions workflow [failed](${{ env.RUN_URL }}). No sponsors, maintainers, manpage and completions were updated.
labels: bug
update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
close-existing: ${{ needs.updates.result == 'success' }}
close-from-author: github-actions[bot]
close-comment: >
The sponsors, maintainers, manpage and completions workflow [succeeded](${{ env.RUN_URL }}). Closing this issue.

View File

@ -38,7 +38,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale Issues and Pull Requests
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
@ -68,7 +68,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 2

View File

@ -0,0 +1,64 @@
name: Sync default branches
on:
push:
branches:
- main
- master
pull_request:
paths:
- .github/workflows/sync-default-branches.yml
permissions: {}
defaults:
run:
shell: bash -xeuo pipefail {0}
concurrency:
group: "sync-default-branches-${{ github.ref }}"
cancel-in-progress: true
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Configure Git user
uses: Homebrew/actions/git-user-config@main
with:
username: github-actions[bot]
- name: Determine source and target branches
id: branches
run: |
if [[ "${GITHUB_REF_NAME}" == "main" ]]; then
target="master"
source="main"
else
target="main"
source="master"
fi
echo "target=${target}" >> "$GITHUB_OUTPUT"
echo "source=${source}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 1
persist-credentials: true
- name: Get target SHA
id: sha
run: |
TARGET_SHA=$(git ls-remote origin "refs/heads/${SOURCE_BRANCH}" | cut -f1)
echo "target=${TARGET_SHA}" >> "$GITHUB_OUTPUT"
env:
SOURCE_BRANCH: ${{ steps.branches.outputs.source }}
- name: Push target branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
run: git push origin "${TARGET_SHA}:refs/heads/${TARGET_BRANCH}" --force
env:
TARGET_SHA: ${{ steps.sha.outputs.target }}
TARGET_BRANCH: ${{ steps.branches.outputs.target }}

View File

@ -3,6 +3,7 @@ name: CI
on:
push:
branches:
- main
- master
pull_request:
merge_group:
@ -32,7 +33,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -80,11 +81,11 @@ jobs:
name: tap syntax
needs: syntax
if: github.repository_owner == 'Homebrew'
runs-on: macos-14
runs-on: macos-15
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: true
cask: true
@ -135,13 +136,13 @@ jobs:
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
runs-on: ubuntu-latest
container:
image: ghcr.io/homebrew/brew:master
image: ghcr.io/homebrew/brew:main
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: true
core: false
cask: false
test-bot: false
@ -162,7 +163,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: true
cask: true
@ -185,14 +186,14 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
test-bot: false
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
@ -213,14 +214,14 @@ jobs:
strategy:
matrix:
include:
- name: update-test (Ubuntu)
- name: update-test (Linux)
runs-on: ubuntu-latest
- name: update-test (macOS)
runs-on: macos-15
runs-on: macos-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -237,7 +238,6 @@ jobs:
name: ${{ matrix.name }}
needs: syntax
runs-on: ${{ matrix.runs-on }}
container: ${{ matrix.container }}
strategy:
matrix:
include:
@ -247,23 +247,16 @@ jobs:
- name: tests (generic OS)
test-flags: --generic --coverage
runs-on: ubuntu-latest
- name: tests (Ubuntu 24.04)
- name: tests (Linux)
test-flags: --coverage
runs-on: ubuntu-24.04
- name: tests (Ubuntu 22.04)
test-flags: --coverage
runs-on: ubuntu-22.04
- name: tests (Ubuntu 20.04)
test-flags: --coverage
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest
- name: tests (macOS 15 arm64)
- name: tests (macOS)
test-flags: --coverage
runs-on: macos-15
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
# We only test needs_homebrew_core tests on macOS because
# homebrew/core is not available by default on GitHub-hosted Ubuntu
@ -335,7 +328,7 @@ jobs:
filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',')
echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT"
- uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0
- uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: ${{ steps.junit_xml.outputs.filenames }}
@ -349,7 +342,7 @@ jobs:
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
test-default-formula:
test-bot:
name: ${{ matrix.name }}
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -358,36 +351,76 @@ jobs:
strategy:
matrix:
include:
- name: test default formula (Ubuntu 24.04)
runs-on: ubuntu-latest
- name: test-bot (Linux arm64)
runs-on: ubuntu-24.04-arm
container: ghcr.io/homebrew/ubuntu24.04:latest
- name: test default formula (Ubuntu 22.04)
- name: test-bot (Linux x86_64)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu22.04:master
- name: test default formula (Ubuntu 20.04)
container: ghcr.io/homebrew/ubuntu22.04:main
# Use Debian Old Stable for testing Homebrew's glibc support.
- name: test-bot (Linux Homebrew glibc)
runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest
- name: test default formula (macOS 13 x86_64)
container: debian:oldstable
- name: test-bot (macOS x86_64)
runs-on: macos-13
- name: test default formula (macOS 15 arm64)
- name: test-bot (macOS arm64)
runs-on: macos-15
env:
HOMEBREW_TEST_BOT_ANALYTICS: 1
HOMEBREW_ENFORCE_SBOM: 1
steps:
- name: Install Homebrew and Homebrew's dependencies
# All other images are built from our Homebrew Dockerfile.
# This is the only one that needs to be set up manually.
if: matrix.container == 'debian:oldstable'
run: |
# Slimmed down version from the Homebrew Dockerfile
apt-get update
apt-get install -y --no-install-recommends \
bzip2 \
ca-certificates \
curl \
file \
g++ \
git-core \
less \
locales \
make \
netbase \
patch \
procps \
sudo \
uuid-runtime \
tzdata
# Install Homebrew
mkdir -p /home/linuxbrew/.linuxbrew/bin
# Don't do shallow clone or it's unshallowed by "Set up Homebrew"
git clone https://github.com/Homebrew/brew.git /home/linuxbrew/.linuxbrew/Homebrew
cd /home/linuxbrew/.linuxbrew/bin
ln -s ../Homebrew/bin/brew brew
echo "/home/linuxbrew/.linuxbrew/bin" >>"$GITHUB_PATH"
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: true
core: false
cask: false
test-bot: true
- run: brew test-bot --only-cleanup-before
- name: Setup environment variables
if: matrix.container == 'ghcr.io/homebrew/ubuntu20.04:latest'
run: echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV"
run: |
# Set enviroment variables to bypass `brew doctor` failures on Tier >=2 configurations
if [[ "${MATRIX_NAME}" == "test-bot (Linux arm64)" ]]; then
echo "HOMEBREW_ARM64_TESTING=1" >> "$GITHUB_ENV"
elif [[ "${MATRIX_NAME}" == "test-bot (Linux Homebrew glibc)" ]]; then
echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV"
fi
env:
MATRIX_NAME: ${{ matrix.name }}
- run: brew test-bot --only-setup
@ -395,7 +428,7 @@ jobs:
- run: brew test-bot --only-formulae --only-json-tab --test-default-formula
test-brew-bundle-services:
bundle-and-services:
name: ${{ matrix.name }}
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -403,23 +436,21 @@ jobs:
strategy:
matrix:
include:
- name: test brew bundle and brew services (Ubuntu)
- name: bundle and services (Linux)
runs-on: ubuntu-latest
- name: test brew bundle and brew services (macOS)
runs-on: macos-15
- name: bundle and services (macOS)
runs-on: macos-latest
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: true
core: false
cask: false
test-bot: false
- run: brew test-bot --only-cleanup-before
- run: brew test-bot --only-setup
- name: Run brew bundle and brew services integration tests
run: |
cat <<EOS >> Brewfile
@ -449,19 +480,22 @@ jobs:
brew services cleanup
brew bundle cleanup --force
test-analytics:
runs-on: ${{ matrix.os }}
analytics:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
include:
- name: analytics (Linux)
runs-on: ubuntu-latest
- name: analytics (macOS)
runs-on: macos-latest
needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0

View File

@ -9,6 +9,7 @@ on:
paths:
- .github/workflows/vendor-gems.yml
branches-ignore:
- main
- master
workflow_dispatch:
inputs:
@ -31,7 +32,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
@ -39,13 +40,13 @@ jobs:
- name: Configure Git user
if: github.event_name == 'workflow_dispatch'
uses: Homebrew/actions/git-user-config@master
uses: Homebrew/actions/git-user-config@main
with:
username: BrewTestBot
- name: Set up commit signing
if: github.event_name == 'workflow_dispatch'
uses: Homebrew/actions/setup-commit-signing@master
uses: Homebrew/actions/setup-commit-signing@main
with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -100,7 +101,7 @@ jobs:
- name: Push to pull request
if: github.event_name == 'workflow_dispatch'
uses: Homebrew/actions/git-try-push@master
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ steps.app-token.outputs.token }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}

View File

@ -19,7 +19,7 @@ jobs:
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false

1
.github/zizmor.yml vendored
View File

@ -1,3 +1,4 @@
# This file is synced from the `.github` repository, do not modify it directly.
rules:
unpinned-uses:
config:

3
.gitignore vendored
View File

@ -116,6 +116,7 @@
**/vendor/bundle/ruby/*/gems/rdoc-*/
**/vendor/bundle/ruby/*/gems/redcarpet-*/
**/vendor/bundle/ruby/*/gems/regexp_parser-*/
**/vendor/bundle/ruby/*/gems/require-hooks-*/
**/vendor/bundle/ruby/*/gems/rexml-*/
**/vendor/bundle/ruby/*/gems/rspec-*/
**/vendor/bundle/ruby/*/gems/rspec-core-*/
@ -164,6 +165,7 @@
!/completions
!/docs
!/manpages
!/CODEOWNERS
# Unignore our packaging files
!/package
@ -172,6 +174,7 @@
# Ignore generated documentation site
/docs/_site
/docs/.jekyll-metadata
/docs/tmp/.htmlproofer
/docs/vendor
/docs/Gemfile.lock

11
.vscode/mcp.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"servers": {
"Homebrew": {
"type": "stdio",
"command": "brew",
"args": [
"mcp-server"
]
}
}
}

View File

@ -1,5 +1,12 @@
#!/bin/bash
HOMEBREW_PREFIX="$(cd "$(dirname "$0")"/../ && pwd)"
if [[ -n "${BASH_SOURCE[0]}" ]]; then
SCRIPT_PATH="${BASH_SOURCE[0]}"
elif [[ -n "${ZSH_VERSION}" ]]; then
SCRIPT_PATH="${(%):-%x}"
else
SCRIPT_PATH="$0"
fi
HOMEBREW_PREFIX="$(cd "$(dirname "${SCRIPT_PATH}")"/../ && pwd)"
"${HOMEBREW_PREFIX}/bin/brew" install-bundler-gems --add-groups=style,typecheck,vscode >/dev/null 2>&1

View File

@ -40,7 +40,6 @@
"id": "default",
"name": "Brew Typecheck",
"description": "Default configuration",
"cwd": "${workspaceFolder}",
"command": [
"./bin/brew",
"typecheck",

11
CODEOWNERS Normal file
View File

@ -0,0 +1,11 @@
# Note that the naming of this file is incorrect for our case: these people do not "own" the provided files but are
# people with write-access to this repository who wish to approve changes to these files.
#
# Their review is required to merge PRs that change these files.
#
# To be explicit: we will never accept changes to this file adding people from outside the Homebrew GitHub
# organisation. If you are not a Homebrew maintainer: you do not personally "own" or "maintain" any files.
#
# Note: @Homebrew/plc does not have write-access to this repository, and therefore cannot be listed in this file.
docs/Support-Tiers.md @Homebrew/tsc @MikeMcQuaid

View File

@ -26,7 +26,7 @@ AllCops:
Include:
- "**/*.rbi"
Exclude:
- "Homebrew/sorbet/rbi/{dsl,gems}/**/*.rbi"
- "Homebrew/sorbet/rbi/{annotations,dsl,gems}/**/*.rbi"
- "Homebrew/sorbet/rbi/parser*.rbi"
- "Homebrew/bin/*"
- "Homebrew/vendor/**/*"
@ -216,6 +216,10 @@ Naming/MethodParameterName:
merge:
- AllowedNames
# Allows a nicer API for boolean methods with side effects.
Naming/PredicateMethod:
AllowBangMethods: true
# Both styles are used depending on context,
# e.g. `sha256` and `something_countable_1`.
Naming/VariableNumber:
@ -304,7 +308,7 @@ Sorbet/StrictSigil:
- "Homebrew/utils/ruby_check_version_script.rb" # A standalone script.
- "Homebrew/{standalone,startup}/*.rb" # These are loaded before sorbet-runtime
- "Homebrew/test/**/*.rb"
- "Homebrew/bundle/{brew_dumper,checker,commands/exec}.rb" # These aren't typed: true yet.
- "Homebrew/bundle/{formula_dumper,checker,commands/exec}.rb" # These aren't typed: true yet.
Sorbet/TrueSigil:
Enabled: true

View File

@ -10,7 +10,13 @@ Homebrew/MoveToExtendOS:
- "{extend,test,requirements}/**/*"
- "os.rb"
Naming/PredicateName:
# We don't use Sorbet for RSpec tests so let's disable this there.
Sorbet/BlockMethodDefinition:
Exclude:
- test/**/*
# Want to preserve our own API for these methods for now.
Naming/PredicatePrefix:
inherit_mode:
merge:
- AllowedMethods
@ -25,6 +31,7 @@ Style/Documentation:
- Homebrew
Include:
- abstract_command.rb
- autobump_constants.rb
- cask/cask.rb
- cask/dsl.rb
- cask/dsl/version.rb

View File

@ -4,20 +4,20 @@ GEM
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.2)
bindata (2.5.1)
coderay (1.1.3)
concurrent-ruby (1.3.5)
csv (3.3.4)
csv (3.3.5)
diff-lcs (1.6.2)
docile (1.4.1)
elftools (1.3.1)
bindata (~> 2)
erubi (1.13.1)
hana (1.3.7)
json (2.12.0)
json (2.12.2)
json_schemer (2.4.0)
bigdecimal
hana (~> 1.3)
@ -32,7 +32,7 @@ GEM
minitest (5.25.5)
netrc (0.11.0)
parallel (1.27.0)
parallel_tests (5.2.0)
parallel_tests (5.3.0)
parallel
parser (3.3.8.0)
ast (~> 2.4.1)
@ -48,37 +48,38 @@ GEM
pycall (1.5.2)
racc (1.8.1)
rainbow (3.1.1)
rbi (0.3.3)
rbi (0.3.6)
prism (~> 1.0)
rbs (>= 3.4.4)
sorbet-runtime (>= 0.5.9204)
rbs (3.9.4)
rbs (4.0.0.dev.4)
logger
prism (>= 1.3.0)
redcarpet (3.6.1)
regexp_parser (2.10.0)
require-hooks (0.2.2)
rexml (3.4.1)
rspec (3.13.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.3)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.4)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-github (3.0.0)
rspec-core (~> 3.0)
rspec-mocks (3.13.4)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-sorbet (1.9.2)
sorbet-runtime
rspec-support (3.13.3)
rspec-support (3.13.4)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.75.6)
rubocop (1.77.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@ -86,10 +87,10 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-ast (>= 1.45.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1)
rubocop-ast (1.45.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-md (2.0.1)
@ -102,15 +103,17 @@ GEM
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sorbet (0.10.0)
rubocop (>= 1)
ruby-lsp (0.23.21)
rubocop-sorbet (0.10.5)
lint_roller
rubocop (>= 1.75.2)
ruby-lsp (0.24.2)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
rbs (>= 3, < 5)
sorbet-runtime (>= 0.5.10782)
ruby-macho (4.1.0)
ruby-prof (1.7.1)
ruby-prof (1.7.2)
base64
ruby-progressbar (1.13.0)
simplecov (0.22.0)
docile (~> 1.1)
@ -122,38 +125,40 @@ GEM
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
sorbet (0.5.12117)
sorbet-static (= 0.5.12117)
sorbet-runtime (0.5.12117)
sorbet-static (0.5.12117-aarch64-linux)
sorbet-static (0.5.12117-universal-darwin)
sorbet-static (0.5.12117-x86_64-linux)
sorbet-static-and-runtime (0.5.12117)
sorbet (= 0.5.12117)
sorbet-runtime (= 0.5.12117)
spoom (1.6.3)
sorbet (0.5.12222)
sorbet-static (= 0.5.12222)
sorbet-runtime (0.5.12222)
sorbet-static (0.5.12222-aarch64-linux)
sorbet-static (0.5.12222-universal-darwin)
sorbet-static (0.5.12222-x86_64-linux)
sorbet-static-and-runtime (0.5.12222)
sorbet (= 0.5.12222)
sorbet-runtime (= 0.5.12222)
spoom (1.7.4)
erubi (>= 1.10.0)
prism (>= 0.28.0)
rbi (>= 0.3.3)
rbs (>= 4.0.0.dev.4)
rexml (>= 3.2.6)
sorbet-static-and-runtime (>= 0.5.10187)
thor (>= 0.19.2)
stackprof (0.2.27)
tapioca (0.16.11)
tapioca (0.17.6)
benchmark
bundler (>= 2.2.25)
netrc (>= 0.11.0)
parallel (>= 1.21.0)
rbi (~> 0.2)
rbi (>= 0.3.1)
require-hooks (>= 0.2.2)
sorbet-static-and-runtime (>= 0.5.11087)
spoom (>= 1.2.0)
spoom (>= 1.7.0)
thor (>= 1.2.0)
yard-sorbet
thor (1.3.2)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
vernier (1.7.1)
vernier (1.8.0)
warning (1.5.0)
yard (0.9.37)
yard-sorbet (0.9.0)

View File

@ -39,12 +39,12 @@ class PATH
self
end
sig { params(block: T.proc.params(arg0: String).returns(T::Boolean)).returns(T.self_type) }
sig { params(block: T.proc.params(arg0: String).returns(BasicObject)).returns(T.self_type) }
def select(&block)
self.class.new(@paths.select(&block))
end
sig { params(block: T.proc.params(arg0: String).returns(T::Boolean)).returns(T.self_type) }
sig { params(block: T.proc.params(arg0: String).returns(BasicObject)).returns(T.self_type) }
def reject(&block)
self.class.new(@paths.reject(&block))
end

View File

@ -75,14 +75,16 @@ module Homebrew
EOS
else
<<~EOS
#
#: * `#{name}` [args...]
#: `brew #{name}` is an alias for *command*
# This is a Homebrew alias script. It'll be called when the user
# types `brew #{name}`. Any remaining arguments are passed to
# this script. You can retrieve those with $*, or only the first
# one with $1. Please keep your script on one line.
# TODO Replace the line below with your script
echo "Hello I'm brew alias "#{name}" and my args are:" $1
# TODO: Replace the line below with your script
echo "Hello I'm 'brew "#{name}"' and my args are:" $*
EOS
end

View File

@ -29,36 +29,36 @@ module Homebrew
Alias.new(name).remove
end
sig { params(only: T::Array[String], block: T.proc.params(target: String, cmd: String).void).void }
sig { params(only: T::Array[String], block: T.proc.params(name: String, command: String).void).void }
def self.each(only, &block)
Dir["#{HOMEBREW_ALIASES}/*"].each do |path|
next if path.end_with? "~" # skip Emacs-like backup files
next if File.directory?(path)
_shebang, _meta, *lines = File.readlines(path)
target = File.basename(path)
next if !only.empty? && only.exclude?(target)
_shebang, meta, *lines = File.readlines(path)
name = T.must(meta)[/alias: brew (\S+)/, 1] || File.basename(path)
next if !only.empty? && only.exclude?(name)
lines.reject! { |line| line.start_with?("#") || line =~ /^\s*$/ }
first_line = T.must(lines.first)
cmd = first_line.chomp
cmd.sub!(/ \$\*$/, "")
command = first_line.chomp
command.sub!(/ \$\*$/, "")
if cmd.start_with? "brew "
cmd.sub!(/^brew /, "")
if command.start_with? "brew "
command.sub!(/^brew /, "")
else
cmd = "!#{cmd}"
command = "!#{command}"
end
yield target, cmd if block.present?
yield name, command if block.present?
end
end
sig { params(aliases: String).void }
def self.show(*aliases)
each([*aliases]) do |target, cmd|
puts "brew alias #{target}='#{cmd}'"
existing_alias = Alias.new(target, cmd)
each([*aliases]) do |name, command|
puts "brew alias #{name}='#{command}'"
existing_alias = Alias.new(name, command)
existing_alias.link unless existing_alias.symlink.exist?
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "api/analytics"
@ -11,10 +11,10 @@ module Homebrew
module API
extend Cachable
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze
HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze
HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname)
sig { params(endpoint: String).returns(Hash) }
sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(endpoint)
return cache[endpoint] if cache.present? && cache.key?(endpoint)
@ -33,7 +33,8 @@ module Homebrew
end
sig {
params(endpoint: String, target: Pathname, stale_seconds: Integer).returns([T.any(Array, Hash), T::Boolean])
params(endpoint: String, target: Pathname,
stale_seconds: Integer).returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
@ -96,7 +97,8 @@ module Homebrew
mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now
FileUtils.touch(target, mtime:) unless skip_download
JSON.parse(target.read(encoding: Encoding::UTF_8), freeze: true)
# Can use `target.read` again when/if https://github.com/sorbet/sorbet/pull/8999 is merged/released.
JSON.parse(File.read(target, encoding: Encoding::UTF_8), freeze: true)
rescue JSON::ParserError
target.unlink
retry_count += 1
@ -122,14 +124,17 @@ module Homebrew
end
end
sig { params(json: Hash).returns(Hash) }
def self.merge_variations(json)
sig {
params(json: T::Hash[String, T.untyped],
bottle_tag: ::Utils::Bottles::Tag).returns(T::Hash[String, T.untyped])
}
def self.merge_variations(json, bottle_tag: T.unsafe(nil))
return json unless json.key?("variations")
bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os,
arch: Homebrew::SimulateSystem.current_arch)
bottle_tag ||= Homebrew::SimulateSystem.current_tag
if (variation = json.dig("variations", bottle_tag.to_s).presence)
if (variation = json.dig("variations", bottle_tag.to_s).presence) ||
(variation = json.dig("variations", bottle_tag.to_sym).presence)
json = json.merge(variation)
end
@ -137,7 +142,7 @@ module Homebrew
end
sig { params(names: T::Array[String], type: String, regenerate: T::Boolean).returns(T::Boolean) }
def self.write_names_file(names, type, regenerate:)
def self.write_names_file!(names, type, regenerate:)
names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
if !names_path.exist? || regenerate
names_path.write(names.join("\n"))
@ -147,7 +152,10 @@ module Homebrew
false
end
sig { params(json_data: Hash).returns([T::Boolean, T.any(String, Array, Hash)]) }
sig {
params(json_data: T::Hash[String, T.untyped])
.returns([T::Boolean, T.any(String, T::Array[T.untyped], T::Hash[String, T.untyped])])
}
private_class_method def self.verify_and_parse_jws(json_data)
signatures = json_data["signatures"]
homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" }

View File

@ -10,7 +10,6 @@ module Homebrew
def analytics_api_path
"analytics"
end
alias generic_analytics_api_path analytics_api_path
sig { params(category: String, days: T.any(Integer, String)).returns(T::Hash[String, T.untyped]) }
def fetch(category, days)

View File

@ -1,7 +1,7 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "extend/cachable"
require "cachable"
require "api/download"
module Homebrew
@ -12,16 +12,23 @@ module Homebrew
DEFAULT_API_FILENAME = "cask.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"cask.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(token: String).returns(Hash) }
sig { params(token: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(token)
Homebrew::API.fetch "cask/#{token}.json"
end
sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) }
def self.source_download(cask)
path = cask.ruby_source_path.to_s || "Casks/#{cask.token}.rb"
path = cask.ruby_source_path.to_s
sha256 = cask.ruby_source_checksum[:sha256]
checksum = Checksum.new(sha256) if sha256
git_head = cask.tap_git_head || "HEAD"
@ -40,13 +47,14 @@ module Homebrew
.load(config: cask.config)
end
sig { returns(Pathname) }
def self.cached_json_file_path
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
HOMEBREW_CACHE_API/api_filename
end
sig { returns(T::Boolean) }
def self.download_and_cache_data!
json_casks, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME
json_casks, updated = Homebrew::API.fetch_json_api_file api_filename
cache["renames"] = {}
cache["casks"] = json_casks.to_h do |json_cask|
@ -63,7 +71,7 @@ module Homebrew
end
private_class_method :download_and_cache_data!
sig { returns(T::Hash[String, Hash]) }
sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
def self.all_casks
unless cache.key?("casks")
json_updated = download_and_cache_data!
@ -87,7 +95,7 @@ module Homebrew
def self.write_names(regenerate: false)
download_and_cache_data! unless cache.key?("casks")
Homebrew::API.write_names_file(all_casks.keys, "cask", regenerate:)
Homebrew::API.write_names_file!(all_casks.keys, "cask", regenerate:)
end
end
end

View File

@ -1,7 +1,7 @@
# typed: strict
# frozen_string_literal: true
require "extend/cachable"
require "cachable"
require "api/download"
module Homebrew
@ -12,6 +12,13 @@ module Homebrew
DEFAULT_API_FILENAME = "formula.jws.json"
sig { returns(String) }
def self.api_filename
return DEFAULT_API_FILENAME unless ENV.fetch("HOMEBREW_USE_INTERNAL_API", false)
"internal/formula.#{SimulateSystem.current_tag}.jws.json"
end
private_class_method :cache
sig { params(name: String).returns(T::Hash[String, T.untyped]) }
@ -42,12 +49,12 @@ module Homebrew
sig { returns(Pathname) }
def self.cached_json_file_path
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME
HOMEBREW_CACHE_API/api_filename
end
sig { returns(T::Boolean) }
def self.download_and_cache_data!
json_formulae, updated = Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME
json_formulae, updated = Homebrew::API.fetch_json_api_file api_filename
cache["aliases"] = {}
cache["renames"] = {}
@ -122,7 +129,7 @@ module Homebrew
def self.write_names_and_aliases(regenerate: false)
download_and_cache_data! unless cache.key?("formulae")
return unless Homebrew::API.write_names_file(all_formulae.keys, "formula", regenerate:)
return unless Homebrew::API.write_names_file!(all_formulae.keys, "formula", regenerate:)
(HOMEBREW_CACHE_API/"formula_aliases.txt").open("w") do |file|
all_aliases.each do |alias_name, real_name|

View File

@ -1,22 +1,26 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
# Used to substitute common paths with generic placeholders when generating JSON for the API.
module APIHashable
sig { void }
def generating_hash!
return if generating_hash?
# Apply monkeypatches for API generation
@old_homebrew_prefix = HOMEBREW_PREFIX
@old_homebrew_cellar = HOMEBREW_CELLAR
@old_home = Dir.home
@old_homebrew_prefix = T.let(HOMEBREW_PREFIX, T.nilable(Pathname))
@old_homebrew_cellar = T.let(HOMEBREW_CELLAR, T.nilable(Pathname))
@old_home = T.let(Dir.home, T.nilable(String))
@old_git_config_global = T.let(ENV.fetch("GIT_CONFIG_GLOBAL", nil), T.nilable(String))
Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER))
ENV["HOME"] = HOMEBREW_HOME_PLACEHOLDER
ENV["GIT_CONFIG_GLOBAL"] = File.join(@old_home, ".gitconfig")
@generating_hash = true
@generating_hash = T.let(true, T.nilable(T::Boolean))
end
sig { void }
def generated_hash!
return unless generating_hash?
@ -24,10 +28,12 @@ module APIHashable
Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix)
ENV["HOME"] = @old_home
ENV["GIT_CONFIG_GLOBAL"] = @old_git_config_global
@generating_hash = false
end
sig { returns(T::Boolean) }
def generating_hash?
@generating_hash ||= false
@generating_hash == true

View File

@ -52,4 +52,4 @@ FORMULA_COMPONENT_PRECEDENCE_LIST = T.let([
[{ name: :caveats, type: :method_definition }],
[{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }],
[{ name: :test, type: :block_call }],
].freeze, T::Array[[{ name: Symbol, type: Symbol }]])
].freeze, T::Array[T::Array[{ name: Symbol, type: Symbol }]])

View File

@ -64,12 +64,8 @@ module Homebrew
sig { returns(T::Boolean) }
def self.enabled?
return false if Homebrew::EnvConfig.no_verify_attestations?
return true if Homebrew::EnvConfig.verify_attestations?
return false if ENV.fetch("CI", false)
return false if OS.not_tier_one_configuration?
# Always check credentials last to avoid unnecessary credential extraction.
(Homebrew::EnvConfig.developer? || Homebrew::EnvConfig.devcmdrun?) && GitHub::API.credentials.present?
Homebrew::EnvConfig.verify_attestations?
end
# Returns a path to a suitable `gh` executable for attestation verification.

View File

@ -1,10 +1,15 @@
# typed: strict
# frozen_string_literal: true
# TODO: add more reasons here
NO_AUTOBUMP_REASONS_LIST = T.let({
incompatible_version_format: "incompatible version format",
bumped_by_upstream: "bumped by upstream",
NO_AUTOBUMP_REASONS_INTERNAL = T.let({
extract_plist: "livecheck uses `:extract_plist` strategy",
latest_version: "`version` is set to `:latest`",
}.freeze, T::Hash[Symbol, String])
# The valid symbols for passing to `no_autobump!` in a `Formula` or `Cask`.
# @api public
NO_AUTOBUMP_REASONS_LIST = T.let({
incompatible_version_format: "incompatible version format",
bumped_by_upstream: "bumped by upstream",
requires_manual_review: "a manual review of this package is required for inclusion in autobump",
}.merge(NO_AUTOBUMP_REASONS_INTERNAL).freeze, T::Hash[Symbol, String])

View File

@ -91,7 +91,7 @@ class Bottle
def fetch(verify_download_integrity: true, timeout: nil, quiet: false)
resource.fetch(verify_download_integrity:, timeout:, quiet:)
rescue DownloadError
raise unless fallback_on_error
raise unless fallback_on_error?
fetch_tab
retry
@ -121,7 +121,7 @@ class Bottle
begin
resource.fetch(timeout:, quiet:)
rescue DownloadError
raise unless fallback_on_error
raise unless fallback_on_error?
retry
rescue Resource::BottleManifest::Error
@ -193,7 +193,7 @@ class Bottle
specs
end
def fallback_on_error
def fallback_on_error?
# Use the default bottle domain as a fallback mirror
if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) &&
Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN

View File

@ -531,9 +531,9 @@ GIT_REVISION=$("${HOMEBREW_GIT}" -C "${HOMEBREW_REPOSITORY}" rev-parse HEAD 2>/d
if [[ -z "${GIT_REVISION}" ]]
then
read -r GIT_HEAD 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/HEAD"
if [[ "${GIT_HEAD}" == "ref: refs/heads/master" ]]
if [[ "${GIT_HEAD}" == "ref: refs/heads/main" ]]
then
read -r GIT_REVISION 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/refs/heads/master"
read -r GIT_REVISION 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/refs/heads/main"
elif [[ "${GIT_HEAD}" == "ref: refs/heads/stable" ]]
then
read -r GIT_REVISION 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/refs/heads/stable"
@ -600,6 +600,11 @@ case "$1" in
homebrew-version
exit 0
;;
mcp-server)
source "${HOMEBREW_LIBRARY}/Homebrew/cmd/mcp-server.sh"
homebrew-mcp-server "$@"
exit 0
;;
esac
# TODO: bump version when new macOS is released or announced and update references in:
@ -609,6 +614,8 @@ esac
# and, if needed:
# - MacOSVersion::SYMBOLS
HOMEBREW_MACOS_NEWEST_UNSUPPORTED="16"
# TODO: bump version when new macOS is released
HOMEBREW_MACOS_NEWEST_SUPPORTED="15"
# TODO: bump version when new macOS is released and update references in:
# - docs/Installation.md
# - HOMEBREW_MACOS_OLDEST_SUPPORTED in .github/workflows/pkg-installer.yml
@ -836,6 +843,7 @@ export HOMEBREW_OS_VERSION
export HOMEBREW_MACOS_VERSION
export HOMEBREW_MACOS_VERSION_NUMERIC
export HOMEBREW_MACOS_NEWEST_UNSUPPORTED
export HOMEBREW_MACOS_NEWEST_SUPPORTED
export HOMEBREW_MACOS_OLDEST_SUPPORTED
export HOMEBREW_MACOS_OLDEST_ALLOWED
export HOMEBREW_USER_AGENT
@ -1078,6 +1086,22 @@ else
export HOMEBREW_GITHUB_PACKAGES_AUTH="Bearer QQ=="
fi
# Avoid picking up any random `sudo` in `PATH`.
if [[ -x /usr/bin/sudo ]]
then
SUDO=/usr/bin/sudo
else
# Do this after ensuring we're using default Bash builtins.
SUDO="$(command -v sudo 2>/dev/null)"
fi
# Reset sudo timestamp to avoid running unauthorized sudo commands
if [[ -n "${SUDO}" ]]
then
"${SUDO}" --reset-timestamp 2>/dev/null || true
fi
unset SUDO
if [[ -n "${HOMEBREW_BASH_COMMAND}" ]]
then
# source rather than executing directly to ensure the entire file is read into

View File

@ -41,7 +41,7 @@ module Homebrew
sig { returns(T::Boolean) }
def mas_installed?
@mas_installed ||= which_formula("mas")
@mas_installed ||= which_formula?("mas")
end
sig { returns(T::Boolean) }
@ -59,7 +59,7 @@ module Homebrew
sig { returns(T::Boolean) }
def whalebrew_installed?
@whalebrew_installed ||= which_formula("whalebrew")
@whalebrew_installed ||= which_formula?("whalebrew")
end
sig { returns(T::Boolean) }
@ -70,7 +70,7 @@ module Homebrew
end
sig { params(name: String).returns(T::Boolean) }
def which_formula(name)
def which_formula?(name)
formula = Formulary.factory(name)
ENV["PATH"] = "#{formula.opt_bin}:#{ENV.fetch("PATH", nil)}" if formula.any_version_installed?
which(name).present?
@ -130,6 +130,9 @@ module Homebrew
@formula_versions_from_env[formula_env_name]
end
sig { void }
def prepend_pkgconf_path_if_needed!; end
sig { void }
def reset!
@mas_installed = T.let(nil, T.nilable(T::Boolean))

View File

@ -1,7 +1,7 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
require "bundle/brew_installer"
require "bundle/formula_installer"
module Homebrew
module Bundle
@ -11,7 +11,7 @@ module Homebrew
PACKAGE_TYPE_NAME = "Formula"
def installed_and_up_to_date?(formula, no_upgrade: false)
Homebrew::Bundle::BrewInstaller.formula_installed_and_up_to_date?(formula, no_upgrade:)
Homebrew::Bundle::FormulaInstaller.formula_installed_and_up_to_date?(formula, no_upgrade:)
end
end
end

View File

@ -24,8 +24,8 @@ module Homebrew
end
def entry_to_formula(entry)
require "bundle/brew_installer"
Homebrew::Bundle::BrewInstaller.new(entry.name, entry.options)
require "bundle/formula_installer"
Homebrew::Bundle::FormulaInstaller.new(entry.name, entry.options)
end
def formula_needs_to_start?(formula)
@ -38,8 +38,8 @@ module Homebrew
end
def lookup_old_name(service_name)
require "bundle/brew_dumper"
@old_names ||= Homebrew::Bundle::BrewDumper.formula_oldnames
require "bundle/formula_dumper"
@old_names ||= Homebrew::Bundle::FormulaDumper.formula_oldnames
old_name = @old_names[service_name]
old_name ||= @old_names[service_name.split("/").last]
old_name

View File

@ -8,6 +8,7 @@ module Homebrew
@casks = nil
@cask_names = nil
@cask_hash = nil
@cask_oldnames = nil
end
def self.cask_names
@ -38,6 +39,25 @@ module Homebrew
end.join("\n")
end
def self.cask_oldnames
return @cask_oldnames if @cask_oldnames
@cask_oldnames = {}
casks.each do |c|
oldnames = c.old_tokens
next if oldnames.blank?
oldnames.each do |oldname|
@cask_oldnames[oldname] = c.full_name
if c.full_name.include? "/" # tap cask
tap_name = c.full_name.rpartition("/").first
@cask_oldnames["#{tap_name}/#{oldname}"] = c.full_name
end
end
end
@cask_oldnames
end
def self.formula_dependencies(cask_list)
return [] unless Bundle.cask_installed?
return [] if cask_list.blank?

View File

@ -18,7 +18,7 @@ module Homebrew
Homebrew::Bundle::CaskDumper.cask_is_outdated_using_greedy?(name)
end
def self.preinstall(name, no_upgrade: false, verbose: false, **options)
def self.preinstall!(name, no_upgrade: false, verbose: false, **options)
if installed_casks.include?(name) && !upgrading?(no_upgrade, name, options)
puts "Skipping install of #{name} cask. It is already installed." if verbose
return false
@ -27,7 +27,7 @@ module Homebrew
true
end
def self.install(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
def self.install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
return true unless preinstall
full_name = options.fetch(:full_name, name)
@ -87,12 +87,25 @@ module Homebrew
!cask_upgradable?(cask)
end
def self.cask_in_array?(cask, array)
return true if array.include?(cask)
return true if array.include?(cask.split("/").last)
require "bundle/cask_dumper"
old_names = Homebrew::Bundle::CaskDumper.cask_oldnames
old_name = old_names[cask]
old_name ||= old_names[cask.split("/").last]
return true if old_name && array.include?(old_name)
false
end
def self.cask_installed?(cask)
installed_casks.include? cask
cask_in_array?(cask, installed_casks)
end
def self.cask_upgradable?(cask)
outdated_casks.include? cask
cask_in_array?(cask, outdated_casks)
end
def self.installed_casks

View File

@ -1,4 +1,4 @@
# typed: false # rubocop:todo Sorbet/TrueSigil
# typed: true
# frozen_string_literal: true
module Homebrew
@ -23,7 +23,7 @@ module Homebrew
else
"needs to be installed or updated."
end
"#{self.class::PACKAGE_TYPE_NAME} #{name} #{reason}"
"#{self.class.const_get(:PACKAGE_TYPE_NAME)} #{name} #{reason}"
end
def full_check(packages, no_upgrade:)
@ -33,7 +33,7 @@ module Homebrew
def checkable_entries(all_entries)
require "bundle/skipper"
all_entries.select { |e| e.type == self.class::PACKAGE_TYPE }
all_entries.select { |e| e.type == self.class.const_get(:PACKAGE_TYPE) }
.reject(&Bundle::Skipper.method(:skip?))
end
@ -139,14 +139,14 @@ module Homebrew
def self.reset!
require "bundle/cask_dumper"
require "bundle/brew_dumper"
require "bundle/formula_dumper"
require "bundle/mac_app_store_dumper"
require "bundle/tap_dumper"
require "bundle/brew_services"
@dsl = nil
Homebrew::Bundle::CaskDumper.reset!
Homebrew::Bundle::BrewDumper.reset!
Homebrew::Bundle::FormulaDumper.reset!
Homebrew::Bundle::MacAppStoreDumper.reset!
Homebrew::Bundle::TapDumper.reset!
Homebrew::Bundle::BrewServices.reset!

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "bundle/adder"
@ -7,6 +7,7 @@ module Homebrew
module Bundle
module Commands
module Add
sig { params(args: String, type: Symbol, global: T::Boolean, file: T.nilable(String)).void }
def self.run(*args, type:, global:, file:)
Homebrew::Bundle::Adder.add(*args, type:, global:, file:)
end

View File

@ -10,7 +10,7 @@ module Homebrew
module Cleanup
def self.reset!
require "bundle/cask_dumper"
require "bundle/brew_dumper"
require "bundle/formula_dumper"
require "bundle/tap_dumper"
require "bundle/vscode_extension_dumper"
require "bundle/brew_services"
@ -19,29 +19,30 @@ module Homebrew
@kept_casks = nil
@kept_formulae = nil
Homebrew::Bundle::CaskDumper.reset!
Homebrew::Bundle::BrewDumper.reset!
Homebrew::Bundle::FormulaDumper.reset!
Homebrew::Bundle::TapDumper.reset!
Homebrew::Bundle::VscodeExtensionDumper.reset!
Homebrew::Bundle::BrewServices.reset!
end
def self.run(global: false, file: nil, force: false, zap: false, dsl: nil)
def self.run(global: false, file: nil, force: false, zap: false, dsl: nil,
formulae: true, casks: true, taps: true, vscode: true)
@dsl ||= dsl
casks = casks_to_uninstall(global:, file:)
formulae = formulae_to_uninstall(global:, file:)
taps = taps_to_untap(global:, file:)
vscode_extensions = vscode_extensions_to_uninstall(global:, file:)
casks = casks ? casks_to_uninstall(global:, file:) : []
formulae = formulae ? formulae_to_uninstall(global:, file:) : []
taps = taps ? taps_to_untap(global:, file:) : []
vscode_extensions = vscode ? vscode_extensions_to_uninstall(global:, file:) : []
if force
if casks.any?
args = zap ? ["--zap"] : []
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--cask", *args, "--force", *casks
puts "Uninstalled #{casks.size} cask#{(casks.size == 1) ? "" : "s"}"
puts "Uninstalled #{casks.size} cask#{"s" if casks.size != 1}"
end
if formulae.any?
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--formula", "--force", *formulae
puts "Uninstalled #{formulae.size} formula#{(formulae.size == 1) ? "" : "e"}"
puts "Uninstalled #{formulae.size} formula#{"e" if formulae.size != 1}"
end
Kernel.system HOMEBREW_BREW_FILE, "untap", *taps if taps.any?
@ -100,11 +101,11 @@ module Homebrew
def self.formulae_to_uninstall(global: false, file: nil)
kept_formulae = self.kept_formulae(global:, file:)
require "bundle/brew_dumper"
require "bundle/brew_installer"
current_formulae = Homebrew::Bundle::BrewDumper.formulae
require "bundle/formula_dumper"
require "bundle/formula_installer"
current_formulae = Homebrew::Bundle::FormulaDumper.formulae
current_formulae.reject! do |f|
Homebrew::Bundle::BrewInstaller.formula_in_array?(f[:full_name], kept_formulae)
Homebrew::Bundle::FormulaInstaller.formula_in_array?(f[:full_name], kept_formulae)
end
# Don't try to uninstall formulae with keepme references
@ -118,7 +119,7 @@ module Homebrew
private_class_method def self.kept_formulae(global: false, file: nil)
require "bundle/brewfile"
require "bundle/brew_dumper"
require "bundle/formula_dumper"
require "bundle/cask_dumper"
@kept_formulae ||= begin
@ -127,12 +128,13 @@ module Homebrew
kept_formulae = @dsl.entries.select { |e| e.type == :brew }.map(&:name)
kept_formulae += Homebrew::Bundle::CaskDumper.formula_dependencies(kept_casks)
kept_formulae.map! do |f|
Homebrew::Bundle::BrewDumper.formula_aliases[f] ||
Homebrew::Bundle::BrewDumper.formula_oldnames[f] ||
f
Homebrew::Bundle::FormulaDumper.formula_aliases.fetch(
f,
Homebrew::Bundle::FormulaDumper.formula_oldnames.fetch(f, f),
)
end
kept_formulae + recursive_dependencies(Homebrew::Bundle::BrewDumper.formulae, kept_formulae)
kept_formulae + recursive_dependencies(Homebrew::Bundle::FormulaDumper.formulae, kept_formulae)
end
end
@ -141,7 +143,11 @@ module Homebrew
return @kept_casks if @kept_casks
@dsl ||= Brewfile.read(global:, file:)
@kept_casks = @dsl.entries.select { |e| e.type == :cask }.map(&:name)
kept_casks = @dsl.entries.select { |e| e.type == :cask }.flat_map(&:name)
kept_casks.map! do |c|
Homebrew::Bundle::CaskDumper.cask_oldnames.fetch(c, c)
end
@kept_casks = kept_casks
end
private_class_method def self.recursive_dependencies(current_formulae, formulae_names, top_level: true)

View File

@ -7,9 +7,10 @@ module Homebrew
module Bundle
module Commands
module Dump
def self.run(global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
def self.run(global:, file:, describe:, force:, no_restart:, taps:, formulae:, casks:, mas:, whalebrew:,
vscode:)
Homebrew::Bundle::Dumper.dump_brewfile(
global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:,
global:, file:, describe:, force:, no_restart:, taps:, formulae:, casks:, mas:, whalebrew:, vscode:,
)
end
end

View File

@ -1,4 +1,4 @@
# typed: false # rubocop:todo Sorbet/TrueSigil
# typed: true
# frozen_string_literal: true
require "English"
@ -13,6 +13,16 @@ module Homebrew
module Exec
PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/
sig {
params(
args: String,
global: T::Boolean,
file: T.nilable(String),
subcommand: String,
services: T::Boolean,
check: T::Boolean,
).void
}
def self.run(*args, global: false, file: nil, subcommand: "", services: false, check: false)
if check
require "bundle/commands/check"
@ -25,9 +35,9 @@ module Homebrew
# Setup Homebrew's ENV extensions
ENV.activate_extensions!
raise UsageError, "No command to execute was specified!" if args.blank?
command = args.first
raise UsageError, "No command to execute was specified!" if command.blank?
require "bundle/brewfile"
@dsl = Brewfile.read(global:, file:)
@ -64,14 +74,8 @@ module Homebrew
ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims"
end
# Setup pkg-config, if present, to help locate packages
# Only need this on Linux as Homebrew provides a shim on macOS
# TODO: use extend/OS here
# rubocop:todo Homebrew/MoveToExtendOS
if OS.linux? && (pkgconf = Formulary.factory("pkgconf")) && pkgconf.any_version_installed?
ENV.prepend_path "PATH", pkgconf.opt_bin.to_s
end
# rubocop:enable Homebrew/MoveToExtendOS
# Setup pkgconf, if needed, to help locate packages
Bundle.prepend_pkgconf_path_if_needed!
# For commands which aren't either absolute or relative
# Add the command directory to PATH, since it may get blown away by superenv
@ -170,15 +174,29 @@ module Homebrew
end
end
return
elsif subcommand == "sh"
preferred_path = Utils::Shell.preferred_path(default: "/bin/bash")
notice = unless Homebrew::EnvConfig.no_env_hints?
<<~EOS
Your shell has been configured to use a build environment from your `Brewfile`.
This should help you build stuff.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
When done, type `exit`.
EOS
end
ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil
args = [Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)]
end
if services
require "bundle/brew_services"
exit_code = 0
exit_code = T.let(0, Integer)
run_services(@dsl.entries) do
Kernel.system(*args)
exit_code = $CHILD_STATUS.exitstatus
if (system_exit_code = $CHILD_STATUS&.exitstatus)
exit_code = system_exit_code
end
end
exit!(exit_code)
else
@ -191,7 +209,7 @@ module Homebrew
entries: T::Array[Homebrew::Bundle::Dsl::Entry],
_block: T.proc.params(
entry: Homebrew::Bundle::Dsl::Entry,
info: T::Hash[String, T.anything],
info: T::Hash[String, T.untyped],
service_file: Pathname,
conflicting_services: T::Array[T::Hash[String, T.anything]],
).void,
@ -271,7 +289,7 @@ module Homebrew
map_service_info(entries) do |entry, info, service_file, conflicting_services|
# Don't restart if already running this version
loaded_file = Pathname.new(info["loaded_file"].to_s)
next if info["running"] && loaded_file&.file? && loaded_file&.realpath == service_file.realpath
next if info["running"] && loaded_file.file? && loaded_file.realpath == service_file.realpath
if info["running"] && !Bundle::BrewServices.stop(info["name"], keep: true)
opoo "Failed to stop #{info["name"]} service"

View File

@ -11,7 +11,7 @@ module Homebrew
def self.run(global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false, force: false,
quiet: false)
@dsl = Brewfile.read(global:, file:)
Homebrew::Bundle::Installer.install(
Homebrew::Bundle::Installer.install!(
@dsl.entries,
global:, file:, no_lock:, no_upgrade:, verbose:, force:, quiet:,
) || exit(1)

View File

@ -8,11 +8,11 @@ module Homebrew
module Bundle
module Commands
module List
def self.run(global:, file:, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
def self.run(global:, file:, formulae:, casks:, taps:, mas:, whalebrew:, vscode:)
parsed_entries = Brewfile.read(global:, file:).entries
Homebrew::Bundle::Lister.list(
parsed_entries,
brews:, casks:, taps:, mas:, whalebrew:, vscode:,
formulae:, casks:, taps:, mas:, whalebrew:, vscode:,
)
end
end

View File

@ -13,9 +13,9 @@ module Homebrew
true
end
def self.build_brewfile(describe:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:, vscode:)
def self.build_brewfile(describe:, no_restart:, formulae:, taps:, casks:, mas:, whalebrew:, vscode:)
require "bundle/tap_dumper"
require "bundle/brew_dumper"
require "bundle/formula_dumper"
require "bundle/cask_dumper"
require "bundle/mac_app_store_dumper"
require "bundle/whalebrew_dumper"
@ -23,7 +23,7 @@ module Homebrew
content = []
content << TapDumper.dump if taps
content << BrewDumper.dump(describe:, no_restart:) if brews
content << FormulaDumper.dump(describe:, no_restart:) if formulae
content << CaskDumper.dump(describe:) if casks
content << MacAppStoreDumper.dump if mas
content << WhalebrewDumper.dump if whalebrew
@ -31,11 +31,11 @@ module Homebrew
"#{content.reject(&:empty?).join("\n")}\n"
end
def self.dump_brewfile(global:, file:, describe:, force:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:,
vscode:)
def self.dump_brewfile(global:, file:, describe:, force:, no_restart:, formulae:, taps:, casks:, mas:,
whalebrew:, vscode:)
path = brewfile_path(global:, file:)
can_write_to_brewfile?(path, force:)
content = build_brewfile(describe:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
content = build_brewfile(describe:, no_restart:, taps:, formulae:, casks:, mas:, whalebrew:, vscode:)
write_file path, content
end

View File

@ -1,4 +1,4 @@
# typed: false # rubocop:todo Sorbet/TrueSigil
# typed: true
# frozen_string_literal: true
require "json"
@ -7,10 +7,8 @@ require "tsort"
module Homebrew
module Bundle
# TODO: refactor into multiple modules
module BrewDumper
module_function
def reset!
module FormulaDumper
def self.reset!
require "bundle/brew_services"
Homebrew::Bundle::BrewServices.reset!
@ -21,14 +19,14 @@ module Homebrew
@formula_oldnames = nil
end
def formulae
def self.formulae
return @formulae if @formulae
formulae_by_full_name
@formulae
end
def formulae_by_full_name(name = nil)
def self.formulae_by_full_name(name = nil)
return @formulae_by_full_name[name] if name.present? && @formulae_by_full_name&.key?(name)
require "formula"
@ -51,11 +49,11 @@ module Homebrew
{}
end
def formulae_by_name(name)
def self.formulae_by_name(name)
formulae_by_full_name(name) || @formulae_by_name[name]
end
def dump(describe: false, no_restart: false)
def self.dump(describe: false, no_restart: false)
require "bundle/brew_services"
requested_formula = formulae.select do |f|
@ -77,7 +75,7 @@ module Homebrew
end.join("\n")
end
def formula_aliases
def self.formula_aliases
return @formula_aliases if @formula_aliases
@formula_aliases = {}
@ -96,7 +94,7 @@ module Homebrew
@formula_aliases
end
def formula_oldnames
def self.formula_oldnames
return @formula_oldnames if @formula_oldnames
@formula_oldnames = {}
@ -115,7 +113,7 @@ module Homebrew
@formula_oldnames
end
def add_formula(formula)
private_class_method def self.add_formula(formula)
hash = formula_to_hash formula
@formulae_by_name[hash[:name]] = hash
@ -123,9 +121,8 @@ module Homebrew
hash
end
private_class_method :add_formula
def formula_to_hash(formula)
private_class_method def self.formula_to_hash(formula)
keg = if formula.linked?
link = true if formula.keg_only?
formula.linked_keg
@ -185,17 +182,21 @@ module Homebrew
official_tap: formula.tap&.official? || false,
}
end
private_class_method :formula_to_hash
class Topo < Hash
include TSort
def each_key(&block)
keys.each(&block)
end
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node.downcase).sort.each(&block)
end
end
def sort!(formulae)
private_class_method def self.sort!(formulae)
# Step 1: Sort by formula full name while putting tap formulae behind core formulae.
# So we can have a nicer output.
formulae = formulae.sort do |a, b|
@ -230,15 +231,14 @@ module Homebrew
odie <<~EOS
Formulae dependency graph sorting failed (likely due to a circular dependency):
#{cycle_first}: #{topo[cycle_first]}
#{cycle_last}: #{topo[cycle_last]}
#{cycle_first}: #{topo[cycle_first] if topo}
#{cycle_last}: #{topo[cycle_last] if topo}
Please run the following commands and try again:
brew update
brew uninstall --ignore-dependencies --force #{cycle_first} #{cycle_last}
brew install #{cycle_first} #{cycle_last}
EOS
end
private_class_method :sort!
end
end
end

View File

@ -3,19 +3,19 @@
module Homebrew
module Bundle
class BrewInstaller
class FormulaInstaller
def self.reset!
@installed_formulae = nil
@outdated_formulae = nil
@pinned_formulae = nil
end
def self.preinstall(name, no_upgrade: false, verbose: false, **options)
new(name, options).preinstall(no_upgrade:, verbose:)
def self.preinstall!(name, no_upgrade: false, verbose: false, **options)
new(name, options).preinstall!(no_upgrade:, verbose:)
end
def self.install(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
new(name, options).install(preinstall:, no_upgrade:, verbose:, force:)
def self.install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
new(name, options).install!(preinstall:, no_upgrade:, verbose:, force:)
end
def initialize(name, options = {})
@ -31,7 +31,7 @@ module Homebrew
@changed = nil
end
def preinstall(no_upgrade: false, verbose: false)
def preinstall!(no_upgrade: false, verbose: false)
if installed? && (self.class.no_upgrade_with_args?(no_upgrade, @name) || !upgradable?)
puts "Skipping install of #{@name} formula. It is already installed." if verbose
@changed = nil
@ -41,7 +41,7 @@ module Homebrew
true
end
def install(preinstall: true, no_upgrade: false, verbose: false, force: false)
def install!(preinstall: true, no_upgrade: false, verbose: false, force: false)
install_result = if preinstall
install_change_state!(no_upgrade:, verbose:, force:)
else
@ -80,9 +80,9 @@ module Homebrew
return false unless resolve_conflicts!(verbose:)
if installed?
upgrade!(verbose:, force:)
upgrade_formula!(verbose:, force:)
else
install!(verbose:, force:)
install_formula!(verbose:, force:)
end
end
@ -179,13 +179,13 @@ module Homebrew
return true if array.include?(formula)
return true if array.include?(formula.split("/").last)
require "bundle/brew_dumper"
old_names = Homebrew::Bundle::BrewDumper.formula_oldnames
require "bundle/formula_dumper"
old_names = Homebrew::Bundle::FormulaDumper.formula_oldnames
old_name = old_names[formula]
old_name ||= old_names[formula.split("/").last]
return true if old_name && array.include?(old_name)
resolved_full_name = Homebrew::Bundle::BrewDumper.formula_aliases[formula]
resolved_full_name = Homebrew::Bundle::FormulaDumper.formula_aliases[formula]
return false unless resolved_full_name
return true if array.include?(resolved_full_name)
return true if array.include?(resolved_full_name.split("/").last)
@ -219,14 +219,14 @@ module Homebrew
end
def self.formulae
require "bundle/brew_dumper"
Homebrew::Bundle::BrewDumper.formulae
require "bundle/formula_dumper"
Homebrew::Bundle::FormulaDumper.formulae
end
private
def installed?
BrewInstaller.formula_installed?(@name)
FormulaInstaller.formula_installed?(@name)
end
def linked?
@ -242,7 +242,7 @@ module Homebrew
end
def upgradable?
BrewInstaller.formula_upgradable?(@name)
FormulaInstaller.formula_upgradable?(@name)
end
def conflicts_with
@ -250,8 +250,8 @@ module Homebrew
conflicts_with = Set.new
conflicts_with += @conflicts_with_arg
require "bundle/brew_dumper"
if (formula = Homebrew::Bundle::BrewDumper.formulae_by_full_name(@full_name)) &&
require "bundle/formula_dumper"
if (formula = Homebrew::Bundle::FormulaDumper.formulae_by_full_name(@full_name)) &&
(formula_conflicts_with = formula[:conflicts_with])
conflicts_with += formula_conflicts_with
end
@ -262,7 +262,7 @@ module Homebrew
def resolve_conflicts!(verbose:)
conflicts_with.each do |conflict|
next unless BrewInstaller.formula_installed?(conflict)
next unless FormulaInstaller.formula_installed?(conflict)
if verbose
puts <<~EOS
@ -282,7 +282,7 @@ module Homebrew
true
end
def install!(verbose:, force:)
def install_formula!(verbose:, force:)
install_args = @args.dup
install_args << "--force" << "--overwrite" if force
install_args << "--skip-link" if @link == false
@ -293,12 +293,12 @@ module Homebrew
return false
end
BrewInstaller.installed_formulae << @name
FormulaInstaller.installed_formulae << @name
@changed = true
true
end
def upgrade!(verbose:, force:)
def upgrade_formula!(verbose:, force:)
upgrade_args = []
upgrade_args << "--force" if force
with_args = " with #{upgrade_args.join(" ")}" if upgrade_args.present?

View File

@ -2,7 +2,7 @@
# frozen_string_literal: true
require "bundle/dsl"
require "bundle/brew_installer"
require "bundle/formula_installer"
require "bundle/cask_installer"
require "bundle/mac_app_store_installer"
require "bundle/whalebrew_installer"
@ -13,7 +13,7 @@ require "bundle/skipper"
module Homebrew
module Bundle
module Installer
def self.install(entries, global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false,
def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false,
force: false, quiet: false)
success = 0
failure = 0
@ -27,8 +27,8 @@ module Homebrew
cls = case type
when :brew
options = entry.options
verb = "Upgrading" if Homebrew::Bundle::BrewInstaller.formula_upgradable?(name)
Homebrew::Bundle::BrewInstaller
verb = "Upgrading" if Homebrew::Bundle::FormulaInstaller.formula_upgradable?(name)
Homebrew::Bundle::FormulaInstaller
when :cask
options = entry.options
verb = "Upgrading" if Homebrew::Bundle::CaskInstaller.cask_upgradable?(name)
@ -49,7 +49,7 @@ module Homebrew
next if cls.nil?
next if Homebrew::Bundle::Skipper.skip? entry
preinstall = if cls.preinstall(*args, **options, no_upgrade:, verbose:)
preinstall = if cls.preinstall!(*args, **options, no_upgrade:, verbose:)
puts Formatter.success("#{verb} #{name}")
true
else
@ -57,7 +57,7 @@ module Homebrew
false
end
if cls.install(*args, **options,
if cls.install!(*args, **options,
preinstall:, no_upgrade:, verbose:, force:)
success += 1
else

View File

@ -4,14 +4,14 @@
module Homebrew
module Bundle
module Lister
def self.list(entries, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
def self.list(entries, formulae:, casks:, taps:, mas:, whalebrew:, vscode:)
entries.each do |entry|
puts entry.name if show?(entry.type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
puts entry.name if show?(entry.type, formulae:, casks:, taps:, mas:, whalebrew:, vscode:)
end
end
private_class_method def self.show?(type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
return true if brews && type == :brew
private_class_method def self.show?(type, formulae:, casks:, taps:, mas:, whalebrew:, vscode:)
return true if formulae && type == :brew
return true if casks && type == :cask
return true if taps && type == :tap
return true if mas && type == :mas

View File

@ -11,7 +11,7 @@ module Homebrew
@outdated_app_ids = nil
end
def self.preinstall(name, id, no_upgrade: false, verbose: false)
def self.preinstall!(name, id, no_upgrade: false, verbose: false)
unless Bundle.mas_installed?
puts "Installing mas. It is not currently installed." if verbose
Bundle.brew("install", "mas", verbose:)
@ -27,7 +27,7 @@ module Homebrew
true
end
def self.install(name, id, preinstall: true, no_upgrade: false, verbose: false, force: false)
def self.install!(name, id, preinstall: true, no_upgrade: false, verbose: false, force: false)
return true unless preinstall
if app_id_installed?(id)

View File

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

View File

@ -4,7 +4,7 @@
module Homebrew
module Bundle
module TapInstaller
def self.preinstall(name, verbose: false, **_options)
def self.preinstall!(name, verbose: false, **_options)
if installed_taps.include? name
puts "Skipping install of #{name} tap. It is already installed." if verbose
return false
@ -13,13 +13,12 @@ module Homebrew
true
end
def self.install(name, preinstall: true, verbose: false, force: false, **options)
def self.install!(name, preinstall: true, verbose: false, force: false, **options)
return true unless preinstall
puts "Installing #{name} tap. It is not currently installed." if verbose
args = []
args << "--force" if force
args.append("--force-auto-update") if options[:force_auto_update]
success = if options[:clone_target]
Bundle.brew("tap", name, options[:clone_target], *args, verbose:)

View File

@ -8,7 +8,7 @@ module Homebrew
@installed_extensions = nil
end
def self.preinstall(name, no_upgrade: false, verbose: false)
def self.preinstall!(name, no_upgrade: false, verbose: false)
if !Bundle.vscode_installed? && Bundle.cask_installed?
puts "Installing visual-studio-code. It is not currently installed." if verbose
Bundle.brew("install", "--cask", "visual-studio-code", verbose:)
@ -24,7 +24,7 @@ module Homebrew
true
end
def self.install(name, preinstall: true, no_upgrade: false, verbose: false, force: false)
def self.install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false)
return true unless preinstall
return true if extension_installed?(name)

View File

@ -8,7 +8,7 @@ module Homebrew
@installed_images = nil
end
def self.preinstall(name, verbose: false, **_options)
def self.preinstall!(name, verbose: false, **_options)
unless Bundle.whalebrew_installed?
puts "Installing whalebrew. It is not currently installed." if verbose
Bundle.brew("install", "--formula", "whalebrew", verbose:)
@ -23,7 +23,7 @@ module Homebrew
true
end
def self.install(name, preinstall: true, verbose: false, force: false, **_options)
def self.install!(name, preinstall: true, verbose: false, force: false, **_options)
odeprecated "`brew bundle` `whalebrew` support", "using `whalebrew` directly"
return true unless preinstall

View File

@ -139,7 +139,11 @@ module Cask
def initialize(cask, *dsl_args)
@cask = cask
@dirmethod = nil
@dsl_args = dsl_args.deep_dup
@dsl_key = nil
@english_article = nil
@english_name = nil
end
def config

View File

@ -41,7 +41,9 @@ module Cask
super
target = target_hash[:target]
@source = nil
@source_string = source.to_s
@target = nil
@target_string = target.to_s
end

View File

@ -1,9 +1,14 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module Cask
# Sorted set containing all cask artifacts.
class ArtifactSet < ::Set
extend T::Generic
Elem = type_member(:out) { { fixed: Artifact::AbstractArtifact } }
sig { params(block: T.nilable(T.proc.params(arg0: Elem).returns(T.untyped))).void }
def each(&block)
return enum_for(T.must(__method__)) { size } unless block
@ -11,6 +16,7 @@ module Cask
self
end
sig { returns(T::Array[Artifact::AbstractArtifact]) }
def to_a
super.sort
end

View File

@ -27,7 +27,7 @@ module Cask
sig {
params(
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean, token_conflicts: T.nilable(T::Boolean),
cask: ::Cask::Cask, download: T::Boolean, quarantine: T::Boolean,
online: T.nilable(T::Boolean), strict: T.nilable(T::Boolean), signing: T.nilable(T::Boolean),
new_cask: T.nilable(T::Boolean), only: T::Array[String], except: T::Array[String]
).void
@ -35,14 +35,13 @@ module Cask
def initialize(
cask,
download: false, quarantine: false,
token_conflicts: nil, online: nil, strict: nil, signing: nil,
online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: []
)
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
# `new_cask` implies `online`, `strict` and `signing`
online = new_cask if online.nil?
strict = new_cask if strict.nil?
signing = new_cask if signing.nil?
token_conflicts = new_cask if token_conflicts.nil?
# `online` and `signing` imply `download`
download ||= online || signing
@ -53,7 +52,6 @@ module Cask
@strict = strict
@signing = signing
@new_cask = new_cask
@token_conflicts = token_conflicts
@only = only
@except = except
end
@ -70,9 +68,6 @@ module Cask
sig { returns(T::Boolean) }
def strict? = !!@strict
sig { returns(T::Boolean) }
def token_conflicts? = !!@token_conflicts
sig { returns(::Cask::Audit) }
def run!
only_audits = @only
@ -430,15 +425,10 @@ module Cask
sig { void }
def audit_token_conflicts
return unless token_conflicts?
Homebrew.with_no_api_env do
return unless core_formula_names.include?(cask.token)
add_error(
"possible duplicate, cask token conflicts with Homebrew core formula: #{Formatter.url(core_formula_url)}",
strict_only: true,
)
add_error("cask token conflicts with an existing homebrew/core formula: #{Formatter.url(core_formula_url)}")
end
end
@ -606,7 +596,10 @@ module Cask
def audit_rosetta
return if (url = cask.url).nil?
return unless online?
# Rosetta 2 is only for ARM-capable macOS versions, which are Big Sur (11.x) and later
return if Homebrew::SimulateSystem.current_arch != :arm
return if MacOSVersion::SYMBOLS.fetch(Homebrew::SimulateSystem.current_os, "10") < "11"
return if cask.depends_on.macos&.maximum_version.to_s < "11"
odebug "Auditing Rosetta 2 requirement"
@ -640,7 +633,7 @@ module Cask
# binary stanza can contain shell scripts, so we just continue if lipo fails.
next unless result.success?
odebug result.merged_output
odebug "Architectures: #{result.merged_output}"
unless /arm64|x86_64/.match?(result.merged_output)
add_error "Artifacts architecture is no longer supported by macOS!",
@ -650,11 +643,12 @@ module Cask
supports_arm = result.merged_output.include?("arm64")
mentions_rosetta = cask.caveats.include?("requires Rosetta 2")
requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel }
if supports_arm && mentions_rosetta
add_error "Artifacts does not require Rosetta 2 but the caveats say otherwise!",
add_error "Artifacts do not require Rosetta 2 but the caveats say otherwise!",
location: url.location
elsif !supports_arm && !mentions_rosetta
elsif !supports_arm && !mentions_rosetta && !requires_intel
add_error "Artifacts require Rosetta 2 but this is not indicated by the caveats!",
location: url.location
end
@ -698,45 +692,53 @@ module Cask
return unless online?
return unless strict?
odebug "Auditing minimum OS version"
odebug "Auditing minimum macOS version"
plist_min_os = cask_plist_min_os
sparkle_min_os = livecheck_min_os
bundle_min_os = cask_bundle_min_os
sparkle_min_os = cask_sparkle_min_os
app_min_os = [bundle_min_os, sparkle_min_os].compact.max
debug_messages = []
debug_messages << "Plist #{plist_min_os}" if plist_min_os
debug_messages << "Sparkle #{sparkle_min_os}" if sparkle_min_os
odebug "Detected minimum OS version: #{debug_messages.join(" | ")}" unless debug_messages.empty?
min_os = [plist_min_os, sparkle_min_os].compact.max
return if min_os.nil? || min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED
debug_messages << "from artifact: #{bundle_min_os.to_sym}" if bundle_min_os
debug_messages << "from upstream: #{sparkle_min_os.to_sym}" if sparkle_min_os
odebug "Detected minimum macOS: #{app_min_os.to_sym} (#{debug_messages.join(" | ")})" if app_min_os
return if app_min_os.nil? || app_min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED
on_system_block_min_os = cask.on_system_block_min_os
cask_min_os = [on_system_block_min_os, cask.depends_on.macos&.minimum_version].compact.max
odebug "Declared minimum OS version: #{cask_min_os&.to_sym}"
return if cask_min_os&.to_sym == min_os.to_sym
return if cask.on_system_blocks_exist? &&
OnSystem.arch_condition_met?(:arm) &&
depends_on_min_os = cask.depends_on.macos&.minimum_version
cask_min_os = [on_system_block_min_os, depends_on_min_os].compact.max
debug_messages = []
debug_messages << "from on_system block: #{on_system_block_min_os.to_sym}" if on_system_block_min_os
if depends_on_min_os > HOMEBREW_MACOS_OLDEST_ALLOWED
debug_messages << "from depends_on stanza: #{depends_on_min_os.to_sym}"
end
odebug "Declared minimum macOS: #{cask_min_os.to_sym} (#{debug_messages.join(" | ").presence || "default"})"
return if cask_min_os.to_sym == app_min_os.to_sym
# ignore declared minimum OS < 11.x when auditing as ARM a cask with arch-specific artifacts
return if OnSystem.arch_condition_met?(:arm) &&
cask.on_system_blocks_exist? &&
cask_min_os.present? &&
cask_min_os < MacOSVersion.new("11")
min_os_definition = if cask_min_os.present?
if on_system_block_min_os.present? &&
on_system_block_min_os > cask.depends_on.macos&.minimum_version
"a block with a minimum OS version of #{cask_min_os.to_sym.inspect}"
min_os_definition = if cask_min_os > HOMEBREW_MACOS_OLDEST_ALLOWED
definition = if T.must(on_system_block_min_os.to_s <=> depends_on_min_os.to_s).positive?
"an on_system block"
else
cask_min_os.to_sym.inspect
"a depends_on stanza"
end
"#{definition} with a minimum macOS version of #{cask_min_os.to_sym.inspect}"
else
"no minimum OS version"
"no minimum macOS version"
end
add_error "Upstream defined #{min_os.to_sym.inspect} as the minimum OS version " \
source = T.must(bundle_min_os.to_s <=> sparkle_min_os.to_s).positive? ? "Artifact" : "Upstream"
add_error "#{source} defined #{app_min_os.to_sym.inspect} as the minimum macOS version " \
"but the cask declared #{min_os_definition}",
strict_only: true
end
sig { returns(T.nilable(MacOSVersion)) }
def livecheck_min_os
def cask_sparkle_min_os
return unless online?
return unless cask.livecheck_defined?
return if cask.livecheck.strategy != :sparkle
@ -769,10 +771,10 @@ module Cask
end
sig { returns(T.nilable(MacOSVersion)) }
def cask_plist_min_os
def cask_bundle_min_os
return unless online?
plist_min_os = T.let(nil, T.untyped)
min_os = T.let(nil, T.untyped)
@staged_path ||= cask.staged_path
extract_artifacts do |artifacts, tmpdir|
@ -783,13 +785,33 @@ module Cask
next unless File.exist?(plist_path)
plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist
plist_min_os = plist["LSMinimumSystemVersion"].presence
break if plist_min_os
min_os = plist["LSMinimumSystemVersion"].presence
break if min_os
next unless (main_binary = get_plist_main_binary(path))
next if !File.exist?(main_binary) || File.open(main_binary, "rb") { |f| f.read(2) == "#!" }
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
macho.machos.map do |slice|
[
slice[:LC_VERSION_MIN_MACOSX].first&.version_string,
slice[:LC_BUILD_VERSION].first&.minos_string,
]
end.flatten
end.compact.min
break if min_os
end
end
begin
MacOSVersion.new(plist_min_os).strip_patch
MacOSVersion.new(min_os).strip_patch
rescue MacOSVersion::Error
nil
end

View File

@ -11,17 +11,17 @@ module Cask
params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).returns(T::Set[String])
}
def self.audit(
cask, audit_download: false, audit_online: nil, audit_strict: nil, audit_signing: nil,
audit_token_conflicts: nil, audit_new_cask: nil, quarantine: false, any_named_args: false, language: nil,
audit_new_cask: nil, quarantine: false, any_named_args: false, language: nil,
only: [], except: []
)
new(
cask, audit_download:, audit_online:, audit_strict:, audit_signing:, audit_token_conflicts:,
cask, audit_download:, audit_online:, audit_strict:, audit_signing:,
audit_new_cask:, quarantine:, any_named_args:, language:, only:, except:
).audit
end
@ -36,7 +36,7 @@ module Cask
params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean),
audit_strict: T.nilable(T::Boolean), audit_signing: T.nilable(T::Boolean),
audit_token_conflicts: T.nilable(T::Boolean), audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
audit_new_cask: T.nilable(T::Boolean), quarantine: T::Boolean,
any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).void
}
@ -46,7 +46,6 @@ module Cask
audit_online: nil,
audit_strict: nil,
audit_signing: nil,
audit_token_conflicts: nil,
audit_new_cask: nil,
quarantine: false,
any_named_args: false,
@ -61,7 +60,6 @@ module Cask
@audit_strict = audit_strict
@audit_signing = audit_signing
@quarantine = quarantine
@audit_token_conflicts = audit_token_conflicts
@any_named_args = any_named_args
@language = language
@only = only
@ -131,7 +129,6 @@ module Cask
strict: @audit_strict,
signing: @audit_signing,
new_cask: @audit_new_cask,
token_conflicts: @audit_token_conflicts,
download: @audit_download,
quarantine: @quarantine,
only: @only,

View File

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

View File

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

View File

@ -26,7 +26,7 @@ require "cask/dsl/version"
require "cask/url"
require "cask/utils"
require "extend/on_system"
require "on_system"
module Cask
# Class representing the domain-specific language used for casks.
@ -69,7 +69,6 @@ module Cask
].freeze
DSL_METHODS = Set.new([
:appcast,
:arch,
:artifacts,
:auto_updates,
@ -123,15 +122,21 @@ module Cask
sig { params(cask: Cask).void }
def initialize(cask)
# NOTE: Variables set by `set_unique_stanza` must be initialized to `nil`.
@auto_updates = T.let(nil, T.nilable(T::Boolean))
# NOTE: `:"@#{stanza}"` variables set by `set_unique_stanza` must be
# initialized to `nil`.
@arch = T.let(nil, T.nilable(String))
@arch_set_in_block = T.let(false, T::Boolean)
@artifacts = T.let(ArtifactSet.new, ArtifactSet)
@auto_updates = T.let(nil, T.nilable(T::Boolean))
@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)
@cask = T.let(cask, Cask)
@caveats = T.let(DSL::Caveats.new(cask), DSL::Caveats)
@conflicts_with = T.let(nil, T.nilable(DSL::ConflictsWith))
@conflicts_with_set_in_block = T.let(false, T::Boolean)
@container = T.let(nil, T.nilable(DSL::Container))
@container_set_in_block = T.let(false, T::Boolean)
@depends_on = T.let(DSL::DependsOn.new, DSL::DependsOn)
@depends_on_set_in_block = T.let(false, T::Boolean)
@deprecated = T.let(false, T::Boolean)
@ -140,29 +145,32 @@ module Cask
@deprecation_replacement_cask = T.let(nil, T.nilable(String))
@deprecation_replacement_formula = T.let(nil, T.nilable(String))
@desc = T.let(nil, T.nilable(String))
@desc_set_in_block = T.let(false, T::Boolean)
@disable_date = T.let(nil, T.nilable(Date))
@disable_reason = T.let(nil, T.nilable(T.any(String, Symbol)))
@disable_replacement_cask = T.let(nil, T.nilable(String))
@disable_replacement_formula = T.let(nil, T.nilable(String))
@disabled = T.let(false, T::Boolean)
@homepage = T.let(nil, T.nilable(String))
@homepage_set_in_block = T.let(false, T::Boolean)
@language_blocks = T.let({}, T::Hash[T::Array[String], Proc])
@language_eval = T.let(nil, T.nilable(String))
@livecheck = T.let(Livecheck.new(cask), Livecheck)
@livecheck_defined = T.let(false, T::Boolean)
@name = T.let([], T::Array[String])
@autobump = T.let(true, T::Boolean)
@no_autobump_defined = T.let(false, T::Boolean)
@on_system_blocks_exist = T.let(false, T::Boolean)
@os = T.let(nil, T.nilable(String))
@on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion))
@os = T.let(nil, T.nilable(String))
@os_set_in_block = T.let(false, T::Boolean)
@sha256 = T.let(nil, T.nilable(T.any(Checksum, Symbol)))
@sha256_set_in_block = T.let(false, T::Boolean)
@staged_path = T.let(nil, T.nilable(Pathname))
@token = T.let(cask.token, String)
@url = T.let(nil, T.nilable(URL))
@url_set_in_block = T.let(false, T::Boolean)
@version = T.let(nil, T.nilable(DSL::Version))
set_no_autobump!
@version_set_in_block = T.let(false, T::Boolean)
end
sig { returns(T::Boolean) }
@ -177,13 +185,6 @@ module Cask
sig { returns(T::Boolean) }
def livecheck_defined? = @livecheck_defined
sig { void }
def set_no_autobump!
return if @livecheck.strategy != :extract_plist
no_autobump! because: :extract_plist
end
sig { returns(T::Boolean) }
def on_system_blocks_exist? = @on_system_blocks_exist
@ -225,7 +226,7 @@ module Cask
raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only appear once.")
end
if instance_variable_defined?(:"@#{stanza}_set_in_block") && @called_in_on_system_block
if instance_variable_get(:"@#{stanza}_set_in_block") && @called_in_on_system_block
raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only be overridden once.")
end
end
@ -485,7 +486,7 @@ module Cask
def add_implicit_macos_dependency
return if (cask_depends_on = @depends_on).present? && cask_depends_on.macos.present?
depends_on macos: ">= :#{MacOSVersion::SYMBOLS.key MacOSVersion::SYMBOLS.values.min}"
depends_on macos: ">= #{MacOSVersion.new(HOMEBREW_MACOS_OLDEST_ALLOWED).to_sym.inspect}"
end
# Declare conflicts that keep a cask from installing or working correctly.
@ -547,6 +548,8 @@ module Cask
@livecheck_defined = true
@livecheck.instance_eval(&block)
no_autobump! because: :extract_plist if @livecheck.strategy == :extract_plist
@livecheck
end
# Whether the cask contains a `livecheck` block. This is a legacy alias

View File

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

View File

@ -52,16 +52,17 @@ module Cask
raise "Only a single 'depends_on macos' is allowed." if defined?(@macos)
# workaround for https://github.com/sorbet/sorbet/issues/6860
first_arg = args.first&.to_s
first_arg = args.first
first_arg_s = first_arg&.to_s
begin
@macos = if args.count > 1
MacOSRequirement.new([args], comparator: "==")
elsif MacOSVersion::SYMBOLS.key?(args.first)
elsif first_arg.is_a?(Symbol) && MacOSVersion::SYMBOLS.key?(first_arg)
MacOSRequirement.new([args.first], comparator: "==")
elsif (md = /^\s*(?<comparator><|>|[=<>]=)\s*:(?<version>\S+)\s*$/.match(first_arg))
elsif (md = /^\s*(?<comparator><|>|[=<>]=)\s*:(?<version>\S+)\s*$/.match(first_arg_s))
MacOSRequirement.new([T.must(md[:version]).to_sym], comparator: md[:comparator])
elsif (md = /^\s*(?<comparator><|>|[=<>]=)\s*(?<version>\S+)\s*$/.match(first_arg))
elsif (md = /^\s*(?<comparator><|>|[=<>]=)\s*(?<version>\S+)\s*$/.match(first_arg_s))
MacOSRequirement.new([md[:version]], comparator: md[:comparator])
# This is not duplicate of the first case: see `args.first` and a different comparator.
else # rubocop:disable Lint/DuplicateBranch

View File

@ -4,6 +4,7 @@
require "formula_installer"
require "unpack_strategy"
require "utils/topological_hash"
require "utils/analytics"
require "cask/config"
require "cask/download"
@ -149,7 +150,7 @@ module Cask
oh1 "Installing Cask #{Formatter.identifier(@cask)}"
# GitHub Actions globally disables Gatekeeper.
opoo "macOS's Gatekeeper has been disabled for this Cask" if !quarantine? && !GitHub::Actions.env_set?
opoo_outside_github_actions "macOS's Gatekeeper has been disabled for this Cask" unless quarantine?
stage
@cask.config = @cask.default_config.merge(old_config)
@ -188,7 +189,7 @@ on_request: true)
when :deprecated
opoo message_full
when :disabled
GitHub::Actions.puts_annotation_if_env_set(:error, message)
GitHub::Actions.puts_annotation_if_env_set!(:error, message)
raise CaskCannotBeInstalledError.new(@cask, message)
end
end
@ -303,6 +304,20 @@ on_request: true)
next if artifact.is_a?(Artifact::Binary) && !binaries?
artifact = T.cast(
artifact,
T.any(
Artifact::AbstractFlightBlock,
Artifact::Installer,
Artifact::KeyboardLayout,
Artifact::Mdimporter,
Artifact::Moved,
Artifact::Pkg,
Artifact::Qlplugin,
Artifact::Symlinked,
),
)
artifact.install_phase(
command: @command, verbose: verbose?, adopt: adopt?, auto_updates: @cask.auto_updates,
force: force?, predecessor:
@ -548,6 +563,18 @@ on_request: true)
artifacts.each do |artifact|
if artifact.respond_to?(:uninstall_phase)
artifact = T.cast(
artifact,
T.any(
Artifact::AbstractFlightBlock,
Artifact::KeyboardLayout,
Artifact::Moved,
Artifact::Qlplugin,
Artifact::Symlinked,
Artifact::Uninstall,
),
)
odebug "Uninstalling artifact of class #{artifact.class}"
artifact.uninstall_phase(
command: @command,
@ -562,6 +589,8 @@ on_request: true)
next unless artifact.respond_to?(:post_uninstall_phase)
artifact = T.cast(artifact, Artifact::Uninstall)
odebug "Post-uninstalling artifact of class #{artifact.class}"
artifact.post_uninstall_phase(
command: @command,
@ -575,7 +604,6 @@ on_request: true)
def zap
load_installed_caskfile!
ohai "Implied `brew uninstall --cask #{@cask}`"
uninstall_artifacts
if (zap_stanzas = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).empty?
opoo "No zap stanza present for Cask '#{@cask}'"
@ -764,10 +792,10 @@ on_request: true)
if installed_caskfile&.exist?
begin
@cask = CaskLoader.load(installed_caskfile)
@cask = CaskLoader.load_from_installed_caskfile(installed_caskfile)
return
rescue CaskInvalidError
# could be caused by trying to load outdated caskfile
rescue CaskInvalidError, CaskUnavailableError
# could be caused by trying to load outdated or deleted caskfile
end
end

View File

@ -5,10 +5,24 @@ require "tab"
module Cask
class Tab < ::AbstractTab
attr_accessor :uninstall_flight_blocks, :uninstall_artifacts
sig { returns(T.nilable(T::Boolean)) }
attr_accessor :uninstall_flight_blocks
sig { returns(T.nilable(T::Array[T.untyped])) }
attr_accessor :uninstall_artifacts
sig { params(attributes: T::Hash[String, 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]))
super
end
# Instantiates a {Tab} for a new installation of a cask.
def self.create(cask)
sig { override.params(formula_or_cask: T.any(Formula, Cask)).returns(T.attached_class) }
def self.create(formula_or_cask)
cask = T.cast(formula_or_cask, Cask)
tab = super
tab.tabfile = cask.metadata_main_container_path/FILENAME
@ -23,6 +37,7 @@ module Cask
# Returns a {Tab} for an already installed cask,
# or a fake one if the cask is not installed.
sig { params(cask: Cask).returns(T.attached_class) }
def self.for_cask(cask)
path = cask.metadata_main_container_path/FILENAME
@ -40,6 +55,7 @@ module Cask
tab
end
sig { returns(T.attached_class) }
def self.empty
tab = super
tab.uninstall_flight_blocks = false
@ -76,10 +92,12 @@ module Cask
runtime_deps
end
sig { returns(T.nilable(String)) }
def version
source["version"]
end
sig { params(_args: T.untyped).returns(String) }
def to_json(*_args)
attributes = {
"homebrew_version" => homebrew_version,
@ -98,6 +116,7 @@ module Cask
JSON.pretty_generate(attributes)
end
sig { returns(String) }
def to_s
s = ["Installed"]
s << "using the formulae.brew.sh API" if loaded_from_api

View File

@ -23,7 +23,7 @@ module Cask
require_sha: T.nilable(T::Boolean),
).returns(T::Boolean)
}
def self.upgrade_casks(
def self.upgrade_casks!(
*casks,
args:,
force: false,
@ -134,7 +134,7 @@ module Cask
return true if caught_exceptions.empty?
raise MultipleCaskErrors, caught_exceptions if caught_exceptions.count > 1
raise caught_exceptions.fetch(0) if caught_exceptions.count == 1
raise caught_exceptions.fetch(0) if caught_exceptions.one?
false
end

View File

@ -11,11 +11,12 @@ module Cask
T.any(URI::Generic, String, [T.any(URI::Generic, String), T::Hash[Symbol, T.untyped]])
end
# Methods for the `url` stanza.
class DSL
sig { returns(T.any(URI::Generic, String)) }
attr_reader :uri
sig { returns(T.nilable(T::Array[String])) }
sig { returns(T.nilable(T::Hash[T.any(Symbol, String), String])) }
attr_reader :revisions
sig { returns(T.nilable(T::Boolean)) }
@ -57,7 +58,7 @@ module Cask
# @api public
branch: T.nilable(String),
# @api public
revisions: T.nilable(T::Array[String]),
revisions: T.nilable(T::Hash[T.any(Symbol, String), String]),
# @api public
revision: T.nilable(String),
# @api public
@ -87,7 +88,7 @@ module Cask
specs[:using] = @using = T.let(using, T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass))
specs[:tag] = @tag = T.let(tag, T.nilable(String))
specs[:branch] = @branch = T.let(branch, T.nilable(String))
specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Array[String]))
specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Hash[T.any(Symbol, String), String]))
specs[:revision] = @revision = T.let(revision, T.nilable(String))
specs[:trust_cert] = @trust_cert = T.let(trust_cert, T.nilable(T::Boolean))
specs[:cookies] = @cookies = T.let(cookies, T.nilable(T::Hash[String, String]))
@ -101,6 +102,7 @@ module Cask
end
end
# Allow passing a block to the `url` stanza.
class BlockDSL
# Allow accessing the URL associated with page contents.
class PageWithURL < SimpleDelegator
@ -197,7 +199,7 @@ module Cask
using: T.any(T::Class[AbstractDownloadStrategy], Symbol, NilClass),
tag: T.nilable(String),
branch: T.nilable(String),
revisions: T.nilable(T::Array[String]),
revisions: T.nilable(T::Hash[T.any(Symbol, String), String]),
revision: T.nilable(String),
trust_cert: T.nilable(T::Boolean),
cookies: T.nilable(T::Hash[String, String]),

View File

@ -14,10 +14,13 @@ class Caveats
sig { params(formula: Formula).void }
def initialize(formula)
@formula = formula
@caveats = T.let(nil, T.nilable(String))
@completions_and_elisp = T.let(nil, T.nilable(T::Array[String]))
end
sig { returns(String) }
def caveats
@caveats ||= begin
caveats = []
build = formula.build
begin
@ -28,7 +31,21 @@ class Caveats
formula.build = build
end
caveats << keg_only_text
caveats << service_caveats
caveats.compact.join("\n")
end
end
sig { returns(T::Boolean) }
def empty?
caveats.blank? && completions_and_elisp.blank?
end
delegate [:to_s] => :caveats
sig { returns(T::Array[String]) }
def completions_and_elisp
@completions_and_elisp ||= begin
valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? &&
@ -38,16 +55,13 @@ class Caveats
else
valid_shells
end
shells.each do |shell|
caveats << function_completion_caveats(shell)
completions_and_elisp = shells.map do |shell|
function_completion_caveats(shell)
end
completions_and_elisp << elisp_caveats
completions_and_elisp.compact
end
caveats << service_caveats
caveats << elisp_caveats
caveats.compact.join("\n")
end
delegate [:empty?, :to_s] => :caveats
sig { params(skip_reason: T::Boolean).returns(T.nilable(String)) }
def keg_only_text(skip_reason: false)
@ -180,7 +194,7 @@ class Caveats
startup = formula.service.requires_root?
if Utils::Service.running?(formula)
s << "To restart #{formula.full_name} after an upgrade:"
s << " #{startup ? "sudo " : ""}brew services restart #{formula.full_name}"
s << " #{"sudo " if startup}brew services restart #{formula.full_name}"
elsif startup
s << "To start #{formula.full_name} now and restart at startup:"
s << " sudo brew services start #{formula.full_name}"

View File

@ -195,7 +195,7 @@ module Homebrew
return false unless (name = basename.to_s[/\A(.*?)--/, 1])
cask = begin
Cask::CaskLoader.load(name)
Cask::CaskLoader.load(name, warn: false)
rescue Cask::CaskError
nil
end

View File

@ -571,6 +571,7 @@ module Homebrew
end
return unless available
return if Context.current.quiet?
return if cask&.old_tokens&.include?(ref)
opoo package_conflicts_message(ref, loaded_type, cask)
end

View File

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

View File

@ -31,7 +31,7 @@ module Homebrew
sig { override.void }
def run
ENV.activate_extensions!
T.cast(ENV, Superenv).deps = args.named.to_formulae if superenv?(nil)
ENV.deps = args.named.to_formulae if superenv?(nil)
ENV.setup_build_environment
shell = if args.plain?

View File

@ -8,13 +8,14 @@ module Homebrew
module Cmd
class Alias < AbstractCommand
cmd_args do
usage_banner "`alias` [<alias> ... | <alias>=<command>]"
usage_banner "`alias` [`--edit`] [<alias>|<alias>=<command>]"
description <<~EOS
Show existing aliases. If no aliases are given, print the whole list.
Show an alias's command. If no alias is given, print the whole list.
EOS
switch "--edit",
description: "Edit aliases in a text editor. Either one or all aliases may be opened at once. " \
"If the given alias doesn't exist it'll be pre-populated with a template."
named_args max: 1
end

View File

@ -73,11 +73,10 @@ module Homebrew
description: "`install` prints output from commands as they are run. " \
"`check` lists all missing dependencies."
switch "--no-upgrade",
env: :bundle_no_upgrade,
description: "`install` does not run `brew upgrade` on outdated dependencies. " \
"`check` does not check for outdated dependencies. " \
"Note they may still be upgraded by `brew install` if needed. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set."
"Note they may still be upgraded by `brew install` if needed.",
env: :bundle_no_upgrade
switch "--upgrade",
description: "`install` runs `brew upgrade` on outdated dependencies, " \
"even if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set."
@ -87,41 +86,36 @@ module Homebrew
switch "--install",
description: "Run `install` before continuing to other operations e.g. `exec`."
switch "--services",
env: :bundle_services,
description: "Temporarily start services while running the `exec` or `sh` command. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_SERVICES` is set."
description: "Temporarily start services while running the `exec` or `sh` command.",
env: :bundle_services
switch "-f", "--force",
description: "`install` runs with `--force`/`--overwrite`. " \
"`dump` overwrites an existing `Brewfile`. " \
"`cleanup` actually performs its cleanup operations."
switch "--cleanup",
env: :bundle_install_cleanup,
description: "`install` performs cleanup operation, same as running `cleanup --force`. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_INSTALL_CLEANUP` is set and " \
"`--global` is passed."
description: "`install` performs cleanup operation, same as running `cleanup --force`.",
env: [:bundle_install_cleanup, "--global"]
switch "--all",
description: "`list` all dependencies."
switch "--formula", "--brews",
description: "`list` or `dump` Homebrew formula dependencies."
switch "--formula", "--formulae", "--brews",
description: "`list`, `dump` or `cleanup` Homebrew formula dependencies."
switch "--cask", "--casks",
description: "`list` or `dump` Homebrew cask dependencies."
description: "`list`, `dump` or `cleanup` Homebrew cask dependencies."
switch "--tap", "--taps",
description: "`list` or `dump` Homebrew tap dependencies."
description: "`list`, `dump` or `cleanup` Homebrew tap dependencies."
switch "--mas",
description: "`list` or `dump` Mac App Store dependencies."
switch "--whalebrew",
description: "`list` or `dump` Whalebrew dependencies."
switch "--vscode",
description: "`list` or `dump` VSCode (and forks/variants) extensions."
description: "`list`, `dump` or `cleanup` VSCode (and forks/variants) extensions."
switch "--no-vscode",
env: :bundle_dump_no_vscode,
description: "`dump` without VSCode (and forks/variants) extensions. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_NO_VSCODE` is set."
description: "`dump` without VSCode (and forks/variants) extensions.",
env: :bundle_dump_no_vscode
switch "--describe",
env: :bundle_dump_describe,
description: "`dump` adds a description comment above each line, unless the " \
"dependency does not have a description. " \
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_DESCRIBE` is set."
"dependency does not have a description.",
env: :bundle_dump_describe
switch "--no-restart",
description: "`dump` does not add `restart_service` to formula lines."
switch "--zap",
@ -168,7 +162,7 @@ module Homebrew
zap = args.zap?
Homebrew::Bundle.upgrade_formulae = args.upgrade_formulae
no_type_args = !args.brews? && !args.casks? && !args.taps? && !args.mas? && !args.whalebrew? && !args.vscode?
no_type_args = [args.formulae?, args.casks?, args.taps?, args.mas?, args.whalebrew?, args.vscode?].none?
if args.install?
if [nil, "install", "upgrade"].include?(subcommand)
@ -215,7 +209,7 @@ module Homebrew
describe: args.describe?,
no_restart: args.no_restart?,
taps: args.taps? || no_type_args,
brews: args.brews? || no_type_args,
formulae: args.formulae? || no_type_args,
casks: args.casks? || no_type_args,
mas: args.mas? || no_type_args,
whalebrew: args.whalebrew? || no_type_args,
@ -226,7 +220,13 @@ module Homebrew
exec_editor(Homebrew::Bundle::Brewfile.path(global:, file:))
when "cleanup"
require "bundle/commands/cleanup"
Homebrew::Bundle::Commands::Cleanup.run(global:, file:, force:, zap:)
Homebrew::Bundle::Commands::Cleanup.run(
global:, file:, force:, zap:,
formulae: args.formulae? || no_type_args,
casks: args.casks? || no_type_args,
taps: args.taps? || no_type_args,
vscode: args.vscode? || no_type_args
)
when "check"
require "bundle/commands/check"
Homebrew::Bundle::Commands::Check.run(global:, file:, no_upgrade:, verbose:)
@ -235,7 +235,7 @@ module Homebrew
Homebrew::Bundle::Commands::List.run(
global:,
file:,
brews: args.brews? || args.all? || no_type_args,
formulae: args.formulae? || args.all? || no_type_args,
casks: args.casks? || args.all?,
taps: args.taps? || args.all?,
mas: args.mas? || args.all?,
@ -243,9 +243,9 @@ module Homebrew
vscode: args.vscode? || args.all?,
)
when "add", "remove"
# We intentionally omit the `s` from `brews`, `casks`, and `taps` for ease of handling later.
# We intentionally omit the s from `brews`, `casks`, and `taps` for ease of handling later.
type_hash = {
brew: args.brews?,
brew: args.formulae?,
cask: args.casks?,
tap: args.taps?,
mas: args.mas?,
@ -276,17 +276,7 @@ module Homebrew
_subcommand, *named_args = args.named
named_args
when "sh"
preferred_path = Utils::Shell.preferred_path(default: "/bin/bash")
notice = unless Homebrew::EnvConfig.no_env_hints?
<<~EOS
Your shell has been configured to use a build environment from your `Brewfile`.
This should help you build stuff.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
When done, type `exit`.
EOS
end
ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil
[Utils::Shell.shell_with_prompt("brew bundle", preferred_path:, notice:)]
["sh"]
when "env"
["env"]
end

View File

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

View File

@ -25,7 +25,8 @@ module Homebrew
"it is interpreted as a regular expression."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if `$HOMEBREW_EVAL_ALL` is set."
"descriptions.",
env: :eval_all
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
@ -47,7 +48,7 @@ module Homebrew
end
if search_type.present?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all? && Homebrew::EnvConfig.no_install_from_api?
if !args.eval_all? && Homebrew::EnvConfig.no_install_from_api?
raise UsageError, "`brew desc --search` needs `--eval-all` passed or `$HOMEBREW_EVAL_ALL` set!"
end

View File

@ -9,7 +9,7 @@ module Homebrew
cmd_args do
description <<~EOS
Control Homebrew's developer mode. When developer mode is enabled,
`brew update` will update Homebrew to the latest commit on the `master`
`brew update` will update Homebrew to the latest commit on the `main`
branch instead of the latest stable version along with some other behaviour changes.
`brew developer` [`state`]:
@ -38,7 +38,7 @@ module Homebrew
puts "However, `brew update` will update to the latest stable tag because " \
"#{Tty.bold}HOMEBREW_UPDATE_TO_TAG#{Tty.reset} is set."
else
puts "`brew update` will update to the latest commit on the `master` branch."
puts "`brew update` will update to the latest commit on the `main` branch."
end
else
puts "`brew update` will update to the latest stable tag."

View File

@ -1,11 +1,11 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "fetch"
require "cask/download"
require "retryable_download"
require "download_queue"
module Homebrew
module Cmd
@ -69,49 +69,6 @@ module Homebrew
named_args [:formula, :cask], min: 1
end
def concurrency
@concurrency ||= args.concurrency&.to_i || 1
end
def download_queue
@download_queue ||= begin
require "download_queue"
DownloadQueue.new(concurrency)
end
end
class Spinner
FRAMES = [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
].freeze
sig { void }
def initialize
@start = Time.now
@i = 0
end
sig { returns(String) }
def to_s
now = Time.now
if @start + 0.1 < now
@start = now
@i = (@i + 1) % FRAMES.count
end
FRAMES.fetch(@i)
end
end
sig { override.void }
def run
Formulary.enable_factory_cache!
@ -136,7 +93,7 @@ module Homebrew
bucket.each do |formula_or_cask|
case formula_or_cask
when Formula
formula = T.cast(formula_or_cask, Formula)
formula = formula_or_cask
ref = formula.loaded_from_api? ? formula.full_name : formula.path
os_arch_combinations.each do |os, arch|
@ -171,9 +128,9 @@ module Homebrew
end
if (manifest_resource = bottle.github_packages_manifest_resource)
fetch_downloadable(manifest_resource)
download_queue.enqueue(manifest_resource)
end
fetch_downloadable(bottle)
download_queue.enqueue(bottle)
rescue Interrupt
raise
rescue => e
@ -189,14 +146,16 @@ module Homebrew
next if fetched_bottle
fetch_downloadable(formula.resource)
formula.resources.each do |r|
fetch_downloadable(r)
r.patches.each { |patch| fetch_downloadable(patch.resource) if patch.external? }
if (resource = formula.resource)
download_queue.enqueue(resource)
end
formula.patchlist.each { |patch| fetch_downloadable(patch.resource) if patch.external? }
formula.resources.each do |r|
download_queue.enqueue(r)
r.patches.each { |patch| download_queue.enqueue(patch.resource) if patch.external? }
end
formula.patchlist.each { |patch| download_queue.enqueue(patch.resource) if patch.external? }
end
end
else
@ -216,131 +175,34 @@ module Homebrew
quarantine = true if quarantine.nil?
download = Cask::Download.new(cask, quarantine:)
fetch_downloadable(download)
download_queue.enqueue(download)
end
end
end
end
if concurrency == 1
downloads.each do |downloadable, promise|
promise.wait!
rescue ChecksumMismatchError => e
opoo "#{downloadable.download_type.capitalize} reports different checksum: #{e.expected}"
Homebrew.failed = true if downloadable.is_a?(Resource::Patch)
end
else
spinner = Spinner.new
remaining_downloads = downloads.dup
previous_pending_line_count = 0
begin
$stdout.print Tty.hide_cursor
$stdout.flush
output_message = lambda do |downloadable, future, last|
status = case future.state
when :fulfilled
"#{Tty.green}✔︎#{Tty.reset}"
when :rejected
"#{Tty.red}#{Tty.reset}"
when :pending, :processing
"#{Tty.blue}#{spinner}#{Tty.reset}"
else
raise future.state.to_s
end
message = "#{downloadable.download_type.capitalize} #{downloadable.name}"
$stdout.print "#{status} #{message}#{"\n" unless last}"
$stdout.flush
if future.rejected?
if (e = future.reason).is_a?(ChecksumMismatchError)
opoo "#{downloadable.download_type.capitalize} reports different checksum: #{e.expected}"
Homebrew.failed = true if downloadable.is_a?(Resource::Patch)
next 2
else
message = future.reason.to_s
onoe message
Homebrew.failed = true
next message.count("\n")
end
end
1
end
until remaining_downloads.empty?
begin
finished_states = [:fulfilled, :rejected]
finished_downloads, remaining_downloads = remaining_downloads.partition do |_, future|
finished_states.include?(future.state)
end
finished_downloads.each do |downloadable, future|
previous_pending_line_count -= 1
$stdout.print Tty.clear_to_end
$stdout.flush
output_message.call(downloadable, future, false)
end
previous_pending_line_count = 0
max_lines = [concurrency, Tty.height].min
remaining_downloads.each_with_index do |(downloadable, future), i|
break if previous_pending_line_count >= max_lines
$stdout.print Tty.clear_to_end
$stdout.flush
last = i == max_lines - 1 || i == remaining_downloads.count - 1
previous_pending_line_count += output_message.call(downloadable, future, last)
end
if previous_pending_line_count.positive?
if (previous_pending_line_count - 1).zero?
$stdout.print Tty.move_cursor_beginning
else
$stdout.print Tty.move_cursor_up_beginning(previous_pending_line_count - 1)
end
$stdout.flush
end
sleep 0.05
rescue Interrupt
remaining_downloads.each do |_, future|
# FIXME: Implement cancellation of running downloads.
end
download_queue.cancel
if previous_pending_line_count.positive?
$stdout.print Tty.move_cursor_down(previous_pending_line_count - 1)
$stdout.flush
end
raise
end
end
ensure
$stdout.print Tty.show_cursor
$stdout.flush
end
end
download_queue.start
ensure
download_queue.shutdown
end
private
def downloads
@downloads ||= {}
sig { returns(Integer) }
def concurrency
@concurrency ||= T.let(args.concurrency&.to_i || 1, T.nilable(Integer))
end
def fetch_downloadable(downloadable)
downloads[downloadable] ||= begin
tries = args.retry? ? {} : { tries: 1 }
download_queue.enqueue(RetryableDownload.new(downloadable, **tries), force: args.force?)
sig { returns(Integer) }
def retries
@retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 0, T.nilable(Integer))
end
sig { returns(DownloadQueue) }
def download_queue
@download_queue ||= T.let(begin
DownloadQueue.new(concurrency:, retries:, force: args.force?)
end, T.nilable(DownloadQueue))
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "abstract_command"
@ -18,7 +18,7 @@ module Homebrew
class Info < AbstractCommand
VALID_DAYS = %w[30 90 365].freeze
VALID_FORMULA_CATEGORIES = %w[install install-on-request build-error].freeze
VALID_CATEGORIES = (VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze
VALID_CATEGORIES = T.let((VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze, T::Array[String])
cmd_args do
description <<~EOS
@ -57,7 +57,7 @@ module Homebrew
switch "--eval-all",
depends_on: "--json",
description: "Evaluate all available formulae and casks, whether installed or not, to print their " \
"JSON. Implied if `$HOMEBREW_EVAL_ALL` is set."
"JSON."
switch "--variations",
depends_on: "--json",
description: "Include the variations hash in each formula's JSON output."
@ -96,14 +96,15 @@ module Homebrew
end
print_analytics
elsif args.json
all = args.eval_all?
print_json(all)
elsif (json = args.json)
print_json(json, args.eval_all?)
elsif args.github?
raise FormulaOrCaskUnspecifiedError if args.no_named?
exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) })
exec_browser(*args.named.to_formulae_and_casks.map do |formula_keg_or_cask|
formula_or_cask = T.cast(formula_keg_or_cask, T.any(Formula, Cask::Cask))
github_info(formula_or_cask)
end)
elsif args.no_named?
print_statistics
else
@ -111,6 +112,7 @@ module Homebrew
end
end
sig { params(remote: String, path: String).returns(String) }
def github_remote_path(remote, path)
if remote =~ %r{^(?:https?://|git(?:@|://))github\.com[:/](.+)/(.+?)(?:\.git)?$}
"https://github.com/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}/blob/HEAD/#{path}"
@ -175,6 +177,7 @@ module Homebrew
end
end
sig { params(version: T.any(T::Boolean, String)).returns(Symbol) }
def json_version(version)
version_hash = {
true => :default,
@ -187,16 +190,16 @@ module Homebrew
version_hash[version]
end
sig { params(all: T::Boolean).void }
def print_json(all)
raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named?
sig { params(json: T.any(T::Boolean, String), eval_all: T::Boolean).void }
def print_json(json, eval_all)
raise FormulaOrCaskUnspecifiedError if !(eval_all || args.installed?) && args.no_named?
json = case json_version(args.json)
json = case json_version(json)
when :v1, :default
raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask?
formulae = if all
Formula.all(eval_all: args.eval_all?).sort
formulae = if eval_all
Formula.all(eval_all:).sort
elsif args.installed?
Formula.installed.sort
else
@ -210,10 +213,10 @@ module Homebrew
end
when :v2
formulae, casks = T.let(
if all
if eval_all
[
Formula.all(eval_all: args.eval_all?).sort,
Cask::Cask.all(eval_all: args.eval_all?).sort_by(&:full_name),
Formula.all(eval_all:).sort,
Cask::Cask.all(eval_all:).sort_by(&:full_name),
]
elsif args.installed?
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]
@ -240,25 +243,31 @@ module Homebrew
puts JSON.pretty_generate(json)
end
sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) }
def github_info(formula_or_cask)
return formula_or_cask.path if formula_or_cask.tap.blank? || formula_or_cask.tap.remote.blank?
path = case formula_or_cask
when Formula
formula = formula_or_cask
formula.path.relative_path_from(T.must(formula.tap).path)
tap = formula.tap
return formula.path.to_s if tap.blank? || tap.remote.blank?
formula.path.relative_path_from(tap.path)
when Cask::Cask
cask = formula_or_cask
tap = cask.tap
return cask.sourcefile_path.to_s if tap.blank? || tap.remote.blank?
if cask.sourcefile_path.blank? || cask.sourcefile_path.extname != ".rb"
return "#{cask.tap.default_remote}/blob/HEAD/#{cask.tap.relative_cask_path(cask.token)}"
return "#{tap.default_remote}/blob/HEAD/#{tap.relative_cask_path(cask.token)}"
end
cask.sourcefile_path.relative_path_from(cask.tap.path)
cask.sourcefile_path.relative_path_from(tap.path)
end
github_remote_path(formula_or_cask.tap.remote, path)
github_remote_path(tap.remote, path.to_s)
end
sig { params(formula: Formula).void }
def info_formula(formula)
specs = []
@ -356,6 +365,7 @@ module Homebrew
Utils::Analytics.formula_output(formula, args:)
end
sig { params(dependencies: T::Array[Dependency]).returns(String) }
def decorate_dependencies(dependencies)
deps_status = dependencies.map do |dep|
if dep.satisfied?([])
@ -367,6 +377,7 @@ module Homebrew
deps_status.join(", ")
end
sig { params(requirements: T::Array[Requirement]).returns(String) }
def decorate_requirements(requirements)
req_status = requirements.map do |req|
req_s = req.display_s
@ -375,12 +386,14 @@ module Homebrew
req_status.join(", ")
end
sig { params(dep: Dependency).returns(String) }
def dep_display_s(dep)
return dep.name if dep.option_tags.empty?
"#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}"
end
sig { params(cask: Cask::Cask).void }
def info_cask(cask)
require "cask/info"

View File

@ -33,8 +33,8 @@ module Homebrew
description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each package at the end of the run."
description: "Print install times for each package at the end of the run.",
env: :display_install_times
switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \
@ -44,9 +44,9 @@ module Homebrew
switch "-n", "--dry-run",
description: "Show what would be installed, but do not actually install anything."
switch "--ask",
env: :ask,
description: "Ask for confirmation before downloading and installing formulae. " \
"Print bottles and dependencies download size and install size."
"Print download and install sizes of bottles and dependencies.",
env: :ask
[
[:switch, "--formula", "--formulae", {
description: "Treat all named arguments as formulae.",
@ -259,7 +259,7 @@ module Homebrew
if !Homebrew::EnvConfig.no_install_upgrade? && installed_casks.any?
require "cask/upgrade"
Cask::Upgrade.upgrade_casks(
Cask::Upgrade.upgrade_casks!(
*installed_casks,
force: args.force?,
dry_run: args.dry_run?,
@ -310,9 +310,7 @@ module Homebrew
Install.perform_preinstall_checks_once
Install.check_cc_argv(args.cc)
Install.ask_formulae(installed_formulae, args: args) if args.ask?
Install.install_formulae(
formulae_installer = Install.formula_installers(
installed_formulae,
installed_on_request: !args.as_dependency?,
installed_as_dependency: args.as_dependency?,
@ -338,9 +336,10 @@ module Homebrew
skip_link: args.skip_link?,
)
Upgrade.check_installed_dependents(
dependants = Upgrade.dependants(
installed_formulae,
flags: args.flags_only,
ask: args.ask?,
installed_on_request: !args.as_dependency?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
@ -354,6 +353,28 @@ module Homebrew
dry_run: args.dry_run?,
)
# Main block: if asking the user is enabled, show dependency and size information.
Install.ask_formulae(formulae_installer, dependants, args: args) if args.ask?
Install.install_formulae(formulae_installer,
dry_run: args.dry_run?,
verbose: args.verbose?)
Upgrade.upgrade_dependents(
dependants, installed_formulae,
flags: args.flags_only,
dry_run: args.dry_run?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?
)
Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?)

View File

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

View File

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

View File

@ -52,8 +52,8 @@ module Homebrew
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i || 0
patch_version = version.patch.to_i || 0
minor_version = version.minor.to_i
patch_version = version.patch.to_i
minor_version_range, patch_version_range = if Homebrew::EnvConfig.env_sync_strict?
# Only create symlinks for the exact installed patch version.

View File

@ -18,7 +18,8 @@ module Homebrew
description: "Show options for formulae that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show their " \
"options."
"options.",
env: :eval_all
flag "--command=",
description: "Show options for the specified <command>."
@ -29,10 +30,10 @@ module Homebrew
sig { override.void }
def run
all = args.eval_all?
eval_all = args.eval_all?
if all
puts_options(Formula.all(eval_all: args.eval_all?).sort)
if eval_all
puts_options(Formula.all(eval_all:).sort)
elsif args.installed?
puts_options(Formula.installed.sort)
elsif args.command.present?

View File

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

View File

@ -25,7 +25,7 @@ module Homebrew
if f.pinned?
opoo "#{f.name} already pinned"
elsif !f.pinnable?
onoe "#{f.name} not installed"
ofail "#{f.name} not installed"
else
f.pin
end

View File

@ -53,7 +53,7 @@ module Homebrew
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i
patch_version = version.patch.to_i || 0
patch_version = version.patch.to_i
patch_version_range = if Homebrew::EnvConfig.env_sync_strict?
# Only create symlinks for the exact installed patch version.

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