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

View File

@ -4,7 +4,7 @@ type: "Bug"
body: body:
- type: markdown - type: markdown
attributes: 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 - type: textarea
attributes: attributes:
render: shell render: shell
@ -16,10 +16,10 @@ body:
label: Verification 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. 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: 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. - label: I ran `brew update` twice and am still able to reproduce my issue.
required: true 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. - 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 required: true
- type: textarea - 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"]

135
.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 version: 2
multi-ecosystem-groups:
updates: all:
- package-ecosystem: github-actions
directory: /
schedule: schedule:
interval: weekly interval: weekly
allow: day: friday
- dependency-type: all time: '08:00'
# The actions in triage-issues.yml are updated in the Homebrew/.github repo timezone: Etc/UTC
ignore: updates:
- dependency-name: actions/stale - package-ecosystem: github-actions
groups: directory: "/"
artifacts: multi-ecosystem-group: all
patterns: patterns:
- actions/*-artifact - "*"
open-pull-requests-limit: 10 allow:
- dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: bundler
directories:
- "/Library/Homebrew"
multi-ecosystem-group: all
patterns:
- "*"
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:
- "*"
allow:
- dependency-type: all
- package-ecosystem: devcontainers
directory: "/"
multi-ecosystem-group: all
patterns:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
include:
- "*"
- package-ecosystem: pip
directories:
- "/Library/Homebrew/formula-analytics/"
multi-ecosystem-group: all
patterns:
- "*"
allow:
- dependency-type: all
cooldown:
default-days: 1
semver-major-days: 14
semver-minor-days: 7
semver-patch-days: 1
include:
- "*"
- package-ecosystem: bundler
directory: /Library/Homebrew
schedule:
interval: daily
allow:
- dependency-type: all
groups:
rspec:
patterns:
- "rspec*"
sorbet:
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
allow:
- dependency-type: all
open-pull-requests-limit: 10
- package-ecosystem: devcontainers
directory: /
schedule:
interval: daily
allow:
- dependency-type: all
open-pull-requests-limit: 10
- 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: on:
push: push:
branches: branches:
- main
- master - master
pull_request: 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: defaults:
run: run:
@ -22,16 +16,25 @@ concurrency:
group: "actionlint-${{ github.ref }}" group: "actionlint-${{ github.ref }}"
cancel-in-progress: ${{ github.event_name == 'pull_request' }} cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
HOMEBREW_DEVELOPER: 1
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_ENV_HINTS: 1
permissions: {} permissions: {}
jobs: jobs:
workflow_syntax: workflow_syntax:
if: github.repository_owner == 'Homebrew' if: github.repository_owner == 'Homebrew'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
container:
image: ghcr.io/homebrew/ubuntu22.04:main
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: setup-homebrew id: setup-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
@ -40,31 +43,44 @@ jobs:
- name: Install tools - name: Install tools
run: brew install actionlint shellcheck zizmor 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: env:
HOMEBREW_REPOSITORY: ${{ steps.setup-homebrew.outputs.repository-path }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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
- name: Upload SARIF file - name: Upload SARIF file
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 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: with:
name: results.sarif name: results.sarif
path: 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 - run: actionlint
upload_sarif: upload_sarif:
needs: workflow_syntax 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 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -77,7 +93,7 @@ jobs:
path: results.sarif path: results.sarif
- name: Upload SARIF file - 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: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

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

View File

@ -3,10 +3,9 @@ name: "CodeQL"
on: on:
push: push:
branches: branches:
- main
- master - master
pull_request: pull_request:
branches:
- master
defaults: defaults:
run: run:
@ -28,7 +27,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
languages: ruby languages: ruby
config: | config: |
@ -36,4 +35,4 @@ jobs:
- Library/Homebrew/vendor - Library/Homebrew/vendor
- name: Perform CodeQL Analysis - 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: pull_request:
push: push:
branches: branches:
- main
- master - master
merge_group: merge_group:
release: release:
@ -38,8 +39,8 @@ jobs:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Fetch origin/master from Git - name: Fetch origin/HEAD from Git
run: git fetch origin master run: git fetch origin HEAD
- name: Determine build attributes - name: Determine build attributes
id: attributes id: attributes
@ -83,12 +84,16 @@ jobs:
) )
fi fi
elif [[ "${GITHUB_EVENT_NAME}" == "push" && elif [[ "${GITHUB_EVENT_NAME}" == "push" &&
"${GITHUB_REF}" == "refs/heads/master" && ("${GITHUB_REF}" == "refs/heads/master" || "${GITHUB_REF}" == "refs/heads/main") &&
"${version}" == "22.04" ]]; then "${version}" == "22.04" ]]; then
tags+=( tags+=(
"ghcr.io/homebrew/brew:main"
"ghcr.io/homebrew/brew:master" "ghcr.io/homebrew/brew:master"
"ghcr.io/homebrew/ubuntu${version}:main"
"ghcr.io/homebrew/ubuntu${version}:master" "ghcr.io/homebrew/ubuntu${version}:master"
"homebrew/brew:main"
"homebrew/brew:master" "homebrew/brew:master"
"homebrew/ubuntu${version}:main"
"homebrew/ubuntu${version}:master" "homebrew/ubuntu${version}:master"
) )
fi fi
@ -160,11 +165,11 @@ jobs:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Fetch origin/master from Git - name: Fetch origin/HEAD from Git
run: git fetch origin master run: git fetch origin HEAD
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with: with:
cache-binary: false cache-binary: false
@ -190,7 +195,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image - name: Build Docker image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
context: . context: .
load: true load: true
@ -227,7 +232,7 @@ jobs:
- name: Deploy the Docker image by digest - name: Deploy the Docker image by digest
id: digest id: digest
if: fromJSON(steps.attributes.outputs.push) 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: with:
context: . context: .
cache-from: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache cache-from: type=registry,ref=ghcr.io/homebrew/ubuntu${{ matrix.version }}:cache
@ -263,7 +268,7 @@ jobs:
version: ${{ fromJSON(needs.generate-tags.outputs.matrix) }} version: ${{ fromJSON(needs.generate-tags.outputs.matrix) }}
steps: steps:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with: with:
cache-binary: false cache-binary: false

View File

@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
@ -52,7 +52,7 @@ jobs:
run: vale docs/ run: vale docs/
- name: Install Ruby - name: Install Ruby
uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0 uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
with: with:
bundler-cache: true bundler-cache: true
working-directory: docs working-directory: docs
@ -67,7 +67,13 @@ jobs:
- name: Generate formulae.brew.sh API samples - name: Generate formulae.brew.sh API samples
if: github.repository == 'Homebrew/formulae.brew.sh' if: github.repository == 'Homebrew/formulae.brew.sh'
working-directory: docs 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 - name: Build the site and check for broken links
working-directory: docs working-directory: docs

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
name: Update schema data name: Update SBOM schema
on: on:
push: push:
paths: paths:
- .github/workflows/schemas.yml - .github/workflows/sbom.yml
branches-ignore: branches-ignore:
- main
- master - master
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
@ -17,25 +18,25 @@ defaults:
shell: bash -xeuo pipefail {0} shell: bash -xeuo pipefail {0}
jobs: jobs:
spdx: sbom:
if: github.repository == 'Homebrew/brew' if: github.repository == 'Homebrew/brew'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
test-bot: false test-bot: false
- name: Configure Git user - name: Configure Git user
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
with: with:
username: BrewTestBot username: BrewTestBot
- name: Set up commit signing - name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master uses: Homebrew/actions/setup-commit-signing@main
with: with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -55,7 +56,7 @@ jobs:
git checkout "${BRANCH}" git checkout "${BRANCH}"
git checkout "Library/Homebrew/data/schemas" git checkout "Library/Homebrew/data/schemas"
else else
git checkout --no-track -B "${BRANCH}" origin/master git checkout --no-track -B "${BRANCH}" origin/HEAD
fi fi
# Intentionally tracking 2.3.x to match what we output in sbom.rb. 3.0 also doesn't have a JSON Schema. # 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 if ! git diff --exit-code Library/Homebrew/data/schemas
then then
git add "Library/Homebrew/data/schemas" 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" 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" ]] if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then then
echo "pull_request=true" >> "$GITHUB_OUTPUT" echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -78,13 +80,13 @@ jobs:
- name: Push commits - name: Push commits
if: steps.update.outputs.committed == 'true' if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master uses: Homebrew/actions/git-try-push@main
with: with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }} branch: ${{ steps.update.outputs.branch }}
force: true force: true
origin_branch: "master" origin_branch: "HEAD"
- name: Open a pull request - name: Open a pull request
if: steps.update.outputs.pull_request == 'true' if: steps.update.outputs.pull_request == 'true'
@ -92,3 +94,26 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} 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: paths:
- .github/workflows/sorbet.yml - .github/workflows/sorbet.yml
branches-ignore: branches-ignore:
- main
- master - master
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
@ -29,7 +30,7 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
@ -37,13 +38,13 @@ jobs:
- name: Configure Git user - name: Configure Git user
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
with: with:
username: BrewTestBot username: BrewTestBot
- name: Set up commit signing - name: Set up commit signing
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: Homebrew/actions/setup-commit-signing@master uses: Homebrew/actions/setup-commit-signing@main
with: with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -63,7 +64,7 @@ jobs:
git checkout "${BRANCH}" git checkout "${BRANCH}"
git checkout "Library/Homebrew/sorbet" git checkout "Library/Homebrew/sorbet"
else else
git checkout --no-track -B "${BRANCH}" origin/master git checkout --no-track -B "${BRANCH}" origin/HEAD
fi fi
fi fi
@ -80,17 +81,17 @@ jobs:
then then
git add "Library/Homebrew/sorbet" git add "Library/Homebrew/sorbet"
git commit -m "sorbet: Update RBI files." \ 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" if ! git diff --stat --exit-code "Library/Homebrew"
then then
git add "Library/Homebrew/" git add "Library/Homebrew/"
git commit -m "sorbet: Autobump sigils via Spoom" \ 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 fi
echo "committed=true" >> "$GITHUB_OUTPUT" 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" ]] if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then then
echo "pull_request=true" >> "$GITHUB_OUTPUT" echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -99,13 +100,13 @@ jobs:
- name: Push commits - name: Push commits
if: steps.commit.outputs.committed == 'true' if: steps.commit.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master uses: Homebrew/actions/git-try-push@main
with: with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }} branch: ${{ steps.update.outputs.branch }}
force: true force: true
origin_branch: "master" origin_branch: "HEAD"
- name: Open a pull request - name: Open a pull request
if: steps.commit.outputs.pull_request == 'true' if: steps.commit.outputs.pull_request == 'true'
@ -113,3 +114,26 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} 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: paths:
- .github/workflows/spdx.yml - .github/workflows/spdx.yml
branches-ignore: branches-ignore:
- main
- master - master
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
@ -23,19 +24,19 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
test-bot: false test-bot: false
- name: Configure Git user - name: Configure Git user
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
with: with:
username: BrewTestBot username: BrewTestBot
- name: Set up commit signing - name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master uses: Homebrew/actions/setup-commit-signing@main
with: with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -55,15 +56,16 @@ jobs:
git checkout "${BRANCH}" git checkout "${BRANCH}"
git checkout "Library/Homebrew/data/spdx" git checkout "Library/Homebrew/data/spdx"
else else
git checkout --no-track -B "${BRANCH}" origin/master git checkout --no-track -B "${BRANCH}" origin/HEAD
fi fi
if brew update-license-data if brew update-license-data
then then
git add "Library/Homebrew/data/spdx" 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" 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" ]] if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then then
echo "pull_request=true" >> "$GITHUB_OUTPUT" echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -72,13 +74,13 @@ jobs:
- name: Push commits - name: Push commits
if: steps.update.outputs.committed == 'true' if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master uses: Homebrew/actions/git-try-push@main
with: with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
branch: ${{ steps.update.outputs.branch }} branch: ${{ steps.update.outputs.branch }}
force: true force: true
origin_branch: "master" origin_branch: "HEAD"
- name: Open a pull request - name: Open a pull request
if: steps.update.outputs.pull_request == 'true' if: steps.update.outputs.pull_request == 'true'
@ -86,3 +88,26 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} 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: on:
push: push:
branches: branches:
- main
- master - master
paths: paths:
- .github/workflows/sponsors-maintainers-man-completions.yml - .github/workflows/sponsors-maintainers-man-completions.yml
@ -32,19 +33,19 @@ jobs:
steps: steps:
- name: Setup Homebrew - name: Setup Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
test-bot: false test-bot: false
- name: Configure Git user - name: Configure Git user
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
with: with:
username: BrewTestBot username: BrewTestBot
- name: Set up commit signing - name: Set up commit signing
uses: Homebrew/actions/setup-commit-signing@master uses: Homebrew/actions/setup-commit-signing@main
with: with:
signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }}
@ -60,7 +61,7 @@ jobs:
run: | run: |
git fetch origin 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 then
BRANCH="$GITHUB_REF_NAME" BRANCH="$GITHUB_REF_NAME"
else else
@ -76,7 +77,7 @@ jobs:
"manpages/brew.1" \ "manpages/brew.1" \
"completions" "completions"
else else
git checkout --force --no-track -B "${BRANCH}" origin/master git checkout --force --no-track -B "${BRANCH}" origin/HEAD
fi fi
if brew update-sponsors if brew update-sponsors
@ -111,7 +112,7 @@ jobs:
if [[ -n "${COMMITTED-}" ]] if [[ -n "${COMMITTED-}" ]]
then then
echo "committed=true" >> "$GITHUB_OUTPUT" 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" ]] if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]]
then then
echo "pull_request=true" >> "$GITHUB_OUTPUT" echo "pull_request=true" >> "$GITHUB_OUTPUT"
@ -124,7 +125,7 @@ jobs:
- name: Push commits - name: Push commits
if: steps.update.outputs.committed == 'true' if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master uses: Homebrew/actions/git-try-push@main
with: with:
token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
@ -137,3 +138,26 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }}
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} 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 pull-requests: write
steps: steps:
- name: Mark/Close Stale Issues and Pull Requests - name: Mark/Close Stale Issues and Pull Requests
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21 days-before-stale: 21
@ -68,7 +68,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests - 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: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 2 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: on:
push: push:
branches: branches:
- main
- master - master
pull_request: pull_request:
merge_group: merge_group:
@ -32,7 +33,7 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
@ -80,11 +81,11 @@ jobs:
name: tap syntax name: tap syntax
needs: syntax needs: syntax
if: github.repository_owner == 'Homebrew' if: github.repository_owner == 'Homebrew'
runs-on: macos-14 runs-on: macos-15
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: true core: true
cask: true cask: true
@ -135,13 +136,13 @@ jobs:
if: github.repository_owner == 'Homebrew' && github.event_name != 'push' if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/homebrew/brew:master image: ghcr.io/homebrew/brew:main
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: true core: false
cask: false cask: false
test-bot: false test-bot: false
@ -162,7 +163,7 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: true core: true
cask: true cask: true
@ -185,14 +186,14 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
test-bot: false test-bot: false
- name: Configure Git user - name: Configure Git user
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
with: with:
username: BrewTestBot username: BrewTestBot
@ -213,14 +214,14 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- name: update-test (Ubuntu) - name: update-test (Linux)
runs-on: ubuntu-latest runs-on: ubuntu-latest
- name: update-test (macOS) - name: update-test (macOS)
runs-on: macos-15 runs-on: macos-latest
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: false cask: false
@ -237,7 +238,6 @@ jobs:
name: ${{ matrix.name }} name: ${{ matrix.name }}
needs: syntax needs: syntax
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
container: ${{ matrix.container }}
strategy: strategy:
matrix: matrix:
include: include:
@ -247,23 +247,16 @@ jobs:
- name: tests (generic OS) - name: tests (generic OS)
test-flags: --generic --coverage test-flags: --generic --coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
- name: tests (Ubuntu 24.04) - name: tests (Linux)
test-flags: --coverage test-flags: --coverage
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
- name: tests (Ubuntu 22.04) - name: tests (macOS)
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)
test-flags: --coverage test-flags: --coverage
runs-on: macos-15 runs-on: macos-15
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
# We only test needs_homebrew_core tests on macOS because # We only test needs_homebrew_core tests on macOS because
# homebrew/core is not available by default on GitHub-hosted Ubuntu # 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' ',') filenames=$(find Library/Homebrew/test/junit -name 'rspec*.xml' -print | tr '\n' ',')
echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT" echo "filenames=${filenames%,}" >> "$GITHUB_OUTPUT"
- uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 - uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
with: with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: ${{ steps.junit_xml.outputs.filenames }} files: ${{ steps.junit_xml.outputs.filenames }}
@ -349,7 +342,7 @@ jobs:
disable_search: true disable_search: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
test-default-formula: test-bot:
name: ${{ matrix.name }} name: ${{ matrix.name }}
needs: syntax needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push' if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -358,36 +351,76 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- name: test default formula (Ubuntu 24.04) - name: test-bot (Linux arm64)
runs-on: ubuntu-latest runs-on: ubuntu-24.04-arm
container: ghcr.io/homebrew/ubuntu24.04:latest container: ghcr.io/homebrew/ubuntu24.04:latest
- name: test default formula (Ubuntu 22.04) - name: test-bot (Linux x86_64)
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu22.04:master container: ghcr.io/homebrew/ubuntu22.04:main
- name: test default formula (Ubuntu 20.04) # Use Debian Old Stable for testing Homebrew's glibc support.
- name: test-bot (Linux Homebrew glibc)
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/homebrew/ubuntu20.04:latest container: debian:oldstable
- name: test default formula (macOS 13 x86_64) - name: test-bot (macOS x86_64)
runs-on: macos-13 runs-on: macos-13
- name: test default formula (macOS 15 arm64) - name: test-bot (macOS arm64)
runs-on: macos-15 runs-on: macos-15
env: env:
HOMEBREW_TEST_BOT_ANALYTICS: 1 HOMEBREW_TEST_BOT_ANALYTICS: 1
HOMEBREW_ENFORCE_SBOM: 1 HOMEBREW_ENFORCE_SBOM: 1
steps: 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 - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: true core: false
cask: false cask: false
test-bot: true test-bot: true
- run: brew test-bot --only-cleanup-before - run: brew test-bot --only-cleanup-before
- name: Setup environment variables - name: Setup environment variables
if: matrix.container == 'ghcr.io/homebrew/ubuntu20.04:latest' run: |
run: echo "HOMEBREW_GLIBC_TESTING=1" >> "$GITHUB_ENV" # 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 - run: brew test-bot --only-setup
@ -395,7 +428,7 @@ jobs:
- run: brew test-bot --only-formulae --only-json-tab --test-default-formula - run: brew test-bot --only-formulae --only-json-tab --test-default-formula
test-brew-bundle-services: bundle-and-services:
name: ${{ matrix.name }} name: ${{ matrix.name }}
needs: syntax needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push' if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
@ -403,23 +436,21 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- name: test brew bundle and brew services (Ubuntu) - name: bundle and services (Linux)
runs-on: ubuntu-latest runs-on: ubuntu-latest
- name: test brew bundle and brew services (macOS) - name: bundle and services (macOS)
runs-on: macos-15 runs-on: macos-latest
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: true core: false
cask: false cask: false
test-bot: false test-bot: false
- run: brew test-bot --only-cleanup-before - run: brew test-bot --only-cleanup-before
- run: brew test-bot --only-setup
- name: Run brew bundle and brew services integration tests - name: Run brew bundle and brew services integration tests
run: | run: |
cat <<EOS >> Brewfile cat <<EOS >> Brewfile
@ -449,19 +480,22 @@ jobs:
brew services cleanup brew services cleanup
brew bundle cleanup --force brew bundle cleanup --force
test-analytics: analytics:
runs-on: ${{ matrix.os }} name: ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
strategy: strategy:
matrix: matrix:
os: include:
- ubuntu-latest - name: analytics (Linux)
- macos-latest runs-on: ubuntu-latest
- name: analytics (macOS)
runs-on: macos-latest
needs: syntax needs: syntax
if: github.repository_owner == 'Homebrew' && github.event_name != 'push' if: github.repository_owner == 'Homebrew' && github.event_name != 'push'
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
- name: Setup Python - name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0

View File

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

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
core: false core: false
cask: 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: rules:
unpinned-uses: unpinned-uses:
config: config:

3
.gitignore vendored
View File

@ -116,6 +116,7 @@
**/vendor/bundle/ruby/*/gems/rdoc-*/ **/vendor/bundle/ruby/*/gems/rdoc-*/
**/vendor/bundle/ruby/*/gems/redcarpet-*/ **/vendor/bundle/ruby/*/gems/redcarpet-*/
**/vendor/bundle/ruby/*/gems/regexp_parser-*/ **/vendor/bundle/ruby/*/gems/regexp_parser-*/
**/vendor/bundle/ruby/*/gems/require-hooks-*/
**/vendor/bundle/ruby/*/gems/rexml-*/ **/vendor/bundle/ruby/*/gems/rexml-*/
**/vendor/bundle/ruby/*/gems/rspec-*/ **/vendor/bundle/ruby/*/gems/rspec-*/
**/vendor/bundle/ruby/*/gems/rspec-core-*/ **/vendor/bundle/ruby/*/gems/rspec-core-*/
@ -164,6 +165,7 @@
!/completions !/completions
!/docs !/docs
!/manpages !/manpages
!/CODEOWNERS
# Unignore our packaging files # Unignore our packaging files
!/package !/package
@ -172,6 +174,7 @@
# Ignore generated documentation site # Ignore generated documentation site
/docs/_site /docs/_site
/docs/.jekyll-metadata /docs/.jekyll-metadata
/docs/tmp/.htmlproofer
/docs/vendor /docs/vendor
/docs/Gemfile.lock /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 #!/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 "${HOMEBREW_PREFIX}/bin/brew" install-bundler-gems --add-groups=style,typecheck,vscode >/dev/null 2>&1

