Class: Reporter Private

Inherits:
Object show all
Includes:
Utils::Output::Mixin
Defined in:
cmd/update_report/reporter.rb

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Defined Under Namespace

Classes: ReporterRevisionUnsetError

Constant Summary collapse

Report =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.type_alias do
  {
    A:  T::Array[String],
    AC: T::Array[String],
    D:  T::Array[String],
    DC: T::Array[String],
    M:  T::Array[String],
    MC: T::Array[String],
    R:  T::Array[[String, String]],
    RC: T::Array[[String, String]],
    T:  T::Array[String],
  }
end

Instance Method Summary collapse

Methods included from Utils::Output::Mixin

#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_outdated, #pretty_uninstalled, #pretty_upgradable

Constructor Details

#initialize(tap, api_names_txt: nil, api_names_before_txt: nil, api_dir_prefix: nil) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • tap (Tap)
  • api_names_txt (Pathname, nil) (defaults to: nil)
  • api_names_before_txt (Pathname, nil) (defaults to: nil)
  • api_dir_prefix (Pathname, nil) (defaults to: nil)


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'cmd/update_report/reporter.rb', line 32

def initialize(tap, api_names_txt: nil, api_names_before_txt: nil, api_dir_prefix: nil)
  @tap = tap

  # This is slightly involved/weird but all the #report logic is shared so it's worth it.
  if installed_from_api?(api_names_txt, api_names_before_txt, api_dir_prefix)
    @api_names_txt = T.let(api_names_txt, T.nilable(Pathname))
    @api_names_before_txt = T.let(api_names_before_txt, T.nilable(Pathname))
    @api_dir_prefix = T.let(api_dir_prefix, T.nilable(Pathname))
  else
    initial_revision_var = "HOMEBREW_UPDATE_BEFORE#{tap.repository_var_suffix}"
    @initial_revision = T.let(ENV[initial_revision_var].to_s, String)
    raise ReporterRevisionUnsetError, initial_revision_var if @initial_revision.empty?

    current_revision_var = "HOMEBREW_UPDATE_AFTER#{tap.repository_var_suffix}"
    @current_revision = T.let(ENV[current_revision_var].to_s, String)
    raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty?
  end

  @report = T.let(nil, T.nilable(Report))
end

Instance Method Details

#migrate_cask_renamevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



309
310
311
312
313
# File 'cmd/update_report/reporter.rb', line 309

def migrate_cask_rename
  Cask::Caskroom.casks.each do |cask|
    Cask::Migrator.migrate_if_needed(cask)
  end
end

#migrate_formula_rename(force:, verbose:) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • force (Boolean)
  • verbose (Boolean)


316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'cmd/update_report/reporter.rb', line 316

def migrate_formula_rename(force:, verbose:)
  Formula.installed.each do |formula|
    next unless Migrator.needs_migration?(formula)

    oldnames_to_migrate = formula.oldnames.select do |oldname|
      oldname_rack = HOMEBREW_CELLAR/oldname
      next false unless oldname_rack.exist?

      if oldname_rack.subdirs.empty?
        oldname_rack.rmdir_if_possible
        next false
      end

      true
    end
    next if oldnames_to_migrate.empty?

    Migrator.migrate_if_needed(formula, force:)
  end
end

#migrate_tap_migrationvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'cmd/update_report/reporter.rb', line 218

