Also updates rubocop-sorbet (0.13.2 -> 0.14.0) and json (2.21.1 ->
2.21.2), which rubocop depends on.
Two suppressions can go away thanks to bug fixes in this release:
- rubocop/rubocop#15438 and rubocop/rubocop#15442 stop
`Style/ArrayIntersect` firing when the `include?` receiver in a block
is not an array literal, so the cop is re-enabled. It still cannot
guarantee the *outer* receiver is an `Array`, and in
`cask/artifact/abstract_uninstall.rb` that receiver is the `Enumerator`
from `Pathname#each_filename`, which has no `intersect?`. Convert with
`to_a` there rather than taking the autocorrect verbatim.
- rubocop/rubocop#15452 fixes the `Layout/HashAlignment` false positive on
multi-line hash keys, so the two `delegate` disable comments in
`formula.rb` are dead and removed.
One suppression is added: rubocop/rubocop#15493 makes
`Style/IfUnlessModifier` respect `Layout/LineLength`'s exemptions rather
than its `Max`. Several of our exemption patterns are unescaped regexes
(`"#{version."` matches `#{version}`, `#{version_text}` and
`#{version_info[...]}`), so the cop demanded modifier form for statements
that then ran to 179 characters. Disable the cop. Reported upstream as
rubocop/rubocop#15531.
rubocop-sorbet 0.14.0 widens `Sorbet/SetterReturnType` to cover setters
taking splat and keyword arguments, so several `depends_on` and ENV
setters switch to `.void`. Under `HOMEBREW_SORBET_RUNTIME` a `.void`
method returns the sentinel `T::Private::Types::Void::VOID`, which breaks
the two callers that consumed a setter's return value. Sorbet does not
catch either statically:
- `DependsOn#load` stored the result of `send(:"#{key}=", ...)` into the
delegated hash, so the whole `depends_on` stanza became `VOID`. Read the
value back through the matching reader instead.
- `Superenv#cc=` assigned `super` to `HOMEBREW_CC`, raising `TypeError:
no implicit conversion of Module into String`. Assign from `val`.
Drop `extend/ENV/shared.rbi`, which existed to widen `[]=` to accept
`Pathname` and `PATH`. Without it `[]=` resolves to Sorbet's own
`ENVClass#[]=`, which takes `T.nilable(String)`, so the ENV assignments
convert explicitly with `to_s`/`&.to_s`. Both `Pathname` and `PATH`
define `to_str`, so Ruby was already converting implicitly and behaviour
is unchanged.
The remaining changes are new offenses in this release:
- `Lint/UselessAssignment` (rubocop/rubocop#12269) catches the
`root_url = root_url =` workaround in `dev-cmd/tap-new.rb`. The Ruby
`assigned but unused variable` warning it silenced no longer fires on
our vendored Ruby, so drop it.
- `Style/RedundantParentheses` (rubocop/rubocop#15472) and
`Layout/MultilineMethodCallIndentation` autocorrections.
The redundant `T.let` on `GitHub::API::ERRORS` is also dropped.
Claude-Session: https://claude.ai/code/session_01AdSQcU5MmY4VWiFdfrPeu2
These instance variables are assigned but never read:
- `@stale_seconds` and `@target` in `Homebrew::API::JSONDownloadStrategy`
and `Homebrew::API::JSONDownload`; the value is used via `meta[:target]`/
`meta[:stale_seconds]` and the `target`/`stale_seconds` arguments, not the
ivars. (`JSONDownloadStrategy#initialize` then only called `super`, so drop
it too.)
- `@old_homebrew_cellar` in `APIHashable`, which is saved during API
generation but never restored (only the prefix, home and git config are).
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
`brew which-formula` is a `ShellCommand` implemented in Bash
(utils/executables.sh), so these Ruby constants were never read at runtime;
only the spec referenced `DATABASE_FILE`. Remove both and have the spec write
the database to the same path `Homebrew::API.write_executables_file!` uses.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
`Info.requirements_lines` has no callers, and its only callee
`Info.cask_requirements_lines` is unused once it is gone. Remove both and
drop `cask_requirements_lines` from the `private_class_method` list.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
`FormulaWrapper#plist?` and `System.user_of_process` have no callers, and
`System.reset_launchctl!`/`Systemctl.reset_executable!` existed only to let
specs clear memoized lookups. Remove all four from the runtime classes and
move the cache reset into a `Test::Helper::Services#reset_services_memoization!`
helper used by the specs that need it.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
Annotate every method in the Formula Cookbook's "Service block methods"
table as `@api public` in `Service`, with documentation matching the
cookbook's descriptions, and add the missing `stop_timeout` row to the table.
Enforce a 1:1 correspondence between the table and the `service.rb`
annotations in the `Homebrew/PublicApiCookbook` cop, backed by a new
`SERVICE_COOKBOOK_METHODS` list, so the two cannot drift.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
These definitions are no longer referenced anywhere after their last
callers were removed:
- GitHubRunnerMatrix's MacOSRunnerSpecHash and LinuxRunnerSpecHash type
aliases, unused since #22239.
- Utils::Git.count_coauthors, GitHub.count_repository_commits,
GitHub.repo_commits_for_user and
GitHub.search_merged_pull_requests_in_user_or_organisation, unused since
#23206.
Also remove the ::count_repo_commits spec block, which only covered the
removed GitHub commit-counting helpers.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
`FormulaInstaller.clear_attempted`, `clear_installed` and `clear_fetched`
exist only to reset the class-level tracking sets between test examples;
they have no production callers. Keep such test-only helpers out of the
runtime class: remove the three methods and reset the sets directly with
`Set#clear` in the specs that need it.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
Each `Homebrew::Cmd::Services::*Subcommand` defined a `TRIGGERS`
constant, but nothing reads it: the subcommand names and aliases used to
dispatch `brew services <subcommand>` come from `subcommand_name` and the
`subcommand_args aliases:` declaration in `AbstractCommand`. The
constants (and the four specs asserting their contents) are dead
duplication, so remove them.
Claude-Session: https://claude.ai/code/session_011BgWRBydtdkvt8ocqH4nj9
The `HOMEBREW_PRODUCT`, `HOMEBREW_DEFAULT_REPOSITORY`,
`HOMEBREW_MACOS_ARM_DEFAULT_REPOSITORY` and
`HOMEBREW_LINUX_DEFAULT_REPOSITORY` constants in `global.rb` are never
referenced anywhere in the codebase. Remove them along with the now
unnecessary env var exports in `brew.sh`. The underlying shell variables
remain set and used within `brew.sh` where needed; only their exports
(which existed to populate the removed Ruby constants) are dropped.
Claude-Session: https://claude.ai/code/session_01XcY6NJn468hSt2n4WVkfoK
Extracted from the typecheck-deadcode branch (Homebrew/brew#22733 was the
first extraction), which uses Spoom to find code with no static callers.
Remove four methods with no callers:
- Homebrew::API.cached_formula_json_file_path
- Homebrew::API::Cask.all_renames
- Homebrew::API::Formula.all_renames
- APIHashable#generated_hash!
Renames are resolved through Homebrew::API.formula_renames and
Homebrew::API.cask_renames, which read the internal API, so the
all_renames pair on the JSON API classes is unused. The only remaining
references were vestigial RSpec stubs, so drop those too.
APIHashable#generated_hash! reverts the global monkeypatches applied by
generating_hash!. The generate-*-api commands exit once they are done and
never call it, so it is only needed to stop tests leaking global state.
Move it to a test helper rather than keeping it in production code.
Without the annotation the frozen array constant infers as a
fixed-size tuple, which changed how Sorbet resolved the curl_output
calls that splat it, leaving stdout/status typed as NilClass and
SystemCommand::Result. Destructure via an explicit .to_a so the
multiple assignment typechecks against the tuple inference.
Sorbet infers constant types from simple literals, literal arrays and
constructor calls (since 0.6.13304, including .freeze-d values), and
infers instance variable types assigned from signature parameters in
initialize. Remove T.let annotations that just restate those inferred
types, as found by the Sorbet/RedundantTLet and
Sorbet/RedundantTLetForLiteral cops proposed in
https://github.com/Shopify/rubocop-sorbet/pull/381.
dev-cmd/bump.rb's DEFAULT_CURL_ARGS is deliberately left annotated:
without T.let it infers as a fixed-size tuple, which changes how
Sorbet resolves the curl_output calls that splat it and fails brew
typecheck.
Sorbet 0.6.13304 infers `X = A.new.freeze` the same as `X = A.new`,
so constants assigned a direct `.new` call followed by `.freeze` no
longer need explicit `T.let` type annotations. `Pathname(...)`
kernel-method calls are not inferred, so those are converted to
`Pathname.new(...)` instead.
Similarly, pure-literal arrays are inferred through `.freeze` as
fixed-size tuple types, which are subtypes of the previously declared
`T::Array` types. Arrays containing splats, constant references,
method calls or no elements at all are still not inferred and keep
their `T.let` annotations.
Sorbet 0.6.13304+ (sorbet/sorbet#10377) now infers `X = A.new.freeze`
with the same type as `X = A.new`, so `RUBY_PATH` and
`HOMEBREW_BREW_FILE` are typed as `Pathname` instead of `T.untyped`.
Passing them to `Kernel#system`, `Open3.capture3` and `String#gsub`,
whose RBIs only accept `String`, now fails typechecking, so call
`.to_s` at those sites and update the sandbox specs to match.
Removes Formula methods with no callers in Homebrew or the official taps:
specified_name, installed_specified_name, full_formulae, prefix_linked?,
core_alias_files, internal_dependencies_hash and allow_network_access!
(also dropped from FORMULA_COMPONENT_PRECEDENCE_LIST and the generated
DSL RBI), along with their now-dead specs.
Annotates the formula DSL methods that the official taps rely on but
which lacked an `@api` tag as `@api public`: pypi_packages, test_fixtures,
xcodebuild and deny_network_access!.
Remove unused methods in utils/ identified by a Spoom-backed deadcode pass
(that respects `override` sigs and `@api` annotations), along with their
now-orphaned specs: Inreplace.inreplace_pairs, GitRepository.git_commit_message,
GitHub.permission, GitHub.public_member_usernames, AST::FormulaAST#remove_stanzas,
and others.
- Mark TopologicalHash#tsort_each_node/#tsort_each_child as `override`, since
they override TSort's abstract methods.
- Relocate test-only cache resets (Svn.clear_version_cache,
Tar.clear_executable_cache) into their specs, keeping the source focused on
shipped behavior.
- Keep Utils.binary_linked_to_library? (`@api internal`) and Utils::Gzip.compress
(`@api public`): nothing in brew calls them, but homebrew-core does (~58 and
~31 call sites respectively). Document Gzip.compress (covered by the
Reproducible Builds guide) and add it to Style/Documentation's Include list.
These two requires were loaded globally on every brew invocation via
extend/pathname → system_command, but neither was needed at startup:
- `plist`: only used in Result#plist method body; deferred with a lazy
require inside the method. This avoids loading the plist gem and its
transitive deps (cgi, date, stringio) for commands that don't parse
plist output (the vast majority).
- `uri`: URI::Generic only appears in Sorbet sigs. Changed the six
affected sigs to T::Sig::WithoutRuntime.sig so the sig blocks are
not evaluated at runtime, allowing the require to be dropped
entirely. Added an explicit require "stringio" since that was a
transitive dep of plist relied on by utils/tty.rb.
Also adds an AGENTS.md note to always use `./bin/brew ruby` instead
of the system Ruby when running Ruby directly.
`deps_for_dependent` always called `recursive_req_includes` in the
recursive branch, even though `condense_requirements` immediately
discards any `Requirement` objects unless `--include-requirements` is
explicitly passed. Skipping the expansion avoids traversing the static
dependency tree and loading formula objects for transitive dependencies
of every installed formula.
This mirrors the existing pattern in `dependables`, which already guards
`select_includes(formula.requirements, ...)` behind
`args.include_requirements?`.
Also replaces `T.unsafe` with `T.cast` in `dependencies_helpers.rb`
since the return type is known.
Benchmarked on `brew deps --installed` with 235 installed formulae:
~10% wall-time improvement (~20% user-time improvement).
- Use f.possible_names (name + oldnames + aliases) instead of f.name
when checking against tab-derived dep name sets in autoremove, so
renamed formulae are not incorrectly marked as removable.
- Rescue FormulaUnavailableError in the two callers that call
to_installed_formula on missing_dependencies results (diagnostic.rb
and installed_dependents.rb), matching the pre-refactor behaviour of
silently skipping deps whose formula files are no longer available.
autoremove / cleanup
--------------------
bottled_formulae_with_no_formula_dependents: replace
installed_runtime_formula_dependencies with direct tab data reads.
Build a Set of dep base names from tab.runtime_dependencies hashes
instead of resolving each dep via Formulary.resolve. Fallback kept for
pre-1.1.6 installations.
Rename formulae_with_cask_dependents to cask_dependent_formula_names,
returning T::Set[String] instead of T::Array[Formula]. Look up cask
formula deps by name in the already-loaded formulae array (O(1) hash)
instead of calling Formulary.resolve. Same tab-data path for transitive
deps.
brew autoremove --dry-run: 3.68s -> 1.65s (~55% faster)
brew cleanup --dry-run: 7.39s -> 5.72s (~23% faster)
brew doctor (check_missing_deps)
---------------------------------
missing_dependencies called dep.to_installed_formula.installed_prefixes.none?
for every runtime dep of every installed formula, triggering
Formulary.resolve per dep.
Replace with (HOMEBREW_CELLAR/base_name).directory? — a single
filesystem stat. Also extract base_name before the hide check, fixing a
pre-existing bug where tapnamed deps (e.g. "homebrew/core/wget") could
not be matched by base-name hide lists.
check_missing_deps: 0.95s -> 0.06s (~15x faster)
brew doctor overall: 6.24s -> 5.51s (~12% faster)
check_deprecated_disabled
--------------------------
Combine two Formula.installed.select calls into one pass.
Tests
-----
- formula_spec: add describe "#missing_dependencies" covering no tab
data, installed dep, missing dep, hidden dep (even when installed),
and tapnamed dep matched by base-name hide list (the bug-fix case).
- autoremove_spec: add context for when tab.runtime_dependencies returns
actual hashes, asserting installed_runtime_formula_dependencies is not
called (exercises the new fast path rather than only the nil fallback).
`Pathname#children.empty?` reads all directory entries into a Ruby Array
just to check whether any exist. `Pathname#empty?` (backed by
`Dir.empty?`) stops at the first entry, avoiding the full enumeration.
Microbenchmark (100,000 iterations, 11-entry Cellar keg directory):
dir.children.empty? : 3084ms
dir.empty? : 2136ms (~1.44x faster)
`Formula#latest_version_installed?` and the prefix-installed check in
install.rb are called for every formula when commands like
`brew deps --installed` enumerate installed formulae, so the savings
compound across the full set.
Add `--enable-experimental-rspec` to the Sorbet config so that Sorbet
type-checks RSpec spec files. This is an isolated piece of the work in
#21690, separated so that the follow-up work of dialling up the `typed:`
level of individual specs can land independently.
Enabling the flag surfaced a pre-existing type error in
`formula-analytics/pycall-setup.rbi` where `InfluxDBClient3#initialize`
was incorrectly declared as a singleton method (`def self.initialize`)
rather than an instance method (`def initialize`). Fixed here since the
error is only visible with RSpec mode enabled.
Use idiomatic RSpec patterns to avoid needing `rubocop:disable`
for `RSpec/DescribedClass`:
- `free_port_spec.rb`: Test the mixin via
`Object.new.extend(described_class)` instead of `include`-ing
the module into the example group.
- `virtualenv_spec.rb`: Capture `described_class` in a local
variable before the `formula` DSL block so the closure carries
it into the class scope where `described_class` is unavailable.
- Fix incorrect comment that claimed `Module` and `Regexp` are
global variables; replace with accurate description of `$times`.
- Track wrapped method names in `injected_methods` to prevent
duplicate wrapping on repeated calls.
- Fix `@injected_dump_stat_modules` type from `T::Array[String]`
to `T::Array[Symbol]` to match `instance_methods` return type.
`inject_dump_stats!` captured methods via `instance_method` (which
returns the prepended module's version) then redefined them on the
class with `bind_call`. When the captured prepended method called
`super`, it resolved to the newly defined class method, creating an
infinite recursion.
Use an anonymous wrapper module with `super` instead of
`bind_call` to correctly delegate through the method resolution
order without cycles.
Fixes#21587
Split `utils.rb` into `homebrew.rb` (Homebrew module) and
`utils.rb` (Utils module) with a corresponding spec file.
This was motivated by rubocop-rspec disallowing multiple top-level
describes, and it also makes sense for files to match the namespace
they open.
- test_formulae: use [] instead of fetch for @downloaded_artifacts so new
SHAs use the hash default (empty array) and avoid KeyError
- junit: require rexml/document, xmldecl, cdata in #initialize so REXML
is loaded before use (fixes uninitialized constant on Linux CI)
- test_cleanup: pass HOMEBREW_REPOSITORY.to_s to checkout_branch_if_needed,
reset_if_needed, clean_if_needed (fixes Pathname type error when tap set)
- Add test_formulae_spec to prevent KeyError regression for new SHA access
- Add junit_spec to prevent REXML NameError regression
- Add test_cleanup_spec to prevent Pathname regression
Enable strict typing for the abstract base classes in `Cask::Artifact`:
- `AbstractArtifact`
- `AbstractFlightBlock`
- `AbstractUninstall`
- `Relocated`
This adds proper type signatures to all methods and updates the typing
level from `typed: true` to `typed: strict`.
Also marks `english_name` and `dirmethod` as `overridable` in
`AbstractArtifact` and adds `override` to subclasses that override them.
Advances https://github.com/Homebrew/brew/issues/17297
Add `typed: strict` sigil and explicit type signatures throughout
the BottleSpecification class for improved type safety.
Update callers in api/formula_struct, dev-cmd/bottle, and formula
to satisfy the stricter type requirements.
Add `typed: strict` sigil and explicit type signatures throughout
the Bottle class and Bottle::Filename class for improved type safety.
Update callers in attestation, cleanup, executables_db, formula_installer,
and utils/bottles to satisfy the stricter type requirements.
Move `tap_and_name_comparison` from `extend/kernel.rb` to `cask/list.rb`
as a class method since it's only used by the list commands. Update
call sites in `cmd/list.rb` and move corresponding specs.
Add Sorbet type signatures to all methods in `Library/Homebrew/utils/popen.rb`
and update callsites to resolve resulting type errors.
- Add `sig` blocks with type parameters to `popen_read`, `safe_popen_read`,
`popen_write`, `safe_popen_write`, and `popen` methods
- Add `IO#wait_readable` type definition to `upstream.rbi`
- Fix callsite type errors across 13 files:
- Use `.fetch()` instead of `.first`/`.last` for array access
- Add `T.must()` for potentially nil values
- Add `T.cast()` for regex scan results
- Update return type signatures where needed
Contributes to #17297.
Move `disk_usage_readable_size_unit`, `disk_usage_readable`,
`number_readable`, and `redact_secrets` from `extend/kernel.rb` to
`utils/formatter.rb` as class methods. These methods are formatting
utilities that fit better in the Formatter module.
Update all call sites and move corresponding specs to formatter_spec.rb.
Move `truncate_text_to_approximate_size` from `extend/kernel.rb` to
`cmd/gist-logs.rb` as a public class method since `gist-logs` is its
only caller. Move the corresponding specs as well.
Refactors the update report version logic to use Homebrew's existing
`Version` class instead of manual string parsing:
- Use `fetch(0)` instead of `first` for safer array access
- Replace manual version string splitting with `Version#major_minor`
and `Version#patch` methods
- Simplify URL construction using `major_minor` method
This improves code maintainability by leveraging existing version
handling infrastructure and provides better type safety.
## Summary
- Add `Descriptions::SearchField` enum with `Name`, `Description`, and
`Either` values to replace symbol-based field parameter
- Upgrade `descriptions.rb` and `search.rb` to `typed: strict`
- Add Sorbet type signatures to all methods in both files
- Use exhaustive case matching with `T.absurd` for type safety
- Remove redundant `cache_store_hash` parameter by using `is_a?` check
Contributes to #17297.
Add Sorbet `typed: strict` sigil and full type signatures to
`cmd/deps.rb`, promoting the last non-strict brew command to
strict typing.
Changes:
- Add `sig` type signatures to all private methods
- Replace block parameter in `deps_for_dependents` with a
`DepsCombineMode` enum for better type safety
- Initialize `@use_runtime_dependencies` with `T.let` in constructor
Ref: https://github.com/Homebrew/brew/issues/17297