Merge branch 'master' into codecov-action

This commit is contained in:
Tom Hu 2021-03-21 14:58:03 -04:00 committed by GitHub
commit ff7c05a285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
595 changed files with 5658 additions and 2890 deletions

View File

@ -1,38 +1,47 @@
name: New issue for Reproducible Bug name: New issue for Reproducible Bug
about: "If you're sure it's reproducible and not just your machine: submit an issue so we can investigate." description: "If you're sure it's reproducible and not just your machine: submit an issue so we can investigate."
labels: bug labels: bug
issue_body: false issue_body: false
inputs: body:
- type: description - type: markdown
attributes: attributes:
value: Please note we will close your issue without comment if you do not fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again. value: Please note we will close your issue without comment if you do not fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again.
- type: textarea - type: textarea
attributes: attributes:
render: shell
label: "`brew config` output" label: "`brew config` output"
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
render: shell
label: "`brew doctor` output" label: "`brew doctor` output"
validations:
required: true required: true
- type: checkboxes - type: checkboxes
attributes: attributes:
description: Please verify that you've followed these steps description: Please verify that you've followed these steps
choices: options:
- label: The `brew doctor` above contains no "Warning" lines. - label: The `brew doctor` above contains no "Warning" lines.
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: What were you trying to do (and why)? label: What were you trying to do (and why)?
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: What happened (include all command output)? label: What happened (include all command output)?
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: What did you expect to happen? label: What did you expect to happen?
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
render: shell
label: Step-by-step reproduction instructions (by running `brew` commands) label: Step-by-step reproduction instructions (by running `brew` commands)
validations:
required: true required: true

View File

@ -9,7 +9,7 @@ contact_links:
about: On macOS/Mac OS X? Having a `brew` problem with a `brew install` or `brew upgrade` of a single formula/package? Report it to Homebrew/homebrew-core (the macOS core tap/repository). about: On macOS/Mac OS X? Having a `brew` problem with a `brew install` or `brew upgrade` of a single formula/package? Report it to Homebrew/homebrew-core (the macOS core tap/repository).
- name: New issue on Homebrew/homebrew-cask - name: New issue on Homebrew/homebrew-cask
url: https://github.com/Homebrew/homebrew-cask/issues/new/choose url: https://github.com/Homebrew/homebrew-cask/issues/new/choose
about: Having a `brew cask` problem? Report it to Homebrew/homebrew-cask (the cask tap/repository). about: Having a `brew --cask` problem? Report it to Homebrew/homebrew-cask (the cask tap/repository).
- name: New issue on Homebrew/linuxbrew-core - name: New issue on Homebrew/linuxbrew-core
url: https://github.com/Homebrew/linuxbrew-core/issues/new/choose url: https://github.com/Homebrew/linuxbrew-core/issues/new/choose
about: On Linux? Having a `brew` problem with a `brew install` or `brew upgrade` of a single formula/package? Report it to Homebrew/linuxbrew-core (the Linux core tap/repository). about: On Linux? Having a `brew` problem with a `brew install` or `brew upgrade` of a single formula/package? Report it to Homebrew/linuxbrew-core (the Linux core tap/repository).

View File

@ -1,27 +1,31 @@
name: New issue for Feature Suggestion name: New issue for Feature Suggestion
about: Request our thoughts on your suggestion for a new feature for Homebrew. description: Request our thoughts on your suggestion for a new feature for Homebrew.
labels: features labels: features
issue_body: false issue_body: false
inputs: body:
- type: description - type: markdown
attributes: attributes:
value: Please note we will close your issue without comment if you do not fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again. value: Please note we will close your issue without comment if you do not fill out the issue checklist below and provide ALL the requested information. If you repeatedly fail to use the issue template, we will block you from ever submitting issues to Homebrew again.
- type: textarea - type: textarea
attributes: attributes:
label: Provide a detailed description of the proposed feature label: Provide a detailed description of the proposed feature
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: What is the motivation for the feature? label: What is the motivation for the feature?
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: How will the feature be relevant to at least 90% of Homebrew users? label: How will the feature be relevant to at least 90% of Homebrew users?
validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: What alternatives to the feature have been considered? label: What alternatives to the feature have been considered?
validations:
required: true required: true
- type: description - type: markdown
attributes: attributes:
value: We will close this issue or ask you to create a pull-request if it's something the maintainers are not actively planning to work on. value: We will close this issue or ask you to create a pull-request if it's something the maintainers are not actively planning to work on.

View File

@ -5,6 +5,5 @@
- [ ] Have you successfully run `brew style` with your changes locally? - [ ] Have you successfully run `brew style` with your changes locally?
- [ ] Have you successfully run `brew typecheck` with your changes locally? - [ ] Have you successfully run `brew typecheck` with your changes locally?
- [ ] Have you successfully run `brew tests` with your changes locally? - [ ] Have you successfully run `brew tests` with your changes locally?
- [ ] Have you successfully run `brew man` locally and committed any changes?
----- -----

View File

@ -10,10 +10,14 @@ updates:
directory: /docs directory: /docs
schedule: schedule:
interval: weekly interval: weekly
allow:
- dependency-type: all
- package-ecosystem: bundler - package-ecosystem: bundler
directory: /Library/Homebrew directory: /Library/Homebrew
schedule: schedule:
interval: daily interval: daily
allow:
- dependency-type: all
ignore: ignore:
- dependency-name: sorbet-runtime - dependency-name: sorbet-runtime

View File

@ -16,7 +16,7 @@ jobs:
tests: tests:
strategy: strategy:
matrix: matrix:
version: ['11-arm', '11.0', '10.15', '10.14'] version: ["11-arm", "11.0", "10.15", "10.14"]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.version }} runs-on: ${{ matrix.version }}
env: env:

View File

@ -31,8 +31,6 @@ jobs:
- run: brew style --display-cop-names - run: brew style --display-cop-names
- run: brew man --fail-if-changed
- run: brew typecheck - run: brew typecheck
- name: Run vale for docs linting - name: Run vale for docs linting
@ -81,6 +79,58 @@ jobs:
- name: Run brew audit --skip-style on all taps - name: Run brew audit --skip-style on all taps
run: brew audit --skip-style run: brew audit --skip-style
- name: Set up all Homebrew taps
run: |
HOMEBREW_REPOSITORY="$(brew --repo)"
HOMEBREW_CORE_REPOSITORY="$HOMEBREW_REPOSITORY/Library/Taps/homebrew/homebrew-core"
git -C "$HOMEBREW_CORE_REPOSITORY" remote add homebrew_core https://github.com/Homebrew/homebrew-core
git -C "$HOMEBREW_CORE_REPOSITORY" fetch homebrew_core || git -C "$HOMEBREW_CORE_REPOSITORY" fetch homebrew_core
git -C "$HOMEBREW_CORE_REPOSITORY" checkout --force -B master homebrew_core/master
brew tap homebrew/aliases
brew tap homebrew/bundle
brew tap homebrew/cask
brew tap homebrew/cask-drivers
brew tap homebrew/cask-fonts
brew tap homebrew/cask-versions
brew tap homebrew/command-not-found
brew tap homebrew/formula-analytics
brew tap homebrew/linux-dev
brew tap homebrew/portable-ruby
brew tap homebrew/services
brew update-reset Library/Taps/homebrew/homebrew-bundle
# brew style doesn't like world writable directories
sudo chmod -R g-w,o-w "$HOMEBREW_REPOSITORY/Library/Taps"
- name: Run brew style on homebrew-core
run: brew style --display-cop-names homebrew/core
- name: Run brew audit --skip-style on homebrew-core
run: brew audit --skip-style --tap=homebrew/core
env:
HOMEBREW_SIMULATE_MACOS_ON_LINUX: 1
- name: Run brew style on official taps
run: |
brew style --display-cop-names homebrew/bundle \
homebrew/services \
homebrew/test-bot
brew style --display-cop-names homebrew/aliases\
homebrew/command-not-found \
homebrew/formula-analytics \
homebrew/linux-dev \
homebrew/portable-ruby
- name: Run brew style on cask taps
run: |
brew style --display-cop-names homebrew/cask \
homebrew/cask-drivers \
homebrew/cask-fonts \
homebrew/cask-versions
vendored-gems: vendored-gems:
name: vendored gems (Linux) name: vendored gems (Linux)
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -231,29 +281,17 @@ jobs:
- name: Run brew readall on all taps - name: Run brew readall on all taps
run: brew readall --aliases run: brew readall --aliases
- name: Run brew style on homebrew-core - name: Run brew audit --skip-style on Cask taps
run: brew style --display-cop-names homebrew/core
- name: Run brew style on official taps
run: | run: |
brew style --display-cop-names homebrew/bundle \ brew audit --skip-style --tap=homebrew/cask
homebrew/services \ brew audit --skip-style --tap=homebrew/cask-drivers
homebrew/test-bot brew audit --skip-style --tap=homebrew/cask-fonts
brew audit --skip-style --tap=homebrew/cask-versions
- name: Run brew style on cask taps
run: |
brew style --display-cop-names homebrew/cask \
homebrew/cask-drivers \
homebrew/cask-fonts \
homebrew/cask-versions
- name: Run brew audit --skip-style on all taps
run: brew audit --skip-style
- name: Install brew tests dependencies - name: Install brew tests dependencies
run: | run: |
brew install subversion brew install subversion
Library/Homebrew/shims/scm/svn --homebrew=print-path brew sh -c "svn --homebrew=print-path"
which svn which svn
which svnadmin which svnadmin

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
- name: Re-run this workflow - name: Re-run this workflow
if: github.event_name == 'schedule' || github.event.action == 'closed' if: github.event_name == 'schedule' || github.event.action == 'closed'
uses: reitermarkus/rerun-workflow@e2647e8885422412d5acbd61f9add5b1e77ad3ab uses: reitermarkus/rerun-workflow@64cba9e834916060e77b7dad424d086837fdd0a6
with: with:
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
continuous-label: waiting for feedback continuous-label: waiting for feedback

82
.github/workflows/update-manpage.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: Update maintainers, manpage and completions
on:
push:
paths:
- .github/workflows/update-manpage.yml
- README.md
- Library/Homebrew/cmd/**
- Library/Homebrew/dev-cmd/**
- Library/Homebrew/completions/**
- Library/Homebrew/manpages/**
- Library/Homebrew/cli/parser.rb
- Library/Homebrew/completions.rb
- Library/Homebrew/env_config.rb
branches:
- master
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
update-manpage:
runs-on: ubuntu-latest
if: github.repository == 'Homebrew/brew'
steps:
- name: Setup Homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Configure Git user
uses: Homebrew/actions/git-user-config@master
with:
username: BrewTestBot
- name: Update maintainers, manpage and completions
id: update
run: |
git fetch origin
BRANCH=update-manpage
echo "::set-output name=branch::${BRANCH}"
if git ls-remote --exit-code --heads origin "$BRANCH"; then
git checkout "$BRANCH"
git reset --hard origin/master
else
git checkout -B "$BRANCH" origin/master
BRANCH_EXISTS="1"
fi
if [ "${{github.event_name}}" != "push" ]; then
brew update-maintainers
fi
if brew man --fail-if-not-changed; then
git add "$GITHUB_WORKSPACE/README.md" \
"$GITHUB_WORKSPACE/docs/Manpage.md" \
"$GITHUB_WORKSPACE/manpages/brew.1" \
"$GITHUB_WORKSPACE/completions"
git commit -m "Update maintainers, manpage and completions." \
-m "Autogenerated by the [update-manpage](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/update-manpage.yml) workflow."
echo "::set-output name=committed::true"
if [ -n "$BRANCH_EXISTS" ]; then
echo "::set-output name=pull_request::true"
fi
fi
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
- name: Push commits
if: steps.update.outputs.committed == 'true'
uses: Homebrew/actions/git-try-push@master
with:
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
branch: ${{ steps.update.outputs.branch }}
force: true
origin_branch: "master"
- name: Open a pull request
if: steps.update.outputs.pull_request == 'true'
run: hub pull-request --no-edit
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}

View File

@ -14,6 +14,7 @@ RUN apt-get update \
file \ file \
fonts-dejavu-core \ fonts-dejavu-core \
g++ \ g++ \
gawk \
git \ git \
less \ less \
libz-dev \ libz-dev \

View File

@ -240,6 +240,12 @@ Layout/SpaceAroundOperators:
Layout/RescueEnsureAlignment: Layout/RescueEnsureAlignment:
Enabled: false Enabled: false
# significantly less indentation involved; more consistent
Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
# favour parens-less DSL-style arguments # favour parens-less DSL-style arguments
Lint/AmbiguousBlockAssociation: Lint/AmbiguousBlockAssociation:
Enabled: false Enabled: false
@ -255,14 +261,6 @@ Naming/MemoizedInstanceVariableName:
Exclude: Exclude:
- "Homebrew/lazy_object.rb" - "Homebrew/lazy_object.rb"
# so many of these in formulae and can't be autocorrected
# TODO: fix these as `ruby -w` complains about them.
Lint/AmbiguousRegexpLiteral:
Exclude:
- "Taps/*/*/*.rb"
- "/**/Formula/*.rb"
- "**/Formula/*.rb"
# useful for metaprogramming in RSpec # useful for metaprogramming in RSpec
Lint/ConstantDefinitionInBlock: Lint/ConstantDefinitionInBlock:
Exclude: Exclude:
@ -462,6 +460,11 @@ Style/StringLiteralsInInterpolation:
Style/TernaryParentheses: Style/TernaryParentheses:
EnforcedStyle: require_parentheses_when_complex EnforcedStyle: require_parentheses_when_complex
# `unless ... ||` and `unless ... &&` are hard to mentally parse
Style/UnlessLogicalOperators:
Enabled: true
EnforcedStyle: forbid_logical_operators
# a bit confusing to non-Rubyists but useful for longer arrays # a bit confusing to non-Rubyists but useful for longer arrays
Style/WordArray: Style/WordArray:
MinSize: 4 MinSize: 4

View File

@ -11,8 +11,6 @@ RSpec/SubjectStub:
Enabled: false Enabled: false
# TODO: try to enable these # TODO: try to enable these
RSpec/ContextWording:
Enabled: false
RSpec/DescribeClass: RSpec/DescribeClass:
Enabled: false Enabled: false
RSpec/LeakyConstantDeclaration: RSpec/LeakyConstantDeclaration:
@ -21,8 +19,6 @@ RSpec/MessageSpies:
Enabled: false Enabled: false
RSpec/RepeatedDescription: RSpec/RepeatedDescription:
Enabled: false Enabled: false
RSpec/RepeatedExampleGroupDescription:
Enabled: false
RSpec/StubbedMock: RSpec/StubbedMock:
Enabled: false Enabled: false

View File

@ -7,10 +7,6 @@ Layout/MultilineMethodCallIndentation:
Exclude: Exclude:
- "**/*_spec.rb" - "**/*_spec.rb"
# TODO: add parentheses for these and remove
Lint/AssignmentInCondition:
Enabled: false
# `formula do` uses nested method definitions # `formula do` uses nested method definitions
Lint/NestedMethodDefinition: Lint/NestedMethodDefinition:
Exclude: Exclude:
@ -36,9 +32,8 @@ Metrics/PerceivedComplexity:
Max: 90 Max: 90
Metrics/MethodLength: Metrics/MethodLength:
Max: 260 Max: 260
# TODO: Reduce to 600 after refactoring utils/github
Metrics/ModuleLength: Metrics/ModuleLength:
Max: 620 Max: 600
Exclude: Exclude:
- "test/**/*" - "test/**/*"

View File

@ -5,6 +5,7 @@ source "https://rubygems.org"
# installed gems (should all be require: false) # installed gems (should all be require: false)
gem "bootsnap", require: false gem "bootsnap", require: false
gem "byebug", require: false gem "byebug", require: false
gem "minitest", require: false
gem "nokogiri", require: false gem "nokogiri", require: false
gem "parallel_tests", require: false gem "parallel_tests", require: false
gem "ronn", require: false gem "ronn", require: false
@ -20,6 +21,7 @@ gem "simplecov", require: false
gem "sorbet", require: false gem "sorbet", require: false
gem "sorbet-runtime", require: false gem "sorbet-runtime", require: false
gem "tapioca", require: false gem "tapioca", require: false
gem "warning", require: false
# vendored gems # vendored gems
gem "activesupport" gem "activesupport"

View File

@ -1,7 +1,7 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (6.1.2) activesupport (6.1.3)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -28,7 +28,7 @@ GEM
hpricot (0.8.6) hpricot (0.8.6)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (1.8.8) i18n (1.8.9)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
mechanize (2.7.7) mechanize (2.7.7)
domain_name (~> 0.5, >= 0.5.1) domain_name (~> 0.5, >= 0.5.1)
@ -43,22 +43,22 @@ GEM
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104) mime-types-data (3.2021.0212)
mini_portile2 (2.5.0) mini_portile2 (2.5.0)
minitest (5.14.3) minitest (5.14.4)
msgpack (1.4.2) msgpack (1.4.2)
mustache (1.1.1) mustache (1.1.1)
net-http-digest_auth (1.4.1) net-http-digest_auth (1.4.1)
net-http-persistent (4.0.1) net-http-persistent (4.0.1)
connection_pool (~> 2.2) connection_pool (~> 2.2)
nokogiri (1.11.1) nokogiri (1.11.2)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
ntlm-http (0.1.1) ntlm-http (0.1.1)
parallel (1.20.1) parallel (1.20.1)
parallel_tests (3.4.0) parallel_tests (3.5.2)
parallel parallel
parlour (5.0.0) parlour (6.0.0)
commander (~> 4.5) commander (~> 4.5)
parser parser
rainbow (~> 3.0) rainbow (~> 3.0)
@ -68,14 +68,14 @@ GEM
patchelf (1.3.0) patchelf (1.3.0)
elftools (>= 1.1.3) elftools (>= 1.1.3)
plist (3.6.0) plist (3.6.0)
pry (0.13.1) pry (0.14.0)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
racc (1.5.2) racc (1.5.2)
rack (2.2.3) rack (2.2.3)
rainbow (3.0.0) rainbow (3.0.0)
rdiscount (2.2.0.2) rdiscount (2.2.0.2)
regexp_parser (2.0.3) regexp_parser (2.1.1)
rexml (3.2.4) rexml (3.2.4)
ronn (0.7.3) ronn (0.7.3)
hpricot (>= 0.8.2) hpricot (>= 0.8.2)
@ -85,9 +85,9 @@ GEM
rspec-core (~> 3.10.0) rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0) rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0) rspec-mocks (~> 3.10.0)
rspec-core (3.10.0) rspec-core (3.10.1)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-expectations (3.10.0) rspec-expectations (3.10.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-github (2.3.1) rspec-github (2.3.1)
@ -95,7 +95,7 @@ GEM
rspec-its (1.3.0) rspec-its (1.3.0)
rspec-core (>= 3.0.0) rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0) rspec-expectations (>= 3.0.0)
rspec-mocks (3.10.0) rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-retry (0.6.2) rspec-retry (0.6.2)
@ -103,10 +103,10 @@ GEM
rspec-sorbet (1.8.0) rspec-sorbet (1.8.0)
sorbet sorbet
sorbet-runtime sorbet-runtime
rspec-support (3.10.0) rspec-support (3.10.2)
rspec-wait (0.0.9) rspec-wait (0.0.9)
rspec (>= 3, < 4) rspec (>= 3, < 4)
rubocop (1.9.1) rubocop (1.11.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
@ -117,7 +117,7 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.4.1) rubocop-ast (1.4.1)
parser (>= 2.7.1.5) parser (>= 2.7.1.5)
rubocop-performance (1.9.2) rubocop-performance (1.10.1)
rubocop (>= 0.90.0, < 2.0) rubocop (>= 0.90.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.9.1) rubocop-rails (2.9.1)
@ -127,7 +127,7 @@ GEM
rubocop-rspec (2.2.0) rubocop-rspec (2.2.0)
rubocop (~> 1.0) rubocop (~> 1.0)
rubocop-ast (>= 1.1.0) rubocop-ast (>= 1.1.0)
rubocop-sorbet (0.5.1) rubocop-sorbet (0.6.1)
rubocop rubocop
ruby-macho (2.5.0) ruby-macho (2.5.0)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
@ -139,28 +139,30 @@ GEM
simplecov_json_formatter (0.1.2) simplecov_json_formatter (0.1.2)
sorbet (0.5.6274) sorbet (0.5.6274)
sorbet-static (= 0.5.6274) sorbet-static (= 0.5.6274)
sorbet-runtime (0.5.6267) sorbet-runtime (0.5.6274)
sorbet-runtime-stub (0.2.0) sorbet-runtime-stub (0.2.0)
sorbet-static (0.5.6274-universal-darwin-14) sorbet-static (0.5.6274-universal-darwin-14)
spoom (1.0.7) spoom (1.0.9)
colorize colorize
sorbet (~> 0.5.5) sorbet (~> 0.5.5)
sorbet-runtime sorbet-runtime
thor (>= 0.19.2) thor (>= 0.19.2)
tapioca (0.4.13) tapioca (0.4.17)
bundler (>= 1.17.3)
parlour (>= 2.1.0) parlour (>= 2.1.0)
pry (>= 0.12.2) pry (>= 0.12.2)
sorbet-runtime sorbet-runtime
sorbet-static (>= 0.4.4471) sorbet-static (>= 0.4.4471)
spoom spoom
thor (>= 0.19.2) thor (>= 0.19.2)
thor (1.0.1) thor (1.1.0)
tzinfo (2.0.4) tzinfo (2.0.4)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.7) unf_ext (0.0.7.7)
unicode-display_width (2.0.0) unicode-display_width (2.0.0)
warning (1.2.0)
webrick (1.7.0) webrick (1.7.0)
webrobots (0.1.2) webrobots (0.1.2)
zeitwerk (2.4.2) zeitwerk (2.4.2)
@ -174,6 +176,7 @@ DEPENDENCIES
byebug byebug
concurrent-ruby concurrent-ruby
mechanize mechanize
minitest
nokogiri nokogiri
parallel_tests parallel_tests
patchelf patchelf
@ -197,6 +200,7 @@ DEPENDENCIES
sorbet-runtime sorbet-runtime
sorbet-runtime-stub sorbet-runtime-stub
tapioca tapioca
warning
BUNDLED WITH BUNDLED WITH
1.17.3 1.17.3

180
Library/Homebrew/archive.rb Normal file
View File