View File

@ -40,7 +40,6 @@
"id": "default", "id": "default",
"name": "Brew Typecheck", "name": "Brew Typecheck",
"description": "Default configuration", "description": "Default configuration",
"cwd": "${workspaceFolder}",
"command": [ "command": [
"./bin/brew", "./bin/brew",
"typecheck", "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: Include:
- "**/*.rbi" - "**/*.rbi"
Exclude: Exclude:
- "Homebrew/sorbet/rbi/{dsl,gems}/**/*.rbi" - "Homebrew/sorbet/rbi/{annotations,dsl,gems}/**/*.rbi"
- "Homebrew/sorbet/rbi/parser*.rbi" - "Homebrew/sorbet/rbi/parser*.rbi"
- "Homebrew/bin/*" - "Homebrew/bin/*"
- "Homebrew/vendor/**/*" - "Homebrew/vendor/**/*"
@ -216,6 +216,10 @@ Naming/MethodParameterName:
merge: merge:
- AllowedNames - AllowedNames
# Allows a nicer API for boolean methods with side effects.
Naming/PredicateMethod:
AllowBangMethods: true
# Both styles are used depending on context, # Both styles are used depending on context,
# e.g. `sha256` and `something_countable_1`. # e.g. `sha256` and `something_countable_1`.
Naming/VariableNumber: Naming/VariableNumber:
@ -304,7 +308,7 @@ Sorbet/StrictSigil:
- "Homebrew/utils/ruby_check_version_script.rb" # A standalone script. - "Homebrew/utils/ruby_check_version_script.rb" # A standalone script.
- "Homebrew/{standalone,startup}/*.rb" # These are loaded before sorbet-runtime - "Homebrew/{standalone,startup}/*.rb" # These are loaded before sorbet-runtime
- "Homebrew/test/**/*.rb" - "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: Sorbet/TrueSigil:
Enabled: true Enabled: true

View File

@ -10,7 +10,13 @@ Homebrew/MoveToExtendOS:
- "{extend,test,requirements}/**/*" - "{extend,test,requirements}/**/*"
- "os.rb" - "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: inherit_mode:
merge: merge:
- AllowedMethods - AllowedMethods
@ -25,6 +31,7 @@ Style/Documentation:
- Homebrew - Homebrew
Include: Include:
- abstract_command.rb - abstract_command.rb
- autobump_constants.rb
- cask/cask.rb - cask/cask.rb
- cask/dsl.rb - cask/dsl.rb
- cask/dsl/version.rb - cask/dsl/version.rb

View File

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

View File

@ -39,12 +39,12 @@ class PATH
self self
end 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) def select(&block)
self.class.new(@paths.select(&block)) self.class.new(@paths.select(&block))
end 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) def reject(&block)
self.class.new(@paths.reject(&block)) self.class.new(@paths.reject(&block))
end end

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "api/analytics" require "api/analytics"
@ -11,10 +11,10 @@ module Homebrew
module API module API
extend Cachable extend Cachable
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze 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) def self.fetch(endpoint)
return cache[endpoint] if cache.present? && cache.key?(endpoint) return cache[endpoint] if cache.present? && cache.key?(endpoint)
@ -33,7 +33,8 @@ module Homebrew
end end
sig { 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, def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) 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 mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now
FileUtils.touch(target, mtime:) unless skip_download 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 rescue JSON::ParserError
target.unlink target.unlink
retry_count += 1 retry_count += 1
@ -122,14 +124,17 @@ module Homebrew
end end
end end
sig { params(json: Hash).returns(Hash) } sig {
def self.merge_variations(json) 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") return json unless json.key?("variations")
bottle_tag = ::Utils::Bottles::Tag.new(system: Homebrew::SimulateSystem.current_os, bottle_tag ||= Homebrew::SimulateSystem.current_tag
arch: Homebrew::SimulateSystem.current_arch)
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) json = json.merge(variation)
end end
@ -137,7 +142,7 @@ module Homebrew
end end
sig { params(names: T::Array[String], type: String, regenerate: T::Boolean).returns(T::Boolean) } 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" names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
if !names_path.exist? || regenerate if !names_path.exist? || regenerate
names_path.write(names.join("\n")) names_path.write(names.join("\n"))
@ -147,7 +152,10 @@ module Homebrew
false false
end 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) private_class_method def self.verify_and_parse_jws(json_data)
signatures = json_data["signatures"] signatures = json_data["signatures"]
homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" } homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "extend/cachable" require "cachable"
require "api/download" require "api/download"
module Homebrew module Homebrew
@ -12,6 +12,13 @@ module Homebrew
DEFAULT_API_FILENAME = "formula.jws.json" 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 private_class_method :cache
sig { params(name: String).returns(T::Hash[String, T.untyped]) } sig { params(name: String).returns(T::Hash[String, T.untyped]) }
@ -42,12 +49,12 @@ module Homebrew
sig { returns(Pathname) } sig { returns(Pathname) }
def self.cached_json_file_path def self.cached_json_file_path
HOMEBREW_CACHE_API/DEFAULT_API_FILENAME HOMEBREW_CACHE_API/api_filename
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def self.download_and_cache_data! 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["aliases"] = {}
cache["renames"] = {} cache["renames"] = {}
@ -122,7 +129,7 @@ module Homebrew
def self.write_names_and_aliases(regenerate: false) def self.write_names_and_aliases(regenerate: false)
download_and_cache_data! unless cache.key?("formulae") 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| (HOMEBREW_CACHE_API/"formula_aliases.txt").open("w") do |file|
all_aliases.each do |alias_name, real_name| 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 # frozen_string_literal: true
# Used to substitute common paths with generic placeholders when generating JSON for the API. # Used to substitute common paths with generic placeholders when generating JSON for the API.
module APIHashable module APIHashable
sig { void }
def generating_hash! def generating_hash!
return if generating_hash? return if generating_hash?
# Apply monkeypatches for API generation # Apply monkeypatches for API generation
@old_homebrew_prefix = HOMEBREW_PREFIX @old_homebrew_prefix = T.let(HOMEBREW_PREFIX, T.nilable(Pathname))
@old_homebrew_cellar = HOMEBREW_CELLAR @old_homebrew_cellar = T.let(HOMEBREW_CELLAR, T.nilable(Pathname))
@old_home = Dir.home @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.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER)) Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER))
ENV["HOME"] = HOMEBREW_HOME_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 end
sig { void }
def generated_hash! def generated_hash!
return unless generating_hash? return unless generating_hash?
@ -24,10 +28,12 @@ module APIHashable
Object.send(:remove_const, :HOMEBREW_PREFIX) Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix) Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix)
ENV["HOME"] = @old_home ENV["HOME"] = @old_home
ENV["GIT_CONFIG_GLOBAL"] = @old_git_config_global
@generating_hash = false @generating_hash = false
end end
sig { returns(T::Boolean) }
def generating_hash? def generating_hash?
@generating_hash ||= false @generating_hash ||= false
@generating_hash == true @generating_hash == true

