Merge branch 'master' into master

This commit is contained in:
Thibaut Hérault 2025-06-17 20:49:18 -04:00 committed by GitHub
commit 27a040cc32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
112 changed files with 2281 additions and 1511 deletions

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"]

View File

@ -1,18 +1,19 @@
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
paths:
- '.github/workflows/*.ya?ml'
- 'Formula/a/actionlint.rb'
- 'Formula/s/shellcheck.rb'
- 'Formula/z/zizmor.rb'
pull_request: pull_request:
paths: paths:
- '.github/workflows/*.ya?ml' - '.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 +23,23 @@ 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
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 +48,42 @@ 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
env: with:
HOMEBREW_REPOSITORY: ${{ steps.setup-homebrew.outputs.repository-path }} persist-credentials: false
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: zizmor --format sarif . > results.sarif
- 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 +96,7 @@ jobs:
path: results.sarif path: results.sarif
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0

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,8 +165,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: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

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@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.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,7 @@ 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: 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
@ -251,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: >
@ -259,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@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.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

@ -0,0 +1,63 @@
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: 0
persist-credentials: true
- name: Setup target branch
run: |
git checkout "${TARGET_BRANCH}" || git checkout -b "${TARGET_BRANCH}"
git reset --hard "origin/${SOURCE_BRANCH}"
env:
SOURCE_BRANCH: ${{ steps.branches.outputs.source }}
TARGET_BRANCH: ${{ steps.branches.outputs.target }}
- name: Push target branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
run: git push origin "${TARGET_BRANCH}" --force-with-lease
env:
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
@ -84,7 +85,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
@ -135,11 +136,12 @@ 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:
# TODO: switch to main when we're pushing those images
image: ghcr.io/homebrew/brew:master image: ghcr.io/homebrew/brew:master
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
@ -162,7 +164,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: true cask: true
@ -185,14 +187,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
@ -220,7 +222,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
@ -255,7 +257,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:
# 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
@ -355,6 +357,7 @@ jobs:
container: ghcr.io/homebrew/ubuntu24.04:latest container: ghcr.io/homebrew/ubuntu24.04:latest
- name: test-bot (Linux x86_64) - name: test-bot (Linux x86_64)
runs-on: ubuntu-latest runs-on: ubuntu-latest
# TODO: switch to main when we've migrated to it
container: ghcr.io/homebrew/ubuntu22.04:master container: ghcr.io/homebrew/ubuntu22.04:master
# Use Debian Old Stable for testing Homebrew's glibc support. # Use Debian Old Stable for testing Homebrew's glibc support.
- name: test-bot (Linux Homebrew glibc) - name: test-bot (Linux Homebrew glibc)
@ -402,7 +405,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
@ -442,7 +445,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
@ -496,7 +499,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
- 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:

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",

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,8 +124,11 @@ module Homebrew
end end
end end
sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) } sig {
def self.merge_variations(json, bottle_tag: nil) 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 ||= Homebrew::SimulateSystem.current_tag bottle_tag ||= Homebrew::SimulateSystem.current_tag
@ -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,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cachable" require "cachable"
@ -21,7 +21,7 @@ module Homebrew
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
@ -47,6 +47,7 @@ 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/api_filename HOMEBREW_CACHE_API/api_filename
end end
@ -70,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!

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

@ -614,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
@ -841,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

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

@ -19,7 +19,6 @@ module Homebrew
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

@ -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

@ -653,11 +653,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 do 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
@ -701,45 +702,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
@ -772,10 +781,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|
@ -786,13 +795,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

@ -295,7 +295,7 @@ module Cask
sig { returns(Pathname) } sig { returns(Pathname) }
attr_reader :path attr_reader :path
sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.anything])) } sig { returns(T.nilable(T::Hash[String, T.untyped])) }
attr_reader :from_json attr_reader :from_json
sig { sig {
@ -320,7 +320,7 @@ module Cask
sig { sig {
params( params(
token: String, token: String,
from_json: T.nilable(T::Hash[T.any(String, Symbol), T.anything]), from_json: T.nilable(T::Hash[String, T.untyped]),
path: T.nilable(Pathname), path: T.nilable(Pathname),
).void ).void
} }

View File

@ -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,27 +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)
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -216,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
@ -476,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.

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"
@ -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,

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"
@ -6,6 +6,7 @@ require "formula"
require "fetch" require "fetch"
require "cask/download" require "cask/download"
require "retryable_download" require "retryable_download"
require "download_queue"
module Homebrew module Homebrew
module Cmd module Cmd
@ -69,15 +70,16 @@ module Homebrew
named_args [:formula, :cask], min: 1 named_args [:formula, :cask], min: 1
end end
sig { returns(Integer) }
def concurrency def concurrency
@concurrency ||= args.concurrency&.to_i || 1 @concurrency ||= T.let(args.concurrency&.to_i || 1, T.nilable(Integer))
end end
sig { returns(DownloadQueue) }
def download_queue def download_queue
@download_queue ||= begin @download_queue ||= T.let(begin
require "download_queue"
DownloadQueue.new(concurrency) DownloadQueue.new(concurrency)
end end, T.nilable(DownloadQueue))
end end
class Spinner class Spinner
@ -96,8 +98,8 @@ module Homebrew
sig { void } sig { void }
def initialize def initialize
@start = Time.now @start = T.let(Time.now, Time)
@i = 0 @i = T.let(0, Integer)
end end
sig { returns(String) } sig { returns(String) }
@ -136,7 +138,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|
@ -189,7 +191,9 @@ module Homebrew
next if fetched_bottle next if fetched_bottle
fetch_downloadable(formula.resource) if (resource = formula.resource)
fetch_downloadable(resource)
end
formula.resources.each do |r| formula.resources.each do |r|
fetch_downloadable(r) fetch_downloadable(r)
@ -231,7 +235,7 @@ module Homebrew
end end
else else
spinner = Spinner.new spinner = Spinner.new
remaining_downloads = downloads.dup remaining_downloads = downloads.dup.to_a
previous_pending_line_count = 0 previous_pending_line_count = 0
begin begin
@ -332,10 +336,13 @@ module Homebrew
private private
sig { returns(T::Hash[T.any(Resource, Bottle, Cask::Download), Concurrent::Promises::Future]) }
def downloads def downloads
@downloads ||= {} @downloads ||= T.let({}, T.nilable(T::Hash[T.any(Resource, Bottle, Cask::Download),
Concurrent::Promises::Future]))
end end
sig { params(downloadable: T.any(Resource, Bottle, Cask::Download)).void }
def fetch_downloadable(downloadable) def fetch_downloadable(downloadable)
downloads[downloadable] ||= begin downloads[downloadable] ||= begin
tries = args.retry? ? {} : { tries: 1 } tries = args.retry? ? {} : { tries: 1 }

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
@ -96,14 +96,17 @@ module Homebrew
end end
print_analytics print_analytics
elsif args.json elsif (json = args.json)
all = args.eval_all? all = args.eval_all?
print_json(all) print_json(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 +114,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 +179,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,11 +192,11 @@ module Homebrew
version_hash[version] version_hash[version]
end end
sig { params(all: T::Boolean).void } sig { params(json: T.any(T::Boolean, String), all: T::Boolean).void }
def print_json(all) def print_json(json, all)
raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named? raise FormulaOrCaskUnspecifiedError if !(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?
@ -240,25 +245,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 +367,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 +379,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 +388,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

@ -63,6 +63,8 @@ module Homebrew
puts info puts info
else else
info = "" info = ""
default_branches = %w[main master].freeze
taps.each_with_index do |tap, i| taps.each_with_index do |tap, i|
puts unless i.zero? puts unless i.zero?
info = "#{tap}: " info = "#{tap}: "
@ -79,7 +81,7 @@ module Homebrew
info += "\norigin: #{tap.remote}" if tap.remote != tap.default_remote info += "\norigin: #{tap.remote}" if tap.remote != tap.default_remote
info += "\nHEAD: #{tap.git_head || "(none)"}" info += "\nHEAD: #{tap.git_head || "(none)"}"
info += "\nlast commit: #{tap.git_last_commit || "never"}" info += "\nlast commit: #{tap.git_last_commit || "never"}"
info += "\nbranch: #{tap.git_branch || "(none)"}" if tap.git_branch != "master" info += "\nbranch: #{tap.git_branch || "(none)"}" if default_branches.exclude?(tap.git_branch)
else else
info += "Not installed" info += "Not installed"
end end

View File

@ -33,8 +33,6 @@ module Homebrew
"integration.", "integration.",
replacement: false, replacement: false,
disable: true disable: true
switch "--[no-]force-auto-update",
hidden: true
switch "--custom-remote", switch "--custom-remote",
description: "Install or change a tap with a custom remote. Useful for mirrors." description: "Install or change a tap with a custom remote. Useful for mirrors."
switch "--repair", switch "--repair",

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"
@ -39,13 +39,15 @@ module Homebrew
private private
sig { void }
def auto_update_header def auto_update_header
@auto_update_header ||= begin @auto_update_header ||= T.let(begin
ohai "Auto-updated Homebrew!" if args.auto_update? ohai "Auto-updated Homebrew!" if args.auto_update?
true true
end end, T.nilable(T::Boolean))
end end
sig { void }
def output_update_report def output_update_report
# Run `brew update` (again) if we've got a linuxbrew-core CoreTap # Run `brew update` (again) if we've got a linuxbrew-core CoreTap
if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? && if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? &&
@ -293,14 +295,17 @@ module Homebrew
EOS EOS
end end
sig { returns(String) }
def no_changes_message def no_changes_message
"No changes to formulae or casks." "No changes to formulae or casks."
end end
sig { params(revision: String).returns(String) }
def shorten_revision(revision) def shorten_revision(revision)
Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp
end end
sig { void }
def tap_or_untap_core_taps_if_necessary def tap_or_untap_core_taps_if_necessary
return if ENV["HOMEBREW_UPDATE_TEST"] return if ENV["HOMEBREW_UPDATE_TEST"]
@ -320,10 +325,11 @@ module Homebrew
return if (HOMEBREW_PREFIX/".homebrewdocker").exist? return if (HOMEBREW_PREFIX/".homebrewdocker").exist?
tap_output_header_printed = T.let(false, T::Boolean) tap_output_header_printed = T.let(false, T::Boolean)
default_branches = %w[main master].freeze
[CoreTap.instance, CoreCaskTap.instance].each do |tap| [CoreTap.instance, CoreCaskTap.instance].each do |tap|
next unless tap.installed? next unless tap.installed?
if tap.git_branch == "master" && if default_branches.include?(tap.git_branch) &&
(Date.parse(T.must(tap.git_repository.last_commit_date)) <= Date.today.prev_month) (Date.parse(T.must(tap.git_repository.last_commit_date)) <= Date.today.prev_month)
ohai "#{tap.name} is old and unneeded, untapping to save space..." ohai "#{tap.name} is old and unneeded, untapping to save space..."
tap.uninstall tap.uninstall
@ -339,6 +345,7 @@ module Homebrew
end end
end end
sig { params(repository: Pathname).void }
def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY) def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY)
command = "brew update" command = "brew update"
Utils::Link.link_completions(repository, command) Utils::Link.link_completions(repository, command)
@ -351,10 +358,12 @@ module Homebrew
EOS EOS
end end
sig { void }
def migrate_gcc_dependents_if_needed def migrate_gcc_dependents_if_needed
# do nothing # do nothing
end end
sig { void }
def analytics_message def analytics_message
return if Utils::Analytics.messages_displayed? return if Utils::Analytics.messages_displayed?
return if Utils::Analytics.no_message_output? return if Utils::Analytics.no_message_output?
@ -384,6 +393,7 @@ module Homebrew
Utils::Analytics.messages_displayed! if $stdout.tty? Utils::Analytics.messages_displayed! if $stdout.tty?
end end
sig { void }
def donation_message def donation_message
return if Settings.read("donationmessage") == "true" return if Settings.read("donationmessage") == "true"
@ -394,6 +404,7 @@ module Homebrew
Settings.write "donationmessage", true if $stdout.tty? Settings.write "donationmessage", true if $stdout.tty?
end end
sig { void }
def install_from_api_message def install_from_api_message
return if Settings.read("installfromapimessage") == "true" return if Settings.read("installfromapimessage") == "true"
@ -418,30 +429,38 @@ require "extend/os/cmd/update-report"
class Reporter class Reporter
class ReporterRevisionUnsetError < RuntimeError class ReporterRevisionUnsetError < RuntimeError
sig { params(var_name: String).void }
def initialize(var_name) def initialize(var_name)
super "#{var_name} is unset!" super "#{var_name} is unset!"
end end
end end
sig {
params(tap: Tap, api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname),
api_dir_prefix: T.nilable(Pathname)).void
}
def initialize(tap, api_names_txt: nil, api_names_before_txt: nil, api_dir_prefix: nil) def initialize(tap, api_names_txt: nil, api_names_before_txt: nil, api_dir_prefix: nil)
@tap = tap @tap = tap
# This is slightly involved/weird but all the #report logic is shared so it's worth it. # This is slightly involved/weird but all the #report logic is shared so it's worth it.
if installed_from_api?(api_names_txt, api_names_before_txt, api_dir_prefix) if installed_from_api?(api_names_txt, api_names_before_txt, api_dir_prefix)
@api_names_txt = api_names_txt @api_names_txt = T.let(api_names_txt, T.nilable(Pathname))
@api_names_before_txt = api_names_before_txt @api_names_before_txt = T.let(api_names_before_txt, T.nilable(Pathname))
@api_dir_prefix = api_dir_prefix @api_dir_prefix = T.let(api_dir_prefix, T.nilable(Pathname))
else else
initial_revision_var = "HOMEBREW_UPDATE_BEFORE#{tap.repository_var_suffix}" initial_revision_var = "HOMEBREW_UPDATE_BEFORE#{tap.repository_var_suffix}"
@initial_revision = ENV[initial_revision_var].to_s @initial_revision = T.let(ENV[initial_revision_var].to_s, String)
raise ReporterRevisionUnsetError, initial_revision_var if @initial_revision.empty? raise ReporterRevisionUnsetError, initial_revision_var if @initial_revision.empty?
current_revision_var = "HOMEBREW_UPDATE_AFTER#{tap.repository_var_suffix}" current_revision_var = "HOMEBREW_UPDATE_AFTER#{tap.repository_var_suffix}"
@current_revision = ENV[current_revision_var].to_s @current_revision = T.let(ENV[current_revision_var].to_s, String)
raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty? raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty?
end end
@report = T.let(nil, T.nilable(T::Hash[Symbol, T::Array[String]]))
end end
sig { params(auto_update: T::Boolean).returns(T::Hash[Symbol, T::Array[String]]) }
def report(auto_update: false) def report(auto_update: false)
return @report if @report return @report if @report
@ -482,9 +501,9 @@ class Reporter
case status case status
when "A", "D" when "A", "D"
full_name = tap.formula_file_to_name(src) full_name = tap.formula_file_to_name(src)
name = full_name.split("/").last name = T.must(full_name.split("/").last)
new_tap = tap.tap_migrations[name] new_tap = tap.tap_migrations[name]
@report[status.to_sym] << full_name unless new_tap @report[T.must(status).to_sym] << full_name unless new_tap
when "M" when "M"
name = tap.formula_file_to_name(src) name = tap.formula_file_to_name(src)
@ -584,6 +603,7 @@ class Reporter
@report @report
end end
sig { returns(T::Boolean) }
def updated? def updated?
if installed_from_api? if installed_from_api?
diff.present? diff.present?
@ -592,9 +612,10 @@ class Reporter
end end
end end
sig { void }
def migrate_tap_migration def migrate_tap_migration
(report[:D] + report[:DC]).each do |full_name| (Array(report[:D]) + Array(report[:DC])).each do |full_name|
name = full_name.split("/").last name = T.must(full_name.split("/").last)
new_tap_name = tap.tap_migrations[name] new_tap_name = tap.tap_migrations[name]
next if new_tap_name.nil? # skip if not in tap_migrations list. next if new_tap_name.nil? # skip if not in tap_migrations list.
@ -609,7 +630,7 @@ class Reporter
end end
# This means it is a cask # This means it is a cask
if report[:DC].include? full_name if Array(report[:DC]).include? full_name
next unless (HOMEBREW_PREFIX/"Caskroom"/new_name).exist? next unless (HOMEBREW_PREFIX/"Caskroom"/new_name).exist?
new_tap = Tap.fetch(new_tap_name) new_tap = Tap.fetch(new_tap_name)
@ -675,12 +696,14 @@ class Reporter
end end
end end
sig { void }
def migrate_cask_rename def migrate_cask_rename
Cask::Caskroom.casks.each do |cask| Cask::Caskroom.casks.each do |cask|
Cask::Migrator.migrate_if_needed(cask) Cask::Migrator.migrate_if_needed(cask)
end end
end end
sig { params(force: T::Boolean, verbose: T::Boolean).void }
def migrate_formula_rename(force:, verbose:) def migrate_formula_rename(force:, verbose:)
Formula.installed.each do |formula| Formula.installed.each do |formula|
next unless Migrator.needs_migration?(formula) next unless Migrator.needs_migration?(formula)
@ -704,14 +727,36 @@ class Reporter
private private
attr_reader :tap, :initial_revision, :current_revision, :api_names_txt, :api_names_before_txt, :api_dir_prefix sig { returns(Tap) }
attr_reader :tap
sig { returns(String) }
attr_reader :initial_revision
sig { returns(String) }
attr_reader :current_revision
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_names_before_txt
sig { returns(T.nilable(Pathname)) }
attr_reader :api_dir_prefix
sig {
params(api_names_txt: T.nilable(Pathname), api_names_before_txt: T.nilable(Pathname),
api_dir_prefix: T.nilable(Pathname)).returns(T::Boolean)
}
def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt, def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt,
api_dir_prefix = @api_dir_prefix) api_dir_prefix = @api_dir_prefix)
!api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil? !api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil?
end end
sig { returns(String) }
def diff def diff
@diff ||= T.let(nil, T.nilable(String))
@diff ||= if installed_from_api? @diff ||= if installed_from_api?
# Hack `git diff` output with regexes to look like `git diff-tree` output. # Hack `git diff` output with regexes to look like `git diff-tree` output.
# Yes, I know this is a bit filthy but it saves duplicating the #report logic. # Yes, I know this is a bit filthy but it saves duplicating the #report logic.
@ -719,12 +764,14 @@ class Reporter
header_regex = /^(---|\+\+\+) / header_regex = /^(---|\+\+\+) /
add_delete_characters = ["+", "-"].freeze add_delete_characters = ["+", "-"].freeze
api_dir_prefix_basename = T.must(api_dir_prefix).basename
diff_output.lines.filter_map do |line| diff_output.lines.filter_map do |line|
next if line.match?(header_regex) next if line.match?(header_regex)
next unless add_delete_characters.include?(line[0]) next unless add_delete_characters.include?(line[0])
line.sub(/^\+/, "A #{api_dir_prefix.basename}/") line.sub(/^\+/, "A #{api_dir_prefix_basename}/")
.sub(/^-/, "D #{api_dir_prefix.basename}/") .sub(/^-/, "D #{api_dir_prefix_basename}/")
.sub(/$/, ".rb") .sub(/$/, ".rb")
.chomp .chomp
end.join("\n") end.join("\n")
@ -738,28 +785,33 @@ class Reporter
end end
class ReporterHub class ReporterHub
sig { returns(T::Array[Reporter]) }
attr_reader :reporters attr_reader :reporters
sig { void } sig { void }
def initialize def initialize
@hash = {} @hash = T.let({}, T::Hash[Symbol, T::Array[String]])
@reporters = [] @reporters = T.let([], T::Array[Reporter])
end end
sig { params(key: Symbol).returns(T::Array[String]) }
def select_formula_or_cask(key) def select_formula_or_cask(key)
@hash.fetch(key, []) @hash.fetch(key, [])
end end
sig { params(reporter: Reporter, auto_update: T::Boolean).void }
def add(reporter, auto_update: false) def add(reporter, auto_update: false)
@reporters << reporter @reporters << reporter
report = reporter.report(auto_update:).delete_if { |_k, v| v.empty? } report = reporter.report(auto_update:).delete_if { |_k, v| v.empty? }
@hash.update(report) { |_key, oldval, newval| oldval.concat(newval) } @hash.update(report) { |_key, oldval, newval| oldval.concat(newval) }
end end
sig { returns(T::Boolean) }
def empty? def empty?
@hash.empty? @hash.empty?
end end
sig { params(auto_update: T::Boolean).void }
def dump(auto_update: false) def dump(auto_update: false)
unless Homebrew::EnvConfig.no_update_report_new? unless Homebrew::EnvConfig.no_update_report_new?
dump_new_formula_report dump_new_formula_report
@ -814,12 +866,14 @@ class ReporterHub
private private
sig { void }
def dump_new_formula_report def dump_new_formula_report
formulae = select_formula_or_cask(:A).sort.reject { |name| installed?(name) } formulae = select_formula_or_cask(:A).sort.reject { |name| installed?(name) }
output_dump_formula_or_cask_report "New Formulae", formulae output_dump_formula_or_cask_report "New Formulae", formulae
end end
sig { void }
def dump_new_cask_report def dump_new_cask_report
return if Homebrew::SimulateSystem.simulating_or_running_on_linux? return if Homebrew::SimulateSystem.simulating_or_running_on_linux?
@ -830,6 +884,7 @@ class ReporterHub
output_dump_formula_or_cask_report "New Casks", casks output_dump_formula_or_cask_report "New Casks", casks
end end
sig { void }
def dump_deleted_formula_report def dump_deleted_formula_report
formulae = select_formula_or_cask(:D).sort.filter_map do |name| formulae = select_formula_or_cask(:D).sort.filter_map do |name|
pretty_uninstalled(name) if installed?(name) pretty_uninstalled(name) if installed?(name)
@ -838,37 +893,43 @@ class ReporterHub
output_dump_formula_or_cask_report "Deleted Installed Formulae", formulae output_dump_formula_or_cask_report "Deleted Installed Formulae", formulae
end end
sig { void }
def dump_deleted_cask_report def dump_deleted_cask_report
return if Homebrew::SimulateSystem.simulating_or_running_on_linux? return if Homebrew::SimulateSystem.simulating_or_running_on_linux?
casks = select_formula_or_cask(:DC).sort.filter_map do |name| casks = select_formula_or_cask(:DC).sort.filter_map do |name|
name = name.split("/").last name = T.must(name.split("/").last)
pretty_uninstalled(name) if cask_installed?(name) pretty_uninstalled(name) if cask_installed?(name)
end end
output_dump_formula_or_cask_report "Deleted Installed Casks", casks output_dump_formula_or_cask_report "Deleted Installed Casks", casks
end end
sig { params(title: String, formulae_or_casks: T::Array[String]).void }
def output_dump_formula_or_cask_report(title, formulae_or_casks) def output_dump_formula_or_cask_report(title, formulae_or_casks)
return if formulae_or_casks.blank? return if formulae_or_casks.blank?
ohai title, Formatter.columns(formulae_or_casks.sort) ohai title, Formatter.columns(formulae_or_casks.sort)
end end
sig { params(formula: String).returns(T::Boolean) }
def installed?(formula) def installed?(formula)
(HOMEBREW_CELLAR/formula.split("/").last).directory? (HOMEBREW_CELLAR/formula.split("/").last).directory?
end end
sig { params(formula: String).returns(T::Boolean) }
def outdated?(formula) def outdated?(formula)
Formula[formula].outdated? Formula[formula].outdated?
rescue FormulaUnavailableError rescue FormulaUnavailableError
false false
end end
sig { params(cask: String).returns(T::Boolean) }
def cask_installed?(cask) def cask_installed?(cask)
(Cask::Caskroom.path/cask).directory? (Cask::Caskroom.path/cask).directory?
end end
sig { params(cask: String).returns(T::Boolean) }
def cask_outdated?(cask) def cask_outdated?(cask)
Cask::CaskLoader.load(cask).outdated? Cask::CaskLoader.load(cask).outdated?
rescue Cask::CaskError rescue Cask::CaskError