@ -0,0 +1,180 @@
# typed: false
# frozen_string_literal: true
require "digest/md5"
require "utils/curl"
# The Internet Archive API client.
#
# @api private
class Archive
extend T::Sig
include Context
include Utils::Curl
class Error < RuntimeError
end
URL_PREFIX = "https://archive.org"
S3_DOMAIN = "s3.us.archive.org"
sig { returns(String) }
def inspect
"#<Archive: item=#{@archive_item}>"
end
sig { params(item: T.nilable(String)).void }
def initialize(item: "homebrew")
raise UsageError, "Must set the Archive item!" unless item
@archive_item = item
end
def open_api(url, *args, auth: true)
if auth
key = Homebrew::EnvConfig.internet_archive_key
raise UsageError, "HOMEBREW_INTERNET_ARCHIVE_KEY is unset." if key.blank?
if key.exclude?(":")
raise UsageError, "Use HOMEBREW_INTERNET_ARCHIVE_KEY=access:secret. See #{URL_PREFIX}/account/s3.php"
end
args += ["--header", "Authorization: AWS #{key}"]
end
curl(*args, url, print_stdout: false, secrets: key)
end
sig {
params(local_file: String,
directory: String,
remote_file: String,
warn_on_error: T.nilable(T::Boolean)).void
}
def upload(local_file, directory:, remote_file:, warn_on_error: false)
local_file = Pathname.new(local_file)
unless local_file.exist?
msg = "#{local_file} for upload doesn't exist!"
raise Error, msg unless warn_on_error
# Warn and return early here since we know this upload is going to fail.
opoo msg
return
end
md5_base64 = Digest::MD5.base64digest(local_file.read)
url = "https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{remote_file}"
args = ["--upload-file", local_file, "--header", "Content-MD5: #{md5_base64}"]
args << "--fail" unless warn_on_error
result = T.unsafe(self).open_api(url, *args)
return if result.success? && result.stdout.exclude?("Error")
msg = "Bottle upload failed: #{result.stdout}"
raise msg unless warn_on_error
opoo msg
end
sig {
params(formula: Formula,
directory: String,
warn_on_error: T::Boolean).returns(String)
}
def mirror_formula(formula, directory: "mirror", warn_on_error: false)
formula.downloader.fetch
filename = ERB::Util.url_encode(formula.downloader.basename)
destination_url = "#{URL_PREFIX}/download/#{@archive_item}/#{directory}/#{filename}"
odebug "Uploading to #{destination_url}"
upload(
formula.downloader.cached_location,
directory: directory,
remote_file: filename,
warn_on_error: warn_on_error,
)
destination_url
end
# Gets the MD5 hash of the specified remote file.
#
# @return the hash, the empty string (if the file doesn't have a hash), nil (if the file doesn't exist)
sig { params(directory: String, remote_file: String).returns(T.nilable(String)) }
def remote_md5(directory:, remote_file:)
url = "https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{remote_file}"
result = curl_output "--fail", "--silent", "--head", "--location", url
if result.success?
result.stdout.match(/^ETag: "(\h{32})"/)&.values_at(1)&.first || ""
else
raise Error if result.status.exitstatus != 22 && result.stderr.exclude?("404 Not Found")
nil
end
end
sig { params(directory: String, filename: String).returns(String) }
def file_delete_instructions(directory, filename)
<<~EOS
Run:
curl -X DELETE -H "Authorization: AWS $HOMEBREW_INTERNET_ARCHIVE_KEY" https://#{@archive_item}.#{S3_DOMAIN}/#{directory}/#{filename}
Or run:
ia delete #{@archive_item} #{directory}/#{filename}
EOS
end
sig {
params(bottles_hash: T::Hash[String, T.untyped],
warn_on_error: T.nilable(T::Boolean)).void
}
def upload_bottles(bottles_hash, warn_on_error: false)
bottles_hash.each do |_formula_name, bottle_hash|
directory = bottle_hash["bintray"]["repository"]
bottle_count = bottle_hash["bottle"]["tags"].length
bottle_hash["bottle"]["tags"].each_value do |tag_hash|
filename = tag_hash["filename"] # URL encoded in Bottle::Filename#archive
delete_instructions = file_delete_instructions(directory, filename)
local_filename = tag_hash["local_filename"]
md5 = Digest::MD5.hexdigest(File.read(local_filename))
odebug "Checking remote file #{@archive_item}/#{directory}/#{filename}"
result = remote_md5(directory: directory, remote_file: filename)
case result
when nil
# File doesn't exist.
odebug "Uploading #{@archive_item}/#{directory}/#{filename}"
upload(local_filename,
directory: directory,
remote_file: filename,
warn_on_error: warn_on_error)
when md5
# File exists, hash matches.
odebug "#{filename} is already published with matching hash."
bottle_count -= 1
when ""
# File exists, but can't find hash
failed_message = "#{filename} is already published!"
raise Error, "#{failed_message}\n#{delete_instructions}" unless warn_on_error
opoo failed_message
else
# File exists, but hash either doesn't exist or is mismatched.
failed_message = <<~EOS
#{filename} is already published with a mismatched hash!
Expected: #{md5}
Actual: #{result}
EOS
raise Error, "#{failed_message}#{delete_instructions}" unless warn_on_error
opoo failed_message
end
end
odebug "Uploaded #{bottle_count} bottles"
end
end
end

View File

@ -14,6 +14,7 @@ class Bintray
include Utils::Curl include Utils::Curl
API_URL = "https://api.bintray.com" API_URL = "https://api.bintray.com"
URL_REGEX = %r{^https://[\w-]+\.bintray\.com/}.freeze
class Error < RuntimeError class Error < RuntimeError
end end

View File

@ -48,7 +48,6 @@ HOMEBREW_TEMP="${HOMEBREW_TEMP:-${HOMEBREW_DEFAULT_TEMP}}"
# Don't need shellcheck to follow these `source`. # Don't need shellcheck to follow these `source`.
# shellcheck disable=SC1090 # shellcheck disable=SC1090
case "$*" in case "$*" in
--prefix) echo "$HOMEBREW_PREFIX"; exit 0 ;;
--cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;; --cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;;
--repository|--repo) echo "$HOMEBREW_REPOSITORY"; exit 0 ;; --repository|--repo) echo "$HOMEBREW_REPOSITORY"; exit 0 ;;
--caskroom) echo "$HOMEBREW_PREFIX/Caskroom"; exit 0 ;; --caskroom) echo "$HOMEBREW_PREFIX/Caskroom"; exit 0 ;;
@ -56,6 +55,8 @@ case "$*" in
shellenv) source "$HOMEBREW_LIBRARY/Homebrew/cmd/shellenv.sh"; homebrew-shellenv; exit 0 ;; shellenv) source "$HOMEBREW_LIBRARY/Homebrew/cmd/shellenv.sh"; homebrew-shellenv; exit 0 ;;
formulae) source "$HOMEBREW_LIBRARY/Homebrew/cmd/formulae.sh"; homebrew-formulae; exit 0 ;; formulae) source "$HOMEBREW_LIBRARY/Homebrew/cmd/formulae.sh"; homebrew-formulae; exit 0 ;;
casks) source "$HOMEBREW_LIBRARY/Homebrew/cmd/casks.sh"; homebrew-casks; exit 0 ;; casks) source "$HOMEBREW_LIBRARY/Homebrew/cmd/casks.sh"; homebrew-casks; exit 0 ;;
# falls back to cmd/prefix.rb on a non-zero return
--prefix*) source "$HOMEBREW_LIBRARY/Homebrew/prefix.sh"; homebrew-prefix "$@" && exit 0 ;;
esac esac
##### #####
@ -187,9 +188,9 @@ update-preinstall() {
# last $HOMEBREW_AUTO_UPDATE_SECS. # last $HOMEBREW_AUTO_UPDATE_SECS.
if [[ "$HOMEBREW_COMMAND" = "cask" ]] if [[ "$HOMEBREW_COMMAND" = "cask" ]]
then then
tap_fetch_head="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-cask/.git/FETCH_HEAD" tap_fetch_head="$HOMEBREW_CASK_REPOSITORY/.git/FETCH_HEAD"
else else
tap_fetch_head="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core/.git/FETCH_HEAD" tap_fetch_head="$HOMEBREW_CORE_REPOSITORY/.git/FETCH_HEAD"
fi fi
if [[ -f "$tap_fetch_head" && if [[ -f "$tap_fetch_head" &&
-n "$(find "$tap_fetch_head" -type f -mtime -"${HOMEBREW_AUTO_UPDATE_SECS}"s 2>/dev/null)" ]] -n "$(find "$tap_fetch_head" -type f -mtime -"${HOMEBREW_AUTO_UPDATE_SECS}"s 2>/dev/null)" ]]
@ -313,6 +314,20 @@ then
HOMEBREW_USER_AGENT_VERSION="2.X.Y" HOMEBREW_USER_AGENT_VERSION="2.X.Y"
fi fi
HOMEBREW_CASK_REPOSITORY="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-cask"
HOMEBREW_CORE_REPOSITORY="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core"
# Don't need shellcheck to follow these `source`.
# shellcheck disable=SC1090
case "$*" in
--version|-v) source "$HOMEBREW_LIBRARY/Homebrew/cmd/--version.sh"; homebrew-version; exit 0 ;;
esac
if [[ -n "$HOMEBREW_SIMULATE_MACOS_ON_LINUX" ]]
then
export HOMEBREW_FORCE_HOMEBREW_ON_LINUX="1"
fi
if [[ -n "$HOMEBREW_MACOS" ]] if [[ -n "$HOMEBREW_MACOS" ]]
then then
HOMEBREW_PRODUCT="Homebrew" HOMEBREW_PRODUCT="Homebrew"
@ -439,13 +454,6 @@ curl_version_output="$("$HOMEBREW_CURL" --version 2>/dev/null)"
curl_name_and_version="${curl_version_output%% (*}" curl_name_and_version="${curl_version_output%% (*}"
HOMEBREW_USER_AGENT_CURL="$HOMEBREW_USER_AGENT ${curl_name_and_version// //}" HOMEBREW_USER_AGENT_CURL="$HOMEBREW_USER_AGENT ${curl_name_and_version// //}"
# Declared in bin/brew
export HOMEBREW_BREW_FILE
export HOMEBREW_PREFIX
export HOMEBREW_REPOSITORY
export HOMEBREW_LIBRARY
# Declared in brew.sh
export HOMEBREW_VERSION export HOMEBREW_VERSION
export HOMEBREW_DEFAULT_CACHE export HOMEBREW_DEFAULT_CACHE
export HOMEBREW_CACHE export HOMEBREW_CACHE
@ -567,16 +575,16 @@ then
# Don't allow non-developers to customise Ruby warnings. # Don't allow non-developers to customise Ruby warnings.
unset HOMEBREW_RUBY_WARNINGS unset HOMEBREW_RUBY_WARNINGS
# Disable Ruby options we don't need. RubyGems provides a decent speedup. # Disable Ruby options we don't need.
RUBY_DISABLE_OPTIONS="--disable=gems,did_you_mean,rubyopt" RUBY_DISABLE_OPTIONS="--disable=did_you_mean,rubyopt"
else else
# Don't disable did_you_mean for developers as it's useful. # Don't disable did_you_mean for developers as it's useful.
RUBY_DISABLE_OPTIONS="--disable=gems,rubyopt" RUBY_DISABLE_OPTIONS="--disable=rubyopt"
fi fi
if [[ -z "$HOMEBREW_RUBY_WARNINGS" ]] if [[ -z "$HOMEBREW_RUBY_WARNINGS" ]]
then then
export HOMEBREW_RUBY_WARNINGS="-W0" export HOMEBREW_RUBY_WARNINGS="-W1"
fi fi
if [[ -z "$HOMEBREW_BOTTLE_DOMAIN" ]] if [[ -z "$HOMEBREW_BOTTLE_DOMAIN" ]]

View File

@ -6,7 +6,7 @@
old_trap = trap("INT") { exit! 130 } old_trap = trap("INT") { exit! 130 }
require "global" require_relative "global"
require "build_options" require "build_options"
require "cxxstdlib" require "cxxstdlib"
require "keg" require "keg"
@ -240,7 +240,14 @@ rescue Exception => e # rubocop:disable Lint/RescueException
error_hash["env"] = e.env error_hash["env"] = e.env
when "ErrorDuringExecution" when "ErrorDuringExecution"
error_hash["cmd"] = e.cmd error_hash["cmd"] = e.cmd
error_hash["status"] = e.status.exitstatus error_hash["status"] = if e.status.is_a?(Process::Status)
{
exitstatus: e.status.exitstatus,
termsig: e.status.termsig,
}
else
e.status
end
error_hash["output"] = e.output error_hash["output"] = e.output
end end

View File

@ -13,9 +13,9 @@ module Cask
# @api private # @api private
class Installer < AbstractArtifact class Installer < AbstractArtifact
VALID_KEYS = Set.new([ VALID_KEYS = Set.new([
:manual, :manual,
:script, :script,
]).freeze ]).freeze
# Extension module for manual installers. # Extension module for manual installers.
module ManualInstaller module ManualInstaller

View File

@ -165,7 +165,7 @@ module Cask
odebug "Auditing stanzas which require an uninstall" odebug "Auditing stanzas which require an uninstall"
return if cask.artifacts.none? { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::Installer) } return if cask.artifacts.none? { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::Installer) }
return if cask.artifacts.any? { |k| k.is_a?(Artifact::Uninstall) } return if cask.artifacts.any?(Artifact::Uninstall)
add_error "installer and pkg stanzas require an uninstall stanza" add_error "installer and pkg stanzas require an uninstall stanza"
end end
@ -696,7 +696,7 @@ module Cask
def check_denylist def check_denylist
return unless cask.tap return unless cask.tap
return unless cask.tap.official? return unless cask.tap.official?
return unless reason = Denylist.reason(cask.token) return unless (reason = Denylist.reason(cask.token))
add_error "#{cask.token} is not allowed: #{reason}" add_error "#{cask.token} is not allowed: #{reason}"
end end
@ -717,7 +717,12 @@ module Cask
check_url_for_https_availability(cask.appcast, check_content: true) if cask.appcast && appcast? check_url_for_https_availability(cask.appcast, check_content: true) if cask.appcast && appcast?
check_url_for_https_availability(cask.homepage, check_content: true, user_agents: [:browser]) if cask.homepage return unless cask.homepage
check_url_for_https_availability(cask.homepage,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end end
def check_url_for_https_availability(url_to_check, **options) def check_url_for_https_availability(url_to_check, **options)

View File

@ -162,7 +162,7 @@ module Cask
end end
def eql?(other) def eql?(other)
token == other.token instance_of?(other.class) && token == other.token
end end
alias == eql? alias == eql?

View File

@ -157,7 +157,7 @@ module Cask
end end
def load(config:) def load(config:)
tap.install unless tap.installed? raise TapCaskUnavailableError.new(tap, token) unless tap.installed?
super super
end end

View File

@ -39,9 +39,9 @@ module Cask
Pathname.glob(path.join("*")).sort.select(&:directory?).map do |path| Pathname.glob(path.join("*")).sort.select(&:directory?).map do |path|
token = path.basename.to_s token = path.basename.to_s
if tap_path = CaskLoader.tap_paths(token).first if (tap_path = CaskLoader.tap_paths(token).first)
CaskLoader::FromTapPathLoader.new(tap_path).load(config: config) CaskLoader::FromTapPathLoader.new(tap_path).load(config: config)
elsif caskroom_path = Pathname.glob(path.join(".metadata/*/*/*/*.rb")).first elsif (caskroom_path = Pathname.glob(path.join(".metadata/*/*/*/*.rb")).first)
CaskLoader::FromPathLoader.new(caskroom_path).load(config: config) CaskLoader::FromPathLoader.new(caskroom_path).load(config: config)
else else
CaskLoader.load(token, config: config) CaskLoader.load(token, config: config)

View File

@ -19,9 +19,10 @@ module Cask
@quarantine = quarantine @quarantine = quarantine
end end
def fetch(verify_download_integrity: true) def fetch(quiet: nil, verify_download_integrity: true, timeout: nil)
downloaded_path = begin downloaded_path = begin
downloader.fetch downloader.shutup! if quiet
downloader.fetch(timeout: timeout)
downloader.cached_location downloader.cached_location
rescue => e rescue => e
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}") error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
@ -40,8 +41,8 @@ module Cask
end end
end end
def time_file_size def time_file_size(timeout: nil)
downloader.resolved_time_file_size downloader.resolved_time_file_size(timeout: timeout)
end end
def clear_cache def clear_cache

View File

@ -64,30 +64,30 @@ module Cask
].freeze ].freeze
DSL_METHODS = Set.new([ DSL_METHODS = Set.new([
:appcast, :appcast,
:artifacts, :artifacts,
:auto_updates, :auto_updates,
:caveats, :caveats,
:conflicts_with, :conflicts_with,
:container, :container,
:desc, :desc,
:depends_on, :depends_on,
:homepage, :homepage,
:language, :language,
:languages, :languages,
:name, :name,
:sha256, :sha256,
:staged_path, :staged_path,
:url, :url,
:version, :version,
:appdir, :appdir,
:discontinued?, :discontinued?,
:livecheck, :livecheck,
:livecheckable?, :livecheckable?,
*ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key), *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),
*ACTIVATABLE_ARTIFACT_CLASSES.map(&:dsl_key), *ACTIVATABLE_ARTIFACT_CLASSES.map(&:dsl_key),
*ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] }, *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
]).freeze ]).freeze
attr_reader :cask, :token attr_reader :cask, :token
@ -181,7 +181,11 @@ module Cask
set_unique_stanza(:url, args.empty? && options.empty? && !block_given?) do set_unique_stanza(:url, args.empty? && options.empty? && !block_given?) do
if block_given? if block_given?
LazyObject.new { URL.new(*yield, from_block: true, caller_location: caller_location) } LazyObject.new do
*args = yield
options = args.last.is_a?(Hash) ? args.pop : {}
URL.new(*args, **options, from_block: true, caller_location: caller_location)
end
else else
URL.new(*args, **options, caller_location: caller_location) URL.new(*args, **options, caller_location: caller_location)
end end

View File

@ -10,9 +10,9 @@ module Cask
# @api private # @api private
class Container class Container
VALID_KEYS = Set.new([ VALID_KEYS = Set.new([
:type, :type,
:nested, :nested,
]).freeze ]).freeze
attr_accessor(*VALID_KEYS, :pairs) attr_accessor(*VALID_KEYS, :pairs)

View File

@ -12,13 +12,13 @@ module Cask
# @api private # @api private
class DependsOn < SimpleDelegator class DependsOn < SimpleDelegator
VALID_KEYS = Set.new([ VALID_KEYS = Set.new([
:formula, :formula,
:cask, :cask,
:macos, :macos,
:arch, :arch,
:x11, :x11,
:java, :java,
]).freeze ]).freeze
VALID_ARCHES = { VALID_ARCHES = {
intel: { type: :intel, bits: 64 }, intel: { type: :intel, bits: 64 },

View File

@ -103,6 +103,27 @@ module Cask
end end
end end
# Error when a cask in a specific tap is not available.
#
# @api private
class TapCaskUnavailableError < CaskUnavailableError
extend T::Sig
attr_reader :tap
def initialize(tap, token)
super("#{tap}/#{token}")
@tap = tap
end
sig { returns(String) }
def to_s
s = super
s += "\nPlease tap it and then try again: brew tap #{tap}" unless tap.installed?
s
end
end
# Error when a cask already exists. # Error when a cask already exists.
# #
# @api private # @api private

View File

@ -62,13 +62,15 @@ module Cask
EOS EOS
end end
def fetch sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).void }
def fetch(quiet: nil, timeout: nil)
odebug "Cask::Installer#fetch" odebug "Cask::Installer#fetch"
verify_has_sha if require_sha? && !force? verify_has_sha if require_sha? && !force?
satisfy_dependencies
download download(quiet: quiet, timeout: timeout)
satisfy_dependencies
end end
def stage def stage
@ -146,7 +148,7 @@ module Cask
installed_cask = installed_caskfile.exist? ? CaskLoader.load(installed_caskfile) : @cask installed_cask = installed_caskfile.exist? ? CaskLoader.load(installed_caskfile) : @cask
# Always force uninstallation, ignore method parameter # Always force uninstallation, ignore method parameter
Installer.new(installed_cask, binaries: binaries?, verbose: verbose?, force: true, upgrade: upgrade?).uninstall Installer.new(installed_cask, verbose: verbose?, force: true, upgrade: upgrade?).uninstall
end end
sig { returns(String) } sig { returns(String) }
@ -162,9 +164,10 @@ module Cask
@downloader ||= Download.new(@cask, quarantine: quarantine?) @downloader ||= Download.new(@cask, quarantine: quarantine?)
end end
sig { returns(Pathname) } sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
def download def download(quiet: nil, timeout: nil)
@download ||= downloader.fetch(verify_download_integrity: @verify_download_integrity) @download ||= downloader.fetch(quiet: quiet, verify_download_integrity: @verify_download_integrity,
timeout: timeout)
end end
def verify_has_sha def verify_has_sha
@ -179,7 +182,7 @@ module Cask
def primary_container def primary_container
@primary_container ||= begin @primary_container ||= begin
downloaded_path = download downloaded_path = download(quiet: true)
UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true) UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
end end
end end
@ -191,7 +194,7 @@ module Cask
basename = downloader.basename basename = downloader.basename
if nested_container = @cask.container&.nested if (nested_container = @cask.container&.nested)
Dir.mktmpdir do |tmpdir| Dir.mktmpdir do |tmpdir|
tmpdir = Pathname(tmpdir) tmpdir = Pathname(tmpdir)
primary_container.extract(to: tmpdir, basename: basename, verbose: verbose?) primary_container.extract(to: tmpdir, basename: basename, verbose: verbose?)

View File

@ -32,9 +32,7 @@ module Cask
odebug "Deleting pkg files" odebug "Deleting pkg files"
@command.run!( @command.run!(
"/usr/bin/xargs", "/usr/bin/xargs",
args: [ args: ["-0", "--", "/bin/rm", "--"],
"-0", "--", "/bin/rm", "--"
],
input: pkgutil_bom_files.join("\0"), input: pkgutil_bom_files.join("\0"),
sudo: true, sudo: true,
) )
@ -44,9 +42,7 @@ module Cask
odebug "Deleting pkg symlinks and special files" odebug "Deleting pkg symlinks and special files"
@command.run!( @command.run!(
"/usr/bin/xargs", "/usr/bin/xargs",
args: [ args: ["-0", "--", "/bin/rm", "--"],
"-0", "--", "/bin/rm", "--"
],
input: pkgutil_bom_specials.join("\0"), input: pkgutil_bom_specials.join("\0"),
sudo: true, sudo: true,
) )
@ -54,19 +50,10 @@ module Cask
unless pkgutil_bom_dirs.empty? unless pkgutil_bom_dirs.empty?
odebug "Deleting pkg directories" odebug "Deleting pkg directories"
deepest_path_first(pkgutil_bom_dirs).each do |dir| rmdir(deepest_path_first(pkgutil_bom_dirs))
with_full_permissions(dir) do
clean_broken_symlinks(dir)
clean_ds_store(dir)
rmdir(dir)
end
end
end end
if root.directory? && !MacOS.undeletable?(root) rmdir(root) unless MacOS.undeletable?(root)
clean_ds_store(root)
rmdir(root)
end
forget forget
end end
@ -118,55 +105,51 @@ module Cask
path.symlink? || path.chardev? || path.blockdev? path.symlink? || path.chardev? || path.blockdev?
end end
sig { params(path: Pathname).void } # Helper script to delete empty directories after deleting `.DS_Store` files and broken symlinks.
# Needed in order to execute all file operations with `sudo`.
RMDIR_SH = <<~'BASH'
set -euo pipefail
for path in "${@}"; do
if [[ ! -e "${path}" ]]; then
continue
fi
if [[ -e "${path}/.DS_Store" ]]; then
/bin/rm -f "${path}/.DS_Store"
fi
# Some packages leave broken symlinks around; we clean them out before
# attempting to `rmdir` to prevent extra cruft from accumulating.
/usr/bin/find "${path}" -mindepth 1 -maxdepth 1 -type l ! -exec /bin/test -e {} \; -delete
if [[ -L "${path}" ]]; then
# Delete directory symlink.
/bin/rm "${path}"
elif [[ -d "${path}" ]]; then
# Delete directory if empty.
/usr/bin/find "${path}" -maxdepth 0 -type d -empty -exec /bin/rmdir {} \;
else
# Try `rmdir` anyways to show a proper error.
/bin/rmdir "${path}"
fi
done
BASH
private_constant :RMDIR_SH
sig { params(path: T.any(Pathname, T::Array[Pathname])).void }
def rmdir(path) def rmdir(path)
return unless path.children.empty? @command.run!(
"/usr/bin/xargs",
if path.symlink? args: ["-0", "--", "/bin/bash", "-c", RMDIR_SH, "--"],
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: true) input: Array(path).join("\0"),
else sudo: true,
@command.run!("/bin/rmdir", args: ["--", path], sudo: true) )
end
end
sig { params(path: Pathname, _block: T.proc.void).void }
def with_full_permissions(path, &_block)
original_mode = (path.stat.mode % 01000).to_s(8)
original_flags = @command.run!("/usr/bin/stat", args: ["-f", "%Of", "--", path]).stdout.chomp
@command.run!("/bin/chmod", args: ["--", "777", path], sudo: true)
yield
ensure
if path.exist? # block may have removed dir
@command.run!("/bin/chmod", args: ["--", original_mode, path], sudo: true)
@command.run!("/usr/bin/chflags", args: ["--", original_flags, path], sudo: true)
end
end end
sig { params(paths: T::Array[Pathname]).returns(T::Array[Pathname]) } sig { params(paths: T::Array[Pathname]).returns(T::Array[Pathname]) }
def deepest_path_first(paths) def deepest_path_first(paths)
paths.sort_by { |path| -path.to_s.split(File::SEPARATOR).count } paths.sort_by { |path| -path.to_s.split(File::SEPARATOR).count }
end end
sig { params(dir: Pathname).void }
def clean_ds_store(dir)
return unless (ds_store = dir.join(".DS_Store")).exist?
@command.run!("/bin/rm", args: ["--", ds_store], sudo: true)
end
# Some packages leave broken symlinks around; we clean them out before
# attempting to `rmdir` to prevent extra cruft from accumulating.
sig { params(dir: Pathname).void }
def clean_broken_symlinks(dir)
dir.children.select(&method(:broken_symlink?)).each do |path|
@command.run!("/bin/rm", args: ["--", path], sudo: true)
end
end
sig { params(path: Pathname).returns(T::Boolean) }
def broken_symlink?(path)
path.symlink? && !path.exist?
end
end end
end end

