unbottled.rb 4.32 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 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")
89
90
91
92
93
94
95

      if analytics.blank?
        raise UsageError,
              "default sort by analytics data requires " \
              "`HOMEBREW_NO_GITHUB_API` and `HOMEBREW_NO_ANALYTICS` to be unset"
      end

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
      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