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
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
issue_body: false
inputs:
- type: description
body:
- type: markdown
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.
- type: textarea
attributes:
render: shell
label: "`brew config` output"
validations:
required: true
- type: textarea
attributes:
render: shell
label: "`brew doctor` output"
validations:
required: true
- type: checkboxes
attributes:
description: Please verify that you've followed these steps
choices:
options:
- label: The `brew doctor` above contains no "Warning" lines.
required: true
- type: textarea
attributes:
label: What were you trying to do (and why)?
validations:
required: true
- type: textarea
attributes:
label: What happened (include all command output)?
validations:
required: true
- type: textarea
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
attributes:
render: shell
label: Step-by-step reproduction instructions (by running `brew` commands)
validations:
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).
- name: New issue on Homebrew/homebrew-cask
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
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).

View File

@ -1,27 +1,31 @@
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
issue_body: false
inputs:
- type: description
body:
- type: markdown
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.
- type: textarea
attributes:
label: Provide a detailed description of the proposed feature
validations:
required: true
- type: textarea
attributes:
label: What is the motivation for the feature?
validations:
required: true
- type: textarea
attributes:
label: How will the feature be relevant to at least 90% of Homebrew users?
validations:
required: true
- type: textarea
attributes:
label: What alternatives to the feature have been considered?
validations:
required: true
- type: description
- type: markdown
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.

View File

@ -5,6 +5,5 @@
- [ ] 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 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
schedule:
interval: weekly
allow:
- dependency-type: all
- package-ecosystem: bundler
directory: /Library/Homebrew
schedule:
interval: daily
allow:
- dependency-type: all
ignore:
- dependency-name: sorbet-runtime

View File

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

View File

@ -31,8 +31,6 @@ jobs:
- run: brew style --display-cop-names
- run: brew man --fail-if-changed
- run: brew typecheck
- name: Run vale for docs linting
@ -81,6 +79,58 @@ jobs:
- name: Run brew audit --skip-style on all taps
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:
name: vendored gems (Linux)
runs-on: ubuntu-latest
@ -231,29 +281,17 @@ jobs:
- name: Run brew readall on all taps
run: brew readall --aliases
- name: Run brew style on homebrew-core
run: brew style --display-cop-names homebrew/core
- name: Run brew style on official taps
- name: Run brew audit --skip-style on Cask taps
run: |
brew style --display-cop-names homebrew/bundle \
homebrew/services \
homebrew/test-bot
- 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
brew audit --skip-style --tap=homebrew/cask
brew audit --skip-style --tap=homebrew/cask-drivers
brew audit --skip-style --tap=homebrew/cask-fonts
brew audit --skip-style --tap=homebrew/cask-versions
- name: Install brew tests dependencies
run: |
brew install subversion
Library/Homebrew/shims/scm/svn --homebrew=print-path
brew sh -c "svn --homebrew=print-path"
which svn
which svnadmin

View File

@ -19,7 +19,7 @@ jobs:
steps:
- name: Re-run this workflow
if: github.event_name == 'schedule' || github.event.action == 'closed'
uses: reitermarkus/rerun-workflow@e2647e8885422412d5acbd61f9add5b1e77ad3ab
uses: reitermarkus/rerun-workflow@64cba9e834916060e77b7dad424d086837fdd0a6
with:
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
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 \
fonts-dejavu-core \
g++ \
gawk \
git \
less \
libz-dev \

View File

@ -240,6 +240,12 @@ Layout/SpaceAroundOperators:
Layout/RescueEnsureAlignment:
Enabled: false
# significantly less indentation involved; more consistent
Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
# favour parens-less DSL-style arguments
Lint/AmbiguousBlockAssociation:
Enabled: false
@ -255,14 +261,6 @@ Naming/MemoizedInstanceVariableName:
Exclude:
- "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
Lint/ConstantDefinitionInBlock:
Exclude:
@ -462,6 +460,11 @@ Style/StringLiteralsInInterpolation:
Style/TernaryParentheses:
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
Style/WordArray:
MinSize: 4

View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,6 @@ HOMEBREW_TEMP="${HOMEBREW_TEMP:-${HOMEBREW_DEFAULT_TEMP}}"
# Don't need shellcheck to follow these `source`.
# shellcheck disable=SC1090
case "$*" in
--prefix) echo "$HOMEBREW_PREFIX"; exit 0 ;;
--cellar) echo "$HOMEBREW_CELLAR"; exit 0 ;;
--repository|--repo) echo "$HOMEBREW_REPOSITORY"; 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 ;;
formulae) source "$HOMEBREW_LIBRARY/Homebrew/cmd/formulae.sh"; homebrew-formulae; 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
#####
@ -187,9 +188,9 @@ update-preinstall() {
# last $HOMEBREW_AUTO_UPDATE_SECS.
if [[ "$HOMEBREW_COMMAND" = "cask" ]]
then
tap_fetch_head="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-cask/.git/FETCH_HEAD"
tap_fetch_head="$HOMEBREW_CASK_REPOSITORY/.git/FETCH_HEAD"
else
tap_fetch_head="$HOMEBREW_LIBRARY/Taps/homebrew/homebrew-core/.git/FETCH_HEAD"
tap_fetch_head="$HOMEBREW_CORE_REPOSITORY/.git/FETCH_HEAD"
fi
if [[ -f "$tap_fetch_head" &&
-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"
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" ]]
then
HOMEBREW_PRODUCT="Homebrew"
@ -439,13 +454,6 @@ curl_version_output="$("$HOMEBREW_CURL" --version 2>/dev/null)"
curl_name_and_version="${curl_version_output%% (*}"
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_DEFAULT_CACHE
export HOMEBREW_CACHE
@ -567,16 +575,16 @@ then
# Don't allow non-developers to customise Ruby warnings.
unset HOMEBREW_RUBY_WARNINGS
# Disable Ruby options we don't need. RubyGems provides a decent speedup.
RUBY_DISABLE_OPTIONS="--disable=gems,did_you_mean,rubyopt"
# Disable Ruby options we don't need.
RUBY_DISABLE_OPTIONS="--disable=did_you_mean,rubyopt"
else
# Don't disable did_you_mean for developers as it's useful.
RUBY_DISABLE_OPTIONS="--disable=gems,rubyopt"
RUBY_DISABLE_OPTIONS="--disable=rubyopt"
fi
if [[ -z "$HOMEBREW_RUBY_WARNINGS" ]]
then
export HOMEBREW_RUBY_WARNINGS="-W0"
export HOMEBREW_RUBY_WARNINGS="-W1"
fi
if [[ -z "$HOMEBREW_BOTTLE_DOMAIN" ]]

View File

@ -6,7 +6,7 @@
old_trap = trap("INT") { exit! 130 }
require "global"
require_relative "global"
require "build_options"
require "cxxstdlib"
require "keg"
@ -240,7 +240,14 @@ rescue Exception => e # rubocop:disable Lint/RescueException
error_hash["env"] = e.env
when "ErrorDuringExecution"
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
end

View File

@ -165,7 +165,7 @@ module Cask
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.any? { |k| k.is_a?(Artifact::Uninstall) }
return if cask.artifacts.any?(Artifact::Uninstall)
add_error "installer and pkg stanzas require an uninstall stanza"
end
@ -696,7 +696,7 @@ module Cask
def check_denylist
return unless cask.tap
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}"
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.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
def check_url_for_https_availability(url_to_check, **options)

View File

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

View File

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

View File

@ -39,9 +39,9 @@ module Cask
Pathname.glob(path.join("*")).sort.select(&:directory?).map do |path|
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)
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)
else
CaskLoader.load(token, config: config)

View File

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

View File

@ -181,7 +181,11 @@ module Cask
set_unique_stanza(:url, args.empty? && options.empty? && !block_given?) do
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
URL.new(*args, **options, caller_location: caller_location)
end

View File

@ -103,6 +103,27 @@ module Cask
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.
#
# @api private

View File

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

View File