View File

@ -3,7 +3,6 @@
require "utils/bottles" require "utils/bottles"
require "utils/gems"
require "formula" require "formula"
require "cask/cask_loader" require "cask/cask_loader"
require "set" require "set"
@ -80,7 +79,7 @@ module Homebrew
version = Version.new(version) version = Version.new(version)
return false unless formula_name = basename.to_s[/\A(.*?)(?:--.*?)*--?(?:#{Regexp.escape(version)})/, 1] return false unless (formula_name = basename.to_s[/\A(.*?)(?:--.*?)*--?(?:#{Regexp.escape(version)})/, 1])
formula = begin formula = begin
Formulary.from_rack(HOMEBREW_CELLAR/formula_name) Formulary.from_rack(HOMEBREW_CELLAR/formula_name)
@ -95,7 +94,7 @@ module Homebrew
if resource_name == "patch" if resource_name == "patch"
patch_hashes = formula.stable&.patches&.select(&:external?)&.map(&:resource)&.map(&:version) patch_hashes = formula.stable&.patches&.select(&:external?)&.map(&:resource)&.map(&:version)
return true unless patch_hashes&.include?(Checksum.new(version.to_s)) return true unless patch_hashes&.include?(Checksum.new(version.to_s))
elsif resource_name && resource_version = formula.stable&.resources&.dig(resource_name)&.version elsif resource_name && (resource_version = formula.stable&.resources&.dig(resource_name)&.version)
return true if resource_version != version return true if resource_version != version
elsif version.is_a?(PkgVersion) elsif version.is_a?(PkgVersion)
return true if formula.pkg_version > version return true if formula.pkg_version > version
@ -111,7 +110,7 @@ module Homebrew
end end
def stale_cask?(scrub) def stale_cask?(scrub)
return false unless name = basename.to_s[/\A(.*?)--/, 1] return false unless (name = basename.to_s[/\A(.*?)--/, 1])
cask = begin cask = begin
Cask::CaskLoader.load(name) Cask::CaskLoader.load(name)

View File

@ -132,6 +132,12 @@ module Homebrew
raise unreadable_error if unreadable_error.present? raise unreadable_error if unreadable_error.present?
user, repo, short_name = name.downcase.split("/", 3)
if repo.present? && short_name.present?
tap = Tap.fetch(user, repo)
raise TapFormulaOrCaskUnavailableError.new(tap, short_name)
end
raise FormulaOrCaskUnavailableError, name raise FormulaOrCaskUnavailableError, name
end end
private :load_formula_or_cask private :load_formula_or_cask
@ -265,28 +271,24 @@ module Homebrew
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}" opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin begin
if opt_prefix.symlink? && opt_prefix.directory? return Keg.new(opt_prefix.resolved_path) if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path) return Keg.new(linked_keg_ref.resolved_path) if linked_keg_ref.symlink? && linked_keg_ref.directory?
elsif linked_keg_ref.symlink? && linked_keg_ref.directory? return Keg.new(dirs.first) if dirs.length == 1
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1 f = if name.include?("/") || File.exist?(name)
Keg.new(dirs.first) Formulary.factory(name)
else else
f = if name.include?("/") || File.exist?(name) Formulary.from_rack(rack)
Formulary.factory(name)
else
Formulary.from_rack(rack)
end
unless (prefix = f.latest_installed_prefix).directory?
raise MultipleVersionsInstalledError, <<~EOS
#{rack.basename} has multiple installed versions
Run `brew uninstall --force #{rack.basename}` to remove all versions.
EOS
end
Keg.new(prefix)
end end
unless (prefix = f.latest_installed_prefix).directory?
raise MultipleVersionsInstalledError, <<~EOS
#{rack.basename} has multiple installed versions
Run `brew uninstall --force #{rack.basename}` to remove all versions.
EOS
end
Keg.new(prefix)
rescue FormulaUnavailableError rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack} Multiple kegs installed to #{rack}

View File

@ -474,7 +474,7 @@ module Homebrew
"<#{type}>" "<#{type}>"
end.compact end.compact
types << "<subcommand>" if @named_args_type.any? { |type| type.is_a? String } types << "<subcommand>" if @named_args_type.any?(String)
types.join("|") types.join("|")
elsif SYMBOL_TO_USAGE_MAPPING.key? @named_args_type elsif SYMBOL_TO_USAGE_MAPPING.key? @named_args_type
SYMBOL_TO_USAGE_MAPPING[@named_args_type] SYMBOL_TO_USAGE_MAPPING[@named_args_type]
@ -623,7 +623,7 @@ module Homebrew
end end
def split_non_options(argv) def split_non_options(argv)
if sep = argv.index("--") if (sep = argv.index("--"))
[argv.take(sep), argv.drop(sep + 1)] [argv.take(sep), argv.drop(sep + 1)]
else else
[argv, []] [argv, []]

View File

@ -18,8 +18,7 @@ module Homebrew
- macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}` - macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}`
- Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}` - Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}`
If <formula> is provided, display the location in the Cellar where <formula> If <formula> is provided, display the location where <formula> is or would be installed.
is or would be installed.
EOS EOS
switch "--unbrewed", switch "--unbrewed",
description: "List files in Homebrew's prefix not installed by Homebrew." description: "List files in Homebrew's prefix not installed by Homebrew."
@ -45,13 +44,10 @@ module Homebrew
else else
formulae = args.named.to_resolved_formulae formulae = args.named.to_resolved_formulae
prefixes = formulae.map do |f| prefixes = formulae.map do |f|
if f.opt_prefix.exist? next nil if args.installed? && !f.opt_prefix.exist?
f.opt_prefix
elsif args.installed? # this case wil be short-circuited by brew.sh logic for a single formula
nil f.opt_prefix
else
f.latest_installed_prefix
end
end.compact end.compact
puts prefixes puts prefixes
if args.installed? if args.installed?

View File

@ -1,30 +0,0 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
module Homebrew
extend T::Sig
module_function
sig { returns(CLI::Parser) }
def __version_args
Homebrew::CLI::Parser.new do
description <<~EOS
Print the version numbers of Homebrew, Homebrew/homebrew-core and Homebrew/homebrew-cask
(if tapped) to standard output.
EOS
named_args :none
end
end
def __version
__version_args.parse
puts "Homebrew #{HOMEBREW_VERSION}"
puts "#{CoreTap.instance.full_name} #{CoreTap.instance.version_string}"
puts "#{Tap.default_cask_tap.full_name} #{Tap.default_cask_tap.version_string}" if Tap.default_cask_tap.installed?
end
end

View File

@ -0,0 +1,31 @@
#: * `--version`, `-v`
#:
#: Print the version numbers of Homebrew, Homebrew/homebrew-core and Homebrew/homebrew-cask (if tapped) to standard output.
version_string() {
local repo="$1"
if ! [ -d "$repo" ]; then
echo "N/A"
return
fi
local pretty_revision
pretty_revision="$(git -C "$repo" rev-parse --short --verify --quiet HEAD)"
if [ -z "$pretty_revision" ]; then
echo "(no Git repository)"
return
fi
local git_last_commit_date
git_last_commit_date=$(git -C "$repo" show -s --format='%cd' --date=short HEAD)
echo "(git revision ${pretty_revision}; last commit ${git_last_commit_date})"
}
homebrew-version() {
echo "Homebrew $HOMEBREW_VERSION"
echo "Homebrew/homebrew-core $(version_string "$HOMEBREW_CORE_REPOSITORY")"
if [ -d "$HOMEBREW_CASK_REPOSITORY" ]; then
echo "Homebrew/homebrew-cask $(version_string "$HOMEBREW_CASK_REPOSITORY")"
fi
}

View File

@ -55,7 +55,7 @@ module Homebrew
files["00.tap.out"] = { content: tap } files["00.tap.out"] = { content: tap }
end end
odie "`brew gist-logs` requires HOMEBREW_GITHUB_API_TOKEN to be set!" if GitHub.api_credentials_type == :none odie "`brew gist-logs` requires HOMEBREW_GITHUB_API_TOKEN to be set!" if GitHub::API.credentials_type == :none
# Description formatted to work well as page title when viewing gist # Description formatted to work well as page title when viewing gist
descr = if f.core_formula? descr = if f.core_formula?
@ -63,9 +63,9 @@ module Homebrew
else else
"#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs" "#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs"
end end
url = create_gist(files, descr, private: args.private?) url = GitHub.create_gist(files, descr, private: args.private?)
url = create_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url) if args.new_issue? url = GitHub.create_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url) if args.new_issue?
puts url if url puts url if url
end end
@ -85,9 +85,9 @@ module Homebrew
# Causes some terminals to display secure password entry indicators. # Causes some terminals to display secure password entry indicators.
def noecho_gets def noecho_gets
system "stty -echo" system "stty", "-echo"
result = $stdin.gets result = $stdin.gets
system "stty echo" system "stty", "echo"
puts puts
result result
end end
@ -108,20 +108,6 @@ module Homebrew
logs logs
end end
def create_gist(files, description, private:)
url = "https://api.github.com/gists"
data = { "public" => !private, "files" => files, "description" => description }
scopes = GitHub::CREATE_GIST_SCOPES
GitHub.open_api(url, data: data, scopes: scopes)["html_url"]
end
def create_issue(repo, title, body)
url = "https://api.github.com/repos/#{repo}/issues"
data = { "title" => title, "body" => body }
scopes = GitHub::CREATE_ISSUE_FORK_OR_PR_SCOPES
GitHub.open_api(url, data: data, scopes: scopes)["html_url"]
end
def gist_logs def gist_logs
args = gist_logs_args.parse args = gist_logs_args.parse

View File

@ -227,7 +227,7 @@ module Homebrew
def info_formula(f, args:) def info_formula(f, args:)
specs = [] specs = []
if stable = f.stable if (stable = f.stable)
s = "stable #{stable.version}" s = "stable #{stable.version}"
s += " (bottled)" if stable.bottled? && f.pour_bottle? s += " (bottled)" if stable.bottled? && f.pour_bottle?
specs << s specs << s
@ -338,6 +338,7 @@ module Homebrew
end end
def info_cask(cask, args:) def info_cask(cask, args:)
require "cask/cmd"
require "cask/cmd/info" require "cask/cmd/info"
Cask::Cmd::Info.info(cask) Cask::Cmd::Info.info(cask)

View File

@ -264,7 +264,7 @@ module Homebrew
end end
end end
opoo msg if msg opoo msg if msg
elsif !f.any_version_installed? && old_formula = f.old_installed_formulae.first elsif !f.any_version_installed? && (old_formula = f.old_installed_formulae.first)
msg = "#{old_formula.full_name} #{old_formula.any_installed_version} already installed" msg = "#{old_formula.full_name} #{old_formula.any_installed_version} already installed"
msg = if !old_formula.linked? && !old_formula.keg_only? msg = if !old_formula.linked? && !old_formula.keg_only?
<<~EOS <<~EOS

View File

@ -51,7 +51,7 @@ module Homebrew
def git_log(cd_dir, path = nil, tap = nil, args:) def git_log(cd_dir, path = nil, tap = nil, args:)
cd cd_dir cd cd_dir
repo = Utils.popen_read("git rev-parse --show-toplevel").chomp repo = Utils.popen_read("git", "rev-parse", "--show-toplevel").chomp
if tap if tap
name = tap.to_s name = tap.to_s
git_cd = "$(brew --repo #{tap})" git_cd = "$(brew --repo #{tap})"

View File

@ -73,7 +73,7 @@ module Homebrew
def search def search
args = search_args.parse args = search_args.parse
if package_manager = PACKAGE_MANAGERS.find { |name,| args[:"#{name}?"] } if (package_manager = PACKAGE_MANAGERS.find { |name,| args[:"#{name}?"] })
_, url = package_manager _, url = package_manager
exec_browser url.call(URI.encode_www_form_component(args.named.join(" "))) exec_browser url.call(URI.encode_www_form_component(args.named.join(" ")))
return return

View File

@ -54,6 +54,7 @@ module Homebrew
Uninstall.uninstall_kegs( Uninstall.uninstall_kegs(
kegs_by_rack, kegs_by_rack,
casks: casks,
force: args.force?, force: args.force?,
ignore_dependencies: args.ignore_dependencies?, ignore_dependencies: args.ignore_dependencies?,
named_args: args.named, named_args: args.named,
@ -68,9 +69,8 @@ module Homebrew
else else
T.unsafe(Cask::Cmd::Uninstall).uninstall_casks( T.unsafe(Cask::Cmd::Uninstall).uninstall_casks(
*casks, *casks,
binaries: EnvConfig.cask_opts_binaries?, verbose: args.verbose?,
verbose: args.verbose?, force: args.force?,
force: args.force?,
) )
end end
end end

View File

@ -129,15 +129,40 @@ module Homebrew
if hub.empty? if hub.empty?
puts_stdout_or_stderr "No changes to formulae." unless args.quiet? puts_stdout_or_stderr "No changes to formulae." unless args.quiet?
else else
hub.dump(updated_formula_report: !args.preinstall?) hub.dump(updated_formula_report: !args.preinstall?) unless args.quiet?
hub.reporters.each(&:migrate_tap_migration) hub.reporters.each(&:migrate_tap_migration)
hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) } hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) }
CacheStoreDatabase.use(:descriptions) do |db| CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db) DescriptionCacheStore.new(db)
.update_from_report!(hub) .update_from_report!(hub)
end end
if !args.preinstall? && !args.quiet?
outdated_formulae = Formula.installed.count(&:outdated?)
outdated_casks = Cask::Caskroom.casks.count(&:outdated?)
update_pronoun = if (outdated_formulae + outdated_casks) == 1
"it"
else
"them"
end
msg = ""
if outdated_formulae.positive?
msg += "#{Tty.bold}#{outdated_formulae}#{Tty.reset} outdated #{"formula".pluralize(outdated_formulae)}"
end
if outdated_casks.positive?
msg += " and " if msg.present?
msg += "#{Tty.bold}#{outdated_casks}#{Tty.reset} outdated #{"cask".pluralize(outdated_casks)}"
end
if msg.present?
puts_stdout_or_stderr
puts_stdout_or_stderr <<~EOS
You have #{msg} installed.
You can update #{update_pronoun} with #{Tty.bold}brew upgrade#{Tty.reset}.
EOS
end
end
end end
puts if args.preinstall? puts_stdout_or_stderr if args.preinstall?
elsif !args.preinstall? && !ENV["HOMEBREW_UPDATE_FAILED"] elsif !args.preinstall? && !ENV["HOMEBREW_UPDATE_FAILED"]
puts_stdout_or_stderr "Already up-to-date." unless args.quiet? puts_stdout_or_stderr "Already up-to-date." unless args.quiet?
end end
@ -161,6 +186,7 @@ module Homebrew
return if new_repository_version.blank? return if new_repository_version.blank?
puts_stdout_or_stderr
ohai_stdout_or_stderr "Homebrew was updated to version #{new_repository_version}" ohai_stdout_or_stderr "Homebrew was updated to version #{new_repository_version}"
if new_repository_version.split(".").last == "0" if new_repository_version.split(".").last == "0"
puts_stdout_or_stderr <<~EOS puts_stdout_or_stderr <<~EOS

View File

@ -308,6 +308,7 @@ homebrew-update() {
-\?|-h|--help|--usage) brew help update; exit $? ;; -\?|-h|--help|--usage) brew help update; exit $? ;;
--verbose) HOMEBREW_VERBOSE=1 ;; --verbose) HOMEBREW_VERBOSE=1 ;;
--debug) HOMEBREW_DEBUG=1 ;; --debug) HOMEBREW_DEBUG=1 ;;
--quiet) HOMEBREW_QUIET=1 ;;
--merge) HOMEBREW_MERGE=1 ;; --merge) HOMEBREW_MERGE=1 ;;
--force) HOMEBREW_UPDATE_FORCE=1 ;; --force) HOMEBREW_UPDATE_FORCE=1 ;;
--simulate-from-current-branch) HOMEBREW_SIMULATE_FROM_CURRENT_BRANCH=1 ;; --simulate-from-current-branch) HOMEBREW_SIMULATE_FROM_CURRENT_BRANCH=1 ;;
@ -315,6 +316,7 @@ homebrew-update() {
--*) ;; --*) ;;
-*) -*)
[[ "$option" = *v* ]] && HOMEBREW_VERBOSE=1 [[ "$option" = *v* ]] && HOMEBREW_VERBOSE=1
[[ "$option" = *q* ]] && HOMEBREW_QUIET=1
[[ "$option" = *d* ]] && HOMEBREW_DEBUG=1 [[ "$option" = *d* ]] && HOMEBREW_DEBUG=1
[[ "$option" = *f* ]] && HOMEBREW_UPDATE_FORCE=1 [[ "$option" = *f* ]] && HOMEBREW_UPDATE_FORCE=1
;; ;;
@ -661,7 +663,8 @@ EOS
then then
brew update-report "$@" brew update-report "$@"
return $? return $?
elif [[ -z "$HOMEBREW_UPDATE_PREINSTALL" ]] elif [[ -z "$HOMEBREW_UPDATE_PREINSTALL" &&
-z "$HOMEBREW_QUIET" ]]
then then
echo "Already up-to-date." echo "Already up-to-date."
fi fi

View File

@ -175,7 +175,7 @@ module Commands
path = self.path(command) path = self.path(command)
return if path.blank? return if path.blank?
if cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path) if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
cmd_parser.processed_options.map do |short, long, _, desc| cmd_parser.processed_options.map do |short, long, _, desc|
[long || short, desc] [long || short, desc]
end end
@ -198,7 +198,7 @@ module Commands
path = self.path(command) path = self.path(command)
return if path.blank? return if path.blank?
if cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path) if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
if short if short
cmd_parser.description.split(".").first cmd_parser.description.split(".").first
else else

View File

@ -129,8 +129,6 @@ module Homebrew
sig { params(command: String).returns(T::Boolean) } sig { params(command: String).returns(T::Boolean) }
def command_gets_completions?(command) def command_gets_completions?(command)
return false if command.start_with? "cask " # TODO: (2.8) remove when `brew cask` commands are removed
command_options(command).any? command_options(command).any?
end end
@ -167,7 +165,7 @@ module Homebrew
return unless command_gets_completions? command return unless command_gets_completions? command
named_completion_string = "" named_completion_string = ""
if types = Commands.named_args_type(command) if (types = Commands.named_args_type(command))
named_args_strings, named_args_types = types.partition { |type| type.is_a? String } named_args_strings, named_args_types = types.partition { |type| type.is_a? String }
named_args_types.each do |type| named_args_types.each do |type|
@ -215,29 +213,51 @@ module Homebrew
def generate_zsh_subcommand_completion(command) def generate_zsh_subcommand_completion(command)
return unless command_gets_completions? command return unless command_gets_completions? command
options = command_options(command).sort.map do |opt, desc| options = command_options(command)
next opt if desc.blank?
conflicts = generate_zsh_option_exclusions(command, opt) args_options = []
"#{conflicts}#{opt}[#{format_description desc}]" if (types = Commands.named_args_type(command))
end
if types = Commands.named_args_type(command)
named_args_strings, named_args_types = types.partition { |type| type.is_a? String } named_args_strings, named_args_types = types.partition { |type| type.is_a? String }
named_args_types.each do |type| named_args_types.each do |type|
next unless ZSH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING.key? type next unless ZSH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING.key? type
options << "::#{type}:#{ZSH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING[type]}" args_options << "- #{type}"
opt = "--#{type.to_s.gsub(/(installed|outdated)_/, "")}"
if options.key?(opt)
desc = options[opt]
if desc.blank?
args_options << opt
else
conflicts = generate_zsh_option_exclusions(command, opt)
args_options << "#{conflicts}#{opt}[#{format_description desc}]"
end
options.delete(opt)
end
args_options << "*::#{type}:#{ZSH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING[type]}"
end end
options << "::subcommand:(#{named_args_strings.join(" ")})" if named_args_strings.any? if named_args_strings.any?
args_options << "- subcommand"
args_options << "*::subcommand:(#{named_args_strings.join(" ")})"
end
end end
options = options.sort.map do |opt, desc|
next opt if desc.blank?
conflicts = generate_zsh_option_exclusions(command, opt)
"#{conflicts}#{opt}[#{format_description desc}]"
end
options += args_options
<<~COMPLETION <<~COMPLETION
# brew #{command} # brew #{command}
_brew_#{Commands.method_name command}() { _brew_#{Commands.method_name command}() {
_arguments \\ _arguments \\
#{options.map! { |opt| "'#{opt}'" }.join(" \\\n ")} #{options.map! { |opt| opt.start_with?("- ") ? opt : "'#{opt}'" }.join(" \\\n ")}
} }
COMPLETION COMPLETION
end end
@ -291,7 +311,7 @@ module Homebrew
subcommands = [] subcommands = []
named_args = [] named_args = []
if types = Commands.named_args_type(command) if (types = Commands.named_args_type(command))
named_args_strings, named_args_types = types.partition { |type| type.is_a? String } named_args_strings, named_args_types = types.partition { |type| type.is_a? String }
named_args_types.each do |type| named_args_types.each do |type|

View File

@ -67,3 +67,9 @@ HOMEBREW_TEMP = Pathname(EnvVar["HOMEBREW_TEMP"]).yield_self do |tmp|
tmp.mkpath unless tmp.exist? tmp.mkpath unless tmp.exist?
tmp.realpath tmp.realpath
end.freeze end.freeze
# The Ruby path and args to use for forked Ruby calls
HOMEBREW_RUBY_EXEC_ARGS = [
RUBY_PATH,
ENV["HOMEBREW_RUBY_WARNINGS"],
].freeze

View File

@ -16,7 +16,7 @@ module Context
end end
def self.current def self.current
if current_context = Thread.current[:context] if (current_context = Thread.current[:context])
return current_context return current_context
end end

View File

@ -52,7 +52,7 @@ class Descriptions
private private
def short_names def short_names
@short_names ||= Hash[@descriptions.keys.map { |k| [k, k.split("/").last] }] @short_names ||= @descriptions.keys.map { |k| [k, k.split("/").last] }.to_h
end end
def short_name_counts def short_name_counts

View File

@ -254,7 +254,7 @@ module Homebrew
def bottle_formula(f, args:) def bottle_formula(f, args:)
return ofail "Formula not installed or up-to-date: #{f.full_name}" unless f.latest_version_installed? return ofail "Formula not installed or up-to-date: #{f.full_name}" unless f.latest_version_installed?
unless tap = f.tap unless (tap = f.tap)
return ofail "Formula not from core or any installed taps: #{f.full_name}" unless args.force_core_tap? return ofail "Formula not from core or any installed taps: #{f.full_name}" unless args.force_core_tap?
tap = CoreTap.instance tap = CoreTap.instance

