diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index 6499d238a7..854b6fdcee 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -124,7 +124,7 @@ GEM rspec (>= 3, < 4) rspec_junit_formatter (0.5.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.32.0) + rubocop (1.33.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.1.0.0) diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index 4eb4689c9b..e381088a1d 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -160,7 +160,7 @@ module Homebrew cleanup = Cleanup.new(dry_run: dry_run) if cleanup.periodic_clean_due? cleanup.periodic_clean! - elsif f.latest_version_installed? && !cleanup.skip_clean_formula?(f) + elsif f.latest_version_installed? && !Cleanup.skip_clean_formula?(f) ohai "Running `brew cleanup #{f}`..." puts_no_install_cleanup_disable_message_if_not_already! cleanup.cleanup_formula(f) @@ -177,7 +177,7 @@ module Homebrew @puts_no_install_cleanup_disable_message_if_not_already = true end - def skip_clean_formula?(f) + def self.skip_clean_formula?(f) return false if Homebrew::EnvConfig.no_cleanup_formulae.blank? skip_clean_formulae = Homebrew::EnvConfig.no_cleanup_formulae.split(",") @@ -215,10 +215,13 @@ module Homebrew if args.empty? Formula.installed .sort_by(&:name) - .reject { |f| skip_clean_formula?(f) } + .reject { |f| Cleanup.skip_clean_formula?(f) } .each do |formula| cleanup_formula(formula, quiet: quiet, ds_store: false, cache_db: false) end + + Cleanup.autoremove(dry_run: dry_run?) if Homebrew::EnvConfig.autoremove? + cleanup_cache cleanup_logs cleanup_lockfiles @@ -253,7 +256,7 @@ module Homebrew nil end - if formula && skip_clean_formula?(formula) + if formula && Cleanup.skip_clean_formula?(formula) onoe "Refusing to clean #{formula} because it is listed in " \ "#{Tty.bold}HOMEBREW_NO_CLEANUP_FORMULAE#{Tty.reset}!" elsif formula @@ -519,5 +522,36 @@ module Homebrew print "and #{d} directories " if d.positive? puts "from #{HOMEBREW_PREFIX}" end + + def self.autoremove(dry_run: false) + require "cask/caskroom" + + # If this runs after install, uninstall, reinstall or upgrade, + # the cache of installed formulae may no longer be valid. + Formula.clear_cache unless dry_run + + # Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE. + formulae = Formula.installed.reject(&method(:skip_clean_formula?)) + casks = Cask::Caskroom.casks + + removable_formulae = Formula.unused_formulae_with_no_dependents(formulae, casks) + + return if removable_formulae.blank? + + formulae_names = removable_formulae.map(&:full_name).sort + + verb = dry_run ? "Would autoremove" : "Autoremoving" + oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:" + puts formulae_names.join("\n") + return if dry_run + + require "uninstall" + + kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) + Uninstall.uninstall_kegs(kegs_by_rack) + + # The installed formula cache will be invalid after uninstalling. + Formula.clear_cache + end end end diff --git a/Library/Homebrew/cli/args.rbi b/Library/Homebrew/cli/args.rbi index e59eb5cac8..693dd3bbe2 100644 --- a/Library/Homebrew/cli/args.rbi +++ b/Library/Homebrew/cli/args.rbi @@ -303,6 +303,15 @@ module Homebrew sig { returns(T.nilable(String)) } def screen_saverdir; end + sig { returns(T::Array[String])} + def repositories; end + + sig { returns(T.nilable(String)) } + def from; end + + sig { returns(T.nilable(String)) } + def to; end + sig { returns(T.nilable(T::Array[String])) } def groups; end diff --git a/Library/Homebrew/cmd/autoremove.rb b/Library/Homebrew/cmd/autoremove.rb index a80cb4ae49..94dea99605 100644 --- a/Library/Homebrew/cmd/autoremove.rb +++ b/Library/Homebrew/cmd/autoremove.rb @@ -1,9 +1,8 @@ # typed: true # frozen_string_literal: true -require "formula" +require "cleanup" require "cli/parser" -require "uninstall" module Homebrew module_function @@ -20,37 +19,9 @@ module Homebrew end end - def get_removable_formulae(formulae) - removable_formulae = Formula.installed_formulae_with_no_dependents(formulae).reject do |f| - Tab.for_keg(f.any_installed_keg).installed_on_request - end - - removable_formulae += get_removable_formulae(formulae - removable_formulae) if removable_formulae.present? - - removable_formulae - end - def autoremove args = autoremove_args.parse - removable_formulae = get_removable_formulae(Formula.installed) - - if (casks = Cask::Caskroom.casks.presence) - removable_formulae -= casks.flat_map { |cask| cask.depends_on[:formula] } - .compact - .map { |f| Formula[f] } - .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } - end - return if removable_formulae.blank? - - formulae_names = removable_formulae.map(&:full_name).sort - - verb = args.dry_run? ? "Would uninstall" : "Uninstalling" - oh1 "#{verb} #{formulae_names.count} unneeded #{"formula".pluralize(formulae_names.count)}:" - puts formulae_names.join("\n") - return if args.dry_run? - - kegs_by_rack = removable_formulae.map(&:any_installed_keg).group_by(&:rack) - Uninstall.uninstall_kegs(kegs_by_rack) + Cleanup.autoremove(dry_run: args.dry_run?) end end diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index 1c003b4b96..ec134de4a1 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -37,7 +37,7 @@ module Homebrew def leaves args = leaves_args.parse - leaves_list = Formula.installed_formulae_with_no_dependents + leaves_list = Formula.formulae_with_no_formula_dependents(Formula.installed) leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request? leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency? diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb index 8c40d4219d..50a9abd5d1 100644 --- a/Library/Homebrew/cmd/uninstall.rb +++ b/Library/Homebrew/cmd/uninstall.rb @@ -50,6 +50,11 @@ module Homebrew all_kegs: args.force?, ) + # If ignore_unavailable is true and the named args + # are a series of invalid kegs and casks, + # #to_kegs_to_casks will return empty arrays. + return if all_kegs.blank? && casks.blank? + kegs_by_rack = all_kegs.group_by(&:rack) Uninstall.uninstall_kegs( @@ -73,5 +78,7 @@ module Homebrew force: args.force?, ) end + + Cleanup.autoremove if Homebrew::EnvConfig.autoremove? end end diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index 89b8f50fca..dbc9a7fbfc 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -148,6 +148,8 @@ module Homebrew Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"] return if Homebrew::EnvConfig.disable_load_formula? + migrate_gcc_dependents_if_needed + hub = ReporterHub.new updated_taps = [] @@ -289,6 +291,33 @@ module Homebrew #{e} EOS end + + def migrate_gcc_dependents_if_needed + return if OS.mac? + return if Settings.read("gcc-rpaths.fixed") == "true" + + Formula.installed.each do |formula| + next unless formula.tap&.core_tap? + + recursive_runtime_dependencies = Dependency.expand( + formula, + cache_key: "update-report", + ) do |_, dependency| + Dependency.prune if dependency.build? || dependency.test? + end + next unless recursive_runtime_dependencies.map(&:name).include? "gcc" + + keg = formula.installed_kegs.last + tab = Tab.for_keg(keg) + # Force reinstallation upon `brew upgrade` to fix the bottle RPATH. + tab.source["versions"]["version_scheme"] = -1 + tab.write + rescue TapFormulaUnavailableError + nil + end + + Settings.write "gcc-rpaths.fixed", true + end end class Reporter diff --git a/Library/Homebrew/default_prefix.rb b/Library/Homebrew/default_prefix.rb index b22f98268d..70e493c233 100644 --- a/Library/Homebrew/default_prefix.rb +++ b/Library/Homebrew/default_prefix.rb @@ -1,10 +1,12 @@ # typed: true # frozen_string_literal: true +require "simulate_system" + module Homebrew DEFAULT_PREFIX, DEFAULT_REPOSITORY = if OS.mac? && Hardware::CPU.arm? [HOMEBREW_MACOS_ARM_DEFAULT_PREFIX, HOMEBREW_MACOS_ARM_DEFAULT_REPOSITORY] - elsif OS.linux? && !EnvConfig.simulate_macos_on_linux? + elsif Homebrew::SimulateSystem.simulating_or_running_on_linux? [HOMEBREW_LINUX_DEFAULT_PREFIX, HOMEBREW_LINUX_DEFAULT_REPOSITORY] else [HOMEBREW_DEFAULT_PREFIX, HOMEBREW_DEFAULT_REPOSITORY] diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb new file mode 100755 index 0000000000..946c40b8f1 --- /dev/null +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -0,0 +1,109 @@ +# typed: true +# frozen_string_literal: true + +require "cli/parser" + +module Homebrew + extend T::Sig + + module_function + + SUPPORTED_REPOS = [ + %w[brew core cask], + OFFICIAL_CMD_TAPS.keys.map { |t| t.delete_prefix("homebrew/") }, + OFFICIAL_CASK_TAPS.reject { |t| t == "cask" }, + ].flatten.freeze + + sig { returns(CLI::Parser) } + def contributions_args + Homebrew::CLI::Parser.new do + usage_banner "`contributions` [<--repositories>`=`]" + description <<~EOS + Contributions to Homebrew repos for a user. + + The first argument is a name (e.g. "BrewTestBot") or an email address (e.g. "brewtestbot@brew.sh"). + EOS + + comma_array "--repositories", + description: "Specify a comma-separated (no spaces) list of repositories to search. " \ + "Supported repositories: #{SUPPORTED_REPOS.map { |t| "`#{t}`" }.to_sentence}." \ + "Omitting this flag, or specifying `--repositories=all`, will search all repositories." + flag "--from=", + description: "Date (ISO-8601 format) to start searching contributions." + + flag "--to=", + description: "Date (ISO-8601 format) to stop searching contributions." + + named_args number: 1 + end + end + + sig { void } + def contributions + args = contributions_args.parse + + commits = 0 + coauthorships = 0 + + all_repos = args.repositories.nil? || args.repositories.include?("all") + repos = all_repos ? SUPPORTED_REPOS : args.repositories + + repos.each do |repo| + if SUPPORTED_REPOS.exclude?(repo) + return ofail "Unsupported repository: #{repo}. Try one of #{SUPPORTED_REPOS.join(", ")}." + end + + repo_path = find_repo_path_for_repo(repo) + unless repo_path.exist? + + opoo "Repository #{repo} not yet tapped! Tapping it now..." + Tap.fetch("homebrew", repo).install + end + + commits += git_log_author_cmd(T.must(repo_path), args) + coauthorships += git_log_coauthor_cmd(T.must(repo_path), args) + end + + sentence = "#{args.named.first} directly authored #{commits} commits " \ + "and co-authored #{coauthorships} commits " \ + "across #{all_repos ? "all Homebrew repos" : repos.to_sentence}" + sentence += if args.from && args.to + " between #{args.from} and #{args.to}" + elsif args.from + " after #{args.from}" + elsif args.to + " before #{args.to}" + else + " in all time" + end + sentence += "." + + puts sentence + end + + sig { params(repo: String).returns(Pathname) } + def find_repo_path_for_repo(repo) + return HOMEBREW_REPOSITORY if repo == "brew" + + Tap.fetch("homebrew", repo).path + end + + sig { params(repo_path: Pathname, args: Homebrew::CLI::Args).returns(Integer) } + def git_log_author_cmd(repo_path, args) + cmd = ["git", "-C", repo_path, "log", "--oneline", "--author=#{args.named.first}"] + cmd << "--before=#{args.to}" if args.to + cmd << "--after=#{args.from}" if args.from + + Utils.safe_popen_read(*cmd).lines.count + end + + sig { params(repo_path: Pathname, args: Homebrew::CLI::Args).returns(Integer) } + def git_log_coauthor_cmd(repo_path, args) + cmd = ["git", "-C", repo_path, "log", "--oneline"] + cmd << "--format='%(trailers:key=Co-authored-by:)'" + cmd << "--before=#{args.to}" if args.to + cmd << "--after=#{args.from}" if args.from + + Utils.safe_popen_read(*cmd).lines.count { |l| l.include?(args.named.first) } + end +end diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb index 52dd6c20c1..9bac23c98c 100644 --- a/Library/Homebrew/dev-cmd/pr-pull.rb +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -368,42 +368,46 @@ module Homebrew end end - def pr_check_conflicts(name, tap_remote_repo, pr) - hash_template = proc { |h, k| h[k] = [] } + def pr_check_conflicts(user, repo, pr) long_build_pr_files = GitHub.search_issues( - "org:#{name}", repo: tap_remote_repo, state: "open", label: "\"no long build conflict\"" - ).each_with_object(Hash.new(hash_template)) do |long_build_pr, hash| + "org:#{user}", repo: repo, state: "open", label: "\"no long build conflict\"" + ).each_with_object({}) do |long_build_pr, hash| number = long_build_pr["number"] - GitHub.get_pull_request_changed_files(name, tap_remote_repo, number).each do |file| + next if number == pr.to_i + + GitHub.get_pull_request_changed_files("#{user}/#{repo}", number).each do |file| key = file["filename"] + hash[key] ||= [] hash[key] << number end end - this_pr_files = GitHub.get_pull_request_changed_files(name, tap_remote_repo, pr) + this_pr_files = GitHub.get_pull_request_changed_files("#{user}/#{repo}", pr) - conflicts = this_pr_files.each_with_object(Hash.new(hash_template)) do |file, hash| + conflicts = this_pr_files.each_with_object({}) do |file, hash| filename = file["filename"] next unless long_build_pr_files.key?(filename) long_build_pr_files[filename].each do |pr_number| - key = "#{tap_remote_repo}/pull/#{pr_number}" + key = "#{user}/#{repo}/pull/#{pr_number}" + hash[key] ||= [] hash[key] << filename end end + return if conflicts.blank? # Raise an error, display the conflicting PR. For example: # Error: You are trying to merge a pull request that conflicts with a long running build in: - # { - # "homebrew-core/pull/98809": [ - # "Formula/icu4c.rb", - # "Formula/node@10.rb" - # ] - # } + # { + # "homebrew-core/pull/98809": [ + # "Formula/icu4c.rb", + # "Formula/node@10.rb" + # ] + # } odie <<~EOS You are trying to merge a pull request that conflicts with a long running build in: - #{JSON.pretty_generate(conflicts)} + #{JSON.pretty_generate(conflicts)} EOS end diff --git a/Library/Homebrew/env_config.rb b/Library/Homebrew/env_config.rb index 6397f0abb4..014b24d27d 100644 --- a/Library/Homebrew/env_config.rb +++ b/Library/Homebrew/env_config.rb @@ -36,6 +36,12 @@ module Homebrew "disable auto-update entirely with HOMEBREW_NO_AUTO_UPDATE.", default: 300, }, + HOMEBREW_AUTOREMOVE: { + description: "If set, calls to `brew cleanup` and `brew uninstall` will automatically " \ + "remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, " \ + "`brew cleanup` will start running `brew autoremove` periodically.", + boolean: true, + }, HOMEBREW_BAT: { description: "If set, use `bat` for the `brew cat` command.", boolean: true, @@ -263,8 +269,8 @@ module Homebrew boolean: true, }, HOMEBREW_NO_CLEANUP_FORMULAE: { - description: "A comma-separated list of formulae. Homebrew will refuse to clean up a " \ - "formula if it appears on this list.", + description: "A comma-separated list of formulae. Homebrew will refuse to clean up " \ + "or autoremove a formula if it appears on this list.", }, HOMEBREW_NO_COLOR: { description: "If set, do not print text with colour added.", diff --git a/Library/Homebrew/extend/on_system.rb b/Library/Homebrew/extend/on_system.rb index a7f7b3744f..77f323f24c 100644 --- a/Library/Homebrew/extend/on_system.rb +++ b/Library/Homebrew/extend/on_system.rb @@ -20,11 +20,6 @@ module OnSystem sig { params(os_name: Symbol, or_condition: T.nilable(Symbol)).returns(T::Boolean) } def os_condition_met?(os_name, or_condition = nil) - if Homebrew::EnvConfig.simulate_macos_on_linux? - return false if os_name == :linux - return true if [:macos, *MacOSVersions::SYMBOLS.keys].include?(os_name) - end - return Homebrew::SimulateSystem.send("simulating_or_running_on_#{os_name}?") if BASE_OS_OPTIONS.include?(os_name) raise ArgumentError, "Invalid OS condition: #{os_name.inspect}" unless MacOSVersions::SYMBOLS.key?(os_name) @@ -36,7 +31,13 @@ module OnSystem return false if Homebrew::SimulateSystem.simulating_or_running_on_linux? base_os = MacOS::Version.from_symbol(os_name) - current_os = MacOS::Version.from_symbol(Homebrew::SimulateSystem.current_os) + current_os = if Homebrew::SimulateSystem.current_os == :macos + # Assume the oldest macOS version when simulating a generic macOS version + # Version::NULL is always treated as less than any other version. + Version::NULL + else + MacOS::Version.from_symbol(Homebrew::SimulateSystem.current_os) + end return current_os >= base_os if or_condition == :or_newer return current_os <= base_os if or_condition == :or_older diff --git a/Library/Homebrew/extend/os/linux/keg_relocate.rb b/Library/Homebrew/extend/os/linux/keg_relocate.rb index 7cc2f27be2..887ff07c80 100644 --- a/Library/Homebrew/extend/os/linux/keg_relocate.rb +++ b/Library/Homebrew/extend/os/linux/keg_relocate.rb @@ -8,9 +8,6 @@ class Keg # Patching the dynamic linker of glibc breaks it. return if name.match? Version.formula_optionally_versioned_regex(:glibc) - # Patching patchelf fails with "Text file busy" or SIGBUS. - return if name == "patchelf" - old_prefix, new_prefix = relocation.replacement_pair_for(:prefix) elf_files.each do |file| @@ -92,16 +89,4 @@ class Keg end elf_files end - - def self.bottle_dependencies - @bottle_dependencies ||= begin - formulae = [] - gcc = Formulary.factory(CompilerSelector.preferred_gcc) - if !Homebrew::EnvConfig.simulate_macos_on_linux? && - DevelopmentTools.non_apple_gcc_version("gcc") < gcc.version.to_i - formulae << gcc - end - formulae - end - end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index d2f9ea56f2..0f2b71767d 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1706,14 +1706,46 @@ class Formula end.uniq(&:name) end - # An array of all installed {Formula} without dependents + # An array of all installed {Formula} with {Cask} dependents. # @private - def self.installed_formulae_with_no_dependents(formulae = installed) + def self.formulae_with_cask_dependents(casks) + casks.flat_map { |cask| cask.depends_on[:formula] } + .compact + .map { |f| Formula[f] } + .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } + end + + # An array of all installed {Formula} without {Formula} dependents + # @private + def self.formulae_with_no_formula_dependents(formulae) return [] if formulae.blank? formulae - formulae.flat_map(&:runtime_formula_dependencies) end + # Recursive function that returns an array of {Formula} without + # {Formula} dependents that weren't installed on request. + # @private + def self.unused_formulae_with_no_formula_dependents(formulae) + unused_formulae = formulae_with_no_formula_dependents(formulae).reject do |f| + Tab.for_keg(f.any_installed_keg).installed_on_request + end + + if unused_formulae.present? + unused_formulae += unused_formulae_with_no_formula_dependents(formulae - unused_formulae) + end + + unused_formulae + end + + # An array of {Formula} without {Formula} or {Cask} + # dependents that weren't installed on request. + # @private + def self.unused_formulae_with_no_dependents(formulae, casks) + unused_formulae = unused_formulae_with_no_formula_dependents(formulae) + unused_formulae - formulae_with_cask_dependents(casks) + end + def self.installed_with_alias_path(alias_path) return [] if alias_path.nil? diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index 2cc9ad690e..bb6507fefa 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -336,7 +336,7 @@ module Homebrew # The number of conflicts on Linux is absurd. # TODO: remove this and check these there too. - return if OS.linux? && !Homebrew::EnvConfig.simulate_macos_on_linux? + return if Homebrew::SimulateSystem.simulating_or_running_on_linux? recursive_runtime_formulae = formula.runtime_formula_dependencies(undeclared: false) version_hash = {} diff --git a/Library/Homebrew/keg_relocate.rb b/Library/Homebrew/keg_relocate.rb index 1df8ee86a4..a396f43205 100644 --- a/Library/Homebrew/keg_relocate.rb +++ b/Library/Homebrew/keg_relocate.rb @@ -368,7 +368,14 @@ class Keg end def self.bottle_dependencies - [] + return [] unless Homebrew::SimulateSystem.simulating_or_running_on_linux? + + @bottle_dependencies ||= begin + formulae = [] + gcc = Formulary.factory(CompilerSelector.preferred_gcc) + formulae << gcc if DevelopmentTools.non_apple_gcc_version("gcc") < gcc.version.to_i + formulae + end end end diff --git a/Library/Homebrew/official_taps.rb b/Library/Homebrew/official_taps.rb index 53fb779b2c..75c0f232db 100644 --- a/Library/Homebrew/official_taps.rb +++ b/Library/Homebrew/official_taps.rb @@ -3,6 +3,8 @@ OFFICIAL_CASK_TAPS = %w[ cask + cask-drivers + cask-fonts cask-versions ].freeze diff --git a/Library/Homebrew/resource_auditor.rb b/Library/Homebrew/resource_auditor.rb index 8155d311c0..624f2b8e07 100644 --- a/Library/Homebrew/resource_auditor.rb +++ b/Library/Homebrew/resource_auditor.rb @@ -105,7 +105,7 @@ module Homebrew # Ideally `ca-certificates` would not be excluded here, but sourcing a HTTP mirror was tricky. # Instead, we have logic elsewhere to pass `--insecure` to curl when downloading the certs. # TODO: try remove the OS/env conditional - if (OS.mac? || Homebrew::EnvConfig.simulate_macos_on_linux?) && spec_name == :stable && + if Homebrew::SimulateSystem.simulating_or_running_on_macos? && spec_name == :stable && owner.name != "ca-certificates" && curl_dep && !urls.find { |u| u.start_with?("http://") } problem "should always include at least one HTTP mirror" end diff --git a/Library/Homebrew/rubocops/components_order.rb b/Library/Homebrew/rubocops/components_order.rb index 002ce1f9cf..5d28a172a1 100644 --- a/Library/Homebrew/rubocops/components_order.rb +++ b/Library/Homebrew/rubocops/components_order.rb @@ -14,12 +14,6 @@ module RuboCop class ComponentsOrder < FormulaCop extend AutoCorrector - def on_system_methods - @on_system_methods ||= [:intel, :arm, :macos, :linux, :system, *MacOSVersions::SYMBOLS.keys].map do |m| - :"on_#{m}" - end - end - def audit_formula(_node, _class_node, _parent_class_node, body_node) @present_components, @offensive_nodes = check_order(FORMULA_COMPONENT_PRECEDENCE_LIST, body_node) diff --git a/Library/Homebrew/rubocops/dependency_order.rb b/Library/Homebrew/rubocops/dependency_order.rb index fc12fe4119..34775de930 100644 --- a/Library/Homebrew/rubocops/dependency_order.rb +++ b/Library/Homebrew/rubocops/dependency_order.rb @@ -16,7 +16,7 @@ module RuboCop def audit_formula(_node, _class_node, _parent_class_node, body_node) check_dependency_nodes_order(body_node) check_uses_from_macos_nodes_order(body_node) - [:head, :stable].each do |block_name| + ([:head, :stable] + on_system_methods).each do |block_name| block = find_block(body_node, block_name) next unless block diff --git a/Library/Homebrew/rubocops/extend/formula.rb b/Library/Homebrew/rubocops/extend/formula.rb index e6cc658350..d4b09bab73 100644 --- a/Library/Homebrew/rubocops/extend/formula.rb +++ b/Library/Homebrew/rubocops/extend/formula.rb @@ -198,6 +198,12 @@ module RuboCop @file_path !~ Regexp.union(paths_to_exclude) end + + def on_system_methods + @on_system_methods ||= [:intel, :arm, :macos, :linux, :system, *MacOSVersions::SYMBOLS.keys].map do |m| + :"on_#{m}" + end + end end end end diff --git a/Library/Homebrew/simulate_system.rb b/Library/Homebrew/simulate_system.rb index de61eabef7..206e9a460f 100644 --- a/Library/Homebrew/simulate_system.rb +++ b/Library/Homebrew/simulate_system.rb @@ -9,7 +9,14 @@ module Homebrew class << self extend T::Sig - attr_reader :os, :arch + attr_reader :arch + + sig { returns(T.nilable(Symbol)) } + def os + return :macos if @os.blank? && !OS.mac? && Homebrew::EnvConfig.simulate_macos_on_linux? + + @os + end sig { params(new_os: Symbol).void } def os=(new_os) @@ -33,16 +40,16 @@ module Homebrew sig { returns(T::Boolean) } def simulating_or_running_on_macos? - return OS.mac? if @os.blank? + return OS.mac? if os.blank? - [:macos, *MacOSVersions::SYMBOLS.keys].include?(@os) + [:macos, *MacOSVersions::SYMBOLS.keys].include?(os) end sig { returns(T::Boolean) } def simulating_or_running_on_linux? - return OS.linux? if @os.blank? + return OS.linux? if os.blank? - @os == :linux + os == :linux end sig { returns(Symbol) } @@ -52,7 +59,7 @@ module Homebrew sig { returns(Symbol) } def current_os - return @os if @os.present? + return T.must(os) if os.present? return :linux if OS.linux? MacOS.version.to_sym diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index 30e9856d83..0a246d3469 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -170,14 +170,16 @@ class SoftwareSpec @uses_from_macos_elements << deps - # Linux simulating macOS. Assume oldest macOS version. - return if Homebrew::EnvConfig.simulate_macos_on_linux? && !bounds.key?(:since) - - # macOS new enough for dependency to not be required. + # Check whether macOS is new enough for dependency to not be required. if Homebrew::SimulateSystem.simulating_or_running_on_macos? - current_os = MacOS::Version.from_symbol(Homebrew::SimulateSystem.current_os) - since_os = MacOS::Version.from_symbol(bounds[:since]) if bounds.key?(:since) - return if current_os >= since_os + # Assume the oldest macOS version when simulating a generic macOS version + return if Homebrew::SimulateSystem.current_os == :macos && !bounds.key?(:since) + + if Homebrew::SimulateSystem.current_os != :macos + current_os = MacOS::Version.from_symbol(Homebrew::SimulateSystem.current_os) + since_os = MacOS::Version.from_symbol(bounds[:since]) if bounds.key?(:since) + return if current_os >= since_os + end end depends_on deps diff --git a/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi b/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi index ba6af6ace7..f22b19bd3a 100644 --- a/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi +++ b/Library/Homebrew/sorbet/rbi/hidden-definitions/hidden.rbi @@ -2442,6 +2442,8 @@ module Homebrew::EnvConfig def self.artifact_domain(); end + def self.autoremove?(); end + def self.auto_update_secs(); end def self.bat?(); end diff --git a/Library/Homebrew/test/cmd/autoremove_spec.rb b/Library/Homebrew/test/cmd/autoremove_spec.rb index 24069e0bb7..1644fbf009 100644 --- a/Library/Homebrew/test/cmd/autoremove_spec.rb +++ b/Library/Homebrew/test/cmd/autoremove_spec.rb @@ -5,4 +5,34 @@ require "cmd/shared_examples/args_parse" describe "brew autoremove" do it_behaves_like "parseable arguments" + + describe "integration test" do + let(:requested_formula) { Formula["testball1"] } + let(:unused_formula) { Formula["testball2"] } + + before do + install_test_formula "testball1" + install_test_formula "testball2" + + # Make testball2 an unused dependency + tab = Tab.for_name("testball2") + tab.installed_on_request = false + tab.installed_as_dependency = true + tab.write + end + + it "only removes unused dependencies", :integration_test do + expect(requested_formula.any_version_installed?).to be true + expect(unused_formula.any_version_installed?).to be true + + # When there are unused dependencies + expect { brew "autoremove" } + .to be_a_success + .and output(/Autoremoving/).to_stdout + .and not_to_output.to_stderr + + expect(requested_formula.any_version_installed?).to be true + expect(unused_formula.any_version_installed?).to be false + end + end end diff --git a/Library/Homebrew/test/dev-cmd/contributions_spec.rb b/Library/Homebrew/test/dev-cmd/contributions_spec.rb new file mode 100644 index 0000000000..bd60333dc0 --- /dev/null +++ b/Library/Homebrew/test/dev-cmd/contributions_spec.rb @@ -0,0 +1,8 @@ +# typed: false +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" + +describe "brew contributions" do + it_behaves_like "parseable arguments" +end diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 0883985623..928f53809b 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -446,40 +446,133 @@ describe Formula do end end - describe "::installed_formulae_with_no_dependents" do - let(:formula_is_dep) do - formula "foo" do - url "foo-1.1" + shared_context "with formulae for dependency testing" do + let(:formula_with_deps) do + formula "zero" do + url "zero-1.0" end end - let(:formula_with_deps) do - formula "bar" do - url "bar-1.0" + let(:formula_is_dep1) do + formula "one" do + url "one-1.1" + end + end + + let(:formula_is_dep2) do + formula "two" do + url "two-1.1" end end let(:formulae) do [ formula_with_deps, - formula_is_dep, + formula_is_dep1, + formula_is_dep2, ] end before do - allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep]) + allow(formula_with_deps).to receive(:runtime_formula_dependencies).and_return([formula_is_dep1, + formula_is_dep2]) + allow(formula_is_dep1).to receive(:runtime_formula_dependencies).and_return([formula_is_dep2]) end + end - specify "without formulae parameter" do - allow(described_class).to receive(:installed).and_return(formulae) + describe "::formulae_with_no_formula_dependents" do + include_context "with formulae for dependency testing" - expect(described_class.installed_formulae_with_no_dependents) + it "filters out dependencies" do + expect(described_class.formulae_with_no_formula_dependents(formulae)) .to eq([formula_with_deps]) end + end - specify "with formulae parameter" do - expect(described_class.installed_formulae_with_no_dependents(formulae)) - .to eq([formula_with_deps]) + describe "::unused_formulae_with_no_formula_dependents" do + include_context "with formulae for dependency testing" + + let(:tab_from_keg) { double } + + before do + allow(Tab).to receive(:for_keg).and_return(tab_from_keg) + end + + specify "installed on request" do + allow(tab_from_keg).to receive(:installed_on_request).and_return(true) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) + .to eq([]) + end + + specify "not installed on request" do + allow(tab_from_keg).to receive(:installed_on_request).and_return(false) + expect(described_class.unused_formulae_with_no_formula_dependents(formulae)) + .to eq(formulae) + end + end + + shared_context "with formulae and casks for dependency testing" do + include_context "with formulae for dependency testing" + + require "cask/cask_loader" + + let(:cask_one_dep) do + Cask::CaskLoader.load(+<<-RUBY) + cask "red" do + depends_on formula: "two" + end + RUBY + end + + let(:cask_multiple_deps) do + Cask::CaskLoader.load(+<<-RUBY) + cask "blue" do + depends_on formula: "zero" + end + RUBY + end + + let(:cask_no_deps1) do + Cask::CaskLoader.load(+<<-RUBY) + cask "green" do + end + RUBY + end + + let(:cask_no_deps2) do + Cask::CaskLoader.load(+<<-RUBY) + cask "purple" do + end + RUBY + end + + let(:casks_no_deps) { [cask_no_deps1, cask_no_deps2] } + let(:casks_one_dep) { [cask_no_deps1, cask_no_deps2, cask_one_dep] } + let(:casks_multiple_deps) { [cask_no_deps1, cask_no_deps2, cask_multiple_deps] } + + before do + allow(described_class).to receive("[]").with("zero").and_return(formula_with_deps) + allow(described_class).to receive("[]").with("one").and_return(formula_is_dep1) + allow(described_class).to receive("[]").with("two").and_return(formula_is_dep2) + end + end + + describe "::formulae_with_cask_dependents" do + include_context "with formulae and casks for dependency testing" + + specify "no dependents" do + expect(described_class.formulae_with_cask_dependents(casks_no_deps)) + .to eq([]) + end + + specify "one dependent" do + expect(described_class.formulae_with_cask_dependents(casks_one_dep)) + .to eq([formula_is_dep2]) + end + + specify "multiple dependents" do + expect(described_class.formulae_with_cask_dependents(casks_multiple_deps)) + .to eq(formulae) end end diff --git a/Library/Homebrew/test/rubocops/dependency_order_spec.rb b/Library/Homebrew/test/rubocops/dependency_order_spec.rb index b080d55ef7..465477dac0 100644 --- a/Library/Homebrew/test/rubocops/dependency_order_spec.rb +++ b/Library/Homebrew/test/rubocops/dependency_order_spec.rb @@ -114,6 +114,34 @@ describe RuboCop::Cop::FormulaAudit::DependencyOrder do end RUBY end + + it "reports and corrects wrong conditional order within a system block" do + expect_offense(<<~RUBY) + class Foo < Formula + homepage "https://brew.sh" + url "https://brew.sh/foo-1.0.tgz" + on_arm do + uses_from_macos "apple" if build.with? "foo" + uses_from_macos "bar" + ^^^^^^^^^^^^^^^^^^^^^ dependency "bar" (line 6) should be put before dependency "apple" (line 5) + uses_from_macos "foo" => :optional + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dependency "foo" (line 7) should be put before dependency "apple" (line 5) + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + homepage "https://brew.sh" + url "https://brew.sh/foo-1.0.tgz" + on_arm do + uses_from_macos "bar" + uses_from_macos "foo" => :optional + uses_from_macos "apple" if build.with? "foo" + end + end + RUBY + end end context "when auditing `depends_on`" do @@ -224,5 +252,33 @@ describe RuboCop::Cop::FormulaAudit::DependencyOrder do end RUBY end + + it "reports and corrects wrong conditional order within a system block" do + expect_offense(<<~RUBY) + class Foo < Formula + homepage "https://brew.sh" + url "https://brew.sh/foo-1.0.tgz" + on_linux do + depends_on "apple" if build.with? "foo" + depends_on "bar" + ^^^^^^^^^^^^^^^^ dependency "bar" (line 6) should be put before dependency "apple" (line 5) + depends_on "foo" => :optional + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dependency "foo" (line 7) should be put before dependency "apple" (line 5) + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + homepage "https://brew.sh" + url "https://brew.sh/foo-1.0.tgz" + on_linux do + depends_on "bar" + depends_on "foo" => :optional + depends_on "apple" if build.with? "foo" + end + end + RUBY + end end end diff --git a/Library/Homebrew/test/simulate_system_spec.rb b/Library/Homebrew/test/simulate_system_spec.rb index 0b72700d9f..3923680064 100644 --- a/Library/Homebrew/test/simulate_system_spec.rb +++ b/Library/Homebrew/test/simulate_system_spec.rb @@ -36,6 +36,12 @@ describe Homebrew::SimulateSystem do described_class.os = :monterey expect(described_class.simulating_or_running_on_macos?).to be true end + + it "returns true on Linux with HOMEBREW_SIMULATE_MACOS_ON_LINUX", :needs_linux do + described_class.clear + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + expect(described_class.simulating_or_running_on_macos?).to be true + end end describe "::simulating_or_running_on_linux?" do @@ -66,6 +72,12 @@ describe Homebrew::SimulateSystem do described_class.os = :monterey expect(described_class.simulating_or_running_on_linux?).to be false end + + it "returns false on Linux with HOMEBREW_SIMULATE_MACOS_ON_LINUX", :needs_linux do + described_class.clear + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + expect(described_class.simulating_or_running_on_linux?).to be false + end end describe "::current_arch" do @@ -114,5 +126,17 @@ describe Homebrew::SimulateSystem do described_class.os = :monterey expect(described_class.current_os).to eq :monterey end + + it "returns the current macOS version on macOS with HOMEBREW_SIMULATE_MACOS_ON_LINUX", :needs_macos do + described_class.clear + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + expect(described_class.current_os).to eq MacOS.version.to_sym + end + + it "returns `:macos` on Linux with HOMEBREW_SIMULATE_MACOS_ON_LINUX", :needs_linux do + described_class.clear + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + expect(described_class.current_os).to eq :macos + end end end diff --git a/Library/Homebrew/test/software_spec_spec.rb b/Library/Homebrew/test/software_spec_spec.rb index a737088803..8271b35681 100644 --- a/Library/Homebrew/test/software_spec_spec.rb +++ b/Library/Homebrew/test/software_spec_spec.rb @@ -150,6 +150,20 @@ describe SoftwareSpec do expect(spec.deps.first.tags).to include(:build) end + it "ignores dependencies with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + spec.uses_from_macos("foo") + + expect(spec.deps).to be_empty + end + + it "ignores dependencies with tags with HOMEBREW_SIMULATE_MACOS_ON_LINUX" do + ENV["HOMEBREW_SIMULATE_MACOS_ON_LINUX"] = "1" + spec.uses_from_macos("foo" => :build) + + expect(spec.deps).to be_empty + end + it "ignores OS version specifications" do spec.uses_from_macos("foo", since: :mojave) spec.uses_from_macos("bar" => :build, :since => :mojave) diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index f75af77fa1..d3d956bd55 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -480,7 +480,7 @@ module GitHub def check_for_duplicate_pull_requests(name, tap_remote_repo, state:, file:, args:, version: nil) pull_requests = fetch_pull_requests(name, tap_remote_repo, state: state, version: version).select do |pr| get_pull_request_changed_files( - name, tap_remote_repo, pr["number"] + tap_remote_repo, pr["number"] ).any? { |f| f["filename"] == file } end return if pull_requests.blank? @@ -502,8 +502,8 @@ module GitHub end end - def get_pull_request_changed_files(name, tap_remote_repo, pr) - API.open_rest(url_to("repos", name, tap_remote_repo, "pulls", pr, "files")) + def get_pull_request_changed_files(tap_remote_repo, pr) + API.open_rest(url_to("repos", tap_remote_repo, "pulls", pr, "files")) end def forked_repo_info!(tap_remote_repo, org: nil) diff --git a/Library/Homebrew/utils/ruby.sh b/Library/Homebrew/utils/ruby.sh index 3293949f53..51e1f9eab0 100644 --- a/Library/Homebrew/utils/ruby.sh +++ b/Library/Homebrew/utils/ruby.sh @@ -47,10 +47,10 @@ need_vendored_ruby() { if [[ -n "${HOMEBREW_FORCE_VENDOR_RUBY}" ]] then return 0 - elif [[ -n "${HOMEBREW_MACOS_SYSTEM_RUBY_NEW_ENOUGH}" ]] + elif [[ -n "${HOMEBREW_MACOS_SYSTEM_RUBY_NEW_ENOUGH}" && -z "${HOMEBREW_USE_RUBY_FROM_PATH}" ]] then return 1 - elif [[ -z "${HOMEBREW_MACOS}" ]] && test_ruby "${HOMEBREW_RUBY_PATH}" + elif [[ -z "${HOMEBREW_MACOS}" || -n "${HOMEBREW_USE_RUBY_FROM_PATH}" ]] && test_ruby "${HOMEBREW_RUBY_PATH}" then return 1 else diff --git a/Library/Homebrew/vendor/bundle/bundler/setup.rb b/Library/Homebrew/vendor/bundle/bundler/setup.rb index b0b852f339..355961b21a 100644 --- a/Library/Homebrew/vendor/bundle/bundler/setup.rb +++ b/Library/Homebrew/vendor/bundle/bundler/setup.rb @@ -86,7 +86,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec_junit_formatter $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-ast-1.19.1/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.11.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-2.2.0/lib" -$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.32.0/lib" +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.33.0/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.14.3/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.15.2/lib" $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.12.1/lib" diff --git a/completions/bash/brew b/completions/bash/brew index ea9d874338..b9a439efb2 100644 --- a/completions/bash/brew +++ b/completions/bash/brew @@ -670,6 +670,25 @@ _brew_config() { esac } +_brew_contributions() { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${cur}" in + -*) + __brewcomp " + --debug + --from + --help + --quiet + --repositories + --to + --verbose + " + return + ;; + *) + esac +} + _brew_create() { local cur="${COMP_WORDS[COMP_CWORD]}" case "${cur}" in @@ -2480,6 +2499,7 @@ _brew() { commands) _brew_commands ;; completions) _brew_completions ;; config) _brew_config ;; + contributions) _brew_contributions ;; create) _brew_create ;; deps) _brew_deps ;; desc) _brew_desc ;; diff --git a/completions/fish/brew.fish b/completions/fish/brew.fish index b80f4b814b..762412032e 100644 --- a/completions/fish/brew.fish +++ b/completions/fish/brew.fish @@ -529,6 +529,16 @@ __fish_brew_complete_arg 'config' -l quiet -d 'Make some output more quiet' __fish_brew_complete_arg 'config' -l verbose -d 'Make some output more verbose' +__fish_brew_complete_cmd 'contributions' 'Contributions to Homebrew repos for a user' +__fish_brew_complete_arg 'contributions' -l debug -d 'Display any debugging information' +__fish_brew_complete_arg 'contributions' -l from -d 'Date (ISO-8601 format) to start searching contributions' +__fish_brew_complete_arg 'contributions' -l help -d 'Show this message' +__fish_brew_complete_arg 'contributions' -l quiet -d 'Make some output more quiet' +__fish_brew_complete_arg 'contributions' -l repositories -d 'Specify a comma-separated (no spaces) list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `autoupdate`, `bundle`, `command-not-found`, `test-bot`, `services`, `cask-drivers`, `cask-fonts` and `cask-versions`.Omitting this flag, or specifying `--repositories=all`, will search all repositories' +__fish_brew_complete_arg 'contributions' -l to -d 'Date (ISO-8601 format) to stop searching contributions' +__fish_brew_complete_arg 'contributions' -l verbose -d 'Make some output more verbose' + + __fish_brew_complete_cmd 'create' 'Generate a formula or, with `--cask`, a cask for the downloadable file at URL and open it in the editor' __fish_brew_complete_arg 'create' -l HEAD -d 'Indicate that URL points to the package\'s repository rather than a file' __fish_brew_complete_arg 'create' -l autotools -d 'Create a basic template for an Autotools-style build' diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index 09af017ba6..408b5635d0 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -26,6 +26,7 @@ command commands completions config +contributions create deps desc diff --git a/completions/zsh/_brew b/completions/zsh/_brew index 8290d9433f..208c74ccb7 100644 --- a/completions/zsh/_brew +++ b/completions/zsh/_brew @@ -152,6 +152,7 @@ __brew_internal_commands() { 'commands:Show lists of built-in and external commands' 'completions:Control whether Homebrew automatically links external tap shell completion files' 'config:Show Homebrew and system configuration info useful for debugging' + 'contributions:Contributions to Homebrew repos for a user' 'create:Generate a formula or, with `--cask`, a cask for the downloadable file at URL and open it in the editor' 'deps:Show dependencies for formula' 'desc:Display formula'\''s name and one-line description' @@ -652,6 +653,18 @@ _brew_config() { '--verbose[Make some output more verbose]' } +# brew contributions +_brew_contributions() { + _arguments \ + '--debug[Display any debugging information]' \ + '--from[Date (ISO-8601 format) to start searching contributions]' \ + '--help[Show this message]' \ + '--quiet[Make some output more quiet]' \ + '--repositories[Specify a comma-separated (no spaces) list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `autoupdate`, `bundle`, `command-not-found`, `test-bot`, `services`, `cask-drivers`, `cask-fonts` and `cask-versions`.Omitting this flag, or specifying `--repositories=all`, will search all repositories]' \ + '--to[Date (ISO-8601 format) to stop searching contributions]' \ + '--verbose[Make some output more verbose]' +} + # brew create _brew_create() { _arguments \ diff --git a/docs/Formula-Cookbook.md b/docs/Formula-Cookbook.md index 0355953b85..daf02b572e 100644 --- a/docs/Formula-Cookbook.md +++ b/docs/Formula-Cookbook.md @@ -544,6 +544,52 @@ Instead of `git diff | pbcopy`, for some editors `git diff >> path/to/your/formu If anything isn’t clear, you can usually figure it out by `grep`ping the `$(brew --repository homebrew/core)` directory. Please submit a pull request to amend this document if you think it will help! +### Handling different system configurations + +Often, formulae need different dependencies, resources, patches, conflicts, deprecations or `keg_only` statuses on different OSes and arches. In these cases, the components can be nested inside `on_macos`, `on_linux`, `on_arm` or `on_intel` blocks. For example, here's how to add `gcc` as a Linux-only dependency: + +```ruby +on_linux do + depends_on "gcc" +end +``` + +Components can also be declared for specific macOS versions or version ranges. For example, to declare a dependency only on High Sierra, nest the `depends_on` call inside an `on_high_sierra` block. Add an `:or_older` or `:or_newer` parameter to the `on_high_sierra` method to add the dependency to all macOS versions that meet the condition. For example, to add `gettext` as a build dependency on Mojave and all later macOS versions, use: + +```ruby +on_mojave :or_newer do + depends_on "gettext" => :build +end +``` + +Sometimes, a dependency is needed on certain macOS versions *and* on Linux. In these cases, a special `on_system` method can be used: + +```ruby +on_system :linux, macos: :sierra_or_older do + depends_on "gettext" => :build +end +``` + +To check multiple conditions, nest the corresponding blocks. For example, the following code adds a `gettext` build dependency when on ARM *and* macOS: + +```ruby +on_macos do + on_arm do + depends_on "gettext" => :build + end +end +``` + +#### Inside `def install` and `test do` + +Inside `def install` and `test do`, don't use these `on_*` methods. Instead, use `if` statements and the following conditionals: + +* `OS.mac?` and `OS.linux?` return `true` or `false` based on the OS +* `Hardware::CPU.intel?` and `Hardware::CPU.arm?` return `true` or `false` based on the arch +* `MacOS.version` returns the current macOS version. Use `==`, `<=` or `>=` to compare to symbols corresponding to macOS versions (e.g. `if MacOS.version >= :mojave`) + +See [`rust`](https://github.com/Homebrew/homebrew-core/blob/fe831237a7c24033a48f588a1578ba54f953f922/Formula/rust.rb#L72) for an example. + ### `livecheck` blocks When `brew livecheck` is unable to identify versions for a formula, we can control its behavior using a `livecheck` block. Here is a simple example to check a page for links containing a filename like `example-1.2.tar.gz`: diff --git a/docs/Manpage.md b/docs/Manpage.md index 57e1e3b4ef..c08de40f3c 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -1081,6 +1081,19 @@ Display the source of a *`formula`* or *`cask`*. Display the path to the file being used when invoking `brew` *`cmd`*. +### `contributions` *`email|name`* [*`--repositories`*`=`] + +Contributions to Homebrew repos for a user. + +The first argument is a name (e.g. "BrewTestBot") or an email address (e.g. "brewtestbot@brew.sh"). + +* `--repositories`: + Specify a comma-separated (no spaces) list of repositories to search. Supported repositories: `brew`, `core`, `cask`, `aliases`, `autoupdate`, `bundle`, `command-not-found`, `test-bot`, `services`, `cask-drivers`, `cask-fonts` and `cask-versions`.Omitting this flag, or specifying `--repositories=all`, will search all repositories. +* `--from`: + Date (ISO-8601 format) to start searching contributions. +* `--to`: + Date (ISO-8601 format) to stop searching contributions. + ### `create` [*`options`*] *`URL`* Generate a formula or, with `--cask`, a cask for the downloadable file at *`URL`* @@ -1947,6 +1960,9 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just *Default:* `300`. +- `HOMEBREW_AUTOREMOVE` +
If set, calls to `brew cleanup` and `brew uninstall` will automatically remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, `brew cleanup` will start running `brew autoremove` periodically. + - `HOMEBREW_BAT`
If set, use `bat` for the `brew cat` command. @@ -2127,7 +2143,7 @@ example, run `export HOMEBREW_NO_INSECURE_REDIRECT=1` rather than just
If set, do not check for broken linkage of dependents or outdated dependents after installing, upgrading or reinstalling formulae. This will result in fewer dependents (and their dependencies) being upgraded or reinstalled but may result in more breakage from running `brew install *`formula`*` or `brew upgrade *`formula`*`. - `HOMEBREW_NO_CLEANUP_FORMULAE` -
A comma-separated list of formulae. Homebrew will refuse to clean up a formula if it appears on this list. +
A comma-separated list of formulae. Homebrew will refuse to clean up or autoremove a formula if it appears on this list. - `HOMEBREW_NO_COLOR`
If set, do not print text with colour added. diff --git a/manpages/brew.1 b/manpages/brew.1 index 760aa819c6..ed2d1ef1ff 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "BREW" "1" "July 2022" "Homebrew" "brew" +.TH "BREW" "1" "August 2022" "Homebrew" "brew" . .SH "NAME" \fBbrew\fR \- The Missing Package Manager for macOS (or Linux) @@ -1539,6 +1539,24 @@ Treat all named arguments as casks\. .SS "\fBcommand\fR \fIcommand\fR [\.\.\.]" Display the path to the file being used when invoking \fBbrew\fR \fIcmd\fR\. . +.SS "\fBcontributions\fR \fIemail|name\fR [\fI\-\-repositories\fR\fB=\fR]" +Contributions to Homebrew repos for a user\. +. +.P +The first argument is a name (e\.g\. "BrewTestBot") or an email address (e\.g\. "brewtestbot@brew\.sh")\. +. +.TP +\fB\-\-repositories\fR +Specify a comma\-separated (no spaces) list of repositories to search\. Supported repositories: \fBbrew\fR, \fBcore\fR, \fBcask\fR, \fBaliases\fR, \fBautoupdate\fR, \fBbundle\fR, \fBcommand\-not\-found\fR, \fBtest\-bot\fR, \fBservices\fR, \fBcask\-drivers\fR, \fBcask\-fonts\fR and \fBcask\-versions\fR\.Omitting this flag, or specifying \fB\-\-repositories=all\fR, will search all repositories\. +. +.TP +\fB\-\-from\fR +Date (ISO\-8601 format) to start searching contributions\. +. +.TP +\fB\-\-to\fR +Date (ISO\-8601 format) to stop searching contributions\. +. .SS "\fBcreate\fR [\fIoptions\fR] \fIURL\fR" Generate a formula or, with \fB\-\-cask\fR, a cask for the downloadable file at \fIURL\fR and open it in the editor\. Homebrew will attempt to automatically derive the formula name and version, but if it fails, you\'ll have to make your own template\. The \fBwget\fR formula serves as a simple example\. For the complete API, see: \fIhttps://rubydoc\.brew\.sh/Formula\fR . @@ -2761,6 +2779,12 @@ Run \fBbrew update\fR once every \fBHOMEBREW_AUTO_UPDATE_SECS\fR seconds before \fIDefault:\fR \fB300\fR\. . .TP +\fBHOMEBREW_AUTOREMOVE\fR +. +.br +If set, calls to \fBbrew cleanup\fR and \fBbrew uninstall\fR will automatically remove unused formula dependents and if HOMEBREW_NO_INSTALL_CLEANUP is not set, \fBbrew cleanup\fR will start running \fBbrew autoremove\fR periodically\. +. +.TP \fBHOMEBREW_BAT\fR . .br @@ -3100,7 +3124,7 @@ If set, do not check for broken linkage of dependents or outdated dependents aft \fBHOMEBREW_NO_CLEANUP_FORMULAE\fR . .br -A comma\-separated list of formulae\. Homebrew will refuse to clean up a formula if it appears on this list\. +A comma\-separated list of formulae\. Homebrew will refuse to clean up or autoremove a formula if it appears on this list\. . .TP \fBHOMEBREW_NO_COLOR\fR