@ -32,9 +32,7 @@ module Cask
odebug "Deleting pkg files"
@command.run!(
"/usr/bin/xargs",
args: [
"-0", "--", "/bin/rm", "--"
],
args: ["-0", "--", "/bin/rm", "--"],
input: pkgutil_bom_files.join("\0"),
sudo: true,
)
@ -44,9 +42,7 @@ module Cask
odebug "Deleting pkg symlinks and special files"
@command.run!(
"/usr/bin/xargs",
args: [
"-0", "--", "/bin/rm", "--"
],
args: ["-0", "--", "/bin/rm", "--"],
input: pkgutil_bom_specials.join("\0"),
sudo: true,
)
@ -54,19 +50,10 @@ module Cask
unless pkgutil_bom_dirs.empty?
odebug "Deleting pkg directories"
deepest_path_first(pkgutil_bom_dirs).each do |dir|
with_full_permissions(dir) do
clean_broken_symlinks(dir)
clean_ds_store(dir)
rmdir(dir)
end
end
rmdir(deepest_path_first(pkgutil_bom_dirs))
end
if root.directory? && !MacOS.undeletable?(root)
clean_ds_store(root)
rmdir(root)
end
rmdir(root) unless MacOS.undeletable?(root)
forget
end
@ -118,55 +105,51 @@ module Cask
path.symlink? || path.chardev? || path.blockdev?
end
sig { params(path: Pathname).void }
def rmdir(path)
return unless path.children.empty?
# 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
if path.symlink?
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: true)
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
@command.run!("/bin/rmdir", args: ["--", path], sudo: true)
end
end
# Try `rmdir` anyways to show a proper error.
/bin/rmdir "${path}"
fi
done
BASH
private_constant :RMDIR_SH
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
sig { params(path: T.any(Pathname, T::Array[Pathname])).void }
def rmdir(path)
@command.run!(
"/usr/bin/xargs",
args: ["-0", "--", "/bin/bash", "-c", RMDIR_SH, "--"],
input: Array(path).join("\0"),
sudo: true,
)
end
sig { params(paths: T::Array[Pathname]).returns(T::Array[Pathname]) }
def deepest_path_first(paths)
paths.sort_by { |path| -path.to_s.split(File::SEPARATOR).count }
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

View File

@ -3,7 +3,6 @@
require "utils/bottles"
require "utils/gems"
require "formula"
require "cask/cask_loader"
require "set"
@ -80,7 +79,7 @@ module Homebrew
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
Formulary.from_rack(HOMEBREW_CELLAR/formula_name)
@ -95,7 +94,7 @@ module Homebrew
if resource_name == "patch"
patch_hashes = formula.stable&.patches&.select(&:external?)&.map(&:resource)&.map(&:version)
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
elsif version.is_a?(PkgVersion)
return true if formula.pkg_version > version
@ -111,7 +110,7 @@ module Homebrew
end
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::CaskLoader.load(name)

View File

@ -132,6 +132,12 @@ module Homebrew
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
end
private :load_formula_or_cask
@ -265,13 +271,10 @@ module Homebrew
opt_prefix = HOMEBREW_PREFIX/"opt/#{rack.basename}"
begin
if opt_prefix.symlink? && opt_prefix.directory?
Keg.new(opt_prefix.resolved_path)
elsif linked_keg_ref.symlink? && linked_keg_ref.directory?
Keg.new(linked_keg_ref.resolved_path)
elsif dirs.length == 1
Keg.new(dirs.first)
else
return Keg.new(opt_prefix.resolved_path) if opt_prefix.symlink? && opt_prefix.directory?
return Keg.new(linked_keg_ref.resolved_path) if linked_keg_ref.symlink? && linked_keg_ref.directory?
return Keg.new(dirs.first) if dirs.length == 1
f = if name.include?("/") || File.exist?(name)
Formulary.factory(name)
else
@ -286,7 +289,6 @@ module Homebrew
end
Keg.new(prefix)
end
rescue FormulaUnavailableError
raise MultipleVersionsInstalledError, <<~EOS
Multiple kegs installed to #{rack}

View File

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

View File

@ -18,8 +18,7 @@ module Homebrew
- macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}`
- Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}`
If <formula> is provided, display the location in the Cellar where <formula>
is or would be installed.
If <formula> is provided, display the location where <formula> is or would be installed.
EOS
switch "--unbrewed",
description: "List files in Homebrew's prefix not installed by Homebrew."
@ -45,13 +44,10 @@ module Homebrew
else
formulae = args.named.to_resolved_formulae
prefixes = formulae.map do |f|
if f.opt_prefix.exist?
next nil if args.installed? && !f.opt_prefix.exist?
# this case wil be short-circuited by brew.sh logic for a single formula
f.opt_prefix
elsif args.installed?
nil
else
f.latest_installed_prefix
end
end.compact
puts prefixes
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 }
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
descr = if f.core_formula?
@ -63,9 +63,9 @@ module Homebrew
else
"#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs"
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
end
@ -85,9 +85,9 @@ module Homebrew
# Causes some terminals to display secure password entry indicators.
def noecho_gets
system "stty -echo"
system "stty", "-echo"
result = $stdin.gets
system "stty echo"
system "stty", "echo"
puts
result
end
@ -108,20 +108,6 @@ module Homebrew
logs
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
args = gist_logs_args.parse

View File

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

View File

@ -264,7 +264,7 @@ module Homebrew
end
end
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 = if !old_formula.linked? && !old_formula.keg_only?
<<~EOS

View File

@ -51,7 +51,7 @@ module Homebrew
def git_log(cd_dir, path = nil, tap = nil, args:)
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
name = tap.to_s
git_cd = "$(brew --repo #{tap})"

View File

@ -73,7 +73,7 @@ module Homebrew
def search
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
exec_browser url.call(URI.encode_www_form_component(args.named.join(" ")))
return

View File

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

View File

@ -129,15 +129,40 @@ module Homebrew
if hub.empty?
puts_stdout_or_stderr "No changes to formulae." unless args.quiet?
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 { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) }
CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db)
.update_from_report!(hub)
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
puts if args.preinstall?
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
puts_stdout_or_stderr if args.preinstall?
elsif !args.preinstall? && !ENV["HOMEBREW_UPDATE_FAILED"]
puts_stdout_or_stderr "Already up-to-date." unless args.quiet?
end
@ -161,6 +186,7 @@ module Homebrew
return if new_repository_version.blank?
puts_stdout_or_stderr
ohai_stdout_or_stderr "Homebrew was updated to version #{new_repository_version}"
if new_repository_version.split(".").last == "0"
puts_stdout_or_stderr <<~EOS

View File

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

View File

@ -175,7 +175,7 @@ module Commands
path = self.path(command)
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|
[long || short, desc]
end
@ -198,7 +198,7 @@ module Commands
path = self.path(command)
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
cmd_parser.description.split(".").first
else

View File

@ -129,8 +129,6 @@ module Homebrew
sig { params(command: String).returns(T::Boolean) }
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?
end
@ -167,7 +165,7 @@ module Homebrew
return unless command_gets_completions? command
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_types.each do |type|
@ -215,29 +213,51 @@ module Homebrew
def generate_zsh_subcommand_completion(command)
return unless command_gets_completions? command
options = command_options(command).sort.map do |opt, desc|
next opt if desc.blank?
options = command_options(command)
conflicts = generate_zsh_option_exclusions(command, opt)
"#{conflicts}#{opt}[#{format_description desc}]"
end
if types = Commands.named_args_type(command)
args_options = []
if (types = Commands.named_args_type(command))
named_args_strings, named_args_types = types.partition { |type| type.is_a? String }
named_args_types.each do |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 << "::subcommand:(#{named_args_strings.join(" ")})" if named_args_strings.any?
options.delete(opt)
end
args_options << "*::#{type}:#{ZSH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING[type]}"
end
if named_args_strings.any?
args_options << "- subcommand"
args_options << "*::subcommand:(#{named_args_strings.join(" ")})"
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
# brew #{command}
_brew_#{Commands.method_name command}() {
_arguments \\
#{options.map! { |opt| "'#{opt}'" }.join(" \\\n ")}
#{options.map! { |opt| opt.start_with?("- ") ? opt : "'#{opt}'" }.join(" \\\n ")}
}
COMPLETION
end
@ -291,7 +311,7 @@ module Homebrew
subcommands = []
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_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.realpath
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
def self.current
if current_context = Thread.current[:context]
if (current_context = Thread.current[:context])
return current_context
end