View File

@ -125,29 +125,21 @@ module Homebrew
tmp_config = cask.config tmp_config = cask.config
tmp_url = tmp_cask.url.to_s tmp_url = tmp_cask.url.to_s
if new_hash.nil? && old_hash != :no_check if old_hash != :no_check
resource_path = fetch_resource(cask, new_version, tmp_url) new_hash = fetch_resource(cask, new_version, tmp_url) if new_hash.nil?
Utils::Tar.validate_file(resource_path)
new_hash = resource_path.sha256 if tmp_contents.include?("Hardware::CPU.intel?")
other_intel = !Hardware::CPU.intel?
other_contents = tmp_contents.gsub("Hardware::CPU.intel?", other_intel.to_s)
replacement_pairs << fetch_cask(other_contents, new_version)
end
end end
cask.languages.each do |language| cask.languages.each do |language|
next if language == cask.language next if language == cask.language
lang_config = tmp_config.merge(Cask::Config.new(explicit: { languages: [language] })) lang_config = tmp_config.merge(Cask::Config.new(explicit: { languages: [language] }))
lang_cask = Cask::CaskLoader.load(tmp_contents) replacement_pairs << fetch_cask(tmp_contents, new_version, config: lang_config)
lang_cask.config = lang_config
lang_url = lang_cask.url.to_s
lang_old_hash = lang_cask.sha256.to_s
resource_path = fetch_resource(cask, new_version, lang_url)
Utils::Tar.validate_file(resource_path)
lang_new_hash = resource_path.sha256
replacement_pairs << [
lang_old_hash,
lang_new_hash,
]
end end
end end
end end
@ -186,12 +178,24 @@ module Homebrew
GitHub.create_bump_pr(pr_info, args: args) GitHub.create_bump_pr(pr_info, args: args)
end end
def fetch_resource(cask, new_version, url, **specs) def fetch_resource(cask, version, url, **specs)
resource = Resource.new resource = Resource.new
resource.url(url, specs) resource.url(url, specs)
resource.owner = Resource.new(cask.token) resource.owner = Resource.new(cask.token)
resource.version = new_version resource.version = version
resource.fetch
resource_path = resource.fetch
Utils::Tar.validate_file(resource_path)
resource_path.sha256
end
def fetch_cask(contents, version, config: nil)
cask = Cask::CaskLoader.load(contents)
cask.config = config if config.present?
url = cask.url.to_s
old_hash = cask.sha256.to_s
new_hash = fetch_resource(cask, version, url)
[old_hash, new_hash]
end end
def check_open_pull_requests(cask, args:) def check_open_pull_requests(cask, args:)

View File

@ -103,7 +103,7 @@ module Homebrew
end end
formula.tap.path.cd do formula.tap.path.cd do
unless Utils.popen_read("git remote -v").match?(%r{^homebrew.*Homebrew/homebrew-core.*$}) unless Utils.popen_read("git", "remote", "-v").match?(%r{^homebrew.*Homebrew/homebrew-core.*$})
ohai "Adding #{homebrew_core_remote} remote" ohai "Adding #{homebrew_core_remote} remote"
safe_system "git", "remote", "add", homebrew_core_remote, homebrew_core_url safe_system "git", "remote", "add", homebrew_core_remote, homebrew_core_url
end end
@ -193,7 +193,7 @@ module Homebrew
end end
check_new_version(formula, tap_full_name, url: old_url, tag: new_tag, args: args) if new_version.blank? check_new_version(formula, tap_full_name, url: old_url, tag: new_tag, args: args) if new_version.blank?
resource_path, forced_version = fetch_resource(formula, new_version, old_url, tag: new_tag) resource_path, forced_version = fetch_resource(formula, new_version, old_url, tag: new_tag)
new_revision = Utils.popen_read("git -C \"#{resource_path}\" rev-parse -q --verify HEAD") new_revision = Utils.popen_read("git", "-C", resource_path.to_s, "rev-parse", "-q", "--verify", "HEAD")
new_revision = new_revision.strip new_revision = new_revision.strip
elsif new_revision.blank? elsif new_revision.blank?
odie "#{formula}: the current URL requires specifying a `--revision=` argument." odie "#{formula}: the current URL requires specifying a `--revision=` argument."

View File

@ -16,66 +16,148 @@ module Homebrew
Display out-of-date brew formulae and the latest version available. Display out-of-date brew formulae and the latest version available.
Also displays whether a pull request has been opened with the URL. Also displays whether a pull request has been opened with the URL.
EOS EOS
switch "--full-name",
description: "Print formulae/casks with fully-qualified names."
switch "--no-pull-requests",
description: "Do not retrieve pull requests from GitHub."
switch "--formula", "--formulae",
description: "Check only formulae."
switch "--cask", "--casks",
description: "Check only casks."
flag "--limit=", flag "--limit=",
description: "Limit number of package results returned." description: "Limit number of package results returned."
named_args :formula conflicts "--cask", "--formula"
named_args [:formula, :cask]
end end
end end
def bump def bump
args = bump_args.parse args = bump_args.parse
requested_formulae = args.named.to_formulae.presence if args.limit.present? && !args.formula? && !args.cask?
requested_limit = args.limit.to_i if args.limit.present? raise UsageError, "`--limit` must be used with either `--formula` or `--cask`."
end
if requested_formulae formulae_and_casks = if args.formula?
Livecheck.load_other_tap_strategies(requested_formulae) args.named.to_formulae
elsif args.cask?
args.named.to_casks
else
args.named.to_formulae_and_casks
end
formulae_and_casks = formulae_and_casks&.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
requested_formulae.each_with_index do |formula, i| limit = args.limit.to_i if args.limit.present?
if formulae_and_casks
Livecheck.load_other_tap_strategies(formulae_and_casks)
ambiguous_casks = []
if !args.formula? && !args.cask?
ambiguous_casks = formulae_and_casks.group_by { |item| Livecheck.formula_or_cask_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
end
ambiguous_names = []
unless args.full_name?
ambiguous_names =
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.formula_or_cask_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
formulae_and_casks.each_with_index do |formula_or_cask, i|
puts if i.positive? puts if i.positive?
if formula.head_only? use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
ohai formula.name name = Livecheck.formula_or_cask_name(formula_or_cask, full_name: use_full_name)
puts "Formula is HEAD-only." repository = if formula_or_cask.is_a?(Formula)
next if formula_or_cask.head_only?
ohai name
puts "Formula is HEAD-only."
next
end
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end end
package_data = Repology.single_package_query(formula.name) package_data = Repology.single_package_query(name, repository: repository)
retrieve_and_display_info(formula, package_data&.values&.first) retrieve_and_display_info(
formula_or_cask,
name,
package_data&.values&.first,
args: args,
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
end end
else else
outdated_packages = Repology.parse_api_response(requested_limit) api_response = {}
outdated_packages.each_with_index do |(_name, repositories), i| unless args.cask?
puts if i.positive? api_response[:formulae] =
Repology.parse_api_response(limit, repository: Repology::HOMEBREW_CORE)
end
unless args.formula?
api_response[:casks] =
Repology.parse_api_response(limit, repository: Repology::HOMEBREW_CASK)
end
homebrew_repo = repositories.find do |repo| api_response.each do |package_type, outdated_packages|
repo["repo"] == "homebrew" repository = if package_type == :formulae
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end end
next if homebrew_repo.blank? outdated_packages.each_with_index do |(_name, repositories), i|
homebrew_repo = repositories.find do |repo|
repo["repo"] == repository
end
formula = begin next if homebrew_repo.blank?
Formula[homebrew_repo["srcname"]]
rescue formula_or_cask = begin
next if repository == Repology::HOMEBREW_CORE
Formula[homebrew_repo["srcname"]]
else
Cask::CaskLoader.load(homebrew_repo["srcname"])
end
rescue
next
end
name = Livecheck.formula_or_cask_name(formula_or_cask)
ambiguous_cask = begin
formula_or_cask.is_a?(Cask::Cask) && !args.cask? && Formula[name]
rescue FormulaUnavailableError
false
end
puts if i.positive?
retrieve_and_display_info(formula_or_cask, name, repositories, args: args, ambiguous_cask: ambiguous_cask)
break if limit && i >= limit
end end
retrieve_and_display_info(formula, repositories)
break if requested_limit && i >= requested_limit
end end
end end
end end
def livecheck_result(formula) def livecheck_result(formula_or_cask)
skip_result = Livecheck::SkipConditions.skip_information(formula) skip_result = Livecheck::SkipConditions.skip_information(formula_or_cask)
if skip_result.present? if skip_result.present?
return "#{skip_result[:status]}#{" - #{skip_result[:messages].join(", ")}" if skip_result[:messages].present?}" return "#{skip_result[:status]}#{" - #{skip_result[:messages].join(", ")}" if skip_result[:messages].present?}"
end end
version_info = Livecheck.latest_version( version_info = Livecheck.latest_version(
formula, formula_or_cask,
json: true, full_name: false, verbose: false, debug: false, json: true, full_name: false, verbose: false, debug: false,
) )
latest = version_info[:latest] if version_info.present? latest = version_info[:latest] if version_info.present?
@ -83,10 +165,12 @@ module Homebrew
return "unable to get versions" if latest.blank? return "unable to get versions" if latest.blank?
latest.to_s latest.to_s
rescue => e
"error: #{e}"
end end
def retrieve_pull_requests(formula) def retrieve_pull_requests(formula_or_cask, name)
pull_requests = GitHub.fetch_pull_requests(formula.name, formula.tap&.full_name, state: "open") pull_requests = GitHub.fetch_pull_requests(name, formula_or_cask.tap&.full_name, state: "open")
if pull_requests.try(:any?) if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ") pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end end
@ -96,8 +180,12 @@ module Homebrew
pull_requests pull_requests
end end
def retrieve_and_display_info(formula, repositories) def retrieve_and_display_info(formula_or_cask, name, repositories, args:, ambiguous_cask: false)
current_version = formula.stable.version.to_s current_version = if formula_or_cask.is_a?(Formula)
formula_or_cask.stable.version
else
Version.new(formula_or_cask.version)
end
repology_latest = if repositories.present? repology_latest = if repositories.present?
Repology.latest_version(repositories) Repology.latest_version(repositories)
@ -105,14 +193,15 @@ module Homebrew
"not found" "not found"
end end
livecheck_latest = livecheck_result(formula) livecheck_latest = livecheck_result(formula_or_cask)
pull_requests = retrieve_pull_requests(formula) pull_requests = retrieve_pull_requests(formula_or_cask, name) unless args.no_pull_requests?
name += " (cask)" if ambiguous_cask
title = if current_version == repology_latest && title = if current_version == repology_latest &&
current_version == livecheck_latest current_version == livecheck_latest
"#{formula} is up to date!" "#{name} is up to date!"
else else
formula.name name
end end
ohai title ohai title
@ -120,7 +209,7 @@ module Homebrew
Current formula version: #{current_version} Current formula version: #{current_version}
Latest Repology version: #{repology_latest} Latest Repology version: #{repology_latest}
Latest livecheck version: #{livecheck_latest} Latest livecheck version: #{livecheck_latest}
Open pull requests: #{pull_requests}
EOS EOS
puts "Open pull requests: #{pull_requests}" unless args.no_pull_requests?
end end
end end

View File

@ -179,7 +179,7 @@ module Homebrew
# Check for disallowed formula, or names that shadow aliases, # Check for disallowed formula, or names that shadow aliases,
# unless --force is specified. # unless --force is specified.
unless args.force? unless args.force?
if reason = MissingFormula.disallowed_reason(fc.name) if (reason = MissingFormula.disallowed_reason(fc.name))
odie <<~EOS odie <<~EOS
The formula '#{fc.name}' is not allowed to be created. The formula '#{fc.name}' is not allowed to be created.
#{reason} #{reason}

View File

@ -25,7 +25,10 @@ module Homebrew
description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)." description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
switch "--upload", switch "--upload",
description: "Upload built bottles to Bintray." description: "Upload built bottles to Bintray."
switch "--linux",
description: "Dispatch bottle for Linux (using GitHub runners)."
conflicts "--macos", "--linux"
named_args :formula, min: 1 named_args :formula, min: 1
end end
end end
@ -33,45 +36,55 @@ module Homebrew
def dispatch_build_bottle def dispatch_build_bottle
args = dispatch_build_bottle_args.parse args = dispatch_build_bottle_args.parse
# Fixup version for ARM/Apple Silicon
# TODO: fix label name to be 11-arm64 instead and remove this.
args.macos&.gsub!(/^11-arm$/, "11-arm64")
macos = args.macos&.yield_self do |s|
MacOS::Version.from_symbol(s.to_sym)
rescue MacOSVersionError
MacOS::Version.new(s)
end
raise UsageError, "Must specify --macos option" if macos.blank?
# Fixup label for ARM/Apple Silicon
macos_label = if macos.arch == :arm64
# TODO: fix label name to be 11-arm64 instead.
"#{macos}-arm"
else
macos.to_s
end
tap = Tap.fetch(args.tap || CoreTap.instance.name) tap = Tap.fetch(args.tap || CoreTap.instance.name)
user, repo = tap.full_name.split("/") user, repo = tap.full_name.split("/")
workflow = args.workflow || "dispatch-build-bottle.yml"
ref = "master" ref = "master"
workflow = args.workflow || "dispatch-build-bottle.yml"
# Ensure we dispatch the bottle in homebrew/homebrew-core
# TODO: remove when core taps are merged
repo.gsub!("linux", "home") unless args.tap
if (macos = args.macos)
# Fixup version for ARM/Apple Silicon
# TODO: fix label name to be 11-arm64 instead and remove this.
macos.gsub!(/^11-arm$/, "11-arm64")
macos = macos.yield_self do |s|
MacOS::Version.from_symbol(s.to_sym)
rescue MacOSVersionError
MacOS::Version.new(s)
end
# Fixup label for ARM/Apple Silicon
macos_label = if macos.arch == :arm64
# TODO: fix label name to be 11-arm64 instead.
"#{macos}-arm"
else
macos.to_s
end
dispatching_for = "macOS #{macos}"
elsif T.unsafe(args).linux?
workflow = args.workflow || "linux-#{workflow}"
dispatching_for = "Linux"
else
raise UsageError, "Must specify --macos or --linux option"
end
args.named.to_resolved_formulae.each do |formula| args.named.to_resolved_formulae.each do |formula|
# Required inputs # Required inputs
inputs = { inputs = {
formula: formula.name, formula: formula.name,
macos: macos_label,
} }
# Optional inputs # Optional inputs
# These cannot be passed as nil to GitHub API # These cannot be passed as nil to GitHub API
inputs[:macos] = macos_label if args.macos
inputs[:issue] = args.issue if args.issue inputs[:issue] = args.issue if args.issue
inputs[:upload] = args.upload?.to_s if args.upload? inputs[:upload] = args.upload?.to_s if args.upload?
ohai "Dispatching #{tap} bottling request of formula \"#{formula.name}\" for macOS #{macos}" ohai "Dispatching #{tap} bottling request of formula \"#{formula.name}\" for #{dispatching_for}"
GitHub.workflow_dispatch_event(user, repo, workflow, ref, inputs) GitHub.workflow_dispatch_event(user, repo, workflow, ref, inputs)
end end
end end

View File

@ -61,54 +61,55 @@ module Homebrew
puts ENV["HOMEBREW_LIVECHECK_WATCHLIST"] if ENV["HOMEBREW_LIVECHECK_WATCHLIST"].present? puts ENV["HOMEBREW_LIVECHECK_WATCHLIST"] if ENV["HOMEBREW_LIVECHECK_WATCHLIST"].present?
end end
formulae_and_casks_to_check = formulae_and_casks_to_check = if args.tap
if args.tap tap = Tap.fetch(args.tap)
tap = Tap.fetch(args.tap) formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) } casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) }
casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) } formulae + casks
formulae + casks elsif args.installed?
elsif args.installed? formulae = args.cask? ? [] : Formula.installed
formulae = args.cask? ? [] : Formula.installed casks = args.formula? ? [] : Cask::Caskroom.casks
casks = args.formula? ? [] : Cask::Caskroom.casks formulae + casks
formulae + casks elsif args.all?
elsif args.all? formulae = args.cask? ? [] : Formula.to_a
formulae = args.cask? ? [] : Formula.to_a casks = args.formula? ? [] : Cask::Cask.to_a
casks = args.formula? ? [] : Cask::Cask.to_a formulae + casks
formulae + casks elsif args.named.present?
elsif args.named.present? if args.formula?
if args.formula? args.named.to_formulae
args.named.to_formulae elsif args.cask?
elsif args.cask? args.named.to_casks
args.named.to_casks
else
args.named.to_formulae_and_casks
end
elsif File.exist?(WATCHLIST_PATH)
begin
names = Pathname.new(WATCHLIST_PATH).read.lines
.reject { |line| line.start_with?("#") || line.blank? }
.map(&:strip)
named_args = T.unsafe(CLI::NamedArgs).new(*names, parent: args)
named_args.to_formulae_and_casks(ignore_unavailable: true)
rescue Errno::ENOENT => e
onoe e
end
else else
raise UsageError, "A watchlist file is required when no arguments are given." args.named.to_formulae_and_casks
end&.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end end
elsif File.exist?(WATCHLIST_PATH)
begin
names = Pathname.new(WATCHLIST_PATH).read.lines
.reject { |line| line.start_with?("#") || line.blank? }
.map(&:strip)
named_args = T.unsafe(CLI::NamedArgs).new(*names, parent: args)
named_args.to_formulae_and_casks(ignore_unavailable: true)
rescue Errno::ENOENT => e
onoe e
end
else
raise UsageError, "A watchlist file is required when no arguments are given."
end
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
raise UsageError, "No formulae or casks to check." if formulae_and_casks_to_check.blank? raise UsageError, "No formulae or casks to check." if formulae_and_casks_to_check.blank?
options = { options = {
json: args.json?, json: args.json?,
full_name: args.full_name?, full_name: args.full_name?,
newer_only: args.newer_only?, handle_name_conflict: !args.formula? && !args.cask?,
quiet: args.quiet?, newer_only: args.newer_only?,
debug: args.debug?, quiet: args.quiet?,
verbose: args.verbose?, debug: args.debug?,
verbose: args.verbose?,
}.compact }.compact
Livecheck.run_checks(formulae_and_casks_to_check, **options) Livecheck.run_checks(formulae_and_casks_to_check, **options)

View File

@ -24,9 +24,9 @@ module Homebrew
*Note:* Not (yet) working on Apple Silicon. *Note:* Not (yet) working on Apple Silicon.
EOS EOS
switch "--fail-if-changed", switch "--fail-if-not-changed",
description: "Return a failing status code if changes are detected in the manpage outputs. This "\ description: "Return a failing status code if no changes are detected in the manpage outputs. "\
"can be used to notify CI when the manpages are out of date. Additionally, "\ "This can be used to notify CI when the manpages are out of date. Additionally, "\
"the date used in new manpages will match those in the existing manpages (to allow "\ "the date used in new manpages will match those in the existing manpages (to allow "\
"comparison without factoring in the date)." "comparison without factoring in the date)."
named_args :none named_args :none
@ -42,19 +42,17 @@ module Homebrew
args = man_args.parse args = man_args.parse
Commands.rebuild_internal_commands_completion_list Commands.rebuild_internal_commands_completion_list
regenerate_man_pages(preserve_date: args.fail_if_changed?, quiet: args.quiet?) regenerate_man_pages(preserve_date: args.fail_if_not_changed?, quiet: args.quiet?)
Completions.update_shell_completions! Completions.update_shell_completions!
diff = system_command "git", args: [ diff = system_command "git", args: [
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions" "-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
] ]
if diff.status.success?
puts "No changes to manpage or completions output detected." return unless diff.status.success?
elsif args.fail_if_changed?
puts "Changes to manpage or completions detected:" puts "No changes to manpage or completions output detected."
puts diff.stdout Homebrew.failed = true if args.fail_if_not_changed?
Homebrew.failed = true
end
end end
def regenerate_man_pages(preserve_date:, quiet:) def regenerate_man_pages(preserve_date:, quiet:)
@ -62,6 +60,7 @@ module Homebrew
markup = build_man_page(quiet: quiet) markup = build_man_page(quiet: quiet)
convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md", preserve_date: preserve_date) convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md", preserve_date: preserve_date)
markup = I18n.transliterate(markup, locale: :en)
convert_man_page(markup, TARGET_MAN_PATH/"brew.1", preserve_date: preserve_date) convert_man_page(markup, TARGET_MAN_PATH/"brew.1", preserve_date: preserve_date)
end end
@ -164,7 +163,7 @@ module Homebrew
# preserve existing manpage order # preserve existing manpage order
cmd_paths.sort_by(&method(:sort_key_for_path)) cmd_paths.sort_by(&method(:sort_key_for_path))
.each do |cmd_path| .each do |cmd_path|
cmd_man_page_lines = if cmd_parser = CLI::Parser.from_cmd_path(cmd_path) cmd_man_page_lines = if (cmd_parser = CLI::Parser.from_cmd_path(cmd_path))
next if cmd_parser.hide_from_man_page next if cmd_parser.hide_from_man_page
cmd_parser_manpage_lines(cmd_parser).join cmd_parser_manpage_lines(cmd_parser).join

View File

@ -49,6 +49,8 @@ module Homebrew
description: "Message to include when autosquashing revision bumps, deletions, and rebuilds." description: "Message to include when autosquashing revision bumps, deletions, and rebuilds."
flag "--artifact=", flag "--artifact=",
description: "Download artifacts with the specified name (default: `bottles`)." description: "Download artifacts with the specified name (default: `bottles`)."
flag "--archive-item=",
description: "Upload to the specified Internet Archive item (default: `homebrew`)."
flag "--bintray-org=", flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)." description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--tap=", flag "--tap=",
@ -65,6 +67,7 @@ module Homebrew
description: "Comma-separated list of workflows which can be ignored if they have not been run." description: "Comma-separated list of workflows which can be ignored if they have not been run."
conflicts "--clean", "--autosquash" conflicts "--clean", "--autosquash"
conflicts "--archive-item", "--bintray-org"
named_args :pull_request, min: 1 named_args :pull_request, min: 1
end end
@ -337,9 +340,9 @@ module Homebrew
end end
def download_artifact(url, dir, pr) def download_artifact(url, dir, pr)
odie "Credentials must be set to access the Artifacts API" if GitHub.api_credentials_type == :none odie "Credentials must be set to access the Artifacts API" if GitHub::API.credentials_type == :none
token = GitHub.api_credentials token = GitHub::API.credentials
curl_args = ["--header", "Authorization: token #{token}"] curl_args = ["--header", "Authorization: token #{token}"]
# Download the artifact as a zip file and unpack it into `dir`. This is # Download the artifact as a zip file and unpack it into `dir`. This is
@ -357,6 +360,7 @@ module Homebrew
workflows = args.workflows.presence || ["tests.yml"] workflows = args.workflows.presence || ["tests.yml"]
artifact = args.artifact || "bottles" artifact = args.artifact || "bottles"
archive_item = args.archive_item
bintray_org = args.bintray_org || "homebrew" bintray_org = args.bintray_org || "homebrew"
mirror_repo = args.bintray_mirror || "mirror" mirror_repo = args.bintray_mirror || "mirror"
tap = Tap.fetch(args.tap || CoreTap.instance.name) tap = Tap.fetch(args.tap || CoreTap.instance.name)
@ -424,7 +428,11 @@ module Homebrew
upload_args << "--keep-old" if args.keep_old? upload_args << "--keep-old" if args.keep_old?
upload_args << "--warn-on-upload-failure" if args.warn_on_upload_failure? upload_args << "--warn-on-upload-failure" if args.warn_on_upload_failure?
upload_args << "--root-url=#{args.root_url}" if args.root_url upload_args << "--root-url=#{args.root_url}" if args.root_url
upload_args << "--bintray-org=#{bintray_org}" upload_args << if archive_item.present?
"--archive-item=#{archive_item}"
else
"--bintray-org=#{bintray_org}"
end
safe_system HOMEBREW_BREW_FILE, *upload_args safe_system HOMEBREW_BREW_FILE, *upload_args
end end
end end

