diff --git a/.github/codeql/extensions/homebrew-actions.yml b/.github/codeql/extensions/homebrew-actions.yml new file mode 100644 index 0000000000..8d4091650b --- /dev/null +++ b/.github/codeql/extensions/homebrew-actions.yml @@ -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"] diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 3185517144..d5e56756f9 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -1,18 +1,19 @@ -name: actionlint +# This file is synced from the `.github` repository, do not modify it directly. +name: Actionlint on: push: branches: + - main - master + paths: + - '.github/workflows/*.ya?ml' + - 'Formula/a/actionlint.rb' + - 'Formula/s/shellcheck.rb' + - 'Formula/z/zizmor.rb' pull_request: paths: - '.github/workflows/*.ya?ml' - - '.github/actionlint.yaml' - -env: - HOMEBREW_DEVELOPER: 1 - HOMEBREW_NO_AUTO_UPDATE: 1 - HOMEBREW_NO_ENV_HINTS: 1 defaults: run: @@ -22,16 +23,23 @@ concurrency: group: "actionlint-${{ github.ref }}" cancel-in-progress: ${{ github.event_name == 'pull_request' }} +env: + HOMEBREW_DEVELOPER: 1 + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_ENV_HINTS: 1 + permissions: {} jobs: workflow_syntax: if: github.repository_owner == 'Homebrew' runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Set up Homebrew id: setup-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -40,31 +48,42 @@ jobs: - name: Install tools run: brew install actionlint shellcheck zizmor - - name: Set up GITHUB_WORKSPACE - env: - HOMEBREW_REPOSITORY: ${{ steps.setup-homebrew.outputs.repository-path }} - run: | - # Annotations work only relative to GITHUB_WORKSPACE - (shopt -s dotglob; rm -rf "${GITHUB_WORKSPACE:?}"/*; mv "${HOMEBREW_REPOSITORY:?}"/* "$GITHUB_WORKSPACE") - rmdir "$HOMEBREW_REPOSITORY" - ln -vs "$GITHUB_WORKSPACE" "$HOMEBREW_REPOSITORY" + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - echo "::add-matcher::.github/actionlint-matcher.json" - - - run: | - # NOTE: exit code intentionally suppressed here - zizmor --format sarif . > results.sarif || true + - run: zizmor --format sarif . > results.sarif - name: Upload SARIF file uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # We can't use the SARIF file when triggered by `merge_group` so we don't upload it. + if: always() && github.event_name != 'merge_group' with: name: results.sarif path: results.sarif + - name: Set up actionlint + run: | + # In homebrew-core, setting `shell: /bin/bash` prevents shellcheck from running on + # those steps, so let's change them to `shell: bash` temporarily for better linting. + sed -i 's|shell: /bin/bash -x|shell: bash -x|' .github/workflows/*.y*ml + + # In homebrew-core, the JSON matcher needs to be accessible to the container host. + cp "$(brew --repository)/.github/actionlint-matcher.json" "$HOME" + + echo "::add-matcher::$HOME/actionlint-matcher.json" + - run: actionlint upload_sarif: needs: workflow_syntax + # We want to always upload this even if `actionlint` failed. + # This is only available on public repositories. + if: > + always() && + !contains(fromJSON('["cancelled", "skipped"]'), needs.workflow_syntax.result) && + !github.event.repository.private && + github.event_name != 'merge_group' runs-on: ubuntu-latest permissions: contents: read @@ -77,7 +96,7 @@ jobs: path: results.sarif - 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: sarif_file: results.sarif category: zizmor diff --git a/.github/workflows/autogenerated-files.yml b/.github/workflows/autogenerated-files.yml index 8dfed9e38d..236e57c42a 100644 --- a/.github/workflows/autogenerated-files.yml +++ b/.github/workflows/autogenerated-files.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a3386e59c4..24dd2a5f1a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,10 +3,9 @@ name: "CodeQL" on: push: branches: + - main - master pull_request: - branches: - - master defaults: run: @@ -28,7 +27,7 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: languages: ruby config: | @@ -36,4 +35,4 @@ jobs: - Library/Homebrew/vendor - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 81c9c7ad86..fd52efede6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,7 @@ on: pull_request: push: branches: + - main - master merge_group: release: @@ -38,8 +39,8 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Fetch origin/master from Git - run: git fetch origin master + - name: Fetch origin/HEAD from Git + run: git fetch origin HEAD - name: Determine build attributes id: attributes @@ -83,12 +84,16 @@ jobs: ) fi elif [[ "${GITHUB_EVENT_NAME}" == "push" && - "${GITHUB_REF}" == "refs/heads/master" && + ("${GITHUB_REF}" == "refs/heads/master" || "${GITHUB_REF}" == "refs/heads/main") && "${version}" == "22.04" ]]; then tags+=( + "ghcr.io/homebrew/brew:main" "ghcr.io/homebrew/brew:master" + "ghcr.io/homebrew/ubuntu${version}:main" "ghcr.io/homebrew/ubuntu${version}:master" + "homebrew/brew:main" "homebrew/brew:master" + "homebrew/ubuntu${version}:main" "homebrew/ubuntu${version}:master" ) fi @@ -160,8 +165,8 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Fetch origin/master from Git - run: git fetch origin master + - name: Fetch origin/HEAD from Git + run: git fetch origin HEAD - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5d88cf7e65..debaaae995 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -52,7 +52,7 @@ jobs: run: vale docs/ - name: Install Ruby - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: bundler-cache: true working-directory: docs @@ -67,7 +67,7 @@ jobs: - name: Generate formulae.brew.sh API samples if: github.repository == 'Homebrew/formulae.brew.sh' working-directory: docs - run: ../script/generate-api-samples.rb + run: ../script/generate-api-samples.rb --template - name: Build the site and check for broken links working-directory: docs diff --git a/.github/workflows/doctor.yml b/.github/workflows/doctor.yml index 7dfa3abbbe..a697a85815 100644 --- a/.github/workflows/doctor.yml +++ b/.github/workflows/doctor.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -55,7 +55,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false diff --git a/.github/workflows/pkg-installer.yml b/.github/workflows/pkg-installer.yml index adb7f99c4c..10dd384215 100644 --- a/.github/workflows/pkg-installer.yml +++ b/.github/workflows/pkg-installer.yml @@ -43,7 +43,7 @@ jobs: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -135,7 +135,7 @@ jobs: fi - name: Generate build provenance - uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-path: Homebrew-${{ steps.homebrew-version.outputs.version }}.pkg @@ -251,7 +251,7 @@ jobs: issues: write steps: - name: Open, update, or close pkg installer issue - uses: Homebrew/actions/create-or-update-issue@master + uses: Homebrew/actions/create-or-update-issue@main with: title: Failed to publish pkg installer body: > @@ -259,7 +259,7 @@ jobs: ${{ github.ref_name }}. No pkg installer was uploaded to the GitHub release. labels: bug,release blocker - update-existing: ${{ contains(needs.*.result, 'failure') }} + update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} close-existing: ${{ needs.upload.result == 'success' }} close-from-author: github-actions[bot] close-comment: > diff --git a/.github/workflows/rubydoc.yml b/.github/workflows/rubydoc.yml index 2c0399ed56..44343e12ff 100644 --- a/.github/workflows/rubydoc.yml +++ b/.github/workflows/rubydoc.yml @@ -3,6 +3,7 @@ name: Ruby Documentation CI on: push: branches: + - main - master pull_request: @@ -28,7 +29,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -42,7 +43,7 @@ jobs: persist-credentials: false - name: Install Ruby - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: bundler-cache: true working-directory: rubydoc diff --git a/.github/workflows/schemas.yml b/.github/workflows/sbom.yml similarity index 66% rename from .github/workflows/schemas.yml rename to .github/workflows/sbom.yml index 43b47a31f4..9bc5db2892 100644 --- a/.github/workflows/schemas.yml +++ b/.github/workflows/sbom.yml @@ -1,9 +1,10 @@ -name: Update schema data +name: Update SBOM schema on: push: paths: - - .github/workflows/schemas.yml + - .github/workflows/sbom.yml branches-ignore: + - main - master schedule: - cron: "0 0 * * *" @@ -17,25 +18,25 @@ defaults: shell: bash -xeuo pipefail {0} jobs: - spdx: + sbom: if: github.repository == 'Homebrew/brew' runs-on: ubuntu-latest steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false test-bot: false - name: Configure Git user - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot - name: Set up commit signing - uses: Homebrew/actions/setup-commit-signing@master + uses: Homebrew/actions/setup-commit-signing@main with: signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} @@ -55,7 +56,7 @@ jobs: git checkout "${BRANCH}" git checkout "Library/Homebrew/data/schemas" else - git checkout --no-track -B "${BRANCH}" origin/master + git checkout --no-track -B "${BRANCH}" origin/HEAD fi # Intentionally tracking 2.3.x to match what we output in sbom.rb. 3.0 also doesn't have a JSON Schema. @@ -67,9 +68,10 @@ jobs: if ! git diff --exit-code Library/Homebrew/data/schemas then git add "Library/Homebrew/data/schemas" - git commit -m "data/schemas: update schema data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/master/.github/workflows/schemas.yml)." + git commit -m "data/schemas: update schema data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/schemas.yml)." + echo "committed=true" >> "$GITHUB_OUTPUT" - PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")" + PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)" if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]] then echo "pull_request=true" >> "$GITHUB_OUTPUT" @@ -78,13 +80,13 @@ jobs: - name: Push commits if: steps.update.outputs.committed == 'true' - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }} branch: ${{ steps.update.outputs.branch }} force: true - origin_branch: "master" + origin_branch: "HEAD" - name: Open a pull request if: steps.update.outputs.pull_request == 'true' @@ -92,3 +94,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} + + issue: + needs: sbom + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + permissions: + # To create or update issues + issues: write + steps: + - name: Open, update, or close schema issue + uses: Homebrew/actions/create-or-update-issue@main + with: + title: Failed to update SBOM schema + body: > + The SBOM schema workflow [failed](${{ env.RUN_URL }}). No SBOM schema was updated. + labels: bug + update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} + close-existing: ${{ needs.sbom.result == 'success' }} + close-from-author: github-actions[bot] + close-comment: > + The SBOM schema workflow [succeeded](${{ env.RUN_URL }}). Closing this issue. diff --git a/.github/workflows/sorbet.yml b/.github/workflows/sorbet.yml index d9ce901d48..bfec7cdde5 100644 --- a/.github/workflows/sorbet.yml +++ b/.github/workflows/sorbet.yml @@ -10,6 +10,7 @@ on: paths: - .github/workflows/sorbet.yml branches-ignore: + - main - master schedule: - cron: "0 0 * * *" @@ -29,7 +30,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -37,13 +38,13 @@ jobs: - name: Configure Git user if: github.event_name != 'pull_request' - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot - name: Set up commit signing if: github.event_name != 'pull_request' - uses: Homebrew/actions/setup-commit-signing@master + uses: Homebrew/actions/setup-commit-signing@main with: signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} @@ -63,7 +64,7 @@ jobs: git checkout "${BRANCH}" git checkout "Library/Homebrew/sorbet" else - git checkout --no-track -B "${BRANCH}" origin/master + git checkout --no-track -B "${BRANCH}" origin/HEAD fi fi @@ -80,17 +81,17 @@ jobs: then git add "Library/Homebrew/sorbet" git commit -m "sorbet: Update RBI files." \ - -m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/master/.github/workflows/sorbet.yml) workflow." + -m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sorbet.yml) workflow." if ! git diff --stat --exit-code "Library/Homebrew" then git add "Library/Homebrew/" git commit -m "sorbet: Autobump sigils via Spoom" \ - -m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/master/.github/workflows/sorbet.yml) workflow." + -m "Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sorbet.yml) workflow." fi echo "committed=true" >> "$GITHUB_OUTPUT" - PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")" + PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)" if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]] then echo "pull_request=true" >> "$GITHUB_OUTPUT" @@ -99,13 +100,13 @@ jobs: - name: Push commits if: steps.commit.outputs.committed == 'true' - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }} branch: ${{ steps.update.outputs.branch }} force: true - origin_branch: "master" + origin_branch: "HEAD" - name: Open a pull request if: steps.commit.outputs.pull_request == 'true' @@ -113,3 +114,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} + + issue: + needs: tapioca + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + permissions: + # To create or update issues + issues: write + steps: + - name: Open, update, or close Sorbet issue + uses: Homebrew/actions/create-or-update-issue@main + with: + title: Failed to update RBI files + body: > + The Sorbet workflow [failed](${{ env.RUN_URL }}). No RBI files were updated. + labels: bug + update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} + close-existing: ${{ needs.tapioca.result == 'success' }} + close-from-author: github-actions[bot] + close-comment: > + The Sorbet workflow [succeeded](${{ env.RUN_URL }}). Closing this issue. diff --git a/.github/workflows/spdx.yml b/.github/workflows/spdx.yml index a67f827e3c..c6e38fabe5 100644 --- a/.github/workflows/spdx.yml +++ b/.github/workflows/spdx.yml @@ -4,6 +4,7 @@ on: paths: - .github/workflows/spdx.yml branches-ignore: + - main - master schedule: - cron: "0 0 * * *" @@ -23,19 +24,19 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false test-bot: false - name: Configure Git user - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot - name: Set up commit signing - uses: Homebrew/actions/setup-commit-signing@master + uses: Homebrew/actions/setup-commit-signing@main with: signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} @@ -55,15 +56,16 @@ jobs: git checkout "${BRANCH}" git checkout "Library/Homebrew/data/spdx" else - git checkout --no-track -B "${BRANCH}" origin/master + git checkout --no-track -B "${BRANCH}" origin/HEAD fi if brew update-license-data then git add "Library/Homebrew/data/spdx" - git commit -m "spdx: update license data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/master/.github/workflows/spdx.yml)." + git commit -m "spdx: update license data." -m "Autogenerated by [a scheduled GitHub Action](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/spdx.yml)." + echo "committed=true" >> "$GITHUB_OUTPUT" - PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")" + PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)" if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]] then echo "pull_request=true" >> "$GITHUB_OUTPUT" @@ -72,13 +74,13 @@ jobs: - name: Push commits if: steps.update.outputs.committed == 'true' - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }} branch: ${{ steps.update.outputs.branch }} force: true - origin_branch: "master" + origin_branch: "HEAD" - name: Open a pull request if: steps.update.outputs.pull_request == 'true' @@ -86,3 +88,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} + + issue: + needs: spdx + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + permissions: + # To create or update issues + issues: write + steps: + - name: Open, update, or close SPDX issue + uses: Homebrew/actions/create-or-update-issue@main + with: + title: Failed to update SPDX license data + body: > + The SPDX license data workflow [failed](${{ env.RUN_URL }}). No SPDX license data was updated. + labels: bug + update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} + close-existing: ${{ needs.spdx.result == 'success' }} + close-from-author: github-actions[bot] + close-comment: > + The SPDX license data workflow [succeeded](${{ env.RUN_URL }}). Closing this issue. diff --git a/.github/workflows/sponsors-maintainers-man-completions.yml b/.github/workflows/sponsors-maintainers-man-completions.yml index 214964efa2..c74ead84ba 100644 --- a/.github/workflows/sponsors-maintainers-man-completions.yml +++ b/.github/workflows/sponsors-maintainers-man-completions.yml @@ -3,6 +3,7 @@ name: Update sponsors, maintainers, manpage and completions on: push: branches: + - main - master paths: - .github/workflows/sponsors-maintainers-man-completions.yml @@ -32,19 +33,19 @@ jobs: steps: - name: Setup Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false test-bot: false - name: Configure Git user - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot - name: Set up commit signing - uses: Homebrew/actions/setup-commit-signing@master + uses: Homebrew/actions/setup-commit-signing@main with: signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} @@ -60,7 +61,7 @@ jobs: run: | git fetch origin - if [[ -n "$GITHUB_REF_NAME" && "$GITHUB_REF_NAME" != "master" ]] + if [[ -n "$GITHUB_REF_NAME" && "$GITHUB_REF_NAME" != "master" && "$GITHUB_REF_NAME" != "main" ]] then BRANCH="$GITHUB_REF_NAME" else @@ -76,7 +77,7 @@ jobs: "manpages/brew.1" \ "completions" else - git checkout --force --no-track -B "${BRANCH}" origin/master + git checkout --force --no-track -B "${BRANCH}" origin/HEAD fi if brew update-sponsors @@ -111,7 +112,7 @@ jobs: if [[ -n "${COMMITTED-}" ]] then echo "committed=true" >> "$GITHUB_OUTPUT" - PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state")" + PULL_REQUEST_STATE="$(gh pr view --json=state | jq -r ".state" || true)" if [[ "${PULL_REQUEST_STATE}" != "OPEN" ]] then echo "pull_request=true" >> "$GITHUB_OUTPUT" @@ -124,7 +125,7 @@ jobs: - name: Push commits if: steps.update.outputs.committed == 'true' - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: token: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }} @@ -137,3 +138,26 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_PUBLIC_REPO_TOKEN }} working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }} + + issue: + needs: updates + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + permissions: + # To create or update issues + issues: write + steps: + - name: Open, update, or close sponsors, maintainers, manpage and completions issue + uses: Homebrew/actions/create-or-update-issue@main + with: + title: Failed to update sponsors, maintainers, manpage and completions + body: > + The sponsors, maintainers, manpage and completions workflow [failed](${{ env.RUN_URL }}). No sponsors, maintainers, manpage and completions were updated. + labels: bug + update-existing: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} + close-existing: ${{ needs.updates.result == 'success' }} + close-from-author: github-actions[bot] + close-comment: > + The sponsors, maintainers, manpage and completions workflow [succeeded](${{ env.RUN_URL }}). Closing this issue. diff --git a/.github/workflows/sync-default-branches.yml b/.github/workflows/sync-default-branches.yml new file mode 100644 index 0000000000..1635b6bc49 --- /dev/null +++ b/.github/workflows/sync-default-branches.yml @@ -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 }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5ee26c11ff..d5c3527d87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,6 +3,7 @@ name: CI on: push: branches: + - main - master pull_request: merge_group: @@ -32,7 +33,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -84,7 +85,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: true cask: true @@ -135,11 +136,12 @@ jobs: if: github.repository_owner == 'Homebrew' && github.event_name != 'push' runs-on: ubuntu-latest container: + # TODO: switch to main when we're pushing those images image: ghcr.io/homebrew/brew:master steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -162,7 +164,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: true @@ -185,14 +187,14 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false test-bot: false - name: Configure Git user - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot @@ -220,7 +222,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -255,7 +257,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: # We only test needs_homebrew_core tests on macOS because # homebrew/core is not available by default on GitHub-hosted Ubuntu @@ -355,6 +357,7 @@ jobs: container: ghcr.io/homebrew/ubuntu24.04:latest - name: test-bot (Linux x86_64) runs-on: ubuntu-latest + # TODO: switch to main when we've migrated to it container: ghcr.io/homebrew/ubuntu22.04:master # Use Debian Old Stable for testing Homebrew's glibc support. - name: test-bot (Linux Homebrew glibc) @@ -402,7 +405,7 @@ jobs: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -442,7 +445,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -496,7 +499,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main - name: Setup Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 diff --git a/.github/workflows/vendor-gems.yml b/.github/workflows/vendor-gems.yml index f931fcc13d..75135a0609 100644 --- a/.github/workflows/vendor-gems.yml +++ b/.github/workflows/vendor-gems.yml @@ -9,6 +9,7 @@ on: paths: - .github/workflows/vendor-gems.yml branches-ignore: + - main - master workflow_dispatch: inputs: @@ -31,7 +32,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false @@ -39,13 +40,13 @@ jobs: - name: Configure Git user if: github.event_name == 'workflow_dispatch' - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main with: username: BrewTestBot - name: Set up commit signing if: github.event_name == 'workflow_dispatch' - uses: Homebrew/actions/setup-commit-signing@master + uses: Homebrew/actions/setup-commit-signing@main with: signing_key: ${{ secrets.BREWTESTBOT_SSH_SIGNING_KEY }} @@ -100,7 +101,7 @@ jobs: - name: Push to pull request if: github.event_name == 'workflow_dispatch' - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: token: ${{ steps.app-token.outputs.token }} directory: ${{ steps.set-up-homebrew.outputs.repository-path }} diff --git a/.github/workflows/vendor-version.yml b/.github/workflows/vendor-version.yml index fe317b3119..028f373260 100644 --- a/.github/workflows/vendor-version.yml +++ b/.github/workflows/vendor-version.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: core: false cask: false diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 37fe3fad56..465fbd2c2e 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,3 +1,4 @@ +# This file is synced from the `.github` repository, do not modify it directly. rules: unpinned-uses: config: diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000000..49ec5f4a56 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,11 @@ +{ + "servers": { + "Homebrew": { + "type": "stdio", + "command": "brew", + "args": [ + "mcp-server" + ] + } + } +} diff --git a/.vscode/ruby-lsp-activate.sh b/.vscode/ruby-lsp-activate.sh index 142acf76b4..8a3fba7c19 100755 --- a/.vscode/ruby-lsp-activate.sh +++ b/.vscode/ruby-lsp-activate.sh @@ -1,5 +1,12 @@ #!/bin/bash -HOMEBREW_PREFIX="$(cd "$(dirname "$0")"/../ && pwd)" +if [[ -n "${BASH_SOURCE[0]}" ]]; then + SCRIPT_PATH="${BASH_SOURCE[0]}" +elif [[ -n "${ZSH_VERSION}" ]]; then + SCRIPT_PATH="${(%):-%x}" +else + SCRIPT_PATH="$0" +fi +HOMEBREW_PREFIX="$(cd "$(dirname "${SCRIPT_PATH}")"/../ && pwd)" "${HOMEBREW_PREFIX}/bin/brew" install-bundler-gems --add-groups=style,typecheck,vscode >/dev/null 2>&1 diff --git a/.vscode/settings.json b/.vscode/settings.json index fae9f4229f..14ab39c7be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,7 +40,6 @@ "id": "default", "name": "Brew Typecheck", "description": "Default configuration", - "cwd": "${workspaceFolder}", "command": [ "./bin/brew", "typecheck", diff --git a/Library/Homebrew/api.rb b/Library/Homebrew/api.rb index 3948bcde3d..a1420cab7e 100644 --- a/Library/Homebrew/api.rb +++ b/Library/Homebrew/api.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "api/analytics" @@ -11,10 +11,10 @@ module Homebrew module API extend Cachable - HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze - HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze + HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname) + HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname) - sig { params(endpoint: String).returns(Hash) } + sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) } def self.fetch(endpoint) return cache[endpoint] if cache.present? && cache.key?(endpoint) @@ -33,7 +33,8 @@ module Homebrew end sig { - params(endpoint: String, target: Pathname, stale_seconds: Integer).returns([T.any(Array, Hash), T::Boolean]) + params(endpoint: String, target: Pathname, + stale_seconds: Integer).returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean]) } def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) @@ -96,7 +97,8 @@ module Homebrew mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now FileUtils.touch(target, mtime:) unless skip_download - JSON.parse(target.read(encoding: Encoding::UTF_8), freeze: true) + # Can use `target.read` again when/if https://github.com/sorbet/sorbet/pull/8999 is merged/released. + JSON.parse(File.read(target, encoding: Encoding::UTF_8), freeze: true) rescue JSON::ParserError target.unlink retry_count += 1 @@ -122,8 +124,11 @@ module Homebrew end end - sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) } - def self.merge_variations(json, bottle_tag: nil) + sig { + params(json: T::Hash[String, T.untyped], + bottle_tag: ::Utils::Bottles::Tag).returns(T::Hash[String, T.untyped]) + } + def self.merge_variations(json, bottle_tag: T.unsafe(nil)) return json unless json.key?("variations") bottle_tag ||= Homebrew::SimulateSystem.current_tag @@ -147,7 +152,10 @@ module Homebrew false end - sig { params(json_data: Hash).returns([T::Boolean, T.any(String, Array, Hash)]) } + sig { + params(json_data: T::Hash[String, T.untyped]) + .returns([T::Boolean, T.any(String, T::Array[T.untyped], T::Hash[String, T.untyped])]) + } private_class_method def self.verify_and_parse_jws(json_data) signatures = json_data["signatures"] homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" } diff --git a/Library/Homebrew/api/analytics.rb b/Library/Homebrew/api/analytics.rb index 2bf4a62e49..ce105b9fd9 100644 --- a/Library/Homebrew/api/analytics.rb +++ b/Library/Homebrew/api/analytics.rb @@ -10,7 +10,6 @@ module Homebrew def analytics_api_path "analytics" end - alias generic_analytics_api_path analytics_api_path sig { params(category: String, days: T.any(Integer, String)).returns(T::Hash[String, T.untyped]) } def fetch(category, days) diff --git a/Library/Homebrew/api/cask.rb b/Library/Homebrew/api/cask.rb index 5de578f0ed..df336931a9 100644 --- a/Library/Homebrew/api/cask.rb +++ b/Library/Homebrew/api/cask.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "cachable" @@ -21,7 +21,7 @@ module Homebrew private_class_method :cache - sig { params(token: String).returns(Hash) } + sig { params(token: String).returns(T::Hash[String, T.untyped]) } def self.fetch(token) Homebrew::API.fetch "cask/#{token}.json" end @@ -47,6 +47,7 @@ module Homebrew .load(config: cask.config) end + sig { returns(Pathname) } def self.cached_json_file_path HOMEBREW_CACHE_API/api_filename end @@ -70,7 +71,7 @@ module Homebrew end private_class_method :download_and_cache_data! - sig { returns(T::Hash[String, Hash]) } + sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) } def self.all_casks unless cache.key?("casks") json_updated = download_and_cache_data! diff --git a/Library/Homebrew/api_hashable.rb b/Library/Homebrew/api_hashable.rb index e1e4098e95..7c64f378d9 100644 --- a/Library/Homebrew/api_hashable.rb +++ b/Library/Homebrew/api_hashable.rb @@ -1,22 +1,26 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true # Used to substitute common paths with generic placeholders when generating JSON for the API. module APIHashable + sig { void } def generating_hash! return if generating_hash? # Apply monkeypatches for API generation - @old_homebrew_prefix = HOMEBREW_PREFIX - @old_homebrew_cellar = HOMEBREW_CELLAR - @old_home = Dir.home + @old_homebrew_prefix = T.let(HOMEBREW_PREFIX, T.nilable(Pathname)) + @old_homebrew_cellar = T.let(HOMEBREW_CELLAR, T.nilable(Pathname)) + @old_home = T.let(Dir.home, T.nilable(String)) + @old_git_config_global = T.let(ENV.fetch("GIT_CONFIG_GLOBAL", nil), T.nilable(String)) Object.send(:remove_const, :HOMEBREW_PREFIX) Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER)) ENV["HOME"] = HOMEBREW_HOME_PLACEHOLDER + ENV["GIT_CONFIG_GLOBAL"] = File.join(@old_home, ".gitconfig") - @generating_hash = true + @generating_hash = T.let(true, T.nilable(T::Boolean)) end + sig { void } def generated_hash! return unless generating_hash? @@ -24,10 +28,12 @@ module APIHashable Object.send(:remove_const, :HOMEBREW_PREFIX) Object.const_set(:HOMEBREW_PREFIX, @old_homebrew_prefix) ENV["HOME"] = @old_home + ENV["GIT_CONFIG_GLOBAL"] = @old_git_config_global @generating_hash = false end + sig { returns(T::Boolean) } def generating_hash? @generating_hash ||= false @generating_hash == true diff --git a/Library/Homebrew/ast_constants.rb b/Library/Homebrew/ast_constants.rb index fa31aef6ed..c6ec8eb61c 100644 --- a/Library/Homebrew/ast_constants.rb +++ b/Library/Homebrew/ast_constants.rb @@ -52,4 +52,4 @@ FORMULA_COMPONENT_PRECEDENCE_LIST = T.let([ [{ name: :caveats, type: :method_definition }], [{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }], [{ name: :test, type: :block_call }], -].freeze, T::Array[[{ name: Symbol, type: Symbol }]]) +].freeze, T::Array[T::Array[{ name: Symbol, type: Symbol }]]) diff --git a/Library/Homebrew/brew.sh b/Library/Homebrew/brew.sh index 537ee55a4e..3557d8a40b 100644 --- a/Library/Homebrew/brew.sh +++ b/Library/Homebrew/brew.sh @@ -614,6 +614,8 @@ esac # and, if needed: # - MacOSVersion::SYMBOLS HOMEBREW_MACOS_NEWEST_UNSUPPORTED="16" +# TODO: bump version when new macOS is released +HOMEBREW_MACOS_NEWEST_SUPPORTED="15" # TODO: bump version when new macOS is released and update references in: # - docs/Installation.md # - HOMEBREW_MACOS_OLDEST_SUPPORTED in .github/workflows/pkg-installer.yml @@ -841,6 +843,7 @@ export HOMEBREW_OS_VERSION export HOMEBREW_MACOS_VERSION export HOMEBREW_MACOS_VERSION_NUMERIC export HOMEBREW_MACOS_NEWEST_UNSUPPORTED +export HOMEBREW_MACOS_NEWEST_SUPPORTED export HOMEBREW_MACOS_OLDEST_SUPPORTED export HOMEBREW_MACOS_OLDEST_ALLOWED export HOMEBREW_USER_AGENT diff --git a/Library/Homebrew/bundle/commands/add.rb b/Library/Homebrew/bundle/commands/add.rb index fe26cd8a28..60718601fb 100644 --- a/Library/Homebrew/bundle/commands/add.rb +++ b/Library/Homebrew/bundle/commands/add.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "bundle/adder" @@ -7,6 +7,7 @@ module Homebrew module Bundle module Commands module Add + sig { params(args: String, type: Symbol, global: T::Boolean, file: T.nilable(String)).void } def self.run(*args, type:, global:, file:) Homebrew::Bundle::Adder.add(*args, type:, global:, file:) end diff --git a/Library/Homebrew/bundle/tap_installer.rb b/Library/Homebrew/bundle/tap_installer.rb index 77718e116d..3de2943285 100644 --- a/Library/Homebrew/bundle/tap_installer.rb +++ b/Library/Homebrew/bundle/tap_installer.rb @@ -19,7 +19,6 @@ module Homebrew puts "Installing #{name} tap. It is not currently installed." if verbose args = [] args << "--force" if force - args.append("--force-auto-update") if options[:force_auto_update] success = if options[:clone_target] Bundle.brew("tap", name, options[:clone_target], *args, verbose:) diff --git a/Library/Homebrew/cask/artifact/abstract_artifact.rb b/Library/Homebrew/cask/artifact/abstract_artifact.rb index 09ed1fabe8..f9199256f6 100644 --- a/Library/Homebrew/cask/artifact/abstract_artifact.rb +++ b/Library/Homebrew/cask/artifact/abstract_artifact.rb @@ -139,7 +139,11 @@ module Cask def initialize(cask, *dsl_args) @cask = cask + @dirmethod = nil @dsl_args = dsl_args.deep_dup + @dsl_key = nil + @english_article = nil + @english_name = nil end def config diff --git a/Library/Homebrew/cask/artifact/relocated.rb b/Library/Homebrew/cask/artifact/relocated.rb index dc01d74545..e33c6431b2 100644 --- a/Library/Homebrew/cask/artifact/relocated.rb +++ b/Library/Homebrew/cask/artifact/relocated.rb @@ -41,7 +41,9 @@ module Cask super target = target_hash[:target] + @source = nil @source_string = source.to_s + @target = nil @target_string = target.to_s end diff --git a/Library/Homebrew/cask/artifact_set.rb b/Library/Homebrew/cask/artifact_set.rb index 517debcd25..8f1da460b4 100644 --- a/Library/Homebrew/cask/artifact_set.rb +++ b/Library/Homebrew/cask/artifact_set.rb @@ -1,9 +1,14 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true module Cask # Sorted set containing all cask artifacts. class ArtifactSet < ::Set + extend T::Generic + + Elem = type_member(:out) { { fixed: Artifact::AbstractArtifact } } + + sig { params(block: T.nilable(T.proc.params(arg0: Elem).returns(T.untyped))).void } def each(&block) return enum_for(T.must(__method__)) { size } unless block @@ -11,6 +16,7 @@ module Cask self end + sig { returns(T::Array[Artifact::AbstractArtifact]) } def to_a super.sort end diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 4f8bae5c14..ca67555668 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -653,11 +653,12 @@ module Cask supports_arm = result.merged_output.include?("arm64") mentions_rosetta = cask.caveats.include?("requires Rosetta 2") + requires_intel = cask.depends_on.arch&.any? { |arch| arch[:type] == :intel } if supports_arm && mentions_rosetta add_error "Artifacts do not require Rosetta 2 but the caveats say otherwise!", location: url.location - elsif !supports_arm && !mentions_rosetta + elsif !supports_arm && !mentions_rosetta && !requires_intel add_error "Artifacts require Rosetta 2 but this is not indicated by the caveats!", location: url.location end @@ -701,45 +702,53 @@ module Cask return unless online? return unless strict? - odebug "Auditing minimum OS version" + odebug "Auditing minimum macOS version" - plist_min_os = cask_plist_min_os - sparkle_min_os = livecheck_min_os + bundle_min_os = cask_bundle_min_os + sparkle_min_os = cask_sparkle_min_os + app_min_os = [bundle_min_os, sparkle_min_os].compact.max debug_messages = [] - debug_messages << "Plist #{plist_min_os}" if plist_min_os - debug_messages << "Sparkle #{sparkle_min_os}" if sparkle_min_os - odebug "Detected minimum OS version: #{debug_messages.join(" | ")}" unless debug_messages.empty? - min_os = [plist_min_os, sparkle_min_os].compact.max - - return if min_os.nil? || min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED + debug_messages << "from artifact: #{bundle_min_os.to_sym}" if bundle_min_os + debug_messages << "from upstream: #{sparkle_min_os.to_sym}" if sparkle_min_os + odebug "Detected minimum macOS: #{app_min_os.to_sym} (#{debug_messages.join(" | ")})" if app_min_os + return if app_min_os.nil? || app_min_os <= HOMEBREW_MACOS_OLDEST_ALLOWED on_system_block_min_os = cask.on_system_block_min_os - cask_min_os = [on_system_block_min_os, cask.depends_on.macos&.minimum_version].compact.max - odebug "Declared minimum OS version: #{cask_min_os&.to_sym}" - return if cask_min_os&.to_sym == min_os.to_sym - return if cask.on_system_blocks_exist? && - OnSystem.arch_condition_met?(:arm) && + depends_on_min_os = cask.depends_on.macos&.minimum_version + + cask_min_os = [on_system_block_min_os, depends_on_min_os].compact.max + debug_messages = [] + debug_messages << "from on_system block: #{on_system_block_min_os.to_sym}" if on_system_block_min_os + if depends_on_min_os > HOMEBREW_MACOS_OLDEST_ALLOWED + debug_messages << "from depends_on stanza: #{depends_on_min_os.to_sym}" + end + odebug "Declared minimum macOS: #{cask_min_os.to_sym} (#{debug_messages.join(" | ").presence || "default"})" + return if cask_min_os.to_sym == app_min_os.to_sym + # ignore declared minimum OS < 11.x when auditing as ARM a cask with arch-specific artifacts + return if OnSystem.arch_condition_met?(:arm) && + cask.on_system_blocks_exist? && cask_min_os.present? && cask_min_os < MacOSVersion.new("11") - min_os_definition = if cask_min_os.present? - if on_system_block_min_os.present? && - on_system_block_min_os > cask.depends_on.macos&.minimum_version - "a block with a minimum OS version of #{cask_min_os.to_sym.inspect}" + min_os_definition = if cask_min_os > HOMEBREW_MACOS_OLDEST_ALLOWED + definition = if T.must(on_system_block_min_os.to_s <=> depends_on_min_os.to_s).positive? + "an on_system block" else - cask_min_os.to_sym.inspect + "a depends_on stanza" end + "#{definition} with a minimum macOS version of #{cask_min_os.to_sym.inspect}" else - "no minimum OS version" + "no minimum macOS version" end - add_error "Upstream defined #{min_os.to_sym.inspect} as the minimum OS version " \ + source = T.must(bundle_min_os.to_s <=> sparkle_min_os.to_s).positive? ? "Artifact" : "Upstream" + add_error "#{source} defined #{app_min_os.to_sym.inspect} as the minimum macOS version " \ "but the cask declared #{min_os_definition}", strict_only: true end sig { returns(T.nilable(MacOSVersion)) } - def livecheck_min_os + def cask_sparkle_min_os return unless online? return unless cask.livecheck_defined? return if cask.livecheck.strategy != :sparkle @@ -772,10 +781,10 @@ module Cask end sig { returns(T.nilable(MacOSVersion)) } - def cask_plist_min_os + def cask_bundle_min_os return unless online? - plist_min_os = T.let(nil, T.untyped) + min_os = T.let(nil, T.untyped) @staged_path ||= cask.staged_path extract_artifacts do |artifacts, tmpdir| @@ -786,13 +795,33 @@ module Cask next unless File.exist?(plist_path) plist = system_command!("plutil", args: ["-convert", "xml1", "-o", "-", plist_path]).plist - plist_min_os = plist["LSMinimumSystemVersion"].presence - break if plist_min_os + min_os = plist["LSMinimumSystemVersion"].presence + break if min_os + + next unless (main_binary = get_plist_main_binary(path)) + next if !File.exist?(main_binary) || File.open(main_binary, "rb") { |f| f.read(2) == "#!" } + + macho = MachO.open(main_binary) + min_os = case macho + when MachO::MachOFile + [ + macho[:LC_VERSION_MIN_MACOSX].first&.version_string, + macho[:LC_BUILD_VERSION].first&.minos_string, + ] + when MachO::FatFile + macho.machos.map do |slice| + [ + slice[:LC_VERSION_MIN_MACOSX].first&.version_string, + slice[:LC_BUILD_VERSION].first&.minos_string, + ] + end.flatten + end.compact.min + break if min_os end end begin - MacOSVersion.new(plist_min_os).strip_patch + MacOSVersion.new(min_os).strip_patch rescue MacOSVersion::Error nil end diff --git a/Library/Homebrew/cask/cask_loader.rb b/Library/Homebrew/cask/cask_loader.rb index 24befa53a3..4dd39bfc0f 100644 --- a/Library/Homebrew/cask/cask_loader.rb +++ b/Library/Homebrew/cask/cask_loader.rb @@ -295,7 +295,7 @@ module Cask sig { returns(Pathname) } 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 sig { @@ -320,7 +320,7 @@ module Cask sig { params( 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), ).void } diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 2bd371b6e2..22a55c4f1f 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -69,7 +69,6 @@ module Cask ].freeze DSL_METHODS = Set.new([ - :appcast, :arch, :artifacts, :auto_updates, @@ -123,15 +122,21 @@ module Cask sig { params(cask: Cask).void } def initialize(cask) - # NOTE: Variables set by `set_unique_stanza` must be initialized to `nil`. - @auto_updates = T.let(nil, T.nilable(T::Boolean)) + # NOTE: `:"@#{stanza}"` variables set by `set_unique_stanza` must be + # initialized to `nil`. @arch = T.let(nil, T.nilable(String)) + @arch_set_in_block = T.let(false, T::Boolean) @artifacts = T.let(ArtifactSet.new, ArtifactSet) + @auto_updates = T.let(nil, T.nilable(T::Boolean)) + @auto_updates_set_in_block = T.let(false, T::Boolean) + @autobump = T.let(true, T::Boolean) @called_in_on_system_block = T.let(false, T::Boolean) @cask = T.let(cask, Cask) @caveats = T.let(DSL::Caveats.new(cask), DSL::Caveats) @conflicts_with = T.let(nil, T.nilable(DSL::ConflictsWith)) + @conflicts_with_set_in_block = T.let(false, T::Boolean) @container = T.let(nil, T.nilable(DSL::Container)) + @container_set_in_block = T.let(false, T::Boolean) @depends_on = T.let(DSL::DependsOn.new, DSL::DependsOn) @depends_on_set_in_block = T.let(false, T::Boolean) @deprecated = T.let(false, T::Boolean) @@ -140,27 +145,32 @@ module Cask @deprecation_replacement_cask = T.let(nil, T.nilable(String)) @deprecation_replacement_formula = T.let(nil, T.nilable(String)) @desc = T.let(nil, T.nilable(String)) + @desc_set_in_block = T.let(false, T::Boolean) @disable_date = T.let(nil, T.nilable(Date)) @disable_reason = T.let(nil, T.nilable(T.any(String, Symbol))) @disable_replacement_cask = T.let(nil, T.nilable(String)) @disable_replacement_formula = T.let(nil, T.nilable(String)) @disabled = T.let(false, T::Boolean) @homepage = T.let(nil, T.nilable(String)) + @homepage_set_in_block = T.let(false, T::Boolean) @language_blocks = T.let({}, T::Hash[T::Array[String], Proc]) @language_eval = T.let(nil, T.nilable(String)) @livecheck = T.let(Livecheck.new(cask), Livecheck) @livecheck_defined = T.let(false, T::Boolean) @name = T.let([], T::Array[String]) - @autobump = T.let(true, T::Boolean) @no_autobump_defined = T.let(false, T::Boolean) @on_system_blocks_exist = T.let(false, T::Boolean) - @os = T.let(nil, T.nilable(String)) @on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion)) + @os = T.let(nil, T.nilable(String)) + @os_set_in_block = T.let(false, T::Boolean) @sha256 = T.let(nil, T.nilable(T.any(Checksum, Symbol))) + @sha256_set_in_block = T.let(false, T::Boolean) @staged_path = T.let(nil, T.nilable(Pathname)) @token = T.let(cask.token, String) @url = T.let(nil, T.nilable(URL)) + @url_set_in_block = T.let(false, T::Boolean) @version = T.let(nil, T.nilable(DSL::Version)) + @version_set_in_block = T.let(false, T::Boolean) end sig { returns(T::Boolean) } @@ -216,7 +226,7 @@ module Cask raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only appear once.") end - if instance_variable_defined?(:"@#{stanza}_set_in_block") && @called_in_on_system_block + if instance_variable_get(:"@#{stanza}_set_in_block") && @called_in_on_system_block raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only be overridden once.") end end @@ -476,7 +486,7 @@ module Cask def add_implicit_macos_dependency return if (cask_depends_on = @depends_on).present? && cask_depends_on.macos.present? - depends_on macos: ">= :#{MacOSVersion::SYMBOLS.key MacOSVersion::SYMBOLS.values.min}" + depends_on macos: ">= #{MacOSVersion.new(HOMEBREW_MACOS_OLDEST_ALLOWED).to_sym.inspect}" end # Declare conflicts that keep a cask from installing or working correctly. diff --git a/Library/Homebrew/cask/dsl/depends_on.rb b/Library/Homebrew/cask/dsl/depends_on.rb index 7a5756f9c0..ddaf48698f 100644 --- a/Library/Homebrew/cask/dsl/depends_on.rb +++ b/Library/Homebrew/cask/dsl/depends_on.rb @@ -52,16 +52,17 @@ module Cask raise "Only a single 'depends_on macos' is allowed." if defined?(@macos) # workaround for https://github.com/sorbet/sorbet/issues/6860 - first_arg = args.first&.to_s + first_arg = args.first + first_arg_s = first_arg&.to_s begin @macos = if args.count > 1 MacOSRequirement.new([args], comparator: "==") - elsif MacOSVersion::SYMBOLS.key?(args.first) + elsif first_arg.is_a?(Symbol) && MacOSVersion::SYMBOLS.key?(first_arg) MacOSRequirement.new([args.first], comparator: "==") - elsif (md = /^\s*(?<|>|[=<>]=)\s*:(?\S+)\s*$/.match(first_arg)) + elsif (md = /^\s*(?<|>|[=<>]=)\s*:(?\S+)\s*$/.match(first_arg_s)) MacOSRequirement.new([T.must(md[:version]).to_sym], comparator: md[:comparator]) - elsif (md = /^\s*(?<|>|[=<>]=)\s*(?\S+)\s*$/.match(first_arg)) + elsif (md = /^\s*(?<|>|[=<>]=)\s*(?\S+)\s*$/.match(first_arg_s)) MacOSRequirement.new([md[:version]], comparator: md[:comparator]) # This is not duplicate of the first case: see `args.first` and a different comparator. else # rubocop:disable Lint/DuplicateBranch diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index dc9cb82f5e..7819d4089a 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -4,6 +4,7 @@ require "formula_installer" require "unpack_strategy" require "utils/topological_hash" +require "utils/analytics" require "cask/config" require "cask/download" @@ -303,6 +304,20 @@ on_request: true) next if artifact.is_a?(Artifact::Binary) && !binaries? + artifact = T.cast( + artifact, + T.any( + Artifact::AbstractFlightBlock, + Artifact::Installer, + Artifact::KeyboardLayout, + Artifact::Mdimporter, + Artifact::Moved, + Artifact::Pkg, + Artifact::Qlplugin, + Artifact::Symlinked, + ), + ) + artifact.install_phase( command: @command, verbose: verbose?, adopt: adopt?, auto_updates: @cask.auto_updates, force: force?, predecessor: @@ -548,6 +563,18 @@ on_request: true) artifacts.each do |artifact| if artifact.respond_to?(:uninstall_phase) + artifact = T.cast( + artifact, + T.any( + Artifact::AbstractFlightBlock, + Artifact::KeyboardLayout, + Artifact::Moved, + Artifact::Qlplugin, + Artifact::Symlinked, + Artifact::Uninstall, + ), + ) + odebug "Uninstalling artifact of class #{artifact.class}" artifact.uninstall_phase( command: @command, @@ -562,6 +589,8 @@ on_request: true) next unless artifact.respond_to?(:post_uninstall_phase) + artifact = T.cast(artifact, Artifact::Uninstall) + odebug "Post-uninstalling artifact of class #{artifact.class}" artifact.post_uninstall_phase( command: @command, diff --git a/Library/Homebrew/cmd/fetch.rb b/Library/Homebrew/cmd/fetch.rb index 08b4de03d4..a7b13180db 100644 --- a/Library/Homebrew/cmd/fetch.rb +++ b/Library/Homebrew/cmd/fetch.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -6,6 +6,7 @@ require "formula" require "fetch" require "cask/download" require "retryable_download" +require "download_queue" module Homebrew module Cmd @@ -69,15 +70,16 @@ module Homebrew named_args [:formula, :cask], min: 1 end + sig { returns(Integer) } def concurrency - @concurrency ||= args.concurrency&.to_i || 1 + @concurrency ||= T.let(args.concurrency&.to_i || 1, T.nilable(Integer)) end + sig { returns(DownloadQueue) } def download_queue - @download_queue ||= begin - require "download_queue" + @download_queue ||= T.let(begin DownloadQueue.new(concurrency) - end + end, T.nilable(DownloadQueue)) end class Spinner @@ -96,8 +98,8 @@ module Homebrew sig { void } def initialize - @start = Time.now - @i = 0 + @start = T.let(Time.now, Time) + @i = T.let(0, Integer) end sig { returns(String) } @@ -136,7 +138,7 @@ module Homebrew bucket.each do |formula_or_cask| case formula_or_cask when Formula - formula = T.cast(formula_or_cask, Formula) + formula = formula_or_cask ref = formula.loaded_from_api? ? formula.full_name : formula.path os_arch_combinations.each do |os, arch| @@ -189,7 +191,9 @@ module Homebrew next if fetched_bottle - fetch_downloadable(formula.resource) + if (resource = formula.resource) + fetch_downloadable(resource) + end formula.resources.each do |r| fetch_downloadable(r) @@ -231,7 +235,7 @@ module Homebrew end else spinner = Spinner.new - remaining_downloads = downloads.dup + remaining_downloads = downloads.dup.to_a previous_pending_line_count = 0 begin @@ -332,10 +336,13 @@ module Homebrew private + sig { returns(T::Hash[T.any(Resource, Bottle, Cask::Download), Concurrent::Promises::Future]) } def downloads - @downloads ||= {} + @downloads ||= T.let({}, T.nilable(T::Hash[T.any(Resource, Bottle, Cask::Download), + Concurrent::Promises::Future])) end + sig { params(downloadable: T.any(Resource, Bottle, Cask::Download)).void } def fetch_downloadable(downloadable) downloads[downloadable] ||= begin tries = args.retry? ? {} : { tries: 1 } diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 5b9fda067d..e1dc53481b 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -18,7 +18,7 @@ module Homebrew class Info < AbstractCommand VALID_DAYS = %w[30 90 365].freeze VALID_FORMULA_CATEGORIES = %w[install install-on-request build-error].freeze - VALID_CATEGORIES = (VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze + VALID_CATEGORIES = T.let((VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze, T::Array[String]) cmd_args do description <<~EOS @@ -96,14 +96,17 @@ module Homebrew end print_analytics - elsif args.json + elsif (json = args.json) all = args.eval_all? - print_json(all) + print_json(json, all) elsif args.github? raise FormulaOrCaskUnspecifiedError if args.no_named? - exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) }) + exec_browser(*args.named.to_formulae_and_casks.map do |formula_keg_or_cask| + formula_or_cask = T.cast(formula_keg_or_cask, T.any(Formula, Cask::Cask)) + github_info(formula_or_cask) + end) elsif args.no_named? print_statistics else @@ -111,6 +114,7 @@ module Homebrew end end + sig { params(remote: String, path: String).returns(String) } def github_remote_path(remote, path) if remote =~ %r{^(?:https?://|git(?:@|://))github\.com[:/](.+)/(.+?)(?:\.git)?$} "https://github.com/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}/blob/HEAD/#{path}" @@ -175,6 +179,7 @@ module Homebrew end end + sig { params(version: T.any(T::Boolean, String)).returns(Symbol) } def json_version(version) version_hash = { true => :default, @@ -187,11 +192,11 @@ module Homebrew version_hash[version] end - sig { params(all: T::Boolean).void } - def print_json(all) + sig { params(json: T.any(T::Boolean, String), all: T::Boolean).void } + def print_json(json, all) raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named? - json = case json_version(args.json) + json = case json_version(json) when :v1, :default raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask? @@ -240,25 +245,31 @@ module Homebrew puts JSON.pretty_generate(json) end + sig { params(formula_or_cask: T.any(Formula, Cask::Cask)).returns(String) } def github_info(formula_or_cask) - return formula_or_cask.path if formula_or_cask.tap.blank? || formula_or_cask.tap.remote.blank? - path = case formula_or_cask when Formula formula = formula_or_cask - formula.path.relative_path_from(T.must(formula.tap).path) + tap = formula.tap + return formula.path.to_s if tap.blank? || tap.remote.blank? + + formula.path.relative_path_from(tap.path) when Cask::Cask cask = formula_or_cask + tap = cask.tap + return cask.sourcefile_path.to_s if tap.blank? || tap.remote.blank? + if cask.sourcefile_path.blank? || cask.sourcefile_path.extname != ".rb" - return "#{cask.tap.default_remote}/blob/HEAD/#{cask.tap.relative_cask_path(cask.token)}" + return "#{tap.default_remote}/blob/HEAD/#{tap.relative_cask_path(cask.token)}" end - cask.sourcefile_path.relative_path_from(cask.tap.path) + cask.sourcefile_path.relative_path_from(tap.path) end - github_remote_path(formula_or_cask.tap.remote, path) + github_remote_path(tap.remote, path.to_s) end + sig { params(formula: Formula).void } def info_formula(formula) specs = [] @@ -356,6 +367,7 @@ module Homebrew Utils::Analytics.formula_output(formula, args:) end + sig { params(dependencies: T::Array[Dependency]).returns(String) } def decorate_dependencies(dependencies) deps_status = dependencies.map do |dep| if dep.satisfied?([]) @@ -367,6 +379,7 @@ module Homebrew deps_status.join(", ") end + sig { params(requirements: T::Array[Requirement]).returns(String) } def decorate_requirements(requirements) req_status = requirements.map do |req| req_s = req.display_s @@ -375,12 +388,14 @@ module Homebrew req_status.join(", ") end + sig { params(dep: Dependency).returns(String) } def dep_display_s(dep) return dep.name if dep.option_tags.empty? "#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}" end + sig { params(cask: Cask::Cask).void } def info_cask(cask) require "cask/info" diff --git a/Library/Homebrew/cmd/tap-info.rb b/Library/Homebrew/cmd/tap-info.rb index bca5486604..8aacf5c6e9 100644 --- a/Library/Homebrew/cmd/tap-info.rb +++ b/Library/Homebrew/cmd/tap-info.rb @@ -63,6 +63,8 @@ module Homebrew puts info else info = "" + default_branches = %w[main master].freeze + taps.each_with_index do |tap, i| puts unless i.zero? info = "#{tap}: " @@ -79,7 +81,7 @@ module Homebrew info += "\norigin: #{tap.remote}" if tap.remote != tap.default_remote info += "\nHEAD: #{tap.git_head || "(none)"}" 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 info += "Not installed" end diff --git a/Library/Homebrew/cmd/tap.rb b/Library/Homebrew/cmd/tap.rb index 17fbfb5639..5af9393a0e 100644 --- a/Library/Homebrew/cmd/tap.rb +++ b/Library/Homebrew/cmd/tap.rb @@ -33,8 +33,6 @@ module Homebrew "integration.", replacement: false, disable: true - switch "--[no-]force-auto-update", - hidden: true switch "--custom-remote", description: "Install or change a tap with a custom remote. Useful for mirrors." switch "--repair", diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 91395f4e98..bcdaecb510 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -39,13 +39,15 @@ module Homebrew private + sig { void } def auto_update_header - @auto_update_header ||= begin + @auto_update_header ||= T.let(begin ohai "Auto-updated Homebrew!" if args.auto_update? true - end + end, T.nilable(T::Boolean)) end + sig { void } def output_update_report # Run `brew update` (again) if we've got a linuxbrew-core CoreTap if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? && @@ -293,14 +295,17 @@ module Homebrew EOS end + sig { returns(String) } def no_changes_message "No changes to formulae or casks." end + sig { params(revision: String).returns(String) } def shorten_revision(revision) Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp end + sig { void } def tap_or_untap_core_taps_if_necessary return if ENV["HOMEBREW_UPDATE_TEST"] @@ -320,10 +325,11 @@ module Homebrew return if (HOMEBREW_PREFIX/".homebrewdocker").exist? tap_output_header_printed = T.let(false, T::Boolean) + default_branches = %w[main master].freeze [CoreTap.instance, CoreCaskTap.instance].each do |tap| 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) ohai "#{tap.name} is old and unneeded, untapping to save space..." tap.uninstall @@ -339,6 +345,7 @@ module Homebrew end end + sig { params(repository: Pathname).void } def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY) command = "brew update" Utils::Link.link_completions(repository, command) @@ -351,10 +358,12 @@ module Homebrew EOS end + sig { void } def migrate_gcc_dependents_if_needed # do nothing end + sig { void } def analytics_message return if Utils::Analytics.messages_displayed? return if Utils::Analytics.no_message_output? @@ -384,6 +393,7 @@ module Homebrew Utils::Analytics.messages_displayed! if $stdout.tty? end + sig { void } def donation_message return if Settings.read("donationmessage") == "true" @@ -394,6 +404,7 @@ module Homebrew Settings.write "donationmessage", true if $stdout.tty? end + sig { void } def install_from_api_message return if Settings.read("installfromapimessage") == "true" @@ -418,30 +429,38 @@ require "extend/os/cmd/update-report" class Reporter class ReporterRevisionUnsetError < RuntimeError + sig { params(var_name: String).void } def initialize(var_name) super "#{var_name} is unset!" 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) @tap = tap # 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) - @api_names_txt = api_names_txt - @api_names_before_txt = api_names_before_txt - @api_dir_prefix = api_dir_prefix + @api_names_txt = T.let(api_names_txt, T.nilable(Pathname)) + @api_names_before_txt = T.let(api_names_before_txt, T.nilable(Pathname)) + @api_dir_prefix = T.let(api_dir_prefix, T.nilable(Pathname)) else 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? 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? end + + @report = T.let(nil, T.nilable(T::Hash[Symbol, T::Array[String]])) end + sig { params(auto_update: T::Boolean).returns(T::Hash[Symbol, T::Array[String]]) } def report(auto_update: false) return @report if @report @@ -482,9 +501,9 @@ class Reporter case status when "A", "D" 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] - @report[status.to_sym] << full_name unless new_tap + @report[T.must(status).to_sym] << full_name unless new_tap when "M" name = tap.formula_file_to_name(src) @@ -584,6 +603,7 @@ class Reporter @report end + sig { returns(T::Boolean) } def updated? if installed_from_api? diff.present? @@ -592,9 +612,10 @@ class Reporter end end + sig { void } def migrate_tap_migration - (report[:D] + report[:DC]).each do |full_name| - name = full_name.split("/").last + (Array(report[:D]) + Array(report[:DC])).each do |full_name| + name = T.must(full_name.split("/").last) new_tap_name = tap.tap_migrations[name] next if new_tap_name.nil? # skip if not in tap_migrations list. @@ -609,7 +630,7 @@ class Reporter end # 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? new_tap = Tap.fetch(new_tap_name) @@ -675,12 +696,14 @@ class Reporter end end + sig { void } def migrate_cask_rename Cask::Caskroom.casks.each do |cask| Cask::Migrator.migrate_if_needed(cask) end end + sig { params(force: T::Boolean, verbose: T::Boolean).void } def migrate_formula_rename(force:, verbose:) Formula.installed.each do |formula| next unless Migrator.needs_migration?(formula) @@ -704,14 +727,36 @@ class Reporter 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, api_dir_prefix = @api_dir_prefix) !api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil? end + sig { returns(String) } def diff + @diff ||= T.let(nil, T.nilable(String)) @diff ||= if installed_from_api? # 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. @@ -719,12 +764,14 @@ class Reporter header_regex = /^(---|\+\+\+) / add_delete_characters = ["+", "-"].freeze + api_dir_prefix_basename = T.must(api_dir_prefix).basename + diff_output.lines.filter_map do |line| next if line.match?(header_regex) next unless add_delete_characters.include?(line[0]) - line.sub(/^\+/, "A #{api_dir_prefix.basename}/") - .sub(/^-/, "D #{api_dir_prefix.basename}/") + line.sub(/^\+/, "A #{api_dir_prefix_basename}/") + .sub(/^-/, "D #{api_dir_prefix_basename}/") .sub(/$/, ".rb") .chomp end.join("\n") @@ -738,28 +785,33 @@ class Reporter end class ReporterHub + sig { returns(T::Array[Reporter]) } attr_reader :reporters sig { void } def initialize - @hash = {} - @reporters = [] + @hash = T.let({}, T::Hash[Symbol, T::Array[String]]) + @reporters = T.let([], T::Array[Reporter]) end + sig { params(key: Symbol).returns(T::Array[String]) } def select_formula_or_cask(key) @hash.fetch(key, []) end + sig { params(reporter: Reporter, auto_update: T::Boolean).void } def add(reporter, auto_update: false) @reporters << reporter report = reporter.report(auto_update:).delete_if { |_k, v| v.empty? } @hash.update(report) { |_key, oldval, newval| oldval.concat(newval) } end + sig { returns(T::Boolean) } def empty? @hash.empty? end + sig { params(auto_update: T::Boolean).void } def dump(auto_update: false) unless Homebrew::EnvConfig.no_update_report_new? dump_new_formula_report @@ -814,12 +866,14 @@ class ReporterHub private + sig { void } def dump_new_formula_report formulae = select_formula_or_cask(:A).sort.reject { |name| installed?(name) } output_dump_formula_or_cask_report "New Formulae", formulae end + sig { void } def dump_new_cask_report return if Homebrew::SimulateSystem.simulating_or_running_on_linux? @@ -830,6 +884,7 @@ class ReporterHub output_dump_formula_or_cask_report "New Casks", casks end + sig { void } def dump_deleted_formula_report formulae = select_formula_or_cask(:D).sort.filter_map do |name| pretty_uninstalled(name) if installed?(name) @@ -838,37 +893,43 @@ class ReporterHub output_dump_formula_or_cask_report "Deleted Installed Formulae", formulae end + sig { void } def dump_deleted_cask_report return if Homebrew::SimulateSystem.simulating_or_running_on_linux? 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) end output_dump_formula_or_cask_report "Deleted Installed Casks", casks end + sig { params(title: String, formulae_or_casks: T::Array[String]).void } def output_dump_formula_or_cask_report(title, formulae_or_casks) return if formulae_or_casks.blank? ohai title, Formatter.columns(formulae_or_casks.sort) end + sig { params(formula: String).returns(T::Boolean) } def installed?(formula) (HOMEBREW_CELLAR/formula.split("/").last).directory? end + sig { params(formula: String).returns(T::Boolean) } def outdated?(formula) Formula[formula].outdated? rescue FormulaUnavailableError false end + sig { params(cask: String).returns(T::Boolean) } def cask_installed?(cask) (Cask::Caskroom.path/cask).directory? end + sig { params(cask: String).returns(T::Boolean) } def cask_outdated?(cask) Cask::CaskLoader.load(cask).outdated? rescue Cask::CaskError diff --git a/Library/Homebrew/cmd/update.sh b/Library/Homebrew/cmd/update.sh index 81439dbe21..21bea55af7 100644 --- a/Library/Homebrew/cmd/update.sh +++ b/Library/Homebrew/cmd/update.sh @@ -54,9 +54,10 @@ git_init_if_necessary() { fi git config remote.origin.url "${HOMEBREW_BREW_GIT_REMOTE}" git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + git config fetch.prune true git fetch --force --tags origin git remote set-head origin --auto >/dev/null - git reset --hard origin/master + git reset --hard origin/HEAD SKIP_FETCH_BREW_REPOSITORY=1 set +e trap - EXIT @@ -77,9 +78,10 @@ git_init_if_necessary() { fi git config remote.origin.url "${HOMEBREW_CORE_GIT_REMOTE}" 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 reset --hard origin/master + git reset --hard origin/HEAD SKIP_FETCH_CORE_REPOSITORY=1 set +e trap - EXIT @@ -110,7 +112,7 @@ upstream_branch() { upstream_branch="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null)" fi upstream_branch="${upstream_branch#refs/remotes/origin/}" - [[ -z "${upstream_branch}" ]] && upstream_branch="master" + [[ -z "${upstream_branch}" ]] && upstream_branch="main" echo "${upstream_branch}" } @@ -242,7 +244,7 @@ merge_or_rebase() { Could not 'git stash' in ${DIR}! Please stash/commit manually if you need to keep your changes or, if not, run: cd ${DIR} - git reset --hard origin/master + git reset --hard origin/HEAD EOS fi git reset --hard "${QUIET_ARGS[@]}" @@ -260,10 +262,12 @@ EOS then git checkout --force "${UPSTREAM_BRANCH}" "${QUIET_ARGS[@]}" else - if [[ -n "${UPSTREAM_TAG}" && "${UPSTREAM_BRANCH}" != "master" ]] && - [[ "${INITIAL_BRANCH}" != "master" ]] + if [[ -n "${UPSTREAM_TAG}" && "${UPSTREAM_BRANCH}" != "master" && "${UPSTREAM_BRANCH}" != "main" ]] && + [[ "${INITIAL_BRANCH}" != "master" && "${INITIAL_BRANCH}" != "main" ]] 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 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." git remote set-url origin "${HOMEBREW_CORE_GIT_REMOTE}" 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 fi @@ -642,9 +647,9 @@ EOS UPDATING_MESSAGE_SHOWN=1 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 - # 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 ( UPSTREAM_REPOSITORY_URL="$(git config remote.origin.url)" @@ -732,9 +737,9 @@ EOS then local git_errors 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" ]] then - # Attempt migration from master to main branch. if git fetch --tags --force "${QUIET_ARGS[@]}" origin \ "refs/heads/main:refs/remotes/origin/main" 2>>"${tmp_failure_file}" then diff --git a/Library/Homebrew/data/schemas/sbom.json b/Library/Homebrew/data/schemas/sbom.json index a04cb525d5..687453268f 100644 --- a/Library/Homebrew/data/schemas/sbom.json +++ b/Library/Homebrew/data/schemas/sbom.json @@ -1,12 +1,12 @@ { "$schema" : "https://json-schema.org/draft/2019-09/schema", "$id" : "http://spdx.org/rdf/terms/2.3", - "title" : "SPDX 2.3", + "title" : "SPDX 2.3.1-dev", "type" : "object", "properties" : { "$schema": { "type": "string", - "description": "Reference the SPDX 2.3 JSON schema." + "description": "Reference the SPDX 2.3.1 JSON schema." }, "SPDXID" : { "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" ] }, "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" } }, @@ -270,10 +270,10 @@ } }, "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", "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" } }, @@ -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" ] }, "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" } }, @@ -375,10 +375,10 @@ "type" : "string" }, "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", "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" } }, @@ -417,7 +417,7 @@ "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.", "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" : { "description" : "This field provides a place for recording the date the package was released.", @@ -494,10 +494,10 @@ } }, "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", "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" } }, @@ -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" ] }, "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" } }, @@ -624,10 +624,10 @@ } }, "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", "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" } }, @@ -709,7 +709,7 @@ } }, "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" } }, diff --git a/Library/Homebrew/debrew/irb.rb b/Library/Homebrew/debrew/irb.rb index a18f892225..6f2ed6c8ed 100644 --- a/Library/Homebrew/debrew/irb.rb +++ b/Library/Homebrew/debrew/irb.rb @@ -1,13 +1,15 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "irb" module IRB + sig { params(binding: Binding).void } def self.start_within(binding) old_stdout_sync = $stdout.sync $stdout.sync = true + @setup_done ||= T.let(false, T.nilable(T::Boolean)) unless @setup_done setup(nil, argv: []) @setup_done = true diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb index 72e20c132e..e7ce29cc99 100644 --- a/Library/Homebrew/dev-cmd/bump-cask-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb @@ -190,7 +190,7 @@ module Homebrew def generate_system_options(cask) current_os = Homebrew::SimulateSystem.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 diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 8c417a4f63..a68aa0829e 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -399,7 +399,7 @@ module Homebrew nil end - if github_release_data.present? + if github_release_data.present? && github_release_data["body"].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. body = Formatter.truncate(github_release_data["body"], max: 32_768) diff --git a/Library/Homebrew/dev-cmd/create.rb b/Library/Homebrew/dev-cmd/create.rb index e3c4f660f8..1823ee59d9 100644 --- a/Library/Homebrew/dev-cmd/create.rb +++ b/Library/Homebrew/dev-cmd/create.rb @@ -183,41 +183,42 @@ module Homebrew :zig end - fc = FormulaCreator.new( - args.set_name, - args.set_version, - tap: args.tap, + formula_creator = FormulaCreator.new( url: args.named.fetch(0), + name: args.set_name, + version: args.set_version, + tap: args.tap, mode:, license: args.set_license, fetch: !args.no_fetch?, head: args.HEAD?, ) - fc.parse_url + # ask for confirmation if name wasn't passed explicitly if args.set_name.blank? - print "Formula name [#{fc.name}]: " - fc.name = __gets || fc.name + print "Formula name [#{formula_creator.name}]: " + confirmed_name = __gets + formula_creator.name = confirmed_name if confirmed_name.present? end - fc.verify + formula_creator.verify_tap_available! # Check for disallowed formula, or names that shadow aliases, # unless --force is specified. unless args.force? - if (reason = MissingFormula.disallowed_reason(fc.name)) + if (reason = MissingFormula.disallowed_reason(formula_creator.name)) odie <<~EOS - The formula '#{fc.name}' is not allowed to be created. + The formula '#{formula_creator.name}' is not allowed to be created. #{reason} If you really want to create this formula use `--force`. EOS end Homebrew.with_no_api_env do - if Formula.aliases.include? fc.name - realname = Formulary.canonical_name(fc.name) + if Formula.aliases.include?(formula_creator.name) + realname = Formulary.canonical_name(formula_creator.name) 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. To force creation use `--force`. EOS @@ -225,19 +226,19 @@ module Homebrew end end - path = fc.write_formula! + path = formula_creator.write_formula! formula = Homebrew.with_no_api_env do CoreTap.instance.clear_cache - Formula[fc.name] + Formula[formula_creator.name] end PyPI.update_python_resources! formula, ignore_non_pypi_packages: true if args.python? puts <<~EOS 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 install --build-from-source --verbose --debug #{fc.name} - HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{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 #{formula_creator.name} + HOMEBREW_NO_INSTALL_FROM_API=1 brew test #{formula_creator.name} EOS path end diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index 6063f9314f..bef3247be9 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:disable Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -172,7 +172,8 @@ module Homebrew with_monkey_patch { Formulary.from_contents(name, file, contents, ignore_errors: true) } 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: BottleSpecification.class_eval do 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 if method_defined?(: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 Module.class_eval do if method_defined?(: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 Resource.class_eval do if method_defined?(: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 DependencyCollector.class_eval do if method_defined?(: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 diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index 9f639aec52..11922c7212 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -45,7 +45,7 @@ module Homebrew Cask::Cask.generating_hash! 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 tap.cask_files.each do |path| cask = Cask::CaskLoader.load(path) diff --git a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb index dfaa9a050e..d6db1b472f 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-ci-matrix.rb @@ -67,8 +67,8 @@ module Homebrew end raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1 - labels = if pr_url - pr = GitHub::API.open_rest(pr_url.first) + labels = if pr_url && (first_pr_url = pr_url.first) + pr = GitHub::API.open_rest(first_pr_url) pr.fetch("labels").map { |l| l.fetch("name") } else [] @@ -263,11 +263,10 @@ module Homebrew audit_exceptions << %w[homepage_https_availability] if labels.include?("ci-skip-homepage") if labels.include?("ci-skip-livecheck") - audit_exceptions << %w[hosting_with_livecheck livecheck_https_availability - livecheck_min_os livecheck_version] + audit_exceptions << %w[hosting_with_livecheck livecheck_https_availability livecheck_version min_os] 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") audit_exceptions << %w[github_repository github_prerelease_version diff --git a/Library/Homebrew/dev-cmd/tap-new.rb b/Library/Homebrew/dev-cmd/tap-new.rb index b1b7e5278f..7be43d3ed8 100644 --- a/Library/Homebrew/dev-cmd/tap-new.rb +++ b/Library/Homebrew/dev-cmd/tap-new.rb @@ -44,12 +44,11 @@ module Homebrew titleized_repository = tap.repository.dup titleized_user[0] = titleized_user[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 - # FIXME: https://github.com/errata-ai/vale/issues/818 - # readme = <<~MARKDOWN # #{titleized_user} #{titleized_repository} @@ -70,7 +69,6 @@ module Homebrew `brew help`, `man brew` or check [Homebrew's documentation](https://docs.brew.sh). MARKDOWN - # write_path(tap, "README.md", readme) tests_yml = <<~ERB @@ -99,7 +97,7 @@ module Homebrew steps: - name: Set up Homebrew id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: token: ${{ github.token }} @@ -164,12 +162,12 @@ module Homebrew pull-requests: write steps: - name: Set up Homebrew - uses: Homebrew/actions/setup-homebrew@master + uses: Homebrew/actions/setup-homebrew@main with: token: ${{ github.token }} - name: Set up git - uses: Homebrew/actions/git-user-config@master + uses: Homebrew/actions/git-user-config@main - name: Pull bottles env: @@ -182,7 +180,7 @@ module Homebrew run: brew pr-pull --debug --tap="$GITHUB_REPOSITORY" "$PULL_REQUEST" - name: Push commits - uses: Homebrew/actions/git-try-push@master + uses: Homebrew/actions/git-try-push@main with: branch: <%= branch %> diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index 7e0329b1be..182c00241f 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -51,6 +51,11 @@ module Homebrew HOMEBREW_LIBRARY_PATH.cd do 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? only = args.only @@ -267,5 +272,3 @@ module Homebrew end end end - -require "extend/os/dev-cmd/tests" diff --git a/Library/Homebrew/dev-cmd/update-test.rb b/Library/Homebrew/dev-cmd/update-test.rb index 45a0257797..ec22c04843 100644 --- a/Library/Homebrew/dev-cmd/update-test.rb +++ b/Library/Homebrew/dev-cmd/update-test.rb @@ -142,6 +142,7 @@ module Homebrew private + sig { returns(String) } def git_tags tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname") if tags.blank? diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 80921c4cc6..2f701b22be 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -350,7 +350,6 @@ module Homebrew sudo chmod +t #{HOMEBREW_TEMP} EOS end - alias generic_check_tmpdir_sticky_bit check_tmpdir_sticky_bit def check_exist_directories return if HOMEBREW_PREFIX.writable? diff --git a/Library/Homebrew/extend/ENV/shared.rb b/Library/Homebrew/extend/ENV/shared.rb index d80724a8fa..38b7c16f4e 100644 --- a/Library/Homebrew/extend/ENV/shared.rb +++ b/Library/Homebrew/extend/ENV/shared.rb @@ -54,8 +54,6 @@ module SharedEnvExtension @debug_symbols = debug_symbols reset end - alias generic_shared_setup_build_environment setup_build_environment - private :generic_shared_setup_build_environment sig { void } def reset diff --git a/Library/Homebrew/extend/ENV/std.rb b/Library/Homebrew/extend/ENV/std.rb index b0c312c24a..22f5cb4056 100644 --- a/Library/Homebrew/extend/ENV/std.rb +++ b/Library/Homebrew/extend/ENV/std.rb @@ -68,7 +68,6 @@ module Stdenv gcc_formula = gcc_version_formula(cc) append_path "PATH", gcc_formula.opt_bin.to_s end - alias generic_setup_build_environment setup_build_environment sig { returns(T.nilable(PATH)) } def determine_pkg_config_libdir diff --git a/Library/Homebrew/extend/ENV/super.rb b/Library/Homebrew/extend/ENV/super.rb index 5dceddbac1..2e2be4f57e 100644 --- a/Library/Homebrew/extend/ENV/super.rb +++ b/Library/Homebrew/extend/ENV/super.rb @@ -125,7 +125,6 @@ module Superenv # These flags will also be present: # a - apply fix for apr-1-config path end - alias generic_setup_build_environment setup_build_environment private @@ -152,7 +151,6 @@ module Superenv .reverse .map { |d| d.opt_libexec/"bin" } end - alias generic_homebrew_extra_paths homebrew_extra_paths sig { returns(T.nilable(PATH)) } def determine_path @@ -372,8 +370,8 @@ module Superenv append_to_cccfg "O" end + # This is an exception where we want to use this method name format. # rubocop: disable Naming/MethodName - # Fixes style error `Naming/MethodName: Use snake_case for method names.` sig { params(block: T.nilable(T.proc.void)).void } def O0(&block) if block diff --git a/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb b/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb index e7f3d30d65..60bc593254 100644 --- a/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb +++ b/Library/Homebrew/extend/os/linux/dev-cmd/tests.rb @@ -15,6 +15,11 @@ module OS def os_bundle_args(bundle_args) non_macos_bundle_args(bundle_args) end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def os_files(files) + non_macos_files(files) + end end end end diff --git a/Library/Homebrew/extend/os/linux/dev-cmd/update-test.rb b/Library/Homebrew/extend/os/linux/dev-cmd/update-test.rb index 836a80c702..346fc1aa2a 100644 --- a/Library/Homebrew/extend/os/linux/dev-cmd/update-test.rb +++ b/Library/Homebrew/extend/os/linux/dev-cmd/update-test.rb @@ -1,19 +1,19 @@ # typed: strict # frozen_string_literal: true -module Homebrew - module DevCmd - class UpdateTest < AbstractCommand - alias generic_git_tags git_tags +module OS + module Linux + module DevCmd + module UpdateTest + private - private - - sig { returns(String) } - def git_tags - tags = generic_git_tags - tags = Utils.popen_read("git tag --list | sort -rV") if tags.blank? - tags + sig { returns(String) } + def git_tags + super.presence || Utils.popen_read("git tag --list | sort -rV") + end end end end end + +Homebrew::DevCmd::UpdateTest.prepend(OS::Linux::DevCmd::UpdateTest) diff --git a/Library/Homebrew/extend/os/linux/development_tools.rb b/Library/Homebrew/extend/os/linux/development_tools.rb index e65e5f8043..d725933391 100644 --- a/Library/Homebrew/extend/os/linux/development_tools.rb +++ b/Library/Homebrew/extend/os/linux/development_tools.rb @@ -62,7 +62,7 @@ module OS def build_system_info super.merge({ "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 diff --git a/Library/Homebrew/extend/os/linux/diagnostic.rb b/Library/Homebrew/extend/os/linux/diagnostic.rb index 034d047961..3cdf01a4d7 100644 --- a/Library/Homebrew/extend/os/linux/diagnostic.rb +++ b/Library/Homebrew/extend/os/linux/diagnostic.rb @@ -32,7 +32,7 @@ module OS end def check_tmpdir_sticky_bit - message = generic_check_tmpdir_sticky_bit + message = super return if message.nil? message + <<~EOS @@ -74,11 +74,11 @@ module OS end def check_supported_architecture - return if Hardware::CPU.intel? - return if Homebrew::EnvConfig.developer? && ENV["HOMEBREW_ARM64_TESTING"].present? && Hardware::CPU.arm? + return if ::Hardware::CPU.intel? + return if Homebrew::EnvConfig.developer? && ENV["HOMEBREW_ARM64_TESTING"].present? && ::Hardware::CPU.arm? <<~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). #{support_tier_message(tier: 2)} diff --git a/Library/Homebrew/extend/os/linux/extend/ENV/shared.rb b/Library/Homebrew/extend/os/linux/extend/ENV/shared.rb index fe4410d22e..284f62aa6b 100644 --- a/Library/Homebrew/extend/os/linux/extend/ENV/shared.rb +++ b/Library/Homebrew/extend/os/linux/extend/ENV/shared.rb @@ -1,16 +1,22 @@ # typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true -module SharedEnvExtension - def effective_arch - if @build_bottle && @bottle_arch - @bottle_arch.to_sym - elsif @build_bottle - Hardware.oldest_cpu - elsif Hardware::CPU.intel? || Hardware::CPU.arm? - :native - else - :dunno +module OS + module Linux + module SharedEnvExtension + def effective_arch + if @build_bottle && @bottle_arch + @bottle_arch.to_sym + elsif @build_bottle + ::Hardware.oldest_cpu + elsif ::Hardware::CPU.intel? || ::Hardware::CPU.arm? + :native + else + :dunno + end + end end end end + +SharedEnvExtension.prepend(OS::Linux::SharedEnvExtension) diff --git a/Library/Homebrew/extend/os/linux/extend/ENV/std.rb b/Library/Homebrew/extend/os/linux/extend/ENV/std.rb index ba2fa12309..67f2620405 100644 --- a/Library/Homebrew/extend/os/linux/extend/ENV/std.rb +++ b/Library/Homebrew/extend/os/linux/extend/ENV/std.rb @@ -1,36 +1,45 @@ # typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true -module Stdenv - 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) - generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:, - testing_formula:, debug_symbols:) +module OS + module Linux + module Stdenv + extend T::Helpers - prepend_path "CPATH", HOMEBREW_PREFIX/"include" - prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib" - prepend_path "LD_RUN_PATH", HOMEBREW_PREFIX/"lib" + requires_ancestor { ::SharedEnvExtension } - 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 "LIBRARY_PATH", @formula.lib - prepend_path "LD_RUN_PATH", @formula.lib - end + prepend_path "CPATH", HOMEBREW_PREFIX/"include" + prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib" + prepend_path "LD_RUN_PATH", HOMEBREW_PREFIX/"lib" - def libxml2 - append "CPPFLAGS", "-I#{Formula["libxml2"].include/"libxml2"}" - rescue FormulaUnavailableError - nil + return unless @formula + + prepend_path "CPATH", @formula.include + 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 + +Stdenv.prepend(OS::Linux::Stdenv) diff --git a/Library/Homebrew/extend/os/linux/extend/ENV/super.rb b/Library/Homebrew/extend/os/linux/extend/ENV/super.rb index 6c5fb6ff4c..7c6d518fce 100644 --- a/Library/Homebrew/extend/os/linux/extend/ENV/super.rb +++ b/Library/Homebrew/extend/os/linux/extend/ENV/super.rb @@ -1,79 +1,93 @@ # typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true -module Superenv - sig { returns(Pathname) } - def self.shims_path - HOMEBREW_SHIMS_PATH/"linux/super" - end +module OS + module Linux + module Superenv + extend T::Helpers - sig { returns(T.nilable(Pathname)) } - def self.bin - shims_path.realpath - end + requires_ancestor { SharedEnvExtension } + requires_ancestor { ::Superenv } - 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) - 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) } + module ClassMethods + sig { returns(Pathname) } + def shims_path + HOMEBREW_SHIMS_PATH/"linux/super" + end - # 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 + sig { returns(T.nilable(Pathname)) } + def bin + shims_path.realpath + end + end - def homebrew_extra_paths - paths = generic_homebrew_extra_paths - paths += %w[binutils make].filter_map do |f| - bin = Formulary.factory(f).opt_bin - bin if bin.directory? - rescue FormulaUnavailableError - nil + 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 + + 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 - 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 + +Superenv.singleton_class.prepend(OS::Linux::Superenv::ClassMethods) +Superenv.prepend(OS::Linux::Superenv) diff --git a/Library/Homebrew/extend/os/linux/formula_cellar_checks.rb b/Library/Homebrew/extend/os/linux/formula_cellar_checks.rb index b08411fb1b..a56c0c5582 100644 --- a/Library/Homebrew/extend/os/linux/formula_cellar_checks.rb +++ b/Library/Homebrew/extend/os/linux/formula_cellar_checks.rb @@ -1,9 +1,15 @@ # typed: strict # frozen_string_literal: true -module FormulaCellarChecks - sig { params(filename: Pathname).returns(T::Boolean) } - def valid_library_extension?(filename) - generic_valid_library_extension?(filename) || filename.basename.to_s.include?(".so.") +module OS + module Linux + module FormulaCellarChecks + sig { params(filename: Pathname).returns(T::Boolean) } + def valid_library_extension?(filename) + super || filename.basename.to_s.include?(".so.") + end + end end end + +FormulaCellarChecks.prepend(OS::Linux::FormulaCellarChecks) diff --git a/Library/Homebrew/extend/os/linux/hardware/cpu.rb b/Library/Homebrew/extend/os/linux/hardware/cpu.rb index b5c81ee078..2597af4eae 100644 --- a/Library/Homebrew/extend/os/linux/hardware/cpu.rb +++ b/Library/Homebrew/extend/os/linux/hardware/cpu.rb @@ -1,161 +1,171 @@ # typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true -module Hardware - class CPU - class << self - def optimization_flags - @optimization_flags ||= begin - flags = generic_optimization_flags.dup - flags[:native] = arch_flag(Homebrew::EnvConfig.arch) - flags - end - end +module OS + module Linux + module Hardware + module CPU + module ClassMethods + extend T::Helpers - def family - return :arm if arm? - return :ppc if ppc? - return :dunno unless intel? + requires_ancestor { T.class_of(::Hardware::CPU) } - # 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 + def optimization_flags + @optimization_flags ||= begin + flags = super.dup + flags[:native] = arch_flag(Homebrew::EnvConfig.arch) + flags + end end - when 0x0f - case cpu_model - when 0x06 - :presler - when 0x03, 0x04 - :prescott + + def family + 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 + # 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 - - 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 + +Hardware::CPU.singleton_class.prepend(OS::Linux::Hardware::CPU::ClassMethods) diff --git a/Library/Homebrew/extend/os/linux/install.rb b/Library/Homebrew/extend/os/linux/install.rb index 0fc3cd16dc..d035a2f547 100644 --- a/Library/Homebrew/extend/os/linux/install.rb +++ b/Library/Homebrew/extend/os/linux/install.rb @@ -1,132 +1,132 @@ # typed: true # rubocop:todo Sorbet/StrictSigil # frozen_string_literal: true -module Homebrew - module Install - # This is a list of known paths to the host dynamic linker on Linux if - # the host glibc is new enough. The symlink_ld_so method will fail if - # the host linker cannot be found in this list. - DYNAMIC_LINKERS = %w[ - /lib64/ld-linux-x86-64.so.2 - /lib64/ld64.so.2 - /lib/ld-linux.so.3 - /lib/ld-linux.so.2 - /lib/ld-linux-aarch64.so.1 - /lib/ld-linux-armhf.so.3 - /system/bin/linker64 - /system/bin/linker - ].freeze - private_constant :DYNAMIC_LINKERS +module OS + module Linux + module Install + module ClassMethods + # This is a list of known paths to the host dynamic linker on Linux if + # the host glibc is new enough. The symlink_ld_so method will fail if + # the host linker cannot be found in this list. + DYNAMIC_LINKERS = %w[ + /lib64/ld-linux-x86-64.so.2 + /lib64/ld64.so.2 + /lib/ld-linux.so.3 + /lib/ld-linux.so.2 + /lib/ld-linux-aarch64.so.1 + /lib/ld-linux-armhf.so.3 + /system/bin/linker64 + /system/bin/linker + ].freeze - # 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 - # as the other shared and static libraries are only used at build time where - # GCC can find its own libraries. - GCC_RUNTIME_LIBS = %w[ - libatomic.so.1 - libgcc_s.so.1 - libgomp.so.1 - libstdc++.so.6 - ].freeze - private_constant :GCC_RUNTIME_LIBS + # 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 + # as the other shared and static libraries are only used at build time where + # GCC can find its own libraries. + GCC_RUNTIME_LIBS = %w[ + libatomic.so.1 + libgcc_s.so.1 + libgomp.so.1 + libstdc++.so.6 + ].freeze - def self.perform_preinstall_checks(all_fatal: false) - generic_perform_preinstall_checks(all_fatal:) - symlink_ld_so - 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 + def perform_preinstall_checks(all_fatal: false) + super + symlink_ld_so + setup_preferred_gcc_libs end - # Add gcc to ld search paths - ld_gcc_conf = ld_so_conf_d/"50-homebrew-preferred-gcc.conf" - ld_gcc_conf_content = <<~EOS - # This file is generated by Homebrew. Do not modify. - #{gcc_opt_prefix}/lib/gcc/current - EOS - - if !ld_gcc_conf.exist? || (ld_gcc_conf.read != ld_gcc_conf_content) - ld_gcc_conf.atomic_write ld_gcc_conf_content - FileUtils.chmod "u=rw,go-wx", ld_gcc_conf - - FileUtils.rm_f HOMEBREW_PREFIX/"etc/ld.so.cache" - system HOMEBREW_PREFIX/"opt/glibc/sbin/ldconfig" + def global_post_install + super + symlink_ld_so + setup_preferred_gcc_libs end - else - odie "#{HOMEBREW_PREFIX}/lib does not exist!" unless (HOMEBREW_PREFIX/"lib").readable? - end - GCC_RUNTIME_LIBS.each do |library| - gcc_library_symlink = HOMEBREW_PREFIX/"lib/#{library}" + def check_cpu + return if ::Hardware::CPU.intel? && ::Hardware::CPU.is_64_bit? + return if ::Hardware::CPU.arm? - 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? + 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 + ::Kernel.abort message + end - # Also skip if the symlink already exists. - next if gcc_library_symlink.readable? && (gcc_library_symlink.readlink == gcc_library) + def symlink_ld_so + 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 - private_class_method :setup_preferred_gcc_libs end end + +Homebrew::Install.singleton_class.prepend(OS::Linux::Install::ClassMethods) diff --git a/Library/Homebrew/extend/os/linux/linkage_checker.rb b/Library/Homebrew/extend/os/linux/linkage_checker.rb index c2392f7d7b..634bd5b71f 100644 --- a/Library/Homebrew/extend/os/linux/linkage_checker.rb +++ b/Library/Homebrew/extend/os/linux/linkage_checker.rb @@ -3,48 +3,55 @@ require "compilers" -class LinkageChecker - # Libraries provided by glibc and gcc. - SYSTEM_LIBRARY_ALLOWLIST = %w[ - ld-linux-x86-64.so.2 - ld-linux-aarch64.so.1 - libanl.so.1 - libatomic.so.1 - libc.so.6 - libdl.so.2 - libm.so.6 - libmvec.so.1 - libnss_files.so.2 - libpthread.so.0 - libresolv.so.2 - librt.so.1 - libthread_db.so.1 - libutil.so.1 - libgcc_s.so.1 - libgomp.so.1 - libstdc++.so.6 - libquadmath.so.0 - ].freeze +module OS + module Linux + module LinkageChecker + # Libraries provided by glibc and gcc. + SYSTEM_LIBRARY_ALLOWLIST = %w[ + ld-linux-x86-64.so.2 + ld-linux-aarch64.so.1 + libanl.so.1 + libatomic.so.1 + libc.so.6 + libdl.so.2 + libm.so.6 + libmvec.so.1 + libnss_files.so.2 + libpthread.so.0 + libresolv.so.2 + librt.so.1 + libthread_db.so.1 + libutil.so.1 + libgcc_s.so.1 + libgomp.so.1 + libstdc++.so.6 + libquadmath.so.0 + ].freeze - private + private - def check_dylibs(rebuild_cache:) - generic_check_dylibs(rebuild_cache:) + def check_dylibs(rebuild_cache:) + super - # glibc and gcc are implicit dependencies. - # No other linkage to system libraries is expected or desired. - @unwanted_system_dylibs = @system_dylibs.reject do |s| - SYSTEM_LIBRARY_ALLOWLIST.include? File.basename(s) + # glibc and gcc are implicit dependencies. + # No other linkage to system libraries is expected or desired. + @unwanted_system_dylibs = @system_dylibs.reject do |s| + SYSTEM_LIBRARY_ALLOWLIST.include? File.basename(s) + end + + # 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 - - # 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 + +LinkageChecker.prepend(OS::Linux::LinkageChecker) diff --git a/Library/Homebrew/extend/os/linux/system_config.rb b/Library/Homebrew/extend/os/linux/system_config.rb index 13ded66558..90dc2de624 100644 --- a/Library/Homebrew/extend/os/linux/system_config.rb +++ b/Library/Homebrew/extend/os/linux/system_config.rb @@ -5,53 +5,59 @@ require "compilers" require "os/linux/glibc" require "system_command" -module SystemConfig - include SystemCommand::Mixin +module OS + 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 - version = OS::Linux::Glibc.system_version - return "N/A" if version.null? + def host_glibc_version + version = OS::Linux::Glibc.system_version + return "N/A" if version.null? - version - end + version + end - def host_gcc_version - gcc = DevelopmentTools.host_gcc_path - return "N/A" unless gcc.executable? + def host_gcc_version + gcc = ::DevelopmentTools.host_gcc_path + return "N/A" unless gcc.executable? - `#{gcc} --version 2>/dev/null`[/ (\d+\.\d+\.\d+)/, 1] - end + Utils.popen_read(gcc, "--version")[/ (\d+\.\d+\.\d+)/, 1] + end - def formula_linked_version(formula) - return "N/A" if Homebrew::EnvConfig.no_install_from_api? && !CoreTap.instance.installed? + def formula_linked_version(formula) + return "N/A" if Homebrew::EnvConfig.no_install_from_api? && !CoreTap.instance.installed? - Formulary.factory(formula).any_installed_version || "N/A" - rescue FormulaUnavailableError - "N/A" - end + Formulary.factory(formula).any_installed_version || "N/A" + rescue FormulaUnavailableError + "N/A" + end - def host_ruby_version - out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false) - return "N/A" unless status.success? + def host_ruby_version + out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false) + return "N/A" unless status.success? - out - end + out + end - def dump_verbose_config(out = $stdout) - kernel = Utils.safe_popen_read("uname", "-mors").chomp - dump_generic_verbose_config(out) - out.puts "Kernel: #{kernel}" - out.puts "OS: #{OS::Linux.os_version}" - out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl? - out.puts "Host glibc: #{host_glibc_version}" - out.puts "#{DevelopmentTools.host_gcc_path}: #{host_gcc_version}" - 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| - out.puts "#{f}: #{formula_linked_version(f)}" + def dump_verbose_config(out = $stdout) + kernel = Utils.safe_popen_read("uname", "-mors").chomp + super + out.puts "Kernel: #{kernel}" + out.puts "OS: #{OS::Linux.os_version}" + out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl? + out.puts "Host glibc: #{host_glibc_version}" + out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}" + 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| + out.puts "#{f}: #{formula_linked_version(f)}" + end + end end end end end + +SystemConfig.singleton_class.prepend(OS::Linux::SystemConfig::ClassMethods) diff --git a/Library/Homebrew/extend/os/mac/dev-cmd/tests.rb b/Library/Homebrew/extend/os/mac/dev-cmd/tests.rb index 112ad3b6c8..6b03ba3ccf 100644 --- a/Library/Homebrew/extend/os/mac/dev-cmd/tests.rb +++ b/Library/Homebrew/extend/os/mac/dev-cmd/tests.rb @@ -15,6 +15,11 @@ module OS def os_bundle_args(bundle_args) non_linux_bundle_args(bundle_args) end + + sig { params(files: T::Array[String]).returns(T::Array[String]) } + def os_files(files) + non_linux_files(files) + end end end end diff --git a/Library/Homebrew/extend/os/mac/extend/ENV/shared.rb b/Library/Homebrew/extend/os/mac/extend/ENV/shared.rb index 42a2cea1c7..30c7848af6 100644 --- a/Library/Homebrew/extend/os/mac/extend/ENV/shared.rb +++ b/Library/Homebrew/extend/os/mac/extend/ENV/shared.rb @@ -1,42 +1,50 @@ # typed: strict # frozen_string_literal: true -module SharedEnvExtension - 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) - generic_shared_setup_build_environment(formula:, cc:, build_bottle:, - bottle_arch:, testing_formula:, - debug_symbols:) +module OS + module Mac + module SharedEnvExtension + extend T::Helpers - # Normalise the system Perl version used, where multiple may be available - self["VERSIONER_PERL_VERSION"] = MacOS.preferred_perl_version - end + requires_ancestor { ::SharedEnvExtension } - sig { returns(T::Boolean) } - def no_weak_imports_support? - return false if compiler != :clang + 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 - return false if !MacOS::Xcode.version.null? && MacOS::Xcode.version < "8.0" - return false if !MacOS::CLT.version.null? && MacOS::CLT.version < "8.0" + # Normalise the system Perl version used, where multiple may be available + self["VERSIONER_PERL_VERSION"] = MacOS.preferred_perl_version + end - true - end + sig { returns(T::Boolean) } + def no_weak_imports_support? + return false if compiler != :clang - 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 + return false if !MacOS::Xcode.version.null? && MacOS::Xcode.version < "8.0" + return false if !MacOS::CLT.version.null? && MacOS::CLT.version < "8.0" + + true + end + + 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 + +SharedEnvExtension.prepend(OS::Mac::SharedEnvExtension) diff --git a/Library/Homebrew/extend/os/mac/extend/ENV/std.rb b/Library/Homebrew/extend/os/mac/extend/ENV/std.rb index c312eb1125..28298fedac 100644 --- a/Library/Homebrew/extend/os/mac/extend/ENV/std.rb +++ b/Library/Homebrew/extend/os/mac/extend/ENV/std.rb @@ -1,117 +1,125 @@ # typed: true # rubocop:disable Sorbet/StrictSigil # frozen_string_literal: true -module Stdenv - undef homebrew_extra_pkg_config_paths +module OS + module Mac + module Stdenv + extend T::Helpers - sig { returns(T::Array[Pathname]) } - def homebrew_extra_pkg_config_paths - [Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")] - end - private :homebrew_extra_pkg_config_paths + requires_ancestor { SharedEnvExtension } + requires_ancestor { ::Stdenv } - 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) - generic_setup_build_environment(formula:, cc:, build_bottle:, bottle_arch:, - testing_formula:, debug_symbols:) + sig { returns(T::Array[Pathname]) } + def homebrew_extra_pkg_config_paths + [Pathname("#{HOMEBREW_LIBRARY}/Homebrew/os/mac/pkgconfig/#{MacOS.version}")] + end + private :homebrew_extra_pkg_config_paths - 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. - delete("LC_ALL") - self["LC_CTYPE"] = "C" + append "LDFLAGS", "-Wl,-headerpad_max_install_names" - # Add `lib` and `include` etc. from the current `macosxsdk` to compiler flags: - macosxsdk(formula: @formula, testing_formula:) + # `sed` is strict and errors out when it encounters files with mixed character sets. + 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" - append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin" - end + return unless MacOS::Xcode.without_clt? - def remove_macosxsdk(version = nil) - # 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" + append_path "PATH", "#{MacOS::Xcode.prefix}/usr/bin" + append_path "PATH", "#{MacOS::Xcode.toolchain_path}/usr/bin" + end - sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version) - return unless sdk + def remove_macosxsdk(version = nil) + # 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") - remove_from_cflags "-isysroot#{sdk}" - remove "CPPFLAGS", "-isysroot#{sdk}" - remove "LDFLAGS", "-isysroot#{sdk}" - if HOMEBREW_PREFIX.to_s == "/usr/local" - 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 + sdk = self["SDKROOT"] || MacOS.sdk_path_if_needed(version) + return unless sdk + + delete("SDKROOT") + remove_from_cflags "-isysroot#{sdk}" + remove "CPPFLAGS", "-isysroot#{sdk}" + remove "LDFLAGS", "-isysroot#{sdk}" + if HOMEBREW_PREFIX.to_s == "/usr/local" + 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 - 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 + +Stdenv.prepend(OS::Mac::Stdenv) diff --git a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb index 8ed7ef263f..cf111ec8c7 100644 --- a/Library/Homebrew/extend/os/mac/extend/ENV/super.rb +++ b/Library/Homebrew/extend/os/mac/extend/ENV/super.rb @@ -1,171 +1,173 @@ # typed: true # rubocop:disable Sorbet/StrictSigil # frozen_string_literal: true -module Superenv - class << self - # The location of Homebrew's shims on macOS. - def shims_path - HOMEBREW_SHIMS_PATH/"mac/super" - end +module OS + module Mac + module Superenv + extend T::Helpers - undef bin + requires_ancestor { SharedEnvExtension } + requires_ancestor { ::Superenv } - def bin - return unless DevelopmentTools.installed? + module ClassMethods + sig { returns(Pathname) } + def shims_path + HOMEBREW_SHIMS_PATH/"mac/super" + end - shims_path.realpath - end - end + sig { returns(T.nilable(Pathname)) } + def bin + return unless ::DevelopmentTools.installed? - undef homebrew_extra_pkg_config_paths, - homebrew_extra_isystem_paths, homebrew_extra_library_paths, - 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" + shims_path.realpath + 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 - ENV["ac_have_clock_syscall"] = "no" + 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 + + 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 - - # 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 + +Superenv.singleton_class.prepend(OS::Mac::Superenv::ClassMethods) +Superenv.prepend(OS::Mac::Superenv) diff --git a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb index 324748d4d9..4aa06cc1eb 100644 --- a/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb +++ b/Library/Homebrew/extend/os/mac/formula_cellar_checks.rb @@ -4,133 +4,145 @@ require "cache_store" require "linkage_checker" -module FormulaCellarChecks - sig { returns(T.nilable(String)) } - def check_shadowed_headers - return if ["libtool", "subversion", "berkeley-db"].any? do |formula_name| - formula.name.start_with?(formula_name) - end +module OS + module Mac + module FormulaCellarChecks + extend T::Helpers - return if formula.name.match?(Version.formula_optionally_versioned_regex(:php)) - return if formula.keg_only? || !formula.include.directory? + requires_ancestor { Homebrew::FormulaAuditor } + requires_ancestor { ::FormulaCellarChecks } - files = relative_glob(formula.include, "**/*.h") - files &= relative_glob("#{MacOS.sdk_path}/usr/include", "**/*.h") - files.map! { |p| File.join(formula.include, p) } + sig { returns(T.nilable(String)) } + def check_shadowed_headers + 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 - Header files that shadow system header files were installed to "#{formula.include}" - The offending files are: - #{files * "\n "} - EOS - end + files = relative_glob(formula.include, "**/*.h") + files &= relative_glob("#{MacOS.sdk_path}/usr/include", "**/*.h") + files.map! { |p| File.join(formula.include, p) } - sig { returns(T.nilable(String)) } - def check_openssl_links - return unless formula.prefix.directory? + return if files.empty? - keg = Keg.new(formula.prefix) - system_openssl = keg.mach_o_files.select do |obj| - dlls = obj.dynamically_linked_libraries - dlls.any? { |dll| %r{/usr/lib/lib(crypto|ssl|tls)\..*dylib}.match? dll } - 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 + Header files that shadow system header files were installed to "#{formula.include}" + The offending files are: + #{files * "\n "} 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) + sig { returns(T.nilable(String)) } + def check_openssl_links + return unless formula.prefix.directory? - keg = ::Keg.new(formula.prefix) - flat_namespace_files = keg.mach_o_files.reject do |file| - next true unless file.dylib? + keg = ::Keg.new(formula.prefix) + system_openssl = keg.mach_o_files.select do |obj| + 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) - if MachO::Utils.fat_magic?(macho.magic) - macho.machos.map(&:header).all? { |h| h.flag? :MH_TWOLEVEL } - else - macho.header.flag? :MH_TWOLEVEL + <<~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 #{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 - 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 + +FormulaCellarChecks.prepend(OS::Mac::FormulaCellarChecks) diff --git a/Library/Homebrew/extend/os/mac/hardware.rb b/Library/Homebrew/extend/os/mac/hardware.rb index d984567c69..38a12686ce 100644 --- a/Library/Homebrew/extend/os/mac/hardware.rb +++ b/Library/Homebrew/extend/os/mac/hardware.rb @@ -1,25 +1,33 @@ # typed: strict # frozen_string_literal: true -module Hardware - sig { params(version: T.nilable(Version)).returns(Symbol) } - def self.oldest_cpu(version = nil) - version = if version - MacOSVersion.new(version.to_s) - else - MacOS.version - end - if CPU.arch == :arm64 - :arm_vortex_tempest - # This cannot use a newer CPU e.g. haswell because Rosetta 2 does not - # support AVX instructions in bottles: - # https://github.com/Homebrew/homebrew-core/issues/67713 - elsif version >= :ventura - :westmere - elsif version >= :mojave - :nehalem - else - generic_oldest_cpu +module OS + module Mac + module Hardware + module ClassMethods + sig { params(version: T.nilable(MacOSVersion)).returns(Symbol) } + def oldest_cpu(version = nil) + version = if version + MacOSVersion.new(version.to_s) + else + MacOS.version + end + if ::Hardware::CPU.arch == :arm64 + :arm_vortex_tempest + # This cannot use a newer CPU e.g. haswell because Rosetta 2 does not + # support AVX instructions in bottles: + # https://github.com/Homebrew/homebrew-core/issues/67713 + elsif version >= :ventura + :westmere + elsif version >= :mojave + :nehalem + else + super + end + end + end end end end + +Hardware.singleton_class.prepend(OS::Mac::Hardware::ClassMethods) diff --git a/Library/Homebrew/extend/os/mac/keg_relocate.rb b/Library/Homebrew/extend/os/mac/keg_relocate.rb index 5b56011faf..3c0a5d34ae 100644 --- a/Library/Homebrew/extend/os/mac/keg_relocate.rb +++ b/Library/Homebrew/extend/os/mac/keg_relocate.rb @@ -95,7 +95,7 @@ module OS end end - generic_fix_dynamic_linkage + super end def loader_name_for(file, target) @@ -199,7 +199,7 @@ module OS end 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"] } perl_path = if brewed_perl || name == "perl" diff --git a/Library/Homebrew/extend/os/mac/missing_formula.rb b/Library/Homebrew/extend/os/mac/missing_formula.rb index 8721980e76..6933c305d6 100644 --- a/Library/Homebrew/extend/os/mac/missing_formula.rb +++ b/Library/Homebrew/extend/os/mac/missing_formula.rb @@ -5,55 +5,59 @@ require "cask/info" require "cask/cask_loader" require "cask/caskroom" -module Homebrew - module MissingFormula - class << self - sig { params(name: String).returns(T.nilable(String)) } - def disallowed_reason(name) - case name.downcase - when "xcode" - <<~EOS - Xcode can be installed from the App Store. - EOS - else - generic_disallowed_reason(name) +module OS + module Mac + module MissingFormula + module ClassMethods + sig { params(name: String).returns(T.nilable(String)) } + def disallowed_reason(name) + case name.downcase + when "xcode" + <<~EOS + Xcode can be installed from the App Store. + EOS + else + super + 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) - return if silent + sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) } + def cask_reason(name, silent: false, show_info: false) + return if silent - suggest_command(name, show_info ? "info" : "install") - end + suggest_command(name, show_info ? "info" : "install") + end - sig { params(name: String, command: String).returns(T.nilable(String)) } - 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) + sig { params(name: String, command: String).returns(T.nilable(String)) } + def suggest_command(name, command) 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 - else - return + case command + 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 - suggestion - rescue Cask::CaskUnavailableError - nil end end end end + +Homebrew::MissingFormula.singleton_class.prepend(OS::Mac::MissingFormula::ClassMethods) diff --git a/Library/Homebrew/extend/os/mac/system_config.rb b/Library/Homebrew/extend/os/mac/system_config.rb index 0f84aca104..e7d33e4b56 100644 --- a/Library/Homebrew/extend/os/mac/system_config.rb +++ b/Library/Homebrew/extend/os/mac/system_config.rb @@ -6,46 +6,45 @@ require "system_command" module OS module Mac module SystemConfig - sig { returns(String) } - def describe_clang - return "N/A" if ::SystemConfig.clang.null? + module ClassMethods + extend T::Helpers - clang_build_info = ::SystemConfig.clang_build.null? ? "(parse error)" : ::SystemConfig.clang_build - "#{::SystemConfig.clang} build #{clang_build_info}" + requires_ancestor { T.class_of(::SystemConfig) } + + 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 - -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 +SystemConfig.singleton_class.prepend(OS::Mac::SystemConfig::ClassMethods) diff --git a/Library/Homebrew/extend/os/mac/utils/bottles.rb b/Library/Homebrew/extend/os/mac/utils/bottles.rb index 9b4c71a914..f982a4dca3 100644 --- a/Library/Homebrew/extend/os/mac/utils/bottles.rb +++ b/Library/Homebrew/extend/os/mac/utils/bottles.rb @@ -1,59 +1,66 @@ # typed: strict # frozen_string_literal: true -module Utils - module Bottles - class << self - module MacOSOverride - sig { params(tag: T.nilable(T.any(Symbol, Tag))).returns(Tag) } +module OS + module Mac + module Bottles + module ClassMethods + sig { params(tag: T.nilable(T.any(Symbol, Utils::Bottles::Tag))).returns(Utils::Bottles::Tag) } def tag(tag = nil) - return Tag.new(system: MacOS.version.to_sym, arch: Hardware::CPU.arch) if tag.nil? - - super + if tag.nil? + Utils::Bottles::Tag.new(system: MacOS.version.to_sym, arch: ::Hardware::CPU.arch) + else + super + end end end - prepend MacOSOverride - end + module Collector + extend T::Helpers - class Collector - private + requires_ancestor { Utils::Bottles::Collector } - 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)) } - def find_matching_tag(tag, no_older_versions: false) - # Used primarily by developers testing beta macOS releases. - if no_older_versions || - (OS::Mac.version.prerelease? && - Homebrew::EnvConfig.developer? && - Homebrew::EnvConfig.skip_or_later_bottles?) - generic_find_matching_tag(tag) - else - generic_find_matching_tag(tag) || - find_older_compatible_tag(tag) - end - 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 + sig { + params(tag: Utils::Bottles::Tag, + no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) + } + def find_matching_tag(tag, no_older_versions: false) + # Used primarily by developers testing beta macOS releases. + if no_older_versions || + (OS::Mac.version.prerelease? && + Homebrew::EnvConfig.developer? && + Homebrew::EnvConfig.skip_or_later_bottles?) + super(tag) + else + super(tag) || find_older_compatible_tag(tag) + 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| - next if candidate.standardized_arch != tag.standardized_arch + return if tag_version.blank? - candidate.to_macos_version <= tag_version - rescue MacOSVersion::Error - false + tags.find do |candidate| + next if candidate.standardized_arch != tag.standardized_arch + + candidate.to_macos_version <= tag_version + rescue MacOSVersion::Error + false + end end end end end end + +Utils::Bottles.singleton_class.prepend(OS::Mac::Bottles::ClassMethods) +Utils::Bottles::Collector.prepend(OS::Mac::Bottles::Collector) diff --git a/Library/Homebrew/formula_cellar_checks.rb b/Library/Homebrew/formula_cellar_checks.rb index 42609a20db..eff5063169 100644 --- a/Library/Homebrew/formula_cellar_checks.rb +++ b/Library/Homebrew/formula_cellar_checks.rb @@ -84,7 +84,6 @@ module FormulaCellarChecks def valid_library_extension?(filename) VALID_LIBRARY_EXTENSIONS.include? filename.extname end - alias generic_valid_library_extension? valid_library_extension? sig { returns(T.nilable(String)) } def check_non_libraries @@ -437,7 +436,6 @@ module FormulaCellarChecks problem_if_output(check_cpuid_instruction(formula)) problem_if_output(check_binary_arches(formula)) end - alias generic_audit_installed audit_installed private diff --git a/Library/Homebrew/formula_creator.rb b/Library/Homebrew/formula_creator.rb index 0d7507938e..4fc3d7c430 100644 --- a/Library/Homebrew/formula_creator.rb +++ b/Library/Homebrew/formula_creator.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "digest" @@ -7,62 +7,81 @@ require "erb" module Homebrew # Class for generating a formula from a template. class FormulaCreator + sig { returns(String) } attr_accessor :name + sig { returns(Version) } + attr_reader :version + + sig { returns(T::Boolean) } + attr_reader :head + 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 } - def initialize(name, version, tap:, url:, mode:, license:, fetch:, head:) - @name = name - @version = Version.new(version) if version - @tap = Tap.fetch(tap || "homebrew/core") + def initialize(url:, name: nil, version: nil, tap: nil, mode: nil, license: nil, fetch: false, head: false) @url = url - @mode = mode - @license = license - @fetch = fetch - @head = head - end - sig { void } - def verify - raise TapUnavailableError, @tap.name unless @tap.installed? - end - - sig { params(url: String).returns(T.nilable(String)) } - def self.name_from_url(url) - stem = Pathname.new(url).stem - # special cases first - if stem.start_with? "index.cgi" - # gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary - stem.rpartition("=").last - elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/} - # e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz - Regexp.last_match(1) - 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)}$/, "") + if name.blank? + stem = Pathname.new(url).stem + name = if stem.start_with?("index.cgi") && stem.include?("=") + # special cases first + # gitweb URLs e.g. http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary + stem.rpartition("=").last + elsif url =~ %r{github\.com/\S+/(\S+)/(archive|releases)/} + # e.g. https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz + T.must(Regexp.last_match(1)) + 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 + odebug "name from url: #{name}" end - end + @name = T.let(name, String) - sig { void } - def parse_url - @name = FormulaCreator.name_from_url(@url) if @name.blank? - odebug "name_from_url: #{@name}" - @version = Version.detect(@url) if @version.nil? + version = if version.present? + Version.new(version) + else + Version.detect(url) + 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} - @head = true + head = true user = Regexp.last_match(1) - repo = Regexp.last_match(2) - @github = GitHub.repository(user, repo) if @fetch + repository = Regexp.last_match(2) + github = GitHub.repository(user, repository) if fetch when %r{github\.com/(\S+)/(\S+)/(archive|releases)/} user = Regexp.last_match(1) - repo = Regexp.last_match(2) - @github = GitHub.repository(user, repo) if @fetch + repository = Regexp.last_match(2) + github = GitHub.repository(user, repository) if fetch 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 sig { returns(Pathname) } @@ -91,7 +110,7 @@ module Homebrew raise "Downloaded URL is not archive" end - @sha256 = filepath.sha256 + @sha256 = T.let(filepath.sha256, T.nilable(String)) end if @github @@ -106,6 +125,8 @@ module Homebrew path end + private + sig { params(name: String).returns(String) } def latest_versioned_formula(name) name_prefix = "#{name}@" @@ -116,8 +137,6 @@ module Homebrew sig { returns(String) } def template - # FIXME: https://github.com/errata-ai/vale/issues/818 - # <<~ERB # Documentation: https://docs.brew.sh/Formula-Cookbook # https://rubydoc.brew.sh/Formula @@ -261,7 +280,6 @@ module Homebrew end end ERB - # end end end diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index ff9d0f79b3..46dac9b308 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -51,6 +51,7 @@ HOMEBREW_HOME_PLACEHOLDER = "/$HOME" HOMEBREW_CASK_APPDIR_PLACEHOLDER = "$APPDIR" 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_ALLOWED = ENV.fetch("HOMEBREW_MACOS_OLDEST_ALLOWED").freeze diff --git a/Library/Homebrew/hardware.rb b/Library/Homebrew/hardware.rb index d7334667b5..6c21c8cdc9 100644 --- a/Library/Homebrew/hardware.rb +++ b/Library/Homebrew/hardware.rb @@ -42,7 +42,6 @@ module Hardware ppc64le: "-mcpu=powerpc64le", }.freeze, T.nilable(T::Hash[Symbol, String])) end - alias generic_optimization_flags optimization_flags sig { returns(Symbol) } def arch_32_bit @@ -219,6 +218,7 @@ module Hardware end end + sig { params(_version: T.nilable(MacOSVersion)).returns(Symbol) } def oldest_cpu(_version = nil) if Hardware::CPU.intel? if Hardware::CPU.is_64_bit? @@ -242,7 +242,6 @@ module Hardware Hardware::CPU.family end end - alias generic_oldest_cpu oldest_cpu # Returns a Rust flag to set the target CPU if necessary. # Defaults to nil. diff --git a/Library/Homebrew/install.rb b/Library/Homebrew/install.rb index d1f23e77e7..0d773bbaf5 100644 --- a/Library/Homebrew/install.rb +++ b/Library/Homebrew/install.rb @@ -37,7 +37,6 @@ module Homebrew end def global_post_install; end - alias generic_global_post_install global_post_install def check_prefix if (Hardware::CPU.intel? || Hardware::CPU.in_rosetta2?) && @@ -399,7 +398,6 @@ module Homebrew Diagnostic.checks(:supported_configuration_checks, fatal: all_fatal) Diagnostic.checks(:fatal_preinstall_checks) end - alias generic_perform_preinstall_checks perform_preinstall_checks def attempt_directory_creation Keg.must_exist_directories.each do |dir| diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index 5ae085f769..5b54c45bfa 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -77,7 +77,6 @@ class Keg FileUtils.ln_s(new_src, file) end end - alias generic_fix_dynamic_linkage fix_dynamic_linkage def relocate_dynamic_linkage(_relocation) [] @@ -102,7 +101,6 @@ class Keg relocation end - alias generic_prepare_relocation_to_placeholders prepare_relocation_to_placeholders def replace_locations_with_placeholders relocation = prepare_relocation_to_placeholders.freeze @@ -123,7 +121,6 @@ class Keg relocation end - alias generic_prepare_relocation_to_locations prepare_relocation_to_locations def replace_placeholders_with_locations(files, skip_linkage: false) relocation = prepare_relocation_to_locations.freeze @@ -221,7 +218,6 @@ class Keg [grep_bin, grep_args] end - alias generic_egrep_args egrep_args def each_unique_file_matching(string) Utils.popen_read("fgrep", recursive_fgrep_args, string, to_s) do |io| diff --git a/Library/Homebrew/linkage_checker.rb b/Library/Homebrew/linkage_checker.rb index 6d11765b2b..0db9ad05db 100644 --- a/Library/Homebrew/linkage_checker.rb +++ b/Library/Homebrew/linkage_checker.rb @@ -188,12 +188,10 @@ class LinkageChecker store&.update!(keg_files_dylibs:) end - alias generic_check_dylibs check_dylibs def system_libraries_exist_in_cache? false end - alias generic_system_libraries_exist_in_cache? system_libraries_exist_in_cache? def dylib_found_in_shared_cache?(dylib) @dyld_shared_cache_contains_path ||= begin diff --git a/Library/Homebrew/macos_version.rb b/Library/Homebrew/macos_version.rb index f71a2e6e73..6d1b8239ed 100644 --- a/Library/Homebrew/macos_version.rb +++ b/Library/Homebrew/macos_version.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strong # frozen_string_literal: true require "version" @@ -10,6 +10,7 @@ class MacOSVersion < Version sig { returns(T.nilable(T.any(String, Symbol))) } attr_reader :version + sig { params(version: T.nilable(T.any(String, Symbol))).void } def initialize(version) @version = version 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 # to `DEPRECATED_MACOS_VERSIONS` in `MacOSRequirement`. - SYMBOLS = { + SYMBOLS = T.let({ tahoe: "26", sequoia: "15", sonoma: "14", @@ -30,7 +31,7 @@ class MacOSVersion < Version high_sierra: "10.13", sierra: "10.12", el_capitan: "10.11", - }.freeze + }.freeze, T::Hash[Symbol, String]) sig { params(macos_version: MacOSVersion).returns(Version) } def self.kernel_major_version(macos_version) @@ -57,7 +58,9 @@ class MacOSVersion < 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 sig { override.params(other: T.untyped).returns(T.nilable(Integer)) } @@ -95,7 +98,7 @@ class MacOSVersion < Version sig { returns(Symbol) } def to_sym - return @sym if defined?(@sym) + return @sym if @sym sym = SYMBOLS.invert.fetch(strip_patch.to_s, :dunno) @@ -106,7 +109,7 @@ class MacOSVersion < Version sig { returns(String) } 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 @@ -154,5 +157,7 @@ class MacOSVersion < Version # Represents the absence of a version. # # 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 diff --git a/Library/Homebrew/missing_formula.rb b/Library/Homebrew/missing_formula.rb index 4b9dbdaee4..43e82914e3 100644 --- a/Library/Homebrew/missing_formula.rb +++ b/Library/Homebrew/missing_formula.rb @@ -93,7 +93,6 @@ module Homebrew EOS end end - alias generic_disallowed_reason disallowed_reason sig { params(name: String).returns(T.nilable(String)) } def tap_migration_reason(name) @@ -195,8 +194,10 @@ module Homebrew 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 + sig { params(name: String, command: String).returns(T.nilable(String)) } def suggest_command(name, command); end require "extend/os/missing_formula" diff --git a/Library/Homebrew/requirements/arch_requirement.rb b/Library/Homebrew/requirements/arch_requirement.rb index 65940a9946..4847d97774 100644 --- a/Library/Homebrew/requirements/arch_requirement.rb +++ b/Library/Homebrew/requirements/arch_requirement.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "requirement" @@ -7,10 +7,14 @@ require "requirement" class ArchRequirement < Requirement fatal true + @arch = T.let(nil, T.nilable(Symbol)) + + sig { returns(T.nilable(Symbol)) } attr_reader :arch + sig { params(tags: T::Array[Symbol]).void } def initialize(tags) - @arch = tags.shift + @arch = T.let(tags.shift, T.nilable(Symbol)) super end diff --git a/Library/Homebrew/requirements/xcode_requirement.rb b/Library/Homebrew/requirements/xcode_requirement.rb index 0d74f0e945..b5514b925a 100644 --- a/Library/Homebrew/requirements/xcode_requirement.rb +++ b/Library/Homebrew/requirements/xcode_requirement.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "requirement" @@ -7,6 +7,7 @@ require "requirement" class XcodeRequirement < Requirement fatal true + sig { returns(T.nilable(String)) } attr_reader :version satisfy(build_env: false) do @@ -14,8 +15,10 @@ class XcodeRequirement < Requirement xcode_installed_version end + sig { params(tags: T::Array[String]).void } 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 end @@ -53,6 +56,7 @@ class XcodeRequirement < Requirement "#<#{self.class.name}: version>=#{@version.inspect} #{tags.inspect}>" end + sig { returns(String) } def display_s return "#{name.capitalize} (on macOS)" unless @version diff --git a/Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi b/Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi index 1ea511aa2e..c0aec7da89 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi @@ -9,9 +9,6 @@ class Cask::Cask sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) } 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) } def appdir(*args, &block); end diff --git a/Library/Homebrew/sorbet/rbi/dsl/cask/config.rbi b/Library/Homebrew/sorbet/rbi/dsl/cask/config.rbi new file mode 100644 index 0000000000..5e2f38940e --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/dsl/cask/config.rbi @@ -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 diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/tap_cmd.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/tap_cmd.rbi index 235eb5e106..92dc432325 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/tap_cmd.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/tap_cmd.rbi @@ -23,9 +23,6 @@ class Homebrew::Cmd::TapCmd::Args < Homebrew::CLI::Args sig { returns(T::Boolean) } def force?; end - sig { returns(T::Boolean) } - def force_auto_update?; end - sig { returns(T::Boolean) } def repair?; end end diff --git a/Library/Homebrew/sorbet/tapioca/compilers/cask/config.rb b/Library/Homebrew/sorbet/tapioca/compilers/cask/config.rb new file mode 100644 index 0000000000..24984b3114 --- /dev/null +++ b/Library/Homebrew/sorbet/tapioca/compilers/cask/config.rb @@ -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 diff --git a/Library/Homebrew/system_config.rb b/Library/Homebrew/system_config.rb index b2f3be5ae8..3a1a53cb44 100644 --- a/Library/Homebrew/system_config.rb +++ b/Library/Homebrew/system_config.rb @@ -128,10 +128,12 @@ module SystemConfig 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} 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 - 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")}" elsif !tap.installed? out.puts "#{tap_name}: N/A" @@ -194,7 +196,6 @@ module SystemConfig out.puts hardware if hardware host_software_config(out) end - alias dump_generic_verbose_config dump_verbose_config end end diff --git a/Library/Homebrew/test/bundle/dsl_spec.rb b/Library/Homebrew/test/bundle/dsl_spec.rb index 27f47bffd3..abdd1e729a 100644 --- a/Library/Homebrew/test/bundle/dsl_spec.rb +++ b/Library/Homebrew/test/bundle/dsl_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Homebrew::Bundle::Dsl do cask_args appdir: '/Applications' tap 'homebrew/cask' 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 'mysql@5.6', restart_service: true, link: true, conflicts_with: ['mysql'] 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[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[2].options).to eql( - clone_target: "https://bitbucket.org/auto/update.git", - force_auto_update: true, - ) + expect(dsl.entries[2].options).to eql(clone_target: "https://bitbucket.org/auto/update.git") expect(dsl.entries[3].name).to eql("imagemagick") 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"]) diff --git a/Library/Homebrew/test/bundle/tap_installer_spec.rb b/Library/Homebrew/test/bundle/tap_installer_spec.rb index e49e90dee8..c254875e43 100644 --- a/Library/Homebrew/test/bundle/tap_installer_spec.rb +++ b/Library/Homebrew/test/bundle/tap_installer_spec.rb @@ -55,25 +55,5 @@ RSpec.describe Homebrew::Bundle::TapInstaller do expect(described_class.install("homebrew/cask", clone_target: "clone_target_path")).to be(false) 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 diff --git a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb index 7359c29261..0ba85761da 100644 --- a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb +++ b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Cask::Artifact::Artifact, :cask do end context "without target" do - it "fails to load" do + it "fails to load", :no_api do expect do Cask::CaskLoader.load("invalid-generic-artifact-no-target") end.to raise_error(Cask::CaskInvalidError, /Generic Artifact.*requires.*target/) diff --git a/Library/Homebrew/test/cask/artifact/manpage_spec.rb b/Library/Homebrew/test/cask/artifact/manpage_spec.rb index 23048504e1..022aee7519 100644 --- a/Library/Homebrew/test/cask/artifact/manpage_spec.rb +++ b/Library/Homebrew/test/cask/artifact/manpage_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Cask::Artifact::Manpage, :cask do context "without section" do 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/) end end diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index 43a7c6be40..5e79f5bc01 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -503,7 +503,7 @@ RSpec.describe Cask::Audit, :cask do end end - describe "livecheck should be skipped" do + describe "livecheck should be skipped", :no_api do let(:only) { ["livecheck_version"] } let(:online) { true } let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ } diff --git a/Library/Homebrew/test/cask/cask_loader/from_tap_loader_spec.rb b/Library/Homebrew/test/cask/cask_loader/from_tap_loader_spec.rb index b01ccfa21f..4f01b7279c 100644 --- a/Library/Homebrew/test/cask/cask_loader/from_tap_loader_spec.rb +++ b/Library/Homebrew/test/cask/cask_loader/from_tap_loader_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Cask::CaskLoader::FromTapLoader do expect { described_class.new("foo/bar/baz").load(config: nil) }.to raise_error(Cask::CaskUnavailableError) end - context "with sharded Cask directory" do + context "with sharded Cask directory", :no_api do let(:cask_path) { tap.cask_dir/cask_name[0]/"#{cask_name}.rb" } it "returns a Cask" do diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb index c9b75cf482..b93c6a33ff 100644 --- a/Library/Homebrew/test/cask/dsl_spec.rb +++ b/Library/Homebrew/test/cask/dsl_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe Cask::DSL, :cask do +RSpec.describe Cask::DSL, :cask, :no_api do let(:cask) { Cask::CaskLoader.load(token) } let(:token) { "basic-cask" } diff --git a/Library/Homebrew/test/cmd/tap_spec.rb b/Library/Homebrew/test/cmd/tap_spec.rb index 5d36439bfb..b01d2ec937 100644 --- a/Library/Homebrew/test/cmd/tap_spec.rb +++ b/Library/Homebrew/test/cmd/tap_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Homebrew::Cmd::TapCmd do it "taps a given Tap", :integration_test do path = setup_test_tap - expect { brew "tap", "--force-auto-update", "homebrew/bar", path/".git" } + expect { brew "tap", "homebrew/bar", path/".git" } .to output(/Tapped/).to_stderr .and be_a_success end diff --git a/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb index 2152491e82..5d78ee310f 100644 --- a/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb +++ b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb @@ -6,7 +6,7 @@ require "dev-cmd/bump-cask-pr" RSpec.describe Homebrew::DevCmd::BumpCaskPr do subject(:bump_cask_pr) { described_class.new(["test"]) } - let(:newest_macos) { MacOSVersion::SYMBOLS.keys.first } + let(:newest_macos) { MacOSVersion.new(HOMEBREW_MACOS_NEWEST_SUPPORTED).to_sym } let(:c) do Cask::Cask.new("test") do diff --git a/Library/Homebrew/test/formula_creator_spec.rb b/Library/Homebrew/test/formula_creator_spec.rb index a708686406..28d5c03f0d 100644 --- a/Library/Homebrew/test/formula_creator_spec.rb +++ b/Library/Homebrew/test/formula_creator_spec.rb @@ -3,28 +3,45 @@ require "formula_creator" RSpec.describe Homebrew::FormulaCreator do - it "gets name from GitHub archive URL" do - t = described_class.name_from_url("https://github.com/abitrolly/lapce/archive/v0.3.0.tar.gz") - expect(t).to eq("lapce") - end + describe ".new" do + tests = { + "generic tarball URL": { + url: "http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz", + name: "synscan", + version: "5.02", + }, + "gitweb URL": { + url: "http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary", + name: "libzipper", + }, + "GitHub repo URL": { + url: "https://github.com/abitrolly/lapce.git", + name: "lapce", + head: true, + }, + "GitHub archive URL": { + url: "https://github.com/abitrolly/lapce/archive/v0.3.0.tar.gz", + name: "lapce", + version: "0.3.0", + }, + "GitHub download URL": { + url: "https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz", + name: "stella", + version: "6.7", + }, + } - it "gets name from gitweb URL" do - t = described_class.name_from_url("http://www.codesrc.com/gitweb/index.cgi?p=libzipper.git;a=summary") - expect(t).to eq("libzipper") - end - - it "gets name from GitHub repo URL" do - t = described_class.name_from_url("https://github.com/abitrolly/lapce.git") - expect(t).to eq("lapce") - end - - it "gets name from GitHub download URL" do - t = described_class.name_from_url("https://github.com/stella-emu/stella/releases/download/6.7/stella-6.7-src.tar.xz") - expect(t).to eq("stella") - end - - it "gets name from generic tarball URL" do - t = described_class.name_from_url("http://digit-labs.org/files/tools/synscan/releases/synscan-5.02.tar.gz") - expect(t).to eq("synscan") + tests.each do |description, test| + it "parses #{description}" do + formula_creator = described_class.new(url: test.fetch(:url)) + expect(formula_creator.name).to eq(test.fetch(:name)) + if (version = test[:version]) + expect(formula_creator.version).to eq(version) + else + expect(formula_creator.version).to be_null + end + expect(formula_creator.head).to eq(test.fetch(:head, false)) + end + end end end diff --git a/Library/Homebrew/test/macos_version_spec.rb b/Library/Homebrew/test/macos_version_spec.rb index b6917f7797..706bcfc9a8 100644 --- a/Library/Homebrew/test/macos_version_spec.rb +++ b/Library/Homebrew/test/macos_version_spec.rb @@ -4,12 +4,15 @@ require "macos_version" RSpec.describe MacOSVersion do let(:version) { described_class.new("10.14") } + let(:tahoe_major) { described_class.new("26.0") } let(:big_sur_major) { described_class.new("11.0") } let(:big_sur_update) { described_class.new("11.1") } + let(:frozen_version) { described_class.new("10.14").freeze } - describe ".kernel_major_version" do + describe "::kernel_major_version" do it "returns the kernel major version" do expect(described_class.kernel_major_version(version)).to eq "18" + expect(described_class.kernel_major_version(tahoe_major)).to eq "25" expect(described_class.kernel_major_version(big_sur_major)).to eq "20" expect(described_class.kernel_major_version(big_sur_update)).to eq "20" end @@ -19,12 +22,43 @@ RSpec.describe MacOSVersion do end end + describe "::from_symbol" do + it "raises an error if the symbol is not a valid macOS version" do + expect do + described_class.from_symbol(:foo) + end.to raise_error(MacOSVersion::Error, "unknown or unsupported macOS version: :foo") + end + + it "creates a new version from a valid macOS version" do + symbol_version = described_class.from_symbol(:mojave) + expect(symbol_version).to eq(version) + end + end + + describe "#new" do + it "raises an error if the version is not a valid macOS version" do + expect do + described_class.new("1.2") + end.to raise_error(MacOSVersion::Error, 'unknown or unsupported macOS version: "1.2"') + end + + it "creates a new version from a valid macOS version" do + string_version = described_class.new("11") + expect(string_version).to eq(:big_sur) + end + end + specify "comparison with Symbol" do expect(version).to be > :high_sierra expect(version).to eq :mojave # We're explicitly testing the `===` operator results here. expect(version).to be === :mojave # rubocop:disable Style/CaseEquality expect(version).to be < :catalina + + # This should work like a normal comparison but the result won't be added + # to the `@comparison_cache` hash because the object is frozen. + expect(frozen_version).to eq :mojave + expect(frozen_version.instance_variable_get(:@comparison_cache)).to eq({}) end specify "comparison with Integer" do @@ -64,44 +98,90 @@ RSpec.describe MacOSVersion do end end - describe "#new" do - it "raises an error if the version is not a valid macOS version" do - expect do - described_class.new("1.2") - end.to raise_error(MacOSVersion::Error, 'unknown or unsupported macOS version: "1.2"') + describe "#strip_patch" do + let(:catalina_update) { described_class.new("10.15.1") } + + it "returns the version without the patch" do + expect(big_sur_update.strip_patch).to eq(described_class.new("11")) + expect(catalina_update.strip_patch).to eq(described_class.new("10.15")) end - it "creates a new version from a valid macOS version" do - string_version = described_class.new("11") - expect(string_version).to eq(:big_sur) + it "returns self if version is null" do + expect(described_class::NULL.strip_patch).to be described_class::NULL end end - describe "#from_symbol" do - it "raises an error if the symbol is not a valid macOS version" do - expect do - described_class.from_symbol(:foo) - end.to raise_error(MacOSVersion::Error, "unknown or unsupported macOS version: :foo") - end + specify "#to_sym" do + version_symbol = :mojave - it "creates a new version from a valid macOS version" do - symbol_version = described_class.from_symbol(:mojave) - expect(symbol_version).to eq(version) - end + # We call this more than once to exercise the caching logic + expect(version.to_sym).to eq(version_symbol) + expect(version.to_sym).to eq(version_symbol) + + # This should work like a normal but the symbol won't be stored as the + # `@sym` instance variable because the object is frozen. + expect(frozen_version.to_sym).to eq(version_symbol) + expect(frozen_version.instance_variable_get(:@sym)).to be_nil + + expect(described_class::NULL.to_sym).to eq(:dunno) end specify "#pretty_name" do + version_pretty_name = "Mojave" + expect(described_class.new("10.11").pretty_name).to eq("El Capitan") - expect(described_class.new("10.14").pretty_name).to eq("Mojave") + + # We call this more than once to exercise the caching logic + expect(version.pretty_name).to eq(version_pretty_name) + expect(version.pretty_name).to eq(version_pretty_name) + + # This should work like a normal but the computed name won't be stored as + # the `@pretty_name` instance variable because the object is frozen. + expect(frozen_version.pretty_name).to eq(version_pretty_name) + expect(frozen_version.instance_variable_get(:@pretty_name)).to be_nil end specify "#inspect" do expect(described_class.new("11").inspect).to eq("#") end - specify "#requires_nehalem_cpu?", :needs_macos do - expect(Hardware::CPU).to receive(:type).at_least(:twice).and_return(:intel) - expect(described_class.new("10.14").requires_nehalem_cpu?).to be true - expect(described_class.new("10.12").requires_nehalem_cpu?).to be false + specify "#outdated_release?" do + expect(described_class.new(described_class::SYMBOLS.values.first).outdated_release?).to be false + expect(described_class.new("10.0").outdated_release?).to be true + end + + specify "#prerelease?" do + expect(described_class.new("1000").prerelease?).to be true + end + + specify "#unsupported_release?" do + expect(described_class.new("10.0").unsupported_release?).to be true + expect(described_class.new("1000").prerelease?).to be true + end + + describe "#requires_nehalem_cpu?", :needs_macos do + context "when CPU is Intel" do + it "returns true if version requires a Nehalem CPU" do + allow(Hardware::CPU).to receive(:type).and_return(:intel) + expect(described_class.new("10.14").requires_nehalem_cpu?).to be true + end + + it "returns false if version does not require a Nehalem CPU" do + allow(Hardware::CPU).to receive(:type).and_return(:intel) + expect(described_class.new("10.12").requires_nehalem_cpu?).to be false + end + end + + context "when CPU is not Intel" do + it "raises an error" do + allow(Hardware::CPU).to receive(:type).and_return(:arm) + expect { described_class.new("10.14").requires_nehalem_cpu? } + .to raise_error(ArgumentError) + end + end + + it "returns false when version is null" do + expect(described_class::NULL.requires_nehalem_cpu?).to be false + end end end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index cc62d453c7..8bf8ea63d0 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -252,7 +252,7 @@ RSpec.configure do |config| # Link original API cache files to test cache directory. Pathname("#{ENV.fetch("HOMEBREW_CACHE")}/api").glob("*.json").each do |path| - FileUtils.ln path, HOMEBREW_CACHE/"api/#{path.basename}" + FileUtils.ln_s path, HOMEBREW_CACHE/"api/#{path.basename}" end begin diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 5493bd335f..6094117831 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -627,6 +627,12 @@ module GitHub pull_requests || [] end + # Check for duplicate pull requests that modify the same file. + # + # Exits the process on duplicates if `strict` or both `version` and + # `official_tap`, otherwise warns. + # + # @api internal sig { params( name: String, @@ -636,10 +642,11 @@ module GitHub state: T.nilable(String), version: T.nilable(String), official_tap: T::Boolean, + strict: T::Boolean, ).void } def self.check_for_duplicate_pull_requests(name, tap_remote_repo, file:, quiet: false, state: nil, - version: nil, official_tap: true) + version: nil, official_tap: true, strict: false) pull_requests = fetch_pull_requests(name, tap_remote_repo, state:, version:) pull_requests.select! do |pr| @@ -659,13 +666,13 @@ module GitHub Manually open these PRs if you are sure that they are not duplicates (and tell us that in the PR). EOS - if !official_tap - opoo duplicates_message - elsif version + if strict || (version && official_tap) odie <<~EOS #{duplicates_message.chomp} #{error_message} EOS + elsif !official_tap + opoo duplicates_message elsif quiet opoo error_message else diff --git a/Library/Homebrew/utils/github/api.rb b/Library/Homebrew/utils/github/api.rb index 6891ebf740..dba7173124 100644 --- a/Library/Homebrew/utils/github/api.rb +++ b/Library/Homebrew/utils/github/api.rb @@ -1,9 +1,10 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "system_command" module GitHub + sig { params(scopes: T::Array[String]).returns(String) } def self.pat_blurb(scopes = ALL_SCOPES) require "utils/formatter" require "utils/shell" @@ -16,20 +17,21 @@ module GitHub EOS end - API_URL = "https://api.github.com" - API_MAX_PAGES = 50 + API_URL = T.let("https://api.github.com", String) + API_MAX_PAGES = T.let(50, Integer) private_constant :API_MAX_PAGES - API_MAX_ITEMS = 5000 + API_MAX_ITEMS = T.let(5000, Integer) private_constant :API_MAX_ITEMS - PAGINATE_RETRY_COUNT = 3 + PAGINATE_RETRY_COUNT = T.let(3, Integer) private_constant :PAGINATE_RETRY_COUNT - CREATE_GIST_SCOPES = ["gist"].freeze - CREATE_ISSUE_FORK_OR_PR_SCOPES = ["repo"].freeze - CREATE_WORKFLOW_SCOPES = ["workflow"].freeze - ALL_SCOPES = (CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze + CREATE_GIST_SCOPES = T.let(["gist"].freeze, T::Array[String]) + CREATE_ISSUE_FORK_OR_PR_SCOPES = T.let(["repo"].freeze, T::Array[String]) + CREATE_WORKFLOW_SCOPES = T.let(["workflow"].freeze, T::Array[String]) + ALL_SCOPES = T.let((CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze, + T::Array[String]) private_constant :ALL_SCOPES - GITHUB_PERSONAL_ACCESS_TOKEN_REGEX = /^(?:[a-f0-9]{40}|(?:gh[pousr]|github_pat)_\w{36,251})$/ + GITHUB_PERSONAL_ACCESS_TOKEN_REGEX = T.let(/^(?:[a-f0-9]{40}|(?:gh[pousr]|github_pat)_\w{36,251})$/, Regexp) private_constant :GITHUB_PERSONAL_ACCESS_TOKEN_REGEX # Helper functions for accessing the GitHub API. @@ -40,46 +42,60 @@ module GitHub # Generic API error. class Error < RuntimeError + sig { returns(T.nilable(String)) } attr_reader :github_message + + sig { params(message: T.nilable(String), github_message: String).void } + def initialize(message = nil, github_message = T.unsafe(nil)) + @github_message = T.let(github_message, T.nilable(String)) + super(message) + end end # Error when the requested URL is not found. class HTTPNotFoundError < Error + sig { params(github_message: String).void } def initialize(github_message) - @github_message = github_message - super + super(nil, github_message) end end # Error when the API rate limit is exceeded. class RateLimitExceededError < Error + sig { params(reset: Integer, github_message: String).void } def initialize(reset, github_message) - @github_message = github_message new_pat_message = ", or:\n#{GitHub.pat_blurb}" if API.credentials.blank? - super <<~EOS + message = <<~EOS GitHub API Error: #{github_message} Try again in #{pretty_ratelimit_reset(reset)}#{new_pat_message} EOS + super(message, github_message) end + sig { params(reset: Integer).returns(String) } def pretty_ratelimit_reset(reset) pretty_duration(Time.at(reset) - Time.now) end end - GITHUB_IP_ALLOWLIST_ERROR = Regexp.new("Although you appear to have the correct authorization credentials, " \ - "the `(.+)` organization has an IP allow list enabled, " \ - "and your IP address is not permitted to access this resource").freeze + GITHUB_IP_ALLOWLIST_ERROR = T.let( + Regexp.new( + "Although you appear to have the correct authorization credentials, " \ + "the `(.+)` organization has an IP allow list enabled, " \ + "and your IP address is not permitted to access this resource", + ).freeze, + Regexp, + ) - NO_CREDENTIALS_MESSAGE = <<~MESSAGE.freeze + NO_CREDENTIALS_MESSAGE = T.let <<~MESSAGE.freeze, String No GitHub credentials found in macOS Keychain, GitHub CLI or the environment. #{GitHub.pat_blurb} MESSAGE # Error when authentication fails. class AuthenticationFailedError < Error + sig { params(credentials_type: Symbol, github_message: String).void } def initialize(credentials_type, github_message) - @github_message = github_message message = "GitHub API Error: #{github_message}\n" message << case credentials_type when :github_cli_token @@ -103,12 +119,13 @@ module GitHub when :none NO_CREDENTIALS_MESSAGE end - super message.freeze + super message.freeze, github_message end end # Error when the user has no GitHub API credentials set at all (macOS keychain, GitHub CLI or envvar). class MissingAuthenticationError < Error + sig { void } def initialize super NO_CREDENTIALS_MESSAGE end @@ -116,24 +133,21 @@ module GitHub # Error when the API returns a validation error. class ValidationFailedError < Error + sig { params(github_message: String, errors: T::Array[String]).void } def initialize(github_message, errors) - @github_message = if errors.empty? - github_message - else - "#{github_message}: #{errors}" - end + github_message = "#{github_message}: #{errors}" unless errors.empty? - super(@github_message) + super(github_message, github_message) end end - ERRORS = [ + ERRORS = T.let([ AuthenticationFailedError, HTTPNotFoundError, RateLimitExceededError, Error, JSON::ParserError, - ].freeze + ].freeze, T::Array[T.any(T.class_of(Error), T.class_of(JSON::ParserError))]) # Gets the token from the GitHub CLI for github.com. sig { returns(T.nilable(String)) } @@ -151,7 +165,7 @@ module GitHub print_stderr: false return unless result.success? - gh_out.chomp + gh_out.chomp.presence end end @@ -178,14 +192,16 @@ module GitHub # https://github.com/Homebrew/brew/issues/6862#issuecomment-572610344 return unless GITHUB_PERSONAL_ACCESS_TOKEN_REGEX.match?(github_password) - github_password + github_password.presence end end + sig { returns(T.nilable(String)) } def self.credentials + @credentials ||= T.let(nil, T.nilable(String)) @credentials ||= Homebrew::EnvConfig.github_api_token.presence - @credentials ||= github_cli_token.presence - @credentials ||= keychain_username_password.presence + @credentials ||= github_cli_token + @credentials ||= keychain_username_password end sig { returns(Symbol) } @@ -201,18 +217,19 @@ module GitHub end end - CREDENTIAL_NAMES = { + CREDENTIAL_NAMES = T.let({ env_token: "HOMEBREW_GITHUB_API_TOKEN", github_cli_token: "GitHub CLI login", keychain_username_password: "macOS Keychain GitHub", - }.freeze + }.freeze, T::Hash[Symbol, String]) # Given an API response from GitHub, warn the user if their credentials # have insufficient permissions. + sig { params(response_headers: T::Hash[String, String], needed_scopes: T::Array[String]).void } def self.credentials_error_message(response_headers, needed_scopes) return if response_headers.empty? - scopes = response_headers["x-accepted-oauth-scopes"].to_s.split(", ") + scopes = response_headers["x-accepted-oauth-scopes"].to_s.split(", ").presence needed_scopes = Set.new(scopes || needed_scopes) credentials_scopes = response_headers["x-oauth-scopes"] return if needed_scopes.subset?(Set.new(credentials_scopes.to_s.split(", "))) @@ -222,17 +239,35 @@ module GitHub credentials_scopes = "none" if credentials_scopes.blank? what = CREDENTIAL_NAMES.fetch(credentials_type) - @credentials_error_message ||= onoe <<~EOS - Your #{what} credentials do not have sufficient scope! - Scopes required: #{needed_scopes} - Scopes present: #{credentials_scopes} - #{github_permission_link} - EOS + @credentials_error_message ||= T.let(begin + error_message = <<~EOS + Your #{what} credentials do not have sufficient scope! + Scopes required: #{needed_scopes} + Scopes present: #{credentials_scopes} + #{github_permission_link} + EOS + onoe error_message + error_message + end, T.nilable(String)) end - def self.open_rest( - url, data: nil, data_binary_path: nil, request_method: nil, scopes: [].freeze, parse_json: true - ) + sig { + params( + url: T.any(String, URI::Generic), + data: T::Hash[Symbol, T.untyped], + data_binary_path: String, + request_method: Symbol, + scopes: T::Array[String], + parse_json: T::Boolean, + _block: T.nilable( + T.proc + .params(data: T::Hash[String, T.untyped]) + .returns(T.untyped), + ), + ).returns(T.untyped) + } + def self.open_rest(url, data: T.unsafe(nil), data_binary_path: T.unsafe(nil), request_method: T.unsafe(nil), + scopes: [].freeze, parse_json: true, &_block) # This is a no-op if the user is opting out of using the GitHub API. return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api? @@ -289,7 +324,7 @@ module GitHub begin if !http_code.start_with?("2") || !result.status.success? - raise_error(output, result.stderr, http_code, headers, scopes) + raise_error(output, result.stderr, http_code, headers || "", scopes) end return if http_code == "204" # No Content @@ -305,7 +340,18 @@ module GitHub end end - def self.paginate_rest(url, additional_query_params: nil, per_page: 100, scopes: [].freeze) + sig { + params( + url: T.any(String, URI::Generic), + additional_query_params: String, + per_page: Integer, + scopes: T::Array[String], + _block: T.proc + .params(result: T.untyped, page: Integer) + .returns(T.untyped), + ).void + } + def self.paginate_rest(url, additional_query_params: T.unsafe(nil), per_page: 100, scopes: [].freeze, &_block) (1..API_MAX_PAGES).each do |page| retry_count = 1 result = begin @@ -324,9 +370,17 @@ module GitHub end end - def self.open_graphql(query, variables: nil, scopes: [].freeze, raise_errors: true) + sig { + params( + query: String, + variables: T::Hash[Symbol, T.untyped], + scopes: T::Array[String], + raise_errors: T::Boolean, + ).returns(T.untyped) + } + def self.open_graphql(query, variables: {}, scopes: [].freeze, raise_errors: true) data = { query:, variables: } - result = open_rest("#{API_URL}/graphql", scopes:, data:, request_method: "POST") + result = open_rest("#{API_URL}/graphql", scopes:, data:, request_method: :POST) if raise_errors raise Error, result["errors"].map { |e| e["message"] }.join("\n") if result["errors"].present? @@ -340,17 +394,16 @@ module GitHub sig { params( query: String, - variables: T.nilable(T::Hash[Symbol, T.untyped]), + variables: T::Hash[Symbol, T.untyped], scopes: T::Array[String], raise_errors: T::Boolean, - _block: T.proc.params(data: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]), + _block: T.proc.params(data: T::Hash[String, T.untyped]).returns(T.untyped), ).void } - def self.paginate_graphql(query, variables: nil, scopes: [].freeze, raise_errors: true, &_block) + def self.paginate_graphql(query, variables: {}, scopes: [].freeze, raise_errors: true, &_block) result = API.open_graphql(query, variables:, scopes:, raise_errors:) has_next_page = T.let(true, T::Boolean) - variables ||= {} while has_next_page page_info = yield result has_next_page = page_info["hasNextPage"] @@ -361,6 +414,15 @@ module GitHub end end + sig { + params( + output: String, + errors: String, + http_code: String, + headers: String, + scopes: T::Array[String], + ).void + } def self.raise_error(output, errors, http_code, headers, scopes) json = begin JSON.parse(output) diff --git a/Library/Homebrew/utils/github/artifacts.rb b/Library/Homebrew/utils/github/artifacts.rb index 1bfc967d4f..e0463e9fd0 100644 --- a/Library/Homebrew/utils/github/artifacts.rb +++ b/Library/Homebrew/utils/github/artifacts.rb @@ -11,11 +11,11 @@ module GitHub # @param artifact_id [String] a value that uniquely identifies the downloaded artifact sig { params(url: String, artifact_id: String).void } def self.download_artifact(url, artifact_id) - raise API::MissingAuthenticationError if API.credentials == :none + token = API.credentials + raise API::MissingAuthenticationError if token.blank? # We use a download strategy here to leverage the Homebrew cache # to avoid repeated downloads of (possibly large) bottles. - token = API.credentials downloader = GitHubArtifactDownloadStrategy.new(url, artifact_id, token:) downloader.fetch downloader.stage diff --git a/README.md b/README.md index aafa616417..fdfcf448cb 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,6 @@ Secure password storage and syncing is provided by [1Password for Teams](https:/ [![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-light.png)](https://dnsimple.com/resolving/homebrew#gh-light-mode-only) [![DNSimple](https://cdn.dnsimple.com/assets/resolving-with-us/logo-dark.png)](https://dnsimple.com/resolving/homebrew#gh-dark-mode-only) -Homebrew is generously supported by [GitHub](https://github.com/github), [Custom Ink](https://github.com/customink), [Randy Reddig](https://github.com/ydnar), [Codecademy](https://github.com/Codecademy), [mikadelbert](https://github.com/mikadelbert), [Workbrew](https://github.com/Workbrew) and many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew). +Homebrew is generously supported by [GitHub](https://github.com/github), [Custom Ink](https://github.com/customink), [Randy Reddig](https://github.com/ydnar), [Codecademy](https://github.com/Codecademy), [Workbrew](https://github.com/Workbrew) and many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew). [![GitHub](https://github.com/github.png?size=64)](https://github.com/github)