View File

@ -52,7 +52,7 @@ class Descriptions
private
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
def short_name_counts

View File

@ -254,7 +254,7 @@ module Homebrew
def bottle_formula(f, args:)
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?
tap = CoreTap.instance

View File

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

View File

@ -103,7 +103,7 @@ module Homebrew
end
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"
safe_system "git", "remote", "add", homebrew_core_remote, homebrew_core_url
end
@ -193,7 +193,7 @@ module Homebrew
end
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)
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
elsif new_revision.blank?
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.
Also displays whether a pull request has been opened with the URL.
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=",
description: "Limit number of package results returned."
named_args :formula
conflicts "--cask", "--formula"
named_args [:formula, :cask]
end
end
def bump
args = bump_args.parse
requested_formulae = args.named.to_formulae.presence
requested_limit = args.limit.to_i if args.limit.present?
if args.limit.present? && !args.formula? && !args.cask?
raise UsageError, "`--limit` must be used with either `--formula` or `--cask`."
end
if requested_formulae
Livecheck.load_other_tap_strategies(requested_formulae)
formulae_and_casks = if args.formula?
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?
if formula.head_only?
ohai formula.name
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.formula_or_cask_name(formula_or_cask, full_name: use_full_name)
repository = if formula_or_cask.is_a?(Formula)
if formula_or_cask.head_only?
ohai name
puts "Formula is HEAD-only."
next
end
package_data = Repology.single_package_query(formula.name)
retrieve_and_display_info(formula, package_data&.values&.first)
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
package_data = Repology.single_package_query(name, repository: repository)
retrieve_and_display_info(
formula_or_cask,
name,
package_data&.values&.first,
args: args,
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
end
else
outdated_packages = Repology.parse_api_response(requested_limit)
outdated_packages.each_with_index do |(_name, repositories), i|
puts if i.positive?
api_response = {}
unless args.cask?
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
api_response.each do |package_type, outdated_packages|
repository = if package_type == :formulae
Repology::HOMEBREW_CORE
else
Repology::HOMEBREW_CASK
end
outdated_packages.each_with_index do |(_name, repositories), i|
homebrew_repo = repositories.find do |repo|
repo["repo"] == "homebrew"
repo["repo"] == repository
end
next if homebrew_repo.blank?
formula = begin
formula_or_cask = begin
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
retrieve_and_display_info(formula, repositories)
puts if i.positive?
retrieve_and_display_info(formula_or_cask, name, repositories, args: args, ambiguous_cask: ambiguous_cask)
break if requested_limit && i >= requested_limit
break if limit && i >= limit
end
end
end
end
def livecheck_result(formula)
skip_result = Livecheck::SkipConditions.skip_information(formula)
def livecheck_result(formula_or_cask)
skip_result = Livecheck::SkipConditions.skip_information(formula_or_cask)
if skip_result.present?
return "#{skip_result[:status]}#{" - #{skip_result[:messages].join(", ")}" if skip_result[:messages].present?}"
end
version_info = Livecheck.latest_version(
formula,
formula_or_cask,
json: true, full_name: false, verbose: false, debug: false,
)
latest = version_info[:latest] if version_info.present?
@ -83,10 +165,12 @@ module Homebrew
return "unable to get versions" if latest.blank?
latest.to_s
rescue => e
"error: #{e}"
end
def retrieve_pull_requests(formula)
pull_requests = GitHub.fetch_pull_requests(formula.name, formula.tap&.full_name, state: "open")
def retrieve_pull_requests(formula_or_cask, name)
pull_requests = GitHub.fetch_pull_requests(name, formula_or_cask.tap&.full_name, state: "open")
if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
@ -96,8 +180,12 @@ module Homebrew
pull_requests
end
def retrieve_and_display_info(formula, repositories)
current_version = formula.stable.version.to_s
def retrieve_and_display_info(formula_or_cask, name, repositories, args:, ambiguous_cask: false)
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_version(repositories)
@ -105,14 +193,15 @@ module Homebrew
"not found"
end
livecheck_latest = livecheck_result(formula)
pull_requests = retrieve_pull_requests(formula)
livecheck_latest = livecheck_result(formula_or_cask)
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 &&
current_version == livecheck_latest
"#{formula} is up to date!"
"#{name} is up to date!"
else
formula.name
name
end
ohai title
@ -120,7 +209,7 @@ module Homebrew
Current formula version: #{current_version}
Latest Repology version: #{repology_latest}
Latest livecheck version: #{livecheck_latest}
Open pull requests: #{pull_requests}
EOS
puts "Open pull requests: #{pull_requests}" unless args.no_pull_requests?
end
end

View File

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

View File

@ -25,7 +25,10 @@ module Homebrew
description: "Dispatch specified workflow (default: `dispatch-build-bottle.yml`)."
switch "--upload",
description: "Upload built bottles to Bintray."
switch "--linux",
description: "Dispatch bottle for Linux (using GitHub runners)."
conflicts "--macos", "--linux"
named_args :formula, min: 1
end
end
@ -33,18 +36,26 @@ module Homebrew
def dispatch_build_bottle
args = dispatch_build_bottle_args.parse
tap = Tap.fetch(args.tap || CoreTap.instance.name)
user, repo = tap.full_name.split("/")
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.
args.macos&.gsub!(/^11-arm$/, "11-arm64")
macos.gsub!(/^11-arm$/, "11-arm64")
macos = args.macos&.yield_self do |s|
macos = 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.
@ -53,25 +64,27 @@ module Homebrew
macos.to_s
end
tap = Tap.fetch(args.tap || CoreTap.instance.name)
user, repo = tap.full_name.split("/")
workflow = args.workflow || "dispatch-build-bottle.yml"
ref = "master"
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|
# Required inputs
inputs = {
formula: formula.name,
macos: macos_label,
}
# Optional inputs
# These cannot be passed as nil to GitHub API
inputs[:macos] = macos_label if args.macos
inputs[:issue] = args.issue if args.issue
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)
end
end

View File