View File

@ -54,9 +54,10 @@ git_init_if_necessary() {
fi fi
git config remote.origin.url "${HOMEBREW_BREW_GIT_REMOTE}" git config remote.origin.url "${HOMEBREW_BREW_GIT_REMOTE}"
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git config fetch.prune true
git fetch --force --tags origin git fetch --force --tags origin
git remote set-head origin --auto >/dev/null git remote set-head origin --auto >/dev/null
git reset --hard origin/master git reset --hard origin/HEAD
SKIP_FETCH_BREW_REPOSITORY=1 SKIP_FETCH_BREW_REPOSITORY=1
set +e set +e
trap - EXIT trap - EXIT
@ -77,9 +78,10 @@ git_init_if_necessary() {
fi fi
git config remote.origin.url "${HOMEBREW_CORE_GIT_REMOTE}" git config remote.origin.url "${HOMEBREW_CORE_GIT_REMOTE}"
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --force origin refs/heads/master:refs/remotes/origin/master git config fetch.prune true
git fetch --force origin
git remote set-head origin --auto >/dev/null git remote set-head origin --auto >/dev/null
git reset --hard origin/master git reset --hard origin/HEAD
SKIP_FETCH_CORE_REPOSITORY=1 SKIP_FETCH_CORE_REPOSITORY=1
set +e set +e
trap - EXIT trap - EXIT
@ -110,7 +112,7 @@ upstream_branch() {
upstream_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null)" upstream_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null)"
fi fi
upstream_branch="${upstream_branch#refs/remotes/origin/}" upstream_branch="${upstream_branch#refs/remotes/origin/}"
[[ -z "${upstream_branch}" ]] && upstream_branch="master" [[ -z "${upstream_branch}" ]] && upstream_branch="main"
echo "${upstream_branch}" echo "${upstream_branch}"
} }
@ -242,7 +244,7 @@ merge_or_rebase() {
Could not 'git stash' in ${DIR}! Could not 'git stash' in ${DIR}!
Please stash/commit manually if you need to keep your changes or, if not, run: Please stash/commit manually if you need to keep your changes or, if not, run:
cd ${DIR} cd ${DIR}
git reset --hard origin/master git reset --hard origin/HEAD
EOS EOS
fi fi
git reset --hard "${QUIET_ARGS[@]}" git reset --hard "${QUIET_ARGS[@]}"
@ -260,10 +262,12 @@ EOS
then then
git checkout --force "${UPSTREAM_BRANCH}" "${QUIET_ARGS[@]}" git checkout --force "${UPSTREAM_BRANCH}" "${QUIET_ARGS[@]}"
else else
if [[ -n "${UPSTREAM_TAG}" && "${UPSTREAM_BRANCH}" != "master" ]] && if [[ -n "${UPSTREAM_TAG}" && "${UPSTREAM_BRANCH}" != "master" && "${UPSTREAM_BRANCH}" != "main" ]] &&
[[ "${INITIAL_BRANCH}" != "master" ]] [[ "${INITIAL_BRANCH}" != "master" && "${INITIAL_BRANCH}" != "main" ]]
then then
git branch --force "master" "origin/master" "${QUIET_ARGS[@]}" local detected_upstream_branch
detected_upstream_branch="$(upstream_branch)"
git branch --force "${detected_upstream_branch}" "origin/${detected_upstream_branch}" "${QUIET_ARGS[@]}"
fi fi
git checkout --force -B "${UPSTREAM_BRANCH}" "${REMOTE_REF}" "${QUIET_ARGS[@]}" git checkout --force -B "${UPSTREAM_BRANCH}" "${REMOTE_REF}" "${QUIET_ARGS[@]}"
@ -541,7 +545,8 @@ EOS
echo "HOMEBREW_CORE_GIT_REMOTE set: using ${HOMEBREW_CORE_GIT_REMOTE} as the Homebrew/homebrew-core Git remote." echo "HOMEBREW_CORE_GIT_REMOTE set: using ${HOMEBREW_CORE_GIT_REMOTE} as the Homebrew/homebrew-core Git remote."
git remote set-url origin "${HOMEBREW_CORE_GIT_REMOTE}" git remote set-url origin "${HOMEBREW_CORE_GIT_REMOTE}"
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch --force origin refs/heads/master:refs/remotes/origin/master git config fetch.prune true
git fetch --force origin
SKIP_FETCH_CORE_REPOSITORY=1 SKIP_FETCH_CORE_REPOSITORY=1
fi fi
@ -642,9 +647,9 @@ EOS
UPDATING_MESSAGE_SHOWN=1 UPDATING_MESSAGE_SHOWN=1
fi fi
# The upstream repository's default branch may not be master; # The upstream repository's default branch may not be main or master;
# check refs/remotes/origin/HEAD to see what the default # check refs/remotes/origin/HEAD to see what the default
# origin branch name is, and use that. If not set, fall back to "master". # origin branch name is, and use that. If not set, fall back to "main".
# the refspec ensures that the default upstream branch gets updated # the refspec ensures that the default upstream branch gets updated
( (
UPSTREAM_REPOSITORY_URL="$(git config remote.origin.url)" UPSTREAM_REPOSITORY_URL="$(git config remote.origin.url)"
@ -732,9 +737,9 @@ EOS
then then
local git_errors local git_errors
git_errors="$(cat "${tmp_failure_file}")" git_errors="$(cat "${tmp_failure_file}")"
# Attempt migration from master to main branch.
if [[ "${git_errors}" == "fatal: couldn't find remote ref refs/heads/master" ]] if [[ "${git_errors}" == "fatal: couldn't find remote ref refs/heads/master" ]]
then then
# Attempt migration from master to main branch.
if git fetch --tags --force "${QUIET_ARGS[@]}" origin \ if git fetch --tags --force "${QUIET_ARGS[@]}" origin \
"refs/heads/main:refs/remotes/origin/main" 2>>"${tmp_failure_file}" "refs/heads/main:refs/remotes/origin/main" 2>>"${tmp_failure_file}"
then then

View File

@ -1,12 +1,12 @@
{ {
"$schema" : "https://json-schema.org/draft/2019-09/schema", "$schema" : "https://json-schema.org/draft/2019-09/schema",
"$id" : "http://spdx.org/rdf/terms/2.3", "$id" : "http://spdx.org/rdf/terms/2.3",
"title" : "SPDX 2.3", "title" : "SPDX 2.3.1-dev",
"type" : "object", "type" : "object",
"properties" : { "properties" : {
"$schema": { "$schema": {
"type": "string", "type": "string",
"description": "Reference the SPDX 2.3 JSON schema." "description": "Reference the SPDX 2.3.1 JSON schema."
}, },
"SPDXID" : { "SPDXID" : {
"type" : "string", "type" : "string",
@ -90,7 +90,7 @@
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ] "enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
}, },
"checksumValue" : { "checksumValue" : {
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.", "description" : "The checksumValue property provides a lower case hexadecimal encoded digest value produced using a specific algorithm.",
"type" : "string" "type" : "string"
} }
}, },
@ -270,10 +270,10 @@
} }
}, },
"attributionTexts" : { "attributionTexts" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "array", "type" : "array",
"items" : { "items" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "string" "type" : "string"
} }
}, },
@ -293,7 +293,7 @@
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ] "enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
}, },
"checksumValue" : { "checksumValue" : {
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.", "description" : "The checksumValue property provides a lower case hexadecimal encoded digest value produced using a specific algorithm.",
"type" : "string" "type" : "string"
} }
}, },
@ -375,10 +375,10 @@
"type" : "string" "type" : "string"
}, },
"licenseInfoFromFiles" : { "licenseInfoFromFiles" : {
"description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.", "description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of all licenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.",
"type" : "array", "type" : "array",
"items" : { "items" : {
"description" : "License expression for licenseInfoFromFiles. See SPDX Annex D for the license expression syntax. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.", "description" : "License expression for licenseInfoFromFiles. See SPDX Annex D for the license expression syntax. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of all licenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.",
"type" : "string" "type" : "string"
} }
}, },
@ -417,7 +417,7 @@
"primaryPackagePurpose" : { "primaryPackagePurpose" : {
"description" : "This field provides information about the primary purpose of the identified package. Package Purpose is intrinsic to how the package is being used rather than the content of the package.", "description" : "This field provides information about the primary purpose of the identified package. Package Purpose is intrinsic to how the package is being used rather than the content of the package.",
"type" : "string", "type" : "string",
"enum" : [ "OTHER", "INSTALL", "ARCHIVE", "FIRMWARE", "APPLICATION", "FRAMEWORK", "LIBRARY", "CONTAINER", "SOURCE", "DEVICE", "OPERATING_SYSTEM", "FILE" ] "enum" : [ "OTHER", "INSTALL", "ARCHIVE", "FIRMWARE", "APPLICATION", "FRAMEWORK", "LIBRARY", "CONTAINER", "SOURCE", "DEVICE", "OPERATING-SYSTEM", "FILE" ]
}, },
"releaseDate" : { "releaseDate" : {
"description" : "This field provides a place for recording the date the package was released.", "description" : "This field provides a place for recording the date the package was released.",
@ -494,10 +494,10 @@
} }
}, },
"attributionTexts" : { "attributionTexts" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "array", "type" : "array",
"items" : { "items" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "string" "type" : "string"
} }
}, },
@ -514,7 +514,7 @@
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ] "enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
}, },
"checksumValue" : { "checksumValue" : {
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.", "description" : "The checksumValue property provides a lower case hexadecimal encoded digest value produced using a specific algorithm.",
"type" : "string" "type" : "string"
} }
}, },
@ -624,10 +624,10 @@
} }
}, },
"attributionTexts" : { "attributionTexts" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "array", "type" : "array",
"items" : { "items" : {
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.", "description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConcluded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
"type" : "string" "type" : "string"
} }
}, },
@ -709,7 +709,7 @@
} }
}, },
"snippetFromFile" : { "snippetFromFile" : {
"description" : "SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).", "description" : "SPDX ID for File. File containing the SPDX element (e.g. the file containing a snippet).",
"type" : "string" "type" : "string"
} }
}, },