View File

@ -2,7 +2,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "cli/parser"
require "archive"
require "bintray" require "bintray"
require "github_packages"
require "github_releases"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -13,7 +16,7 @@ module Homebrew
def pr_upload_args def pr_upload_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Apply the bottle commit and publish bottles to Bintray or GitHub Releases. Apply the bottle commit and publish bottles to a host.
EOS EOS
switch "--no-publish", switch "--no-publish",
description: "Apply the bottle commit and upload the bottles, but don't publish them." description: "Apply the bottle commit and upload the bottles, but don't publish them."
@ -27,8 +30,12 @@ module Homebrew
switch "--warn-on-upload-failure", switch "--warn-on-upload-failure",
description: "Warn instead of raising an error if the bottle upload fails. "\ description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed." "Useful for repairing bottle uploads that previously failed."
flag "--archive-item=",
description: "Upload to the specified Internet Archive item (default: `homebrew`)."
flag "--bintray-org=", flag "--bintray-org=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)." description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--github-org=",
description: "Upload to the specified GitHub organisation's GitHub Packages (default: `homebrew`)."
flag "--root-url=", flag "--root-url=",
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default." description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
@ -47,16 +54,34 @@ module Homebrew
end end
end end
def internet_archive?(bottles_hash)
@internet_archive ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].start_with? "#{Archive::URL_PREFIX}/"
end
end
def bintray?(bottles_hash)
@bintray ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].match? Bintray::URL_REGEX
end
end
def github_releases?(bottles_hash) def github_releases?(bottles_hash)
@github_releases ||= bottles_hash.values.all? do |bottle_hash| @github_releases ||= bottles_hash.values.all? do |bottle_hash|
root_url = bottle_hash["bottle"]["root_url"] root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX url_match = root_url.match GitHubReleases::URL_REGEX
_, _, _, tag = *url_match _, _, _, tag = *url_match
tag tag
end end
end end
def github_packages?(bottles_hash)
@github_packages ||= bottles_hash.values.all? do |bottle_hash|
bottle_hash["bottle"]["root_url"].match? GitHubPackages::URL_REGEX
end
end
def pr_upload def pr_upload
args = pr_upload_args.parse args = pr_upload_args.parse
@ -76,11 +101,18 @@ module Homebrew
bottle_args += json_files bottle_args += json_files
if args.dry_run? if args.dry_run?
service = if github_releases?(bottles_hash) service =
"GitHub Releases" if internet_archive?(bottles_hash)
else "Internet Archive"
"Bintray" elsif bintray?(bottles_hash)
end "Bintray"
elsif github_releases?(bottles_hash)
"GitHub Releases"
elsif github_packages?(bottles_hash)
"GitHub Packages"
else
odie "Service specified by root_url is not recognized"
end
puts <<~EOS puts <<~EOS
brew #{bottle_args.join " "} brew #{bottle_args.join " "}
Upload bottles described by these JSON files to #{service}: Upload bottles described by these JSON files to #{service}:
@ -102,38 +134,26 @@ module Homebrew
safe_system HOMEBREW_BREW_FILE, *audit_args safe_system HOMEBREW_BREW_FILE, *audit_args
end end
if github_releases?(bottles_hash) if internet_archive?(bottles_hash)
# Handle uploading to GitHub Releases. archive_item = args.archive_item || "homebrew"
bottles_hash.each_value do |bottle_hash| archive = Archive.new(item: archive_item)
root_url = bottle_hash["bottle"]["root_url"] archive.upload_bottles(bottles_hash,
url_match = root_url.match HOMEBREW_RELEASES_URL_REGEX warn_on_error: args.warn_on_upload_failure?)
_, user, repo, tag = *url_match elsif bintray?(bottles_hash)
# Ensure a release is created.
release = begin
rel = GitHub.get_release user, repo, tag
odebug "Existing GitHub release \"#{tag}\" found"
rel
rescue GitHub::HTTPNotFoundError
odebug "Creating new GitHub release \"#{tag}\""
GitHub.create_or_update_release user, repo, tag
end
# Upload bottles as release assets.
bottle_hash["bottle"]["tags"].each_value do |tag_hash|
remote_file = tag_hash["filename"]
local_file = tag_hash["local_filename"]
odebug "Uploading #{remote_file}"
GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file
end
end
else
# Handle uploading to Bintray.
bintray_org = args.bintray_org || "homebrew" bintray_org = args.bintray_org || "homebrew"
bintray = Bintray.new(org: bintray_org) bintray = Bintray.new(org: bintray_org)
bintray.upload_bottles(bottles_hash, bintray.upload_bottles(bottles_hash,
publish_package: !args.no_publish?, publish_package: !args.no_publish?,
warn_on_error: args.warn_on_upload_failure?) warn_on_error: args.warn_on_upload_failure?)
elsif github_releases?(bottles_hash)
github_releases = GitHubReleases.new
github_releases.upload_bottles(bottles_hash)
elsif github_packages?(bottles_hash)
github_org = args.github_org || "homebrew"
github_packages = GitHubPackages.new(org: github_org)
github_packages.upload_bottles(bottles_hash)
else
odie "Service specified by root_url is not recognized"
end end
end end
end end

View File

@ -13,15 +13,19 @@ module Homebrew
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Run Homebrew with a Ruby profiler. For example, `brew prof readall`. Run Homebrew with a Ruby profiler. For example, `brew prof readall`.
*Note:* Not (yet) working on Apple Silicon.
EOS EOS
switch "--stackprof", switch "--stackprof",
description: "Use `stackprof` instead of `ruby-prof` (the default)." description: "Use `stackprof` instead of `ruby-prof` (the default)."
named_args :command named_args :command, min: 1
end end
end end
def prof def prof
raise UsageError, "not (yet) working on Apple Silicon!" if Hardware::CPU.arm?
args = prof_args.parse args = prof_args.parse
brew_rb = (HOMEBREW_LIBRARY_PATH/"brew.rb").resolved_path brew_rb = (HOMEBREW_LIBRARY_PATH/"brew.rb").resolved_path

View File

@ -39,7 +39,7 @@ module Homebrew
begin begin
latest_release = GitHub.get_latest_release "Homebrew", "brew" latest_release = GitHub.get_latest_release "Homebrew", "brew"
rescue GitHub::HTTPNotFoundError rescue GitHub::API::HTTPNotFoundError
odie "No existing releases found!" odie "No existing releases found!"
end end
latest_version = Version.new latest_release["tag_name"] latest_version = Version.new latest_release["tag_name"]
@ -48,7 +48,7 @@ module Homebrew
one_month_ago = Date.today << 1 one_month_ago = Date.today << 1
latest_major_minor_release = begin latest_major_minor_release = begin
GitHub.get_release "Homebrew", "brew", "#{latest_version.major_minor}.0" GitHub.get_release "Homebrew", "brew", "#{latest_version.major_minor}.0"
rescue GitHub::HTTPNotFoundError rescue GitHub::API::HTTPNotFoundError
nil nil
end end
@ -89,7 +89,7 @@ module Homebrew
begin begin
release = GitHub.create_or_update_release "Homebrew", "brew", new_version, body: release_notes, draft: true release = GitHub.create_or_update_release "Homebrew", "brew", new_version, body: release_notes, draft: true
rescue *GitHub::API_ERRORS => e rescue *GitHub::API::ERRORS => e
odie "Unable to create release: #{e.message}!" odie "Unable to create release: #{e.message}!"
end end

View File

@ -18,8 +18,6 @@ homebrew-rubocop() {
export GEM_HOME export GEM_HOME
export PATH="$GEM_HOME/bin:$PATH" export PATH="$GEM_HOME/bin:$PATH"
# Unconditional -W0 to avoid printing e.g.: RUBOCOP="$HOMEBREW_LIBRARY/Homebrew/utils/rubocop.rb"
# warning: parser/current is loading parser/ruby26, which recognizes exec "$HOMEBREW_RUBY_PATH" "$RUBOCOP" "$@"
# warning: 2.6.6-compliant syntax, but you are running 2.6.3.
exec "$HOMEBREW_RUBY_PATH" "$RUBY_DISABLE_OPTIONS" -W0 -S rubocop "$@"
} }

View File

@ -9,55 +9,68 @@ module Homebrew
module_function module_function
NAMED_TIER_AMOUNT = 100
URL_TIER_AMOUNT = 1000
sig { returns(CLI::Parser) } sig { returns(CLI::Parser) }
def sponsors_args def sponsors_args
Homebrew::CLI::Parser.new do Homebrew::CLI::Parser.new do
description <<~EOS description <<~EOS
Print a Markdown summary of Homebrew's GitHub Sponsors, suitable for pasting into a README. Update the list of GitHub Sponsors in the `Homebrew/brew` README.
EOS EOS
named_args :none named_args :none
end end
end end
def sponsor_name(s)
s["name"] || s["login"]
end
def sponsor_logo(s)
"https://github.com/#{s["login"]}.png?size=64"
end
def sponsor_url(s)
"https://github.com/#{s["login"]}"
end
def sponsors def sponsors
sponsors_args.parse sponsors_args.parse
sponsors = { named_sponsors = []
"named" => [], logo_sponsors = []
"users" => 0,
"orgs" => 0,
}
GitHub.sponsors_by_tier("Homebrew").each do |tier| GitHub.sponsors_by_tier("Homebrew").each do |tier|
sponsors["named"] += tier["sponsors"] if tier["tier"] >= 100 if tier["tier"] >= NAMED_TIER_AMOUNT
sponsors["users"] += tier["count"] named_sponsors += tier["sponsors"].map do |s|
sponsors["orgs"] += tier["sponsors"].count { |s| s["type"] == "organization" } "[#{sponsor_name(s)}](#{sponsor_url(s)})"
end
end
next if tier["tier"] < URL_TIER_AMOUNT
logo_sponsors += tier["sponsors"].map do |s|
"[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
end
end end
items = [] named_sponsors << "many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew)"
items += sponsors["named"].map { |s| "[#{s["name"]}](https://github.com/#{s["login"]})" }
anon_users = sponsors["users"] - sponsors["named"].length - sponsors["orgs"] readme = HOMEBREW_REPOSITORY/"README.md"
content = readme.read
content.gsub!(/(Homebrew is generously supported by) .*\Z/m, "\\1 #{named_sponsors.to_sentence}.\n")
content << "\n#{logo_sponsors.join}\n" if logo_sponsors.presence
items << if items.length > 1 File.open(readme, "w+") { |f| f.write(content) }
"#{anon_users} other users"
diff = system_command "git", args: [
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "README.md"
]
if diff.status.success?
puts "No changes to list of sponsors."
else else
"#{anon_users} users" puts "List of sponsors updated in the README."
end end
if sponsors["orgs"] == 1
items << "#{sponsors["orgs"]} organization"
elsif sponsors["orgs"] > 1
items << "#{sponsors["orgs"]} organizations"
end
sponsor_text = if items.length > 2
items[0..-2].join(", ") + " and #{items.last}"
else
items.join(" and ")
end
puts "Homebrew is generously supported by #{sponsor_text} via [GitHub Sponsors](https://github.com/sponsors/Homebrew)."
end end
end end

View File

@ -22,9 +22,8 @@ module Homebrew
flag "--pull-label=", flag "--pull-label=",
description: "Label name for pull requests ready to be pulled (default: `pr-pull`)." description: "Label name for pull requests ready to be pulled (default: `pr-pull`)."
flag "--branch=", flag "--branch=",
description: "Initialize Git repository with the specified branch name (default: `main`)." description: "Initialize Git repository and setup GitHub Actions workflows with the " \
"specified branch name (default: `main`)."
conflicts "--no-git", "--branch"
named_args :tap, number: 1 named_args :tap, number: 1
end end
@ -50,11 +49,13 @@ module Homebrew
# #{titleized_user} #{titleized_repo} # #{titleized_user} #{titleized_repo}
## How do I install these formulae? ## How do I install these formulae?
`brew install #{tap}/<formula>` `brew install #{tap}/<formula>`
Or `brew tap #{tap}` and then `brew install <formula>`. Or `brew tap #{tap}` and then `brew install <formula>`.
## Documentation ## Documentation
`brew help`, `man brew` or check [Homebrew's documentation](https://docs.brew.sh). `brew help`, `man brew` or check [Homebrew's documentation](https://docs.brew.sh).
MARKDOWN MARKDOWN
write_path(tap, "README.md", readme) write_path(tap, "README.md", readme)
@ -63,13 +64,14 @@ module Homebrew
name: brew test-bot name: brew test-bot
on: on:
push: push:
branches: #{branch} branches:
- #{branch}
pull_request: pull_request:
jobs: jobs:
test-bot: test-bot:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macOS-latest] os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Set up Homebrew - name: Set up Homebrew

View File

@ -35,6 +35,8 @@ module Homebrew
def test def test
args = test_args.parse args = test_args.parse
Homebrew.install_bundler_gems!(setup_path: false)
require "formula_assertions" require "formula_assertions"
require "formula_free_port" require "formula_free_port"
@ -75,10 +77,7 @@ module Homebrew
env = ENV.to_hash env = ENV.to_hash
begin begin
exec_args = %W[ exec_args = HOMEBREW_RUBY_EXEC_ARGS + %W[
#{RUBY_PATH}
#{ENV["HOMEBREW_RUBY_WARNINGS"]}
-I #{$LOAD_PATH.join(File::PATH_SEPARATOR)}
-- --
#{HOMEBREW_LIBRARY_PATH}/test.rb #{HOMEBREW_LIBRARY_PATH}/test.rb
#{f.path} #{f.path}
@ -106,7 +105,7 @@ module Homebrew
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception => e # rubocop:disable Lint/RescueException
retry if retry_test?(f, args: args) retry if retry_test?(f, args: args)
ofail "#{f.full_name}: failed" ofail "#{f.full_name}: failed"
puts e, e.backtrace $stderr.puts e, e.backtrace
ensure ensure
ENV.replace(env) ENV.replace(env)
end end

View File

@ -78,7 +78,7 @@ module Homebrew
elsif args.dependents? elsif args.dependents?
formulae = all_formulae = Formula.to_a formulae = all_formulae = Formula.to_a
@sort = " (sorted by installs in the last 90 days)" @sort = " (sorted by number of dependents)"
else else
formula_installs = {} formula_installs = {}
@ -103,7 +103,7 @@ module Homebrew
nil nil
end end
end.compact end.compact
@sort = " (sorted by installs in the last 90 days)" @sort = " (sorted by installs in the last 90 days; top 10,000 only)"
all_formulae = Formula all_formulae = Formula
end end
@ -154,20 +154,51 @@ module Homebrew
formulae.each do |f| formulae.each do |f|
name = f.name.downcase name = f.name.downcase
if f.bottle_specification.tag?(@bottle_tag) if f.bottle_specification.tag?(@bottle_tag, exact: true)
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}: already bottled" if any_named_args puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}: already bottled" if any_named_args
next next
end end
requirement_classes = f.recursive_requirements.map(&:class) if f.disabled?
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}: formula disabled" if any_named_args
next
end
requirements = f.recursive_requirements
if @bottle_tag.to_s.end_with?("_linux") if @bottle_tag.to_s.end_with?("_linux")
if requirement_classes.include?(MacOSRequirement) if requirements.any?(MacOSRequirement)
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires macOS" if any_named_args puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires macOS" if any_named_args
next next
end end
elsif requirement_classes.include?(LinuxRequirement) elsif requirements.any?(LinuxRequirement)
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires Linux" if any_named_args puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires Linux" if any_named_args
next next
else
macos_version = MacOS::Version.from_symbol(@bottle_tag)
macos_satisfied = requirements.all? do |r|
case r
when MacOSRequirement
next true unless r.version_specified?
macos_version.public_send(r.comparator, r.version)
when XcodeRequirement
next true unless r.version
Version.new(MacOS::Xcode.latest_version(macos: macos_version)) >= r.version
when ArchRequirement
arch = r.arch
arch = :intel if arch == :x86_64
arch = :arm64 if arch == :arm
arch == macos_version.arch
else
true
end
end
unless macos_satisfied
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: doesn't support this macOS" if any_named_args
next
end
end end
if f.bottle_unneeded? || f.bottle_disabled? if f.bottle_unneeded? || f.bottle_disabled?
@ -181,7 +212,7 @@ module Homebrew
end end
deps = Array(deps_hash[f.name]).reject do |dep| deps = Array(deps_hash[f.name]).reject do |dep|
dep.bottle_specification.tag?(@bottle_tag) || dep.bottle_unneeded? dep.bottle_specification.tag?(@bottle_tag, exact: true) || dep.bottle_unneeded?
end end
if deps.blank? if deps.blank?

View File

@ -38,7 +38,7 @@ module Homebrew
formulae = args.named.to_formulae formulae = args.named.to_formulae
if dir = args.destdir if (dir = args.destdir)
unpack_dir = Pathname.new(dir).expand_path unpack_dir = Pathname.new(dir).expand_path
unpack_dir.mkpath unpack_dir.mkpath
else else

View File

@ -3,6 +3,7 @@
require "cli/parser" require "cli/parser"
require "utils/github" require "utils/github"
require "dev-cmd/man"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -61,7 +62,8 @@ module Homebrew
if diff.status.success? if diff.status.success?
puts "No changes to list of maintainers." puts "No changes to list of maintainers."
else else
puts "List of maintainers updated in README." Homebrew.regenerate_man_pages(preserve_date: true, quiet: true)
puts "List of maintainers updated in the README and the generated man pages."
end end
end end
end end

View File

@ -54,9 +54,9 @@ module Homebrew
start_commit, end_commit = nil start_commit, end_commit = nil
cd HOMEBREW_REPOSITORY do cd HOMEBREW_REPOSITORY do
start_commit = if commit = args.commit start_commit = if (commit = args.commit)
commit commit
elsif date = args.before elsif (date = args.before)
Utils.popen_read("git", "rev-list", "-n1", "--before=#{date}", "origin/master").chomp Utils.popen_read("git", "rev-list", "-n1", "--before=#{date}", "origin/master").chomp
elsif args.to_tag? elsif args.to_tag?
tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname") tags = Utils.popen_read("git", "tag", "--list", "--sort=-version:refname")

View File

