diff --git a/Library/Homebrew/dev-cmd/unbottled.rb b/Library/Homebrew/dev-cmd/unbottled.rb new file mode 100644 index 0000000000000000000000000000000000000000..f258db70024ce233109fb50bc8349fb1f2bfb7d4 --- /dev/null +++ b/Library/Homebrew/dev-cmd/unbottled.rb @@ -0,0 +1,166 @@ +# typed: true +# frozen_string_literal: true + +require "cli/parser" +require "formula" + +module Homebrew + extend T::Sig + + module_function + + sig { returns(CLI::Parser) } + def unbottled_args + Homebrew::CLI::Parser.new do + usage_banner <<~EOS + `unbottled` [<formula>] + + Outputs the unbottled dependents of formulae. + EOS + flag "--tag=", + description: "Use the specified bottle tag (e.g. big_sur) instead of the current OS." + switch "--dependents", + description: "Don't get analytics data and sort by number of dependents instead." + switch "--total", + description: "Output the number of unbottled and total formulae." + conflicts "--dependents", "--total" + end + end + + sig { void } + def unbottled + args = unbottled_args.parse + + Formulary.enable_factory_cache! + + @bottle_tag = if args.tag.present? + args.tag.to_sym + else + Utils::Bottles.tag + end + + if args.named.blank? + ohai "Getting formulae..." + elsif args.total? + raise UsageError, "cannot specify `<formula>` and `--total`." + end + + formulae, all_formulae, sort, formula_installs = + formulae_all_sort_installs_from_args(args) + deps_hash, uses_hash = deps_uses_from_formulae(all_formulae) + + if args.dependents? + formula_dependents = {} + formulae = formulae.sort_by do |f| + dependents = uses_hash[f.name]&.length || 0 + formula_dependents[f.name] ||= dependents + end.reverse + end + + if args.total? + output_total(formulae) + return + end + + noun, hash = if args.named.present? + [nil, {}] + elsif args.dependents? + ["dependents", formula_dependents] + else + ["installs", formula_installs] + end + output_unbottled(sort, formulae, deps_hash, noun, hash) + end + + def formulae_all_sort_installs_from_args(args) + if args.named.present? + formulae = all_formulae = args.named.to_formulae + elsif args.total? + formulae = all_formulae = Formula.to_a + elsif args.dependents? + formulae = all_formulae = Formula.to_a + + sort = " (sorted by installs in the last 90 days)" + else + formula_installs = {} + + ohai "Getting analytics data..." + analytics = Utils::Analytics.formulae_brew_sh_json("analytics/install/90d.json") + formulae = analytics["items"].map do |i| + f = i["formula"].split.first + next if f.include?("/") + next if formula_installs[f].present? + + formula_installs[f] = i["count"] + begin + Formula[f] + rescue FormulaUnavailableError + nil + end + end.compact + sort = " (sorted by installs in the last 90 days)" + + all_formulae = Formula + end + + [formulae, all_formulae, sort, formula_installs] + end + + def deps_uses_from_formulae(all_formulae) + ohai "Populating dependency tree..." + + deps_hash = {} + uses_hash = {} + + all_formulae.each do |f| + next unless f.core_formula? + + deps = f.recursive_dependencies do |_, dep| + Dependency.prune if dep.optional? + end.map(&:to_formula) + deps_hash[f.name] = deps + + deps.each do |dep| + uses_hash[dep.name] ||= [] + uses_hash[dep.name] << f + end + end + + [deps_hash, uses_hash] + end + + def output_total(formulae) + ohai "Unbottled :#{@bottle_tag} formulae" + unbottled_formulae = 0 + + formulae.each do |f| + next if f.bottle_specification.tag?(@bottle_tag) + next if f.bottle_unneeded? + + unbottled_formulae += 1 + end + + puts "#{unbottled_formulae}/#{formulae.length} remaining." + end + + def output_unbottled(sort, formulae, deps_hash, noun, hash) + ohai "Unbottled :#{@bottle_tag} dependencies#{sort}" + any_found = T.let(false, T::Boolean) + + formulae.each do |f| + next if f.bottle_specification.tag?(@bottle_tag) + + deps = Array(deps_hash[f.name]).reject do |dep| + dep.bottle_specification.tag?(@bottle_tag) || dep.bottle_unneeded? + end + next if deps.blank? + + any_found ||= true + count = " (#{hash[f.name]} #{noun})" if noun + puts "#{f.name}#{count}: #{deps.join(" ")}" + end + return if any_found + + puts "No unbottled dependencies found!" + end +end diff --git a/Library/Homebrew/test/dev-cmd/unbottled_spec.rb b/Library/Homebrew/test/dev-cmd/unbottled_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6e54f948b3edebd57f26dc58438db6ba50a786ce --- /dev/null +++ b/Library/Homebrew/test/dev-cmd/unbottled_spec.rb @@ -0,0 +1,8 @@ +# typed: false +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" + +describe "Homebrew.unbottled_args" do + it_behaves_like "parseable arguments" +end diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt index 13cf54e93a95e671672477f10e5126c8c1f8a712..f2c3de6ba2005a92d447397ec1568fe67e70ff4f 100644 --- a/completions/internal_commands_list.txt +++ b/completions/internal_commands_list.txt @@ -87,6 +87,7 @@ tc test tests typecheck +unbottled uninstal uninstall unlink diff --git a/docs/Manpage.md b/docs/Manpage.md index 6a9136cc8209800cb7bfaca0345d127e30a36c9d..5af239c2b78f156a3f240fa06453a09acb25c9c8 100644 --- a/docs/Manpage.md +++ b/docs/Manpage.md @@ -1278,6 +1278,17 @@ Check for typechecking errors using Sorbet. * `--ignore`: Ignores input files that contain the given string in their paths (relative to the input path passed to Sorbet). +### `unbottled` [*`formula`*] + +Outputs the unbottled dependents of formulae. + +* `--tag`: + Use the specified bottle tag (e.g. big_sur) instead of the current OS. +* `--dependents`: + Don't get analytics data and sort by number of dependents instead. +* `--total`: + Output the number of unbottled and total formulae. + ### `unpack` [*`options`*] *`formula`* Unpack the source files for *`formula`* into subdirectories of the current diff --git a/manpages/brew.1 b/manpages/brew.1 index e0e0cb953f5525a2829a55ca6cf78f72eecf6525..5cdaf4721bf125de361ec176dc128c7b1038f9ad 100644 --- a/manpages/brew.1 +++ b/manpages/brew.1 @@ -1768,6 +1768,21 @@ Typecheck a single file\. \fB\-\-ignore\fR Ignores input files that contain the given string in their paths (relative to the input path passed to Sorbet)\. . +.SS "\fBunbottled\fR [\fIformula\fR]" +Outputs the unbottled dependents of formulae\. +. +.TP +\fB\-\-tag\fR +Use the specified bottle tag (e\.g\. big_sur) instead of the current OS\. +. +.TP +\fB\-\-dependents\fR +Don\'t get analytics data and sort by number of dependents instead\. +. +.TP +\fB\-\-total\fR +Output the number of unbottled and total formulae\. +. .SS "\fBunpack\fR [\fIoptions\fR] \fIformula\fR" Unpack the source files for \fIformula\fR into subdirectories of the current working directory\. .