View File

@ -52,4 +52,4 @@ FORMULA_COMPONENT_PRECEDENCE_LIST = T.let([
[{ name: :caveats, type: :method_definition }], [{ name: :caveats, type: :method_definition }],
[{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }], [{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }],
[{ name: :test, type: :block_call }], [{ 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) } sig { returns(T::Boolean) }
def self.enabled? def self.enabled?
return false if Homebrew::EnvConfig.no_verify_attestations? 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.verify_attestations?
(Homebrew::EnvConfig.developer? || Homebrew::EnvConfig.devcmdrun?) && GitHub::API.credentials.present?
end end
# Returns a path to a suitable `gh` executable for attestation verification. # Returns a path to a suitable `gh` executable for attestation verification.

View File

@ -1,10 +1,15 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
# TODO: add more reasons here 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({ NO_AUTOBUMP_REASONS_LIST = T.let({
incompatible_version_format: "incompatible version format", incompatible_version_format: "incompatible version format",
bumped_by_upstream: "bumped by upstream", bumped_by_upstream: "bumped by upstream",
extract_plist: "livecheck uses `:extract_plist` strategy", requires_manual_review: "a manual review of this package is required for inclusion in autobump",
latest_version: "`version` is set to `:latest`", }.merge(NO_AUTOBUMP_REASONS_INTERNAL).freeze, T::Hash[Symbol, String])
}.freeze, T::Hash[Symbol, String])