def migrate_tap_migration
  [report[:D], report[:DC], report[:T]].flatten.each do |full_name|
    name = Utils.name_from_full_name(full_name)
    migration_target = tap.tap_migrations[name]
    next if migration_target.nil? # skip if not in tap_migrations list.

    migrated_tap_name = Utils.tap_from_full_name(migration_target)
    new_name = if migrated_tap_name
      new_full_name = Utils.name_from_full_name(migration_target)
      new_tap_name = migrated_tap_name
      new_full_name
    elsif migration_target.include?("/")
      new_tap_name = migration_target
      new_full_name = "#{new_tap_name}/#{name}"
      name
    else
      new_tap_name = tap.name
      new_full_name = "#{new_tap_name}/#{migration_target}"
      migration_target
    end

    # This means it is a cask
    if Array(report[:DC]).include? full_name
      next unless (HOMEBREW_PREFIX/"Caskroom"/name).exist?

      new_tap = Tap.fetch(new_tap_name)
      new_tap.ensure_installed!
      ohai "#{name} has been moved to Homebrew.", <<~EOS
        To uninstall the cask, run:
          brew uninstall --cask --force #{name}
      EOS
      next if (HOMEBREW_CELLAR/Utils.name_from_full_name(new_name)).directory?

      ohai "Installing #{new_name}..."
      begin
        system HOMEBREW_BREW_FILE, "install", "--overwrite", new_full_name
      # Rescue any possible exception types.
      rescue Exception => e # rubocop:disable Lint/RescueException
        if Homebrew::EnvConfig.developer?
          require "utils/backtrace"
          onoe "#{e.message}\n#{Utils::Backtrace.clean(e)&.join("\n")}"
        end
      end
      next
    end

    next unless (dir = HOMEBREW_CELLAR/name).exist? # skip if formula is not installed.

    tabs = dir.subdirs.map { |d| Keg.new(d).tab }
    next if tabs.first.tap != tap # skip if installed formula is not from this tap.

    new_tap = Tap.fetch(new_tap_name)
    # For formulae migrated to cask: Auto-install cask or provide install instructions.
    # Check if the migration target is a cask (either in homebrew/cask or any other tap)
    if new_tap.core_cask_tap? || new_tap.cask_tokens.intersect?([new_full_name, new_name])
      migration_message = if new_tap == tap
        "#{full_name} has been migrated from a formula to a cask."
      else
        "#{name} has been moved to #{new_tap_name}."
      end
      if new_tap.installed? && (HOMEBREW_PREFIX/"Caskroom").directory?
        ohai migration_message
        ohai "brew unlink #{name}"
        system HOMEBREW_BREW_FILE, "unlink", name
        ohai "brew cleanup"
        system HOMEBREW_BREW_FILE, "cleanup"
        ohai "brew install --cask #{new_full_name}"
        system HOMEBREW_BREW_FILE, "install", "--cask", new_full_name
        ohai migration_message, <<~EOS
          The existing keg has been unlinked.
          Please uninstall the formula when convenient by running:
            brew uninstall --formula --force #{name}
        EOS
      else
        ohai migration_message, <<~EOS
          To uninstall the formula and install the cask, run:
            brew uninstall --formula --force #{name}
            brew tap #{new_tap_name}
            brew install --cask #{new_full_name}
        EOS
      end
    else
      new_tap.ensure_installed!
      # update tap for each Tab
      tabs.each { |tab| tab.tap = new_tap }
      tabs.each(&:write)
    end
  end
end

#report(auto_update: false) ⇒ Report

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • auto_update (Boolean) (defaults to: false)

Returns:



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
89
90
91
92
93
94
95
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'cmd/update_report/reporter.rb', line 54