View File

@ -1,13 +1,15 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "irb" require "irb"
module IRB module IRB
sig { params(binding: Binding).void }
def self.start_within(binding) def self.start_within(binding)
old_stdout_sync = $stdout.sync old_stdout_sync = $stdout.sync
$stdout.sync = true $stdout.sync = true
@setup_done ||= T.let(false, T.nilable(T::Boolean))
unless @setup_done unless @setup_done
setup(nil, argv: []) setup(nil, argv: [])
@setup_done = true @setup_done = true

View File

@ -190,7 +190,7 @@ module Homebrew
def generate_system_options(cask) def generate_system_options(cask)
current_os = Homebrew::SimulateSystem.current_os current_os = Homebrew::SimulateSystem.current_os
current_os_is_macos = MacOSVersion::SYMBOLS.include?(current_os) current_os_is_macos = MacOSVersion::SYMBOLS.include?(current_os)
newest_macos = MacOSVersion::SYMBOLS.keys.first newest_macos = MacOSVersion.new(HOMEBREW_MACOS_NEWEST_SUPPORTED).to_sym
depends_on_archs = cask.depends_on.arch&.filter_map { |arch| arch[:type] }&.uniq depends_on_archs = cask.depends_on.arch&.filter_map { |arch| arch[:type] }&.uniq

View File

@ -399,7 +399,7 @@ module Homebrew
nil nil
end end
if github_release_data.present? if github_release_data.present? && github_release_data["body"].present?
pre = "pre" if github_release_data["prerelease"].present? pre = "pre" if github_release_data["prerelease"].present?
# maximum length of PR body is 65,536 characters so let's truncate release notes to half of that. # maximum length of PR body is 65,536 characters so let's truncate release notes to half of that.
body = Formatter.truncate(github_release_data["body"], max: 32_768) body = Formatter.truncate(github_release_data["body"], max: 32_768)

View File

@ -183,41 +183,42 @@ module Homebrew
:zig :zig
end end
fc = FormulaCreator.new( formula_creator = FormulaCreator.new(
args.set_name,
args.set_version,
tap: args.tap,
url: args.named.fetch(0), url: args.named.fetch(0),
name: args.set_name,
version: args.set_version,
tap: args.tap,
mode:, mode:,
license: args.set_license, license: args.set_license,
fetch: !args.no_fetch?, fetch: !args.no_fetch?,
head: args.HEAD?, head: args.HEAD?,
) )
fc.parse_url
# ask for confirmation if name wasn't passed explicitly # ask for confirmation if name wasn't passed explicitly
if args.set_name.blank? if args.set_name.blank?
print "Formula name [#{fc.name}]: " print "Formula name [#{formula_creator.name}]: "
fc.name = __gets || fc.name confirmed_name = __gets
formula_creator.name = confirmed_name if confirmed_name.present?
end end
fc.verify formula_creator.verify_tap_available!
# Check for disallowed formula, or names that shadow aliases, # Check for disallowed formula, or names that shadow aliases,
# unless --force is specified. # unless --force is specified.
unless args.force? unless args.force?
if (reason = MissingFormula.disallowed_reason(fc.name)) if (reason = MissingFormula.disallowed_reason(formula_creator.name))
odie <<~EOS odie <<~EOS
The formula '#{fc.name}' is not allowed to be created. The formula '#{formula_creator.name}' is not allowed to be created.
#{reason} #{reason}
If you really want to create this formula use `--force`. If you really want to create this formula use `--force`.
EOS EOS
end end
Homebrew.with_no_api_env do Homebrew.with_no_api_env do
if Formula.aliases.include? fc.name if Formula.aliases.include?(formula_creator.name)
realname = Formulary.canonical_name(fc.name) realname = Formulary.canonical_name(formula_creator.name)
odie <<~EOS odie <<~EOS
The formula '#{realname}' is already aliased to '#{fc.name}'. The formula '#{realname}' is already aliased to '#{formula_creator.name}'.
Please check that you are not creating a duplicate. Please check that you are not creating a duplicate.
To force creation use `--force`. To force creation use `--force`.
EOS EOS
@ -225,19 +226,19 @@ module Homebrew
end end
end end
path = fc.write_formula! path = formula_creator.write_formula!
formula = Homebrew.with_no_api_env do formula = Homebrew.with_no_api_env do
CoreTap.instance.clear_cache CoreTap.instance.clear_cache
Formula[fc.name] Formula[formula_creator.name]
end end
PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python? PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python?
puts <<~EOS puts <<~EOS
Please audit and test formula before submitting: Please audit and test formula before submitting:
HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{fc.name} HOMEBREW_NO_INSTALL_FROM_API=1 brew audit --new #{formula_creator.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source --verbose --debug #{fc.name} HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source --verbose --debug #{formula_creator.name}
HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{fc.name} HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{formula_creator.name}
EOS EOS
path path
end end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
@ -172,7 +172,8 @@ module Homebrew
with_monkey_patch { Formulary.from_contents(name, file, contents, ignore_errors: true) } with_monkey_patch { Formulary.from_contents(name, file, contents, ignore_errors: true) }
end end
def with_monkey_patch sig { params(_block: T.proc.void).returns(T.untyped) }
def with_monkey_patch(&_block)
# Since `method_defined?` is not a supported type guard, the use of `alias_method` below is not typesafe: # Since `method_defined?` is not a supported type guard, the use of `alias_method` below is not typesafe:
BottleSpecification.class_eval do BottleSpecification.class_eval do
T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing) T.unsafe(self).alias_method :old_method_missing, :method_missing if method_defined?(:method_missing)
@ -210,28 +211,28 @@ module Homebrew
BottleSpecification.class_eval do BottleSpecification.class_eval do
if method_defined?(:old_method_missing) if method_defined?(:old_method_missing)
T.unsafe(self).alias_method :method_missing, :old_method_missing T.unsafe(self).alias_method :method_missing, :old_method_missing
undef :old_method_missing T.unsafe(self).undef :old_method_missing
end end
end end
Module.class_eval do Module.class_eval do
if method_defined?(:old_method_missing) if method_defined?(:old_method_missing)
T.unsafe(self).alias_method :method_missing, :old_method_missing T.unsafe(self).alias_method :method_missing, :old_method_missing
undef :old_method_missing T.unsafe(self).undef :old_method_missing
end end
end end
Resource.class_eval do Resource.class_eval do
if method_defined?(:old_method_missing) if method_defined?(:old_method_missing)
T.unsafe(self).alias_method :method_missing, :old_method_missing T.unsafe(self).alias_method :method_missing, :old_method_missing
undef :old_method_missing T.unsafe(self).undef :old_method_missing
end end
end end
DependencyCollector.class_eval do DependencyCollector.class_eval do
if method_defined?(:old_parse_symbol_spec) if method_defined?(:old_parse_symbol_spec)
T.unsafe(self).alias_method :parse_symbol_spec, :old_parse_symbol_spec T.unsafe(self).alias_method :parse_symbol_spec, :old_parse_symbol_spec
undef :old_parse_symbol_spec T.unsafe(self).undef :old_parse_symbol_spec
end end
end end
end end

View File

@ -45,7 +45,7 @@ module Homebrew
Cask::Cask.generating_hash! Cask::Cask.generating_hash!
all_casks = {} all_casks = {}
latest_macos = MacOSVersion.new((HOMEBREW_MACOS_NEWEST_UNSUPPORTED.to_i - 1).to_s).to_sym latest_macos = MacOSVersion.new(HOMEBREW_MACOS_NEWEST_SUPPORTED).to_sym
Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do Homebrew::SimulateSystem.with(os: latest_macos, arch: :arm) do
tap.cask_files.each do |path| tap.cask_files.each do |path|
cask = Cask::CaskLoader.load(path) cask = Cask::CaskLoader.load(path)

View File