@ -43,7 +43,7 @@ class DevelopmentTools
def clang_version def clang_version
@clang_version ||= begin @clang_version ||= begin
if (path = locate("clang")) && if (path = locate("clang")) &&
build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d)/, 1] (build_version = `#{path} --version`[/(?:clang|LLVM) version (\d+\.\d)/, 1])
Version.new build_version Version.new build_version
else else
Version::NULL Version::NULL
@ -54,7 +54,7 @@ class DevelopmentTools
def clang_build_version def clang_build_version
@clang_build_version ||= begin @clang_build_version ||= begin
if (path = locate("clang")) && if (path = locate("clang")) &&
build_version = `#{path} --version`[%r{clang(-| version [^ ]+ \(tags/RELEASE_)(\d{2,})}, 2] (build_version = `#{path} --version`[%r{clang(-| version [^ ]+ \(tags/RELEASE_)(\d{2,})}, 2])
Version.new build_version Version.new build_version
else else
Version::NULL Version::NULL
@ -66,7 +66,7 @@ class DevelopmentTools
@llvm_clang_build_version ||= begin @llvm_clang_build_version ||= begin
path = Formulary.factory("llvm").opt_prefix/"bin/clang" path = Formulary.factory("llvm").opt_prefix/"bin/clang"
if path.executable? && if path.executable? &&
build_version = `#{path} --version`[/clang version (\d\.\d\.\d)/, 1] (build_version = `#{path} --version`[/clang version (\d+\.\d\.\d)/, 1])
Version.new build_version Version.new build_version
else else
Version::NULL Version::NULL
@ -76,10 +76,10 @@ class DevelopmentTools
def non_apple_gcc_version(cc) def non_apple_gcc_version(cc)
(@non_apple_gcc_version ||= {}).fetch(cc) do (@non_apple_gcc_version ||= {}).fetch(cc) do
path = HOMEBREW_PREFIX/"opt/gcc/bin"/cc path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc
path = locate(cc) unless path.exist? path = locate(cc) unless path.exist?
version = if path && version = if path &&
build_version = `#{path} --version`[/gcc(?:(?:-\d+(?:\.\d)?)? \(.+\))? (\d+\.\d\.\d)/, 1] (build_version = `#{path} --version`[/gcc(?:(?:-\d+(?:\.\d)?)? \(.+\))? (\d+\.\d\.\d)/, 1])
Version.new build_version Version.new build_version
else else
Version::NULL Version::NULL

View File

@ -13,6 +13,11 @@ require "mechanize/http/content_disposition_parser"
require "utils/curl" require "utils/curl"
require "github_packages"
require "extend/time"
using TimeRemaining
# @abstract Abstract superclass for all download strategies. # @abstract Abstract superclass for all download strategies.
# #
# @api private # @api private
@ -50,7 +55,7 @@ class AbstractDownloadStrategy
# Download and cache the resource at {#cached_location}. # Download and cache the resource at {#cached_location}.
# #
# @api public # @api public
def fetch; end def fetch(timeout: nil); end
# Disable any output during downloading. # Disable any output during downloading.
# #
@ -66,30 +71,38 @@ class AbstractDownloadStrategy
Context.current.quiet? || @quiet Context.current.quiet? || @quiet
end end
# Unpack {#cached_location} into the current working directory, and possibly # Unpack {#cached_location} into the current working directory.
# chdir into the newly-unpacked directory. #
# Unlike {Resource#stage}, this does not take a block. # Additionally, if a block is given, the working directory was previously empty
# and a single directory is extracted from the archive, the block will be called
# with the working directory changed to that directory. Otherwise this method
# will return, or the block will be called, without changing the current working
# directory.
# #
# @api public # @api public
def stage def stage(&block)
UnpackStrategy.detect(cached_location, UnpackStrategy.detect(cached_location,
prioritise_extension: true, prioritise_extension: true,
ref_type: @ref_type, ref: @ref) ref_type: @ref_type, ref: @ref)
.extract_nestedly(basename: basename, .extract_nestedly(basename: basename,
prioritise_extension: true, prioritise_extension: true,
verbose: verbose? && !quiet?) verbose: verbose? && !quiet?)
chdir chdir(&block) if block
end end
def chdir def chdir(&block)
entries = Dir["*"] entries = Dir["*"]
raise "Empty archive" if entries.length.zero? raise "Empty archive" if entries.length.zero?
return if entries.length != 1
begin if entries.length != 1
Dir.chdir entries.first yield
rescue return
nil end
if File.directory? entries.first
Dir.chdir(entries.first, &block)
else
yield
end end
end end
private :chdir private :chdir
@ -166,18 +179,20 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
# Download and cache the repository at {#cached_location}. # Download and cache the repository at {#cached_location}.
# #
# @api public # @api public
def fetch def fetch(timeout: nil)
end_time = Time.now + timeout if timeout
ohai "Cloning #{url}" ohai "Cloning #{url}"
if cached_location.exist? && repo_valid? if cached_location.exist? && repo_valid?
puts "Updating #{cached_location}" puts "Updating #{cached_location}"
update update(timeout: timeout)
elsif cached_location.exist? elsif cached_location.exist?
puts "Removing invalid repository from cache" puts "Removing invalid repository from cache"
clear_cache clear_cache
clone_repo clone_repo(timeout: end_time)
else else
clone_repo clone_repo(timeout: end_time)
end end
version.update_commit(last_commit) if head? version.update_commit(last_commit) if head?
@ -222,9 +237,11 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
raise NotImplementedError raise NotImplementedError
end end
def clone_repo; end sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil); end
def update; end sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil); end
def current_revision; end def current_revision; end
@ -303,7 +320,7 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
query_params = CGI.parse(uri.query) query_params = CGI.parse(uri.query)
query_params["response-content-disposition"].each do |param| query_params["response-content-disposition"].each do |param|
query_basename = param[/attachment;\s*filename=(["']?)(.+)\1/i, 2] query_basename = param[/attachment;\s*filename=(["']?)(.+)\1/i, 2]
return query_basename if query_basename return File.basename(query_basename) if query_basename
end end
end end
@ -343,7 +360,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
# Download and cache the file at {#cached_location}. # Download and cache the file at {#cached_location}.
# #
# @api public # @api public
def fetch def fetch(timeout: nil)
end_time = Time.now + timeout if timeout
download_lock = LockFile.new(temporary_path.basename) download_lock = LockFile.new(temporary_path.basename)
download_lock.lock download_lock.lock
@ -354,7 +373,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
ohai "Downloading #{url}" ohai "Downloading #{url}"
resolved_url, _, url_time, = resolve_url_basename_time_file_size(url) resolved_url, _, url_time, = resolve_url_basename_time_file_size(url, timeout: end_time&.remaining!)
fresh = if cached_location.exist? && url_time fresh = if cached_location.exist? && url_time
url_time <= cached_location.mtime url_time <= cached_location.mtime
@ -368,7 +387,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
puts "Already downloaded: #{cached_location}" puts "Already downloaded: #{cached_location}"
else else
begin begin
_fetch(url: url, resolved_url: resolved_url) _fetch(url: url, resolved_url: resolved_url, timeout: end_time&.remaining!)
rescue ErrorDuringExecution rescue ErrorDuringExecution
raise CurlDownloadStrategyError, url raise CurlDownloadStrategyError, url
end end
@ -385,6 +404,8 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
puts "Trying a mirror..." puts "Trying a mirror..."
retry retry
rescue Timeout::Error => e
raise Timeout::Error, "Timed out downloading #{self.url}: #{e}"
end end
ensure ensure
download_lock&.unlock download_lock&.unlock
@ -396,19 +417,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
rm_rf(temporary_path) rm_rf(temporary_path)
end end
def resolved_time_file_size def resolved_time_file_size(timeout: nil)
_, _, time, file_size = resolve_url_basename_time_file_size(url) _, _, time, file_size = resolve_url_basename_time_file_size(url, timeout: timeout)
[time, file_size] [time, file_size]
end end
private private
def resolved_url_and_basename def resolved_url_and_basename(timeout: nil)
resolved_url, basename, = resolve_url_basename_time_file_size(url) resolved_url, basename, = resolve_url_basename_time_file_size(url, timeout: nil)
[resolved_url, basename] [resolved_url, basename]
end end
def resolve_url_basename_time_file_size(url) def resolve_url_basename_time_file_size(url, timeout: nil)
@resolved_info_cache ||= {} @resolved_info_cache ||= {}
return @resolved_info_cache[url] if @resolved_info_cache.include?(url) return @resolved_info_cache[url] if @resolved_info_cache.include?(url)
@ -416,7 +437,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
url = url.sub(%r{^((ht|f)tps?://)?}, "#{domain.chomp("/")}/") url = url.sub(%r{^((ht|f)tps?://)?}, "#{domain.chomp("/")}/")
end end
out, _, status= curl_output("--location", "--silent", "--head", "--request", "GET", url.to_s) out, _, status= curl_output("--location", "--silent", "--head", "--request", "GET", url.to_s, timeout: timeout)
lines = status.success? ? out.lines.map(&:chomp) : [] lines = status.success? ? out.lines.map(&:chomp) : []
@ -441,16 +462,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new
parse_content_disposition = lambda do |line| parse_content_disposition = lambda do |line|
next unless content_disposition = content_disposition_parser.parse(line.sub(/; *$/, ""), true) next unless (content_disposition = content_disposition_parser.parse(line.sub(/; *$/, ""), true))
filename = nil filename = nil
if filename_with_encoding = content_disposition.parameters["filename*"] if (filename_with_encoding = content_disposition.parameters["filename*"])
encoding, encoded_filename = filename_with_encoding.split("''", 2) encoding, encoded_filename = filename_with_encoding.split("''", 2)
filename = URI.decode_www_form_component(encoded_filename).encode(encoding) if encoding && encoded_filename filename = URI.decode_www_form_component(encoded_filename).encode(encoding) if encoding && encoded_filename
end end
filename || content_disposition.filename # Servers may include '/' in their Content-Disposition filename header. Take only the basename of this, because:
# - Unpacking code assumes this is a single file - not something living in a subdirectory.
# - Directory traversal attacks are possible without limiting this to just the basename.
File.basename(filename || content_disposition.filename)
end end
filenames = lines.map(&parse_content_disposition).compact filenames = lines.map(&parse_content_disposition).compact
@ -472,7 +496,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
@resolved_info_cache[url] = [redirect_url, basename, time, file_size] @resolved_info_cache[url] = [redirect_url, basename, time, file_size]
end end
def _fetch(url:, resolved_url:) def _fetch(url:, resolved_url:, timeout:)
ohai "Downloading from #{resolved_url}" if url != resolved_url ohai "Downloading from #{resolved_url}" if url != resolved_url
if Homebrew::EnvConfig.no_insecure_redirect? && if Homebrew::EnvConfig.no_insecure_redirect? &&
@ -481,7 +505,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
raise CurlDownloadStrategyError, url raise CurlDownloadStrategyError, url
end end
curl_download resolved_url, to: temporary_path curl_download resolved_url, to: temporary_path, timeout: timeout
end end
# Curl options to be always passed to curl, # Curl options to be always passed to curl,
@ -516,6 +540,25 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
end end
end end
# Strategy for downloading a file from an GitHub Packages URL.
#
# @api public
class CurlGitHubPackagesDownloadStrategy < CurlDownloadStrategy
attr_accessor :checksum, :name
private
def _fetch(url:, resolved_url:)
raise "Empty checksum" if checksum.blank?
raise "Empty name" if name.blank?
_, org, repo, = *url.match(GitHubPackages::URL_REGEX)
blob_url = "https://ghcr.io/v2/#{org}/#{repo}/#{name}/blobs/sha256:#{checksum}"
curl_download(blob_url, "--header", "Authorization: Bearer", to: temporary_path)
end
end
# Strategy for downloading a file from an Apache Mirror URL. # Strategy for downloading a file from an Apache Mirror URL.
# #
# @api public # @api public
@ -535,9 +578,9 @@ class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
@combined_mirrors = [*@mirrors, *backup_mirrors] @combined_mirrors = [*@mirrors, *backup_mirrors]
end end
def resolve_url_basename_time_file_size(url) def resolve_url_basename_time_file_size(url, timeout: nil)
if url == self.url if url == self.url
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}") super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}", timeout: timeout)
else else
super super
end end
@ -560,7 +603,7 @@ end
class CurlPostDownloadStrategy < CurlDownloadStrategy class CurlPostDownloadStrategy < CurlDownloadStrategy
private private
def _fetch(url:, resolved_url:) def _fetch(url:, resolved_url:, timeout:)
args = if meta.key?(:data) args = if meta.key?(:data)
escape_data = ->(d) { ["-d", URI.encode_www_form([d])] } escape_data = ->(d) { ["-d", URI.encode_www_form([d])] }
[url, *meta[:data].flat_map(&escape_data)] [url, *meta[:data].flat_map(&escape_data)]
@ -569,7 +612,7 @@ class CurlPostDownloadStrategy < CurlDownloadStrategy
query.nil? ? [url, "-X", "POST"] : [url, "-d", query] query.nil? ? [url, "-X", "POST"] : [url, "-d", query]
end end
curl_download(*args, to: temporary_path) curl_download(*args, to: temporary_path, timeout: timeout)
end end
end end
@ -582,6 +625,7 @@ class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
UnpackStrategy::Uncompressed.new(cached_location) UnpackStrategy::Uncompressed.new(cached_location)
.extract(basename: basename, .extract(basename: basename,
verbose: verbose? && !quiet?) verbose: verbose? && !quiet?)
yield if block_given?
end end
end end
@ -608,7 +652,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
# Download and cache the repository at {#cached_location}. # Download and cache the repository at {#cached_location}.
# #
# @api public # @api public
def fetch def fetch(timeout: nil)
if @url.chomp("/") != repo_url || !silent_command("svn", args: ["switch", @url, cached_location]).success? if @url.chomp("/") != repo_url || !silent_command("svn", args: ["switch", @url, cached_location]).success?
clear_cache clear_cache
end end
@ -649,7 +693,11 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
end end
end end
def fetch_repo(target, url, revision = nil, ignore_externals: false) sig {
params(target: Pathname, url: String, revision: T.nilable(String), ignore_externals: T::Boolean,
timeout: T.nilable(Time)).void
}
def fetch_repo(target, url, revision = nil, ignore_externals: false, timeout: nil)
# Use "svn update" when the repository already exists locally. # Use "svn update" when the repository already exists locally.
# This saves on bandwidth and will have a similar effect to verifying the # This saves on bandwidth and will have a similar effect to verifying the
# cache as it will make any changes to get the right revision. # cache as it will make any changes to get the right revision.
@ -669,9 +717,9 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
end end
if target.directory? if target.directory?
command!("svn", args: ["update", *args], chdir: target.to_s) command! "svn", args: ["update", *args], chdir: target.to_s, timeout: timeout&.remaining
else else
command!("svn", args: ["checkout", url, target, *args]) command! "svn", args: ["checkout", url, target, *args], timeout: timeout&.remaining
end end
end end
@ -684,20 +732,22 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
(cached_location/".svn").directory? (cached_location/".svn").directory?
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
case @ref_type case @ref_type
when :revision when :revision
fetch_repo cached_location, @url, @ref fetch_repo cached_location, @url, @ref, timeout: timeout
when :revisions when :revisions
# nil is OK for main_revision, as fetch_repo will then get latest # nil is OK for main_revision, as fetch_repo will then get latest
main_revision = @ref[:trunk] main_revision = @ref[:trunk]
fetch_repo cached_location, @url, main_revision, ignore_externals: true fetch_repo cached_location, @url, main_revision, ignore_externals: true, timeout: timeout
externals do |external_name, external_url| externals do |external_name, external_url|
fetch_repo cached_location/external_name, external_url, @ref[external_name], ignore_externals: true fetch_repo cached_location/external_name, external_url, @ref[external_name], ignore_externals: true,
timeout: timeout
end end
else else
fetch_repo cached_location, @url fetch_repo cached_location, @url, timeout: timeout
end end
end end
alias update clone_repo alias update clone_repo
@ -746,12 +796,13 @@ class GitDownloadStrategy < VCSDownloadStrategy
0 0
end end
def update sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
config_repo config_repo
update_repo update_repo(timeout: timeout)
checkout checkout(timeout: timeout)
reset reset
update_submodules if submodules? update_submodules(timeout: timeout) if submodules?
end end
def shallow_clone? def shallow_clone?
@ -812,6 +863,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
end end
end end
sig { void }
def config_repo def config_repo
command! "git", command! "git",
args: ["config", "remote.origin.url", @url], args: ["config", "remote.origin.url", @url],
@ -824,35 +876,42 @@ class GitDownloadStrategy < VCSDownloadStrategy
chdir: cached_location chdir: cached_location
end end
def update_repo sig { params(timeout: T.nilable(Time)).void }
def update_repo(timeout: nil)
return if @ref_type != :branch && ref? return if @ref_type != :branch && ref?
if !shallow_clone? && shallow_dir? if !shallow_clone? && shallow_dir?
command! "git", command! "git",
args: ["fetch", "origin", "--unshallow"], args: ["fetch", "origin", "--unshallow"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
else else
command! "git", command! "git",
args: ["fetch", "origin"], args: ["fetch", "origin"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
end end
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
command! "git", args: clone_args def clone_repo(timeout: nil)
command! "git", args: clone_args, timeout: timeout&.remaining
command! "git", command! "git",
args: ["config", "homebrew.cacheversion", cache_version], args: ["config", "homebrew.cacheversion", cache_version],
chdir: cached_location chdir: cached_location,
checkout timeout: timeout&.remaining
update_submodules if submodules? checkout(timeout: timeout)
update_submodules(timeout: timeout) if submodules?
end end
def checkout sig { params(timeout: T.nilable(Time)).void }
def checkout(timeout: nil)
ohai "Checking out #{@ref_type} #{@ref}" if @ref_type && @ref ohai "Checking out #{@ref_type} #{@ref}" if @ref_type && @ref
command! "git", args: ["checkout", "-f", @ref, "--"], chdir: cached_location command! "git", args: ["checkout", "-f", @ref, "--"], chdir: cached_location, timeout: timeout&.remaining
end end
sig { void }
def reset def reset
ref = case @ref_type ref = case @ref_type
when :branch when :branch
@ -866,13 +925,16 @@ class GitDownloadStrategy < VCSDownloadStrategy
chdir: cached_location chdir: cached_location
end end
def update_submodules sig { params(timeout: T.nilable(Time)).void }
def update_submodules(timeout: nil)
command! "git", command! "git",
args: ["submodule", "foreach", "--recursive", "git submodule sync"], args: ["submodule", "foreach", "--recursive", "git submodule sync"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
command! "git", command! "git",
args: ["submodule", "update", "--init", "--recursive"], args: ["submodule", "update", "--init", "--recursive"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
fix_absolute_submodule_gitdir_references! fix_absolute_submodule_gitdir_references!
end end
@ -1024,23 +1086,27 @@ class CVSDownloadStrategy < VCSDownloadStrategy
"-Q" unless verbose? "-Q" unless verbose?
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
# Login is only needed (and allowed) with pserver; skip for anoncvs. # Login is only needed (and allowed) with pserver; skip for anoncvs.
command! "cvs", args: [*quiet_flag, "-d", @url, "login"] if @url.include? "pserver" command! "cvs", args: [*quiet_flag, "-d", @url, "login"], timeout: timeout&.remaining if @url.include? "pserver"
command! "cvs", command! "cvs",
args: [*quiet_flag, "-d", @url, "checkout", "-d", cached_location.basename, @module], args: [*quiet_flag, "-d", @url, "checkout", "-d", cached_location.basename, @module],
chdir: cached_location.dirname chdir: cached_location.dirname,
timeout: timeout&.remaining
end end
def update sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
command! "cvs", command! "cvs",
args: [*quiet_flag, "update"], args: [*quiet_flag, "update"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
end end
def split_url(in_url) def split_url(in_url)
parts = in_url.split(/:/) parts = in_url.split(":")
mod = parts.pop mod = parts.pop
url = parts.join(":") url = parts.join(":")
[mod, url] [mod, url]
@ -1088,12 +1154,14 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
(cached_location/".hg").directory? (cached_location/".hg").directory?
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
command! "hg", args: ["clone", @url, cached_location] def clone_repo(timeout: nil)
command! "hg", args: ["clone", @url, cached_location], timeout: timeout&.remaining
end end
def update sig { params(timeout: T.nilable(Time)).void }
command! "hg", args: ["--cwd", cached_location, "pull", "--update"] def update(timeout: nil)
command! "hg", args: ["--cwd", cached_location, "pull", "--update"], timeout: timeout&.remaining
update_args = if @ref_type && @ref update_args = if @ref_type && @ref
ohai "Checking out #{@ref_type} #{@ref}" ohai "Checking out #{@ref_type} #{@ref}"
@ -1102,7 +1170,7 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
["--clean"] ["--clean"]
end end
command! "hg", args: ["--cwd", cached_location, "update", *update_args] command! "hg", args: ["--cwd", cached_location, "update", *update_args], timeout: timeout&.remaining
end end
end end
@ -1151,16 +1219,20 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
(cached_location/".bzr").directory? (cached_location/".bzr").directory?
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
# "lightweight" means history-less # "lightweight" means history-less
command! "bzr", command! "bzr",
args: ["checkout", "--lightweight", @url, cached_location] args: ["checkout", "--lightweight", @url, cached_location],
timeout: timeout&.remaining
end end
def update sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
command! "bzr", command! "bzr",
args: ["update"], args: ["update"],
chdir: cached_location chdir: cached_location,
timeout: timeout&.remaining
end end
end end
@ -1203,12 +1275,14 @@ class FossilDownloadStrategy < VCSDownloadStrategy
"fossil" "fossil"
end end
def clone_repo sig { params(timeout: T.nilable(Time)).void }
silent_command!("fossil", args: ["clone", @url, cached_location]) def clone_repo(timeout: nil)
silent_command! "fossil", args: ["clone", @url, cached_location], timeout: timeout&.remaining
end end
def update sig { params(timeout: T.nilable(Time)).void }
silent_command!("fossil", args: ["pull", "-R", cached_location]) def update(timeout: nil)
silent_command! "fossil", args: ["pull", "-R", cached_location], timeout: timeout&.remaining
end end
end end
@ -1231,6 +1305,8 @@ class DownloadStrategyDetector
def self.detect_from_url(url) def self.detect_from_url(url)
case url case url
when GitHubPackages::URL_REGEX
CurlGitHubPackagesDownloadStrategy
when %r{^https?://github\.com/[^/]+/[^/]+\.git$} when %r{^https?://github\.com/[^/]+/[^/]+\.git$}
GitHubGitDownloadStrategy GitHubGitDownloadStrategy
when %r{^https?://.+\.git$}, when %r{^https?://.+\.git$},

View File

@ -46,8 +46,12 @@ module Homebrew
}, },
HOMEBREW_BOTTLE_DOMAIN: { HOMEBREW_BOTTLE_DOMAIN: {
description: "Use this URL as the download mirror for bottles. " \ description: "Use this URL as the download mirror for bottles. " \
"If bottles at that URL are temporarily unavailable, " \
"the default bottle domain will be used as a fallback mirror. " \
"For example, `HOMEBREW_BOTTLE_DOMAIN=http://localhost:8080` will cause all bottles to " \ "For example, `HOMEBREW_BOTTLE_DOMAIN=http://localhost:8080` will cause all bottles to " \
"download from the prefix `http://localhost:8080/`.", "download from the prefix `http://localhost:8080/`. " \
"If bottles are not available at `HOMEBREW_BOTTLE_DOMAIN` " \
"they will be downloaded from the default bottle domain.",
default_text: "macOS: `https://homebrew.bintray.com/`, " \ default_text: "macOS: `https://homebrew.bintray.com/`, " \
"Linux: `https://linuxbrew.bintray.com/`.", "Linux: `https://linuxbrew.bintray.com/`.",
default: HOMEBREW_BOTTLE_DEFAULT_DOMAIN, default: HOMEBREW_BOTTLE_DEFAULT_DOMAIN,
@ -168,6 +172,13 @@ module Homebrew
"\n\n *Note:* Homebrew doesn't require permissions for any of the scopes, but some " \ "\n\n *Note:* Homebrew doesn't require permissions for any of the scopes, but some " \
"developer commands may require additional permissions.", "developer commands may require additional permissions.",
}, },
HOMEBREW_GITHUB_PACKAGES_TOKEN: {
description: "Use this GitHub personal access token when accessing the GitHub Packages Registry "\
"(where bottles may be stored).",
},
HOMEBREW_GITHUB_PACKAGES_USER: {
description: "Use this username when accessing the GitHub Packages Registry (where bottles may be stored).",
},
HOMEBREW_GIT_EMAIL: { HOMEBREW_GIT_EMAIL: {
description: "Set the Git author and committer email to this value.", description: "Set the Git author and committer email to this value.",
}, },
@ -179,6 +190,10 @@ module Homebrew
default_text: 'The "Beer Mug" emoji.', default_text: 'The "Beer Mug" emoji.',
default: "🍺", default: "🍺",
}, },
HOMEBREW_INTERNET_ARCHIVE_KEY: {
description: "Use this API key when accessing the Internet Archive S3 API, where bottles are stored. " \
"The format is access:secret. See https://archive.org/account/s3.php",
},
HOMEBREW_LIVECHECK_WATCHLIST: { HOMEBREW_LIVECHECK_WATCHLIST: {
description: "Consult this file for the list of formulae to check by default when no formula argument " \ description: "Consult this file for the list of formulae to check by default when no formula argument " \
"is passed to `brew livecheck`.", "is passed to `brew livecheck`.",
@ -204,19 +219,14 @@ module Homebrew
boolean: true, boolean: true,
}, },
HOMEBREW_NO_AUTO_UPDATE: { HOMEBREW_NO_AUTO_UPDATE: {
description: "If set, do not automatically update before running " \ description: "If set, do not automatically update before running some commands e.g. " \
"`brew install`, `brew upgrade` or `brew tap`.", "`brew install`, `brew upgrade` and `brew tap`.",
boolean: true, boolean: true,
}, },
HOMEBREW_NO_BOOTSNAP: { HOMEBREW_NO_BOOTSNAP: {
description: "If set, do not use Bootsnap to speed up repeated `brew` calls.", description: "If set, do not use Bootsnap to speed up repeated `brew` calls.",
boolean: true, boolean: true,
}, },
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: {
description: "If set, fail on the failure of installation from a bottle rather than " \
"falling back to building from source.",
boolean: true,
},
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: { HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: {
description: "If set, do not check for broken dependents after installing, upgrading or reinstalling " \ description: "If set, do not check for broken dependents after installing, upgrading or reinstalling " \
"formulae.", "formulae.",
@ -257,6 +267,11 @@ module Homebrew
description: "If set, use Pry for the `brew irb` command.", description: "If set, use Pry for the `brew irb` command.",
boolean: true, boolean: true,
}, },
HOMEBREW_SIMULATE_MACOS_ON_LINUX: {
description: "If set, running Homebrew on Linux will simulate certain macOS code paths. This is useful " \
"when auditing macOS formulae while on Linux. Implies `HOMEBREW_FORCE_HOMEBREW_ON_LINUX`.",
boolean: true,
},
HOMEBREW_SKIP_OR_LATER_BOTTLES: { HOMEBREW_SKIP_OR_LATER_BOTTLES: {
description: "If set along with `HOMEBREW_DEVELOPER`, do not use bottles from older versions " \ description: "If set along with `HOMEBREW_DEVELOPER`, do not use bottles from older versions " \
"of macOS. This is useful in development on new macOS versions.", "of macOS. This is useful in development on new macOS versions.",

View File

@ -97,6 +97,25 @@ class FormulaOrCaskUnavailableError < RuntimeError
end end
end end
# Raised when a formula or cask in a specific tap is not available.
class TapFormulaOrCaskUnavailableError < FormulaOrCaskUnavailableError
extend T::Sig
attr_reader :tap
def initialize(tap, name)
super "#{tap}/#{name}"
@tap = tap
end
sig { returns(String) }
def to_s
s = super
s += "\nPlease tap it and then try again: brew tap #{tap}" unless tap.installed?
s
end
end
# Raised when a formula is not available. # Raised when a formula is not available.
class FormulaUnavailableError < FormulaOrCaskUnavailableError class FormulaUnavailableError < FormulaOrCaskUnavailableError
extend T::Sig extend T::Sig
@ -423,7 +442,7 @@ class BuildError < RuntimeError
def fetch_issues def fetch_issues
GitHub.issues_for_formula(formula.name, tap: formula.tap, state: "open") GitHub.issues_for_formula(formula.name, tap: formula.tap, state: "open")
rescue GitHub::RateLimitExceededError => e rescue GitHub::API::RateLimitExceededError => e
opoo e.message opoo e.message
[] []
end end
@ -453,7 +472,7 @@ class BuildError < RuntimeError
if formula.tap && defined?(OS::ISSUES_URL) if formula.tap && defined?(OS::ISSUES_URL)
if formula.tap.official? if formula.tap.official?
puts Formatter.error(Formatter.url(OS::ISSUES_URL), label: "READ THIS") puts Formatter.error(Formatter.url(OS::ISSUES_URL), label: "READ THIS")
elsif issues_url = formula.tap.issues_url elsif (issues_url = formula.tap.issues_url)
puts <<~EOS puts <<~EOS
If reporting this issue please do so at (not Homebrew/brew or Homebrew/core): If reporting this issue please do so at (not Homebrew/brew or Homebrew/core):
#{Formatter.url(issues_url)} #{Formatter.url(issues_url)}
@ -579,19 +598,32 @@ class ErrorDuringExecution < RuntimeError
@status = status @status = status
@output = output @output = output
raise ArgumentError, "Status cannot be nil." if status.nil?
exitstatus = case status exitstatus = case status
when Integer when Integer
status status
when Hash
status["exitstatus"]
else else
status&.exitstatus status.exitstatus
end
termsig = case status
when Integer
nil
when Hash
status["termsig"]
else
status.termsig
end end
redacted_cmd = redact_secrets(cmd.shelljoin.gsub('\=', "="), secrets) redacted_cmd = redact_secrets(cmd.shelljoin.gsub('\=', "="), secrets)
reason = if exitstatus reason = if exitstatus
"exited with #{exitstatus}" "exited with #{exitstatus}"
elsif (uncaught_signal = status&.termsig) elsif termsig
"was terminated by uncaught signal #{Signal.signame(uncaught_signal)}" "was terminated by uncaught signal #{Signal.signame(termsig)}"
else else
raise ArgumentError, "Status neither has `exitstatus` nor `termsig`." raise ArgumentError, "Status neither has `exitstatus` nor `termsig`."
end end

View File

@ -35,7 +35,7 @@ module EnvActivation
params( params(
env: T.nilable(String), env: T.nilable(String),
cc: T.nilable(String), cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean), build_bottle: T::Boolean,
bottle_arch: T.nilable(String), bottle_arch: T.nilable(String),
_block: T.proc.returns(T.untyped), _block: T.proc.returns(T.untyped),
).returns(T.untyped) ).returns(T.untyped)

View File

@ -35,13 +35,14 @@ module SharedEnvExtension
sig { sig {
params( params(
formula: T.nilable(Formula), formula: T.nilable(Formula),
cc: T.nilable(String), cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean), build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean), bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).void ).void
} }
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
@formula = formula @formula = formula
@cc = cc @cc = cc
@build_bottle = build_bottle @build_bottle = build_bottle

View File

@ -16,13 +16,14 @@ module Stdenv
# @private # @private
sig { sig {
params( params(
formula: T.nilable(Formula), formula: T.nilable(Formula),
cc: T.nilable(String), cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean), build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean), bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).void ).void
} }
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
super super
self["HOMEBREW_ENV"] = "std" self["HOMEBREW_ENV"] = "std"

View File

@ -44,13 +44,14 @@ module Superenv
# @private # @private
sig { sig {
params( params(
formula: T.nilable(Formula), formula: T.nilable(Formula),
cc: T.nilable(String), cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean), build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean), bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).void ).void
} }
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
super super
send(compiler) send(compiler)

View File

@ -32,14 +32,14 @@ module GitRepositoryExtension
# Gets the full commit hash of the HEAD commit. # Gets the full commit hash of the HEAD commit.
sig { params(safe: T::Boolean).returns(T.nilable(String)) } sig { params(safe: T::Boolean).returns(T.nilable(String)) }
def git_head(safe: false) def git_head(safe: false)
popen_git("rev-parse", "--verify", "-q", "HEAD", safe: safe) popen_git("rev-parse", "--verify", "--quiet", "HEAD", safe: safe)
end end
# Gets a short commit hash of the HEAD commit. # Gets a short commit hash of the HEAD commit.
sig { params(length: T.nilable(Integer), safe: T::Boolean).returns(T.nilable(String)) } sig { params(length: T.nilable(Integer), safe: T::Boolean).returns(T.nilable(String)) }
def git_short_head(length: nil, safe: false) def git_short_head(length: nil, safe: false)
short_arg = length.present? ? "--short=#{length}" : "--short" short_arg = length.present? ? "--short=#{length}" : "--short"
popen_git("rev-parse", short_arg, "--verify", "-q", "HEAD", safe: safe) popen_git("rev-parse", short_arg, "--verify", "--quiet", "HEAD", safe: safe)
end end
# Gets the relative date of the last commit, e.g. "1 hour ago" # Gets the relative date of the last commit, e.g. "1 hour ago"

View File

@ -3,7 +3,10 @@
module Stdenv module Stdenv
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
generic_setup_build_environment(formula: formula, cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch) generic_setup_build_environment(
formula: formula, cc: cc, build_bottle: build_bottle,
bottle_arch: bottle_arch, testing_formula: testing_formula
)
prepend_path "CPATH", HOMEBREW_PREFIX/"include" prepend_path "CPATH", HOMEBREW_PREFIX/"include"
prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib" prepend_path "LIBRARY_PATH", HOMEBREW_PREFIX/"lib"

View File

@ -11,7 +11,10 @@ module Superenv
# @private # @private
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
generic_setup_build_environment(formula: formula, cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch) generic_setup_build_environment(
formula: formula, cc: cc, build_bottle: build_bottle,
bottle_arch: bottle_arch, testing_formula: testing_formula
)
self["HOMEBREW_OPTIMIZATION_LEVEL"] = "O2" self["HOMEBREW_OPTIMIZATION_LEVEL"] = "O2"
self["HOMEBREW_DYNAMIC_LINKER"] = determine_dynamic_linker_path self["HOMEBREW_DYNAMIC_LINKER"] = determine_dynamic_linker_path
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula) self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula)