View File

@ -91,7 +91,7 @@ class Bottle
def fetch(verify_download_integrity: true, timeout: nil, quiet: false) def fetch(verify_download_integrity: true, timeout: nil, quiet: false)
resource.fetch(verify_download_integrity:, timeout:, quiet:) resource.fetch(verify_download_integrity:, timeout:, quiet:)
rescue DownloadError rescue DownloadError
raise unless fallback_on_error raise unless fallback_on_error?
fetch_tab fetch_tab
retry retry
@ -121,7 +121,7 @@ class Bottle
begin begin
resource.fetch(timeout:, quiet:) resource.fetch(timeout:, quiet:)
rescue DownloadError rescue DownloadError
raise unless fallback_on_error raise unless fallback_on_error?
retry retry
rescue Resource::BottleManifest::Error rescue Resource::BottleManifest::Error
@ -193,7 +193,7 @@ class Bottle
specs specs
end end
def fallback_on_error def fallback_on_error?
# Use the default bottle domain as a fallback mirror # Use the default bottle domain as a fallback mirror
if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) && if @resource.url.start_with?(Homebrew::EnvConfig.bottle_domain) &&
Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_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}" ]] if [[ -z "${GIT_REVISION}" ]]
then then
read -r GIT_HEAD 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/HEAD" 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 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" ]] elif [[ "${GIT_HEAD}" == "ref: refs/heads/stable" ]]
then then
read -r GIT_REVISION 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/refs/heads/stable" read -r GIT_REVISION 2>/dev/null <"${HOMEBREW_REPOSITORY}/.git/refs/heads/stable"
@ -600,6 +600,11 @@ case "$1" in
homebrew-version homebrew-version
exit 0 exit 0
;; ;;
mcp-server)
source "${HOMEBREW_LIBRARY}/Homebrew/cmd/mcp-server.sh"
homebrew-mcp-server "$@"
exit 0
;;
esac esac
# TODO: bump version when new macOS is released or announced and update references in: # TODO: bump version when new macOS is released or announced and update references in:
@ -609,6 +614,8 @@ esac
# and, if needed: # and, if needed:
# - MacOSVersion::SYMBOLS # - MacOSVersion::SYMBOLS
HOMEBREW_MACOS_NEWEST_UNSUPPORTED="16" 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: # TODO: bump version when new macOS is released and update references in:
# - docs/Installation.md # - docs/Installation.md
# - HOMEBREW_MACOS_OLDEST_SUPPORTED in .github/workflows/pkg-installer.yml # - 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
export HOMEBREW_MACOS_VERSION_NUMERIC export HOMEBREW_MACOS_VERSION_NUMERIC
export HOMEBREW_MACOS_NEWEST_UNSUPPORTED export HOMEBREW_MACOS_NEWEST_UNSUPPORTED
export HOMEBREW_MACOS_NEWEST_SUPPORTED
export HOMEBREW_MACOS_OLDEST_SUPPORTED export HOMEBREW_MACOS_OLDEST_SUPPORTED
export HOMEBREW_MACOS_OLDEST_ALLOWED export HOMEBREW_MACOS_OLDEST_ALLOWED
export HOMEBREW_USER_AGENT export HOMEBREW_USER_AGENT
@ -1078,6 +1086,22 @@ else
export HOMEBREW_GITHUB_PACKAGES_AUTH="Bearer QQ==" export HOMEBREW_GITHUB_PACKAGES_AUTH="Bearer QQ=="
fi 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}" ]] if [[ -n "${HOMEBREW_BASH_COMMAND}" ]]
then then
# source rather than executing directly to ensure the entire file is read into # 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) } sig { returns(T::Boolean) }
def mas_installed? def mas_installed?
@mas_installed ||= which_formula("mas") @mas_installed ||= which_formula?("mas")
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -59,7 +59,7 @@ module Homebrew
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def whalebrew_installed? def whalebrew_installed?
@whalebrew_installed ||= which_formula("whalebrew") @whalebrew_installed ||= which_formula?("whalebrew")
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -70,7 +70,7 @@ module Homebrew
end end
sig { params(name: String).returns(T::Boolean) } sig { params(name: String).returns(T::Boolean) }
def which_formula(name) def which_formula?(name)
formula = Formulary.factory(name) formula = Formulary.factory(name)
ENV["PATH"] = "#{formula.opt_bin}:#{ENV.fetch("PATH", nil)}" if formula.any_version_installed? ENV["PATH"] = "#{formula.opt_bin}:#{ENV.fetch("PATH", nil)}" if formula.any_version_installed?
which(name).present? which(name).present?
@ -130,6 +130,9 @@ module Homebrew
@formula_versions_from_env[formula_env_name] @formula_versions_from_env[formula_env_name]
end end
sig { void }
def prepend_pkgconf_path_if_needed!; end
sig { void } sig { void }
def reset! def reset!
@mas_installed = T.let(nil, T.nilable(T::Boolean)) @mas_installed = T.let(nil, T.nilable(T::Boolean))