@ -61,8 +61,7 @@ module Homebrew
puts ENV["HOMEBREW_LIVECHECK_WATCHLIST"] if ENV["HOMEBREW_LIVECHECK_WATCHLIST"].present?
end
formulae_and_casks_to_check =
if args.tap
formulae_and_casks_to_check = if args.tap
tap = Tap.fetch(args.tap)
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) }
@ -96,7 +95,8 @@ module Homebrew
end
else
raise UsageError, "A watchlist file is required when no arguments are given."
end&.sort_by do |formula_or_cask|
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
@ -105,6 +105,7 @@ module Homebrew
options = {
json: args.json?,
full_name: args.full_name?,
handle_name_conflict: !args.formula? && !args.cask?,
newer_only: args.newer_only?,
quiet: args.quiet?,
debug: args.debug?,

View File

@ -24,9 +24,9 @@ module Homebrew
*Note:* Not (yet) working on Apple Silicon.
EOS
switch "--fail-if-changed",
description: "Return a failing status code if changes are detected in the manpage outputs. This "\
"can be used to notify CI when the manpages are out of date. Additionally, "\
switch "--fail-if-not-changed",
description: "Return a failing status code if no changes are detected in the manpage outputs. "\
"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 "\
"comparison without factoring in the date)."
named_args :none
@ -42,19 +42,17 @@ module Homebrew
args = man_args.parse
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!
diff = system_command "git", args: [
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
]
if diff.status.success?
return unless diff.status.success?
puts "No changes to manpage or completions output detected."
elsif args.fail_if_changed?
puts "Changes to manpage or completions detected:"
puts diff.stdout
Homebrew.failed = true
end
Homebrew.failed = true if args.fail_if_not_changed?
end
def regenerate_man_pages(preserve_date:, quiet:)
@ -62,6 +60,7 @@ module Homebrew
markup = build_man_page(quiet: quiet)
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)
end
@ -164,7 +163,7 @@ module Homebrew
# preserve existing manpage order
cmd_paths.sort_by(&method(:sort_key_for_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
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."
flag "--artifact=",
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=",
description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--tap=",
@ -65,6 +67,7 @@ module Homebrew
description: "Comma-separated list of workflows which can be ignored if they have not been run."
conflicts "--clean", "--autosquash"
conflicts "--archive-item", "--bintray-org"
named_args :pull_request, min: 1
end
@ -337,9 +340,9 @@ module Homebrew
end
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}"]
# 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"]
artifact = args.artifact || "bottles"
archive_item = args.archive_item
bintray_org = args.bintray_org || "homebrew"
mirror_repo = args.bintray_mirror || "mirror"
tap = Tap.fetch(args.tap || CoreTap.instance.name)
@ -424,7 +428,11 @@ module Homebrew
upload_args << "--keep-old" if args.keep_old?
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 << "--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
end
end

View File

@ -2,7 +2,10 @@
# frozen_string_literal: true
require "cli/parser"
require "archive"
require "bintray"
require "github_packages"
require "github_releases"
module Homebrew
extend T::Sig
@ -13,7 +16,7 @@ module Homebrew
def pr_upload_args
Homebrew::CLI::Parser.new do
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
switch "--no-publish",
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",
description: "Warn instead of raising an error if the bottle upload fails. "\
"Useful for repairing bottle uploads that previously failed."
flag "--archive-item=",
description: "Upload to the specified Internet Archive item (default: `homebrew`)."
flag "--bintray-org=",
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=",
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
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)
@github_releases ||= bottles_hash.values.all? do |bottle_hash|
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
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
args = pr_upload_args.parse
@ -76,10 +101,17 @@ module Homebrew
bottle_args += json_files
if args.dry_run?
service = if github_releases?(bottles_hash)
"GitHub Releases"
else
service =
if internet_archive?(bottles_hash)
"Internet Archive"
elsif bintray?(bottles_hash)
"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
brew #{bottle_args.join " "}
@ -102,38 +134,26 @@ module Homebrew
safe_system HOMEBREW_BREW_FILE, *audit_args
end
if github_releases?(bottles_hash)
# Handle uploading to GitHub Releases.
bottles_hash.each_value do |bottle_hash|
root_url = bottle_hash["bottle"]["root_url"]
url_match = root_url.match HOMEBREW_RELEASES_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::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.
if internet_archive?(bottles_hash)
archive_item = args.archive_item || "homebrew"
archive = Archive.new(item: archive_item)
archive.upload_bottles(bottles_hash,
warn_on_error: args.warn_on_upload_failure?)
elsif bintray?(bottles_hash)
bintray_org = args.bintray_org || "homebrew"
bintray = Bintray.new(org: bintray_org)
bintray.upload_bottles(bottles_hash,
publish_package: !args.no_publish?,
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

View File

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

View File

@ -39,7 +39,7 @@ module Homebrew
begin
latest_release = GitHub.get_latest_release "Homebrew", "brew"
rescue GitHub::HTTPNotFoundError
rescue GitHub::API::HTTPNotFoundError
odie "No existing releases found!"
end
latest_version = Version.new latest_release["tag_name"]
@ -48,7 +48,7 @@ module Homebrew
one_month_ago = Date.today << 1
latest_major_minor_release = begin
GitHub.get_release "Homebrew", "brew", "#{latest_version.major_minor}.0"
rescue GitHub::HTTPNotFoundError
rescue GitHub::API::HTTPNotFoundError
nil
end
@ -89,7 +89,7 @@ module Homebrew
begin
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}!"
end

View File

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

View File

@ -9,55 +9,68 @@ module Homebrew
module_function
NAMED_TIER_AMOUNT = 100
URL_TIER_AMOUNT = 1000
sig { returns(CLI::Parser) }
def sponsors_args
Homebrew::CLI::Parser.new do
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
named_args :none
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
sponsors_args.parse
sponsors = {
"named" => [],
"users" => 0,
"orgs" => 0,
}
named_sponsors = []
logo_sponsors = []
GitHub.sponsors_by_tier("Homebrew").each do |tier|
sponsors["named"] += tier["sponsors"] if tier["tier"] >= 100
sponsors["users"] += tier["count"]
sponsors["orgs"] += tier["sponsors"].count { |s| s["type"] == "organization" }
if tier["tier"] >= NAMED_TIER_AMOUNT
named_sponsors += tier["sponsors"].map do |s|
"[#{sponsor_name(s)}](#{sponsor_url(s)})"
end
end
items = []
items += sponsors["named"].map { |s| "[#{s["name"]}](https://github.com/#{s["login"]})" }
next if tier["tier"] < URL_TIER_AMOUNT
anon_users = sponsors["users"] - sponsors["named"].length - sponsors["orgs"]
logo_sponsors += tier["sponsors"].map do |s|
"[![#{sponsor_name(s)}](#{sponsor_logo(s)})](#{sponsor_url(s)})"
end
end
items << if items.length > 1
"#{anon_users} other users"
named_sponsors << "many other users and organisations via [GitHub Sponsors](https://github.com/sponsors/Homebrew)"
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
File.open(readme, "w+") { |f| f.write(content) }
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
"#{anon_users} users"
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)."
puts "List of sponsors updated in the README."
end
end
end

View File

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

View File

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

View File

@ -78,7 +78,7 @@ module Homebrew
elsif args.dependents?
formulae = all_formulae = Formula.to_a
@sort = " (sorted by installs in the last 90 days)"
@sort = " (sorted by number of dependents)"
else
formula_installs = {}
@ -103,7 +103,7 @@ module Homebrew
nil
end
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
end
@ -154,20 +154,51 @@ module Homebrew
formulae.each do |f|
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
next
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 requirement_classes.include?(MacOSRequirement)
if requirements.any?(MacOSRequirement)
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires macOS" if any_named_args
next
end
elsif requirement_classes.include?(LinuxRequirement)
elsif requirements.any?(LinuxRequirement)
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires Linux" if any_named_args
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
if f.bottle_unneeded? || f.bottle_disabled?
@ -181,7 +212,7 @@ module Homebrew
end
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
if deps.blank?

View File

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

View File

@ -3,6 +3,7 @@
require "cli/parser"
require "utils/github"
require "dev-cmd/man"
module Homebrew
extend T::Sig
@ -61,7 +62,8 @@ module Homebrew
if diff.status.success?
puts "No changes to list of maintainers."
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

View File

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

View File