View File

@ -80,7 +80,11 @@ class Keg
end end
def self.relocation_formulae def self.relocation_formulae
["patchelf"] @relocation_formulae ||= if HOMEBREW_PATCHELF_RB_WRITE
[]
else
["patchelf"]
end.freeze
end end
def self.bottle_dependencies def self.bottle_dependencies

View File

@ -51,10 +51,7 @@ class DevelopmentTools
sig { returns(String) } sig { returns(String) }
def installation_instructions def installation_instructions
<<~EOS MacOS::CLT.installation_instructions
Install the Command Line Tools:
xcode-select --install
EOS
end end
sig { returns(String) } sig { returns(String) }

View File

@ -64,6 +64,7 @@ module Homebrew
check_clt_minimum_version check_clt_minimum_version
check_if_xcode_needs_clt_installed check_if_xcode_needs_clt_installed
check_if_supported_sdk_available check_if_supported_sdk_available
check_broken_sdks
].freeze ].freeze
end end
@ -425,7 +426,7 @@ module Homebrew
source = if locator.source == :clt source = if locator.source == :clt
update_instructions = MacOS::CLT.update_instructions update_instructions = MacOS::CLT.update_instructions
"CLT" "Command Line Tools (CLT)"
else else
update_instructions = MacOS::Xcode.update_instructions update_instructions = MacOS::Xcode.update_instructions
"Xcode" "Xcode"
@ -438,6 +439,40 @@ module Homebrew
#{update_instructions} #{update_instructions}
EOS EOS
end end
# The CLT 10.x -> 11.x upgrade process on 10.14 contained a bug which broke the SDKs.
# Notably, MacOSX10.14.sdk would indirectly symlink to MacOSX10.15.sdk.
# This diagnostic was introduced to check for this and recommend a full reinstall.
def check_broken_sdks
locator = MacOS.sdk_locator
return if locator.all_sdks.all? do |sdk|
path_version = sdk.path.basename.to_s[MacOS::SDK::VERSIONED_SDK_REGEX, 1]
next true if path_version.blank?
sdk.version == MacOS::Version.new(path_version).strip_patch
end
if locator.source == :clt
source = "Command Line Tools (CLT)"
path_to_remove = MacOS::CLT::PKG_PATH
installation_instructions = MacOS::CLT.installation_instructions
else
source = "Xcode"
path_to_remove = MacOS::Xcode.bundle_path
installation_instructions = MacOS::Xcode.installation_instructions
end
<<~EOS
The contents of the SDKs in your #{source} installation do not match the SDK folder names.
A clean reinstall of #{source} should fix this.
Remove the broken installation before reinstalling:
sudo rm -rf #{path_to_remove}
#{installation_instructions}
EOS
end
end end
end end
end end

View File

@ -11,7 +11,10 @@ module Stdenv
end end
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false) def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil, testing_formula: false)
generic_setup_build_environment(formula: formula, cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch) generic_setup_build_environment(
formula: formula, cc: cc, build_bottle: build_bottle,
bottle_arch: bottle_arch, testing_formula: testing_formula
)
# sed is strict, and errors out when it encounters files with # sed is strict, and errors out when it encounters files with
# mixed character sets # mixed character sets

View File

@ -98,8 +98,6 @@ module Superenv
def determine_cccfg def determine_cccfg
s = +"" s = +""
# Fix issue with sed barfing on unicode characters on Mountain Lion
s << "s"
# Fix issue with >= Mountain Lion apr-1-config having broken paths # Fix issue with >= Mountain Lion apr-1-config having broken paths
s << "a" s << "a"
s.freeze s.freeze
@ -121,7 +119,10 @@ module Superenv
self["HOMEBREW_SDKROOT"] = nil self["HOMEBREW_SDKROOT"] = nil
self["HOMEBREW_DEVELOPER_DIR"] = nil self["HOMEBREW_DEVELOPER_DIR"] = nil
end end
generic_setup_build_environment(formula: formula, cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch) generic_setup_build_environment(
formula: formula, cc: cc, build_bottle: build_bottle,
bottle_arch: bottle_arch, testing_formula: testing_formula
)
# Filter out symbols known not to be defined since GNU Autotools can't # Filter out symbols known not to be defined since GNU Autotools can't
# reliably figure this out with Xcode 8 and above. # reliably figure this out with Xcode 8 and above.

View File

@ -1,6 +1,8 @@
# typed: false # typed: false
# frozen_string_literal: true # frozen_string_literal: true
# The Library/Homebrew/extend/os/software_spec.rb conditional logic will need to be more nuanced
# if this file ever includes more than `uses_from_macos`.
class SoftwareSpec class SoftwareSpec
undef uses_from_macos undef uses_from_macos
@ -9,11 +11,12 @@ class SoftwareSpec
if deps.is_a?(Hash) if deps.is_a?(Hash)
bounds = deps.dup bounds = deps.dup
deps = Hash[*bounds.shift] deps = bounds.shift
end end
bounds = bounds.transform_values { |v| MacOS::Version.from_symbol(v) } bounds = bounds.transform_values { |v| MacOS::Version.from_symbol(v) }
if MacOS.version >= bounds[:since] if MacOS.version >= bounds[:since] ||
(Homebrew::EnvConfig.simulate_macos_on_linux? && !bounds.key?(:since))
@uses_from_macos_elements << deps @uses_from_macos_elements << deps
else else
depends_on deps depends_on deps

View File

@ -20,10 +20,10 @@ module Utils
alias generic_find_matching_tag find_matching_tag alias generic_find_matching_tag find_matching_tag
def find_matching_tag(tag) def find_matching_tag(tag, exact: false)
# Used primarily by developers testing beta macOS releases. # Used primarily by developers testing beta macOS releases.
if OS::Mac.prerelease? && Homebrew::EnvConfig.developer? && if exact || (OS::Mac.prerelease? && Homebrew::EnvConfig.developer? &&
Homebrew::EnvConfig.skip_or_later_bottles? Homebrew::EnvConfig.skip_or_later_bottles?)
generic_find_matching_tag(tag) generic_find_matching_tag(tag)
else else
generic_find_matching_tag(tag) || generic_find_matching_tag(tag) ||

View File

@ -1,7 +1,7 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
if OS.mac? if OS.mac? || Homebrew::EnvConfig.simulate_macos_on_linux?
require "extend/os/mac/on_os" require "extend/os/mac/on_os"
elsif OS.linux? elsif OS.linux?
require "extend/os/linux/on_os" require "extend/os/linux/on_os"

View File

@ -1,8 +1,9 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
if OS.linux? # This logic will need to be more nuanced if this file includes more than `uses_from_macos`.
require "extend/os/linux/software_spec" if OS.mac? || Homebrew::EnvConfig.simulate_macos_on_linux?
elsif OS.mac?
require "extend/os/mac/software_spec" require "extend/os/mac/software_spec"
elsif OS.linux?
require "extend/os/linux/software_spec"
end end

View File

@ -0,0 +1,18 @@
# typed: false
# frozen_string_literal: true
module TimeRemaining
refine Time do
def remaining
[0, self - Time.now].max
end
def remaining!
r = remaining
raise Timeout::Error if r <= 0
r
end
end
end

View File

@ -2061,7 +2061,7 @@ class Formula
if verbose_using_dots if verbose_using_dots
last_dot = Time.at(0) last_dot = Time.at(0)
while buf = rd.gets while (buf = rd.gets)
log.puts buf log.puts buf
# make sure dots printed with interval of at least 1 min. # make sure dots printed with interval of at least 1 min.
next unless (Time.now - last_dot) > 60 next unless (Time.now - last_dot) > 60
@ -2072,7 +2072,7 @@ class Formula
end end
puts puts
else else
while buf = rd.gets while (buf = rd.gets)
log.puts buf log.puts buf
puts buf puts buf
end end

View File

@ -8,8 +8,54 @@ module Homebrew
module Assertions module Assertions
include Context include Context
require "test/unit/assertions" require "minitest"
include ::Test::Unit::Assertions require "minitest/assertions"
include ::Minitest::Assertions
attr_writer :assertions
def assertions
@assertions ||= 0
end
# Test::Unit backwards compatibility methods
{
assert_include: :assert_includes,
assert_path_exist: :assert_path_exists,
assert_raise: :assert_raises,
assert_throw: :assert_throws,
assert_not_empty: :refute_empty,
assert_not_equal: :refute_equal,
assert_not_in_delta: :refute_in_delta,
assert_not_in_epsilon: :refute_in_epsilon,
assert_not_include: :refute_includes,
assert_not_includes: :refute_includes,
assert_not_instance_of: :refute_instance_of,
assert_not_kind_of: :refute_kind_of,
assert_not_match: :refute_match,
assert_no_match: :refute_match,
assert_not_nil: :refute_nil,
assert_not_operator: :refute_operator,
assert_path_not_exist: :refute_path_exists,
assert_not_predicate: :refute_predicate,
assert_not_respond_to: :refute_respond_to,
assert_not_same: :refute_same,
}.each do |old_method, new_method|
define_method(old_method) do |*args|
# odeprecated old_method, new_method
send(new_method, *args)
end
end
def assert_true(act, msg = nil)
# odeprecated "assert_true", "assert(...) or assert_equal(true, ...)"
assert_equal(true, act, msg)
end
def assert_false(act, msg = nil)
# odeprecated "assert_false", "assert(!...) or assert_equal(false, ...)"
assert_equal(false, act, msg)
end
# Returns the output of running cmd, and asserts the exit status. # Returns the output of running cmd, and asserts the exit status.
# @api public # @api public
@ -18,7 +64,7 @@ module Homebrew
output = `#{cmd}` output = `#{cmd}`
assert_equal result, $CHILD_STATUS.exitstatus assert_equal result, $CHILD_STATUS.exitstatus
output output
rescue Test::Unit::AssertionFailedError rescue Minitest::Assertion
puts output if verbose? puts output if verbose?
raise raise
end end
@ -35,7 +81,7 @@ module Homebrew
end end
assert_equal result, $CHILD_STATUS.exitstatus unless result.nil? assert_equal result, $CHILD_STATUS.exitstatus unless result.nil?
output output
rescue Test::Unit::AssertionFailedError rescue Minitest::Assertion
puts output if verbose? puts output if verbose?
raise raise
end end

View File

@ -133,7 +133,7 @@ module Homebrew
return return
end end
if oldname = CoreTap.instance.formula_renames[name] if (oldname = CoreTap.instance.formula_renames[name])
problem "'#{name}' is reserved as the old name of #{oldname} in homebrew/core." problem "'#{name}' is reserved as the old name of #{oldname} in homebrew/core."
return return
end end
@ -282,7 +282,7 @@ module Homebrew
# The number of conflicts on Linux is absurd. # The number of conflicts on Linux is absurd.
# TODO: remove this and check these there too. # TODO: remove this and check these there too.
return if OS.linux? return if OS.linux? && !Homebrew::EnvConfig.simulate_macos_on_linux?
recursive_runtime_formulae = formula.runtime_formula_dependencies(undeclared: false) recursive_runtime_formulae = formula.runtime_formula_dependencies(undeclared: false)
version_hash = {} version_hash = {}
@ -339,6 +339,18 @@ module Homebrew
end end
end end
def audit_glibc
return if formula.name != "glibc"
return unless @core_tap
version = formula.version.to_s
return if version == "2.23"
problem "The glibc version must be #{version}, as this is the version used by our CI on Linux. " \
"Glibc is for users who have a system Glibc with a lower version, " \
"which allows them to use our Linux bottles, which were compiled against system Glibc on CI."
end
def audit_versioned_keg_only def audit_versioned_keg_only
return unless @versioned_formula return unless @versioned_formula
return unless @core_tap return unless @core_tap
@ -367,10 +379,10 @@ module Homebrew
return unless DevelopmentTools.curl_handles_most_https_certificates? return unless DevelopmentTools.curl_handles_most_https_certificates?
if http_content_problem = curl_check_http_content(homepage, if (http_content_problem = curl_check_http_content(homepage,
user_agents: [:browser, :default], user_agents: [:browser, :default],
check_content: true, check_content: true,
strict: @strict) strict: @strict))
problem http_content_problem problem http_content_problem
end end
end end
@ -472,7 +484,7 @@ module Homebrew
%w[Stable HEAD].each do |name| %w[Stable HEAD].each do |name|
spec_name = name.downcase.to_sym spec_name = name.downcase.to_sym
next unless spec = formula.send(spec_name) next unless (spec = formula.send(spec_name))
ra = ResourceAuditor.new(spec, spec_name, online: @online, strict: @strict).audit ra = ResourceAuditor.new(spec, spec_name, online: @online, strict: @strict).audit
ra.problems.each do |message| ra.problems.each do |message|
@ -497,7 +509,7 @@ module Homebrew
) )
end end
if stable = formula.stable if (stable = formula.stable)
version = stable.version version = stable.version
problem "Stable: version (#{version}) is set to a string without a digit" if version.to_s !~ /\d/ problem "Stable: version (#{version}) is set to a string without a digit" if version.to_s !~ /\d/
if version.to_s.start_with?("HEAD") if version.to_s.start_with?("HEAD")

View File

@ -79,7 +79,7 @@ module Homebrew
@desc = metadata["description"] @desc = metadata["description"]
@homepage = metadata["homepage"] @homepage = metadata["homepage"]
@license = metadata["license"]["spdx_id"] if metadata["license"] @license = metadata["license"]["spdx_id"] if metadata["license"]
rescue GitHub::HTTPNotFoundError rescue GitHub::API::HTTPNotFoundError
# If there was no repository found assume the network connection is at # If there was no repository found assume the network connection is at
# fault rather than the input URL. # fault rather than the input URL.
nil nil
@ -153,13 +153,14 @@ module Homebrew
def install def install
# ENV.deparallelize # if your formula fails when building in parallel # ENV.deparallelize # if your formula fails when building in parallel
<% if mode == :cmake %> <% if mode == :cmake %>
system "cmake", ".", *std_cmake_args system "cmake", "-S", ".", "-B", "build", *std_cmake_args
system "cmake", "--build", "build"
system "cmake", "--install", "build"
<% elsif mode == :autotools %> <% elsif mode == :autotools %>
# Remove unrecognized options if warned by configure # Remove unrecognized options if warned by configure
system "./configure", "--disable-debug", # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
"--disable-dependency-tracking", system "./configure", *std_configure_args, "--disable-silent-rules"
"--disable-silent-rules", system "make", "install" # if this fails, try separate make/make install steps
"--prefix=\#{prefix}"
<% elsif mode == :crystal %> <% elsif mode == :crystal %>
system "shards", "build", "--release" system "shards", "build", "--release"
bin.install "bin/#{name}" bin.install "bin/#{name}"
@ -206,14 +207,9 @@ module Homebrew
system "cargo", "install", *std_cargo_args system "cargo", "install", *std_cargo_args
<% else %> <% else %>
# Remove unrecognized options if warned by configure # Remove unrecognized options if warned by configure
system "./configure", "--disable-debug", # https://rubydoc.brew.sh/Formula.html#std_configure_args-instance_method
"--disable-dependency-tracking", system "./configure", *std_configure_args, "--disable-silent-rules"
"--disable-silent-rules", # system "cmake", "-S", ".", "-B", "build", *std_cmake_args
"--prefix=\#{prefix}"
# system "cmake", ".", *std_cmake_args
<% end %>
<% if mode == :autotools || mode == :cmake %>
system "make", "install" # if this fails, try separate make/make install steps
<% end %> <% end %>
end end

View File

@ -16,13 +16,11 @@ class FormulaInfo
# Returns nil if formula is absent or if there was an error reading it. # Returns nil if formula is absent or if there was an error reading it.
def self.lookup(name) def self.lookup(name)
json = Utils.popen_read( json = Utils.popen_read(
RUBY_PATH, *HOMEBREW_RUBY_EXEC_ARGS,
ENV["HOMEBREW_RUBY_WARNINGS"],
"-I", $LOAD_PATH.join(File::PATH_SEPARATOR),
HOMEBREW_LIBRARY_PATH/"brew.rb", HOMEBREW_LIBRARY_PATH/"brew.rb",
"info", "info",
"--json=v1", "--json=v1",
name name,
) )
return unless $CHILD_STATUS.success? return unless $CHILD_STATUS.success?

View File

