Compare commits

...

121 Commits

Author SHA1 Message Date
Michael Cho
cd4fc80ec1
Migrate Linux CI to GCC 12 2025-09-12 14:44:55 -04:00
Michael Cho
f5c11fa342
Check host libstdc++ for brew gcc dependency
For most formulae, the bottles need a minimum libstdc++ rather than a
minimum GCC version. This is particularly important when building on
Ubuntu where the default compiler version is older than libstdc++.

So, checking the host libstdc++ version is a more accurate way to
determine whether brew GCC is needed at runtime. This can be improved in
the future to check symbol versions (e.g. GLIBCXX, CXXABI, GLIBC) which
can allow some bottles to be installed even with older glibc/libstdc++.
2025-09-12 14:15:56 -04:00
Michael Cho
eda9e78529
Merge pull request #20633 from Homebrew/ld-system
os/linux/ld: add support for using system ld.so
2025-09-12 13:19:40 +00:00
Mike McQuaid
5f4e42a2c8
Merge pull request #20656 from Homebrew/copilot/fix-cask-rename-issue-linux
Fix Cask artifact rename operation on Linux by making xattr metadata no-op
2025-09-12 08:49:14 +00:00
Ruoyu Zhong
852574dedf
Merge pull request #20676 from Homebrew/dependabot/all-3d8734bf05
Bump the "all" group with 2 updates across multiple ecosystems
2025-09-12 08:42:40 +00:00
copilot-swe-agent[bot]
3e413b4521
Fix Cask artifact rename operation on Linux by making xattr metadata no-op
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-12 09:30:17 +01:00
Mike McQuaid
2d9e9ce5d1
Merge pull request #20655 from Homebrew/copilot/fix-brew-fetch-tap-repo-issue
Fix brew fetch failure with symlinked taps and refactor path validation logic
2025-09-12 08:26:15 +00:00
BrewTestBot
2fe1076281
brew vendor-gems: commit updates. 2025-09-12 08:21:55 +00:00
dependabot[bot]
05985fabc3
build(deps): bump the all group across 1 directory with 8 updates
Bumps the all group with 3 updates in the /Library/Homebrew directory: [rubocop-performance](https://github.com/rubocop/rubocop-performance), [sorbet-static-and-runtime](https://github.com/sorbet/sorbet) and [rexml](https://github.com/ruby/rexml).


Updates `rubocop-performance` from 1.25.0 to 1.26.0
- [Release notes](https://github.com/rubocop/rubocop-performance/releases)
- [Changelog](https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-performance/compare/v1.25.0...v1.26.0)

Updates `sorbet-static-and-runtime` from 0.6.12479 to 0.6.12521
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `rexml` from 3.4.2 to 3.4.4
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.4.2...v3.4.4)

Updates `sorbet-runtime` from 0.6.12479 to 0.6.12521
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `sorbet` from 0.6.12479 to 0.6.12521
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `sorbet-static` from 0.6.12479 to 0.6.12521
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `unicode-display_width` from 3.1.5 to 3.2.0
- [Changelog](https://github.com/janlelis/unicode-display_width/blob/main/CHANGELOG.md)
- [Commits](https://github.com/janlelis/unicode-display_width/compare/v3.1.5...v3.2.0)

Updates `unicode-emoji` from 4.0.4 to 4.1.0
- [Changelog](https://github.com/janlelis/unicode-emoji/blob/main/CHANGELOG.md)
- [Commits](https://github.com/janlelis/unicode-emoji/compare/v4.0.4...v4.1.0)
build(deps): bump github/codeql-action in the all group

Bumps the all group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3.30.0 to 3.30.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](2d92b76c45...192325c861)

---
updated-dependencies:
- dependency-name: rubocop-performance
  dependency-version: 1.26.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: sorbet-static-and-runtime
  dependency-version: 0.6.12521
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: rexml
  dependency-version: 3.4.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet-runtime
  dependency-version: 0.6.12521
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet
  dependency-version: 0.6.12521
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet-static
  dependency-version: 0.6.12521
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: unicode-display_width
  dependency-version: 3.2.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: unicode-emoji
  dependency-version: 4.1.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: github/codeql-action
  dependency-version: 3.30.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-12 08:08:51 +00:00
copilot-swe-agent[bot]
6297f98d06
Fix symlinked tap loading issue in cask loader
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-12 09:02:43 +01:00
Mike McQuaid
07091cfbea
Simplify pluralisation of common words
Formulae, dependencies, tries are all used in multiple places so let's
simplify them
2025-09-12 09:02:43 +01:00
Mike McQuaid
1019f9ef21
Merge pull request #20673 from Homebrew/add-unreachable-cask-reason
deprecate_disable: add `unreachable` reason
2025-09-12 07:09:34 +00:00
Patrick Linnane
e88ceb8178
deprecate_disable: add unreachable reason
Signed-off-by: Patrick Linnane <patrick@linnane.io>
2025-09-11 21:12:24 -07:00
Patrick Linnane
2d8ba1e209
Merge pull request #20672 from Homebrew/drop-ventura-ci
Drop macOS Ventura CI in Homebrew/core
2025-09-12 03:35:57 +00:00
Patrick Linnane
675a588f6c
Drop macOS Ventura CI in Homebrew/core
Signed-off-by: Patrick Linnane <patrick@linnane.io>
2025-09-11 17:55:38 -07:00
Mike McQuaid
0a7a60f506
Merge pull request #20670 from bayandin/former-bayandin
Add @bayandin to former maintainers
2025-09-11 18:04:14 +00:00
Alexander Bayandin
9b364ad25b Add @bayandin to former maintainers 2025-09-11 17:32:40 +01:00
Mike McQuaid
613d6466a9
Merge pull request #20669 from Homebrew/sponsors-maintainers-man-completions
Update maintainers.
2025-09-11 11:36:46 +00:00
BrewTestBot
99456ee150
Update maintainers.
Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow.
2025-09-11 11:17:02 +00:00
Mike McQuaid
29270a8858
Merge pull request #20666 from Homebrew/contributions_team
dev-cmd/contributions: add `--team` flag.
2025-09-11 10:53:21 +00:00
Mike McQuaid
3fb55a1577
Merge pull request #20668 from gibfahn/private_repo_fix
github: handle user opting out of github API in private_repo check
2025-09-11 10:39:23 +00:00
Mike McQuaid
3d80dfadd1
dev-cmd/contributions: add --team flag.
This allows specifying an organisation team instead of an organisation
or individual users.
2025-09-11 11:36:48 +01:00
Gibson Fahnestock
f233244ab7
Update Library/Homebrew/utils/github.rb
Co-authored-by: Mike McQuaid <mike@mikemcquaid.com>
2025-09-11 11:23:23 +01:00
Gibson Fahnestock
e4ac3bfeed
github: handle user opting out of github API in private_repo check
Today we get a sorbet error when the user opts out, because `json` is
`{}`, so `json["private"]` is `nil`.

Given this function is used to check whether to send analytics, I assume
we should default to treating the repo as a private repo.

Refs: 8ef7a9dbd4/Library/Homebrew/utils/github/api.rb (L276)
2025-09-11 10:31:46 +01:00
Mike McQuaid
610c67b715
Merge pull request #20650 from botantony/build-typecheck
build build_environment build_options: enable `typed: strict`
2025-09-11 07:25:51 +00:00
botantony
6d548f784b
build: suggestions from @MikeMcQuaid
Signed-off-by: botantony <antonsm21@gmail.com>
Co-authored-by: Mike McQuaid <mike@mikemcquaid.com>
2025-09-11 06:26:01 +02:00
botantony
b2539d37fe
build_options: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-11 06:26:01 +02:00
botantony
1cc4d0bc25
build_environment: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-11 06:26:01 +02:00
botantony
89d36e0dd5
build: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-11 06:26:00 +02:00
Bo Anderson
deb6666f32
Merge pull request #20665 from Homebrew/sorbet-files-update
sorbet: Update RBI files.
2025-09-11 01:07:49 +00:00
BrewTestBot
66f8afa734
sorbet: Update RBI files.
Autogenerated by the [sorbet](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sorbet.yml) workflow.
2025-09-11 00:27:48 +00:00
Ruoyu Zhong
34be1486e2
Merge pull request #20664 from Homebrew/sponsors-maintainers-man-completions
Update manpage and completions.
2025-09-10 17:40:18 +00:00
BrewTestBot
ccda7a5de1
Update manpage and completions.
Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow.
2025-09-10 17:22:49 +00:00
Mike McQuaid
0d8da983d8
Merge pull request #20663 from Homebrew/contributions_tweaks
Add additional `brew contributions` functionality.
2025-09-10 17:06:48 +00:00
Mike McQuaid
3023e6dcad
Merge pull request #20660 from botantony/os/mac-typed
os/mac/*: `typed: strict`
2025-09-10 15:36:23 +00:00
botantony
843fc7c97a
os/mac/*: more style corrections
Signed-off-by: botantony <antonsm21@gmail.com>
Co-authored-by: Bo Anderson <mail@boanderson.me>
2025-09-10 16:59:51 +02:00
Mike McQuaid
a31fb2a6c2
Add additional brew contributions functionality.
- Add an `--organisation` flag to search a specific organisation.
- Wait for the GitHub API rate limit to reset before automatically
  retrying.
- Use (much) fewer API calls by using organisation-wide API PR searches
  rather than per-repository. This makes the rate limit easier to avoid
  and also makes things much faster (with the trade-off of showing a max
  PR count per-user rather than per-repository).
- Improve output to clarify when the max PR/commit count is reached.
- Move more logic and add more Sorbet signatures to the `GitHub` and
  `Utils::Git` modules.
- Rename a few GitHub API methods.
- Remove a lot of (now unused) `GitHub` module methods.
- Add, use a `Tap#full_repository` method.
- Add `formula-analytics` as a deprecated tap.
2025-09-10 15:32:06 +01:00
Mike McQuaid
8ef7a9dbd4
Merge pull request #20662 from Homebrew/tap-syntax-cache-fix
workflows/tests: fix tap-syntax caching
2025-09-10 07:36:34 +00:00
Bo Anderson
67f4be419b
workflows/tests: fix tap-syntax caching 2025-09-10 04:41:24 +01:00
Bo Anderson
51a98eb950
Merge pull request #20661 from Homebrew/macos-26-rc
os/mac: updates for Xcode/macOS 26 RC
2025-09-10 02:58:02 +00:00
Bo Anderson
055c87e47e
os/mac: updates for Xcode/macOS 26 RC 2025-09-10 03:39:46 +01:00
botantony
bc2c12c742
os/mac/*: style corrections
Signed-off-by: botantony <antonsm21@gmail.com>
Co-authored-by: Mike McQuaid <mike@mikemcquaid.com>
2025-09-09 18:13:19 +02:00
botantony
0adf85970d
os/mac/xcode: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-09 15:28:53 +02:00
botantony
28761d8756
os/mac/sdk: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-09 15:28:53 +02:00
botantony
289fed93b4
os/mac/mach: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-09 15:28:53 +02:00
botantony
744e2452d8
os/mac/keg: typed: strict
Signed-off-by: botantony <antonsm21@gmail.com>
2025-09-09 15:07:35 +02:00
Bo Anderson
80f9e6714f
Merge pull request #20654 from Homebrew/system_command_parameter
system_command: add missing `must_succeed`
2025-09-09 05:06:42 +00:00
Bevan Kay
971ca17180
system_command: add missing must_succeed 2025-09-09 10:49:21 +08:00
Michael Cho
4d36861ad0
Merge pull request #20647 from Homebrew/jemalloc-sys-page-size
ENV/super: set `JEMALLOC_SYS_WITH_LG_PAGE`
2025-09-08 20:24:52 +00:00
Michael Cho
14d7942c95
ENV/super: set JEMALLOC_SYS_WITH_LG_PAGE
AArch64 Linux supports up to 64KB page size

Co-authored-by: Mike McQuaid <mike@mikemcquaid.com>
2025-09-08 15:58:48 -04:00
Mike McQuaid
5453c0a74c
Merge pull request #20649 from Homebrew/more_tap_docs_tweaks
docs/How-to-Create-and-Maintain-a-Tap: more improvements.
2025-09-08 18:33:10 +00:00
Douglas Eichelberger
6e6c06f5a9
Merge pull request #20643 from Homebrew/dug/typed-utils-bottles
Enable strict typing in Utils::Bottles
2025-09-08 18:03:39 +00:00
Sam Ford
b54695d6e5
Merge pull request #20652 from Homebrew/bump-use-explicit-true-comparison
bump: compare newer_than_upstream values to true
2025-09-08 17:56:26 +00:00
Douglas Eichelberger
b827a1337a
Enable strict typing in Utils::Bottles 2025-09-08 10:48:23 -07:00
Sam Ford
2f827242aa
bump: compare newer_than_upstream values to true
When `Enumerable#all?` is called without an argument, it should check
whether values are truthy but it doesn't appear to work as expected
for the `newer_than_upstream` hash. In this case,
`{ general: false }.all?` returns `true` when it seemingly should
return `false`. This is preventing autobump from opening PRs for new
versions, so I've updated related `all?` calls to use a block with an
explicit comparison to `true` as a workaround to fix autobump in the
immediate term.
2025-09-08 13:00:32 -04:00
Mike McQuaid
f68908d492
Merge pull request #20651 from Homebrew/bump-use-livecheck_version-for-comparison
bump: use LivecheckVersion for comparison
2025-09-08 15:21:42 +00:00
Sam Ford
3541b4989f
bump: use LivecheckVersion for comparison
I recently modified `bump` to show the upstream version even when the
formula/cask version is newer (instead of an opaque `Unable to get
versions` error) but I noticed an issue while reviewing output from
a recent autobump run in homebrew/cask. This change works as expected
for versions with only one part (e.g., 1.2.3) but some multipart cask
versions (e.g., 1.5,15039) aren't being handled like they should
(where we split on commas and compare the version parts separately).
As a result, a cask version like 1.5,15039 is incorrectly seen as
newer than an upstream version like 1.5.1,15145 because 15039 from
the cask version is being compared to 1 in the upstream version.

This addresses the issue by using `LivecheckVersion` objects in the
related comparison, so versions will be handled as expected. This was
an oversight on my part but it only affects one cask at the moment
(`ia-presenter`), so it wasn't a widespread issue.
2025-09-08 10:30:57 -04:00
Mike McQuaid
afcaae3be8
docs/How-to-Create-and-Maintain-a-Tap: more improvements. 2025-09-08 08:58:16 +01:00
Mike McQuaid
c5b8cc906e
Merge pull request #20640 from Homebrew/bump-output-lower-livecheck-version
bump: output lower upstream version
2025-09-08 07:40:43 +00:00
Mike McQuaid
78d34b45af
Merge pull request #20631 from Homebrew/cc-rpath-link-glibc
shims/super/cc: rpath-link `glibc` if indirect dep
2025-09-08 07:36:12 +00:00
Mike McQuaid
efc036f75a
Merge pull request #20613 from Homebrew/dug/typed-system-command
Enable strict typing in SystemCommand
2025-09-08 07:32:58 +00:00
Douglas Eichelberger
31fb2adc84
Merge pull request #20648 from Homebrew/dug/type-safe-blocks
Use type-safe blocks
2025-09-07 20:49:32 +00:00
Douglas Eichelberger
bbca610601
Use type-safe blocks 2025-09-07 12:13:36 -07:00
Rylan Polster
3f11702fb1
Merge pull request #20642 from Homebrew/third-party-head-only-allowlist
Allow head-only formulae in third party taps
2025-09-06 06:13:12 +00:00
Rylan Polster
e2f02bcfbf
Allow head-only formulae in third party taps 2025-09-06 00:13:07 -04:00
Sam Ford
b22d2328b7
bump: output lower upstream version
Currently `brew bump` will output `unable to get versions` for the
livecheck (or Repology) version if it's lower than the current
package version. This makes it impossible to distinguish between a
failing livecheck and one where the livecheck version is lower. We can
detect when the package version is newer than the upstream version but
`bump` doesn't do anything to handle the situation.

This addresses the issue by updating `bump` to display the lower
upstream version and flag the current version with a trailing "(newer
than upstream)" parenthetical to make the situation apparent (and so
we can easily search for this text in the output).
2025-09-05 16:24:59 -04:00
Mike McQuaid
1da17b8fde
Merge pull request #20639 from Homebrew/improve_new_tap_docs
How-to-Create-and-Maintain-a-Tap: improve docs.
2025-09-05 18:05:24 +00:00
Mike McQuaid
83c1ed68f3
Apply suggestion from @samford
Co-authored-by: Sam Ford <1584702+samford@users.noreply.github.com>
2025-09-05 18:47:03 +01:00
Mike McQuaid
62a3b5e43e
How-to-Create-and-Maintain-a-Tap: improve docs.
We don't really tell people actually how to create a tap here so let's
provide more commands and output to help them.

While we're here, also fix some bad references to `repo` that weren't
updated when they should have been and let VSCode autoformat the
Markdown.
2025-09-05 17:42:34 +01:00
Michael Cho
896edb4451
os/linux/ld: add support for using system ld.so 2025-09-05 09:04:21 -04:00
Mike McQuaid
5b003154e5
Merge pull request #20638 from cprecioso/cprecioso/fix-docs
Remove docs saying that installing from a file is allowed
2025-09-05 11:18:00 +00:00
Ruoyu Zhong
74095ab480
Merge pull request #20637 from Homebrew/dependabot/all-c2f971b171
Bump the "all" group with 2 updates across multiple ecosystems
2025-09-05 11:10:14 +00:00
Carlos Precioso
0e81158e3a
Remove docs saying that installing from a file is allowed 2025-09-05 10:58:33 +02:00
BrewTestBot
ffc3c4bd6a
Update RBI files for all.
Autogenerated by the [vendor-gems](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/vendor-gems.yml) workflow.
2025-09-05 08:31:07 +00:00
BrewTestBot
f26e296cbb
brew vendor-gems: commit updates. 2025-09-05 08:30:35 +00:00
Mike McQuaid
0264a1e2ee
Merge pull request #20634 from Homebrew/cmd-dev-options
command options: various `--eval-all` fixes
2025-09-05 08:28:20 +00:00
dependabot[bot]
faf936a589
build(deps): bump the all group across 1 directory with 7 updates
Bumps the all group with 4 updates in the /Library/Homebrew directory: [rubocop](https://github.com/rubocop/rubocop), [rubocop-rspec](https://github.com/rubocop/rubocop-rspec), [sorbet-static-and-runtime](https://github.com/sorbet/sorbet) and [bigdecimal](https://github.com/ruby/bigdecimal).


Updates `rubocop` from 1.80.1 to 1.80.2
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.80.1...v1.80.2)

Updates `rubocop-rspec` from 3.6.0 to 3.7.0
- [Release notes](https://github.com/rubocop/rubocop-rspec/releases)
- [Changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rspec/compare/v3.6.0...v3.7.0)

Updates `sorbet-static-and-runtime` from 0.6.12466 to 0.6.12479
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `sorbet-runtime` from 0.6.12466 to 0.6.12479
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `bigdecimal` from 3.2.2 to 3.2.3
- [Release notes](https://github.com/ruby/bigdecimal/releases)
- [Changelog](https://github.com/ruby/bigdecimal/blob/master/CHANGES.md)
- [Commits](https://github.com/ruby/bigdecimal/compare/v3.2.2...v3.2.3)

Updates `sorbet` from 0.6.12466 to 0.6.12479
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)

Updates `sorbet-static` from 0.6.12466 to 0.6.12479
- [Release notes](https://github.com/sorbet/sorbet/releases)
- [Commits](https://github.com/sorbet/sorbet/commits)
build(deps): bump the all group with 5 updates

Bumps the all group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.29.11` | `3.30.0` |
| [ruby/setup-ruby](https://github.com/ruby/setup-ruby) | `1.256.0` | `1.257.0` |
| [actions/stale](https://github.com/actions/stale) | `9.1.0` | `10.0.0` |
| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `5.5.0` | `5.5.1` |
| [actions/setup-python](https://github.com/actions/setup-python) | `5.6.0` | `6.0.0` |


Updates `github/codeql-action` from 3.29.11 to 3.30.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3c3833e0f8...2d92b76c45)

Updates `ruby/setup-ruby` from 1.256.0 to 1.257.0
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](efbf473cab...4451173596)

Updates `actions/stale` from 9.1.0 to 10.0.0
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](5bef64f19d...3a9db7e6a4)

Updates `codecov/codecov-action` from 5.5.0 to 5.5.1
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](fdcc847654...5a1091511a)

Updates `actions/setup-python` from 5.6.0 to 6.0.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](a26af69be9...e797f83bcb)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.80.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: rubocop-rspec
  dependency-version: 3.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: sorbet-static-and-runtime
  dependency-version: 0.6.12479
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet-runtime
  dependency-version: 0.6.12479
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: bigdecimal
  dependency-version: 3.2.3
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet
  dependency-version: 0.6.12479
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sorbet-static
  dependency-version: 0.6.12479
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: github/codeql-action
  dependency-version: 3.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: ruby/setup-ruby
  dependency-version: 1.257.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: actions/stale
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: actions/setup-python
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 08:06:01 +00:00
Eric Knibbe
1c4cb6936e
command options: various --eval-all fixes 2025-09-05 08:53:44 +01:00
Mike McQuaid
09452ed3c7
Merge pull request #20636 from Homebrew/add-more-relocations
keg_relocate: add more relocation paths
2025-09-05 07:27:45 +00:00
Daeho Ro
cba6e40e77
keg_relocate: add more relocation paths 2025-09-05 14:48:12 +09:00
Rylan Polster
24e7d8f0e0
Merge pull request #20635 from Homebrew/sponsors-maintainers-man-completions
Update manpage and completions.
2025-09-05 00:24:50 +00:00
BrewTestBot
32a5e9f779
Update manpage and completions.
Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow.
2025-09-05 00:08:21 +00:00
Mike McQuaid
6e9da0ba4c
Merge pull request #20632 from Homebrew/gcc_version
development_tools: make gcc_version public api
2025-09-04 07:47:18 +00:00
Mike McQuaid
c73538ac03
Merge pull request #20619 from Homebrew/single-api-fetch-per-run
Download new API files once per Homebrew instance
2025-09-04 07:44:49 +00:00
Michael Cho
5fb96d9d8a
shims/super/cc: rpath-link glibc if indirect dep
This helps ld.bfd find the correct `glibc` dependency. Needed when using
host toolchain which will only search for /etc/ld.so.conf.

Also can help unsupported systems that force poured non-relocatable
`binutils` bottle.
2025-09-03 22:08:10 -04:00
Michael Cho
08181692f3
development_tools: make gcc_version public api 2025-09-03 20:35:20 -04:00
Bo Anderson
1c959c22ce
Merge pull request #20630 from Homebrew/pid_path-libSystem
utils/pid_path: use libSystem
2025-09-03 21:26:12 +00:00
Rylan Polster
00f960174f
Download new API files once per Homebrew instance 2025-09-03 15:30:31 -04:00
Mike McQuaid
bd57ad891d
Merge pull request #20629 from Homebrew/cask/audit-fix-key-not-found-error
Cask::Audit: fix `key not found: :latest` error
2025-09-03 18:07:17 +00:00
Bo Anderson
f23b84897c
utils/pid_path: use libSystem 2025-09-03 18:02:28 +01:00
Sam Ford
dce220e518
Cask::Audit: fix key not found: :latest error
`Cask::Audit.audit_livecheck_version` can raise a `key not found:
:latest` error when a hash from livecheck's `latest_version` method
doesn't have a `:latest` value. This error means that livecheck was
unable to identify the latest upstream version but it can only be
understood if the reader knows how this audit is implemented (and it
may also depend on knowing the structure of livecheck's
`latest_version` hash). Without that knowledge, the error doesn't
make it clear which audit is failing and why.

This addresses the issue by using `nil` as the default value for this
`fetch` call and accounting for a `nil` `latest_version` value. This
allows the audit to surface the usual "Version '1.2.3' differs from
'' retrieved by livecheck" failure, which makes it more clear that
livecheck isn't returning a version.
2025-09-03 12:34:06 -04:00
Mike McQuaid
4343324468
Merge pull request #20623 from Homebrew/livecheck/support-trailing-comments-in-watchlist
livecheck: support trailing comments in watchlist
2025-09-03 15:35:15 +00:00
Sam Ford
31cf8b43a9
livecheck: support trailing comments in watchlist
I ran `brew livecheck` today to check the packages in my watchlist
and realized that it wasn't checking one package because I had added
a trailing comment after the name (and `package # Comment` isn't a
valid package name). I thought we had added support for trailing
comments when we originally added comment support years back but I
must have been mistaken.

This adds support for trailing comments in livecheck watchlist files
as part of refactoring the watchlist line parsing logic to only use
one pass (instead of multiple `#map` and `#reject` calls). This
maintains the existing behavior, where blank lines and lines starting
with `#` are skipped, but does so in a more flexible manner. For
example, the existing logic wouldn't skip a comment line that has one
or more spaces before the `#` character but this new logic will
correctly skip it.
2025-09-03 09:10:58 -04:00
Mike McQuaid
6c98d2bf1b
Merge pull request #20625 from osalbahr/config-prioritize-pretty-name
config: Remove use of `/etc/redhat-release`
2025-09-03 11:07:03 +00:00
Osama Albahrani
de8ff312e6 config: Remove use of /etc/redhat-release 2025-09-02 23:24:07 +03:00
Osama Albahrani
ce8ae46054 config: prioritize ::OS_VERSION
On the [beta Bluefin LTS](https://docs.projectbluefin.io/lts), `brew config` reports:

```console
OS: CentOS Stream release 10 (Coughlan)
```

This is due to the `/etc/redhat-release` file being prioritized over `::OS_VERSION` (`PRETTY_NAME` from `/etc/os-release`).

On Bluefin LTS, `PRETTY_NAME` is `Bluefin LTS`.

This commit reverses the order so `brew config` has better detection:

```
OS: Bluefin LTS
```

I tested on RHEL 10 and the OS line only changed in that the word
"release" was omitted:

```
$ cat /etc/redhat-release
Red Hat Enterprise Linux release 10.0 (Coughlan)
$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Red Hat Enterprise Linux 10.0 (Coughlan)"
```

- This is an improvement on my previous PR https://github.com/Homebrew/brew/pull/15788
2025-09-02 22:50:36 +03:00
Douglas Eichelberger
fb6c51da07
Enable strict typing in SystemCommand 2025-09-02 10:26:40 -07:00
Mike McQuaid
ab980dc3b7
Merge pull request #20624 from Homebrew/later_pathname_prepend
Make `Pathname.prepend WriteMkpathExtension` as late as possible.
2025-09-02 16:19:44 +00:00
Mike McQuaid
89f3c76cd7
Merge pull request #20622 from Homebrew/livecheck/SkipConditions-check-disabled-before-deprecated
SkipConditions: check disabled before deprecated
2025-09-02 16:02:40 +00:00
Mike McQuaid
e28fe444a5
Make Pathname.prepend WriteMkpathExtension as late as possible.
Let's avoid weirdness in other parts of Homebrew by moving this prepend
to be as late as possible.
2025-09-02 17:00:34 +01:00
Sam Ford
45a642c363
SkipConditions: check disabled before deprecated
We have some formulae and casks that contain both `deprecate!` and
`disable!` calls, presumably as a way of controlling the deprecation
behavior before the disable date is reached. However, once the disable
date has been reached, `Livecheck::SkipConditions` continues to skip
the package as deprecated instead of disabled. This isn't a functional
issue as the package is still skipped but it isn't accurate because
it's not being skipped as disabled.

This reorders `FORMULA_CHECKS` and `CASK_CHECKS` to run the disabled
check before the deprecated check, so the disable date will take
precedence when it's been reached.
2025-09-02 11:32:22 -04:00
Sam Ford
75350b48a3
Merge pull request #20615 from Homebrew/remove-lsr-special-case
Remove temporary audit exception for lsr
2025-09-02 14:23:48 +00:00
Mike McQuaid
bfb0a55347
Merge pull request #20621 from Homebrew/audit-container-deps
cask/audit: fix install of container deps
2025-09-02 13:09:17 +00:00
Bevan Kay
9da27e1ac2
Merge pull request #20620 from Homebrew/sponsors-maintainers-man-completions
Update manpage and completions.
2025-09-02 08:50:23 +00:00
BrewTestBot
aa40480c46
Update manpage and completions.
Autogenerated by the [sponsors-maintainers-man-completions](https://github.com/Homebrew/brew/blob/HEAD/.github/workflows/sponsors-maintainers-man-completions.yml) workflow.
2025-09-02 08:34:29 +00:00
Bevan Kay
dc28719c8e
cask/audit: fix install of container deps 2025-09-02 16:33:04 +08:00
Patrick Linnane
48170b8957
Merge pull request #20601 from Homebrew/copilot/fix-20600
Add cask support to `brew unpack` command
2025-09-02 08:13:29 +00:00
Bevan Kay
25ed81f14b
test/dev-cmd/unpack_spec: fix test 2025-09-02 15:55:09 +08:00
Bevan Kay
12ea343c9f
fix brew typecheck 2025-09-02 15:55:09 +08:00
Patrick Linnane
a8b4d115f2
brew style --fix
Signed-off-by: Patrick Linnane <patrick@linnane.io>
2025-09-02 15:55:08 +08:00
copilot-swe-agent[bot]
f973bea35c
Remove obvious comments as requested in review
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-02 15:55:08 +08:00
copilot-swe-agent[bot]
ac6f770cdc
Address review feedback: fix description, use if/else, enable quarantine, improve fetch efficiency, and use local test cask
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-02 15:55:08 +08:00
Patrick Linnane
45a2ef9159
unpack_spec: final newlines are hard for AI 2025-09-02 15:55:08 +08:00
copilot-swe-agent[bot]
6be546a5bc
Skip git logic for casks as they don't need it for patch creation
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-02 15:55:08 +08:00
copilot-swe-agent[bot]
88b7d2900a
Address fetching consistency concern and fix cask path handling
Co-authored-by: MikeMcQuaid <125011+MikeMcQuaid@users.noreply.github.com>
2025-09-02 15:55:07 +08:00
copilot-swe-agent[bot]
a9cd0c4b5d
Add cask support to brew unpack command
Co-authored-by: p-linnane <105994585+p-linnane@users.noreply.github.com>
2025-09-02 15:55:07 +08:00
copilot-swe-agent[bot]
63fee348f7
Initial plan 2025-09-02 15:55:07 +08:00
Rylan Polster
e6711c5b5f
Merge pull request #20618 from Homebrew/unlink-old-names-and-aliases-files
Unlink old `*_names.txt` and `*_aliases.txt` files before overwriting
2025-09-01 20:48:31 +00:00
Rylan Polster
244c3e0e71
Don't remove files unless they exist 2025-09-01 16:32:48 -04:00
Rylan Polster
58e1f13842
Unlink old *_names.txt and *_aliases.txt files before overwriting 2025-09-01 14:49:21 -04:00
Sam Ford
3eb57bc41d
Remove temporary audit exception for lsr
This removes the temporary `audit_download_strategy` exception for
the `lsr` formula. This was necessary to be able to merge support for
tangled.sh Git URLs, as it made the `using: :git` argument in the
`lsr` formula redundant and caused the "formula audit" check to fail
in brew CI.
2025-09-01 09:50:10 -04:00
208 changed files with 1835 additions and 813 deletions

View File

@ -93,7 +93,7 @@ jobs:
path: results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
sarif_file: results.sarif
category: zizmor

View File

@ -27,7 +27,7 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
languages: ruby
config: |
@ -35,4 +35,4 @@ jobs:
- Library/Homebrew/vendor
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3

View File

@ -52,7 +52,7 @@ jobs:
run: vale docs/
- name: Install Ruby
uses: ruby/setup-ruby@efbf473cab83af4468e8606cc33eca9281bb213f # v1.256.0
uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0
with:
bundler-cache: true
working-directory: docs

View File

@ -43,7 +43,7 @@ jobs:
persist-credentials: false
- name: Install Ruby
uses: ruby/setup-ruby@efbf473cab83af4468e8606cc33eca9281bb213f # v1.256.0
uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0
with:
bundler-cache: true
working-directory: rubydoc

View File

@ -38,7 +38,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale Issues and Pull Requests
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
@ -68,7 +68,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale `bump-formula-pr` and `bump-cask-pr` Pull Requests
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 2

View File

@ -104,7 +104,7 @@ jobs:
- name: Cache style cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.cache/Homebrew/style
path: ~/Library/Caches/Homebrew/style
key: tap-syntax-style-cache-${{ github.sha }}
restore-keys: tap-syntax-style-cache-
@ -331,7 +331,7 @@ jobs:
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
- uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
files: Library/Homebrew/test/coverage/coverage.xml
@ -495,7 +495,7 @@ jobs:
uses: Homebrew/actions/setup-homebrew@main
- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version-file: ${{ steps.set-up-homebrew.outputs.repository-path }}/Library/Homebrew/formula-analytics/.python-version

View File

@ -45,6 +45,7 @@ RUN apt-get update \
tzdata \
jq \
&& if [ "$(. /etc/lsb-release; echo "${DISTRIB_RELEASE}" | cut -d. -f1)" -ge 22 ]; then apt-get install -y --no-install-recommends skopeo; fi \
&& if [ "$(. /etc/lsb-release; echo "${DISTRIB_RELEASE}" | cut -d. -f1)" -eq 22 ]; then apt-get install -y --no-install-recommends g++-12; fi \
&& mkdir -p /etc/apt/keyrings \
&& chmod 0755 /etc /etc/apt /etc/apt/keyrings \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null \

View File

@ -6,7 +6,7 @@ GEM
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.2)
bigdecimal (3.2.3)
bindata (2.5.1)
coderay (1.1.3)
concurrent-ruby (1.3.5)
@ -57,7 +57,7 @@ GEM
redcarpet (3.6.1)
regexp_parser (2.11.2)
require-hooks (0.2.2)
rexml (3.4.2)
rexml (3.4.4)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
@ -79,7 +79,7 @@ GEM
rspec-support (3.13.5)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.80.1)
rubocop (1.80.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@ -96,11 +96,11 @@ GEM
rubocop-md (2.0.2)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
rubocop-performance (1.25.0)
rubocop-performance (1.26.0)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rspec (3.6.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rspec (3.7.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sorbet (0.10.5)
@ -124,15 +124,15 @@ GEM
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
simpleidn (0.2.3)
sorbet (0.6.12466)
sorbet-static (= 0.6.12466)
sorbet-runtime (0.6.12466)
sorbet-static (0.6.12466-aarch64-linux)
sorbet-static (0.6.12466-universal-darwin)
sorbet-static (0.6.12466-x86_64-linux)
sorbet-static-and-runtime (0.6.12466)
sorbet (= 0.6.12466)
sorbet-runtime (= 0.6.12466)
sorbet (0.6.12521)
sorbet-static (= 0.6.12521)
sorbet-runtime (0.6.12521)
sorbet-static (0.6.12521-aarch64-linux)
sorbet-static (0.6.12521-universal-darwin)
sorbet-static (0.6.12521-x86_64-linux)
sorbet-static-and-runtime (0.6.12521)
sorbet (= 0.6.12521)
sorbet-runtime (= 0.6.12521)
spoom (1.7.6)
erubi (>= 1.10.0)
prism (>= 0.28.0)
@ -154,9 +154,9 @@ GEM
thor (>= 1.2.0)
yard-sorbet
thor (1.4.0)
unicode-display_width (3.1.5)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
vernier (1.8.0)
warning (1.5.0)
yard (0.9.37)

View File

@ -17,7 +17,7 @@ module Homebrew
HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname)
TAP_MIGRATIONS_STALE_SECONDS = T.let(86400, Integer) # 1 day
DEFAULT_API_STALE_SECONDS = T.let(86400, Integer) # 1 day
sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(endpoint)
@ -37,12 +37,25 @@ module Homebrew
raise ArgumentError, "Invalid JSON file: #{Tty.underline}#{api_url}#{Tty.reset}"
end
sig { params(target: Pathname, stale_seconds: T.nilable(Integer)).returns(T::Boolean) }
def self.skip_download?(target:, stale_seconds:)
return true if Homebrew.running_as_root_but_not_owned_by_root?
return false if !target.exist? || target.empty?
return true unless stale_seconds
(Time.now - stale_seconds) < target.mtime
end
sig {
params(endpoint: String, target: Pathname, stale_seconds: Integer, download_queue: T.nilable(DownloadQueue))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
params(
endpoint: String,
target: Pathname,
stale_seconds: T.nilable(Integer),
download_queue: T.nilable(DownloadQueue),
).returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i, download_queue: nil)
stale_seconds: nil, download_queue: nil)
# Lazy-load dependency.
require "development_tools"
@ -63,12 +76,7 @@ module Homebrew
insecure_download = DevelopmentTools.ca_file_substitution_required? ||
DevelopmentTools.curl_substitution_required?
skip_download = target.exist? &&
!target.empty? &&
(!Homebrew.auto_update_command? ||
(Homebrew::EnvConfig.no_auto_update? && !Homebrew::EnvConfig.force_api_auto_update?) ||
((Time.now - stale_seconds) < target.mtime))
skip_download ||= Homebrew.running_as_root_but_not_owned_by_root?
skip_download = skip_download?(target:, stale_seconds:)
if download_queue
unless skip_download
@ -161,17 +169,28 @@ module Homebrew
require "download_queue"
Homebrew::DownloadQueue.new
end
stale_seconds = 86400 # 1 day
stale_seconds = if ENV["HOMEBREW_API_UPDATED"].present? ||
(Homebrew::EnvConfig.no_auto_update? && !Homebrew::EnvConfig.force_api_auto_update?)
nil
elsif Homebrew.auto_update_command?
Homebrew::EnvConfig.api_auto_update_secs.to_i
else
DEFAULT_API_STALE_SECONDS
end
if Homebrew::EnvConfig.use_internal_api?
Homebrew::API::Internal.fetch_formula_api!(download_queue:, stale_seconds:)
Homebrew::API::Internal.fetch_cask_api!(download_queue:, stale_seconds:)
else
Homebrew::API::Formula.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds:)
Homebrew::API::Formula.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS)
Homebrew::API::Cask.fetch_api!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds:)
Homebrew::API::Cask.fetch_tap_migrations!(download_queue:, stale_seconds: DEFAULT_API_STALE_SECONDS)
end
ENV["HOMEBREW_API_UPDATED"] = "1"
return unless download_queue
begin
@ -196,6 +215,7 @@ module Homebrew
def self.write_names_file!(names, type, regenerate:)
names_path = HOMEBREW_CACHE_API/"#{type}_names.txt"
if !names_path.exist? || regenerate
names_path.unlink if names_path.exist?
names_path.write(names.join("\n"))
return true
end
@ -210,6 +230,7 @@ module Homebrew
aliases_text = aliases.map do |alias_name, real_name|
"#{alias_name}|#{real_name}"
end
aliases_path.unlink if aliases_path.exist?
aliases_path.write(aliases_text.join("\n"))
return true
end

View File

@ -75,18 +75,18 @@ module Homebrew
end
sig {
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
def self.fetch_api!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end
sig {
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(::Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: Homebrew::API::TAP_MIGRATIONS_STALE_SECONDS)
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json", stale_seconds:, download_queue:
end

View File

@ -74,18 +74,18 @@ module Homebrew
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
def self.fetch_api!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file DEFAULT_API_FILENAME, stale_seconds:, download_queue:
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
}
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: Homebrew::API::TAP_MIGRATIONS_STALE_SECONDS)
def self.fetch_tap_migrations!(download_queue: nil, stale_seconds: nil)
Homebrew::API.fetch_json_api_file "formula_tap_migrations.jws.json", stale_seconds:, download_queue:
end

View File

@ -56,20 +56,20 @@ module Homebrew
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T::Hash[String, T.untyped], T::Boolean])
}
def self.fetch_formula_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
json_contents, updated = (Homebrew::API.fetch_json_api_file formula_endpoint, stale_seconds:, download_queue:)
def self.fetch_formula_api!(download_queue: nil, stale_seconds: nil)
json_contents, updated = Homebrew::API.fetch_json_api_file(formula_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end
sig {
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: Integer)
params(download_queue: T.nilable(Homebrew::DownloadQueue), stale_seconds: T.nilable(Integer))
.returns([T::Hash[String, T.untyped], T::Boolean])
}
def self.fetch_cask_api!(download_queue: nil, stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
json_contents, updated = (Homebrew::API.fetch_json_api_file cask_endpoint, stale_seconds:, download_queue:)
def self.fetch_cask_api!(download_queue: nil, stale_seconds: nil)
json_contents, updated = Homebrew::API.fetch_json_api_file(cask_endpoint, stale_seconds:, download_queue:)
[T.cast(json_contents, T::Hash[String, T.untyped]), updated]
end

View File

@ -10,13 +10,13 @@ module Homebrew
def initialize(url, name, version, **meta)
super
@target = T.let(meta.fetch(:target), Pathname)
@stale_seconds = T.let(meta.fetch(:stale_seconds), Integer)
@stale_seconds = T.let(meta[:stale_seconds], T.nilable(Integer))
end
sig { override.params(timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
def fetch(timeout: nil)
with_context quiet: quiet? do
Homebrew::API.fetch_json_api_file(url, target: cached_location, stale_seconds: meta.fetch(:stale_seconds))
Homebrew::API.fetch_json_api_file(url, target: cached_location, stale_seconds: meta[:stale_seconds])
end
cached_location
end
@ -30,7 +30,7 @@ module Homebrew
class JSONDownload
include Downloadable
sig { params(url: String, target: Pathname, stale_seconds: Integer).void }
sig { params(url: String, target: Pathname, stale_seconds: T.nilable(Integer)).void }
def initialize(url, target:, stale_seconds:)
super()
@url = T.let(URL.new(url, using: API::JSONDownloadStrategy, target:, stale_seconds:), URL)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
# This script is loaded by formula_installer as a separate instance.
@ -23,28 +23,40 @@ require "extend/pathname/write_mkpath_extension"
class Build
include Utils::Output::Mixin
attr_reader :formula, :deps, :reqs, :args
sig { returns(Formula) }
attr_reader :formula
sig { returns(T::Array[Dependency]) }
attr_reader :deps
sig { returns(Requirements) }
attr_reader :reqs
sig { returns(Homebrew::Cmd::InstallCmd::Args) }
attr_reader :args
sig { params(formula: Formula, options: Options, args: Homebrew::Cmd::InstallCmd::Args).void }
def initialize(formula, options, args:)
@formula = formula
@formula.build = BuildOptions.new(options, formula.options)
@args = args
@args = T.let(args, Homebrew::Cmd::InstallCmd::Args)
@deps = T.let([], T::Array[Dependency])
@reqs = T.let(Requirements.new, Requirements)
if args.ignore_dependencies?
@deps = []
@reqs = []
else
@deps = expand_deps
@reqs = expand_reqs
end
return if args.ignore_dependencies?
@deps = expand_deps
@reqs = expand_reqs
end
sig { params(dependent: Formula).returns(BuildOptions) }
def effective_build_options_for(dependent)
args = dependent.build.used_options
args |= Tab.for_formula(dependent).used_options
BuildOptions.new(args, dependent.options)
end
sig { returns(Requirements) }
def expand_reqs
formula.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent)
@ -54,6 +66,7 @@ class Build
end
end
sig { returns(T::Array[Dependency]) }
def expand_deps
formula.recursive_dependencies do |dependent, dep|
build = effective_build_options_for(dependent)
@ -67,6 +80,7 @@ class Build
end
end
sig { void }
def install
formula_deps = deps.map(&:to_formula)
keg_only_deps = formula_deps.select(&:keg_only?)
@ -79,7 +93,7 @@ class Build
ENV.activate_extensions!(env: args.env)
if superenv?(args.env)
superenv = T.cast(ENV, Superenv)
superenv = ENV
superenv.keg_only_deps = keg_only_deps
superenv.deps = formula_deps
superenv.run_time_deps = run_time_deps
@ -183,6 +197,8 @@ class Build
(formula.logs/"00.options.out").write \
"#{formula.full_name} #{formula.build.used_options.sort.join(" ")}".strip
Pathname.prepend WriteMkpathExtension
formula.install
stdlibs = detect_stdlibs
@ -190,7 +206,7 @@ class Build
tab.write
# Find and link metafiles
formula.prefix.install_metafiles formula.buildpath
formula.prefix.install_metafiles T.must(formula.buildpath)
formula.prefix.install_metafiles formula.libexec if formula.libexec.exist?
normalize_pod2man_outputs!(formula)
@ -200,6 +216,7 @@ class Build
end
end
sig { returns(T::Array[Symbol]) }
def detect_stdlibs
keg = Keg.new(formula.prefix)
@ -209,13 +226,15 @@ class Build
keg.detect_cxx_stdlibs(skip_executables: true)
end
sig { params(formula: Formula).void }
def fixopt(formula)
path = if formula.linked_keg.directory? && formula.linked_keg.symlink?
formula.linked_keg.resolved_path
elsif formula.prefix.directory?
formula.prefix
elsif (kids = formula.rack.children).size == 1 && kids.first.directory?
kids.first
elsif (children = formula.rack.children.presence) && children.size == 1 &&
(first_child = children.first.presence) && first_child.directory?
first_child
else
raise
end
@ -224,6 +243,7 @@ class Build
raise "#{formula.opt_prefix} not present or broken\nPlease reinstall #{formula.full_name}. Sorry :("
end
sig { params(formula: Formula).void }
def normalize_pod2man_outputs!(formula)
keg = Keg.new(formula.prefix)
keg.normalize_pod2man_outputs!
@ -243,12 +263,10 @@ begin
trap("INT", old_trap)
formula = args.named.to_formulae.first
formula = args.named.to_formulae.fetch(0)
options = Options.create(args.flags_only)
build = Build.new(formula, options, args:)
Pathname.prepend WriteMkpathExtension
build.install
# Any exception means the build did not complete.
# The `case` for what to do per-exception class is further down.

View File

@ -1,11 +1,11 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
# Settings for the build environment.
class BuildEnvironment
sig { params(settings: Symbol).void }
def initialize(*settings)
@settings = Set.new(settings)
@settings = T.let(Set.new(settings), T::Set[Symbol])
end
sig { params(args: T::Enumerable[Symbol]).returns(T.self_type) }
@ -29,16 +29,17 @@ class BuildEnvironment
module DSL
# Initialise @env for each class which may use this DSL (e.g. each formula subclass).
# `env` may never be called and it needs to be initialised before the class is frozen.
sig { params(child: T.untyped).void }
def inherited(child)
super
child.instance_eval do
@env = BuildEnvironment.new
@env = T.let(BuildEnvironment.new, T.nilable(BuildEnvironment))
end
end
sig { params(settings: Symbol).returns(BuildEnvironment) }
def env(*settings)
@env.merge(settings)
T.must(@env).merge(settings)
end
end

View File

@ -1,11 +1,12 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
# Options for a formula build.
class BuildOptions
sig { params(args: Options, options: Options).void }
def initialize(args, options)
@args = args
@options = options
@args = T.let(args, Options)
@options = T.let(options, Options)
end
# True if a {Formula} is being built with a specific option.
@ -29,8 +30,13 @@ class BuildOptions
# args << "--with-example1"
# end
# ```
sig { params(val: T.any(String, Requirement, Dependency)).returns(T::Boolean) }
def with?(val)
option_names = val.respond_to?(:option_names) ? val.option_names : [val]
option_names = if val.is_a?(String)
[val]
else
val.option_names
end
option_names.any? do |name|
if option_defined? "with-#{name}"
@ -50,11 +56,13 @@ class BuildOptions
# ```ruby
# args << "--no-spam-plz" if build.without? "spam"
# ```
sig { params(val: T.any(String, Requirement, Dependency)).returns(T::Boolean) }
def without?(val)
!with?(val)
end
# True if a {Formula} is being built as a bottle (i.e. binary package).
sig { returns(T::Boolean) }
def bottle?
include? "build-bottle"
end
@ -75,6 +83,7 @@ class BuildOptions
# args << "--and-a-cold-beer" if build.with? "cold-beer"
# end
# ```
sig { returns(T::Boolean) }
def head?
include? "HEAD"
end
@ -87,29 +96,35 @@ class BuildOptions
# ```ruby
# args << "--some-feature" if build.stable?
# ```
sig { returns(T::Boolean) }
def stable?
!head?
end
# True if the build has any arguments or options specified.
sig { returns(T::Boolean) }
def any_args_or_options?
!@args.empty? || !@options.empty?
end
sig { returns(Options) }
def used_options
@options & @args
end
sig { returns(Options) }
def unused_options
@options - @args
end
private
sig { params(name: String).returns(T::Boolean) }
def include?(name)
@args.include?("--#{name}")
end
sig { params(name: String).returns(T::Boolean) }
def option_defined?(name)
@options.include? name
end

View File

@ -4,6 +4,8 @@
module Homebrew
# Class handling architecture-specific version information.
class BumpVersionParser
VERSION_SYMBOLS = [:general, :arm, :intel].freeze
sig { returns(T.nilable(T.any(Version, Cask::DSL::Version))) }
attr_reader :arm, :general, :intel

View File

@ -183,7 +183,7 @@ module Homebrew
require "bundle/tap_dumper"
@dsl ||= Brewfile.read(global:, file:)
kept_formulae = self.kept_formulae(global:, file:).filter_map(&method(:lookup_formula))
kept_formulae = self.kept_formulae(global:, file:).filter_map { lookup_formula(_1) }
kept_taps = @dsl.entries.select { |e| e.type == :tap }.map(&:name)
kept_taps += kept_formulae.filter_map(&:tap).map(&:name)
current_taps = Homebrew::Bundle::TapDumper.tap_names

View File

@ -40,7 +40,7 @@ module Homebrew
@formulae_by_full_name ||= {}
if name.nil?
formulae = Formula.installed.map(&method(:add_formula))
formulae = Formula.installed.map { add_formula(_1) }
sort!(formulae)
return @formulae_by_full_name
end

View File

@ -172,7 +172,7 @@ module Cask
.stdout.lines.drop(1) # skip stdout column headers
.filter_map do |line|
pid, _state, id = line.chomp.split(/\s+/)
id if pid.to_i.nonzero? && id.match?(regex)
id if pid.to_i.nonzero? && T.must(id).match?(regex)
end
end
@ -460,9 +460,9 @@ module Cask
def trash_paths(*paths, command: nil, **_)
return if paths.empty?
stdout, = system_command HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift",
args: paths,
print_stderr: Homebrew::EnvConfig.developer?
stdout = system_command(HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift",
args: paths,
print_stderr: Homebrew::EnvConfig.developer?).stdout
trashed, _, untrashable = stdout.partition("\n")
trashed = trashed.split(":")

View File

@ -79,8 +79,9 @@ module Cask
# Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles.
def add_altname_metadata(file, altname, command: nil)
return if altname.to_s.casecmp(file.basename.to_s).zero?
sig { params(file: Pathname, altname: Pathname, command: T.class_of(SystemCommand)).returns(T.nilable(SystemCommand::Result)) }
def add_altname_metadata(file, altname, command:)
return if altname.to_s.casecmp(file.basename.to_s)&.zero?
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
altnames = command.run("/usr/bin/xattr",
@ -108,3 +109,5 @@ module Cask
end
end
end
require "extend/os/cask/artifact/relocated"

View File

@ -543,6 +543,7 @@ module Cask
print_stderr: false)
else
add_error "Unknown artifact type: #{artifact.class}", location: url.location
next
end
next false if result.success?
@ -622,15 +623,17 @@ module Cask
}.compact
Homebrew::Install.perform_preinstall_checks_once
valid_formula_installers = Homebrew::Install.fetch_formulae(primary_container.dependencies)
primary_container.dependencies.each do |dep|
next unless valid_formula_installers.include?(dep)
fi = FormulaInstaller.new(
formula_installers = primary_container.dependencies.map do |dep|
FormulaInstaller.new(
dep,
**install_options,
)
end
valid_formula_installers = Homebrew::Install.fetch_formulae(formula_installers)
formula_installers.each do |fi|
next unless valid_formula_installers.include?(fi)
fi.install
fi.finish
end
@ -695,7 +698,7 @@ module Cask
add_error "No binaries in App: #{artifact.source}", location: url.location if files.empty?
main_binary = get_plist_main_binary(path)
main_binary ||= files.first
main_binary ||= files.fetch(0)
system_command("lipo", args: ["-archs", main_binary], print_stderr: false)
when Artifact::Binary
@ -753,9 +756,9 @@ module Cask
latest_version = Homebrew::Livecheck.latest_version(
cask,
referenced_formula_or_cask: referenced_cask,
)&.fetch(:latest)
)&.fetch(:latest, nil)
return :auto_detected if cask.version.to_s == latest_version.to_s
return :auto_detected if latest_version && (cask.version.to_s == latest_version.to_s)
add_error "Version '#{cask.version}' differs from '#{latest_version}' retrieved by livecheck."

View File

@ -6,6 +6,7 @@ require "cask/cask"
require "uri"
require "utils/curl"
require "utils/output"
require "utils/path"
require "extend/hash/keys"
require "api"
@ -112,9 +113,7 @@ module Cask
return unless path.expand_path.exist?
return if invalid_path?(path)
return if Homebrew::EnvConfig.forbid_packages_from_paths? &&
!path.realpath.to_s.start_with?("#{Caskroom.path}/", "#{HOMEBREW_LIBRARY}/Taps/")
return unless ::Utils::Path.loadable_package_path?(path, :cask)
new(path)
end

View File

@ -49,7 +49,7 @@ module Cask
SystemCommand.run("/bin/mkdir", args: ["-p", path], sudo:)
SystemCommand.run("/bin/chmod", args: ["g+rwx", path], sudo:)
SystemCommand.run("/usr/sbin/chown", args: [User.current, path], sudo:)
SystemCommand.run("/usr/sbin/chown", args: [User.current.to_s, path], sudo:)
SystemCommand.run("/usr/bin/chgrp", args: ["admin", path], sudo:)
end

View File

@ -733,7 +733,7 @@ module Homebrew
formulae_names = removable_formulae.map(&:full_name).sort
verb = dry_run ? "Would autoremove" : "Autoremoving"
oh1 "#{verb} #{formulae_names.count} unneeded #{Utils.pluralize("formula", formulae_names.count, plural: "e")}:"
oh1 "#{verb} #{formulae_names.count} unneeded #{Utils.pluralize("formula", formulae_names.count)}:"
puts formulae_names.join("\n")
return if dry_run

View File

@ -127,7 +127,7 @@ module Homebrew
conflicts "--all", "--no-vscode"
conflicts "--vscode", "--no-vscode"
conflicts "--install", "--upgrade"
conflicts "--file=", "--global"
conflicts "--file", "--global"
named_args %w[install dump cleanup check exec list sh env edit]
end

View File

@ -77,7 +77,6 @@ module Homebrew
description: "Show the size of installed formulae and casks."
conflicts "--installed", "--eval-all"
conflicts "--installed", "--all"
conflicts "--formula", "--cask"
conflicts "--fetch-manifest", "--cask"
conflicts "--fetch-manifest", "--json"

View File

@ -233,8 +233,8 @@ module Homebrew
.map(&:name)
next if dep_names.blank?
ohai "Would install #{::Utils.pluralize("dependenc", dep_names.count, plural: "ies", singular: "y",
include_count: true)} for #{cask.full_name}:"
ohai "Would install #{::Utils.pluralize("dependency", dep_names.count, include_count: true)} " \
"for #{cask.full_name}:"
puts dep_names.join(" ")
end
return

View File

@ -23,7 +23,7 @@ module Homebrew
flag "--command=",
description: "Show options for the specified <command>."
conflicts "--installed", "--all", "--command"
conflicts "--command", "--installed", "--eval-all"
named_args :formula
end
@ -47,7 +47,7 @@ module Homebrew
puts
end
elsif args.no_named?
raise FormulaUnspecifiedError
raise UsageError, "`brew options` needs a formula or `--eval-all` passed or `HOMEBREW_EVAL_ALL=1` set!"
else
puts_options args.named.to_formulae
end

View File

@ -37,8 +37,7 @@ module Homebrew
description: "Search for casks."
switch "--desc",
description: "Search for formulae with a description matching <text> and casks with " \
"a name or description matching <text>.",
depends_on: "--eval-all"
"a name or description matching <text>."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions.",

View File

@ -68,7 +68,7 @@ module Homebrew
description: "Output as JSON."
conflicts "--all", "--file"
conflicts "--max-wait=", "--no-wait"
conflicts "--max-wait", "--no-wait"
named_args %w[list info run start stop kill restart cleanup]
end

View File

@ -57,7 +57,7 @@ module Homebrew
end
info = Utils.pluralize("tap", tap_count, include_count: true)
info += ", #{private_count} private"
info += ", #{Utils.pluralize("formula", formula_count, plural: "e", include_count: true)}"
info += ", #{Utils.pluralize("formula", formula_count, include_count: true)}"
info += ", #{Utils.pluralize("command", command_count, include_count: true)}"
info += ", #{HOMEBREW_TAP_DIRECTORY.dup.abv}" if HOMEBREW_TAP_DIRECTORY.directory?
puts info

View File

@ -842,7 +842,7 @@ class ReporterHub
msg = ""
if outdated_formulae.positive?
noun = Utils.pluralize("formula", outdated_formulae, plural: "e")
noun = Utils.pluralize("formula", outdated_formulae)
msg += "#{Tty.bold}#{outdated_formulae}#{Tty.reset} outdated #{noun}"
end

View File

@ -55,7 +55,7 @@ module Homebrew
description: "Include only casks."
conflicts "--formula", "--cask"
conflicts "--installed", "--all"
conflicts "--installed", "--eval-all"
conflicts "--missing", "--installed"
named_args :formula, min: 1

View File

@ -128,7 +128,7 @@ module Commands
OFFICIAL_CMD_TAPS.flat_map do |tap_name, cmds|
tap = Tap.fetch(tap_name)
tap.install(quiet:) unless tap.installed?
cmds.map(&method(:external_ruby_v2_cmd_path)).compact
cmds.map { external_ruby_v2_cmd_path(_1) }.compact
end
end

View File

@ -28,6 +28,7 @@ module DeprecateDisable
no_longer_meets_criteria: "no longer meets the criteria for acceptable casks",
unmaintained: "is not maintained upstream",
fails_gatekeeper_check: "does not pass the macOS Gatekeeper check",
unreachable: "is no longer reliably reachable upstream",
# odeprecate: remove the unsigned reason in a future release
unsigned: "is unsigned or does not meet signature requirements",
}.freeze, T::Hash[Symbol, String])

View File

@ -83,7 +83,7 @@ class DescriptionCacheStore < CacheStore
def delete_from_formula_names!(formula_names)
return if database.empty?
formula_names.each(&method(:delete!))
formula_names.each { delete!(_1) }
end
alias delete_from_cask_tokens! delete_from_formula_names!

View File

@ -93,11 +93,11 @@ module Homebrew
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--installed", "--eval-all"
conflicts "--only", "--except"
conflicts "--only-cops", "--except-cops", "--strict"
conflicts "--only-cops", "--except-cops", "--only"
conflicts "--formula", "--cask"
conflicts "--installed", "--all"
named_args [:formula, :cask], without_api: true
end
@ -294,9 +294,7 @@ module Homebrew
errors_summary = Utils.pluralize("problem", total_problems_count, include_count: true)
error_sources = []
if formula_count.positive?
error_sources << Utils.pluralize("formula", formula_count, plural: "e", include_count: true)
end
error_sources << Utils.pluralize("formula", formula_count, include_count: true) if formula_count.positive?
error_sources << Utils.pluralize("cask", cask_count, include_count: true) if cask_count.positive?
error_sources << Utils.pluralize("tap", tap_count, include_count: true) if tap_count.positive?

View File

@ -182,7 +182,8 @@ module Homebrew
sig {
params(old_keys: T::Array[String], old_bottle_spec: BottleSpecification,
new_bottle_hash: T::Hash[String, T.untyped]).returns(T::Array[T::Array[String]])
new_bottle_hash: T::Hash[String, T.untyped])
.returns([T::Array[String], T::Array[T::Hash[Symbol, T.any(String, Symbol)]]])
}
def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
mismatches = []
@ -409,7 +410,7 @@ module Homebrew
bottle_tag, rebuild = if local_bottle_json
_, tag_string, rebuild_string = Utils::Bottles.extname_tag_rebuild(formula.local_bottle_path.to_s)
[tag_string.to_sym, rebuild_string.to_i]
[T.must(tag_string).to_sym, rebuild_string.to_i]
end
bottle_tag = if bottle_tag
@ -860,8 +861,8 @@ module Homebrew
end
sig {
params(formula: Formula, formula_ast: Utils::AST::FormulaAST,
bottle_hash: T::Hash[String, T.untyped]).returns(T.nilable(T::Array[String]))
params(formula: Formula, formula_ast: Utils::AST::FormulaAST, bottle_hash: T::Hash[String, T.untyped])
.returns(T.nilable(T::Array[T::Hash[Symbol, T.any(String, Symbol)]]))
}
def old_checksums(formula, formula_ast, bottle_hash)
bottle_node = T.cast(formula_ast.bottle_block, T.nilable(RuboCop::AST::BlockNode))

View File

@ -49,8 +49,8 @@ module Homebrew
description: "Use the specified GitHub organization for forking."
conflicts "--dry-run", "--write"
conflicts "--version=", "--version-arm="
conflicts "--version=", "--version-intel="
conflicts "--version", "--version-arm"
conflicts "--version", "--version-intel"
named_args :cask, number: 1, without_api: true
end

View File

@ -9,6 +9,8 @@ require "utils/repology"
module Homebrew
module DevCmd
class Bump < AbstractCommand
NEWER_THAN_UPSTREAM_MSG = " (newer than upstream)"
class VersionBumpInfo < T::Struct
const :type, Symbol
const :multiple_versions, T::Boolean
@ -16,6 +18,7 @@ module Homebrew
const :current_version, BumpVersionParser
const :repology_latest, T.any(String, Version)
const :new_version, BumpVersionParser
const :newer_than_upstream, T::Hash[Symbol, T::Boolean], default: {}
const :duplicate_pull_requests, T.nilable(T.any(T::Array[String], String))
const :maybe_duplicate_pull_requests, T.nilable(T.any(T::Array[String], String))
end
@ -57,10 +60,10 @@ module Homebrew
switch "--bump-synced",
description: "Bump additional formulae marked as synced with the given formulae."
conflicts "--cask", "--formula"
conflicts "--tap=", "--installed"
conflicts "--tap=", "--no-autobump"
conflicts "--eval-all", "--installed"
conflicts "--formula", "--cask"
conflicts "--tap", "--installed"
conflicts "--tap", "--no-autobump"
conflicts "--installed", "--eval-all"
conflicts "--installed", "--auto"
conflicts "--no-pull-requests", "--open-pr"
@ -315,6 +318,7 @@ module Homebrew
new_versions = {}
repology_latest = repositories.present? ? Repology.latest_version(repositories) : "not found"
repology_latest_is_a_version = repology_latest.is_a?(Version)
# When blocks are absent, arch is not relevant. For consistency, we simulate the arm architecture.
arch_options = is_cask_with_blocks ? OnSystem::ARCH_OPTIONS : [:arm]
@ -331,23 +335,31 @@ module Homebrew
loaded_formula_or_cask = Cask::CaskLoader.load(formula_or_cask.sourcefile_path)
current_version_value = Version.new(loaded_formula_or_cask.version)
end
formula_or_cask_has_livecheck = loaded_formula_or_cask.livecheck_defined?
livecheck_latest = livecheck_result(loaded_formula_or_cask)
livecheck_latest_is_a_version = livecheck_latest.is_a?(Version)
new_version_value = if (livecheck_latest.is_a?(Version) &&
new_version_value = if (livecheck_latest_is_a_version &&
Livecheck::LivecheckVersion.create(formula_or_cask, livecheck_latest) >=
Livecheck::LivecheckVersion.create(formula_or_cask, current_version_value)) ||
current_version_value == "latest"
livecheck_latest
elsif livecheck_latest.is_a?(String) && livecheck_latest.start_with?("skipped")
"skipped"
elsif repology_latest.is_a?(Version) &&
elsif repology_latest_is_a_version &&
!formula_or_cask_has_livecheck &&
repology_latest > current_version_value &&
!loaded_formula_or_cask.livecheck_defined? &&
current_version_value != "latest"
repology_latest
end.presence
# Fall back to the upstream version if there isn't a new version
# value at this point, as this will allow us to surface an upstream
# version that's lower than the current version.
new_version_value ||= livecheck_latest if livecheck_latest_is_a_version
new_version_value ||= repology_latest if repology_latest_is_a_version && !formula_or_cask_has_livecheck
# Store old and new versions
old_versions[version_key] = current_version_value
new_versions[version_key] = new_version_value
@ -380,10 +392,22 @@ module Homebrew
new_version = BumpVersionParser.new(general: "unable to get versions")
end
newer_than_upstream = {}
BumpVersionParser::VERSION_SYMBOLS.each do |version_type|
new_version_value = new_version.send(version_type)
next unless new_version_value.is_a?(Version)
newer_than_upstream[version_type] =
(current_version_value = current_version.send(version_type)).is_a?(Version) &&
(Livecheck::LivecheckVersion.create(formula_or_cask, current_version_value) >
Livecheck::LivecheckVersion.create(formula_or_cask, new_version_value))
end
if !args.no_pull_requests? &&
(new_version.general != "unable to get versions") &&
(new_version.general != "skipped") &&
(new_version != current_version)
(new_version != current_version) &&
!newer_than_upstream.all? { |_k, v| v == true }
# We use the ARM version for the pull request version. This is
# consistent with the behavior of bump-cask-pr.
pull_request_version = if multiple_versions
@ -410,6 +434,7 @@ module Homebrew
current_version:,
repology_latest:,
new_version:,
newer_than_upstream:,
duplicate_pull_requests:,
maybe_duplicate_pull_requests:,
)
@ -432,8 +457,8 @@ module Homebrew
new_version = version_info.new_version
repology_latest = version_info.repology_latest
# Check if all versions are equal
versions_equal = (new_version == current_version)
all_newer_than_upstream = version_info.newer_than_upstream.all? { |_k, v| v == true }
title_name = ambiguous_cask ? "#{name} (cask)" : name
title = if (repology_latest == current_version.general || !repology_latest.is_a?(Version)) && versions_equal
@ -444,10 +469,13 @@ module Homebrew
# Conditionally format output based on type of formula_or_cask
current_versions = if version_info.multiple_versions
"arm: #{current_version.arm}
intel: #{current_version.intel}"
"arm: #{current_version.arm}" \
"#{NEWER_THAN_UPSTREAM_MSG if version_info.newer_than_upstream[:arm]}" \
"\n intel: #{current_version.intel}" \
"#{NEWER_THAN_UPSTREAM_MSG if version_info.newer_than_upstream[:intel]}"
else
current_version.general.to_s
newer_than_upstream_general = version_info.newer_than_upstream[:general]
"#{current_version.general}#{NEWER_THAN_UPSTREAM_MSG if newer_than_upstream_general}"
end
current_versions << " (deprecated)" if formula_or_cask.deprecated?
@ -482,7 +510,8 @@ module Homebrew
if !args.no_pull_requests? &&
(new_version.general != "unable to get versions") &&
(new_version.general != "skipped") &&
!versions_equal
!versions_equal &&
!all_newer_than_upstream
if duplicate_pull_requests
duplicate_pull_requests_text = duplicate_pull_requests
elsif maybe_duplicate_pull_requests
@ -501,7 +530,8 @@ module Homebrew
if !args.open_pr? ||
(new_version.general == "unable to get versions") ||
(new_version.general == "skipped")
(new_version.general == "skipped") ||
all_newer_than_upstream
return
end

View File

@ -6,13 +6,23 @@ require "abstract_command"
module Homebrew
module DevCmd
class Contributions < AbstractCommand
PRIMARY_REPOS = T.let(%w[brew core cask].freeze, T::Array[String])
SUPPORTED_REPOS = T.let([
PRIMARY_REPOS,
OFFICIAL_CMD_TAPS.keys.map { |t| t.delete_prefix("homebrew/") },
OFFICIAL_CASK_TAPS.reject { |t| t == "cask" },
].flatten.freeze, T::Array[String])
MAX_REPO_COMMITS = 1000
PRIMARY_REPOS = T.let(%w[
Homebrew/brew
Homebrew/homebrew-core
Homebrew/homebrew-cask
].freeze, T::Array[String])
ALL_REPOS = T.let([
*PRIMARY_REPOS,
*OFFICIAL_CMD_TAPS.keys,
].freeze, T::Array[String])
CONTRIBUTION_TYPES = T.let({
merged_pr_author: "merged PR author",
approved_pr_review: "approved PR reviewer",
committer: "commit author or committer",
coauthor: "commit coauthor",
}.freeze, T::Hash[Symbol, String])
MAX_COMMITS = T.let(1000, Integer)
MAX_PR_SEARCH = T.let(100, Integer)
cmd_args do
usage_banner "`contributions` [`--user=`] [`--repositories=`] [`--from=`] [`--to=`] [`--csv`]"
@ -24,10 +34,17 @@ module Homebrew
"contributions from. Omitting this flag searches Homebrew maintainers."
comma_array "--repositories",
description: "Specify a comma-separated list of repositories to search. " \
"Supported repositories: #{SUPPORTED_REPOS.map { |t| "`#{t}`" }.to_sentence}. " \
"All repositories must be under the same user or organisation. " \
"Omitting this flag, or specifying `--repositories=primary`, searches only the " \
"main repositories: `brew`, `core`, `cask`. " \
"Specifying `--repositories=all` searches all repositories. "
"main repositories: `Homebrew/brew`, `Homebrew/homebrew-core`, " \
"`Homebrew/homebrew-cask`. Specifying `--repositories=all` searches all " \
"non-deprecated Homebrew repositories. "
flag "--organisation=", "--organization=", "--org=",
description: "Specify the organisation to populate sources repositories from. " \
"Omitting this flag searches the Homebrew primary repositories."
flag "--team=",
description: "Specify the team to populate users from. " \
"The first part of the team name will be used as the organisation."
flag "--from=",
description: "Date (ISO 8601 format) to start searching contributions. " \
"Omitting this flag searches the past year."
@ -35,54 +52,93 @@ module Homebrew
description: "Date (ISO 8601 format) to stop searching contributions."
switch "--csv",
description: "Print a CSV of contributions across repositories over the time period."
conflicts "--organisation", "--repositories"
conflicts "--organisation", "--team"
conflicts "--user", "--team"
end
sig { override.void }
def run
odie "Cannot get contributions as `$HOMEBREW_NO_GITHUB_API` is set!" if Homebrew::EnvConfig.no_github_api?
Homebrew.install_bundler_gems!(groups: ["contributions"]) if args.csv?
require "utils/github"
results = {}
grand_totals = {}
from = args.from.presence || Date.today.prev_year.iso8601
to = args.to.presence || (Date.today + 1).iso8601
organisation = nil
repos = T.must(
if args.repositories.blank? || args.repositories&.include?("primary")
PRIMARY_REPOS
elsif args.repositories&.include?("all")
SUPPORTED_REPOS
else
args.repositories
end,
)
repos.each do |repo|
if SUPPORTED_REPOS.exclude?(repo)
odie "Unsupported repository: #{repo}. Try one of #{SUPPORTED_REPOS.join(", ")}."
users = if (team = args.team.presence)
team_sections = team.split("/")
organisation = team_sections.first.presence
team_name = team_sections.last.presence
if team_sections.length != 2 || organisation.nil? || team_name.nil?
odie "Team must be in the format `organisation/team`!"
end
puts "Getting members for #{organisation}/#{team_name}..." if args.verbose?
GitHub.members_by_team(organisation, team_name).keys
elsif (users = args.user.presence)
users
else
puts "Getting members for Homebrew/maintainers..." if args.verbose?
GitHub.members_by_team("Homebrew", "maintainers").keys
end
from = args.from.presence || Date.today.prev_year.iso8601
repositories = if (org = organisation.presence) || (org = args.organisation.presence)
organisation = org
puts "Getting repositories for #{organisation}..." if args.verbose?
GitHub.organisation_repositories(organisation, from, to, args.verbose?)
elsif (repos = args.repositories.presence) && repos.length == 1 && (first_repository = repos.first)
case first_repository
when "primary"
PRIMARY_REPOS
when "all"
ALL_REPOS
else
Array(first_repository)
end
elsif (repos = args.repositories.presence)
organisations = repos.map { |repository| repository.split("/").first }.uniq
odie "All repositories must be under the same user or organisation!" if organisations.length > 1
contribution_types = [:author, :committer, :coauthor, :review]
repos
else
PRIMARY_REPOS
end
organisation ||= T.must(repositories.fetch(0).split("/").first)
require "utils/github"
users = args.user.presence || GitHub.members_by_team("Homebrew", "maintainers").keys
users.each do |username|
# TODO: Using the GitHub username to scan the `git log` undercounts some
# contributions as people might not always have configured their Git
# committer details to match the ones on GitHub.
# TODO: Switch to using the GitHub APIs instead of `git log` if
# they ever support trailers.
results[username] = scan_repositories(repos, username, from:)
results[username] = scan_repositories(organisation, repositories, username, from:, to:)
grand_totals[username] = total(results[username])
contributions = contribution_types.filter_map do |type|
search_types = [:merged_pr_author, :approved_pr_review].freeze
greater_than_total = T.let(false, T::Boolean)
contributions = CONTRIBUTION_TYPES.keys.filter_map do |type|
type_count = grand_totals[username][type]
next if type_count.to_i.zero?
next if type_count.zero?
"#{Utils.pluralize("time", type_count, include_count: true)} (#{type})"
count_prefix = ""
if (search_types.include?(type) && type_count == MAX_PR_SEARCH) ||
(type == :committer && type_count == MAX_COMMITS)
greater_than_total ||= true
count_prefix = ">="
end
pretty_type = CONTRIBUTION_TYPES.fetch(type)
"#{count_prefix}#{Utils.pluralize("time", type_count, include_count: true)} (#{pretty_type})"
end
contributions <<
"#{Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)} (total)"
total = Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)
total_prefix = ">=" if greater_than_total
contributions << "#{total_prefix}#{total} (total)"
contributions_string = [
"#{username} contributed",
@ -104,12 +160,16 @@ module Homebrew
private
sig { params(repo: String).returns(Pathname) }
def find_repo_path_for_repo(repo)
return HOMEBREW_REPOSITORY if repo == "brew"
sig { params(repository: String).returns([T.nilable(Pathname), T.nilable(Tap)]) }
def repository_path_and_tap(repository)
return [HOMEBREW_REPOSITORY, nil] if repository == "Homebrew/brew"
return [nil, nil] if repository.exclude?("/homebrew-")
require "tap"
Tap.fetch("homebrew", repo).path
tap = Tap.fetch(repository)
return [nil, nil] if tap.user == "Homebrew" && DEPRECATED_OFFICIAL_TAPS.include?(tap.repository)
[tap.path, tap]
end
sig { params(from: T.nilable(String), to: T.nilable(String)).returns(String) }
@ -130,7 +190,7 @@ module Homebrew
require "csv"
CSV.generate do |csv|
csv << %w[user repo author committer coauthor review total]
csv << ["user", "repository", *CONTRIBUTION_TYPES.keys, "total"]
totals.sort_by { |_, v| -v.values.sum }.each do |user, total|
csv << grand_total_row(user, total)
@ -138,63 +198,67 @@ module Homebrew
end
end
sig {
params(
user: String,
grand_total: T::Hash[Symbol, Integer],
).returns(
[String, String, T.nilable(Integer), T.nilable(Integer), T.nilable(Integer), T.nilable(Integer), Integer],
)
}
sig { params(user: String, grand_total: T::Hash[Symbol, Integer]).returns(T::Array[T.any(String, T.nilable(Integer))]) }
def grand_total_row(user, grand_total)
[
user,
"all",
grand_total[:author],
grand_total[:committer],
grand_total[:coauthor],
grand_total[:review],
grand_total.values.sum,
]
grand_totals = grand_total.slice(*CONTRIBUTION_TYPES.keys).values
[user, "all", *grand_totals, grand_totals.sum]
end
sig {
params(
repos: T::Array[String],
person: String,
from: String,
organisation: String,
repositories: T::Array[String],
person: String,
from: String,
to: String,
).returns(T::Hash[Symbol, T.untyped])
}
def scan_repositories(repos, person, from:)
def scan_repositories(organisation, repositories, person, from:, to:)
data = {}
return data if repos.blank?
return data if repositories.blank?
require "tap"
require "utils/github"
repos.each do |repo|
repo_path = find_repo_path_for_repo(repo)
tap = Tap.fetch("homebrew", repo)
unless repo_path.exist?
opoo "Repository #{repo} not yet tapped! Tapping it now..."
max = MAX_COMMITS
verbose = args.verbose?
puts "Querying pull requests for #{person} in #{organisation}..." if args.verbose?
organisation_merged_prs = \
GitHub.search_merged_pull_requests_in_user_or_organisation(organisation, person, from:, to:)
organisation_approved_reviews = \
GitHub.search_approved_pull_requests_in_user_or_organisation(organisation, person, from:, to:)
require "utils/git"
repositories.each do |repository|
repository_path, tap = repository_path_and_tap(repository)
if repository_path && tap && !repository_path.exist?
opoo "Repository #{repository} not yet tapped! Tapping it now..."
tap.install
end
repo_full_name = if repo == "brew"
"homebrew/brew"
else
tap.full_name
repository_full_name = tap&.full_name
repository_full_name ||= repository
repository_api_url = "#{GitHub::API_URL}/repos/#{repository_full_name}"
puts "Determining contributions for #{person} on #{repository_full_name}..." if args.verbose?
merged_pr_author = organisation_merged_prs.count do |pr|
pr.fetch("repository_url") == repository_api_url
end
approved_pr_review = organisation_approved_reviews.count do |pr|
pr.fetch("repository_url") == repository_api_url
end
committer = GitHub.count_repository_commits(repository_full_name, person, max:, verbose:, from:, to:)
coauthor = Utils::Git.count_coauthors(repository_path, person, from:, to:)
puts "Determining contributions for #{person} on #{repo_full_name}..." if args.verbose?
author_commits, committer_commits = GitHub.count_repo_commits(repo_full_name, person,
from:, to: args.to, max: MAX_REPO_COMMITS)
data[repo] = {
author: author_commits,
committer: committer_commits,
coauthor: git_log_trailers_cmd(repo_path, person, "Co-authored-by", from:, to: args.to),
review: count_reviews(repo_full_name, person, from:, to: args.to),
}
data[repository] = { merged_pr_author:, approved_pr_review:, committer:, coauthor: }
rescue GitHub::API::RateLimitExceededError => e
sleep_seconds = e.reset - Time.now.to_i
opoo "GitHub rate limit exceeded, sleeping for #{sleep_seconds} seconds..."
sleep sleep_seconds
retry
end
data
@ -202,43 +266,17 @@ module Homebrew
sig { params(results: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, Integer]) }
def total(results)
totals = { author: 0, committer: 0, coauthor: 0, review: 0 }
totals = {}
results.each_value do |counts|
counts.each do |kind, count|
totals[kind] ||= 0
totals[kind] += count
end
end
totals
end
sig {
params(repo_path: Pathname, person: String, trailer: String, from: T.nilable(String),
to: T.nilable(String)).returns(Integer)
}
def git_log_trailers_cmd(repo_path, person, trailer, from:, to:)
cmd = ["git", "-C", repo_path, "log", "--oneline"]
cmd << "--format='%(trailers:key=#{trailer}:)'"
cmd << "--before=#{to}" if to
cmd << "--after=#{from}" if from
Utils.safe_popen_read(*cmd).lines.count { |l| l.include?(person) }
end
sig {
params(repo_full_name: String, person: String, from: T.nilable(String),
to: T.nilable(String)).returns(Integer)
}
def count_reviews(repo_full_name, person, from:, to:)
require "utils/github"
GitHub.count_issues("", is: "pr", repo: repo_full_name, reviewed_by: person, review: "approved", from:, to:)
rescue GitHub::API::ValidationFailedError
if args.verbose?
onoe "Couldn't search GitHub for PRs by #{person}. Their profile might be private. Defaulting to 0."
end
0 # Users who have made their contributions private are not searchable to determine counts.
end
end
end
end

View File

@ -41,9 +41,9 @@ module Homebrew
switch "--autobump",
description: "Include packages that are autobumped by BrewTestBot. By default these are skipped."
conflicts "--debug", "--json"
conflicts "--tap=", "--eval-all", "--installed"
conflicts "--cask", "--formula"
conflicts "--tap", "--installed", "--eval-all"
conflicts "--json", "--debug"
conflicts "--formula", "--cask"
conflicts "--formula", "--extract-plist"
named_args [:formula, :cask], without_api: true
@ -78,9 +78,15 @@ module Homebrew
formulae + casks
elsif File.exist?(watchlist_path)
begin
# This removes blank lines, comment lines, and trailing comments
names = Pathname.new(watchlist_path).read.lines
.reject { |line| line.start_with?("#") || line.blank? }
.map(&:strip)
.filter_map do |line|
comment_index = line.index("#")
next if comment_index&.zero?
line = line[0...comment_index] if comment_index
line&.strip.presence
end
named_args = CLI::NamedArgs.new(*names, parent: args)
named_args.to_formulae_and_casks(ignore_unavailable: true)

View File

@ -208,8 +208,7 @@ module Homebrew
if pull_request
# This is a tap pull request and approving reviewers should also sign-off.
tap = T.must(Tap.from_path(git_repo.pathname))
review_trailers = GitHub.approved_reviews(tap.user, tap.full_name.split("/").last,
pull_request).map do |r|
review_trailers = GitHub.repository_approved_reviews(tap.user, tap.full_repository, pull_request).map do |r|
"Signed-off-by: #{r["name"]} <#{r["email"]}>"
end
trailers = trailers.lines.concat(review_trailers).map(&:strip).uniq.join("\n")

View File

@ -5,6 +5,8 @@ require "abstract_command"
require "fileutils"
require "stringio"
require "formula"
require "cask/download"
require "unpack_strategy"
module Homebrew
module DevCmd
@ -13,7 +15,7 @@ module Homebrew
cmd_args do
description <<~EOS
Unpack the source files for <formula> into subdirectories of the current
Unpack the files for the <formula> or <cask> into subdirectories of the current
working directory.
EOS
flag "--destdir=",
@ -25,15 +27,28 @@ module Homebrew
"patches for the software."
switch "-f", "--force",
description: "Overwrite the destination directory if it already exists."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--git", "--patch"
conflicts "--formula", "--cask"
conflicts "--cask", "--patch"
conflicts "--cask", "--git"
named_args :formula, min: 1
named_args [:formula, :cask], min: 1
end
sig { override.void }
def run
formulae = args.named.to_formulae
formulae_and_casks = if args.casks?
args.named.to_formulae_and_casks(only: :cask)
elsif args.formulae?
args.named.to_formulae_and_casks(only: :formula)
else
args.named.to_formulae_and_casks
end
if (dir = args.destdir)
unpack_dir = Pathname.new(dir).expand_path
@ -44,35 +59,70 @@ module Homebrew
odie "Cannot write to #{unpack_dir}" unless unpack_dir.writable?
formulae.each do |f|
stage_dir = unpack_dir/"#{f.name}-#{f.version}"
if stage_dir.exist?
odie "Destination #{stage_dir} already exists!" unless args.force?
rm_rf stage_dir
end
oh1 "Unpacking #{Formatter.identifier(f.full_name)} to: #{stage_dir}"
# show messages about tar
with_env VERBOSE: "1" do
f.brew do
f.patch if args.patch?
cp_r getwd, stage_dir, preserve: true
end
end
next unless args.git?
ohai "Setting up Git repository"
cd(stage_dir) do
system "git", "init", "-q"
system "git", "add", "-A"
system "git", "commit", "-q", "-m", "brew-unpack"
formulae_and_casks.each do |formula_or_cask|
if formula_or_cask.is_a?(Cask::Cask)
unpack_cask(formula_or_cask, unpack_dir)
elsif (formula = T.cast(formula_or_cask, Formula))
unpack_formula(formula, unpack_dir)
end
end
end
private
sig { params(formula: Formula, unpack_dir: Pathname).void }
def unpack_formula(formula, unpack_dir)
stage_dir = unpack_dir/"#{formula.name}-#{formula.version}"
if stage_dir.exist?
odie "Destination #{stage_dir} already exists!" unless args.force?
rm_rf stage_dir
end
oh1 "Unpacking #{Formatter.identifier(formula.full_name)} to: #{stage_dir}"
# show messages about tar
with_env VERBOSE: "1" do
formula.brew do
formula.patch if args.patch?
cp_r getwd, stage_dir, preserve: true
end
end
return unless args.git?
ohai "Setting up Git repository"
cd(stage_dir) do
system "git", "init", "-q"
system "git", "add", "-A"
system "git", "commit", "-q", "-m", "brew-unpack"
end
end
sig { params(cask: Cask::Cask, unpack_dir: Pathname).void }
def unpack_cask(cask, unpack_dir)
stage_dir = unpack_dir/"#{cask.token}-#{cask.version}"
if stage_dir.exist?
odie "Destination #{stage_dir} already exists!" unless args.force?
rm_rf stage_dir
end
oh1 "Unpacking #{Formatter.identifier(cask.full_name)} to: #{stage_dir}"
download = Cask::Download.new(cask, quarantine: true)
downloaded_path = if download.downloaded?
download.cached_download
else
download.fetch(quiet: false)
end
stage_dir.mkpath
UnpackStrategy.detect(downloaded_path).extract_nestedly(to: stage_dir, verbose: true)
end
end
end
end

View File

@ -114,9 +114,9 @@ class DevelopmentTools
# Get the GCC version.
#
# @api internal
# @api public
sig { params(cc: String).returns(Version) }
def gcc_version(cc)
def gcc_version(cc = host_gcc_path.to_s)
(@gcc_version ||= T.let({}, T.nilable(T::Hash[String, Version]))).fetch(cc) do
path = HOMEBREW_PREFIX/"opt/#{CompilerSelector.preferred_gcc}/bin"/cc
path = locate(cc) unless path.exist?

View File

@ -579,7 +579,7 @@ class UnbottledError < RuntimeError
require "utils"
msg = <<~EOS
The following #{Utils.pluralize("formula", formulae.count, plural: "e")} cannot be installed from #{Utils.pluralize("bottle", formulae.count)} and must be
The following #{Utils.pluralize("formula", formulae.count)} cannot be installed from #{Utils.pluralize("bottle", formulae.count)} and must be
built from source.
#{formulae.to_sentence}
EOS

View File

@ -0,0 +1,4 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/linux/cask/artifact/relocated" if OS.linux?

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
module OS
module Linux
module Cask
module Artifact
module Relocated
extend T::Helpers
requires_ancestor { ::Cask::Artifact::Relocated }
sig { params(file: Pathname, altname: Pathname, command: T.class_of(SystemCommand)).returns(T.nilable(SystemCommand::Result)) }
def add_altname_metadata(file, altname, command:)
# no-op on Linux: /usr/bin/xattr for setting extended attributes is not available there.
end
end
end
end
end
end
Cask::Artifact::Relocated.prepend(OS::Linux::Cask::Artifact::Relocated)

View File

@ -42,14 +42,14 @@ module OS
@needs_libc_formula ||= OS::Linux::Glibc.below_ci_version?
end
# Keep this method around for now to make it easier to add this functionality later.
# rubocop:disable Lint/UselessMethodDefinition
sig { returns(Pathname) }
def host_gcc_path
# TODO: override this if/when we to pick the GCC based on e.g. the Ubuntu version.
# Prioritise versioned path if installed
path = Pathname.new("/usr/bin/#{OS::LINUX_PREFERRED_GCC_COMPILER_FORMULA.tr("@", "-")}")
return path if path.exist?
super
end
# rubocop:enable Lint/UselessMethodDefinition
sig { returns(T::Boolean) }
def needs_compiler_formula?
@ -60,12 +60,7 @@ module OS
# Undocumented environment variable to make it easier to test compiler
# formula automatic installation.
@needs_compiler_formula = true if ENV["HOMEBREW_FORCE_COMPILER_FORMULA"]
@needs_compiler_formula ||= if host_gcc_path.exist?
::DevelopmentTools.gcc_version(host_gcc_path.to_s) < OS::LINUX_GCC_CI_VERSION
else
true
end
@needs_compiler_formula ||= OS::Linux::Libstdcxx.below_ci_version?
end
sig { returns(T::Hash[String, T.nilable(String)]) }

View File

@ -40,6 +40,8 @@ module OS
self["HOMEBREW_RPATH_PATHS"] = determine_rpath_paths(@formula)
m4_path_deps = ["libtool", "bison"]
self["M4"] = "#{HOMEBREW_PREFIX}/opt/m4/bin/m4" if deps.any? { m4_path_deps.include?(_1.name) }
# Build jemalloc-sys rust crate on ARM64/AArch64 with support for page sizes up to 64K.
self["JEMALLOC_SYS_WITH_LG_PAGE"] = "16" if ::Hardware::CPU.arch == :arm64
# Pointer authentication and BTI are hardening techniques most distros
# use by default on their packages. arm64 Linux we're packaging

View File

@ -1,36 +1,24 @@
# typed: strict
# frozen_string_literal: true
require "os/linux/ld"
require "os/linux/libstdcxx"
require "utils/output"
module OS
module Linux
module Install
module ClassMethods
# This is a list of known paths to the host dynamic linker on Linux if
# the host glibc is new enough. The symlink_ld_so method will fail if
# the host linker cannot be found in this list.
DYNAMIC_LINKERS = %w[
/lib64/ld-linux-x86-64.so.2
/lib64/ld64.so.2
/lib/ld-linux.so.3
/lib/ld-linux.so.2
/lib/ld-linux-aarch64.so.1
/lib/ld-linux-armhf.so.3
/system/bin/linker64
/system/bin/linker
].freeze
# We link GCC runtime libraries that are not specifically used for Fortran,
# which are linked by the GCC formula. We only use the versioned shared libraries
# as the other shared and static libraries are only used at build time where
# GCC can find its own libraries.
GCC_RUNTIME_LIBS = %w[
GCC_RUNTIME_LIBS = T.let(%W[
libatomic.so.1
libgcc_s.so.1
libgomp.so.1
libstdc++.so.6
].freeze
#{OS::Linux::Libstdcxx::SONAME}
].freeze, T::Array[String])
sig { params(all_fatal: T::Boolean).void }
def perform_preinstall_checks(all_fatal: false)
@ -67,7 +55,7 @@ module OS
ld_so = HOMEBREW_PREFIX/"opt/glibc/bin/ld.so"
unless ld_so.readable?
ld_so = DYNAMIC_LINKERS.find { |s| File.executable? s }
ld_so = OS::Linux::Ld.system_ld_so
if ld_so.blank?
::Kernel.raise "Unable to locate the system's dynamic linker" unless brew_ld_so.readable?

View File

@ -2,12 +2,13 @@
# frozen_string_literal: true
require "compilers"
require "os/linux/libstdcxx"
module OS
module Linux
module LinkageChecker
# Libraries provided by glibc and gcc.
SYSTEM_LIBRARY_ALLOWLIST = %w[
SYSTEM_LIBRARY_ALLOWLIST = %W[
ld-linux-x86-64.so.2
ld-linux-aarch64.so.1
libanl.so.1
@ -24,7 +25,7 @@ module OS
libutil.so.1
libgcc_s.so.1
libgomp.so.1
libstdc++.so.6
#{OS::Linux::Libstdcxx::SONAME}
libquadmath.so.0
].freeze

View File

@ -3,6 +3,7 @@
require "compilers"
require "os/linux/glibc"
require "os/linux/libstdcxx"
require "system_command"
module OS
@ -20,6 +21,13 @@ module OS
version
end
def host_libstdcxx_version
version = OS::Linux::Libstdcxx.system_version
return "N/A" if version.null?
version
end
def host_gcc_version
gcc = ::DevelopmentTools.host_gcc_path
return "N/A" unless gcc.executable?
@ -36,7 +44,7 @@ module OS
end
def host_ruby_version
out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false)
out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false).to_a
return "N/A" unless status.success?
out
@ -49,6 +57,7 @@ module OS
out.puts "OS: #{OS::Linux.os_version}"
out.puts "WSL: #{OS::Linux.wsl_version}" if OS::Linux.wsl?
out.puts "Host glibc: #{host_glibc_version}"
out.puts "Host libstdc++: #{host_libstdcxx_version}"
out.puts "#{::DevelopmentTools.host_gcc_path}: #{host_gcc_version}"
out.puts "/usr/bin/ruby: #{host_ruby_version}" if RUBY_PATH != HOST_RUBY_PATH
["glibc", ::CompilerSelector.preferred_gcc, OS::LINUX_PREFERRED_GCC_RUNTIME_FORMULA, "xorg"].each do |f|

View File

@ -422,11 +422,13 @@ class Pathname
sig { returns(T::Array[String]) }
def zipinfo
@zipinfo ||= T.let(nil, T.nilable(String))
@zipinfo ||= system_command("zipinfo", args: ["-1", self], print_stderr: false)
.stdout
.encode(Encoding::UTF_8, invalid: :replace)
.split("\n")
@zipinfo ||= T.let(
system_command("zipinfo", args: ["-1", self], print_stderr: false)
.stdout
.encode(Encoding::UTF_8, invalid: :replace)
.split("\n"),
T.nilable(T::Array[String]),
)
end
private

View File

@ -749,7 +749,7 @@ module Homebrew
end
def audit_specs
problem "HEAD-only (no stable download)" if head_only?(formula)
problem "HEAD-only (no stable download)" if head_only?(formula) && @core_tap
%w[Stable HEAD].each do |name|
spec_name = name.downcase.to_sym

View File

@ -609,8 +609,8 @@ on_request: installed_on_request?, options:)
clean
# Store the formula used to build the keg in the keg.
formula_contents = if formula.local_bottle_path
Utils::Bottles.formula_contents formula.local_bottle_path, name: formula.name
formula_contents = if (local_bottle_path = formula.local_bottle_path)
Utils::Bottles.formula_contents local_bottle_path, name: formula.name
else
formula.path.read
end

View File

@ -7,6 +7,7 @@ require "tab"
require "utils"
require "utils/bottles"
require "utils/output"
require "utils/path"
require "service"
require "utils/curl"
require "deprecate_disable"
@ -727,29 +728,7 @@ module Formulary
end
return unless path.expand_path.exist?
if Homebrew::EnvConfig.forbid_packages_from_paths?
path_realpath = path.realpath.to_s
path_string = path.to_s
if (path_realpath.end_with?(".rb") || path_string.end_with?(".rb")) &&
!path_realpath.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/") &&
!path_string.start_with?("#{HOMEBREW_CELLAR}/", "#{HOMEBREW_LIBRARY}/Taps/")
if path_string.include?("./") || path_string.end_with?(".rb") || path_string.count("/") != 2
raise <<~WARNING
Homebrew requires formulae to be in a tap, rejecting:
#{path_string} (#{path_realpath})
To create a tap, run e.g.
brew tap-new <user|org>/<repository>
To create a formula in a tap run e.g.
brew create <url> --tap=<user|org>/<repository>
WARNING
elsif path_string.count("/") == 2
# Looks like a tap, let's quietly return but not error.
return
end
end
end
return unless ::Utils::Path.loadable_package_path?(path, :formula)
if (tap = Tap.from_path(path))
# Only treat symlinks in taps as aliases.

View File

@ -10,7 +10,7 @@ class GitHubRunnerMatrix
# `git tag 15-sequoia f42c4a659e4da887fc714f8f41cc26794a4bb320`
# to allow people to jump to specific commits based on their macOS version.
NEWEST_HOMEBREW_CORE_MACOS_RUNNER = :sequoia
OLDEST_HOMEBREW_CORE_MACOS_RUNNER = :ventura
OLDEST_HOMEBREW_CORE_MACOS_RUNNER = :sonoma
NEWEST_HOMEBREW_CORE_INTEL_MACOS_RUNNER = :sonoma
RunnerSpec = T.type_alias { T.any(LinuxRunnerSpec, MacOSRunnerSpec) }

View File

@ -405,8 +405,7 @@ module Homebrew
return if formulae_names_to_install.empty?
if dry_run
ohai "Would install #{Utils.pluralize("formula", formulae_names_to_install.count,
plural: "e", include_count: true)}:"
ohai "Would install #{Utils.pluralize("formula", formulae_names_to_install.count, include_count: true)}:"
puts formulae_names_to_install.join(" ")
formula_installers.each do |fi|
@ -429,8 +428,8 @@ module Homebrew
def print_dry_run_dependencies(formula, dependencies)
return if dependencies.empty?
ohai "Would install #{Utils.pluralize("dependenc", dependencies.count, plural: "ies", singular: "y",
include_count: true)} for #{formula.name}:"
ohai "Would install #{Utils.pluralize("dependency", dependencies.count, include_count: true)} " \
"for #{formula.name}:"
formula_names = dependencies.map { |(dep, _options)| yield dep.to_formula }
puts formula_names.join(" ")
end
@ -446,7 +445,7 @@ module Homebrew
sizes = compute_total_sizes(formulae, debug: args.debug?)
puts "#{::Utils.pluralize("Formula", formulae.count, plural: "e")} \
puts "#{::Utils.pluralize("Formula", formulae.count)} \
(#{formulae.count}): #{formulae.join(", ")}\n\n"
puts "Download Size: #{disk_usage_readable(sizes.fetch(:download))}"
puts "Install Size: #{disk_usage_readable(sizes.fetch(:installed))}"

View File

@ -113,6 +113,18 @@ class Keg
old: "/usr/local/var/log/#{name}",
new: "#{PREFIX_PLACEHOLDER}/var/log/#{name}",
},
var_run_name: {
old: "/usr/local/var/run/#{name}",
new: "#{PREFIX_PLACEHOLDER}/var/run/#{name}",
},
var_db_name: {
old: "/usr/local/var/db/#{name}",
new: "#{PREFIX_PLACEHOLDER}/var/db/#{name}",
},
share_name: {
old: "/usr/local/share/#{name}",
new: "#{PREFIX_PLACEHOLDER}/share/#{name}",
},
}
end

View File

@ -198,8 +198,8 @@ module Homebrew
FORMULA_CHECKS = T.let([
:package_or_resource_skip,
:formula_head_only,
:formula_deprecated,
:formula_disabled,
:formula_deprecated,
:formula_versioned,
].freeze, T::Array[Symbol])
private_constant :FORMULA_CHECKS
@ -207,8 +207,8 @@ module Homebrew
# Skip conditions for casks.
CASK_CHECKS = T.let([
:package_or_resource_skip,
:cask_deprecated,
:cask_disabled,
:cask_deprecated,
:cask_extract_plist,
:cask_version_latest,
:cask_url_unversioned,

View File

@ -129,9 +129,9 @@ module Homebrew
print_stderr: false,
debug: false,
verbose: false,
)
).to_a
tags_data = { tags: [] }
tags_data = { tags: T.let([], T::Array[String]) }
tags_data[:messages] = stderr.split("\n") if stderr.present?
return tags_data if stdout.blank?

View File

@ -154,12 +154,6 @@ can take several different forms:
You can still access these formulae by using a special syntax, e.g.
`homebrew/dupes/vim` or `homebrew/versions/node4`.
* An arbitrary file:
Homebrew can install formulae from a local path. It can point to either a
formula file or a bottle.
Prefix relative paths with `./` to prevent them from being interpreted as a
formula or tap name.
## SPECIFYING CASKS
Many Homebrew Cask commands accept one or more <cask> arguments. These can be

View File

@ -23,6 +23,7 @@ DEPRECATED_OFFICIAL_TAPS = %w[
devel-only
dupes
emacs
formula-analytics
fuse
games
gui

View File

@ -49,8 +49,9 @@ module OS
LINUX_CI_OS_VERSION = "Ubuntu 22.04"
LINUX_GLIBC_CI_VERSION = "2.35"
LINUX_GLIBC_NEXT_CI_VERSION = "2.39"
LINUX_GCC_CI_VERSION = "11.0"
LINUX_PREFERRED_GCC_COMPILER_FORMULA = "gcc@11" # https://packages.ubuntu.com/jammy/gcc
LINUX_GCC_CI_VERSION = "12" # https://packages.ubuntu.com/jammy/gcc-12
LINUX_LIBSTDCXX_CI_VERSION = "6.0.30" # https://packages.ubuntu.com/jammy/libstdc++6
LINUX_PREFERRED_GCC_COMPILER_FORMULA = T.let("gcc@#{LINUX_GCC_CI_VERSION}".freeze, String)
LINUX_PREFERRED_GCC_RUNTIME_FORMULA = "gcc"
if OS.mac?

View File

@ -29,8 +29,6 @@ module OS
else
"#{description} (#{codename})"
end
elsif (redhat_release = Pathname.new("/etc/redhat-release")).readable?
redhat_release.read.chomp
elsif ::OS_VERSION.present?
::OS_VERSION
else

View File

@ -5,37 +5,69 @@ module OS
module Linux
# Helper functions for querying `ld` information.
module Ld
sig { returns(String) }
def self.brewed_ld_so_diagnostics
@brewed_ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
# This is a list of known paths to the host dynamic linker on Linux if
# the host glibc is new enough. Brew will fail to create a symlink for
# ld.so if the host linker cannot be found in this list.
DYNAMIC_LINKERS = %w[
/lib64/ld-linux-x86-64.so.2
/lib64/ld64.so.2
/lib/ld-linux.so.3
/lib/ld-linux.so.2
/lib/ld-linux-aarch64.so.1
/lib/ld-linux-armhf.so.3
/system/bin/linker64
/system/bin/linker
].freeze
brewed_ld_so = HOMEBREW_PREFIX/"lib/ld.so"
return "" unless brewed_ld_so.exist?
# The path to the system's dynamic linker or `nil` if not found
sig { returns(T.nilable(Pathname)) }
def self.system_ld_so
@system_ld_so ||= T.let(nil, T.nilable(Pathname))
@system_ld_so ||= begin
linker = DYNAMIC_LINKERS.find { |s| File.executable? s }
Pathname(linker) if linker
end
end
brewed_ld_so_target = brewed_ld_so.readlink
@brewed_ld_so_diagnostics[brewed_ld_so_target] ||= begin
ld_so_output = Utils.popen_read(brewed_ld_so, "--list-diagnostics")
sig { params(brewed: T::Boolean).returns(String) }
def self.ld_so_diagnostics(brewed: true)
@ld_so_diagnostics ||= T.let({}, T.nilable(T::Hash[Pathname, String]))
ld_so_target = if brewed
ld_so = HOMEBREW_PREFIX/"lib/ld.so"
return "" unless ld_so.exist?
ld_so.readlink
else
ld_so = system_ld_so
return "" unless ld_so&.exist?
ld_so
end
@ld_so_diagnostics[ld_so_target] ||= begin
ld_so_output = Utils.popen_read(ld_so, "--list-diagnostics")
ld_so_output if $CHILD_STATUS.success?
end
@brewed_ld_so_diagnostics[brewed_ld_so_target].to_s
@ld_so_diagnostics[ld_so_target].to_s
end
sig { returns(String) }
def self.sysconfdir
sig { params(brewed: T::Boolean).returns(String) }
def self.sysconfdir(brewed: true)
fallback_sysconfdir = "/etc"
match = brewed_ld_so_diagnostics.match(/path.sysconfdir="(.+)"/)
match = ld_so_diagnostics(brewed:).match(/path.sysconfdir="(.+)"/)
return fallback_sysconfdir unless match
match.captures.compact.first || fallback_sysconfdir
end
sig { returns(T::Array[String]) }
def self.system_dirs
sig { params(brewed: T::Boolean).returns(T::Array[String]) }
def self.system_dirs(brewed: true)
dirs = []
brewed_ld_so_diagnostics.split("\n").each do |line|
ld_so_diagnostics(brewed:).split("\n").each do |line|
match = line.match(/path.system_dirs\[0x.*\]="(.*)"/)
next unless match
@ -45,9 +77,9 @@ module OS
dirs
end
sig { params(conf_path: T.any(Pathname, String)).returns(T::Array[String]) }
def self.library_paths(conf_path = Pathname(sysconfdir)/"ld.so.conf")
conf_file = Pathname(conf_path)
sig { params(conf_path: T.any(Pathname, String), brewed: T::Boolean).returns(T::Array[String]) }
def self.library_paths(conf_path = "ld.so.conf", brewed: true)
conf_file = Pathname(sysconfdir(brewed:))/conf_path
return [] unless conf_file.exist?
return [] unless conf_file.file?
return [] unless conf_file.readable?
@ -68,8 +100,7 @@ module OS
line.sub!(/\s*#.*$/, "")
if line.start_with?(/\s*include\s+/)
include_path = Pathname(line.sub(/^\s*include\s+/, "")).expand_path
wildcard = include_path.absolute? ? include_path : directory/include_path
wildcard = Pathname(line.sub(/^\s*include\s+/, "")).expand_path(directory)
Dir.glob(wildcard.to_s).each do |include_file|
paths += library_paths(include_file)

View File

@ -0,0 +1,47 @@
# typed: strict
# frozen_string_literal: true
require "os/linux/ld"
module OS
module Linux
# Helper functions for querying `libstdc++` information.
module Libstdcxx
SOVERSION = 6
SONAME = T.let("libstdc++.so.#{SOVERSION}".freeze, String)
sig { returns(T::Boolean) }
def self.below_ci_version?
system_version < LINUX_LIBSTDCXX_CI_VERSION
end
sig { returns(Version) }
def self.system_version
@system_version ||= T.let(nil, T.nilable(Version))
@system_version ||= if (path = system_path)
Version.new("#{SOVERSION}#{path.realpath.basename.to_s.delete_prefix!(SONAME)}")
else
Version::NULL
end
end
sig { returns(T.nilable(Pathname)) }
def self.system_path
@system_path ||= T.let(nil, T.nilable(Pathname))
@system_path ||= find_library(OS::Linux::Ld.library_paths(brewed: false))
@system_path ||= find_library(OS::Linux::Ld.system_dirs(brewed: false))
end
sig { params(paths: T::Array[String]).returns(T.nilable(Pathname)) }
private_class_method def self.find_library(paths)
paths.each do |path|
next if path.start_with?(HOMEBREW_PREFIX)
candidate = Pathname(path)/SONAME
return candidate if candidate.exist? && candidate.elf?
end
nil
end
end
end
end

View File

@ -1,7 +1,14 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
class Keg
sig { params(path: Pathname).void }
def initialize(path)
super
@require_relocation = T.let(false, T::Boolean)
end
sig { params(id: String, file: Pathname).returns(T::Boolean) }
def change_dylib_id(id, file)
return false if file.dylib_id == id
@ -36,6 +43,7 @@ class Keg
raise
end
sig { params(old: String, new: String, file: Pathname).returns(T::Boolean) }
def change_rpath(old, new, file)
return false if old == new

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "macho"
@ -12,25 +12,28 @@ module MachOShim
delegate [:dylib_id] => :macho
sig { params(args: T.untyped).void }
def initialize(*args)
@macho = T.let(nil, T.nilable(MachO::MachOFile))
@mach_data = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
@macho = T.let(nil, T.nilable(T.any(MachO::MachOFile, MachO::FatFile)))
@mach_data = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, Symbol]]))
super
end
sig { returns(T.any(MachO::MachOFile, MachO::FatFile)) }
def macho
@macho ||= MachO.open(to_s)
end
private :macho
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
sig { returns(T::Array[T::Hash[Symbol, Symbol]]) }
def mach_data
@mach_data ||= begin
machos = []
mach_data = []
if MachO::Utils.fat_magic?(macho.magic)
case (macho = self.macho)
when MachO::FatFile
machos = macho.machos
else
machos << macho
@ -68,34 +71,38 @@ module MachOShim
# TODO: See if the `#write!` call can be delayed until
# we know we're not making any changes to the rpaths.
def delete_rpath(rpath, **options)
sig { params(rpath: String, strict: T::Boolean).void }
def delete_rpath(rpath, strict: true)
candidates = rpaths(resolve_variable_references: false).select do |r|
resolve_variable_name(r) == resolve_variable_name(rpath)
end
# Delete the last instance to avoid changing the order in which rpaths are searched.
rpath_to_delete = candidates.last
options[:last] = true
macho.delete_rpath(rpath_to_delete, options)
macho.delete_rpath(rpath_to_delete, { last: true, strict: })
macho.write!
end
def change_rpath(old, new, **options)
macho.change_rpath(old, new, options)
sig { params(old: String, new: String, uniq: T::Boolean, last: T::Boolean, strict: T::Boolean).void }
def change_rpath(old, new, uniq: false, last: false, strict: true)
macho.change_rpath(old, new, { uniq:, last:, strict: })
macho.write!
end
def change_dylib_id(id, **options)
macho.change_dylib_id(id, options)
sig { params(id: String, strict: T::Boolean).void }
def change_dylib_id(id, strict: true)
macho.change_dylib_id(id, { strict: })
macho.write!
end
def change_install_name(old, new, **options)
macho.change_install_name(old, new, options)
sig { params(old: String, new: String, strict: T::Boolean).void }
def change_install_name(old, new, strict: true)
macho.change_install_name(old, new, { strict: })
macho.write!
end
sig { params(except: Symbol, resolve_variable_references: T::Boolean).returns(T::Array[String]) }
def dynamically_linked_libraries(except: :none, resolve_variable_references: true)
lcs = macho.dylib_load_commands
lcs.reject! { |lc| lc.flag?(except) } if except != :none
@ -105,6 +112,7 @@ module MachOShim
names
end
sig { params(resolve_variable_references: T::Boolean).returns(T::Array[String]) }
def rpaths(resolve_variable_references: true)
names = macho.rpaths
# Don't recursively resolve rpaths to avoid infinite loops.
@ -113,11 +121,12 @@ module MachOShim
names
end
sig { params(name: String, resolve_rpaths: T::Boolean).returns(String) }
def resolve_variable_name(name, resolve_rpaths: true)
if name.start_with? "@loader_path"
Pathname(name.sub("@loader_path", dirname)).cleanpath.to_s
Pathname(name.sub("@loader_path", dirname.to_s)).cleanpath.to_s
elsif name.start_with?("@executable_path") && binary_executable?
Pathname(name.sub("@executable_path", dirname)).cleanpath.to_s
Pathname(name.sub("@executable_path", dirname.to_s)).cleanpath.to_s
elsif resolve_rpaths && name.start_with?("@rpath") && (target = resolve_rpath(name)).present?
target
else
@ -125,6 +134,7 @@ module MachOShim
end
end
sig { params(name: String).returns(T.nilable(String)) }
def resolve_rpath(name)
target = T.let(nil, T.nilable(String))
return unless rpaths(resolve_variable_references: true).find do |rpath|
@ -134,48 +144,58 @@ module MachOShim
target
end
sig { returns(T::Array[Symbol]) }
def archs
mach_data.map { |m| m.fetch :arch }
end
sig { returns(Symbol) }
def arch
case archs.length
when 0 then :dunno
when 1 then archs.first
when 1 then archs.fetch(0)
else :universal
end
end
sig { returns(T::Boolean) }
def universal?
arch == :universal
end
sig { returns(T::Boolean) }
def i386?
arch == :i386
end
sig { returns(T::Boolean) }
def x86_64?
arch == :x86_64
end
sig { returns(T::Boolean) }
def ppc7400?
arch == :ppc7400
end
sig { returns(T::Boolean) }
def ppc64?
arch == :ppc64
end
sig { returns(T::Boolean) }
def dylib?
mach_data.any? { |m| m.fetch(:type) == :dylib }
end
sig { returns(T::Boolean) }
def mach_o_executable?
mach_data.any? { |m| m.fetch(:type) == :executable }
end
alias binary_executable? mach_o_executable?
sig { returns(T::Boolean) }
def mach_o_bundle?
mach_data.any? { |m| m.fetch(:type) == :bundle }
end

View File

@ -6,7 +6,7 @@ includedir=${prefix}/include
Name: SQLite
Description: SQL database engine
Version: 3.48.0
Version: 3.51.0
Libs: -L${libdir} -lsqlite3
Libs.private:
Cflags:

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "system_command"
@ -22,7 +22,7 @@ module OS
sig { params(version: MacOSVersion, path: T.any(String, Pathname), source: Symbol).void }
def initialize(version, path, source)
@version = version
@path = Pathname.new(path)
@path = T.let(Pathname(path), Pathname)
@source = source
end
end
@ -36,6 +36,12 @@ module OS
class NoSDKError < StandardError; end
sig { void }
def initialize
@all_sdks = T.let(nil, T.nilable(T::Array[SDK]))
@sdk_prefix = T.let(nil, T.nilable(String))
end
sig { params(version: MacOSVersion).returns(SDK) }
def sdk_for(version)
sdk = all_sdks.find { |s| s.version == version }

View File

@ -1,11 +1,11 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module OS
module Mac
# Helper module for querying Xcode information.
module Xcode
DEFAULT_BUNDLE_PATH = Pathname("/Applications/Xcode.app").freeze
DEFAULT_BUNDLE_PATH = T.let(Pathname("/Applications/Xcode.app").freeze, Pathname)
BUNDLE_ID = "com.apple.dt.Xcode"
OLD_BUNDLE_ID = "com.apple.Xcode"
APPLE_DEVELOPER_DOWNLOAD_URL = "https://developer.apple.com/download/all/"
@ -98,7 +98,7 @@ module OS
# directory or nil if Xcode.app is not installed.
sig { returns(T.nilable(Pathname)) }
def self.prefix
@prefix ||= begin
@prefix ||= T.let(begin
dir = MacOS.active_developer_dir
if dir.empty? || dir == CLT::PKG_PATH || !File.directory?(dir)
@ -108,7 +108,7 @@ module OS
# Use cleanpath to avoid pathological trailing slash
Pathname.new(dir).cleanpath
end
end
end, T.nilable(Pathname))
end
sig { returns(Pathname) }
@ -134,7 +134,7 @@ module OS
sig { returns(XcodeSDKLocator) }
def self.sdk_locator
@sdk_locator ||= XcodeSDKLocator.new
@sdk_locator ||= T.let(XcodeSDKLocator.new, T.nilable(OS::Mac::XcodeSDKLocator))
end
sig { params(version: T.nilable(MacOSVersion)).returns(T.nilable(SDK)) }
@ -183,7 +183,7 @@ module OS
# may return a version string
# that is guessed based on the compiler, so do not
# use it in order to check if Xcode is installed.
if @version ||= detect_version
if @version ||= T.let(detect_version, T.nilable(String))
::Version.new @version
else
::Version::NULL
@ -293,7 +293,7 @@ module OS
sig { returns(CLTSDKLocator) }
def self.sdk_locator
@sdk_locator ||= CLTSDKLocator.new
@sdk_locator ||= T.let(CLTSDKLocator.new, T.nilable(OS::Mac::CLTSDKLocator))
end
sig { params(version: T.nilable(MacOSVersion)).returns(T.nilable(SDK)) }
@ -372,7 +372,7 @@ module OS
sig { returns(String) }
def self.latest_clang_version
case MacOS.version
when "26" then "1700.3.9.908"
when "26" then "1700.3.19.1"
when "15" then "1700.0.13.5"
when "14" then "1600.0.26.6"
when "13" then "1500.1.0.2.5"
@ -444,7 +444,7 @@ module OS
# @api internal
sig { returns(::Version) }
def self.version
if @version ||= detect_version
if @version ||= T.let(detect_version, T.nilable(String))
::Version.new @version
else
::Version::NULL

View File

@ -32,8 +32,8 @@ begin
end
Pathname.prepend WriteMkpathExtension
formula.run_post_install
# Handle all possible exceptions.
rescue Exception => e # rubocop:disable Lint/RescueException
error_pipe&.puts e.to_json

View File

@ -125,7 +125,7 @@ module Readall
sig { params(filename: Pathname).returns(T::Boolean) }
private_class_method def self.syntax_errors_or_warnings?(filename)
# Retrieve messages about syntax errors/warnings printed to `$stderr`.
_, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false)
_, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false).to_a
# Ignore unnecessary warning about named capture conflicts.
# See https://bugs.ruby-lang.org/issues/12359.

View File

@ -85,9 +85,6 @@ module Homebrew
end
end
# TODO: Remove this exception for `lsr` after support for tangled.sh
# Git URLs is available in a brew release.
return if name == "lsr"
return if url_strategy != DownloadStrategyDetector.detect("", using)
problem "Redundant `using:` value in URL"

View File

@ -93,7 +93,7 @@ module Homebrew
wait = 2 ** @try
unless quiet
what = Utils.pluralize("tr", tries_remaining, plural: "ies", singular: "y")
what = Utils.pluralize("try", tries_remaining)
ohai "Retrying download in #{wait}s... (#{tries_remaining} #{what} left)"
end
sleep wait

View File

@ -34,7 +34,7 @@ module Homebrew
unofficial = Tap.all.sum { |tap| tap.official? ? 0 : tap.formula_files.size }
if unofficial.positive?
opoo "Use `--eval-all` to search #{unofficial} additional " \
"#{Utils.pluralize("formula", unofficial, plural: "e")} in third party taps."
"#{Utils.pluralize("formula", unofficial)} in third party taps."
end
descriptions = Homebrew::API::Formula.all_formulae.transform_values { |data| data["desc"] }
Descriptions.search(string_or_regex, search_type, descriptions, eval_all, cache_store_hash: true).print

View File

@ -34,7 +34,7 @@ module Homebrew
private_class_method def self._run(*args, mode:)
require "system_command"
result = SystemCommand.run(executable,
result = SystemCommand.run(T.must(executable),
args: [scope, *args.map(&:to_s)],
print_stdout: mode == :default,
print_stderr: mode == :default,

View File

@ -369,9 +369,13 @@ class Cmd
end
args += rpath_flags("#{wl}-rpath=", rpath_paths)
args += ["#{wl}--dynamic-linker=#{dynamic_linker_path}"] if dynamic_linker_path
# Use -rpath-link to make sure linker uses versioned glibc rather than the system glibc for indirect
# Use -rpath-link to make sure linker uses brew glibc rather than the system glibc for indirect
# dependencies because -L will only handle direct dependencies.
args << "#{wl}-rpath-link=#{@opt}/#{versioned_glibc_dep}/lib" if versioned_glibc_dep
if versioned_glibc_dep
args << "#{wl}-rpath-link=#{@opt}/#{versioned_glibc_dep}/lib"
else
args << "#{wl}-rpath-link=#{@opt}/glibc/lib"
end
args
end

View File

@ -17,9 +17,21 @@ class Homebrew::DevCmd::Contributions::Args < Homebrew::CLI::Args
sig { returns(T.nilable(String)) }
def from; end
sig { returns(T.nilable(String)) }
def org; end
sig { returns(T.nilable(String)) }
def organisation; end
sig { returns(T.nilable(String)) }
def organization; end
sig { returns(T.nilable(T::Array[String])) }
def repositories; end
sig { returns(T.nilable(String)) }
def team; end
sig { returns(T.nilable(String)) }
def to; end

View File

@ -11,6 +11,12 @@ class Homebrew::DevCmd::Unpack
end
class Homebrew::DevCmd::Unpack::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) }
def cask?; end
sig { returns(T::Boolean) }
def casks?; end
sig { returns(T.nilable(String)) }
def destdir; end
@ -20,6 +26,12 @@ class Homebrew::DevCmd::Unpack::Args < Homebrew::CLI::Args
sig { returns(T::Boolean) }
def force?; end
sig { returns(T::Boolean) }
def formula?; end
sig { returns(T::Boolean) }
def formulae?; end
sig { returns(T::Boolean) }
def g?; end

View File

@ -988,38 +988,38 @@ RuboCop::Cop::RSpec::DescribeSymbol::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Arr
# end
# end
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#71
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#76
class RuboCop::Cop::RSpec::DescribedClass < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::ConfigurableEnforcedStyle
include ::RuboCop::Cop::RSpec::Namespace
extend ::RuboCop::Cop::AutoCorrector
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#80
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#85
def common_instance_exec_closure?(param0 = T.unsafe(nil)); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#97
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#108
def contains_described_class?(param0); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#92
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#103
def described_constant(param0 = T.unsafe(nil)); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#100
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#111
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#85
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#96
def rspec_block?(param0 = T.unsafe(nil)); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#89
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#100
def scope_changing_syntax?(param0 = T.unsafe(nil)); end
private
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#136
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#147
def allowed?(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#117
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#128
def autocorrect(corrector, match); end
# @example
@ -1033,7 +1033,7 @@ class RuboCop::Cop::RSpec::DescribedClass < ::RuboCop::Cop::RSpec::Base
# @param const [Array<Symbol>]
# @return [Array<Symbol>]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#202
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#213
def collapse_namespace(namespace, const); end
# @example
@ -1043,50 +1043,50 @@ class RuboCop::Cop::RSpec::DescribedClass < ::RuboCop::Cop::RSpec::Base
# @param node [RuboCop::AST::Node]
# @return [Array<Symbol>]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#219
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#230
def const_name(node); end
# @yield [node]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#127
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#138
def find_usage(node, &block); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#187
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#198
def full_const_name(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#140
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#151
def message(offense); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#165
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#176
def offensive?(node); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#173
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#184
def offensive_described_class?(node); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#161
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#172
def only_static_constants?; end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#149
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#160
def scope_change?(node); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#155
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#166
def skippable_block?(node); end
end
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#76
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#81
RuboCop::Cop::RSpec::DescribedClass::DESCRIBED_CLASS = T.let(T.unsafe(nil), String)
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#77
# source://rubocop-rspec//lib/rubocop/cop/rspec/described_class.rb#82
RuboCop::Cop::RSpec::DescribedClass::MSG = T.let(T.unsafe(nil), String)
# Avoid opening modules and defining specs within them.
@ -1135,6 +1135,7 @@ RuboCop::Cop::RSpec::DescribedClassModuleWrapping::MSG = T.let(T.unsafe(nil), St
# - let, let!
# - subject, subject!
# - expect, is_expected, expect_any_instance_of
# - raise_error, raise_exception
#
# By default all of the RSpec methods and aliases are allowed. By setting
# a config like:
@ -1169,19 +1170,19 @@ RuboCop::Cop::RSpec::DescribedClassModuleWrapping::MSG = T.let(T.unsafe(nil), St
# # ...
# end
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#58
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#59
class RuboCop::Cop::RSpec::Dialect < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::MethodPreference
extend ::RuboCop::Cop::AutoCorrector
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#67
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#68
def on_send(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#65
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#66
def rspec_method?(param0 = T.unsafe(nil)); end
end
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#62
# source://rubocop-rspec//lib/rubocop/cop/rspec/dialect.rb#63
RuboCop::Cop::RSpec::Dialect::MSG = T.let(T.unsafe(nil), String)
# Avoid duplicated metadata.
@ -1578,7 +1579,7 @@ class RuboCop::Cop::RSpec::EmptyLineAfterHook < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/empty_line_after_hook.rb#60
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/empty_line_after_hook.rb#60
# source://rubocop-rspec//lib/rubocop/cop/rspec/empty_line_after_hook.rb#70
def on_numblock(node); end
private
@ -2189,7 +2190,7 @@ class RuboCop::Cop::RSpec::ExpectInHook < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/expect_in_hook.rb#30
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/expect_in_hook.rb#30
# source://rubocop-rspec//lib/rubocop/cop/rspec/expect_in_hook.rb#40
def on_numblock(node); end
private
@ -2482,7 +2483,7 @@ class RuboCop::Cop::RSpec::HookArgument < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/hook_argument.rb#78
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/hook_argument.rb#78
# source://rubocop-rspec//lib/rubocop/cop/rspec/hook_argument.rb#91
def on_numblock(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/hook_argument.rb#69
@ -2546,7 +2547,7 @@ class RuboCop::Cop::RSpec::HooksBeforeExamples < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/hooks_before_examples.rb#41
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/hooks_before_examples.rb#41
# source://rubocop-rspec//lib/rubocop/cop/rspec/hooks_before_examples.rb#47
def on_numblock(node); end
private
@ -2838,18 +2839,18 @@ RuboCop::Cop::RSpec::ImplicitSubject::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Ar
# # good
# it_behaves_like 'examples'
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#22
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#73
class RuboCop::Cop::RSpec::IncludeExamples < ::RuboCop::Cop::RSpec::Base
extend ::RuboCop::Cop::AutoCorrector
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#29
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#80
def on_send(node); end
end
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#25
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#76
RuboCop::Cop::RSpec::IncludeExamples::MSG = T.let(T.unsafe(nil), String)
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#27
# source://rubocop-rspec//lib/rubocop/cop/rspec/include_examples.rb#78
RuboCop::Cop::RSpec::IncludeExamples::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
# Do not set up test data using indexes (e.g., `item_1`, `item_2`).
@ -3211,16 +3212,18 @@ RuboCop::Cop::RSpec::ItBehavesLike::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Arra
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#19
class RuboCop::Cop::RSpec::IteratedExpectation < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#24
extend ::RuboCop::Cop::AutoCorrector
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#26
def each?(param0 = T.unsafe(nil)); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#33
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#35
def each_numblock?(param0 = T.unsafe(nil)); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#40
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#42
def expectation?(param0 = T.unsafe(nil), param1); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#44
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#46
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#52
@ -3228,18 +3231,29 @@ class RuboCop::Cop::RSpec::IteratedExpectation < ::RuboCop::Cop::RSpec::Base
private
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#60
def check_offense(node, argument); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#66
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#88
def only_expectations?(body, arg); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#62
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#84
def single_expectation?(body, arg); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#73
def single_expectation_replacement(node); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#80
def uses_argument_in_matcher?(node, argument); end
end
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#20
# source://rubocop-rspec//lib/rubocop/cop/rspec/iterated_expectation.rb#22
RuboCop::Cop::RSpec::IteratedExpectation::MSG = T.let(T.unsafe(nil), String)
# Enforce that subject is the first definition in the test.
@ -3393,17 +3407,22 @@ class RuboCop::Cop::RSpec::LeakyConstantDeclaration < ::RuboCop::Cop::RSpec::Bas
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#101
def on_casgn(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#107
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#108
def on_class(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#113
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#115
def on_module(node); end
private
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#121
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#128
def explicit_namespace?(namespace); end
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/leaky_constant_declaration.rb#124
def inside_describe_block?(node); end
end
@ -3772,7 +3791,7 @@ module RuboCop::Cop::RSpec::Metadata
# source://rubocop-rspec//lib/rubocop/cop/rspec/mixin/metadata.rb#43
def on_metadata(_symbols, _hash); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/mixin/metadata.rb#30
# source://rubocop-rspec//lib/rubocop/cop/rspec/mixin/metadata.rb#41
def on_numblock(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/mixin/metadata.rb#21
@ -4046,7 +4065,7 @@ class RuboCop::Cop::RSpec::MultipleExpectations < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/multiple_expectations.rb#86
def expect?(param0 = T.unsafe(nil)); end
# source://rubocop/1.75.6/lib/rubocop/cop/exclude_limit.rb#11
# source://rubocop-rspec//lib/rubocop/cop/rspec/multiple_expectations.rb#75
def max=(value); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/multiple_expectations.rb#93
@ -4161,7 +4180,7 @@ RuboCop::Cop::RSpec::MultipleExpectations::TRUE_NODE = T.let(T.unsafe(nil), Proc
class RuboCop::Cop::RSpec::MultipleMemoizedHelpers < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RSpec::Variable
# source://rubocop/1.75.6/lib/rubocop/cop/exclude_limit.rb#11
# source://rubocop-rspec//lib/rubocop/cop/rspec/multiple_memoized_helpers.rb#89
def max=(value); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/multiple_memoized_helpers.rb#91
@ -4503,7 +4522,7 @@ end
class RuboCop::Cop::RSpec::NestedGroups < ::RuboCop::Cop::RSpec::Base
include ::RuboCop::Cop::RSpec::TopLevelGroup
# source://rubocop/1.75.6/lib/rubocop/cop/exclude_limit.rb#11
# source://rubocop-rspec//lib/rubocop/cop/rspec/nested_groups.rb#105
def max=(value); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/nested_groups.rb#107
@ -4617,7 +4636,7 @@ class RuboCop::Cop::RSpec::NoExpectationExample < ::RuboCop::Cop::RSpec::Base
# @param node [RuboCop::AST::BlockNode]
#
# source://rubocop-rspec//lib/rubocop/cop/rspec/no_expectation_example.rb#89
# source://rubocop-rspec//lib/rubocop/cop/rspec/no_expectation_example.rb#98
def on_numblock(node); end
# @param node [RuboCop::AST::Node]
@ -4997,7 +5016,7 @@ RuboCop::Cop::RSpec::ReceiveCounts::MSG = T.let(T.unsafe(nil), String)
# source://rubocop-rspec//lib/rubocop/cop/rspec/receive_counts.rb#30
RuboCop::Cop::RSpec::ReceiveCounts::RESTRICT_ON_SEND = T.let(T.unsafe(nil), Array)
# Checks for multiple messages stubbed on the same object.
# Prefer `receive_messages` over multiple `receive`s on the same object.
#
# @example
# # bad
@ -5140,7 +5159,7 @@ class RuboCop::Cop::RSpec::RedundantAround < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/redundant_around.rb#23
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/redundant_around.rb#23
# source://rubocop-rspec//lib/rubocop/cop/rspec/redundant_around.rb#30
def on_numblock(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/redundant_around.rb#32
@ -6126,7 +6145,7 @@ class RuboCop::Cop::RSpec::SkipBlockInsideExample < ::RuboCop::Cop::RSpec::Base
# source://rubocop-rspec//lib/rubocop/cop/rspec/skip_block_inside_example.rb#29
def on_block(node); end
# source://rubocop-rspec//lib/rubocop/cop/rspec/skip_block_inside_example.rb#29
# source://rubocop-rspec//lib/rubocop/cop/rspec/skip_block_inside_example.rb#36
def on_numblock(node); end
private
@ -7109,7 +7128,7 @@ class RuboCop::RSpec::Concept
# @return [Boolean]
#
# source://rubocop-rspec//lib/rubocop/rspec/concept.rb#14
# source://rubocop-rspec//lib/rubocop/rspec/concept.rb#18
def ==(other); end
# @return [Boolean]
@ -7356,129 +7375,137 @@ end
# This is used in Dialect and DescribeClass cops to detect RSpec blocks.
#
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#201
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#207
module RuboCop::RSpec::Language::ALL
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#202
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#208
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#75
module RuboCop::RSpec::Language::ErrorMatchers
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#76
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#81
module RuboCop::RSpec::Language::ExampleGroups
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#77
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#83
def all(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#87
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#93
def focused(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#83
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#89
def regular(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#91
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#97
def skipped(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#97
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#103
module RuboCop::RSpec::Language::Examples
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#99
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#105
def all(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#110
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#116
def focused(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#118
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#124
def pending(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#106
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#112
def regular(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#114
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#120
def skipped(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#124
module RuboCop::RSpec::Language::Expectations
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#125
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#130
module RuboCop::RSpec::Language::Helpers
module RuboCop::RSpec::Language::Expectations
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#131
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#142
module RuboCop::RSpec::Language::HookScopes
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#144
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#143
RuboCop::RSpec::Language::HookScopes::ALL = T.let(T.unsafe(nil), Array)
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#136
module RuboCop::RSpec::Language::Hooks
module RuboCop::RSpec::Language::Helpers
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#137
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#148
module RuboCop::RSpec::Language::HookScopes
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#150
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#149
RuboCop::RSpec::Language::HookScopes::ALL = T.let(T.unsafe(nil), Array)
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#142
module RuboCop::RSpec::Language::Hooks
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#143
def all(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#155
module RuboCop::RSpec::Language::Includes
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#151
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#157
def all(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#160
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#166
def context(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#156
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#162
def examples(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#166
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#172
module RuboCop::RSpec::Language::Runners
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#169
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#175
def all(element = T.unsafe(nil)); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#167
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#173
RuboCop::RSpec::Language::Runners::ALL = T.let(T.unsafe(nil), Array)
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#177
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#183
module RuboCop::RSpec::Language::SharedGroups
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#179
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#185
def all(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#188
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#194
def context(element); end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#184
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#190
def examples(element); end
end
end
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#194
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#200
module RuboCop::RSpec::Language::Subjects
class << self
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#195
# source://rubocop-rspec//lib/rubocop/rspec/language.rb#201
def all(element); end
end
end

View File

@ -49,7 +49,7 @@ module Homebrew
ruby_files = T.let([], T::Array[Pathname])
shell_files = T.let([], T::Array[Pathname])
actionlint_files = T.let([], T::Array[Pathname])
Array(files).map(&method(:Pathname))
Array(files).map { Pathname(_1) }
.each do |path|
case path.extname
when ".rb"

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "plist"
@ -21,33 +21,121 @@ class SystemCommand
# Run a fallible system command.
#
# @api internal
def system_command(executable, **options)
SystemCommand.run(executable, **options)
sig {
params(
executable: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean,
sudo_as_root: T::Boolean,
env: T::Hash[String, String],
input: T.any(String, T::Array[String]),
must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol),
print_stderr: T.any(T::Boolean, Symbol),
debug: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
secrets: T.any(String, T::Array[String]),
chdir: T.any(String, Pathname),
reset_uid: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
).returns(SystemCommand::Result)
}
def system_command(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [],
must_succeed: false, print_stdout: false, print_stderr: true, debug: nil, verbose: nil,
secrets: [], chdir: T.unsafe(nil), reset_uid: false, timeout: nil)
SystemCommand.run(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed:, print_stdout:,
print_stderr:, debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:)
end
# Run an infallible system command.
#
# @api internal
def system_command!(command, **options)
SystemCommand.run!(command, **options)
sig {
params(
executable: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean,
sudo_as_root: T::Boolean,
env: T::Hash[String, String],
input: T.any(String, T::Array[String]),
print_stdout: T.any(T::Boolean, Symbol),
print_stderr: T.any(T::Boolean, Symbol),
debug: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
secrets: T.any(String, T::Array[String]),
chdir: T.any(String, Pathname),
reset_uid: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
).returns(SystemCommand::Result)
}
def system_command!(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [],
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [],
chdir: T.unsafe(nil), reset_uid: false, timeout: nil)
SystemCommand.run!(executable, args:, sudo:, sudo_as_root:, env:, input:, print_stdout:,
print_stderr:, debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:)
end
end
include Context
def self.run(executable, **options)
new(executable, **options).run!
sig {
params(
executable: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean,
sudo_as_root: T::Boolean,
env: T::Hash[String, String],
input: T.any(String, T::Array[String]),
must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol),
print_stderr: T.any(T::Boolean, Symbol),
debug: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
secrets: T.any(String, T::Array[String]),
chdir: T.any(NilClass, String, Pathname),
reset_uid: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
).returns(SystemCommand::Result)
}
def self.run(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: false,
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil,
reset_uid: false, timeout: nil)
new(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed:, print_stdout:, print_stderr:, debug:,
verbose:, secrets:, chdir:, reset_uid:, timeout:).run!
end
def self.run!(command, **options)
run(command, **options, must_succeed: true)
sig {
params(
executable: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean,
sudo_as_root: T::Boolean,
env: T::Hash[String, String],
input: T.any(String, T::Array[String]),
must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol),
print_stderr: T.any(T::Boolean, Symbol),
debug: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
secrets: T.any(String, T::Array[String]),
chdir: T.any(NilClass, String, Pathname),
reset_uid: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
).returns(SystemCommand::Result)
}
def self.run!(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: true,
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil,
reset_uid: false, timeout: nil)
run(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed:, print_stdout:, print_stderr:,
debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:)
end
sig { returns(SystemCommand::Result) }
def run!
$stderr.puts redact_secrets(command.shelljoin.gsub('\=', "="), @secrets) if verbose? && debug?
@output = []
@output = T.let([], T.nilable(T::Array[[Symbol, String]]))
@output = T.must(@output)
each_output_line do |type, line|
case type
@ -70,7 +158,7 @@ class SystemCommand
end
end
result = Result.new(command, @output, @status, secrets: @secrets)
result = Result.new(command, @output, T.must(@status), secrets: @secrets)
result.assert_success! if must_succeed?
result
end
@ -78,7 +166,7 @@ class SystemCommand
sig {
params(
executable: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Float, URI::Generic)],
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean,
sudo_as_root: T::Boolean,
env: T::Hash[String, String],
@ -89,28 +177,14 @@ class SystemCommand
debug: T.nilable(T::Boolean),
verbose: T.nilable(T::Boolean),
secrets: T.any(String, T::Array[String]),
chdir: T.any(String, Pathname),
chdir: T.any(NilClass, String, Pathname),
reset_uid: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
).void
}
def initialize(
executable,
args: [],
sudo: false,
sudo_as_root: false,
env: {},
input: [],
must_succeed: false,
print_stdout: false,
print_stderr: true,
debug: nil,
verbose: T.unsafe(nil),
secrets: [],
chdir: T.unsafe(nil),
reset_uid: false,
timeout: nil
)
def initialize(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: false,
print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil,
reset_uid: false, timeout: nil)
require "extend/ENV"
@executable = executable
@args = args
@ -132,13 +206,13 @@ class SystemCommand
raise ArgumentError, "Invalid variable name: #{name}"
end
@env = env
@input = Array(input)
@input = T.let(Array(input), T::Array[String])
@must_succeed = must_succeed
@print_stdout = print_stdout
@print_stderr = print_stderr
@debug = debug
@verbose = verbose
@secrets = (Array(secrets) + ENV.sensitive_environment.values).uniq
@secrets = T.let((Array(secrets) + ENV.sensitive_environment.values).uniq, T::Array[String])
@chdir = chdir
@reset_uid = reset_uid
@timeout = timeout
@ -151,7 +225,20 @@ class SystemCommand
private
attr_reader :executable, :args, :input, :chdir, :env
sig { returns(T.any(Pathname, String)) }
attr_reader :executable
sig { returns(T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)]) }
attr_reader :args
sig { returns(T::Array[String]) }
attr_reader :input
sig { returns(T.any(NilClass, String, Pathname)) }
attr_reader :chdir
sig { returns(T::Hash[String, String]) }
attr_reader :env
sig { returns(T::Boolean) }
def must_succeed? = @must_succeed
@ -227,15 +314,13 @@ class SystemCommand
sig { returns(T::Array[String]) }
def expanded_args
@expanded_args ||= args.map do |arg|
if arg.respond_to?(:to_path)
@expanded_args ||= T.let(args.map do |arg|
if arg.is_a?(Pathname)
File.absolute_path(arg)
elsif arg.is_a?(Integer) || arg.is_a?(Float) || arg.is_a?(URI::Generic)
arg.to_s
else
arg.to_str
arg.to_s
end
end
end, T.nilable(T::Array[String]))
end
class ProcessTerminatedInterrupt < StandardError; end
@ -275,7 +360,7 @@ class SystemCommand
end_time = Time.now + @timeout if @timeout
raise Timeout::Error if raw_wait_thr.join(Utils::Timer.remaining(end_time)).nil?
@status = raw_wait_thr.value
@status = T.let(raw_wait_thr.value, T.nilable(Process::Status))
rescue Interrupt
Process.kill("INT", raw_wait_thr.pid) if raw_wait_thr && !sudo?
raise Interrupt
@ -383,7 +468,14 @@ class SystemCommand
include Context
include Utils::Output::Mixin
attr_accessor :command, :status, :exit_status
sig { returns(T::Array[String]) }
attr_accessor :command
sig { returns(Process::Status) }
attr_accessor :status
sig { returns(T.nilable(Integer)) }
attr_accessor :exit_status
sig {
params(
@ -397,7 +489,7 @@ class SystemCommand
@command = command
@output = output
@status = status
@exit_status = status.exitstatus
@exit_status = T.let(status.exitstatus, T.nilable(Integer))
@secrets = secrets
end
@ -410,22 +502,21 @@ class SystemCommand
sig { returns(String) }
def stdout
@stdout ||= @output.select { |type,| type == :stdout }
.map { |_, line| line }
.join
@stdout ||= T.let(@output.select { |type,| type == :stdout }
.map { |_, line| line }
.join, T.nilable(String))
end
sig { returns(String) }
def stderr
@stderr ||= @output.select { |type,| type == :stderr }
.map { |_, line| line }
.join
@stderr ||= T.let(@output.select { |type,| type == :stderr }
.map { |_, line| line }
.join, T.nilable(String))
end
sig { returns(String) }
def merged_output
@merged_output ||= @output.map { |_, line| line }
.join
@merged_output ||= T.let(@output.map { |_, line| line }.join, T.nilable(String))
end
sig { returns(T::Boolean) }
@ -439,10 +530,11 @@ class SystemCommand
def to_ary
[stdout, stderr, status]
end
alias to_a to_ary
sig { returns(T.nilable(T.any(Array, Hash))) }
sig { returns(T.untyped) }
def plist
@plist ||= begin
@plist ||= T.let(begin
output = stdout
output = output.sub(/\A(.*?)(\s*<\?\s*xml)/m) do
@ -456,7 +548,7 @@ class SystemCommand
end
Plist.parse_xml(output, marshal: false)
end
end, T.untyped)
end
sig { params(garbage: String).void }

View File

@ -102,7 +102,7 @@ module SystemConfig
sig { returns(String) }
def describe_curl
out, = system_command(Utils::Curl.curl_executable, args: ["--version"], verbose: false)
out = system_command(Utils::Curl.curl_executable, args: ["--version"], verbose: false).stdout
match_data = /^curl (?<curl_version>[\d.]+)/.match(out)
if match_data

View File

@ -169,6 +169,12 @@ class Tap
sig { returns(String) }
attr_reader :repository
# The repository name of this {Tap} including the leading `homebrew-`.
#
# @api public
sig { returns(String) }
attr_reader :full_repository
# The name of this {Tap}. It combines {#user} and {#repository} with a slash.
# {#name} is always in lowercase.
# e.g. `user/repository`
@ -210,7 +216,8 @@ class Tap
@user = user
@repository = repository
@name = T.let("#{@user}/#{@repository}".downcase, String)
@full_name = T.let("#{@user}/homebrew-#{@repository}", String)
@full_repository = T.let("homebrew-#{@repository}", String)
@full_name = T.let("#{@user}/#{@full_repository}", String)
@path = T.let(HOMEBREW_TAP_DIRECTORY/@full_name.downcase, Pathname)
@git_repository = T.let(GitRepository.new(@path), GitRepository)
end
@ -731,7 +738,7 @@ class Tap
end
if (formula_count = formula_files.count).positive?
contents << Utils.pluralize("formula", formula_count, plural: "e", include_count: true)
contents << Utils.pluralize("formula", formula_count, include_count: true)
end
contents

View File

@ -45,10 +45,9 @@ begin
formula.extend(Debrew::Formula)
end
Pathname.prepend WriteMkpathExtension
ENV.extend(Stdenv)
ENV.setup_build_environment(formula:, testing_formula: true)
Pathname.prepend WriteMkpathExtension
# tests can also return false to indicate failure
run_test = proc { |_ = nil| raise "test returned false" if formula.run_test(keep_tmp: args.keep_tmp?) == false }

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require "cask/artifact/relocated"
RSpec.describe Cask::Artifact::Relocated, :cask do
let(:cask) do
Cask::Cask.new("test-cask") do
url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
homepage "https://brew.sh/"
version "1.0"
sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94"
end
end
let(:command) { NeverSudoSystemCommand }
let(:artifact) { described_class.new(cask, "test_file.txt") }
describe "#add_altname_metadata" do
let(:file) { Pathname("/tmp/test_file.txt") }
let(:altname) { Pathname("alternate_name.txt") }
before do
allow(file).to receive_messages(basename: Pathname("test_file.txt"), writable?: true, realpath: file)
end
context "when running on Linux", :needs_linux do
it "is a no-op and does not call xattr commands" do
expect(command).not_to receive(:run)
expect(command).not_to receive(:run!)
artifact.send(:add_altname_metadata, file, altname, command: command)
end
end
context "when running on macOS", :needs_macos do
before do
stdout_double = instance_double(SystemCommand::Result, stdout: "")
allow(command).to receive(:run).and_return(stdout_double)
allow(command).to receive(:run!)
end
it "calls xattr commands to set metadata" do
expect(command).to receive(:run).with("/usr/bin/xattr",
args: ["-p", "com.apple.metadata:kMDItemAlternateNames", file],
print_stderr: false)
expect(command).to receive(:run!).twice
artifact.send(:add_altname_metadata, file, altname, command: command)
end
end
end
end

View File

@ -236,4 +236,73 @@ RSpec.describe Cask::CaskLoader, :cask do
expect(described_class.load_prefer_installed("test-cask").tap).to eq(foo_tap)
end
end
describe "FromPathLoader with symlinked taps" do
let(:cask_token) { "testcask" }
let(:tmpdir) { mktmpdir }
let(:real_tap_path) { tmpdir / "real_tap" }
let(:homebrew_prefix) { tmpdir / "homebrew" }
let(:taps_dir) { homebrew_prefix / "Library" / "Taps" / "testuser" }
let(:symlinked_tap_path) { taps_dir / "homebrew-testtap" }
let(:cask_file_path) { symlinked_tap_path / "Casks" / "#{cask_token}.rb" }
let(:cask_content) do
<<~RUBY
cask "#{cask_token}" do
version "1.0.0"
sha256 "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
url "https://example.com/#{cask_token}-\#{version}.dmg"
name "Test Cask"
desc "A test cask for symlink testing"
homepage "https://example.com"
app "TestCask.app"
end
RUBY
end
after do
tmpdir.rmtree if tmpdir.exist?
end
before do
# Create real tap directory structure
(real_tap_path / "Casks").mkpath
(real_tap_path / "Casks" / "#{cask_token}.rb").write(cask_content)
# Create homebrew prefix structure
taps_dir.mkpath
# Create symlink to the tap (this simulates what setup-homebrew does)
symlinked_tap_path.make_symlink(real_tap_path)
# Set HOMEBREW_LIBRARY to our test prefix for the security check
stub_const("HOMEBREW_LIBRARY", homebrew_prefix / "Library")
allow(Homebrew::EnvConfig).to receive(:forbid_packages_from_paths?).and_return(true)
end
context "when HOMEBREW_FORBID_PACKAGES_FROM_PATHS is enabled" do
it "allows loading casks from symlinked taps" do
loader = Cask::CaskLoader::FromPathLoader.try_new(cask_file_path)
expect(loader).not_to be_nil
expect(loader).to be_a(Cask::CaskLoader::FromPathLoader)
cask = loader.load(config: nil)
expect(cask.token).to eq(cask_token)
expect(cask.version).to eq(Version.new("1.0.0"))
end
end
context "when HOMEBREW_FORBID_PACKAGES_FROM_PATHS is disabled" do
before do
allow(Homebrew::EnvConfig).to receive(:forbid_packages_from_paths?).and_return(false)
end
it "allows loading casks from symlinked taps" do
loader = Cask::CaskLoader::FromPathLoader.try_new(cask_file_path)
expect(loader).not_to be_nil
expect(loader).to be_a(Cask::CaskLoader::FromPathLoader)
end
end
end
end

View File

@ -175,7 +175,7 @@ RSpec.describe Homebrew::CLI::Parser do
flag "--flag2=", depends_on: "--flag1="
flag "--flag3="
conflicts "--flag1=", "--flag3="
conflicts "--flag1", "--flag3"
end
end
@ -204,7 +204,8 @@ RSpec.describe Homebrew::CLI::Parser do
described_class.new(Cmd) do
flag "--flag1="
flag "--flag2=", depends_on: "--flag1="
conflicts "--flag1=", "--flag2="
conflicts "--flag1", "--flag2"
end
end

View File

@ -16,4 +16,15 @@ RSpec.describe Homebrew::DevCmd::Unpack do
expect(path/"testball-0.1").to be_a_directory
end
end
it "unpacks a given Cask's archive", :integration_test do
caffeine_cask = Cask::CaskLoader.load(cask_path("local-caffeine"))
mktmpdir do |path|
expect { brew "unpack", cask_path("local-caffeine"), "--destdir=#{path}" }
.to be_a_success
expect(path/"local-caffeine-#{caffeine_cask.version}").to be_a_directory
end
end
end

View File

@ -4,6 +4,61 @@ require "os/linux/ld"
require "tmpdir"
RSpec.describe OS::Linux::Ld do
let(:diagnostics) do
<<~EOS
path.prefix="/usr"
path.sysconfdir="/usr/local/etc"
path.system_dirs[0x0]="/lib64"
path.system_dirs[0x1]="/var/lib"
EOS
end
describe "::system_ld_so" do
let(:ld_so) { "/lib/ld-linux.so.3" }
before do
allow(File).to receive(:executable?).and_return(false)
described_class.instance_variable_set(:@system_ld_so, nil)
end
it "returns the path to a known dynamic linker" do
allow(File).to receive(:executable?).with(ld_so).and_return(true)
expect(described_class.system_ld_so).to eq(Pathname(ld_so))
end
it "returns nil when there is no known dynamic linker" do
expect(described_class.system_ld_so).to be_nil
end
end
describe "::sysconfdir" do
it "returns path.sysconfdir" do
allow(described_class).to receive(:ld_so_diagnostics).and_return(diagnostics)
expect(described_class.sysconfdir).to eq("/usr/local/etc")
expect(described_class.sysconfdir(brewed: false)).to eq("/usr/local/etc")
end
it "returns fallback on blank diagnostics" do
allow(described_class).to receive(:ld_so_diagnostics).and_return("")
expect(described_class.sysconfdir).to eq("/etc")
expect(described_class.sysconfdir(brewed: false)).to eq("/etc")
end
end
describe "::system_dirs" do
it "returns all path.system_dirs" do
allow(described_class).to receive(:ld_so_diagnostics).and_return(diagnostics)
expect(described_class.system_dirs).to eq(["/lib64", "/var/lib"])
expect(described_class.system_dirs(brewed: false)).to eq(["/lib64", "/var/lib"])
end
it "returns an empty array on blank diagnostics" do
allow(described_class).to receive(:ld_so_diagnostics).and_return("")
expect(described_class.system_dirs).to eq([])
expect(described_class.system_dirs(brewed: false)).to eq([])
end
end
describe "::library_paths" do
ld_etc = Pathname("")
before do

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require "os/linux/libstdcxx"
RSpec.describe OS::Linux::Libstdcxx do
describe "::below_ci_version?" do
it "returns false when system version matches CI version" do
allow(described_class).to receive(:system_version).and_return(Version.new(OS::LINUX_LIBSTDCXX_CI_VERSION))
expect(described_class.below_ci_version?).to be false
end
it "returns true when system version cannot be detected" do
allow(described_class).to receive(:system_version).and_return(Version::NULL)
expect(described_class.below_ci_version?).to be true
end
end
describe "::system_version" do
let(:tmpdir) { mktmpdir }
let(:libstdcxx) { tmpdir/described_class::SONAME }
let(:soversion) { Version.new(described_class::SOVERSION.to_s) }
before do
tmpdir.mkpath
described_class.instance_variable_set(:@system_version, nil)
allow(described_class).to receive(:system_path).and_return(libstdcxx)
end
after do
FileUtils.rm_rf(tmpdir)
end
it "returns NULL when unable to find system path" do
allow(described_class).to receive(:system_path).and_return(nil)
expect(described_class.system_version).to be Version::NULL
end
it "returns full version from filename" do
full_version = Version.new("#{soversion}.0.999")
libstdcxx_real = libstdcxx.sub_ext(".#{full_version}")
FileUtils.touch libstdcxx_real
FileUtils.ln_s libstdcxx_real, libstdcxx
expect(described_class.system_version).to eq full_version
end
it "returns major version when non-standard libstdc++ filename without full version" do
FileUtils.touch libstdcxx
expect(described_class.system_version).to eq soversion
end
it "returns major version when non-standard libstdc++ filename with unexpected realpath" do
libstdcxx_real = tmpdir/"libstdc++.so.real"
FileUtils.touch libstdcxx_real
FileUtils.ln_s libstdcxx_real, libstdcxx
expect(described_class.system_version).to eq soversion
end
end
end

View File

@ -164,8 +164,8 @@ RSpec.describe Utils::Git do
end
describe "::ensure_installed!" do
it "returns nil if git already available" do
expect(described_class.ensure_installed!).to be_nil
it "doesn't fail if git already available" do
expect { described_class.ensure_installed! }.not_to raise_error
end
context "when git is not already available" do

View File

@ -32,9 +32,9 @@ RSpec.describe GitHub do
end
end
describe "::approved_reviews", :needs_network do
describe "::repository_approved_reviews", :needs_network do
it "can get reviews for a pull request" do
reviews = described_class.approved_reviews("Homebrew", "homebrew-core", 1, commit: "deadbeef")
reviews = described_class.repository_approved_reviews("Homebrew", "homebrew-core", 1, commit: "deadbeef")
expect(reviews).to eq([])
end
end
@ -88,51 +88,60 @@ RSpec.describe GitHub do
describe "::count_repo_commits" do
let(:five_shas) { %w[abcdef ghjkl mnop qrst uvwxyz] }
let(:ten_shas) { %w[abcdef ghjkl mnop qrst uvwxyz fedcba lkjhg ponm tsrq zyxwvu] }
let(:max) { 1000 }
let(:verbose) { false }
let(:from) { nil }
let(:to) { nil }
it "counts commits authored by a user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", nil, nil, nil).and_return(five_shas)
.with("homebrew/cask", "user1", "author", nil, nil, max, verbose).and_return(five_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "committer", nil, nil, nil).and_return([])
.with("homebrew/cask", "user1", "committer", nil, nil, max, verbose).and_return([])
expect(described_class.count_repo_commits("homebrew/cask", "user1")).to eq([5, 0])
expect(described_class.count_repository_commits("homebrew/cask", "user1", max:, verbose:, from:,
to:)).to eq(5)
end
it "counts commits committed by a user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "author", nil, nil, nil).and_return([])
.with("homebrew/core", "user1", "author", nil, nil, max, verbose).and_return([])
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "committer", nil, nil, nil).and_return(five_shas)
.with("homebrew/core", "user1", "committer", nil, nil, max, verbose).and_return(five_shas)
expect(described_class.count_repo_commits("homebrew/core", "user1")).to eq([0, 5])
expect(described_class.count_repository_commits("homebrew/core", "user1", max:, verbose:, from:,
to:)).to eq(5)
end
it "calculates correctly when authored > committed with different shas" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", nil, nil, nil).and_return(ten_shas)
.with("homebrew/cask", "user1", "author", nil, nil, max, verbose).and_return(ten_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "committer", nil, nil, nil).and_return(%w[1 2 3 4 5])
.with("homebrew/cask", "user1", "committer", nil, nil, max, verbose).and_return(%w[1 2 3 4 5])
expect(described_class.count_repo_commits("homebrew/cask", "user1")).to eq([10, 5])
expect(described_class.count_repository_commits("homebrew/cask", "user1", max:, verbose:, from:,
to:)).to eq(15)
end
it "calculates correctly when committed > authored" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", nil, nil, nil).and_return(five_shas)
.with("homebrew/cask", "user1", "author", nil, nil, max, verbose).and_return(five_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "committer", nil, nil, nil).and_return(ten_shas)
.with("homebrew/cask", "user1", "committer", nil, nil, max, verbose).and_return(ten_shas)
expect(described_class.count_repo_commits("homebrew/cask", "user1")).to eq([5, 5])
expect(described_class.count_repository_commits("homebrew/cask", "user1", max:, verbose:, from:,
to:)).to eq(10)
end
it "deduplicates commits authored and committed by the same user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "author", nil, nil, nil).and_return(five_shas)
.with("homebrew/core", "user1", "author", nil, nil, max, verbose).and_return(five_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "committer", nil, nil, nil).and_return(five_shas)
.with("homebrew/core", "user1", "committer", nil, nil, max, verbose).and_return(five_shas)
# Because user1 authored and committed the same 5 commits.
expect(described_class.count_repo_commits("homebrew/core", "user1")).to eq([5, 0])
expect(described_class.count_repository_commits("homebrew/core", "user1", max:, verbose:, from:,
to:)).to eq(5)
end
end
end

View File

@ -11,7 +11,8 @@ RSpec.describe User do
before do
allow(SystemCommand).to receive(:run)
.with("who", any_args)
.and_return([who_output, "", instance_double(Process::Status, success?: true)])
.and_return(instance_double(SystemCommand::Result,
to_a: [who_output, "", instance_double(Process::Status, success?: true)]))
end
context "when the current user is in a console session" do

View File

@ -155,7 +155,7 @@ module UnpackStrategy
filelist.close
system_command! "mkbom",
args: ["-s", "-i", filelist.path, "--", bomfile.path],
args: ["-s", "-i", T.must(filelist.path), "--", T.must(bomfile.path)],
verbose:
end
@ -179,8 +179,8 @@ module UnpackStrategy
sig { override.params(path: Pathname).returns(T::Boolean) }
def self.can_extract?(path)
stdout, _, status = system_command("hdiutil", args: ["imageinfo", "-format", path], print_stderr: false)
status.success? && !stdout.empty?
stdout, _, status = system_command("hdiutil", args: ["imageinfo", "-format", path], print_stderr: false).to_a
(status.success? && !stdout.empty?) || false
end
private

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