@ -43,7 +43,7 @@ class DevelopmentTools
def clang_version
@clang_version ||= begin
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
else
Version::NULL
@ -54,7 +54,7 @@ class DevelopmentTools
def clang_build_version
@clang_build_version ||= begin
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
else
Version::NULL
@ -66,7 +66,7 @@ class DevelopmentTools
@llvm_clang_build_version ||= begin
path = Formulary.factory("llvm").opt_prefix/"bin/clang"
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
else
Version::NULL
@ -76,10 +76,10 @@ class DevelopmentTools
def non_apple_gcc_version(cc)
(@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?
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
else
Version::NULL

View File

@ -13,6 +13,11 @@ require "mechanize/http/content_disposition_parser"
require "utils/curl"
require "github_packages"
require "extend/time"
using TimeRemaining
# @abstract Abstract superclass for all download strategies.
#
# @api private
@ -50,7 +55,7 @@ class AbstractDownloadStrategy
# Download and cache the resource at {#cached_location}.
#
# @api public
def fetch; end
def fetch(timeout: nil); end
# Disable any output during downloading.
#
@ -66,30 +71,38 @@ class AbstractDownloadStrategy
Context.current.quiet? || @quiet
end
# Unpack {#cached_location} into the current working directory, and possibly
# chdir into the newly-unpacked directory.
# Unlike {Resource#stage}, this does not take a block.
# Unpack {#cached_location} into the current working directory.
#
# 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
def stage
def stage(&block)
UnpackStrategy.detect(cached_location,
prioritise_extension: true,
ref_type: @ref_type, ref: @ref)
.extract_nestedly(basename: basename,
prioritise_extension: true,
verbose: verbose? && !quiet?)
chdir
chdir(&block) if block
end
def chdir
def chdir(&block)
entries = Dir["*"]
raise "Empty archive" if entries.length.zero?
return if entries.length != 1
begin
Dir.chdir entries.first
rescue
nil
if entries.length != 1
yield
return
end
if File.directory? entries.first
Dir.chdir(entries.first, &block)
else
yield
end
end
private :chdir
@ -166,18 +179,20 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
# Download and cache the repository at {#cached_location}.
#
# @api public
def fetch
def fetch(timeout: nil)
end_time = Time.now + timeout if timeout
ohai "Cloning #{url}"
if cached_location.exist? && repo_valid?
puts "Updating #{cached_location}"
update
update(timeout: timeout)
elsif cached_location.exist?
puts "Removing invalid repository from cache"
clear_cache
clone_repo
clone_repo(timeout: end_time)
else
clone_repo
clone_repo(timeout: end_time)
end
version.update_commit(last_commit) if head?
@ -222,9 +237,11 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
raise NotImplementedError
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
@ -303,7 +320,7 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
query_params = CGI.parse(uri.query)
query_params["response-content-disposition"].each do |param|
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
@ -343,7 +360,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
# Download and cache the file at {#cached_location}.
#
# @api public
def fetch
def fetch(timeout: nil)
end_time = Time.now + timeout if timeout
download_lock = LockFile.new(temporary_path.basename)
download_lock.lock
@ -354,7 +373,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
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
url_time <= cached_location.mtime
@ -368,7 +387,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
puts "Already downloaded: #{cached_location}"
else
begin
_fetch(url: url, resolved_url: resolved_url)
_fetch(url: url, resolved_url: resolved_url, timeout: end_time&.remaining!)
rescue ErrorDuringExecution
raise CurlDownloadStrategyError, url
end
@ -385,6 +404,8 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
puts "Trying a mirror..."
retry
rescue Timeout::Error => e
raise Timeout::Error, "Timed out downloading #{self.url}: #{e}"
end
ensure
download_lock&.unlock
@ -396,19 +417,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
rm_rf(temporary_path)
end
def resolved_time_file_size
_, _, time, file_size = resolve_url_basename_time_file_size(url)
def resolved_time_file_size(timeout: nil)
_, _, time, file_size = resolve_url_basename_time_file_size(url, timeout: timeout)
[time, file_size]
end
private
def resolved_url_and_basename
resolved_url, basename, = resolve_url_basename_time_file_size(url)
def resolved_url_and_basename(timeout: nil)
resolved_url, basename, = resolve_url_basename_time_file_size(url, timeout: nil)
[resolved_url, basename]
end
def resolve_url_basename_time_file_size(url)
def resolve_url_basename_time_file_size(url, timeout: nil)
@resolved_info_cache ||= {}
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("/")}/")
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) : []
@ -441,16 +462,19 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new
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
if filename_with_encoding = content_disposition.parameters["filename*"]
if (filename_with_encoding = content_disposition.parameters["filename*"])
encoding, encoded_filename = filename_with_encoding.split("''", 2)
filename = URI.decode_www_form_component(encoded_filename).encode(encoding) if encoding && encoded_filename
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
filenames = lines.map(&parse_content_disposition).compact
@ -472,7 +496,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
@resolved_info_cache[url] = [redirect_url, basename, time, file_size]
end
def _fetch(url:, resolved_url:)
def _fetch(url:, resolved_url:, timeout:)
ohai "Downloading from #{resolved_url}" if url != resolved_url
if Homebrew::EnvConfig.no_insecure_redirect? &&
@ -481,7 +505,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
raise CurlDownloadStrategyError, url
end
curl_download resolved_url, to: temporary_path
curl_download resolved_url, to: temporary_path, timeout: timeout
end
# Curl options to be always passed to curl,
@ -516,6 +540,25 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
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.
#
# @api public
@ -535,9 +578,9 @@ class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
@combined_mirrors = [*@mirrors, *backup_mirrors]
end
def resolve_url_basename_time_file_size(url)
def resolve_url_basename_time_file_size(url, timeout: nil)
if url == self.url
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}")
super("#{apache_mirrors["preferred"]}#{apache_mirrors["path_info"]}", timeout: timeout)
else
super
end
@ -560,7 +603,7 @@ end
class CurlPostDownloadStrategy < CurlDownloadStrategy
private
def _fetch(url:, resolved_url:)
def _fetch(url:, resolved_url:, timeout:)
args = if meta.key?(:data)
escape_data = ->(d) { ["-d", URI.encode_www_form([d])] }
[url, *meta[:data].flat_map(&escape_data)]
@ -569,7 +612,7 @@ class CurlPostDownloadStrategy < CurlDownloadStrategy
query.nil? ? [url, "-X", "POST"] : [url, "-d", query]
end
curl_download(*args, to: temporary_path)
curl_download(*args, to: temporary_path, timeout: timeout)
end
end
@ -582,6 +625,7 @@ class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
UnpackStrategy::Uncompressed.new(cached_location)
.extract(basename: basename,
verbose: verbose? && !quiet?)
yield if block_given?
end
end
@ -608,7 +652,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
# Download and cache the repository at {#cached_location}.
#
# @api public
def fetch
def fetch(timeout: nil)
if @url.chomp("/") != repo_url || !silent_command("svn", args: ["switch", @url, cached_location]).success?
clear_cache
end
@ -649,7 +693,11 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
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.
# 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.
@ -669,9 +717,9 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
end
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
command!("svn", args: ["checkout", url, target, *args])
command! "svn", args: ["checkout", url, target, *args], timeout: timeout&.remaining
end
end
@ -684,20 +732,22 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
(cached_location/".svn").directory?
end
def clone_repo
sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
case @ref_type
when :revision
fetch_repo cached_location, @url, @ref
fetch_repo cached_location, @url, @ref, timeout: timeout
when :revisions
# nil is OK for main_revision, as fetch_repo will then get latest
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|
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
else
fetch_repo cached_location, @url
fetch_repo cached_location, @url, timeout: timeout
end
end
alias update clone_repo
@ -746,12 +796,13 @@ class GitDownloadStrategy < VCSDownloadStrategy
0
end
def update
sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
config_repo
update_repo
checkout
update_repo(timeout: timeout)
checkout(timeout: timeout)
reset
update_submodules if submodules?
update_submodules(timeout: timeout) if submodules?
end
def shallow_clone?
@ -812,6 +863,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
end
end
sig { void }
def config_repo
command! "git",
args: ["config", "remote.origin.url", @url],
@ -824,35 +876,42 @@ class GitDownloadStrategy < VCSDownloadStrategy
chdir: cached_location
end
def update_repo
sig { params(timeout: T.nilable(Time)).void }
def update_repo(timeout: nil)
return if @ref_type != :branch && ref?
if !shallow_clone? && shallow_dir?
command! "git",
args: ["fetch", "origin", "--unshallow"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
else
command! "git",
args: ["fetch", "origin"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
end
end
def clone_repo
command! "git", args: clone_args
sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
command! "git", args: clone_args, timeout: timeout&.remaining
command! "git",
args: ["config", "homebrew.cacheversion", cache_version],
chdir: cached_location
checkout
update_submodules if submodules?
chdir: cached_location,
timeout: timeout&.remaining
checkout(timeout: timeout)
update_submodules(timeout: timeout) if submodules?
end
def checkout
sig { params(timeout: T.nilable(Time)).void }
def checkout(timeout: nil)
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
sig { void }
def reset
ref = case @ref_type
when :branch
@ -866,13 +925,16 @@ class GitDownloadStrategy < VCSDownloadStrategy
chdir: cached_location
end
def update_submodules
sig { params(timeout: T.nilable(Time)).void }
def update_submodules(timeout: nil)
command! "git",
args: ["submodule", "foreach", "--recursive", "git submodule sync"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
command! "git",
args: ["submodule", "update", "--init", "--recursive"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
fix_absolute_submodule_gitdir_references!
end
@ -1024,23 +1086,27 @@ class CVSDownloadStrategy < VCSDownloadStrategy
"-Q" unless verbose?
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.
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",
args: [*quiet_flag, "-d", @url, "checkout", "-d", cached_location.basename, @module],
chdir: cached_location.dirname
chdir: cached_location.dirname,
timeout: timeout&.remaining
end
def update
sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
command! "cvs",
args: [*quiet_flag, "update"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
end
def split_url(in_url)
parts = in_url.split(/:/)
parts = in_url.split(":")
mod = parts.pop
url = parts.join(":")
[mod, url]
@ -1088,12 +1154,14 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
(cached_location/".hg").directory?
end
def clone_repo
command! "hg", args: ["clone", @url, cached_location]
sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
command! "hg", args: ["clone", @url, cached_location], timeout: timeout&.remaining
end
def update
command! "hg", args: ["--cwd", cached_location, "pull", "--update"]
sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
command! "hg", args: ["--cwd", cached_location, "pull", "--update"], timeout: timeout&.remaining
update_args = if @ref_type && @ref
ohai "Checking out #{@ref_type} #{@ref}"
@ -1102,7 +1170,7 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
["--clean"]
end
command! "hg", args: ["--cwd", cached_location, "update", *update_args]
command! "hg", args: ["--cwd", cached_location, "update", *update_args], timeout: timeout&.remaining
end
end
@ -1151,16 +1219,20 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
(cached_location/".bzr").directory?
end
def clone_repo
sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
# "lightweight" means history-less
command! "bzr",
args: ["checkout", "--lightweight", @url, cached_location]
args: ["checkout", "--lightweight", @url, cached_location],
timeout: timeout&.remaining
end
def update
sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
command! "bzr",
args: ["update"],
chdir: cached_location
chdir: cached_location,
timeout: timeout&.remaining
end
end
@ -1203,12 +1275,14 @@ class FossilDownloadStrategy < VCSDownloadStrategy
"fossil"
end
def clone_repo
silent_command!("fossil", args: ["clone", @url, cached_location])
sig { params(timeout: T.nilable(Time)).void }
def clone_repo(timeout: nil)
silent_command! "fossil", args: ["clone", @url, cached_location], timeout: timeout&.remaining
end
def update
silent_command!("fossil", args: ["pull", "-R", cached_location])
sig { params(timeout: T.nilable(Time)).void }
def update(timeout: nil)
silent_command! "fossil", args: ["pull", "-R", cached_location], timeout: timeout&.remaining
end
end
@ -1231,6 +1305,8 @@ class DownloadStrategyDetector
def self.detect_from_url(url)
case url
when GitHubPackages::URL_REGEX
CurlGitHubPackagesDownloadStrategy
when %r{^https?://github\.com/[^/]+/[^/]+\.git$}
GitHubGitDownloadStrategy
when %r{^https?://.+\.git$},

View File

@ -46,8 +46,12 @@ module Homebrew
},
HOMEBREW_BOTTLE_DOMAIN: {
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 " \
"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/`, " \
"Linux: `https://linuxbrew.bintray.com/`.",
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 " \
"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: {
description: "Set the Git author and committer email to this value.",
},
@ -179,6 +190,10 @@ module Homebrew
default_text: 'The "Beer Mug" emoji.',
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: {
description: "Consult this file for the list of formulae to check by default when no formula argument " \
"is passed to `brew livecheck`.",
@ -204,19 +219,14 @@ module Homebrew
boolean: true,
},
HOMEBREW_NO_AUTO_UPDATE: {
description: "If set, do not automatically update before running " \
"`brew install`, `brew upgrade` or `brew tap`.",
description: "If set, do not automatically update before running some commands e.g. " \
"`brew install`, `brew upgrade` and `brew tap`.",
boolean: true,
},
HOMEBREW_NO_BOOTSNAP: {
description: "If set, do not use Bootsnap to speed up repeated `brew` calls.",
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: {
description: "If set, do not check for broken dependents after installing, upgrading or reinstalling " \
"formulae.",
@ -257,6 +267,11 @@ module Homebrew
description: "If set, use Pry for the `brew irb` command.",
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: {
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.",

View File

@ -97,6 +97,25 @@ class FormulaOrCaskUnavailableError < RuntimeError
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.
class FormulaUnavailableError < FormulaOrCaskUnavailableError
extend T::Sig
@ -423,7 +442,7 @@ class BuildError < RuntimeError
def fetch_issues
GitHub.issues_for_formula(formula.name, tap: formula.tap, state: "open")
rescue GitHub::RateLimitExceededError => e
rescue GitHub::API::RateLimitExceededError => e
opoo e.message
[]
end
@ -453,7 +472,7 @@ class BuildError < RuntimeError
if formula.tap && defined?(OS::ISSUES_URL)
if formula.tap.official?
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
If reporting this issue please do so at (not Homebrew/brew or Homebrew/core):
#{Formatter.url(issues_url)}
@ -579,19 +598,32 @@ class ErrorDuringExecution < RuntimeError
@status = status
@output = output
raise ArgumentError, "Status cannot be nil." if status.nil?
exitstatus = case status
when Integer
status
when Hash
status["exitstatus"]
else
status&.exitstatus
status.exitstatus
end
termsig = case status
when Integer
nil
when Hash
status["termsig"]
else
status.termsig
end
redacted_cmd = redact_secrets(cmd.shelljoin.gsub('\=', "="), secrets)
reason = if exitstatus
"exited with #{exitstatus}"
elsif (uncaught_signal = status&.termsig)
"was terminated by uncaught signal #{Signal.signame(uncaught_signal)}"
elsif termsig
"was terminated by uncaught signal #{Signal.signame(termsig)}"
else
raise ArgumentError, "Status neither has `exitstatus` nor `termsig`."
end

View File

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

View File

@ -38,10 +38,11 @@ module SharedEnvExtension
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).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
@cc = cc
@build_bottle = build_bottle

View File

@ -19,10 +19,11 @@ module Stdenv
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).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
self["HOMEBREW_ENV"] = "std"

View File

@ -47,10 +47,11 @@ module Superenv
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
testing_formula: T::Boolean,
).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
send(compiler)

View File

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

View File

@ -3,7 +3,10 @@
module Stdenv
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 "LIBRARY_PATH", HOMEBREW_PREFIX/"lib"

View File

@ -11,7 +11,10 @@ module Superenv
# @private
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_DYNAMIC_LINKER"] = determine_dynamic_linker_path
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula)

View File

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

View File

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

View File

@ -64,6 +64,7 @@ module Homebrew
check_clt_minimum_version
check_if_xcode_needs_clt_installed
check_if_supported_sdk_available
check_broken_sdks
].freeze
end
@ -425,7 +426,7 @@ module Homebrew
source = if locator.source == :clt
update_instructions = MacOS::CLT.update_instructions
"CLT"
"Command Line Tools (CLT)"
else
update_instructions = MacOS::Xcode.update_instructions
"Xcode"
@ -438,6 +439,40 @@ module Homebrew
#{update_instructions}
EOS
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

View File

@ -11,7 +11,10 @@ module Stdenv
end
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
# mixed character sets

View File

@ -98,8 +98,6 @@ module Superenv
def determine_cccfg
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
s << "a"
s.freeze
@ -121,7 +119,10 @@ module Superenv
self["HOMEBREW_SDKROOT"] = nil
self["HOMEBREW_DEVELOPER_DIR"] = nil
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
# reliably figure this out with Xcode 8 and above.

View File

@ -1,6 +1,8 @@
# typed: false
# 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
undef uses_from_macos
@ -9,11 +11,12 @@ class SoftwareSpec
if deps.is_a?(Hash)
bounds = deps.dup
deps = Hash[*bounds.shift]
deps = bounds.shift
end
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
else
depends_on deps

View File

@ -20,10 +20,10 @@ module Utils
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.
if OS::Mac.prerelease? && Homebrew::EnvConfig.developer? &&
Homebrew::EnvConfig.skip_or_later_bottles?
if exact || (OS::Mac.prerelease? && Homebrew::EnvConfig.developer? &&
Homebrew::EnvConfig.skip_or_later_bottles?)
generic_find_matching_tag(tag)
else
generic_find_matching_tag(tag) ||

View File

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

View File

@ -1,8 +1,9 @@
# typed: strict
# frozen_string_literal: true
if OS.linux?
require "extend/os/linux/software_spec"
elsif OS.mac?
# This logic will need to be more nuanced if this file includes more than `uses_from_macos`.
if OS.mac? || Homebrew::EnvConfig.simulate_macos_on_linux?
require "extend/os/mac/software_spec"
elsif OS.linux?
require "extend/os/linux/software_spec"
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
last_dot = Time.at(0)
while buf = rd.gets
while (buf = rd.gets)
log.puts buf
# make sure dots printed with interval of at least 1 min.
next unless (Time.now - last_dot) > 60
@ -2072,7 +2072,7 @@ class Formula
end
puts
else
while buf = rd.gets
while (buf = rd.gets)
log.puts buf
puts buf
end

View File

@ -8,8 +8,54 @@ module Homebrew
module Assertions
include Context
require "test/unit/assertions"
include ::Test::Unit::Assertions
require "minitest"
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.
# @api public
@ -18,7 +64,7 @@ module Homebrew
output = `#{cmd}`
assert_equal result, $CHILD_STATUS.exitstatus
output
rescue Test::Unit::AssertionFailedError
rescue Minitest::Assertion
puts output if verbose?
raise
end
@ -35,7 +81,7 @@ module Homebrew
end
assert_equal result, $CHILD_STATUS.exitstatus unless result.nil?
output
rescue Test::Unit::AssertionFailedError
rescue Minitest::Assertion
puts output if verbose?
raise
end

View File

@ -133,7 +133,7 @@ module Homebrew
return
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."
return
end
@ -282,7 +282,7 @@ module Homebrew
# The number of conflicts on Linux is absurd.
# 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)
version_hash = {}
@ -339,6 +339,18 @@ module Homebrew
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
return unless @versioned_formula
return unless @core_tap
@ -367,10 +379,10 @@ module Homebrew
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],
check_content: true,
strict: @strict)
strict: @strict))
problem http_content_problem
end
end
@ -472,7 +484,7 @@ module Homebrew
%w[Stable HEAD].each do |name|
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.problems.each do |message|
@ -497,7 +509,7 @@ module Homebrew
)
end
if stable = formula.stable
if (stable = formula.stable)
version = stable.version
problem "Stable: version (#{version}) is set to a string without a digit" if version.to_s !~ /\d/
if version.to_s.start_with?("HEAD")