@ -94,7 +94,6 @@ class FormulaInstaller
@options = options @options = options
@requirement_messages = [] @requirement_messages = []
@poured_bottle = false @poured_bottle = false
@pour_failed = false
@start_time = nil @start_time = nil
end end
@ -154,8 +153,6 @@ class FormulaInstaller
sig { params(output_warning: T::Boolean).returns(T::Boolean) } sig { params(output_warning: T::Boolean).returns(T::Boolean) }
def pour_bottle?(output_warning: false) def pour_bottle?(output_warning: false)
return false if @pour_failed
return false if !formula.bottle_tag? && !formula.local_bottle_path return false if !formula.bottle_tag? && !formula.local_bottle_path
return true if force_bottle? return true if force_bottle?
return false if build_from_source? || build_bottle? || interactive? return false if build_from_source? || build_bottle? || interactive?
@ -230,13 +227,12 @@ class FormulaInstaller
raise CannotInstallFormulaError, "--force-bottle passed but #{formula.full_name} has no bottle!" raise CannotInstallFormulaError, "--force-bottle passed but #{formula.full_name} has no bottle!"
end end
if Homebrew.default_prefix? && !Homebrew::EnvConfig.developer? && if Homebrew.default_prefix? &&
# TODO: re-enable this on Linux when we merge linuxbrew-core into # TODO: re-enable this on Linux when we merge linuxbrew-core into
# homebrew-core and have full bottle coverage. # homebrew-core and have full bottle coverage.
(OS.mac? || ENV["CI"]) && (OS.mac? || ENV["CI"]) &&
!build_from_source? && !build_bottle? && !build_from_source? && !build_bottle? && !formula.head? &&
!installed_as_dependency? && formula.tap&.core_tap? && !formula.bottle_unneeded? &&
formula.tap&.core_tap? && !formula.bottle_unneeded? && !formula.any_version_installed? &&
# Integration tests override homebrew-core locations # Integration tests override homebrew-core locations
ENV["HOMEBREW_TEST_TMPDIR"].nil? && ENV["HOMEBREW_TEST_TMPDIR"].nil? &&
!pour_bottle? !pour_bottle?
@ -244,21 +240,31 @@ class FormulaInstaller
formula_message = formula.pour_bottle_check_unsatisfied_reason formula_message = formula.pour_bottle_check_unsatisfied_reason
formula_message[0] = formula_message[0].downcase formula_message[0] = formula_message[0].downcase
"#{formula}: #{formula_message}" <<~EOS
else #{formula}: #{formula_message}
EOS
# don't want to complain about no bottle available if doing an
# upgrade/reinstall/dependency install (but do in the case the bottle
# check fails)
elsif !Homebrew::EnvConfig.developer? &&
(!installed_as_dependency? || !formula.any_version_installed?) &&
(!OS.mac? || !OS::Mac.outdated_release?)
<<~EOS <<~EOS
#{formula}: no bottle available! #{formula}: no bottle available!
EOS EOS
end end
message += <<~EOS
You can try to install from source with: if message
brew install --build-from-source #{formula} message += <<~EOS
Please note building from source is unsupported. You will encounter build You can try to install from source with:
failures with some formulae. If you experience any issues please create pull brew install --build-from-source #{formula}
requests instead of asking for help on Homebrew's GitHub, Twitter or any other Please note building from source is unsupported. You will encounter build
official channels. failures with some formulae. If you experience any issues please create pull
EOS requests instead of asking for help on Homebrew's GitHub, Twitter or any other
raise CannotInstallFormulaError, message official channels.
EOS
raise CannotInstallFormulaError, message
end
end end
type, reason = DeprecateDisable.deprecate_disable_info formula type, reason = DeprecateDisable.deprecate_disable_info formula
@ -282,49 +288,51 @@ class FormulaInstaller
return if ignore_deps? return if ignore_deps?
recursive_deps = formula.recursive_dependencies if Homebrew::EnvConfig.developer?
recursive_formulae = recursive_deps.map(&:to_formula) # `recursive_dependencies` trims cyclic dependencies, so we do one level and take the recursive deps of that.
# Mapping direct dependencies to deeper dependencies in a hash is also useful for the cyclic output below.
recursive_dep_map = formula.deps.to_h { |dep| [dep, dep.to_formula.recursive_dependencies] }
recursive_dependencies = [] cyclic_dependencies = []
invalid_arch_dependencies = [] recursive_dep_map.each do |dep, recursive_deps|
recursive_formulae.each do |dep| if [formula.name, formula.full_name].include?(dep.name)
dep_recursive_dependencies = dep.recursive_dependencies.map(&:to_s) cyclic_dependencies << "#{formula.full_name} depends on itself directly"
if dep_recursive_dependencies.include?(formula.name) elsif recursive_deps.any? { |rdep| [formula.name, formula.full_name].include?(rdep.name) }
recursive_dependencies << "#{formula.full_name} depends on #{dep.full_name}" cyclic_dependencies << "#{formula.full_name} depends on itself via #{dep.name}"
recursive_dependencies << "#{dep.full_name} depends on #{formula.full_name}" end
end end
if (tab = Tab.for_formula(dep)) && tab.arch.present? && tab.arch.to_s != Hardware::CPU.arch.to_s if cyclic_dependencies.present?
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself:
#{cyclic_dependencies.join("\n ")}
EOS
end
# Merge into one list
recursive_deps = recursive_dep_map.flat_map { |dep, rdeps| [dep] + rdeps }
Dependency.merge_repeats(recursive_deps)
else
recursive_deps = formula.recursive_dependencies
end
invalid_arch_dependencies = []
pinned_unsatisfied_deps = []
recursive_deps.each do |dep|
if (tab = Tab.for_formula(dep.to_formula)) && tab.arch.present? && tab.arch.to_s != Hardware::CPU.arch.to_s
invalid_arch_dependencies << "#{dep} was built for #{tab.arch}" invalid_arch_dependencies << "#{dep} was built for #{tab.arch}"
end end
pinned_unsatisfied_deps << dep if dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))
end end
unless recursive_dependencies.empty? if invalid_arch_dependencies.present?
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself:
#{recursive_dependencies.join("\n ")}
EOS
end
if recursive_formulae.flat_map(&:recursive_dependencies)
.map(&:to_s)
.include?(formula.name)
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself!
EOS
end
unless invalid_arch_dependencies.empty?
raise CannotInstallFormulaError, <<~EOS raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} dependencies not built for the #{Hardware::CPU.arch} CPU architecture: #{formula.full_name} dependencies not built for the #{Hardware::CPU.arch} CPU architecture:
#{invalid_arch_dependencies.join("\n ")} #{invalid_arch_dependencies.join("\n ")}
EOS EOS
end end
pinned_unsatisfied_deps = recursive_deps.select do |dep|
dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))
end
return if pinned_unsatisfied_deps.empty? return if pinned_unsatisfied_deps.empty?
raise CannotInstallFormulaError, raise CannotInstallFormulaError,
@ -429,7 +437,7 @@ class FormulaInstaller
if pour_bottle? if pour_bottle?
begin begin
pour pour
rescue Exception => e # rubocop:disable Lint/RescueException rescue Exception # rubocop:disable Lint/RescueException
# any exceptions must leave us with nothing installed # any exceptions must leave us with nothing installed
ignore_interrupts do ignore_interrupts do
begin begin
@ -442,17 +450,7 @@ class FormulaInstaller
end end
formula.rack.rmdir_if_possible formula.rack.rmdir_if_possible
end end
raise if Homebrew::EnvConfig.developer? || raise
Homebrew::EnvConfig.no_bottle_source_fallback? ||
force_bottle? ||
e.is_a?(Interrupt)
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
raise UnbottledError, [formula] unless DevelopmentTools.installed?
compute_and_install_dependencies unless ignore_deps?
else else
@poured_bottle = true @poured_bottle = true
end end
@ -513,7 +511,7 @@ class FormulaInstaller
$stderr.puts "Please report this issue to the #{formula.tap} tap (not Homebrew/brew or Homebrew/core)!" $stderr.puts "Please report this issue to the #{formula.tap} tap (not Homebrew/brew or Homebrew/core)!"
false false
else # rubocop:disable Layout/ElseAlignment else
f.linked_keg.exist? && f.opt_prefix.exist? f.linked_keg.exist? && f.opt_prefix.exist?
end end
@ -523,9 +521,11 @@ class FormulaInstaller
# Compute and collect the dependencies needed by the formula currently # Compute and collect the dependencies needed by the formula currently
# being installed. # being installed.
def compute_dependencies def compute_dependencies
req_map, req_deps = expand_requirements @compute_dependencies ||= begin
check_requirements(req_map) req_map, req_deps = expand_requirements
expand_dependencies(req_deps + formula.deps) check_requirements(req_map)
expand_dependencies(req_deps + formula.deps)
end
end end
def unbottled_dependencies(deps) def unbottled_dependencies(deps)
@ -575,7 +575,7 @@ class FormulaInstaller
formula_deps_map = Dependency.expand(formula) formula_deps_map = Dependency.expand(formula)
.index_by(&:name) .index_by(&:name)
while f = formulae.pop while (f = formulae.pop)
runtime_requirements = runtime_requirements(f) runtime_requirements = runtime_requirements(f)
f.recursive_requirements do |dependent, req| f.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent) build = effective_build_options_for(dependent)
@ -707,16 +707,17 @@ class FormulaInstaller
quiet: quiet?, quiet: quiet?,
verbose: verbose?, verbose: verbose?,
) )
fi.prelude
fi.fetch fi.fetch
end end
sig { params(dep: Dependency, inherited_options: Options).void } sig { params(dep: Dependency, inherited_options: Options).void }
def install_dependency(dep, inherited_options) def install_dependency(dep, inherited_options)
df = dep.to_formula df = dep.to_formula
tab = Tab.for_formula(df)
if df.linked_keg.directory? if df.linked_keg.directory?
linked_keg = Keg.new(df.linked_keg.resolved_path) linked_keg = Keg.new(df.linked_keg.resolved_path)
tab = Tab.for_keg(linked_keg)
keg_had_linked_keg = true keg_had_linked_keg = true
keg_was_linked = linked_keg.linked? keg_was_linked = linked_keg.linked?
linked_keg.unlink linked_keg.unlink
@ -724,12 +725,13 @@ class FormulaInstaller
if df.latest_version_installed? if df.latest_version_installed?
installed_keg = Keg.new(df.prefix) installed_keg = Keg.new(df.prefix)
tab ||= Tab.for_keg(installed_keg)
tmp_keg = Pathname.new("#{installed_keg}.tmp") tmp_keg = Pathname.new("#{installed_keg}.tmp")
installed_keg.rename(tmp_keg) installed_keg.rename(tmp_keg)
end end
tab_tap = tab.source["tap"] if df.tap.present? && tab.present? && (tab_tap = tab.source["tap"].presence) &&
if tab_tap.present? && df.tap.present? && df.tap.to_s != tab_tap.to_s df.tap.to_s != tab_tap.to_s
odie <<~EOS odie <<~EOS
#{df} is already installed from #{tab_tap}! #{df} is already installed from #{tab_tap}!
Please `brew uninstall #{df}` first." Please `brew uninstall #{df}` first."
@ -737,7 +739,7 @@ class FormulaInstaller
end end
options = Options.new options = Options.new
options |= tab.used_options options |= tab.used_options if tab.present?
options |= Tab.remap_deprecated_options(df.deprecated_options, dep.options) options |= Tab.remap_deprecated_options(df.deprecated_options, dep.options)
options |= inherited_options options |= inherited_options
options &= df.options options &= df.options
@ -748,7 +750,7 @@ class FormulaInstaller
options: options, options: options,
link_keg: keg_had_linked_keg ? keg_was_linked : nil, link_keg: keg_had_linked_keg ? keg_was_linked : nil,
installed_as_dependency: true, installed_as_dependency: true,
installed_on_request: df.any_version_installed? && tab.installed_on_request, installed_on_request: df.any_version_installed? && tab.present? && tab.installed_on_request,
force_bottle: false, force_bottle: false,
include_test_formulae: @include_test_formulae, include_test_formulae: @include_test_formulae,
build_from_source_formulae: @build_from_source_formulae, build_from_source_formulae: @build_from_source_formulae,
@ -759,7 +761,6 @@ class FormulaInstaller
verbose: verbose?, verbose: verbose?,
}, },
) )
fi.prelude
oh1 "Installing #{formula.full_name} dependency: #{Formatter.identifier(dep.name)}" oh1 "Installing #{formula.full_name} dependency: #{Formatter.identifier(dep.name)}"
fi.install fi.install
fi.finish fi.finish
@ -901,13 +902,12 @@ class FormulaInstaller
# 1. formulae can modify ENV, so we must ensure that each # 1. formulae can modify ENV, so we must ensure that each
# installation has a pristine ENV when it starts, forking now is # installation has a pristine ENV when it starts, forking now is
# the easiest way to do this # the easiest way to do this
args = %W[ args = [
nice #{RUBY_PATH} "nice",
#{ENV["HOMEBREW_RUBY_WARNINGS"]} *HOMEBREW_RUBY_EXEC_ARGS,
-I #{$LOAD_PATH.join(File::PATH_SEPARATOR)} "--",
-- HOMEBREW_LIBRARY_PATH/"build.rb",
#{HOMEBREW_LIBRARY_PATH}/build.rb formula.specified_path,
#{formula.specified_path}
].concat(build_argv) ].concat(build_argv)
Utils.safe_fork do Utils.safe_fork do
@ -1123,25 +1123,10 @@ class FormulaInstaller
return if only_deps? return if only_deps?
if pour_bottle?(output_warning: true) unless pour_bottle?(output_warning: true)
begin formula.fetch_patches
downloader.fetch formula.resources.each(&:fetch)
rescue Exception => e # rubocop:disable Lint/RescueException
raise if Homebrew::EnvConfig.developer? ||
Homebrew::EnvConfig.no_bottle_source_fallback? ||
force_bottle? ||
e.is_a?(Interrupt)
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
fetch_dependencies
end
end end
return if pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
downloader.fetch downloader.fetch
end end
@ -1170,10 +1155,12 @@ class FormulaInstaller
tab = Tab.for_keg(keg) tab = Tab.for_keg(keg)
CxxStdlib.check_compatibility( unless ignore_deps?
formula, formula.recursive_dependencies, CxxStdlib.check_compatibility(
Keg.new(formula.prefix), tab.compiler formula, formula.recursive_dependencies,
) Keg.new(formula.prefix), tab.compiler
)
end
tab.tap = formula.tap tab.tap = formula.tap
tab.poured_from_bottle = true tab.poured_from_bottle = true
@ -1253,10 +1240,9 @@ class FormulaInstaller
end end
return if forbidden_licenses.blank? return if forbidden_licenses.blank?
return if ignore_deps?
compute_dependencies.each do |dep, _| compute_dependencies.each do |dep, _|
next if @ignore_deps
dep_f = dep.to_formula dep_f = dep.to_formula
next unless SPDX.licenses_forbid_installation? dep_f.license, forbidden_licenses next unless SPDX.licenses_forbid_installation? dep_f.license, forbidden_licenses
@ -1265,7 +1251,8 @@ class FormulaInstaller
#{SPDX.license_expression_to_string dep_f.license}. #{SPDX.license_expression_to_string dep_f.license}.
EOS EOS
end end
return if @only_deps
return if only_deps?
return unless SPDX.licenses_forbid_installation? formula.license, forbidden_licenses return unless SPDX.licenses_forbid_installation? formula.license, forbidden_licenses

View File

@ -33,6 +33,19 @@ module Formulary
cache.fetch(path) cache.fetch(path)
end end
def self.clear_cache
cache.each do |key, klass|
next if key == :formulary_factory
namespace = klass.name.deconstantize
next if namespace.deconstantize != name
remove_const(namespace.demodulize)
end
super
end
def self.load_formula(name, path, contents, namespace, flags:) def self.load_formula(name, path, contents, namespace, flags:)
raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!" if Homebrew::EnvConfig.disable_load_formula? raise "Formula loading disabled by HOMEBREW_DISABLE_LOAD_FORMULA!" if Homebrew::EnvConfig.disable_load_formula?
@ -47,6 +60,7 @@ module Formulary
mod.const_set(:BUILD_FLAGS, flags) mod.const_set(:BUILD_FLAGS, flags)
mod.module_eval(contents, path) mod.module_eval(contents, path)
rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError => e rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError => e
remove_const(namespace)
raise FormulaUnreadableError.new(name, e) raise FormulaUnreadableError.new(name, e)
end end
class_name = class_s(name) class_name = class_s(name)
@ -58,6 +72,7 @@ module Formulary
.map { |const_name| mod.const_get(const_name) } .map { |const_name| mod.const_get(const_name) }
.select { |const| const.is_a?(Class) } .select { |const| const.is_a?(Class) }
new_exception = FormulaClassUnavailableError.new(name, path, class_name, class_list) new_exception = FormulaClassUnavailableError.new(name, path, class_name, class_list)
remove_const(namespace)
raise new_exception, "", e.backtrace raise new_exception, "", e.backtrace
end end
end end
@ -467,14 +482,14 @@ module Formulary
return FormulaLoader.new(name, path) return FormulaLoader.new(name, path)
end end
if newref = CoreTap.instance.formula_renames[ref] if (newref = CoreTap.instance.formula_renames[ref])
formula_with_that_oldname = core_path(newref) formula_with_that_oldname = core_path(newref)
return FormulaLoader.new(newref, formula_with_that_oldname) if formula_with_that_oldname.file? return FormulaLoader.new(newref, formula_with_that_oldname) if formula_with_that_oldname.file?
end end
possible_tap_newname_formulae = [] possible_tap_newname_formulae = []
Tap.each do |tap| Tap.each do |tap|
if newref = tap.formula_renames[ref] if (newref = tap.formula_renames[ref])
possible_tap_newname_formulae << "#{tap.name}/#{newref}" possible_tap_newname_formulae << "#{tap.name}/#{newref}"
end end
end end
@ -502,11 +517,11 @@ module Formulary
name = name.to_s.downcase name = name.to_s.downcase
taps.map do |tap| taps.map do |tap|
Pathname.glob([ Pathname.glob([
"#{tap}Formula/#{name}.rb", "#{tap}Formula/#{name}.rb",
"#{tap}HomebrewFormula/#{name}.rb", "#{tap}HomebrewFormula/#{name}.rb",
"#{tap}#{name}.rb", "#{tap}#{name}.rb",
"#{tap}Aliases/#{name}", "#{tap}Aliases/#{name}",
]).find(&:file?) ]).find(&:file?)
end.compact end.compact
end end
end end

View File

@ -0,0 +1,185 @@
# typed: false
# frozen_string_literal: true
require "utils/curl"
require "json"
# GitHub Packages client.
#
# @api private
class GitHubPackages
extend T::Sig
include Context
include Utils::Curl
URL_DOMAIN = "ghcr.io"
URL_PREFIX = "https://#{URL_DOMAIN}/v2/"
URL_REGEX = %r{#{Regexp.escape(URL_PREFIX)}([\w-]+)/([\w-]+)}.freeze
sig { returns(String) }
def inspect
"#<GitHubPackages: org=#{@github_org}>"
end
sig { params(org: T.nilable(String)).void }
def initialize(org: "homebrew")
@github_org = org
raise UsageError, "Must set a GitHub organisation!" unless @github_org
ENV["HOMEBREW_FORCE_HOMEBREW_ON_LINUX"] = "1" if @github_org == "homebrew" && !OS.mac?
end
sig { params(bottles_hash: T::Hash[String, T.untyped]).void }
def upload_bottles(bottles_hash)
user = Homebrew::EnvConfig.github_packages_user
token = Homebrew::EnvConfig.github_packages_token
raise UsageError, "HOMEBREW_GITHUB_PACKAGES_USER is unset." if user.blank?
raise UsageError, "HOMEBREW_GITHUB_PACKAGES_TOKEN is unset." if token.blank?
docker = HOMEBREW_PREFIX/"bin/docker"
unless docker.exist?
ohai "Installing `docker` for upload..."
safe_system HOMEBREW_BREW_FILE, "install", "--formula", "docker"
docker = Formula["docker"].opt_bin/"docker"
end
puts
system_command!(docker, verbose: true, print_stdout: true, input: token, args: [
"login", "--username", user, "--password-stdin", URL_DOMAIN
])
oras = HOMEBREW_PREFIX/"bin/oras"
unless oras.exist?
ohai "Installing `oras` for upload..."
safe_system HOMEBREW_BREW_FILE, "install", "oras"
oras = Formula["oras"].opt_bin/"oras"
end
bottles_hash.each do |formula_name, bottle_hash|
_, org, repo, = *bottle_hash["bottle"]["root_url"].match(URL_REGEX)
# docker CLI insists on lowercase org ("repository name")
org = org.downcase
image = "#{URL_DOMAIN}/#{org}/#{repo}/#{formula_name}"
version = bottle_hash["formula"]["pkg_version"]
rebuild = if (rebuild = bottle_hash["bottle"]["rebuild"]).positive?
".#{rebuild}"
end
formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]
formula = Formulary.factory(formula_path)
image_tags = bottle_hash["bottle"]["tags"].map do |bottle_tag, tag_hash|
local_file = tag_hash["local_filename"]
odebug "Uploading #{local_file}"
tag = "#{version}.#{bottle_tag}#{rebuild}"
tab = Tab.from_file_content(
Utils.safe_popen_read("tar", "xfO", local_file, "#{formula_name}/#{version}/INSTALL_RECEIPT.json"),
"#{local_file}/#{formula_name}/#{version}",
)
created_time = tab.source_modified_time
created_time ||= Time.now
# TODO: ideally most/all of these attributes would be stored in the
# bottle JSON rather than reading them from the formula.
git_revision = formula.tap.git_head
git_path = formula_path.to_s.delete_prefix("#{formula.tap.path}/")
manifest_hash = {
"org.opencontainers.image.title" => formula.full_name,
"org.opencontainers.image.url" => formula.homepage,
"org.opencontainers.image.version" => version,
"org.opencontainers.image.revision" => git_revision,
"org.opencontainers.image.source" => "https://github.com/#{org}/#{repo}/blob/#{git_revision}/#{git_path}",
"org.opencontainers.image.created" => created_time.strftime("%F"),
}
manifest_hash["org.opencontainers.image.description"] = formula.desc if formula.desc.present?
manifest_hash["org.opencontainers.image.license"] = formula.license if formula.license.present?
manifest_annotations = Pathname("#{formula_name}.#{tag}.annotations.json")
manifest_annotations.unlink if manifest_annotations.exist?
manifest_annotations.write({ "$manifest" => manifest_hash }.to_json)
os_version = if tab.built_on.present?
/(\d+\.)*\d+/ =~ tab.built_on["os_version"]
Regexp.last_match(0)
end
# TODO: ideally most/all of these attributes would be stored in the
# bottle JSON rather than reading them from the formula.
os, arch = if @bottle_tag.to_s.end_with?("_linux")
["linux", "amd64"]
else
os = "darwin"
macos_version = MacOS::Version.from_symbol(bottle_tag.to_sym)
os_version ||= macos_version.to_f.to_s
arch = if macos_version.arch == :arm64
"arm64"
else
"amd64"
end
[os, arch]
end
tar_sha256 = Digest::SHA256.hexdigest(
Utils.safe_popen_read("gunzip", "--stdout", "--decompress", local_file),
)
config_hash = {
"architecture" => arch,
"os" => os,
"os.version" => os_version,
"rootfs" => {
"type" => "layers",
"diff_ids" => ["sha256:#{tar_sha256}"],
},
}
manifest_config = Pathname("#{formula_name}.#{tag}.config.json")
manifest_config.unlink if manifest_config.exist?
manifest_config.write(config_hash.to_json)
# TODO: If we push the architecture-specific images to the tag :latest,
# then we don't need to delete the architecture-specific tags.
image_tag = "#{image}:#{tag}"
puts
system_command!(oras, verbose: true, print_stdout: true, args: [
"push", image_tag,
"--verbose",
"--manifest-annotations=#{manifest_annotations}",
"--manifest-config=#{manifest_config}:application/vnd.oci.image.config.v1+json",
"--username", user,
"--password", token,
"#{local_file}:application/vnd.oci.image.layer.v1.tar+gzip"
])
image_tag
end
image_tag = "#{image}:#{version}#{rebuild}"
puts
system_command!(docker, verbose: true, print_stdout: true, args: [
"buildx", "imagetools", "create", "--tag", image_tag, *image_tags
])
# TODO: once the main image metadata is working correctly delete the package using:
# `curl -X DELETE -u $HOMEBREW_GITHUB_PACKAGES_USER:$HOMEBREW_GITHUB_PACKAGES_TOKEN
# https://api.github.com/orgs/Homebrew/packages/container/homebrew-core%2F$PACKAGE/versions/$VERSION`
# Alternatively, if we push the architecture-specific images to the tag :latest,
# then we don't need to delete the architecture-specific tags.
# Alternatively, remove all usage of `docker` here instead.
end
ensure
if docker
puts
system_command!(docker, verbose: true, print_stdout: true, args: [
"logout", URL_DOMAIN
])
end
end
end

View File

@ -0,0 +1,44 @@
# typed: false
# frozen_string_literal: true
require "utils/github"
require "json"
# GitHub Releases client.
#
# @api private
class GitHubReleases
extend T::Sig
include Context
include Utils::Curl
URL_REGEX = %r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze
sig { params(bottles_hash: T::Hash[String, T.untyped]).void }
def upload_bottles(bottles_hash)
bottles_hash.each_value do |bottle_hash|
root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match URL_REGEX
_, user, repo, tag = *url_match
# Ensure a release is created.
release = begin
rel = GitHub.get_release user, repo, tag
odebug "Existing GitHub release \"#{tag}\" found"
rel
rescue GitHub::API::HTTPNotFoundError
odebug "Creating new GitHub release \"#{tag}\""
GitHub.create_or_update_release user, repo, tag
end
# Upload bottles as release assets.
bottle_hash["bottle"]["tags"].each_value do |tag_hash|
remote_file = tag_hash["filename"]
local_file = tag_hash["local_filename"]
odebug "Uploading #{remote_file}"
GitHub.upload_release_asset user, repo, release["id"], local_file: local_file, remote_file: remote_file
end
end
end
end

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