def report(auto_update: false)
  return @report if @report

  @report = {
    A: [], AC: [], D: [], DC: [], M: [], MC: [], R: T.let([], T::Array[[String, String]]),
    RC: T.let([], T::Array[[String, String]]), T: []
  }
  return @report unless updated?

  diff.each_line do |line|
    status, *paths = line.split
    src = Pathname.new paths.first
    dst = Pathname.new paths.last

    next if dst.extname != ".rb"

    if paths.any? { |p| tap.cask_file?(p) }
      case status
      when "A"
        # Have a dedicated report array for new casks.
        @report[:AC] << tap.formula_file_to_name(src)
      when "D"
        # Have a dedicated report array for deleted casks.
        @report[:DC] << tap.formula_file_to_name(src)
      when "M"
        # Report updated casks
        @report[:MC] << tap.formula_file_to_name(src)
      when /^R\d{0,3}/
        src_full_name = tap.formula_file_to_name(src)
        dst_full_name = tap.formula_file_to_name(dst)
        # Don't report formulae that are moved within a tap but not renamed
        next if src_full_name == dst_full_name

        @report[:DC] << src_full_name
        @report[:AC] << dst_full_name
      end
    end

    next unless paths.any? do |p|
      tap.formula_file?(p) ||
      # Need to check for case where Formula directory was deleted
      (status == "D" && File.fnmatch?("{Homebrew,}Formula/**/*.rb", p, File::FNM_EXTGLOB | File::FNM_PATHNAME))
    end

    case status
    when "A", "D"
      full_name = tap.formula_file_to_name(src)
      name = Utils.name_from_full_name(full_name)
      new_tap = tap.tap_migrations[name]
      if new_tap.blank?
        @report[T.must(status).to_sym] << full_name
      elsif status == "D"
        # Retain deleted formulae for tap migrations separately to avoid reporting as deleted
        @report[:T] << full_name
      end
    when "M"
      name = tap.formula_file_to_name(src)

      @report[:M] << name
    when /^R\d{0,3}/
      src_full_name = tap.formula_file_to_name(src)
      dst_full_name = tap.formula_file_to_name(dst)
      # Don't report formulae that are moved within a tap but not renamed
      next if src_full_name == dst_full_name

      @report[:D] << src_full_name
      @report[:A] << dst_full_name
    end
  end

  renamed_casks = Set.new
  @report[:DC].each do |old_full_name|
    old_name = Utils.name_from_full_name(old_full_name)
    new_name = tap.cask_renames[old_name]
    next unless new_name

    new_full_name = if tap.core_cask_tap?
      new_name
    else
      "#{tap}/#{new_name}"
    end

    renamed_casks << [old_full_name, new_full_name] if @report[:AC].include?(new_full_name)
  end

  @report[:AC].each do |new_full_name|
    new_name = Utils.name_from_full_name(new_full_name)
    old_name = tap.cask_renames.key(new_name)
    next unless old_name

    old_full_name = if tap.core_cask_tap?
      old_name
    else
      "#{tap}/#{old_name}"
    end

    renamed_casks << [old_full_name, new_full_name]
  end

  if renamed_casks.any?
    @report[:AC] -= renamed_casks.map(&:last)
    @report[:DC] -= renamed_casks.map(&:first)
    @report[:RC] = renamed_casks.to_a
  end

  renamed_formulae = Set.new
  @report[:D].each do |old_full_name|
    old_name = Utils.name_from_full_name(old_full_name)
    new_name = tap.formula_renames[old_name]
    next unless new_name

    new_full_name = if tap.core_tap?
      new_name
    else
      "#{tap}/#{new_name}"
    end

    renamed_formulae << [old_full_name, new_full_name] if @report[:A].include? new_full_name
  end

  @report[:A].each do |new_full_name|
    new_name = Utils.name_from_full_name(new_full_name)
    old_name = tap.formula_renames.key(new_name)
    next unless old_name

    old_full_name = if tap.core_tap?
      old_name
    else
      "#{tap}/#{old_name}"
    end

    renamed_formulae << [old_full_name, new_full_name]
  end

  if renamed_formulae.any?
    @report[:A] -= renamed_formulae.map(&:last)
    @report[:D] -= renamed_formulae.map(&:first)
    @report[:R] = renamed_formulae.to_a
  end

  # If any formulae/casks are marked as added and deleted, remove them from
  # the report as we've not detected things correctly.
  if (added_and_deleted_formulae = (@report[:A] & @report[:D]).presence)
    @report[:A] -= added_and_deleted_formulae
    @report[:D] -= added_and_deleted_formulae
  end
  if (added_and_deleted_casks = (@report[:AC] & @report[:DC]).presence)
    @report[:AC] -= added_and_deleted_casks
    @report[:DC] -= added_and_deleted_casks
  end

  @report
end

#updated?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


209
210
211
212
213
214
215
# File 'cmd/update_report/reporter.rb', line 209

def updated?
  if installed_from_api?
    diff.present?
  else
    initial_revision != current_revision
  end
end