View File

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

View File

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

View File

@ -94,7 +94,6 @@ class FormulaInstaller
@options = options
@requirement_messages = []
@poured_bottle = false
@pour_failed = false
@start_time = nil
end
@ -154,8 +153,6 @@ class FormulaInstaller
sig { params(output_warning: T::Boolean).returns(T::Boolean) }
def pour_bottle?(output_warning: false)
return false if @pour_failed
return false if !formula.bottle_tag? && !formula.local_bottle_path
return true if force_bottle?
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!"
end
if Homebrew.default_prefix? && !Homebrew::EnvConfig.developer? &&
if Homebrew.default_prefix? &&
# TODO: re-enable this on Linux when we merge linuxbrew-core into
# homebrew-core and have full bottle coverage.
(OS.mac? || ENV["CI"]) &&
!build_from_source? && !build_bottle? &&
!installed_as_dependency? &&
formula.tap&.core_tap? && !formula.bottle_unneeded? && !formula.any_version_installed? &&
!build_from_source? && !build_bottle? && !formula.head? &&
formula.tap&.core_tap? && !formula.bottle_unneeded? &&
# Integration tests override homebrew-core locations
ENV["HOMEBREW_TEST_TMPDIR"].nil? &&
!pour_bottle?
@ -244,12 +240,21 @@ class FormulaInstaller
formula_message = formula.pour_bottle_check_unsatisfied_reason
formula_message[0] = formula_message[0].downcase
"#{formula}: #{formula_message}"
else
<<~EOS
#{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
#{formula}: no bottle available!
EOS
end
if message
message += <<~EOS
You can try to install from source with:
brew install --build-from-source #{formula}
@ -260,6 +265,7 @@ class FormulaInstaller
EOS
raise CannotInstallFormulaError, message
end
end
type, reason = DeprecateDisable.deprecate_disable_info formula
@ -282,49 +288,51 @@ class FormulaInstaller
return if ignore_deps?
recursive_deps = formula.recursive_dependencies
recursive_formulae = recursive_deps.map(&:to_formula)
if Homebrew::EnvConfig.developer?
# `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 = []
invalid_arch_dependencies = []
recursive_formulae.each do |dep|
dep_recursive_dependencies = dep.recursive_dependencies.map(&:to_s)
if dep_recursive_dependencies.include?(formula.name)
recursive_dependencies << "#{formula.full_name} depends on #{dep.full_name}"
recursive_dependencies << "#{dep.full_name} depends on #{formula.full_name}"
end
if (tab = Tab.for_formula(dep)) && tab.arch.present? && tab.arch.to_s != Hardware::CPU.arch.to_s
invalid_arch_dependencies << "#{dep} was built for #{tab.arch}"
cyclic_dependencies = []
recursive_dep_map.each do |dep, recursive_deps|
if [formula.name, formula.full_name].include?(dep.name)
cyclic_dependencies << "#{formula.full_name} depends on itself directly"
elsif recursive_deps.any? { |rdep| [formula.name, formula.full_name].include?(rdep.name) }
cyclic_dependencies << "#{formula.full_name} depends on itself via #{dep.name}"
end
end
unless recursive_dependencies.empty?
if cyclic_dependencies.present?
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself:
#{recursive_dependencies.join("\n ")}
#{cyclic_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
# 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
unless invalid_arch_dependencies.empty?
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}"
end
pinned_unsatisfied_deps << dep if dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))
end
if invalid_arch_dependencies.present?
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} dependencies not built for the #{Hardware::CPU.arch} CPU architecture:
#{invalid_arch_dependencies.join("\n ")}
EOS
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?
raise CannotInstallFormulaError,
@ -429,7 +437,7 @@ class FormulaInstaller
if pour_bottle?
begin
pour
rescue Exception => e # rubocop:disable Lint/RescueException
rescue Exception # rubocop:disable Lint/RescueException
# any exceptions must leave us with nothing installed
ignore_interrupts do
begin
@ -442,17 +450,7 @@ class FormulaInstaller
end
formula.rack.rmdir_if_possible
end
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."
raise UnbottledError, [formula] unless DevelopmentTools.installed?
compute_and_install_dependencies unless ignore_deps?
raise
else
@poured_bottle = true
end
@ -513,7 +511,7 @@ class FormulaInstaller
$stderr.puts "Please report this issue to the #{formula.tap} tap (not Homebrew/brew or Homebrew/core)!"
false
else # rubocop:disable Layout/ElseAlignment
else
f.linked_keg.exist? && f.opt_prefix.exist?
end
@ -523,10 +521,12 @@ class FormulaInstaller
# Compute and collect the dependencies needed by the formula currently
# being installed.
def compute_dependencies
@compute_dependencies ||= begin
req_map, req_deps = expand_requirements
check_requirements(req_map)
expand_dependencies(req_deps + formula.deps)
end
end
def unbottled_dependencies(deps)
deps.map(&:first).map(&:to_formula).reject do |dep_f|
@ -575,7 +575,7 @@ class FormulaInstaller
formula_deps_map = Dependency.expand(formula)
.index_by(&:name)
while f = formulae.pop
while (f = formulae.pop)
runtime_requirements = runtime_requirements(f)
f.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent)
@ -707,16 +707,17 @@ class FormulaInstaller
quiet: quiet?,
verbose: verbose?,
)
fi.prelude
fi.fetch
end
sig { params(dep: Dependency, inherited_options: Options).void }
def install_dependency(dep, inherited_options)
df = dep.to_formula
tab = Tab.for_formula(df)
if df.linked_keg.directory?
linked_keg = Keg.new(df.linked_keg.resolved_path)
tab = Tab.for_keg(linked_keg)
keg_had_linked_keg = true
keg_was_linked = linked_keg.linked?
linked_keg.unlink
@ -724,12 +725,13 @@ class FormulaInstaller
if df.latest_version_installed?
installed_keg = Keg.new(df.prefix)
tab ||= Tab.for_keg(installed_keg)
tmp_keg = Pathname.new("#{installed_keg}.tmp")
installed_keg.rename(tmp_keg)
end
tab_tap = tab.source["tap"]
if tab_tap.present? && df.tap.present? && df.tap.to_s != tab_tap.to_s
if df.tap.present? && tab.present? && (tab_tap = tab.source["tap"].presence) &&
df.tap.to_s != tab_tap.to_s
odie <<~EOS
#{df} is already installed from #{tab_tap}!
Please `brew uninstall #{df}` first."
@ -737,7 +739,7 @@ class FormulaInstaller
end
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 |= inherited_options
options &= df.options
@ -748,7 +750,7 @@ class FormulaInstaller
options: options,
link_keg: keg_had_linked_keg ? keg_was_linked : nil,
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,
include_test_formulae: @include_test_formulae,
build_from_source_formulae: @build_from_source_formulae,
@ -759,7 +761,6 @@ class FormulaInstaller
verbose: verbose?,
},
)
fi.prelude
oh1 "Installing #{formula.full_name} dependency: #{Formatter.identifier(dep.name)}"
fi.install
fi.finish
@ -901,13 +902,12 @@ class FormulaInstaller
# 1. formulae can modify ENV, so we must ensure that each
# installation has a pristine ENV when it starts, forking now is
# the easiest way to do this
args = %W[
nice #{RUBY_PATH}
#{ENV["HOMEBREW_RUBY_WARNINGS"]}
-I #{$LOAD_PATH.join(File::PATH_SEPARATOR)}
--
#{HOMEBREW_LIBRARY_PATH}/build.rb
#{formula.specified_path}
args = [
"nice",
*HOMEBREW_RUBY_EXEC_ARGS,
"--",
HOMEBREW_LIBRARY_PATH/"build.rb",
formula.specified_path,
].concat(build_argv)
Utils.safe_fork do
@ -1123,25 +1123,10 @@ class FormulaInstaller
return if only_deps?
if pour_bottle?(output_warning: true)
begin
downloader.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
return if pour_bottle?
unless pour_bottle?(output_warning: true)
formula.fetch_patches
formula.resources.each(&:fetch)
end
downloader.fetch
end
@ -1170,10 +1155,12 @@ class FormulaInstaller
tab = Tab.for_keg(keg)
unless ignore_deps?
CxxStdlib.check_compatibility(
formula, formula.recursive_dependencies,
Keg.new(formula.prefix), tab.compiler
)
end
tab.tap = formula.tap
tab.poured_from_bottle = true
@ -1253,10 +1240,9 @@ class FormulaInstaller
end
return if forbidden_licenses.blank?
return if ignore_deps?
compute_dependencies.each do |dep, _|
next if @ignore_deps
dep_f = dep.to_formula
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}.
EOS
end
return if @only_deps
return if only_deps?
return unless SPDX.licenses_forbid_installation? formula.license, forbidden_licenses