View File

@ -1,7 +1,7 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
require "bundle/brew_installer" require "bundle/formula_installer"
module Homebrew module Homebrew
module Bundle module Bundle
@ -11,7 +11,7 @@ module Homebrew
PACKAGE_TYPE_NAME = "Formula" PACKAGE_TYPE_NAME = "Formula"
def installed_and_up_to_date?(formula, no_upgrade: false) 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 end
end end

View File

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

View File

@ -8,6 +8,7 @@ module Homebrew
@casks = nil @casks = nil
@cask_names = nil @cask_names = nil
@cask_hash = nil @cask_hash = nil
@cask_oldnames = nil
end end
def self.cask_names def self.cask_names
@ -38,6 +39,25 @@ module Homebrew
end.join("\n") end.join("\n")
end 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) def self.formula_dependencies(cask_list)
return [] unless Bundle.cask_installed? return [] unless Bundle.cask_installed?
return [] if cask_list.blank? return [] if cask_list.blank?

View File

@ -18,7 +18,7 @@ module Homebrew
Homebrew::Bundle::CaskDumper.cask_is_outdated_using_greedy?(name) Homebrew::Bundle::CaskDumper.cask_is_outdated_using_greedy?(name)
end 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) if installed_casks.include?(name) && !upgrading?(no_upgrade, name, options)
puts "Skipping install of #{name} cask. It is already installed." if verbose puts "Skipping install of #{name} cask. It is already installed." if verbose
return false return false
@ -27,7 +27,7 @@ module Homebrew
true true
end 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 return true unless preinstall
full_name = options.fetch(:full_name, name) full_name = options.fetch(:full_name, name)
@ -87,12 +87,25 @@ module Homebrew
!cask_upgradable?(cask) !cask_upgradable?(cask)
end 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) def self.cask_installed?(cask)
installed_casks.include? cask cask_in_array?(cask, installed_casks)
end end
def self.cask_upgradable?(cask) def self.cask_upgradable?(cask)
outdated_casks.include? cask cask_in_array?(cask, outdated_casks)
end end
def self.installed_casks def self.installed_casks

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
# typed: false # rubocop:todo Sorbet/TrueSigil # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "English" require "English"
@ -13,6 +13,16 @@ module Homebrew
module Exec module Exec
PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/ 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) def self.run(*args, global: false, file: nil, subcommand: "", services: false, check: false)
if check if check
require "bundle/commands/check" require "bundle/commands/check"
@ -25,9 +35,9 @@ module Homebrew
# Setup Homebrew's ENV extensions # Setup Homebrew's ENV extensions
ENV.activate_extensions! ENV.activate_extensions!
raise UsageError, "No command to execute was specified!" if args.blank?
command = args.first command = args.first
raise UsageError, "No command to execute was specified!" if command.blank?
require "bundle/brewfile" require "bundle/brewfile"
@dsl = Brewfile.read(global:, file:) @dsl = Brewfile.read(global:, file:)
@ -64,14 +74,8 @@ module Homebrew
ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims" ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims"
end end
# Setup pkg-config, if present, to help locate packages # Setup pkgconf, if needed, to help locate packages
# Only need this on Linux as Homebrew provides a shim on macOS Bundle.prepend_pkgconf_path_if_needed!
# 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
# For commands which aren't either absolute or relative # For commands which aren't either absolute or relative
# Add the command directory to PATH, since it may get blown away by superenv # Add the command directory to PATH, since it may get blown away by superenv
@ -170,15 +174,29 @@ module Homebrew
end end
end end
return 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 end
if services if services
require "bundle/brew_services" require "bundle/brew_services"
exit_code = 0 exit_code = T.let(0, Integer)
run_services(@dsl.entries) do run_services(@dsl.entries) do
Kernel.system(*args) Kernel.system(*args)
exit_code = $CHILD_STATUS.exitstatus if (system_exit_code = $CHILD_STATUS&.exitstatus)
exit_code = system_exit_code
end
end end
exit!(exit_code) exit!(exit_code)
else else
@ -191,7 +209,7 @@ module Homebrew
entries: T::Array[Homebrew::Bundle::Dsl::Entry], entries: T::Array[Homebrew::Bundle::Dsl::Entry],
_block: T.proc.params( _block: T.proc.params(
entry: Homebrew::Bundle::Dsl::Entry, entry: Homebrew::Bundle::Dsl::Entry,
info: T::Hash[String, T.anything], info: T::Hash[String, T.untyped],
service_file: Pathname, service_file: Pathname,
conflicting_services: T::Array[T::Hash[String, T.anything]], conflicting_services: T::Array[T::Hash[String, T.anything]],
).void, ).void,
@ -271,7 +289,7 @@ module Homebrew
map_service_info(entries) do |entry, info, service_file, conflicting_services| map_service_info(entries) do |entry, info, service_file, conflicting_services|
# Don't restart if already running this version # Don't restart if already running this version
loaded_file = Pathname.new(info["loaded_file"].to_s) 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) if info["running"] && !Bundle::BrewServices.stop(info["name"], keep: true)
opoo "Failed to stop #{info["name"]} service" 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, def self.run(global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false, force: false,
quiet: false) quiet: false)
@dsl = Brewfile.read(global:, file:) @dsl = Brewfile.read(global:, file:)
Homebrew::Bundle::Installer.install( Homebrew::Bundle::Installer.install!(
@dsl.entries, @dsl.entries,
global:, file:, no_lock:, no_upgrade:, verbose:, force:, quiet:, global:, file:, no_lock:, no_upgrade:, verbose:, force:, quiet:,
) || exit(1) ) || exit(1)