@ -67,8 +67,8 @@ module Homebrew
end end
raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1 raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1
labels = if pr_url labels = if pr_url && (first_pr_url = pr_url.first)
pr = GitHub::API.open_rest(pr_url.first) pr = GitHub::API.open_rest(first_pr_url)
pr.fetch("labels").map { |l| l.fetch("name") } pr.fetch("labels").map { |l| l.fetch("name") }
else else
[] []
@ -263,11 +263,10 @@ module Homebrew
audit_exceptions << %w[homepage_https_availability] if labels.include?("ci-skip-homepage") audit_exceptions << %w[homepage_https_availability] if labels.include?("ci-skip-homepage")
if labels.include?("ci-skip-livecheck") if labels.include?("ci-skip-livecheck")
audit_exceptions << %w[hosting_with_livecheck livecheck_https_availability audit_exceptions << %w[hosting_with_livecheck livecheck_https_availability livecheck_version min_os]
livecheck_min_os livecheck_version]
end end
audit_exceptions << "livecheck_min_os" if labels.include?("ci-skip-livecheck-min-os") audit_exceptions << "min_os" if labels.include?("ci-skip-livecheck-min-os")
if labels.include?("ci-skip-repository") if labels.include?("ci-skip-repository")
audit_exceptions << %w[github_repository github_prerelease_version audit_exceptions << %w[github_repository github_prerelease_version

View File

@ -44,12 +44,11 @@ module Homebrew
titleized_repository = tap.repository.dup titleized_repository = tap.repository.dup
titleized_user[0] = titleized_user[0].upcase titleized_user[0] = titleized_user[0].upcase
titleized_repository[0] = titleized_repository[0].upcase titleized_repository[0] = titleized_repository[0].upcase
root_url = GitHubPackages.root_url(tap.user, "homebrew-#{tap.repository}") if args.github_packages? # Duplicate assignment to silence `assigned but unused variable` warning
root_url = root_url = GitHubPackages.root_url(tap.user, "homebrew-#{tap.repository}") if args.github_packages?
(tap.path/"Formula").mkpath (tap.path/"Formula").mkpath
# FIXME: https://github.com/errata-ai/vale/issues/818
# <!-- vale off -->
readme = <<~MARKDOWN readme = <<~MARKDOWN
# #{titleized_user} #{titleized_repository} # #{titleized_user} #{titleized_repository}
@ -70,7 +69,6 @@ module Homebrew
`brew help`, `man brew` or check [Homebrew's documentation](https://docs.brew.sh). `brew help`, `man brew` or check [Homebrew's documentation](https://docs.brew.sh).
MARKDOWN MARKDOWN
# <!-- vale on -->
write_path(tap, "README.md", readme) write_path(tap, "README.md", readme)
tests_yml = <<~ERB tests_yml = <<~ERB
@ -99,7 +97,7 @@ module Homebrew
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:
token: ${{ github.token }} token: ${{ github.token }}
@ -164,12 +162,12 @@ module Homebrew
pull-requests: write pull-requests: write
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@main
with: with:
token: ${{ github.token }} token: ${{ github.token }}
- name: Set up git - name: Set up git
uses: Homebrew/actions/git-user-config@master uses: Homebrew/actions/git-user-config@main
- name: Pull bottles - name: Pull bottles
env: env:
@ -182,7 +180,7 @@ module Homebrew
run: brew pr-pull --debug --tap="$GITHUB_REPOSITORY" "$PULL_REQUEST" run: brew pr-pull --debug --tap="$GITHUB_REPOSITORY" "$PULL_REQUEST"
- name: Push commits - name: Push commits
uses: Homebrew/actions/git-try-push@master uses: Homebrew/actions/git-try-push@main
with: with:
branch: <%= branch %> branch: <%= branch %>

View File

@ -51,6 +51,11 @@ module Homebrew
HOMEBREW_LIBRARY_PATH.cd do HOMEBREW_LIBRARY_PATH.cd do
setup_environment! setup_environment!
# Needs required here, after `setup_environment!`, so that
# `HOMEBREW_TEST_GENERIC_OS` is set and `OS.linux?` and `OS.mac?` both
# `return false`.
require "extend/os/dev-cmd/tests"
parallel = !args.no_parallel? parallel = !args.no_parallel?
only = args.only only = args.only
@ -267,5 +272,3 @@ module Homebrew
end end
end end
end end
require "extend/os/dev-cmd/tests"

View File

@ -142,6 +142,7 @@ module Homebrew
private private
sig { returns(String) }
def git_tags def git_tags
tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname") tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname")
if tags.blank? if tags.blank?

View File

@ -350,7 +350,6 @@ module Homebrew
sudo chmod +t #{HOMEBREW_TEMP} sudo chmod +t #{HOMEBREW_TEMP}
EOS EOS
end end
alias generic_check_tmpdir_sticky_bit check_tmpdir_sticky_bit
def check_exist_directories def check_exist_directories
return if HOMEBREW_PREFIX.writable? return if HOMEBREW_PREFIX.writable?

View File

@ -54,8 +54,6 @@ module SharedEnvExtension
@debug_symbols = debug_symbols @debug_symbols = debug_symbols
reset reset
end end
alias generic_shared_setup_build_environment setup_build_environment
private :generic_shared_setup_build_environment
sig { void } sig { void }
def reset def reset

View File

@ -68,7 +68,6 @@ module Stdenv
gcc_formula = gcc_version_formula(cc) gcc_formula = gcc_version_formula(cc)
append_path "PATH", gcc_formula.opt_bin.to_s append_path "PATH", gcc_formula.opt_bin.to_s
end end
alias generic_setup_build_environment setup_build_environment
sig { returns(T.nilable(PATH)) } sig { returns(T.nilable(PATH)) }
def determine_pkg_config_libdir def determine_pkg_config_libdir

View File

@ -125,7 +125,6 @@ module Superenv
# These flags will also be present: # These flags will also be present:
# a - apply fix for apr-1-config path # a - apply fix for apr-1-config path
end end
alias generic_setup_build_environment setup_build_environment
private private
@ -152,7 +151,6 @@ module Superenv
.reverse .reverse
.map { |d| d.opt_libexec/"bin" } .map { |d| d.opt_libexec/"bin" }
end end
alias generic_homebrew_extra_paths homebrew_extra_paths
sig { returns(T.nilable(PATH)) } sig { returns(T.nilable(PATH)) }
def determine_path def determine_path
@ -372,8 +370,8 @@ module Superenv
append_to_cccfg "O" append_to_cccfg "O"
end end
# This is an exception where we want to use this method name format.
# rubocop: disable Naming/MethodName # rubocop: disable Naming/MethodName
# Fixes style error `Naming/MethodName: Use snake_case for method names.`
sig { params(block: T.nilable(T.proc.void)).void } sig { params(block: T.nilable(T.proc.void)).void }
def O0(&block) def O0(&block)
if block if block

View File

@ -15,6 +15,11 @@ module OS
def os_bundle_args(bundle_args) def os_bundle_args(bundle_args)
non_macos_bundle_args(bundle_args) non_macos_bundle_args(bundle_args)
end end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
non_macos_files(files)
end
end end
end end
end end

View File

@ -1,19 +1,19 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Homebrew module OS
module DevCmd module Linux
class UpdateTest < AbstractCommand module DevCmd
alias generic_git_tags git_tags module UpdateTest
private
private sig { returns(String) }
def git_tags
sig { returns(String) } super.presence || Utils.popen_read("git tag --list | sort -rV")
def git_tags end
tags = generic_git_tags
tags = Utils.popen_read("git tag --list | sort -rV") if tags.blank?
tags
end end
end end
end end
end end
Homebrew::DevCmd::UpdateTest.prepend(OS::Linux::DevCmd::UpdateTest)

View File

@ -62,7 +62,7 @@ module OS
def build_system_info def build_system_info
super.merge({ super.merge({
"glibc_version" => OS::Linux::Glibc.version.to_s.presence, "glibc_version" => OS::Linux::Glibc.version.to_s.presence,
"oldest_cpu_family" => Hardware.oldest_cpu.to_s, "oldest_cpu_family" => ::Hardware.oldest_cpu.to_s,
}) })
end end
end end

View File

@ -32,7 +32,7 @@ module OS
end end
def check_tmpdir_sticky_bit def check_tmpdir_sticky_bit
message = generic_check_tmpdir_sticky_bit message = super
return if message.nil? return if message.nil?
message + <<~EOS message + <<~EOS
@ -74,11 +74,11 @@ module OS
end end
def check_supported_architecture def check_supported_architecture
return if Hardware::CPU.intel? return if ::Hardware::CPU.intel?
return if Homebrew::EnvConfig.developer? && ENV["HOMEBREW_ARM64_TESTING"].present? && Hardware::CPU.arm? return if Homebrew::EnvConfig.developer? && ENV["HOMEBREW_ARM64_TESTING"].present? && ::Hardware::CPU.arm?
<<~EOS <<~EOS
Your CPU architecture (#{Hardware::CPU.arch}) is not supported. We only support Your CPU architecture (#{::Hardware::CPU.arch}) is not supported. We only support
x86_64 CPU architectures. You will be unable to use binary packages (bottles). x86_64 CPU architectures. You will be unable to use binary packages (bottles).
#{support_tier_message(tier: 2)} #{support_tier_message(tier: 2)}

View File

@ -1,16 +1,22 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module SharedEnvExtension module OS
def effective_arch module Linux
if @build_bottle && @bottle_arch module SharedEnvExtension
@bottle_arch.to_sym def effective_arch
elsif @build_bottle if @build_bottle && @bottle_arch
Hardware.oldest_cpu @bottle_arch.to_sym
elsif Hardware::CPU.intel? || Hardware::CPU.arm? elsif @build_bottle
:native ::Hardware.oldest_cpu
else elsif ::Hardware::CPU.intel? || ::Hardware::CPU.arm?
:dunno :native
else
:dunno
end
end
end end
end end
end end
SharedEnvExtension.prepend(OS::Linux::SharedEnvExtension)

View File

@ -1,36 +1,45 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Stdenv module OS
sig { module Linux
params( module Stdenv
formula: T.nilable(Formula), extend T::Helpers
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
prepend_path "CPATH", HOMEBREW_PREFIX/"include" requires_ancestor { ::SharedEnvExtension }
prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib"
prepend_path "LD_RUN_PATH", HOMEBREW_PREFIX/"lib"
return unless @formula sig {
params(
formula: T.nilable(::Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
super
prepend_path "CPATH", @formula.include prepend_path "CPATH", HOMEBREW_PREFIX/"include"
prepend_path "LIBRARY_PATH", @formula.lib prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib"
prepend_path "LD_RUN_PATH", @formula.lib prepend_path "LD_RUN_PATH", HOMEBREW_PREFIX/"lib"
end
def libxml2 return unless @formula
append "CPPFLAGS", "-I#{Formula["libxml2"].include/"libxml2"}"
rescue FormulaUnavailableError prepend_path "CPATH", @formula.include
nil prepend_path "LIBRARY_PATH", @formula.lib
prepend_path "LD_RUN_PATH", @formula.lib
end
def libxml2
append "CPPFLAGS", "-I#{::Formula["libxml2"].include/"libxml2"}"
rescue FormulaUnavailableError
nil
end
end
end end
end end
Stdenv.prepend(OS::Linux::Stdenv)

View File

@ -1,79 +1,93 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Superenv module OS
sig { returns(Pathname) } module Linux
def self.shims_path module Superenv
HOMEBREW_SHIMS_PATH/"linux/super" extend T::Helpers
end
sig { returns(T.nilable(Pathname)) } requires_ancestor { SharedEnvExtension }
def self.bin requires_ancestor { ::Superenv }
shims_path.realpath
end
sig { module ClassMethods
params( sig { returns(Pathname) }
formula: T.nilable(Formula), def shims_path
cc: T.nilable(String), HOMEBREW_SHIMS_PATH/"linux/super"
build_bottle: T.nilable(T::Boolean), end
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
self["HOMEBREW_OPTIMIZATION_LEVEL"] = "O2"
self["HOMEBREW_DYNAMIC_LINKER"] = determine_dynamic_linker_path
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula)
m4_path_deps = ["libtool", "bison"]
self["M4"] = "#{HOMEBREW_PREFIX}/opt/m4/bin/m4" if deps.any? { m4_path_deps.include?(_1.name) }
# Pointer authentication and BTI are hardening techniques most distros sig { returns(T.nilable(Pathname)) }
# use by default on their packages. arm64 Linux we're packaging def bin
# everything from scratch so the entire dependency tree can have it. shims_path.realpath
append_to_cccfg "b" if Hardware::CPU.arch == :arm64 && DevelopmentTools.gcc_version("gcc") >= 9 end
end end
def homebrew_extra_paths sig {
paths = generic_homebrew_extra_paths params(
paths += %w[binutils make].filter_map do |f| formula: T.nilable(Formula),
bin = Formulary.factory(f).opt_bin cc: T.nilable(String),
bin if bin.directory? build_bottle: T.nilable(T::Boolean),
rescue FormulaUnavailableError bottle_arch: T.nilable(String),
nil testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
super
self["HOMEBREW_OPTIMIZATION_LEVEL"] = "O2"
self["HOMEBREW_DYNAMIC_LINKER"] = determine_dynamic_linker_path
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula)
m4_path_deps = ["libtool", "bison"]
self["M4"] = "#{HOMEBREW_PREFIX}/opt/m4/bin/m4" if deps.any? { m4_path_deps.include?(_1.name) }
# Pointer authentication and BTI are hardening techniques most distros
# use by default on their packages. arm64 Linux we're packaging
# everything from scratch so the entire dependency tree can have it.
append_to_cccfg "b" if ::Hardware::CPU.arch == :arm64 && ::DevelopmentTools.gcc_version("gcc") >= 9
end
def homebrew_extra_paths
paths = super
paths += %w[binutils make].filter_map do |f|
bin = Formulary.factory(f).opt_bin
bin if bin.directory?
rescue FormulaUnavailableError
nil
end
paths
end
def homebrew_extra_isystem_paths
paths = []
# Add paths for GCC headers when building against versioned glibc because we have to use -nostdinc.
if deps.any? { |d| d.name.match?(/^glibc@.+$/) }
gcc_include_dir = Utils.safe_popen_read(cc, "--print-file-name=include").chomp
gcc_include_fixed_dir = Utils.safe_popen_read(cc, "--print-file-name=include-fixed").chomp
paths << gcc_include_dir << gcc_include_fixed_dir
end
paths
end
def determine_rpath_paths(formula)
PATH.new(
*formula&.lib,
"#{HOMEBREW_PREFIX}/opt/gcc/lib/gcc/current",
PATH.new(run_time_deps.map { |dep| dep.opt_lib.to_s }).existing,
"#{HOMEBREW_PREFIX}/lib",
)
end
sig { returns(T.nilable(String)) }
def determine_dynamic_linker_path
path = "#{HOMEBREW_PREFIX}/lib/ld.so"
return unless File.readable? path
path
end
end end
paths
end
def homebrew_extra_isystem_paths
paths = []
# Add paths for GCC headers when building against versioned glibc because we have to use -nostdinc.
if deps.any? { |d| d.name.match?(/^glibc@.+$/) }
gcc_include_dir = Utils.safe_popen_read(cc, "--print-file-name=include").chomp
gcc_include_fixed_dir = Utils.safe_popen_read(cc, "--print-file-name=include-fixed").chomp
paths << gcc_include_dir << gcc_include_fixed_dir
end
paths
end
def determine_rpath_paths(formula)
PATH.new(
*formula&.lib,
"#{HOMEBREW_PREFIX}/opt/gcc/lib/gcc/current",
PATH.new(run_time_deps.map { |dep| dep.opt_lib.to_s }).existing,
"#{HOMEBREW_PREFIX}/lib",
)
end
sig { returns(T.nilable(String)) }
def determine_dynamic_linker_path
path = "#{HOMEBREW_PREFIX}/lib/ld.so"
return unless File.readable? path
path
end end
end end
Superenv.singleton_class.prepend(OS::Linux::Superenv::ClassMethods)
Superenv.prepend(OS::Linux::Superenv)

View File

@ -1,9 +1,15 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module FormulaCellarChecks module OS
sig { params(filename: Pathname).returns(T::Boolean) } module Linux
def valid_library_extension?(filename) module FormulaCellarChecks
generic_valid_library_extension?(filename) || filename.basename.to_s.include?(".so.") sig { params(filename: Pathname).returns(T::Boolean) }
def valid_library_extension?(filename)
super || filename.basename.to_s.include?(".so.")
end
end
end end
end end
FormulaCellarChecks.prepend(OS::Linux::FormulaCellarChecks)

View File

@ -1,161 +1,171 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Hardware module OS
class CPU module Linux
class << self module Hardware
def optimization_flags module CPU
@optimization_flags ||= begin module ClassMethods
flags = generic_optimization_flags.dup extend T::Helpers
flags[:native] = arch_flag(Homebrew::EnvConfig.arch)
flags
end
end
def family requires_ancestor { T.class_of(::Hardware::CPU) }
return :arm if arm?
return :ppc if ppc?
return :dunno unless intel?
# See https://software.intel.com/en-us/articles/intel-architecture-and-processor-identification-with-cpuid-model-and-family-numbers def optimization_flags
# and https://github.com/llvm/llvm-project/blob/main/llvm/lib/TargetParser/Host.cpp @optimization_flags ||= begin
# and https://en.wikipedia.org/wiki/List_of_Intel_CPU_microarchitectures#Roadmap flags = super.dup
vendor_id = cpuinfo[/^vendor_id\s*: (.*)/, 1] flags[:native] = arch_flag(Homebrew::EnvConfig.arch)
cpu_family = cpuinfo[/^cpu family\s*: ([0-9]+)/, 1].to_i flags
cpu_model = cpuinfo[/^model\s*: ([0-9]+)/, 1].to_i end
unknown = :"unknown_0x#{cpu_family.to_s(16)}_0x#{cpu_model.to_s(16)}"
case vendor_id
when "GenuineIntel"
intel_family(cpu_family, cpu_model)
when "AuthenticAMD"
amd_family(cpu_family, cpu_model)
end || unknown
end
def intel_family(family, cpu_model)
case family
when 0x06
case cpu_model
when 0x3a, 0x3e
:ivybridge
when 0x2a, 0x2d
:sandybridge
when 0x25, 0x2c, 0x2f
:westmere
when 0x1a, 0x1e, 0x1f, 0x2e
:nehalem
when 0x17, 0x1d
:penryn
when 0x0f, 0x16
:merom
when 0x0d
:dothan
when 0x1c, 0x26, 0x27, 0x35, 0x36
:atom
when 0x3c, 0x3f, 0x45, 0x46
:haswell
when 0x3d, 0x47, 0x4f, 0x56
:broadwell
when 0x4e, 0x5e, 0x8e, 0x9e, 0xa5, 0xa6
:skylake
when 0x66
:cannonlake
when 0x6a, 0x6c, 0x7d, 0x7e
:icelake
when 0xa7
:rocketlake
when 0x8c, 0x8d
:tigerlake
when 0x97, 0x9a, 0xbe, 0xb7, 0xba, 0xbf, 0xaa, 0xac
:alderlake
when 0xc5, 0xb5, 0xc6, 0xbd
:arrowlake
when 0xcc
:pantherlake
when 0xad, 0xae
:graniterapids
when 0xcf, 0x8f
:sapphirerapids
end end
when 0x0f
case cpu_model def family
when 0x06 return :arm if arm?
:presler return :ppc if ppc?
when 0x03, 0x04 return :dunno unless intel?
:prescott
# See https://software.intel.com/en-us/articles/intel-architecture-and-processor-identification-with-cpuid-model-and-family-numbers
# and https://github.com/llvm/llvm-project/blob/main/llvm/lib/TargetParser/Host.cpp
# and https://en.wikipedia.org/wiki/List_of_Intel_CPU_microarchitectures#Roadmap
vendor_id = cpuinfo[/^vendor_id\s*: (.*)/, 1]
cpu_family = cpuinfo[/^cpu family\s*: ([0-9]+)/, 1].to_i
cpu_model = cpuinfo[/^model\s*: ([0-9]+)/, 1].to_i
unknown = :"unknown_0x#{cpu_family.to_s(16)}_0x#{cpu_model.to_s(16)}"
case vendor_id
when "GenuineIntel"
intel_family(cpu_family, cpu_model)
when "AuthenticAMD"
amd_family(cpu_family, cpu_model)
end || unknown
end
def intel_family(family, cpu_model)
case family
when 0x06
case cpu_model
when 0x3a, 0x3e
:ivybridge
when 0x2a, 0x2d
:sandybridge
when 0x25, 0x2c, 0x2f
:westmere
when 0x1a, 0x1e, 0x1f, 0x2e
:nehalem
when 0x17, 0x1d
:penryn
when 0x0f, 0x16
:merom
when 0x0d
:dothan
when 0x1c, 0x26, 0x27, 0x35, 0x36
:atom
when 0x3c, 0x3f, 0x45, 0x46
:haswell
when 0x3d, 0x47, 0x4f, 0x56
:broadwell
when 0x4e, 0x5e, 0x8e, 0x9e, 0xa5, 0xa6
:skylake
when 0x66
:cannonlake
when 0x6a, 0x6c, 0x7d, 0x7e
:icelake
when 0xa7
:rocketlake
when 0x8c, 0x8d
:tigerlake
when 0x97, 0x9a, 0xbe, 0xb7, 0xba, 0xbf, 0xaa, 0xac
:alderlake
when 0xc5, 0xb5, 0xc6, 0xbd
:arrowlake
when 0xcc
:pantherlake
when 0xad, 0xae
:graniterapids
when 0xcf, 0x8f
:sapphirerapids
end
when 0x0f
case cpu_model
when 0x06
:presler
when 0x03, 0x04
:prescott
end
end
end
def amd_family(family, cpu_model)
case family
when 0x06
:amd_k7
when 0x0f
:amd_k8
when 0x10
:amd_k10
when 0x11
:amd_k8_k10_hybrid
when 0x12
:amd_k10_llano
when 0x14
:bobcat
when 0x15
:bulldozer
when 0x16
:jaguar
when 0x17
case cpu_model
when 0x10..0x2f
:zen
when 0x30..0x3f, 0x47, 0x60..0x7f, 0x84..0x87, 0x90..0xaf
:zen2
end
when 0x19
case cpu_model
when ..0x0f, 0x20..0x5f
:zen3
when 0x10..0x1f, 0x60..0x7f, 0xa0..0xaf
:zen4
end
when 0x1a
:zen5
end
end
# Supported CPU instructions
def flags
@flags ||= cpuinfo[/^(?:flags|Features)\s*: (.*)/, 1]&.split
@flags ||= []
end
# Compatibility with Mac method, which returns lowercase symbols
# instead of strings.
def features
@features ||= flags[1..].map(&:intern)
end
%w[aes altivec avx avx2 lm ssse3 sse4_2].each do |flag|
define_method(:"#{flag}?") do
T.bind(self, OS::Linux::Hardware::CPU::ClassMethods)
flags.include? flag
end
end
def sse3?
flags.include?("pni") || flags.include?("sse3")
end
def sse4?
flags.include? "sse4_1"
end
private
def cpuinfo
@cpuinfo ||= File.read("/proc/cpuinfo")
end end
end end
end end
def amd_family(family, cpu_model)
case family
when 0x06
:amd_k7
when 0x0f
:amd_k8
when 0x10
:amd_k10
when 0x11
:amd_k8_k10_hybrid
when 0x12
:amd_k10_llano
when 0x14
:bobcat
when 0x15
:bulldozer
when 0x16
:jaguar
when 0x17
case cpu_model
when 0x10..0x2f
:zen
when 0x30..0x3f, 0x47, 0x60..0x7f, 0x84..0x87, 0x90..0xaf
:zen2
end
when 0x19
case cpu_model
when ..0x0f, 0x20..0x5f
:zen3
when 0x10..0x1f, 0x60..0x7f, 0xa0..0xaf
:zen4
end
when 0x1a
:zen5
end
end
# Supported CPU instructions
def flags
@flags ||= cpuinfo[/^(?:flags|Features)\s*: (.*)/, 1]&.split
@flags ||= []
end
# Compatibility with Mac method, which returns lowercase symbols
# instead of strings.
def features
@features ||= flags[1..].map(&:intern)
end
%w[aes altivec avx avx2 lm ssse3 sse4_2].each do |flag|
define_method(:"#{flag}?") do
T.bind(self, T.class_of(Hardware::CPU))
flags.include? flag
end
end
def sse3?
flags.include?("pni") || flags.include?("sse3")
end
def sse4?
flags.include? "sse4_1"
end
private
def cpuinfo
@cpuinfo ||= File.read("/proc/cpuinfo")
end
end end
end end
end end
Hardware::CPU.singleton_class.prepend(OS::Linux::Hardware::CPU::ClassMethods)

View File

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

View File

@ -3,48 +3,55 @@
require "compilers" require "compilers"
class LinkageChecker module OS
# Libraries provided by glibc and gcc. module Linux
SYSTEM_LIBRARY_ALLOWLIST = %w[ module LinkageChecker
ld-linux-x86-64.so.2 # Libraries provided by glibc and gcc.
ld-linux-aarch64.so.1 SYSTEM_LIBRARY_ALLOWLIST = %w[
libanl.so.1 ld-linux-x86-64.so.2
libatomic.so.1 ld-linux-aarch64.so.1
libc.so.6 libanl.so.1
libdl.so.2 libatomic.so.1
libm.so.6 libc.so.6
libmvec.so.1 libdl.so.2
libnss_files.so.2 libm.so.6
libpthread.so.0 libmvec.so.1
libresolv.so.2 libnss_files.so.2
librt.so.1 libpthread.so.0
libthread_db.so.1 libresolv.so.2
libutil.so.1 librt.so.1
libgcc_s.so.1 libthread_db.so.1
libgomp.so.1 libutil.so.1
libstdc++.so.6 libgcc_s.so.1
libquadmath.so.0 libgomp.so.1
].freeze libstdc++.so.6
libquadmath.so.0
].freeze
private private
def check_dylibs(rebuild_cache:) def check_dylibs(rebuild_cache:)
generic_check_dylibs(rebuild_cache:) super
# glibc and gcc are implicit dependencies. # glibc and gcc are implicit dependencies.
# No other linkage to system libraries is expected or desired. # No other linkage to system libraries is expected or desired.
@unwanted_system_dylibs = @system_dylibs.reject do |s| @unwanted_system_dylibs = @system_dylibs.reject do |s|
SYSTEM_LIBRARY_ALLOWLIST.include? File.basename(s) SYSTEM_LIBRARY_ALLOWLIST.include? File.basename(s)
end
# We build all formulae with an RPATH that includes the gcc formula's runtime lib directory.
# See: https://github.com/Homebrew/brew/blob/e689cc07/Library/Homebrew/extend/os/linux/extend/ENV/super.rb#L53
# This results in formulae showing linkage with gcc whenever it is installed, even if no dependency is
# declared.
# See discussions at:
# https://github.com/Homebrew/brew/pull/13659
# https://github.com/Homebrew/brew/pull/13796
# TODO: Find a nicer way to handle this. (e.g. examining the ELF file to determine the required libstdc++.)
@undeclared_deps.delete("gcc")
@indirect_deps.delete("gcc")
end
end end
# We build all formulae with an RPATH that includes the gcc formula's runtime lib directory.
# See: https://github.com/Homebrew/brew/blob/e689cc07/Library/Homebrew/extend/os/linux/extend/ENV/super.rb#L53
# This results in formulae showing linkage with gcc whenever it is installed, even if no dependency is declared.
# See discussions at:
# https://github.com/Homebrew/brew/pull/13659
# https://github.com/Homebrew/brew/pull/13796
# TODO: Find a nicer way to handle this. (e.g. examining the ELF file to determine the required libstdc++.)
@undeclared_deps.delete("gcc")
@indirect_deps.delete("gcc")
end end
end end
LinkageChecker.prepend(OS::Linux::LinkageChecker)

View File

@ -5,53 +5,59 @@ require "compilers"
require "os/linux/glibc" require "os/linux/glibc"
require "system_command" require "system_command"
module SystemConfig module OS
include SystemCommand::Mixin module Linux
module SystemConfig
module ClassMethods
include SystemCommand::Mixin
HOST_RUBY_PATH = "/usr/bin/ruby" HOST_RUBY_PATH = "/usr/bin/ruby"
class << self def host_glibc_version
def host_glibc_version version = OS::Linux::Glibc.system_version
version = OS::Linux::Glibc.system_version return "N/A" if version.null?
return "N/A" if version.null?
version version
end end
def host_gcc_version def host_gcc_version
gcc = DevelopmentTools.host_gcc_path gcc = ::DevelopmentTools.host_gcc_path
return "N/A" unless gcc.executable? return "N/A" unless gcc.executable?
`#{gcc} --version 2>/dev/null`[/ (\d+\.\d+\.\d+)/, 1] Utils.popen_read(gcc, "--version")[/ (\d+\.\d+\.\d+)/, 1]
end end
def formula_linked_version(formula) def formula_linked_version(formula)
return "N/A" if Homebrew::EnvConfig.no_install_from_api? && !CoreTap.instance.installed? return "N/A" if Homebrew::EnvConfig.no_install_from_api? && !CoreTap.instance.installed?
Formulary.factory(formula).any_installed_version || "N/A" Formulary.factory(formula).any_installed_version || "N/A"
rescue FormulaUnavailableError rescue FormulaUnavailableError
"N/A" "N/A"
end end
def host_ruby_version def host_ruby_version
out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false) out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false)
return "N/A" unless status.success? return "N/A" unless status.success?
out out
end end
def dump_verbose_config(out = $stdout) def dump_verbose_config(out = $stdout)
kernel = Utils.safe_popen_read("uname", "-mors").chomp kernel = Utils.safe_popen_read("uname", "-mors").chomp
dump_generic_verbose_config(out) super
out.puts "Kernel: #{kernel}" out.puts "Kernel: #{kernel}"
out.puts "OS: #{OS::Linux.os_version}" out.puts "OS: #{OS::Linux.os_version}"
out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl? out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl?
out.puts "Host glibc: #{host_glibc_version}" out.puts "Host glibc: #{host_glibc_version}"
out.puts "#{DevelopmentTools.host_gcc_path}: #{host_gcc_version}" out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}"
out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH
["glibc", CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f| ["glibc", CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f|
out.puts "#{f}: #{formula_linked_version(f)}" out.puts "#{f}: #{formula_linked_version(f)}"
end
end
end end
end end
end end
end end
SystemConfig.singleton_class.prepend(OS::Linux::SystemConfig::ClassMethods)

View File

@ -15,6 +15,11 @@ module OS
def os_bundle_args(bundle_args) def os_bundle_args(bundle_args)
non_linux_bundle_args(bundle_args) non_linux_bundle_args(bundle_args)
end end
sig { params(files: T::Array[String]).returns(T::Array[String]) }
def os_files(files)
non_linux_files(files)
end
end end
end end
end end

View File

@ -1,42 +1,50 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module SharedEnvExtension module OS
sig { module Mac
params( module SharedEnvExtension
formula: T.nilable(Formula), extend T::Helpers
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
generic_shared_setup_build_environment(formula:, cc:, build_bottle:,
bottle_arch:, testing_formula:,
debug_symbols:)
# Normalise the system Perl version used, where multiple may be available requires_ancestor { ::SharedEnvExtension }
self["VERSIONER_PERL_VERSION"] = MacOS.preferred_perl_version
end
sig { returns(T::Boolean) } sig {
def no_weak_imports_support? params(
return false if compiler != :clang formula: T.nilable(::Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
super
return false if !MacOS::Xcode.version.null? && MacOS::Xcode.version < "8.0" # Normalise the system Perl version used, where multiple may be available
return false if !MacOS::CLT.version.null? && MacOS::CLT.version < "8.0" self["VERSIONER_PERL_VERSION"] = MacOS.preferred_perl_version
end
true sig { returns(T::Boolean) }
end def no_weak_imports_support?
return false if compiler != :clang
sig { returns(T::Boolean) } return false if !MacOS::Xcode.version.null? && MacOS::Xcode.version < "8.0"
def no_fixup_chains_support? return false if !MacOS::CLT.version.null? && MacOS::CLT.version < "8.0"
# This is supported starting Xcode 13, which ships ld64-711.
# https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes true
# https://en.wikipedia.org/wiki/Xcode#Xcode_11.0_-_14.x_(since_SwiftUI_framework)_2 end
OS::Mac::DevelopmentTools.ld64_version >= 711
sig { returns(T::Boolean) }
def no_fixup_chains_support?
# This is supported starting Xcode 13, which ships ld64-711.
# https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes
# https://en.wikipedia.org/wiki/Xcode#Xcode_11.0_-_14.x_(since_SwiftUI_framework)_2
OS::Mac::DevelopmentTools.ld64_version >= 711
end
end
end end
end end
SharedEnvExtension.prepend(OS::Mac::SharedEnvExtension)

View File

@ -1,117 +1,125 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Stdenv module OS
undef homebrew_extra_pkg_config_paths module Mac
module Stdenv
extend T::Helpers
sig { returns(T::Array[Pathname]) } requires_ancestor { SharedEnvExtension }
def homebrew_extra_pkg_config_paths requires_ancestor { ::Stdenv }
[Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
sig { sig { returns(T::Array[Pathname]) }
params( def homebrew_extra_pkg_config_paths
formula: T.nilable(Formula), [Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
cc: T.nilable(String), end
build_bottle: T.nilable(T::Boolean), private :homebrew_extra_pkg_config_paths
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
append "LDFLAGS", "-Wl,-headerpad_max_install_names" sig {
params(
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
debug_symbols: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
super
# `sed` is strict and errors out when it encounters files with mixed character sets. append "LDFLAGS", "-Wl,-headerpad_max_install_names"
delete("LC_ALL")
self["LC_CTYPE"] = "C"
# Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags: # `sed` is strict and errors out when it encounters files with mixed character sets.
macosxsdk(formula: @formula, testing_formula:) delete("LC_ALL")
self["LC_CTYPE"] = "C"
return unless MacOS::Xcode.without_clt? # Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags:
macosxsdk(formula: @formula, testing_formula:)
append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin" return unless MacOS::Xcode.without_clt?
append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
end
def remove_macosxsdk(version = nil) append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin"
# Clear all `lib` and `include` dirs from `CFLAGS`, `CPPFLAGS`, `LDFLAGS` that were append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin"
# previously added by `macosxsdk`. end
remove_from_cflags(/ ?-mmacosx-version-min=\d+\.\d+/)
delete("CPATH")
remove "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version) def remove_macosxsdk(version = nil)
return unless sdk # Clear all `lib` and `include` dirs from `CFLAGS`, `CPPFLAGS`, `LDFLAGS` that were
# previously added by `macosxsdk`.
remove_from_cflags(/ ?-mmacosx-version-min=\d+\.\d+/)
delete("CPATH")
remove "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
delete("SDKROOT") sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version)
remove_from_cflags "-isysroot#{sdk}" return unless sdk
remove "CPPFLAGS", "-isysroot#{sdk}"
remove "LDFLAGS", "-isysroot#{sdk}" delete("SDKROOT")
if HOMEBREW_PREFIX.to_s == "/usr/local" remove_from_cflags "-isysroot#{sdk}"
delete("CMAKE_PREFIX_PATH") remove "CPPFLAGS", "-isysroot#{sdk}"
else remove "LDFLAGS", "-isysroot#{sdk}"
# It was set in `setup_build_environment`, so we have to restore it here. if HOMEBREW_PREFIX.to_s == "/usr/local"
self["CMAKE_PREFIX_PATH"] = HOMEBREW_PREFIX.to_s delete("CMAKE_PREFIX_PATH")
else
# It was set in `setup_build_environment`, so we have to restore it here.
self["CMAKE_PREFIX_PATH"] = HOMEBREW_PREFIX.to_s
end
remove "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
def macosxsdk(version = nil, formula: nil, testing_formula: false)
# Sets all needed `lib` and `include` dirs to `CFLAGS`, `CPPFLAGS`, `LDFLAGS`.
remove_macosxsdk
min_version = version || MacOS.version
append_to_cflags("-mmacosx-version-min=#{min_version}")
self["CPATH"] = "#{HOMEBREW_PREFIX}/include"
prepend "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = if formula
MacOS.sdk_for_formula(formula, version, check_only_runtime_requirements: testing_formula)
else
MacOS.sdk(version)
end
return if !MacOS.sdk_root_needed? && sdk&.source != :xcode
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
sdk = sdk.path
# Extra setup to support Xcode 4.3+ without CLT.
self["SDKROOT"] = sdk
# Tell clang/gcc where system include's are:
append_path "CPATH", "#{sdk}/usr/include"
# The -isysroot is needed, too, because of the Frameworks
append_to_cflags "-isysroot#{sdk}"
append "CPPFLAGS", "-isysroot#{sdk}"
# And the linker needs to find sdk/usr/lib
append "LDFLAGS", "-isysroot#{sdk}"
# Needed to build cmake itself and perhaps some cmake projects:
append_path "CMAKE_PREFIX_PATH", "#{sdk}/usr"
append_path "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
# Some configure scripts won't find libxml2 without help.
# This is a no-op with macOS SDK 10.15.4 and later.
def libxml2
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed
if !sdk
append "CPPFLAGS", "-I/usr/include/libxml2"
elsif !Pathname("#{sdk}/usr/include/libxml").directory?
# Use the includes form the sdk
append "CPPFLAGS", "-I#{sdk}/usr/include/libxml2"
end
end
def no_weak_imports
append "LDFLAGS", "-Wl,-no_weak_imports" if no_weak_imports_support?
end
def no_fixup_chains
append "LDFLAGS", "-Wl,-no_fixup_chains" if no_fixup_chains_support?
end
end end
remove "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
def macosxsdk(version = nil, formula: nil, testing_formula: false)
# Sets all needed `lib` and `include` dirs to `CFLAGS`, `CPPFLAGS`, `LDFLAGS`.
remove_macosxsdk
min_version = version || MacOS.version
append_to_cflags("-mmacosx-version-min=#{min_version}")
self["CPATH"] = "#{HOMEBREW_PREFIX}/include"
prepend "LDFLAGS", "-L#{HOMEBREW_PREFIX}/lib"
sdk = if formula
MacOS.sdk_for_formula(formula, version, check_only_runtime_requirements: testing_formula)
else
MacOS.sdk(version)
end
return if !MacOS.sdk_root_needed? && sdk&.source != :xcode
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
sdk = sdk.path
# Extra setup to support Xcode 4.3+ without CLT.
self["SDKROOT"] = sdk
# Tell clang/gcc where system include's are:
append_path "CPATH", "#{sdk}/usr/include"
# The -isysroot is needed, too, because of the Frameworks
append_to_cflags "-isysroot#{sdk}"
append "CPPFLAGS", "-isysroot#{sdk}"
# And the linker needs to find sdk/usr/lib
append "LDFLAGS", "-isysroot#{sdk}"
# Needed to build cmake itself and perhaps some cmake projects:
append_path "CMAKE_PREFIX_PATH", "#{sdk}/usr"
append_path "CMAKE_FRAMEWORK_PATH", "#{sdk}/System/Library/Frameworks"
end
# Some configure scripts won't find libxml2 without help.
# This is a no-op with macOS SDK 10.15.4 and later.
def libxml2
sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed
if !sdk
append "CPPFLAGS", "-I/usr/include/libxml2"
elsif !Pathname("#{sdk}/usr/include/libxml").directory?
# Use the includes form the sdk
append "CPPFLAGS", "-I#{sdk}/usr/include/libxml2"
end
end
def no_weak_imports
append "LDFLAGS", "-Wl,-no_weak_imports" if no_weak_imports_support?
end
def no_fixup_chains
append "LDFLAGS", "-Wl,-no_fixup_chains" if no_fixup_chains_support?
end end
end end
Stdenv.prepend(OS::Mac::Stdenv)

View File

@ -1,171 +1,173 @@
# typed: true # rubocop:disable Sorbet/StrictSigil # typed: true # rubocop:disable Sorbet/StrictSigil
# frozen_string_literal: true # frozen_string_literal: true
module Superenv module OS
class << self module Mac
# The location of Homebrew's shims on macOS. module Superenv
def shims_path extend T::Helpers
HOMEBREW_SHIMS_PATH/"mac/super"
end
undef bin requires_ancestor { SharedEnvExtension }
requires_ancestor { ::Superenv }
def bin module ClassMethods
return unless DevelopmentTools.installed? sig { returns(Pathname) }
def shims_path
HOMEBREW_SHIMS_PATH/"mac/super"
end
shims_path.realpath sig { returns(T.nilable(Pathname)) }
end def bin
end return unless ::DevelopmentTools.installed?
undef homebrew_extra_pkg_config_paths, shims_path.realpath
homebrew_extra_isystem_paths, homebrew_extra_library_paths, end
homebrew_extra_cmake_include_paths,
homebrew_extra_cmake_library_paths,
homebrew_extra_cmake_frameworks_paths,
determine_cccfg
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("/usr/lib/pkgconfig"), Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
private :homebrew_extra_pkg_config_paths
sig { returns(T::Boolean) }
def libxml2_include_needed?
return false if deps.any? { |d| d.name == "libxml2" }
return false if Pathname("#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml").directory?
true
end
private :libxml2_include_needed?
def homebrew_extra_isystem_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_library_paths
paths = []
if compiler == :llvm_clang
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/lib"
paths << Formula["llvm"].opt_lib.to_s
end
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths
end
def homebrew_extra_cmake_include_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_cmake_library_paths
[Pathname("#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries")]
end
def homebrew_extra_cmake_frameworks_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks" if MacOS::Xcode.without_clt?
paths
end
def determine_cccfg
s = +""
# Fix issue with >= Mountain Lion apr-1-config having broken paths
s << "a"
s.freeze
end
# @private
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false,
debug_symbols: false)
sdk = formula ? MacOS.sdk_for_formula(formula) : MacOS.sdk
is_xcode_sdk = sdk&.source == :xcode
if is_xcode_sdk || MacOS.sdk_root_needed?
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
self["HOMEBREW_SDKROOT"] = sdk.path if sdk
end
self["HOMEBREW_DEVELOPER_DIR"] = if is_xcode_sdk
MacOS::Xcode.prefix.to_s
else
MacOS::CLT::PKG_PATH
end
# This is a workaround for the missing `m4` in Xcode CLT 15.3, which was
# reported in FB13679972. Apple has fixed this in Xcode CLT 16.0.
# See https://github.com/Homebrew/homebrew-core/issues/165388
if deps.none? { |d| d.name == "m4" } &&
MacOS.active_developer_dir == MacOS::CLT::PKG_PATH &&
!File.exist?("#{MacOS::CLT::PKG_PATH}/usr/bin/m4") &&
(gm4 = DevelopmentTools.locate("gm4").to_s).present?
self["M4"] = gm4
end
generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:,
testing_formula:, debug_symbols:)
# Filter out symbols known not to be defined since GNU Autotools can't
# reliably figure this out with Xcode 8 and above.
if MacOS.version == "10.12" && MacOS::Xcode.version >= "9.0"
%w[fmemopen futimens open_memstream utimensat].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
elsif MacOS.version == "10.11" && MacOS::Xcode.version >= "8.0"
%w[basename_r clock_getres clock_gettime clock_settime dirname_r
getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end end
ENV["ac_cv_search_clock_gettime"] = "no" sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[Pathname("/usr/lib/pkgconfig"), Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")]
end
# works around libev.m4 unsetting ac_cv_func_clock_gettime sig { returns(T::Boolean) }
ENV["ac_have_clock_syscall"] = "no" def libxml2_include_needed?
return false if deps.any? { |d| d.name == "libxml2" }
return false if Pathname("#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml").directory?
true
end
def homebrew_extra_isystem_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_library_paths
paths = []
if compiler == :llvm_clang
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/lib"
paths << ::Formula["llvm"].opt_lib.to_s
end
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries"
paths
end
def homebrew_extra_cmake_include_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/libxml2" if libxml2_include_needed?
paths << "#{self["HOMEBREW_SDKROOT"]}/usr/include/apache2" if MacOS::Xcode.without_clt?
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers"
paths
end
def homebrew_extra_cmake_library_paths
brew_sdkroot = self["HOMEBREW_SDKROOT"]
[Pathname("#{brew_sdkroot}/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries")]
end
def homebrew_extra_cmake_frameworks_paths
paths = []
paths << "#{self["HOMEBREW_SDKROOT"]}/System/Library/Frameworks" if MacOS::Xcode.without_clt?
paths
end
def determine_cccfg
s = +""
# Fix issue with >= Mountain Lion apr-1-config having broken paths
s << "a"
s.freeze
end
# @private
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil,
testing_formula: false, debug_symbols: false)
sdk = formula ? MacOS.sdk_for_formula(formula) : MacOS.sdk
is_xcode_sdk = sdk&.source == :xcode
if is_xcode_sdk || MacOS.sdk_root_needed?
Homebrew::Diagnostic.checks(:fatal_setup_build_environment_checks)
self["HOMEBREW_SDKROOT"] = sdk.path if sdk
end
self["HOMEBREW_DEVELOPER_DIR"] = if is_xcode_sdk
MacOS::Xcode.prefix.to_s
else
MacOS::CLT::PKG_PATH
end
# This is a workaround for the missing `m4` in Xcode CLT 15.3, which was
# reported in FB13679972. Apple has fixed this in Xcode CLT 16.0.
# See https://github.com/Homebrew/homebrew-core/issues/165388
if deps.none? { |d| d.name == "m4" } &&
MacOS.active_developer_dir == MacOS::CLT::PKG_PATH &&
!File.exist?("#{MacOS::CLT::PKG_PATH}/usr/bin/m4") &&
(gm4 = ::DevelopmentTools.locate("gm4").to_s).present?
self["M4"] = gm4
end
super
# Filter out symbols known not to be defined since GNU Autotools can't
# reliably figure this out with Xcode 8 and above.
if MacOS.version == "10.12" && MacOS::Xcode.version >= "9.0"
%w[fmemopen futimens open_memstream utimensat].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
elsif MacOS.version == "10.11" && MacOS::Xcode.version >= "8.0"
%w[basename_r clock_getres clock_gettime clock_settime dirname_r
getentropy mkostemp mkostemps timingsafe_bcmp].each do |s|
ENV["ac_cv_func_#{s}"] = "no"
end
ENV["ac_cv_search_clock_gettime"] = "no"
# works around libev.m4 unsetting ac_cv_func_clock_gettime
ENV["ac_have_clock_syscall"] = "no"
end
# On macOS Sonoma (at least release candidate), iconv() is generally
# present and working, but has a minor regression that defeats the
# test implemented in gettext's configure script (and used by many
# gettext dependents).
ENV["am_cv_func_iconv_works"] = "yes" if MacOS.version == "14"
# The tools in /usr/bin proxy to the active developer directory.
# This means we can use them for any combination of CLT and Xcode.
self["HOMEBREW_PREFER_CLT_PROXIES"] = "1"
# Deterministic timestamping.
# This can work on older Xcode versions, but they contain some bugs.
# Notably, Xcode 10.2 fixes issues where ZERO_AR_DATE affected file mtimes.
# Xcode 11.0 contains fixes for lldb reading things built with ZERO_AR_DATE.
self["ZERO_AR_DATE"] = "1" if MacOS::Xcode.version >= "11.0" || MacOS::CLT.version >= "11.0"
# Pass `-no_fixup_chains` whenever the linker is invoked with `-undefined dynamic_lookup`.
# See: https://github.com/python/cpython/issues/97524
# https://github.com/pybind/pybind11/pull/4301
no_fixup_chains
# Strip build prefixes from linker where supported, for deterministic builds.
append_to_cccfg "o" if OS::Mac::DevelopmentTools.ld64_version >= 512
# Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs`
# on `ld` versions that don't properly handle that option.
return unless OS::Mac::DevelopmentTools.ld64_version.between?("1015.7", "1022.1")
append_to_cccfg "c"
end
def no_weak_imports
append_to_cccfg "w" if no_weak_imports_support?
end
def no_fixup_chains
append_to_cccfg "f" if no_fixup_chains_support?
end
end end
# On macOS Sonoma (at least release candidate), iconv() is generally
# present and working, but has a minor regression that defeats the
# test implemented in gettext's configure script (and used by many
# gettext dependents).
ENV["am_cv_func_iconv_works"] = "yes" if MacOS.version == "14"
# The tools in /usr/bin proxy to the active developer directory.
# This means we can use them for any combination of CLT and Xcode.
self["HOMEBREW_PREFER_CLT_PROXIES"] = "1"
# Deterministic timestamping.
# This can work on older Xcode versions, but they contain some bugs.
# Notably, Xcode 10.2 fixes issues where ZERO_AR_DATE affected file mtimes.
# Xcode 11.0 contains fixes for lldb reading things built with ZERO_AR_DATE.
self["ZERO_AR_DATE"] = "1" if MacOS::Xcode.version >= "11.0" || MacOS::CLT.version >= "11.0"
# Pass `-no_fixup_chains` whenever the linker is invoked with `-undefined dynamic_lookup`.
# See: https://github.com/python/cpython/issues/97524
# https://github.com/pybind/pybind11/pull/4301
no_fixup_chains
# Strip build prefixes from linker where supported, for deterministic builds.
append_to_cccfg "o" if OS::Mac::DevelopmentTools.ld64_version >= 512
# Pass `-ld_classic` whenever the linker is invoked with `-dead_strip_dylibs`
# on `ld` versions that don't properly handle that option.
return unless OS::Mac::DevelopmentTools.ld64_version.between?("1015.7", "1022.1")
append_to_cccfg "c"
end
def no_weak_imports
append_to_cccfg "w" if no_weak_imports_support?
end
def no_fixup_chains
append_to_cccfg "f" if no_fixup_chains_support?
end end
end end
Superenv.singleton_class.prepend(OS::Mac::Superenv::ClassMethods)
Superenv.prepend(OS::Mac::Superenv)

View File

@ -4,133 +4,145 @@
require "cache_store" require "cache_store"
require "linkage_checker" require "linkage_checker"
module FormulaCellarChecks module OS
sig { returns(T.nilable(String)) } module Mac
def check_shadowed_headers module FormulaCellarChecks
return if ["libtool", "subversion", "berkeley-db"].any? do |formula_name| extend T::Helpers
formula.name.start_with?(formula_name)
end
return if formula.name.match?(Version.formula_optionally_versioned_regex(:php)) requires_ancestor { Homebrew::FormulaAuditor }
return if formula.keg_only? || !formula.include.directory? requires_ancestor { ::FormulaCellarChecks }
files = relative_glob(formula.include, "**/*.h") sig { returns(T.nilable(String)) }
files &= relative_glob("#{MacOS.sdk_path}/usr/include", "**/*.h") def check_shadowed_headers
files.map! { |p| File.join(formula.include, p) } return if ["libtool", "subversion", "berkeley-db"].any? do |formula_name|
formula.name.start_with?(formula_name)
end
return if files.empty? return if formula.name.match?(Version.formula_optionally_versioned_regex(:php))
return if formula.keg_only? || !formula.include.directory?
<<~EOS files = relative_glob(formula.include, "**/*.h")
Header files that shadow system header files were installed to "#{formula.include}" files &= relative_glob("#{MacOS.sdk_path}/usr/include", "**/*.h")
The offending files are: files.map! { |p| File.join(formula.include, p) }
#{files * "\n "}
EOS
end
sig { returns(T.nilable(String)) } return if files.empty?
def check_openssl_links
return unless formula.prefix.directory?
keg = Keg.new(formula.prefix) <<~EOS
system_openssl = keg.mach_o_files.select do |obj| Header files that shadow system header files were installed to "#{formula.include}"
dlls = obj.dynamically_linked_libraries The offending files are:
dlls.any? { |dll| %r{/usr/lib/lib(crypto|ssl|tls)\..*dylib}.match? dll } #{files * "\n "}
end
return if system_openssl.empty?
<<~EOS
object files were linked against system openssl
These object files were linked against the deprecated system OpenSSL or
the system's private LibreSSL.
Adding `depends_on "openssl"` to the formula may help.
#{system_openssl * "\n "}
EOS
end
sig { params(lib: Pathname).returns(T.nilable(String)) }
def check_python_framework_links(lib)
python_modules = Pathname.glob lib/"python*/site-packages/**/*.so"
framework_links = python_modules.select do |obj|
dlls = obj.dynamically_linked_libraries
dlls.any? { |dll| dll.include?("Python.framework") }
end
return if framework_links.empty?
<<~EOS
python modules have explicit framework links
These python extension modules were linked directly to a Python
framework binary. They should be linked with -undefined dynamic_lookup
instead of -lpython or -framework Python.
#{framework_links * "\n "}
EOS
end
sig { void }
def check_linkage
return unless formula.prefix.directory?
keg = Keg.new(formula.prefix)
CacheStoreDatabase.use(:linkage) do |db|
checker = LinkageChecker.new(keg, formula, cache_db: db)
next unless checker.broken_library_linkage?
output = <<~EOS
#{formula} has broken dynamic library links:
#{checker.display_test_output}
EOS
tab = keg.tab
if tab.poured_from_bottle
output += <<~EOS
Rebuild this from source with:
brew reinstall --build-from-source #{formula}
If that's successful, file an issue#{formula.tap ? " here:\n #{T.must(formula.tap).issues_url}" : "."}
EOS EOS
end end
problem_if_output output
end
end
sig { params(formula: Formula).returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def check_flat_namespace(formula) def check_openssl_links
return unless formula.prefix.directory? return unless formula.prefix.directory?
return if formula.tap&.audit_exception(:flat_namespace_allowlist, formula.name)
keg = ::Keg.new(formula.prefix) keg = ::Keg.new(formula.prefix)
flat_namespace_files = keg.mach_o_files.reject do |file| system_openssl = keg.mach_o_files.select do |obj|
next true unless file.dylib? dlls = obj.dynamically_linked_libraries
dlls.any? { |dll| %r{/usr/lib/lib(crypto|ssl|tls)\..*dylib}.match? dll }
end
return if system_openssl.empty?
macho = MachO.open(file) <<~EOS
if MachO::Utils.fat_magic?(macho.magic) object files were linked against system openssl
macho.machos.map(&:header).all? { |h| h.flag? :MH_TWOLEVEL } These object files were linked against the deprecated system OpenSSL or
else the system's private LibreSSL.
macho.header.flag? :MH_TWOLEVEL Adding `depends_on "openssl"` to the formula may help.
#{system_openssl * "\n "}
EOS
end
sig { params(lib: Pathname).returns(T.nilable(String)) }
def check_python_framework_links(lib)
python_modules = Pathname.glob lib/"python*/site-packages/**/*.so"
framework_links = python_modules.select do |obj|
dlls = obj.dynamically_linked_libraries
dlls.any? { |dll| dll.include?("Python.framework") }
end
return if framework_links.empty?
<<~EOS
python modules have explicit framework links
These python extension modules were linked directly to a Python
framework binary. They should be linked with -undefined dynamic_lookup
instead of -lpython or -framework Python.
#{framework_links * "\n "}
EOS
end
sig { void }
def check_linkage
return unless formula.prefix.directory?
keg = ::Keg.new(formula.prefix)
CacheStoreDatabase.use(:linkage) do |db|
checker = ::LinkageChecker.new(keg, formula, cache_db: db)
next unless checker.broken_library_linkage?
output = <<~EOS
#{formula} has broken dynamic library links:
#{checker.display_test_output}
EOS
tab = keg.tab
if tab.poured_from_bottle
output += <<~EOS
Rebuild this from source with:
brew reinstall --build-from-source #{formula}
If that's successful, file an issue#{formula.tap ? " here:\n #{formula.tap.issues_url}" : "."}
EOS
end
problem_if_output output
end
end
sig { params(formula: ::Formula).returns(T.nilable(String)) }
def check_flat_namespace(formula)
return unless formula.prefix.directory?
return if formula.tap&.audit_exception(:flat_namespace_allowlist, formula.name)
keg = ::Keg.new(formula.prefix)
flat_namespace_files = keg.mach_o_files.reject do |file|
next true unless file.dylib?
macho = MachO.open(file)
if MachO::Utils.fat_magic?(macho.magic)
macho.machos.map(&:header).all? { |h| h.flag? :MH_TWOLEVEL }
else
macho.header.flag? :MH_TWOLEVEL
end
end
return if flat_namespace_files.empty?
<<~EOS
Libraries were compiled with a flat namespace.
This can cause linker errors due to name collisions and
is often due to a bug in detecting the macOS version.
#{flat_namespace_files * "\n "}
EOS
end
sig { void }
def audit_installed
super
problem_if_output(check_shadowed_headers)
problem_if_output(check_openssl_links)
problem_if_output(check_python_framework_links(formula.lib))
check_linkage
problem_if_output(check_flat_namespace(formula))
end
MACOS_LIB_EXTENSIONS = %w[.dylib .framework].freeze
sig { params(filename: Pathname).returns(T::Boolean) }
def valid_library_extension?(filename)
super || MACOS_LIB_EXTENSIONS.include?(filename.extname)
end end
end end
return if flat_namespace_files.empty?
<<~EOS
Libraries were compiled with a flat namespace.
This can cause linker errors due to name collisions and
is often due to a bug in detecting the macOS version.
#{flat_namespace_files * "\n "}
EOS
end
sig { void }
def audit_installed
generic_audit_installed
problem_if_output(check_shadowed_headers)
problem_if_output(check_openssl_links)
problem_if_output(check_python_framework_links(formula.lib))
check_linkage
problem_if_output(check_flat_namespace(formula))
end
sig { params(filename: Pathname).returns(T::Boolean) }
def valid_library_extension?(filename)
macos_lib_extensions = %w[.dylib .framework]
generic_valid_library_extension?(filename) || macos_lib_extensions.include?(filename.extname)
end end
end end
FormulaCellarChecks.prepend(OS::Mac::FormulaCellarChecks)

View File

@ -1,25 +1,33 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Hardware module OS
sig { params(version: T.nilable(Version)).returns(Symbol) } module Mac
def self.oldest_cpu(version = nil) module Hardware
version = if version module ClassMethods
MacOSVersion.new(version.to_s) sig { params(version: T.nilable(MacOSVersion)).returns(Symbol) }
else def oldest_cpu(version = nil)
MacOS.version version = if version
end MacOSVersion.new(version.to_s)
if CPU.arch == :arm64 else
:arm_vortex_tempest MacOS.version
# This cannot use a newer CPU e.g. haswell because Rosetta 2 does not end
# support AVX instructions in bottles: if ::Hardware::CPU.arch == :arm64
# https://github.com/Homebrew/homebrew-core/issues/67713 :arm_vortex_tempest
elsif version >= :ventura # This cannot use a newer CPU e.g. haswell because Rosetta 2 does not
:westmere # support AVX instructions in bottles:
elsif version >= :mojave # https://github.com/Homebrew/homebrew-core/issues/67713
:nehalem elsif version >= :ventura
else :westmere
generic_oldest_cpu elsif version >= :mojave
:nehalem
else
super
end
end
end
end end
end end
end end
Hardware.singleton_class.prepend(OS::Mac::Hardware::ClassMethods)

View File

@ -95,7 +95,7 @@ module OS
end end
end end
generic_fix_dynamic_linkage super
end end
def loader_name_for(file, target) def loader_name_for(file, target)
@ -199,7 +199,7 @@ module OS
end end
def prepare_relocation_to_locations def prepare_relocation_to_locations
relocation = generic_prepare_relocation_to_locations relocation = super
brewed_perl = runtime_dependencies&.any? { |dep| dep["full_name"] == "perl" && dep["declared_directly"] } brewed_perl = runtime_dependencies&.any? { |dep| dep["full_name"] == "perl" && dep["declared_directly"] }
perl_path = if brewed_perl || name == "perl" perl_path = if brewed_perl || name == "perl"

View File

@ -5,55 +5,59 @@ require "cask/info"
require "cask/cask_loader" require "cask/cask_loader"
require "cask/caskroom" require "cask/caskroom"
module Homebrew module OS
module MissingFormula module Mac
class << self module MissingFormula
sig { params(name: String).returns(T.nilable(String)) } module ClassMethods
def disallowed_reason(name) sig { params(name: String).returns(T.nilable(String)) }
case name.downcase def disallowed_reason(name)
when "xcode" case name.downcase
<<~EOS when "xcode"
Xcode can be installed from the App Store. <<~EOS
EOS Xcode can be installed from the App Store.
else EOS
generic_disallowed_reason(name) else
super
end
end end
end
sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) } sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) }
def cask_reason(name, silent: false, show_info: false) def cask_reason(name, silent: false, show_info: false)
return if silent return if silent
suggest_command(name, show_info ? "info" : "install") suggest_command(name, show_info ? "info" : "install")
end end
sig { params(name: String, command: String).returns(T.nilable(String)) } sig { params(name: String, command: String).returns(T.nilable(String)) }
def suggest_command(name, command) def suggest_command(name, command)
suggestion = <<~EOS
Found a cask named "#{name}" instead. Try
brew #{command} --cask #{name}
EOS
case command
when "install"
Cask::CaskLoader.load(name)
when "uninstall"
cask = Cask::Caskroom.casks.find { |installed_cask| installed_cask.to_s == name }
raise Cask::CaskUnavailableError, name if cask.nil?
when "info"
cask = Cask::CaskLoader.load(name)
suggestion = <<~EOS suggestion = <<~EOS
Found a cask named "#{name}" instead. Found a cask named "#{name}" instead. Try
brew #{command} --cask #{name}
#{Cask::Info.get_info(cask)}
EOS EOS
else case command
return when "install"
::Cask::CaskLoader.load(name)
when "uninstall"
cask = ::Cask::Caskroom.casks.find { |installed_cask| installed_cask.to_s == name }
Kernel.raise ::Cask::CaskUnavailableError, name if cask.nil?
when "info"
cask = ::Cask::CaskLoader.load(name)
suggestion = <<~EOS
Found a cask named "#{name}" instead.
#{::Cask::Info.get_info(cask)}
EOS
else
return
end
suggestion
rescue ::Cask::CaskUnavailableError
nil
end end
suggestion
rescue Cask::CaskUnavailableError
nil
end end
end end
end end
end end
Homebrew::MissingFormula.singleton_class.prepend(OS::Mac::MissingFormula::ClassMethods)

View File

@ -6,46 +6,45 @@ require "system_command"
module OS module OS
module Mac module Mac
module SystemConfig module SystemConfig
sig { returns(String) } module ClassMethods
def describe_clang extend T::Helpers
return "N/A" if ::SystemConfig.clang.null?
clang_build_info = ::SystemConfig.clang_build.null? ? "(parse error)" : ::SystemConfig.clang_build requires_ancestor { T.class_of(::SystemConfig) }
"#{::SystemConfig.clang} build #{clang_build_info}"
sig { returns(String) }
def describe_clang
return "N/A" if ::SystemConfig.clang.null?
clang_build_info = ::SystemConfig.clang_build.null? ? "(parse error)" : ::SystemConfig.clang_build
"#{::SystemConfig.clang} build #{clang_build_info}"
end
def xcode
@xcode ||= if MacOS::Xcode.installed?
xcode = MacOS::Xcode.version.to_s
xcode += " => #{MacOS::Xcode.prefix}" unless MacOS::Xcode.default_prefix?
xcode
end
end
def clt
@clt ||= MacOS::CLT.version if MacOS::CLT.installed?
end
def core_tap_config(out = $stdout)
dump_tap_config(CoreTap.instance, out)
dump_tap_config(CoreCaskTap.instance, out)
end
def dump_verbose_config(out = $stdout)
super
out.puts "macOS: #{MacOS.full_version}-#{kernel}"
out.puts "CLT: #{clt || "N/A"}"
out.puts "Xcode: #{xcode || "N/A"}"
out.puts "Rosetta 2: #{::Hardware::CPU.in_rosetta2?}" if ::Hardware::CPU.physical_cpu_arm64?
end
end end
end end
end end
end end
SystemConfig.singleton_class.prepend(OS::Mac::SystemConfig::ClassMethods)
SystemConfig.prepend(OS::Mac::SystemConfig)
module SystemConfig
class << self
include SystemCommand::Mixin
def xcode
@xcode ||= if MacOS::Xcode.installed?
xcode = MacOS::Xcode.version.to_s
xcode += " => #{MacOS::Xcode.prefix}" unless MacOS::Xcode.default_prefix?
xcode
end
end
def clt
@clt ||= MacOS::CLT.version if MacOS::CLT.installed?
end
def core_tap_config(out = $stdout)
dump_tap_config(CoreTap.instance, out)
dump_tap_config(CoreCaskTap.instance, out)
end
def dump_verbose_config(out = $stdout)
dump_generic_verbose_config(out)
out.puts "macOS: #{MacOS.full_version}-#{kernel}"
out.puts "CLT: #{clt || "N/A"}"
out.puts "Xcode: #{xcode || "N/A"}"
out.puts "Rosetta 2: #{Hardware::CPU.in_rosetta2?}" if Hardware::CPU.physical_cpu_arm64?
end
end
end

View File

@ -1,59 +1,66 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
module Utils module OS
module Bottles module Mac
class << self module Bottles
module MacOSOverride module ClassMethods
sig { params(tag: T.nilable(T.any(Symbol, Tag))).returns(Tag) } sig { params(tag: T.nilable(T.any(Symbol, Utils::Bottles::Tag))).returns(Utils::Bottles::Tag) }
def tag(tag = nil) def tag(tag = nil)
return Tag.new(system: MacOS.version.to_sym, arch: Hardware::CPU.arch) if tag.nil? if tag.nil?
Utils::Bottles::Tag.new(system: MacOS.version.to_sym, arch: ::Hardware::CPU.arch)
super else
super
end
end end
end end
prepend MacOSOverride module Collector
end extend T::Helpers
class Collector requires_ancestor { Utils::Bottles::Collector }
private
alias generic_find_matching_tag find_matching_tag private
sig { params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) } sig {
def find_matching_tag(tag, no_older_versions: false) params(tag: Utils::Bottles::Tag,
# Used primarily by developers testing beta macOS releases. no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag))
if no_older_versions || }
(OS::Mac.version.prerelease? && def find_matching_tag(tag, no_older_versions: false)
Homebrew::EnvConfig.developer? && # Used primarily by developers testing beta macOS releases.
Homebrew::EnvConfig.skip_or_later_bottles?) if no_older_versions ||
generic_find_matching_tag(tag) (OS::Mac.version.prerelease? &&
else Homebrew::EnvConfig.developer? &&
generic_find_matching_tag(tag) || Homebrew::EnvConfig.skip_or_later_bottles?)
find_older_compatible_tag(tag) super(tag)
end else
end super(tag) || find_older_compatible_tag(tag)
end
# Find a bottle built for a previous version of macOS.
sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) }
def find_older_compatible_tag(tag)
tag_version = begin
tag.to_macos_version
rescue MacOSVersion::Error
nil
end end
return if tag_version.blank? # Find a bottle built for a previous version of macOS.
sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) }
def find_older_compatible_tag(tag)
tag_version = begin
tag.to_macos_version
rescue MacOSVersion::Error
nil
end
tags.find do |candidate| return if tag_version.blank?
next if candidate.standardized_arch != tag.standardized_arch
candidate.to_macos_version <= tag_version tags.find do |candidate|
rescue MacOSVersion::Error next if candidate.standardized_arch != tag.standardized_arch
false
candidate.to_macos_version <= tag_version
rescue MacOSVersion::Error
false
end
end end
end end
end end
end end
end end
Utils::Bottles.singleton_class.prepend(OS::Mac::Bottles::ClassMethods)
Utils::Bottles::Collector.prepend(OS::Mac::Bottles::Collector)

View File

@ -84,7 +84,6 @@ module FormulaCellarChecks
def valid_library_extension?(filename) def valid_library_extension?(filename)
VALID_LIBRARY_EXTENSIONS.include? filename.extname VALID_LIBRARY_EXTENSIONS.include? filename.extname
end end
alias generic_valid_library_extension? valid_library_extension?
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def check_non_libraries def check_non_libraries
@ -437,7 +436,6 @@ module FormulaCellarChecks
problem_if_output(check_cpuid_instruction(formula)) problem_if_output(check_cpuid_instruction(formula))
problem_if_output(check_binary_arches(formula)) problem_if_output(check_binary_arches(formula))
end end
alias generic_audit_installed audit_installed
private private

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "digest" require "digest"
@ -7,62 +7,81 @@ require "erb"
module Homebrew module Homebrew
# Class for generating a formula from a template. # Class for generating a formula from a template.
class FormulaCreator class FormulaCreator
sig { returns(String) }
attr_accessor :name attr_accessor :name
sig { returns(Version) }
attr_reader :version
sig { returns(T::Boolean) }
attr_reader :head
sig { sig {
params(name: T.nilable(String), version: T.nilable(String), tap: T.nilable(String), url: String, params(url: String, name: T.nilable(String), version: T.nilable(String), tap: T.nilable(String),
mode: T.nilable(Symbol), license: T.nilable(String), fetch: T::Boolean, head: T::Boolean).void mode: T.nilable(Symbol), license: T.nilable(String), fetch: T::Boolean, head: T::Boolean).void
} }
def initialize(name, version, tap:, url:, mode:, license:, fetch:, head:) def initialize(url:, name: nil, version: nil, tap: nil, mode: nil, license: nil, fetch: false, head: false)
@name = name
@version = Version.new(version) if version
@tap = Tap.fetch(tap || "homebrew/core")
@url = url @url = url
@mode = mode
@license = license
@fetch = fetch
@head = head
end
sig { void } if name.blank?
def verify stem = Pathname.new(url).stem
raise TapUnavailableError, @tap.name unless @tap.installed? name = if stem.start_with?("index.cgi") && stem.include?("=")
end # special cases first
# gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary
sig { params(url: String).returns(T.nilable(String)) } stem.rpartition("=").last
def self.name_from_url(url) elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/}
stem = Pathname.new(url).stem # e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz
# special cases first T.must(Regexp.last_match(1))
if stem.start_with? "index.cgi" else
# gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary # e.g. http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz
stem.rpartition("=").last pathver = Version.parse(stem).to_s
elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/} stem.sub(/[-_.]?#{Regexp.escape(pathver)}$/, "")
# e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz end
Regexp.last_match(1) odebug "name from url: #{name}"
else
# e.g. http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz
pathver = Version.parse(stem).to_s
stem.sub(/[-_.]?#{Regexp.escape(pathver)}$/, "")
end end
end @name = T.let(name, String)
sig { void } version = if version.present?
def parse_url Version.new(version)
@name = FormulaCreator.name_from_url(@url) if @name.blank? else
odebug "name_from_url: #{@name}" Version.detect(url)
@version = Version.detect(@url) if @version.nil? end
@version = T.let(version, Version)
case @url tap = if tap.blank?
CoreTap.instance
else
Tap.fetch(tap)
end
@tap = T.let(tap, Tap)
@mode = T.let(mode.presence, T.nilable(Symbol))
@license = T.let(license.presence, T.nilable(String))
@fetch = fetch
case url
when %r{github\.com/(\S+)/(\S+)\.git} when %r{github\.com/(\S+)/(\S+)\.git}
@head = true head = true
user = Regexp.last_match(1) user = Regexp.last_match(1)
repo = Regexp.last_match(2) repository = Regexp.last_match(2)
@github = GitHub.repository(user, repo) if @fetch github = GitHub.repository(user, repository) if fetch
when %r{github\.com/(\S+)/(\S+)/(archive|releases)/} when %r{github\.com/(\S+)/(\S+)/(archive|releases)/}
user = Regexp.last_match(1) user = Regexp.last_match(1)
repo = Regexp.last_match(2) repository = Regexp.last_match(2)
@github = GitHub.repository(user, repo) if @fetch github = GitHub.repository(user, repository) if fetch
end end
@head = head
@github = T.let(github, T.untyped)
@sha256 = T.let(nil, T.nilable(String))
@desc = T.let(nil, T.nilable(String))
@homepage = T.let(nil, T.nilable(String))
@license = T.let(nil, T.nilable(String))
end
sig { void }
def verify_tap_available!
raise TapUnavailableError, @tap.name unless @tap.installed?
end end
sig { returns(Pathname) } sig { returns(Pathname) }
@ -91,7 +110,7 @@ module Homebrew
raise "Downloaded URL is not archive" raise "Downloaded URL is not archive"
end end
@sha256 = filepath.sha256 @sha256 = T.let(filepath.sha256, T.nilable(String))
end end
if @github if @github
@ -106,6 +125,8 @@ module Homebrew
path path
end end
private
sig { params(name: String).returns(String) } sig { params(name: String).returns(String) }
def latest_versioned_formula(name) def latest_versioned_formula(name)
name_prefix = "#{name}@" name_prefix = "#{name}@"
@ -116,8 +137,6 @@ module Homebrew
sig { returns(String) } sig { returns(String) }
def template def template
# FIXME: https://github.com/errata-ai/vale/issues/818
# <!-- vale off -->
<<~ERB <<~ERB
# Documentation: https://docs.brew.sh/Formula-Cookbook # Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula # https://rubydoc.brew.sh/Formula
@ -261,7 +280,6 @@ module Homebrew
end end
end end
ERB ERB
# <!-- vale on -->
end end
end end
end end

View File

@ -51,6 +51,7 @@ HOMEBREW_HOME_PLACEHOLDER = "/$HOME"
HOMEBREW_CASK_APPDIR_PLACEHOLDER = "$APPDIR" HOMEBREW_CASK_APPDIR_PLACEHOLDER = "$APPDIR"
HOMEBREW_MACOS_NEWEST_UNSUPPORTED = ENV.fetch("HOMEBREW_MACOS_NEWEST_UNSUPPORTED").freeze HOMEBREW_MACOS_NEWEST_UNSUPPORTED = ENV.fetch("HOMEBREW_MACOS_NEWEST_UNSUPPORTED").freeze
HOMEBREW_MACOS_NEWEST_SUPPORTED = ENV.fetch("HOMEBREW_MACOS_NEWEST_SUPPORTED").freeze
HOMEBREW_MACOS_OLDEST_SUPPORTED = ENV.fetch("HOMEBREW_MACOS_OLDEST_SUPPORTED").freeze HOMEBREW_MACOS_OLDEST_SUPPORTED = ENV.fetch("HOMEBREW_MACOS_OLDEST_SUPPORTED").freeze
HOMEBREW_MACOS_OLDEST_ALLOWED = ENV.fetch("HOMEBREW_MACOS_OLDEST_ALLOWED").freeze HOMEBREW_MACOS_OLDEST_ALLOWED = ENV.fetch("HOMEBREW_MACOS_OLDEST_ALLOWED").freeze

View File

@ -42,7 +42,6 @@ module Hardware
ppc64le: "-mcpu=powerpc64le", ppc64le: "-mcpu=powerpc64le",
}.freeze, T.nilable(T::Hash[Symbol, String])) }.freeze, T.nilable(T::Hash[Symbol, String]))
end end
alias generic_optimization_flags optimization_flags
sig { returns(Symbol) } sig { returns(Symbol) }
def arch_32_bit def arch_32_bit
@ -219,6 +218,7 @@ module Hardware
end end
end end
sig { params(_version: T.nilable(MacOSVersion)).returns(Symbol) }
def oldest_cpu(_version = nil) def oldest_cpu(_version = nil)
if Hardware::CPU.intel? if Hardware::CPU.intel?
if Hardware::CPU.is_64_bit? if Hardware::CPU.is_64_bit?
@ -242,7 +242,6 @@ module Hardware
Hardware::CPU.family Hardware::CPU.family
end end
end end
alias generic_oldest_cpu oldest_cpu
# Returns a Rust flag to set the target CPU if necessary. # Returns a Rust flag to set the target CPU if necessary.
# Defaults to nil. # Defaults to nil.

View File

@ -37,7 +37,6 @@ module Homebrew
end end
def global_post_install; end def global_post_install; end
alias generic_global_post_install global_post_install
def check_prefix def check_prefix
if (Hardware::CPU.intel? || Hardware::CPU.in_rosetta2?) && if (Hardware::CPU.intel? || Hardware::CPU.in_rosetta2?) &&
@ -399,7 +398,6 @@ module Homebrew
Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal)
Diagnostic.checks(:fatal_preinstall_checks) Diagnostic.checks(:fatal_preinstall_checks)
end end
alias generic_perform_preinstall_checks perform_preinstall_checks
def attempt_directory_creation def attempt_directory_creation
Keg.must_exist_directories.each do |dir| Keg.must_exist_directories.each do |dir|

View File

@ -77,7 +77,6 @@ class Keg
FileUtils.ln_s(new_src, file) FileUtils.ln_s(new_src, file)
end end
end end
alias generic_fix_dynamic_linkage fix_dynamic_linkage
def relocate_dynamic_linkage(_relocation) def relocate_dynamic_linkage(_relocation)
[] []
@ -102,7 +101,6 @@ class Keg
relocation relocation
end end
alias generic_prepare_relocation_to_placeholders prepare_relocation_to_placeholders
def replace_locations_with_placeholders def replace_locations_with_placeholders
relocation = prepare_relocation_to_placeholders.freeze relocation = prepare_relocation_to_placeholders.freeze
@ -123,7 +121,6 @@ class Keg
relocation relocation
end end
alias generic_prepare_relocation_to_locations prepare_relocation_to_locations
def replace_placeholders_with_locations(files, skip_linkage: false) def replace_placeholders_with_locations(files, skip_linkage: false)
relocation = prepare_relocation_to_locations.freeze relocation = prepare_relocation_to_locations.freeze
@ -221,7 +218,6 @@ class Keg
[grep_bin, grep_args] [grep_bin, grep_args]
end end
alias generic_egrep_args egrep_args
def each_unique_file_matching(string) def each_unique_file_matching(string)
Utils.popen_read("fgrep", recursive_fgrep_args, string, to_s) do |io| Utils.popen_read("fgrep", recursive_fgrep_args, string, to_s) do |io|

View File

@ -188,12 +188,10 @@ class LinkageChecker
store&.update!(keg_files_dylibs:) store&.update!(keg_files_dylibs:)
end end
alias generic_check_dylibs check_dylibs
def system_libraries_exist_in_cache? def system_libraries_exist_in_cache?
false false
end end
alias generic_system_libraries_exist_in_cache? system_libraries_exist_in_cache?
def dylib_found_in_shared_cache?(dylib) def dylib_found_in_shared_cache?(dylib)
@dyld_shared_cache_contains_path ||= begin @dyld_shared_cache_contains_path ||= begin

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strong
# frozen_string_literal: true # frozen_string_literal: true
require "version" require "version"
@ -10,6 +10,7 @@ class MacOSVersion < Version
sig { returns(T.nilable(T.any(String, Symbol))) } sig { returns(T.nilable(T.any(String, Symbol))) }
attr_reader :version attr_reader :version
sig { params(version: T.nilable(T.any(String, Symbol))).void }
def initialize(version) def initialize(version)
@version = version @version = version
super "unknown or unsupported macOS version: #{version.inspect}" super "unknown or unsupported macOS version: #{version.inspect}"
@ -18,7 +19,7 @@ class MacOSVersion < Version
# NOTE: When removing symbols here, ensure that they are added # NOTE: When removing symbols here, ensure that they are added
# to `DEPRECATED_MACOS_VERSIONS` in `MacOSRequirement`. # to `DEPRECATED_MACOS_VERSIONS` in `MacOSRequirement`.
SYMBOLS = { SYMBOLS = T.let({
tahoe: "26", tahoe: "26",
sequoia: "15", sequoia: "15",
sonoma: "14", sonoma: "14",
@ -30,7 +31,7 @@ class MacOSVersion < Version
high_sierra: "10.13", high_sierra: "10.13",
sierra: "10.12", sierra: "10.12",
el_capitan: "10.11", el_capitan: "10.11",
}.freeze }.freeze, T::Hash[Symbol, String])
sig { params(macos_version: MacOSVersion).returns(Version) } sig { params(macos_version: MacOSVersion).returns(Version) }
def self.kernel_major_version(macos_version) def self.kernel_major_version(macos_version)
@ -57,7 +58,9 @@ class MacOSVersion < Version
super(T.must(version)) super(T.must(version))
@comparison_cache = {} @comparison_cache = T.let({}, T::Hash[T.untyped, T.nilable(Integer)])
@pretty_name = T.let(nil, T.nilable(String))
@sym = T.let(nil, T.nilable(Symbol))
end end
sig { override.params(other: T.untyped).returns(T.nilable(Integer)) } sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
@ -95,7 +98,7 @@ class MacOSVersion < Version
sig { returns(Symbol) } sig { returns(Symbol) }
def to_sym def to_sym
return @sym if defined?(@sym) return @sym if @sym
sym = SYMBOLS.invert.fetch(strip_patch.to_s, :dunno) sym = SYMBOLS.invert.fetch(strip_patch.to_s, :dunno)
@ -106,7 +109,7 @@ class MacOSVersion < Version
sig { returns(String) } sig { returns(String) }
def pretty_name def pretty_name
return @pretty_name if defined?(@pretty_name) return @pretty_name if @pretty_name
pretty_name = to_sym.to_s.split("_").map(&:capitalize).join(" ").freeze pretty_name = to_sym.to_s.split("_").map(&:capitalize).join(" ").freeze
@ -154,5 +157,7 @@ class MacOSVersion < Version
# Represents the absence of a version. # Represents the absence of a version.
# #
# NOTE: Constructor needs to called with an arbitrary macOS-like version which is then set to `nil`. # NOTE: Constructor needs to called with an arbitrary macOS-like version which is then set to `nil`.
NULL = MacOSVersion.new("10.0").tap { |v| v.instance_variable_set(:@version, nil) }.freeze NULL = T.let(MacOSVersion.new("10.0").tap do |v|
T.let(v, MacOSVersion).instance_variable_set(:@version, nil)
end.freeze, MacOSVersion)
end end

View File

@ -93,7 +93,6 @@ module Homebrew
EOS EOS
end end
end end
alias generic_disallowed_reason disallowed_reason
sig { params(name: String).returns(T.nilable(String)) } sig { params(name: String).returns(T.nilable(String)) }
def tap_migration_reason(name) def tap_migration_reason(name)
@ -195,8 +194,10 @@ module Homebrew
end end
end end
sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) }
def cask_reason(name, silent: false, show_info: false); end def cask_reason(name, silent: false, show_info: false); end
sig { params(name: String, command: String).returns(T.nilable(String)) }
def suggest_command(name, command); end def suggest_command(name, command); end
require "extend/os/missing_formula" require "extend/os/missing_formula"

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "requirement" require "requirement"
@ -7,10 +7,14 @@ require "requirement"
class ArchRequirement < Requirement class ArchRequirement < Requirement
fatal true fatal true
@arch = T.let(nil, T.nilable(Symbol))
sig { returns(T.nilable(Symbol)) }
attr_reader :arch attr_reader :arch
sig { params(tags: T::Array[Symbol]).void }
def initialize(tags) def initialize(tags)
@arch = tags.shift @arch = T.let(tags.shift, T.nilable(Symbol))
super super
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 "requirement" require "requirement"
@ -7,6 +7,7 @@ require "requirement"
class XcodeRequirement < Requirement class XcodeRequirement < Requirement
fatal true fatal true
sig { returns(T.nilable(String)) }
attr_reader :version attr_reader :version
satisfy(build_env: false) do satisfy(build_env: false) do
@ -14,8 +15,10 @@ class XcodeRequirement < Requirement
xcode_installed_version xcode_installed_version
end end
sig { params(tags: T::Array[String]).void }
def initialize(tags = []) def initialize(tags = [])
@version = tags.shift if tags.first.to_s.match?(/(\d\.)+\d/) version = tags.shift if tags.first.to_s.match?(/(\d\.)+\d/)
@version = T.let(version, T.nilable(String))
super super
end end
@ -53,6 +56,7 @@ class XcodeRequirement < Requirement
"#<#{self.class.name}: version>=#{@version.inspect} #{tags.inspect}>" "#<#{self.class.name}: version>=#{@version.inspect} #{tags.inspect}>"
end end
sig { returns(String) }
def display_s def display_s
return "#{name.capitalize} (on macOS)" unless @version return "#{name.capitalize} (on macOS)" unless @version

View File

@ -9,9 +9,6 @@ class Cask::Cask
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def app(*args, &block); end def app(*args, &block); end
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def appcast(*args, &block); end
sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
def appdir(*args, &block); end def appdir(*args, &block); end

View File

@ -0,0 +1,58 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Cask::Config`.
# Please instead update this file by running `bin/tapioca dsl Cask::Config`.
module Cask
class Config
sig { returns(String) }
def appdir; end
sig { returns(String) }
def audio_unit_plugindir; end
sig { returns(String) }
def colorpickerdir; end
sig { returns(String) }
def dictionarydir; end
sig { returns(String) }
def fontdir; end
sig { returns(String) }
def input_methoddir; end
sig { returns(String) }
def internet_plugindir; end
sig { returns(String) }
def keyboard_layoutdir; end
sig { returns(T::Array[String]) }
def languages; end
sig { returns(String) }
def mdimporterdir; end
sig { returns(String) }
def prefpanedir; end
sig { returns(String) }
def qlplugindir; end
sig { returns(String) }
def screen_saverdir; end
sig { returns(String) }
def servicedir; end
sig { returns(String) }
def vst3_plugindir; end
sig { returns(String) }
def vst_plugindir; end
end
end

View File

@ -23,9 +23,6 @@ class Homebrew::Cmd::TapCmd::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def force?; end def force?; end
sig { returns(T::Boolean) }
def force_auto_update?; end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def repair?; end def repair?; end
end end

View File

@ -0,0 +1,37 @@
# typed: strict
# frozen_string_literal: true
require_relative "../../../../global"
require "cask/config"
module Tapioca
module Compilers
class CaskConfig < Tapioca::Dsl::Compiler
ConstantType = type_member { { fixed: Module } }
sig { override.returns(T::Enumerable[Module]) }
def self.gather_constants = [Cask::Config]
sig { override.void }
def decorate
root.create_module("Cask") do |mod|
mod.create_class("Config") do |klass|
Cask::Config.defaults.each do |key, value|
return_type = if key == :languages
# :languages is a `LazyObject`, so it lazily evaluates to an
# array of strings when a method is called on it.
"T::Array[String]"
elsif key.end_with?("?")
"T::Boolean"
else
value.class.to_s
end
klass.create_method(key.to_s, return_type:, class_method: false)
end
end
end
end
end
end
end

View File

@ -128,10 +128,12 @@ module SystemConfig
out.puts "#{tap_name} origin: #{tap.remote}" if tap.remote != tap.default_remote out.puts "#{tap_name} origin: #{tap.remote}" if tap.remote != tap.default_remote
out.puts "#{tap_name} HEAD: #{tap.git_head || "(none)"}" out.puts "#{tap_name} HEAD: #{tap.git_head || "(none)"}"
out.puts "#{tap_name} last commit: #{tap.git_last_commit || "never"}" out.puts "#{tap_name} last commit: #{tap.git_last_commit || "never"}"
out.puts "#{tap_name} branch: #{tap.git_branch || "(none)"}" if tap.git_branch != "master" default_branches = %w[main master].freeze
out.puts "#{tap_name} branch: #{tap.git_branch || "(none)"}" if default_branches.exclude?(tap.git_branch)
end end
if (json_file = Homebrew::API::HOMEBREW_CACHE_API/json_file_name) && json_file.exist? json_file = Homebrew::API::HOMEBREW_CACHE_API/json_file_name
if json_file.exist?
out.puts "#{tap_name} JSON: #{json_file.mtime.utc.strftime("%d %b %H:%M UTC")}" out.puts "#{tap_name} JSON: #{json_file.mtime.utc.strftime("%d %b %H:%M UTC")}"
elsif !tap.installed? elsif !tap.installed?
out.puts "#{tap_name}: N/A" out.puts "#{tap_name}: N/A"
@ -194,7 +196,6 @@ module SystemConfig
out.puts hardware if hardware out.puts hardware if hardware
host_software_config(out) host_software_config(out)
end end
alias dump_generic_verbose_config dump_verbose_config
end end
end end

View File

@ -15,7 +15,7 @@ RSpec.describe Homebrew::Bundle::Dsl do
cask_args appdir: '/Applications' cask_args appdir: '/Applications'
tap 'homebrew/cask' tap 'homebrew/cask'
tap 'telemachus/brew', 'https://telemachus@bitbucket.org/telemachus/brew.git' tap 'telemachus/brew', 'https://telemachus@bitbucket.org/telemachus/brew.git'
tap 'auto/update', 'https://bitbucket.org/auto/update.git', force_auto_update: true tap 'auto/update', 'https://bitbucket.org/auto/update.git'
brew 'imagemagick' brew 'imagemagick'
brew 'mysql@5.6', restart_service: true, link: true, conflicts_with: ['mysql'] brew 'mysql@5.6', restart_service: true, link: true, conflicts_with: ['mysql']
brew 'emacs', args: ['with-cocoa', 'with-gnutls'], link: :overwrite brew 'emacs', args: ['with-cocoa', 'with-gnutls'], link: :overwrite
@ -40,10 +40,7 @@ RSpec.describe Homebrew::Bundle::Dsl do
expect(dsl.entries[0].name).to eql("homebrew/cask") expect(dsl.entries[0].name).to eql("homebrew/cask")
expect(dsl.entries[1].name).to eql("telemachus/brew") expect(dsl.entries[1].name).to eql("telemachus/brew")
expect(dsl.entries[1].options).to eql(clone_target: "https://telemachus@bitbucket.org/telemachus/brew.git") expect(dsl.entries[1].options).to eql(clone_target: "https://telemachus@bitbucket.org/telemachus/brew.git")
expect(dsl.entries[2].options).to eql( expect(dsl.entries[2].options).to eql(clone_target: "https://bitbucket.org/auto/update.git")
clone_target: "https://bitbucket.org/auto/update.git",
force_auto_update: true,
)
expect(dsl.entries[3].name).to eql("imagemagick") expect(dsl.entries[3].name).to eql("imagemagick")
expect(dsl.entries[4].name).to eql("mysql@5.6") expect(dsl.entries[4].name).to eql("mysql@5.6")
expect(dsl.entries[4].options).to eql(restart_service: true, link: true, conflicts_with: ["mysql"]) expect(dsl.entries[4].options).to eql(restart_service: true, link: true, conflicts_with: ["mysql"])

View File

@ -55,25 +55,5 @@ RSpec.describe Homebrew::Bundle::TapInstaller do
expect(described_class.install("homebrew/cask", clone_target: "clone_target_path")).to be(false) expect(described_class.install("homebrew/cask", clone_target: "clone_target_path")).to be(false)
end end
end end
context "with force_auto_update" do
it "taps" do
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask",
"--force-auto-update",
verbose: false)
.and_return(true)
expect(described_class.preinstall("homebrew/cask", force_auto_update: true)).to be(true)
expect(described_class.install("homebrew/cask", force_auto_update: true)).to be(true)
end
it "fails" do
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask",
"--force-auto-update",
verbose: false)
.and_return(false)
expect(described_class.preinstall("homebrew/cask", force_auto_update: true)).to be(true)
expect(described_class.install("homebrew/cask", force_auto_update: true)).to be(false)
end
end
end end
end end

View File

@ -19,7 +19,7 @@ RSpec.describe Cask::Artifact::Artifact, :cask do
end end
context "without target" do context "without target" do
it "fails to load" do it "fails to load", :no_api do
expect do expect do
Cask::CaskLoader.load("invalid-generic-artifact-no-target") Cask::CaskLoader.load("invalid-generic-artifact-no-target")
end.to raise_error(Cask::CaskInvalidError, /Generic Artifact.*requires.*target/) end.to raise_error(Cask::CaskInvalidError, /Generic Artifact.*requires.*target/)

View File

@ -6,7 +6,7 @@ RSpec.describe Cask::Artifact::Manpage, :cask do
context "without section" do context "without section" do
let(:cask_token) { "invalid-manpage-no-section" } let(:cask_token) { "invalid-manpage-no-section" }
it "fails to load a cask without section" do it "fails to load a cask without section", :no_api do
expect { cask }.to raise_error(Cask::CaskInvalidError, /is not a valid man page name/) expect { cask }.to raise_error(Cask::CaskInvalidError, /is not a valid man page name/)
end end
end end

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