View File

@ -33,6 +33,19 @@ module Formulary
cache.fetch(path)
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:)
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.module_eval(contents, path)
rescue NameError, ArgumentError, ScriptError, MethodDeprecatedError => e
remove_const(namespace)
raise FormulaUnreadableError.new(name, e)
end
class_name = class_s(name)
@ -58,6 +72,7 @@ module Formulary
.map { |const_name| mod.const_get(const_name) }
.select { |const| const.is_a?(Class) }
new_exception = FormulaClassUnavailableError.new(name, path, class_name, class_list)
remove_const(namespace)
raise new_exception, "", e.backtrace
end
end
@ -467,14 +482,14 @@ module Formulary
return FormulaLoader.new(name, path)
end
if newref = CoreTap.instance.formula_renames[ref]
if (newref = CoreTap.instance.formula_renames[ref])
formula_with_that_oldname = core_path(newref)
return FormulaLoader.new(newref, formula_with_that_oldname) if formula_with_that_oldname.file?
end
possible_tap_newname_formulae = []
Tap.each do |tap|
if newref = tap.formula_renames[ref]
if (newref = tap.formula_renames[ref])
possible_tap_newname_formulae << "#{tap.name}/#{newref}"
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

View File

@ -16,7 +16,6 @@ require "rbconfig"
RUBY_PATH = Pathname.new(RbConfig.ruby).freeze
RUBY_BIN = RUBY_PATH.dirname.freeze
require "rubygems"
# Only require "core_ext" here to ensure we're only requiring the minimum of
# what we need.
require "active_support/core_ext/object/blank"
@ -72,8 +71,6 @@ HOMEBREW_PULL_API_REGEX =
%r{https://api\.github\.com/repos/([\w-]+)/([\w-]+)?/pulls/(\d+)}.freeze
HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
%r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})].freeze
HOMEBREW_RELEASES_URL_REGEX =
%r{https://github\.com/([\w-]+)/([\w-]+)?/releases/download/(.+)}.freeze
require "fileutils"

View File

@ -1,22 +1,27 @@
# typed: false
# frozen_string_literal: true
if !ENV["HOMEBREW_NO_BOOTSNAP"] &&
ENV["HOMEBREW_BOOTSNAP"] &&
homebrew_bootsnap_enabled = !ENV["HOMEBREW_NO_BOOTSNAP"] && ENV["HOMEBREW_BOOTSNAP"]
# portable ruby doesn't play nice with bootsnap
!ENV["HOMEBREW_FORCE_VENDOR_RUBY"] &&
(!ENV["HOMEBREW_MACOS_VERSION"] || ENV["HOMEBREW_MACOS_SYSTEM_RUBY_NEW_ENOUGH"]) &&
# Can't use .exclude? here because we haven't required active_support yet.
homebrew_bootsnap_enabled &&= !ENV["HOMEBREW_RUBY_PATH"].to_s.include?("/vendor/portable-ruby/") # rubocop:disable Rails/NegateInclude
homebrew_bootsnap_enabled &&= if ENV["HOMEBREW_MACOS_VERSION"]
# Apple Silicon doesn't play nice with bootsnap
(ENV["HOMEBREW_PROCESSOR"] == "Intel")
require "rubygems"
ENV["HOMEBREW_PROCESSOR"] == "Intel" &&
# we need some development tools to build bootsnap native code
(File.directory?("/Applications/Xcode.app") || File.directory?("/Library/Developer/CommandLineTools"))
else
File.executable?("/usr/bin/clang") || File.executable?("/usr/bin/gcc")
end
if homebrew_bootsnap_enabled
begin
require "bootsnap"
rescue LoadError
unless ENV["HOMEBREW_BOOTSNAP_RETRY"]
require "utils/gems"
Homebrew.install_bundler_gems!
Homebrew.install_bundler_gems!(only_warn_on_failure: true)
ENV["HOMEBREW_BOOTSNAP_RETRY"] = "1"
exec ENV["HOMEBREW_BREW_FILE"], *ARGV

View File

@ -0,0 +1,74 @@
# typed: false
# frozen_string_literal: true
require "cask_dependent"
# Helper functions for installed dependents.
#
# @api private
module InstalledDependents
extend T::Sig
module_function
# Given an array of kegs, this method will try to find some other kegs
# or casks that depend on them. If it does, it returns:
#
# - some kegs in the passed array that have installed dependents
# - some installed dependents of those kegs.
#
# If it doesn't, it returns nil.
#
# Note that nil will be returned if the only installed dependents of the
# passed kegs are other kegs in the array or casks present in the casks
# parameter.
#
# For efficiency, we don't bother trying to get complete data.
def find_some_installed_dependents(kegs, casks: [])
keg_names = kegs.select(&:optlinked?).map(&:name)
keg_formulae = []
kegs_by_source = kegs.group_by do |keg|
# First, attempt to resolve the keg to a formula
# to get up-to-date name and tap information.
f = keg.to_formula
keg_formulae << f
[f.name, f.tap]
rescue
# If the formula for the keg can't be found,
# fall back to the information in the tab.
[keg.name, keg.tab.tap]
end
all_required_kegs = Set.new
all_dependents = []
# Don't include dependencies of kegs that were in the given array.
dependents_to_check = (Formula.installed - keg_formulae) + (Cask::Caskroom.casks - casks)
dependents_to_check.each do |dependent|
required = case dependent
when Formula
dependent.missing_dependencies(hide: keg_names)
when Cask::Cask
CaskDependent.new(dependent).runtime_dependencies.map(&:to_formula)
end
required_kegs = required.map do |f|
f_kegs = kegs_by_source[[f.name, f.tap]]
next unless f_kegs
f_kegs.max_by(&:version)
end.compact
next if required_kegs.empty?
all_required_kegs += required_kegs
all_dependents << dependent.to_s
end
return if all_required_kegs.empty?
return if all_dependents.empty?
[all_required_kegs.to_a, all_dependents.sort]
end
end

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