View File

@ -8,11 +8,11 @@ module Homebrew
module Bundle module Bundle
module Commands module Commands
module List 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 parsed_entries = Brewfile.read(global:, file:).entries
Homebrew::Bundle::Lister.list( Homebrew::Bundle::Lister.list(
parsed_entries, parsed_entries,
brews:, casks:, taps:, mas:, whalebrew:, vscode:, formulae:, casks:, taps:, mas:, whalebrew:, vscode:,
) )
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -4,14 +4,14 @@
module Homebrew module Homebrew
module Bundle module Bundle
module Lister 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| 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
end end
private_class_method def self.show?(type, brews:, casks:, taps:, mas:, whalebrew:, vscode:) private_class_method def self.show?(type, formulae:, casks:, taps:, mas:, whalebrew:, vscode:)
return true if brews && type == :brew return true if formulae && type == :brew
return true if casks && type == :cask return true if casks && type == :cask
return true if taps && type == :tap return true if taps && type == :tap
return true if mas && type == :mas return true if mas && type == :mas

View File

@ -11,7 +11,7 @@ module Homebrew
@outdated_app_ids = nil @outdated_app_ids = nil
end 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? unless Bundle.mas_installed?
puts "Installing mas. It is not currently installed." if verbose puts "Installing mas. It is not currently installed." if verbose
Bundle.brew("install", "mas", verbose:) Bundle.brew("install", "mas", verbose:)
@ -27,7 +27,7 @@ module Homebrew
true true
end 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 return true unless preinstall
if app_id_installed?(id) if app_id_installed?(id)

View File

@ -9,21 +9,8 @@ module Homebrew
class << self class << self
sig { params(entry: Dsl::Entry, silent: T::Boolean).returns(T::Boolean) } sig { params(entry: Dsl::Entry, silent: T::Boolean).returns(T::Boolean) }
def skip?(entry, silent: false) 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| return true if @failed_taps&.any? do |tap|
prefix = "#{tap}/" prefix = "#{tap}/"
entry.name.start_with?(prefix) || entry.options[:full_name]&.start_with?(prefix) entry.name.start_with?(prefix) || entry.options[:full_name]&.start_with?(prefix)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,14 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Cask module Cask
# Sorted set containing all cask artifacts. # Sorted set containing all cask artifacts.
class ArtifactSet < ::Set 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) def each(&block)
return enum_for(T.must(__method__)) { size } unless block return enum_for(T.must(__method__)) { size } unless block
@ -11,6 +16,7 @@ module Cask
self self
end end
sig { returns(T::Array[Artifact::AbstractArtifact]) }
def to_a def to_a
super.sort super.sort
end end

View File

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

View File

@ -11,17 +11,17 @@ module Cask
params( params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean), 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_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] any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).returns(T::Set[String]) ).returns(T::Set[String])
} }
def self.audit( def self.audit(
cask, audit_download: false, audit_online: nil, audit_strict: nil, audit_signing: nil, 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: [] only: [], except: []
) )
new( 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_new_cask:, quarantine:, any_named_args:, language:, only:, except:
).audit ).audit
end end
@ -36,7 +36,7 @@ module Cask
params( params(
cask: ::Cask::Cask, audit_download: T::Boolean, audit_online: T.nilable(T::Boolean), 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_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] any_named_args: T::Boolean, language: T.nilable(String), only: T::Array[String], except: T::Array[String]
).void ).void
} }
@ -46,7 +46,6 @@ module Cask
audit_online: nil, audit_online: nil,
audit_strict: nil, audit_strict: nil,
audit_signing: nil, audit_signing: nil,
audit_token_conflicts: nil,
audit_new_cask: nil, audit_new_cask: nil,
quarantine: false, quarantine: false,
any_named_args: false, any_named_args: false,
@ -61,7 +60,6 @@ module Cask
@audit_strict = audit_strict @audit_strict = audit_strict
@audit_signing = audit_signing @audit_signing = audit_signing
@quarantine = quarantine @quarantine = quarantine
@audit_token_conflicts = audit_token_conflicts
@any_named_args = any_named_args @any_named_args = any_named_args
@language = language @language = language
@only = only @only = only
@ -127,15 +125,14 @@ module Cask
def audit_cask_instance(cask) def audit_cask_instance(cask)
audit = Audit.new( audit = Audit.new(
cask, cask,
online: @audit_online, online: @audit_online,
strict: @audit_strict, strict: @audit_strict,
signing: @audit_signing, signing: @audit_signing,
new_cask: @audit_new_cask, new_cask: @audit_new_cask,
token_conflicts: @audit_token_conflicts, download: @audit_download,
download: @audit_download, quarantine: @quarantine,
quarantine: @quarantine, only: @only,
only: @only, except: @except,
except: @except,
) )
audit.run! audit.run!
end end

View File

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

View File

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

View File

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

View File

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

View File

@ -52,16 +52,17 @@ module Cask
raise "Only a single 'depends_on macos' is allowed." if defined?(@macos) raise "Only a single 'depends_on macos' is allowed." if defined?(@macos)
# workaround for https://github.com/sorbet/sorbet/issues/6860 # 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 begin
@macos = if args.count > 1 @macos = if args.count > 1
MacOSRequirement.new([args], comparator: "==") 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: "==") 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]) 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]) MacOSRequirement.new([md[:version]], comparator: md[:comparator])
# This is not duplicate of the first case: see `args.first` and a different comparator. # This is not duplicate of the first case: see `args.first` and a different comparator.
else # rubocop:disable Lint/DuplicateBranch else # rubocop:disable Lint/DuplicateBranch

View File

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

View File

@ -5,10 +5,24 @@ require "tab"
module Cask module Cask
class Tab < ::AbstractTab 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. # 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 = super
tab.tabfile = cask.metadata_main_container_path/FILENAME tab.tabfile = cask.metadata_main_container_path/FILENAME
@ -23,6 +37,7 @@ module Cask
# Returns a {Tab} for an already installed cask, # Returns a {Tab} for an already installed cask,
# or a fake one if the cask is not installed. # or a fake one if the cask is not installed.
sig { params(cask: Cask).returns(T.attached_class) }
def self.for_cask(cask) def self.for_cask(cask)
path = cask.metadata_main_container_path/FILENAME path = cask.metadata_main_container_path/FILENAME
@ -40,6 +55,7 @@ module Cask
tab tab
end end
sig { returns(T.attached_class) }
def self.empty def self.empty
tab = super tab = super
tab.uninstall_flight_blocks = false tab.uninstall_flight_blocks = false
@ -76,10 +92,12 @@ module Cask
runtime_deps runtime_deps
end end
sig { returns(T.nilable(String)) }
def version def version
source["version"] source["version"]
end end
sig { params(_args: T.untyped).returns(String) }
def to_json(*_args) def to_json(*_args)
attributes = { attributes = {
"homebrew_version" => homebrew_version, "homebrew_version" => homebrew_version,
@ -98,6 +116,7 @@ module Cask
JSON.pretty_generate(attributes) JSON.pretty_generate(attributes)
end end
sig { returns(String) }
def to_s def to_s
s = ["Installed"] s = ["Installed"]
s << "using the formulae.brew.sh API" if loaded_from_api 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), require_sha: T.nilable(T::Boolean),
).returns(T::Boolean) ).returns(T::Boolean)
} }
def self.upgrade_casks( def self.upgrade_casks!(
*casks, *casks,
args:, args:,
force: false, force: false,
@ -134,7 +134,7 @@ module Cask
return true if caught_exceptions.empty? return true if caught_exceptions.empty?
raise MultipleCaskErrors, caught_exceptions if caught_exceptions.count > 1 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 false
end end

View File

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

View File

@ -14,40 +14,54 @@ class Caveats
sig { params(formula: Formula).void } sig { params(formula: Formula).void }
def initialize(formula) def initialize(formula)
@formula = formula @formula = formula
@caveats = T.let(nil, T.nilable(String))
@completions_and_elisp = T.let(nil, T.nilable(T::Array[String]))
end end
sig { returns(String) } sig { returns(String) }
def caveats def caveats
caveats = [] @caveats ||= begin
build = formula.build caveats = []
begin build = formula.build
formula.build = Tab.for_formula(formula) begin
string = formula.caveats.to_s formula.build = Tab.for_formula(formula)
caveats << "#{string.chomp}\n" unless string.empty? string = formula.caveats.to_s
ensure caveats << "#{string.chomp}\n" unless string.empty?
formula.build = build ensure
formula.build = build
end
caveats << keg_only_text
caveats << service_caveats
caveats.compact.join("\n")
end end
caveats << keg_only_text
valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? &&
(shell_sym = current_shell.to_sym) &&
valid_shells.include?(shell_sym)
[shell_sym]
else
valid_shells
end
shells.each do |shell|
caveats << function_completion_caveats(shell)
end
caveats << service_caveats
caveats << elisp_caveats
caveats.compact.join("\n")
end end
delegate [:empty?, :to_s] => :caveats sig { returns(T::Boolean) }
def empty?
caveats.blank? && completions_and_elisp.blank?
end
delegate [:to_s] => :caveats
sig { returns(T::Array[String]) }
def completions_and_elisp
@completions_and_elisp ||= begin
valid_shells = [:bash, :zsh, :fish, :pwsh].freeze
current_shell = Utils::Shell.preferred || Utils::Shell.parent
shells = if current_shell.present? &&
(shell_sym = current_shell.to_sym) &&
valid_shells.include?(shell_sym)
[shell_sym]
else
valid_shells
end
completions_and_elisp = shells.map do |shell|
function_completion_caveats(shell)
end
completions_and_elisp << elisp_caveats
completions_and_elisp.compact
end
end
sig { params(skip_reason: T::Boolean).returns(T.nilable(String)) } sig { params(skip_reason: T::Boolean).returns(T.nilable(String)) }
def keg_only_text(skip_reason: false) def keg_only_text(skip_reason: false)
@ -180,7 +194,7 @@ class Caveats
startup = formula.service.requires_root? startup = formula.service.requires_root?
if Utils::Service.running?(formula) if Utils::Service.running?(formula)
s << "To restart #{formula.full_name} after an upgrade:" 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 elsif startup
s << "To start #{formula.full_name} now and restart at startup:" s << "To start #{formula.full_name} now and restart at startup:"
s << " sudo brew services start #{formula.full_name}" 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]) return false unless (name = basename.to_s[/\A(.*?)--/, 1])
cask = begin cask = begin
Cask::CaskLoader.load(name) Cask::CaskLoader.load(name, warn: false)
rescue Cask::CaskError rescue Cask::CaskError
nil nil
end end

View File

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

View File

@ -208,11 +208,15 @@ module Homebrew
return if global_switch return if global_switch
description = option_description(description, *names, hidden:) 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 if replacement || disable
description += " (#{disable ? "disabled" : "deprecated"}#{"; replaced by #{replacement}" if replacement})" description += " (#{disable ? "disabled" : "deprecated"}#{"; replaced by #{replacement}" if replacement})"
end end
process_option(*names, description, type: :switch, hidden:) unless disable
@parser.public_send(method, *names, *wrap_option_desc(description)) do |value| @parser.public_send(method, *names, *wrap_option_desc(description)) do |value|
# This odeprecated should stick around indefinitely. # This odeprecated should stick around indefinitely.

View File

@ -31,7 +31,7 @@ module Homebrew
sig { override.void } sig { override.void }
def run def run
ENV.activate_extensions! 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 ENV.setup_build_environment
shell = if args.plain? shell = if args.plain?

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ module Homebrew
cmd_args do cmd_args do
description <<~EOS description <<~EOS
Control Homebrew's developer mode. When developer mode is enabled, 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. branch instead of the latest stable version along with some other behaviour changes.
`brew developer` [`state`]: `brew developer` [`state`]:
@ -38,7 +38,7 @@ module Homebrew
puts "However, `brew update` will update to the latest stable tag because " \ puts "However, `brew update` will update to the latest stable tag because " \
"#{Tty.bold}HOMEBREW_UPDATE_TO_TAG#{Tty.reset} is set." "#{Tty.bold}HOMEBREW_UPDATE_TO_TAG#{Tty.reset} is set."
else 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 end
else else
puts "`brew update` will update to the latest stable tag." 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 # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "formula" require "formula"
require "fetch" require "fetch"
require "cask/download" require "cask/download"
require "retryable_download" require "download_queue"
module Homebrew module Homebrew
module Cmd module Cmd
@ -69,49 +69,6 @@ module Homebrew
named_args [:formula, :cask], min: 1 named_args [:formula, :cask], min: 1
end 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 } sig { override.void }
def run def run
Formulary.enable_factory_cache! Formulary.enable_factory_cache!
@ -136,7 +93,7 @@ module Homebrew
bucket.each do |formula_or_cask| bucket.each do |formula_or_cask|
case formula_or_cask case formula_or_cask
when Formula when Formula
formula = T.cast(formula_or_cask, Formula) formula = formula_or_cask
ref = formula.loaded_from_api? ? formula.full_name : formula.path ref = formula.loaded_from_api? ? formula.full_name : formula.path
os_arch_combinations.each do |os, arch| os_arch_combinations.each do |os, arch|
@ -171,9 +128,9 @@ module Homebrew
end end
if (manifest_resource = bottle.github_packages_manifest_resource) if (manifest_resource = bottle.github_packages_manifest_resource)
fetch_downloadable(manifest_resource) download_queue.enqueue(manifest_resource)
end end
fetch_downloadable(bottle) download_queue.enqueue(bottle)
rescue Interrupt rescue Interrupt
raise raise
rescue => e rescue => e
@ -189,14 +146,16 @@ module Homebrew
next if fetched_bottle next if fetched_bottle
fetch_downloadable(formula.resource) if (resource = formula.resource)
download_queue.enqueue(resource)
formula.resources.each do |r|
fetch_downloadable(r)
r.patches.each { |patch| fetch_downloadable(patch.resource) if patch.external? }
end 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
end end
else else
@ -216,131 +175,34 @@ module Homebrew
quarantine = true if quarantine.nil? quarantine = true if quarantine.nil?
download = Cask::Download.new(cask, quarantine:) download = Cask::Download.new(cask, quarantine:)
fetch_downloadable(download) download_queue.enqueue(download)
end end
end end
end end
end end
if concurrency == 1 download_queue.start
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
ensure ensure
download_queue.shutdown download_queue.shutdown
end end
private private
def downloads sig { returns(Integer) }
@downloads ||= {} def concurrency
@concurrency ||= T.let(args.concurrency&.to_i || 1, T.nilable(Integer))
end end
def fetch_downloadable(downloadable) sig { returns(Integer) }
downloads[downloadable] ||= begin def retries
tries = args.retry? ? {} : { tries: 1 } @retries ||= T.let(args.retry? ? FETCH_MAX_TRIES : 0, T.nilable(Integer))
download_queue.enqueue(RetryableDownload.new(downloadable, **tries), force: args.force?) end
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 end
end end

View File

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

View File

@ -33,8 +33,8 @@ module Homebrew
description: "If brewing fails, open an interactive debugging session with access to IRB " \ description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory." "or a shell inside the temporary build directory."
switch "--display-times", 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", switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \ description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \ "non-migrated versions. When installing casks, overwrite existing files " \
@ -44,9 +44,9 @@ module Homebrew
switch "-n", "--dry-run", switch "-n", "--dry-run",
description: "Show what would be installed, but do not actually install anything." description: "Show what would be installed, but do not actually install anything."
switch "--ask", switch "--ask",
env: :ask,
description: "Ask for confirmation before downloading and installing formulae. " \ 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", { [:switch, "--formula", "--formulae", {
description: "Treat all named arguments as formulae.", description: "Treat all named arguments as formulae.",
@ -259,7 +259,7 @@ module Homebrew
if !Homebrew::EnvConfig.no_install_upgrade? && installed_casks.any? if !Homebrew::EnvConfig.no_install_upgrade? && installed_casks.any?
require "cask/upgrade" require "cask/upgrade"
Cask::Upgrade.upgrade_casks( Cask::Upgrade.upgrade_casks!(
*installed_casks, *installed_casks,
force: args.force?, force: args.force?,
dry_run: args.dry_run?, dry_run: args.dry_run?,
@ -310,9 +310,7 @@ module Homebrew
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
Install.check_cc_argv(args.cc) Install.check_cc_argv(args.cc)
Install.ask_formulae(installed_formulae, args: args) if args.ask? formulae_installer = Install.formula_installers(
Install.install_formulae(
installed_formulae, installed_formulae,
installed_on_request: !args.as_dependency?, installed_on_request: !args.as_dependency?,
installed_as_dependency: args.as_dependency?, installed_as_dependency: args.as_dependency?,
@ -338,9 +336,10 @@ module Homebrew
skip_link: args.skip_link?, skip_link: args.skip_link?,
) )
Upgrade.check_installed_dependents( dependants = Upgrade.dependants(
installed_formulae, installed_formulae,
flags: args.flags_only, flags: args.flags_only,
ask: args.ask?,
installed_on_request: !args.as_dependency?, installed_on_request: !args.as_dependency?,
force_bottle: args.force_bottle?, force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae, build_from_source_formulae: args.build_from_source_formulae,
@ -354,6 +353,28 @@ module Homebrew
dry_run: args.dry_run?, 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?) Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?) 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 version = Keg.new(path).version
major_version = version.major.to_i major_version = version.major.to_i
minor_version = version.minor.to_i || 0 minor_version = version.minor.to_i
patch_version = version.patch.to_i || 0 patch_version = version.patch.to_i
minor_version_range, patch_version_range = if Homebrew::EnvConfig.env_sync_strict? minor_version_range, patch_version_range = if Homebrew::EnvConfig.env_sync_strict?
# Only create symlinks for the exact installed patch version. # 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." description: "Show options for formulae that are currently installed."
switch "--eval-all", switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show their " \ description: "Evaluate all available formulae and casks, whether installed or not, to show their " \
"options." "options.",
env: :eval_all
flag "--command=", flag "--command=",
description: "Show options for the specified <command>." description: "Show options for the specified <command>."
@ -29,10 +30,10 @@ module Homebrew
sig { override.void } sig { override.void }
def run def run
all = args.eval_all? eval_all = args.eval_all?
if all if eval_all
puts_options(Formula.all(eval_all: args.eval_all?).sort) puts_options(Formula.all(eval_all:).sort)
elsif args.installed? elsif args.installed?
puts_options(Formula.installed.sort) puts_options(Formula.installed.sort)
elsif args.command.present? 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 " \ "formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
"updates when a new stable or development version has been released." "updates when a new stable or development version has been released."
switch "-g", "--greedy", 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", switch "--greedy-latest",
description: "Also include outdated casks including those with `version :latest`." description: "Also include outdated casks including those with `version :latest`."
switch "--greedy-auto-updates", switch "--greedy-auto-updates",
description: "Also include outdated casks including those with `auto_updates true`." description: "Also include outdated casks including those with `auto_updates true`."

View File

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

View File

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