From 536ae08a44a8a78662be17628e59fe30dcf5d85d Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Sun, 25 Feb 2024 15:14:22 -0800 Subject: [PATCH 1/3] cachable: make sure to clear caches between tests This adds a registry for all modules and classes that cachable is included in. The registry allows us to programmatically clear all caches in between tests so that we don't forget to do that when adding a new class or refactoring code. The goal here is to reduce the number of flaky tests in the future. --- Library/Homebrew/api/cask.rb | 4 +-- Library/Homebrew/api/formula.rb | 4 +-- Library/Homebrew/extend/cachable.rb | 41 +++++++++++++++++++++++++++- Library/Homebrew/readall.rb | 3 +- Library/Homebrew/test/spec_helper.rb | 21 ++------------ 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/Library/Homebrew/api/cask.rb b/Library/Homebrew/api/cask.rb index 17ca86b716..8d0274cda8 100644 --- a/Library/Homebrew/api/cask.rb +++ b/Library/Homebrew/api/cask.rb @@ -10,9 +10,9 @@ module Homebrew # # @api private module Cask - class << self - include Cachable + extend Cachable + class << self private :cache sig { params(token: String).returns(Hash) } diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index 8704ec6ad4..ba5a1de330 100644 --- a/Library/Homebrew/api/formula.rb +++ b/Library/Homebrew/api/formula.rb @@ -10,9 +10,9 @@ module Homebrew # # @api private module Formula - class << self - include Cachable + extend Cachable + class << self private :cache sig { params(name: String).returns(Hash) } diff --git a/Library/Homebrew/extend/cachable.rb b/Library/Homebrew/extend/cachable.rb index b124c638e6..9c65e1bc36 100644 --- a/Library/Homebrew/extend/cachable.rb +++ b/Library/Homebrew/extend/cachable.rb @@ -1,4 +1,4 @@ -# typed: strict +# typed: true # frozen_string_literal: true module Cachable @@ -11,4 +11,43 @@ module Cachable def clear_cache cache.clear end + + # Collect all classes that mix in Cachable so that those caches can be cleared in-between tests. + if ENV["HOMEBREW_TESTS"] + def self.included(klass) + raise ArgumentError, "Don't use Cachable with singleton classes" if klass.singleton_class? + + super if defined?(super) + end + + # Ignore classes that get inherited from a lot and that have + # caches that we don't need to clear on the class level. + IGNORE_INHERITED_CLASSES = %w[Formula Cask].freeze + private_constant :IGNORE_INHERITED_CLASSES + + def self.extended(klass) + Registry.list << klass + klass.extend(Inherited) unless IGNORE_INHERITED_CLASSES.include?(klass.name) + super if defined?(super) + end + + module Inherited + def inherited(klass) + # A class might inherit Cachable at the instance level + # and in that case we just want to skip registering it. + Registry.list << klass if klass.respond_to?(:clear_cache) + super if defined?(super) + end + end + + module Registry + def self.list + @list ||= [] + end + + def self.clear_all_caches + list.each(&:clear_cache) + end + end + end end diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index 91407529aa..778b10ca65 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -9,8 +9,9 @@ require "system_command" # # @api private module Readall + extend Cachable + class << self - include Cachable include SystemCommand::Mixin # TODO: remove this once the `MacOS` module is undefined on Linux diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 9d2f2d10ee..f9ed707426 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -202,16 +202,7 @@ RSpec.configure do |config| config.around do |example| Homebrew.raise_deprecation_exceptions = true - Formulary.clear_cache - Tap.each(&:clear_cache) - Tap.clear_cache - DependencyCollector.clear_cache - Formula.clear_cache - Keg.clear_cache - Tab.clear_cache - Dependency.clear_cache - Requirement.clear_cache - Readall.clear_cache if defined?(Readall) + Cachable::Registry.clear_all_caches FormulaInstaller.clear_attempted FormulaInstaller.clear_installed FormulaInstaller.clear_fetched @@ -263,15 +254,7 @@ RSpec.configure do |config| @__stderr.close @__stdin.close - Formulary.clear_cache - Tap.clear_cache - DependencyCollector.clear_cache - Formula.clear_cache - Keg.clear_cache - Tab.clear_cache - Dependency.clear_cache - Requirement.clear_cache - Readall.clear_cache if defined?(Readall) + Cachable::Registry.clear_all_caches FileUtils.rm_rf [ *TEST_DIRECTORIES, From 5cc1c85a5fad4c4f69d313b7f89c37fb30523e05 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Mon, 26 Feb 2024 20:26:06 -0800 Subject: [PATCH 2/3] cachable: Move registry to test directory Now we only include the cachable registry when running tests. We basically just load it first and add a bunch of methods to it before loading the rest of the formula files when we require global.rb. I added a check to make sure this require order is preserved. I also made a bunch of methods private, stop excluding classes that inherit from casks since it's unnecessary and add more docs. --- Library/Homebrew/extend/cachable.rb | 41 +------------- Library/Homebrew/test/spec_helper.rb | 2 + .../Homebrew/test/support/extend/cachable.rb | 53 +++++++++++++++++++ 3 files changed, 56 insertions(+), 40 deletions(-) create mode 100644 Library/Homebrew/test/support/extend/cachable.rb diff --git a/Library/Homebrew/extend/cachable.rb b/Library/Homebrew/extend/cachable.rb index 9c65e1bc36..b124c638e6 100644 --- a/Library/Homebrew/extend/cachable.rb +++ b/Library/Homebrew/extend/cachable.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true module Cachable @@ -11,43 +11,4 @@ module Cachable def clear_cache cache.clear end - - # Collect all classes that mix in Cachable so that those caches can be cleared in-between tests. - if ENV["HOMEBREW_TESTS"] - def self.included(klass) - raise ArgumentError, "Don't use Cachable with singleton classes" if klass.singleton_class? - - super if defined?(super) - end - - # Ignore classes that get inherited from a lot and that have - # caches that we don't need to clear on the class level. - IGNORE_INHERITED_CLASSES = %w[Formula Cask].freeze - private_constant :IGNORE_INHERITED_CLASSES - - def self.extended(klass) - Registry.list << klass - klass.extend(Inherited) unless IGNORE_INHERITED_CLASSES.include?(klass.name) - super if defined?(super) - end - - module Inherited - def inherited(klass) - # A class might inherit Cachable at the instance level - # and in that case we just want to skip registering it. - Registry.list << klass if klass.respond_to?(:clear_cache) - super if defined?(super) - end - end - - module Registry - def self.list - @list ||= [] - end - - def self.clear_all_caches - list.each(&:clear_cache) - end - end - end end diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index f9ed707426..2c09cbbd21 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -35,6 +35,8 @@ require "timeout" $LOAD_PATH.unshift(File.expand_path("#{ENV.fetch("HOMEBREW_LIBRARY")}/Homebrew/test/support/lib")) +require_relative "support/extend/cachable" + require_relative "../global" require "test/support/quiet_progress_formatter" diff --git a/Library/Homebrew/test/support/extend/cachable.rb b/Library/Homebrew/test/support/extend/cachable.rb new file mode 100644 index 0000000000..764314674d --- /dev/null +++ b/Library/Homebrew/test/support/extend/cachable.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +raise "This needs to be required before Cachable gets loaded normally." if defined?(Cachable) + +# Collect all classes that mix in Cachable so that those caches can be cleared in-between tests. +module Cachable + private_class_method def self.included(klass) + # It's difficult to backtrack from a singleton class to find the original class + # and you can always just extend this module instead for equivalent behavior. + raise ArgumentError, "Don't use Cachable with singleton classes" if klass.singleton_class? + + super if defined?(super) + end + + private_class_method def self.extended(klass) + Registry.class_list << klass + # Ignore the `Formula` class that gets inherited from a lot and + # that has caches that we don't need to clear on the class level. + klass.extend(Inherited) if klass.name != "Formula" + super if defined?(super) + end + + module Inherited + private + + def inherited(klass) + # A class might inherit Cachable at the instance level + # and in that case we just want to skip registering it. + Registry.class_list << klass if klass.respond_to?(:clear_cache) + super if defined?(super) + end + end + + module Registry + # A list of all classes that have been loaded into memory that mixin or + # inherit `Cachable` at the class or module level. + # + # Note: Classes that inherit from `Formula` are excluded since it's not + # necessary to track and clear individual formula caches. + def self.class_list + @class_list ||= [] + end + + # Clear the cache of every class or module that mixes in or inherits + # `Cachable` at the class or module level. + # + # Note: Classes that inherit from `Formula` are excluded since it's not + # necessary to track and clear individual formula caches. + def self.clear_all_caches + class_list.each(&:clear_cache) + end + end +end From bea2dc65fec2ef41c90918a541fef6deb3e15142 Mon Sep 17 00:00:00 2001 From: apainintheneck Date: Mon, 26 Feb 2024 21:21:38 -0800 Subject: [PATCH 3/3] Clean up files that use to include cachable These were changed to extend to make it easier to determine where the classes come to in the extended callback but that means that the file is somewhat inconsistent. On the one hand we're using class methods and on the other we're extend self. This cleans that up but now the diff is atrocious and the blame is even worse. Oh well... --- Library/Homebrew/api/cask.rb | 116 +++++++++--------- Library/Homebrew/api/formula.rb | 138 +++++++++++----------- Library/Homebrew/readall.rb | 202 ++++++++++++++++---------------- 3 files changed, 224 insertions(+), 232 deletions(-) diff --git a/Library/Homebrew/api/cask.rb b/Library/Homebrew/api/cask.rb index 8d0274cda8..8e06edc840 100644 --- a/Library/Homebrew/api/cask.rb +++ b/Library/Homebrew/api/cask.rb @@ -12,80 +12,78 @@ module Homebrew module Cask extend Cachable - class << self - private :cache + private_class_method :cache - sig { params(token: String).returns(Hash) } - def fetch(token) - Homebrew::API.fetch "cask/#{token}.json" - end + sig { params(token: String).returns(Hash) } + def self.fetch(token) + Homebrew::API.fetch "cask/#{token}.json" + end - sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) } - def source_download(cask) - path = cask.ruby_source_path.to_s || "Casks/#{cask.token}.rb" - sha256 = cask.ruby_source_checksum[:sha256] - checksum = Checksum.new(sha256) if sha256 - git_head = cask.tap_git_head || "HEAD" - tap = cask.tap&.full_name || "Homebrew/homebrew-cask" + sig { params(cask: ::Cask::Cask).returns(::Cask::Cask) } + def self.source_download(cask) + path = cask.ruby_source_path.to_s || "Casks/#{cask.token}.rb" + sha256 = cask.ruby_source_checksum[:sha256] + checksum = Checksum.new(sha256) if sha256 + git_head = cask.tap_git_head || "HEAD" + tap = cask.tap&.full_name || "Homebrew/homebrew-cask" - download = Homebrew::API::Download.new( - "https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}", - checksum, - mirrors: [ - "#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{File.basename(path)}", - ], - cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Cask", - ) - download.fetch - ::Cask::CaskLoader::FromPathLoader.new(download.symlink_location) - .load(config: cask.config) - end + download = Homebrew::API::Download.new( + "https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}", + checksum, + mirrors: [ + "#{HOMEBREW_API_DEFAULT_DOMAIN}/cask-source/#{File.basename(path)}", + ], + cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Cask", + ) + download.fetch + ::Cask::CaskLoader::FromPathLoader.new(download.symlink_location) + .load(config: cask.config) + end - sig { returns(T::Boolean) } - def download_and_cache_data! - json_casks, updated = Homebrew::API.fetch_json_api_file "cask.jws.json" + sig { returns(T::Boolean) } + def self.download_and_cache_data! + json_casks, updated = Homebrew::API.fetch_json_api_file "cask.jws.json" - cache["renames"] = {} - cache["casks"] = json_casks.to_h do |json_cask| - token = json_cask["token"] + cache["renames"] = {} + cache["casks"] = json_casks.to_h do |json_cask| + token = json_cask["token"] - json_cask.fetch("old_tokens", []).each do |old_token| - cache["renames"][old_token] = token - end - - [token, json_cask.except("token")] + json_cask.fetch("old_tokens", []).each do |old_token| + cache["renames"][old_token] = token end - updated - end - private :download_and_cache_data! - - sig { returns(T::Hash[String, Hash]) } - def all_casks - unless cache.key?("casks") - json_updated = download_and_cache_data! - write_names(regenerate: json_updated) - end - - cache.fetch("casks") + [token, json_cask.except("token")] end - sig { returns(T::Hash[String, String]) } - def all_renames - unless cache.key?("renames") - json_updated = download_and_cache_data! - write_names(regenerate: json_updated) - end + updated + end + private_class_method :download_and_cache_data! - cache.fetch("renames") + sig { returns(T::Hash[String, Hash]) } + def self.all_casks + unless cache.key?("casks") + json_updated = download_and_cache_data! + write_names(regenerate: json_updated) end - sig { params(regenerate: T::Boolean).void } - def write_names(regenerate: false) - download_and_cache_data! unless cache.key?("casks") + cache.fetch("casks") + end - Homebrew::API.write_names_file(all_casks.keys, "cask", regenerate: regenerate) + sig { returns(T::Hash[String, String]) } + def self.all_renames + unless cache.key?("renames") + json_updated = download_and_cache_data! + write_names(regenerate: json_updated) end + + cache.fetch("renames") + end + + sig { params(regenerate: T::Boolean).void } + def self.write_names(regenerate: false) + download_and_cache_data! unless cache.key?("casks") + + Homebrew::API.write_names_file(all_casks.keys, "cask", regenerate: regenerate) end end end diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index ba5a1de330..e9747c1ba4 100644 --- a/Library/Homebrew/api/formula.rb +++ b/Library/Homebrew/api/formula.rb @@ -12,93 +12,91 @@ module Homebrew module Formula extend Cachable - class << self - private :cache + private_class_method :cache - sig { params(name: String).returns(Hash) } - def fetch(name) - Homebrew::API.fetch "formula/#{name}.json" - end + sig { params(name: String).returns(Hash) } + def self.fetch(name) + Homebrew::API.fetch "formula/#{name}.json" + end - sig { params(formula: ::Formula).returns(::Formula) } - def source_download(formula) - path = formula.ruby_source_path || "Formula/#{formula.name}.rb" - git_head = formula.tap_git_head || "HEAD" - tap = formula.tap&.full_name || "Homebrew/homebrew-core" + sig { params(formula: ::Formula).returns(::Formula) } + def self.source_download(formula) + path = formula.ruby_source_path || "Formula/#{formula.name}.rb" + git_head = formula.tap_git_head || "HEAD" + tap = formula.tap&.full_name || "Homebrew/homebrew-core" - download = Homebrew::API::Download.new( - "https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}", - formula.ruby_source_checksum, - cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Formula", - ) - download.fetch - Formulary.factory(download.symlink_location, - formula.active_spec_sym, - alias_path: formula.alias_path, - flags: formula.class.build_flags) - end + download = Homebrew::API::Download.new( + "https://raw.githubusercontent.com/#{tap}/#{git_head}/#{path}", + formula.ruby_source_checksum, + cache: HOMEBREW_CACHE_API_SOURCE/"#{tap}/#{git_head}/Formula", + ) + download.fetch + Formulary.factory(download.symlink_location, + formula.active_spec_sym, + alias_path: formula.alias_path, + flags: formula.class.build_flags) + end - sig { returns(T::Boolean) } - def download_and_cache_data! - json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.jws.json" + sig { returns(T::Boolean) } + def self.download_and_cache_data! + json_formulae, updated = Homebrew::API.fetch_json_api_file "formula.jws.json" - cache["aliases"] = {} - cache["renames"] = {} - cache["formulae"] = json_formulae.to_h do |json_formula| - json_formula["aliases"].each do |alias_name| - cache["aliases"][alias_name] = json_formula["name"] - end - (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| - cache["renames"][oldname] = json_formula["name"] - end - - [json_formula["name"], json_formula.except("name")] + cache["aliases"] = {} + cache["renames"] = {} + cache["formulae"] = json_formulae.to_h do |json_formula| + json_formula["aliases"].each do |alias_name| + cache["aliases"][alias_name] = json_formula["name"] + end + (json_formula["oldnames"] || [json_formula["oldname"]].compact).each do |oldname| + cache["renames"][oldname] = json_formula["name"] end - updated - end - private :download_and_cache_data! - - sig { returns(T::Hash[String, Hash]) } - def all_formulae - unless cache.key?("formulae") - json_updated = download_and_cache_data! - write_names_and_aliases(regenerate: json_updated) - end - - cache["formulae"] + [json_formula["name"], json_formula.except("name")] end - sig { returns(T::Hash[String, String]) } - def all_aliases - unless cache.key?("aliases") - json_updated = download_and_cache_data! - write_names_and_aliases(regenerate: json_updated) - end + updated + end + private_class_method :download_and_cache_data! - cache["aliases"] + sig { returns(T::Hash[String, Hash]) } + def self.all_formulae + unless cache.key?("formulae") + json_updated = download_and_cache_data! + write_names_and_aliases(regenerate: json_updated) end - sig { returns(T::Hash[String, String]) } - def all_renames - unless cache.key?("renames") - json_updated = download_and_cache_data! - write_names_and_aliases(regenerate: json_updated) - end + cache["formulae"] + end - cache["renames"] + sig { returns(T::Hash[String, String]) } + def self.all_aliases + unless cache.key?("aliases") + json_updated = download_and_cache_data! + write_names_and_aliases(regenerate: json_updated) end - sig { params(regenerate: T::Boolean).void } - def write_names_and_aliases(regenerate: false) - download_and_cache_data! unless cache.key?("formulae") + cache["aliases"] + end - return unless Homebrew::API.write_names_file(all_formulae.keys, "formula", regenerate: regenerate) + sig { returns(T::Hash[String, String]) } + def self.all_renames + unless cache.key?("renames") + json_updated = download_and_cache_data! + write_names_and_aliases(regenerate: json_updated) + end - (HOMEBREW_CACHE_API/"formula_aliases.txt").open("w") do |file| - all_aliases.each do |alias_name, real_name| - file.puts "#{alias_name}|#{real_name}" - end + cache["renames"] + end + + sig { params(regenerate: T::Boolean).void } + def self.write_names_and_aliases(regenerate: false) + download_and_cache_data! unless cache.key?("formulae") + + return unless Homebrew::API.write_names_file(all_formulae.keys, "formula", regenerate: regenerate) + + (HOMEBREW_CACHE_API/"formula_aliases.txt").open("w") do |file| + all_aliases.each do |alias_name, real_name| + file.puts "#{alias_name}|#{real_name}" end end end diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index 778b10ca65..ab1d24f508 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -10,127 +10,123 @@ require "system_command" # @api private module Readall extend Cachable + extend SystemCommand::Mixin - class << self - include SystemCommand::Mixin + # TODO: remove this once the `MacOS` module is undefined on Linux + MACOS_MODULE_REGEX = /\b(MacOS|OS::Mac)(\.|::)\b/ + private_constant :MACOS_MODULE_REGEX - # TODO: remove this once the `MacOS` module is undefined on Linux - MACOS_MODULE_REGEX = /\b(MacOS|OS::Mac)(\.|::)\b/ - private_constant :MACOS_MODULE_REGEX + private_class_method :cache - private :cache - - def valid_ruby_syntax?(ruby_files) - failed = T.let(false, T::Boolean) - ruby_files.each do |ruby_file| - # As a side effect, print syntax errors/warnings to `$stderr`. - failed = true if syntax_errors_or_warnings?(ruby_file) - end - !failed + def self.valid_ruby_syntax?(ruby_files) + failed = T.let(false, T::Boolean) + ruby_files.each do |ruby_file| + # As a side effect, print syntax errors/warnings to `$stderr`. + failed = true if syntax_errors_or_warnings?(ruby_file) end + !failed + end - def valid_aliases?(alias_dir, formula_dir) - return true unless alias_dir.directory? + def self.valid_aliases?(alias_dir, formula_dir) + return true unless alias_dir.directory? - failed = T.let(false, T::Boolean) - alias_dir.each_child do |f| - if !f.symlink? - onoe "Non-symlink alias: #{f}" - failed = true - elsif !f.file? - onoe "Non-file alias: #{f}" - failed = true - end - - if formula_dir.glob("**/#{f.basename}.rb").any?(&:exist?) - onoe "Formula duplicating alias: #{f}" - failed = true - end - end - !failed - end - - def valid_formulae?(tap, bottle_tag: nil) - cache[:valid_formulae] ||= {} - - success = T.let(true, T::Boolean) - tap.formula_files.each do |file| - valid = cache[:valid_formulae][file] - next if valid == true || valid&.include?(bottle_tag) - - formula_name = file.basename(".rb").to_s - formula_contents = file.read(encoding: "UTF-8") - - readall_namespace = "ReadallNamespace" - readall_formula_class = Formulary.load_formula(formula_name, file, formula_contents, readall_namespace, - flags: [], ignore_errors: false) - readall_formula = readall_formula_class.new(formula_name, file, :stable, tap: tap) - readall_formula.to_hash - # TODO: Remove check for MACOS_MODULE_REGEX once the `MacOS` module is undefined on Linux - cache[:valid_formulae][file] = if readall_formula.on_system_blocks_exist? || - formula_contents.match?(MACOS_MODULE_REGEX) - [bottle_tag, *cache[:valid_formulae][file]] - else - true - end - rescue Interrupt - raise - rescue Exception => e # rubocop:disable Lint/RescueException - onoe "Invalid formula (#{bottle_tag}): #{file}" - $stderr.puts e - success = false - end - success - end - - def valid_casks?(_tap, os_name: nil, arch: nil) - true - end - - def valid_tap?(tap, aliases: false, no_simulate: false, os_arch_combinations: OnSystem::ALL_OS_ARCH_COMBINATIONS) - success = true - - if aliases - valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir) - success = false unless valid_aliases + failed = T.let(false, T::Boolean) + alias_dir.each_child do |f| + if !f.symlink? + onoe "Non-symlink alias: #{f}" + failed = true + elsif !f.file? + onoe "Non-file alias: #{f}" + failed = true end - if no_simulate - success = false unless valid_formulae?(tap) - success = false unless valid_casks?(tap) + if formula_dir.glob("**/#{f.basename}.rb").any?(&:exist?) + onoe "Formula duplicating alias: #{f}" + failed = true + end + end + !failed + end + + def self.valid_formulae?(tap, bottle_tag: nil) + cache[:valid_formulae] ||= {} + + success = T.let(true, T::Boolean) + tap.formula_files.each do |file| + valid = cache[:valid_formulae][file] + next if valid == true || valid&.include?(bottle_tag) + + formula_name = file.basename(".rb").to_s + formula_contents = file.read(encoding: "UTF-8") + + readall_namespace = "ReadallNamespace" + readall_formula_class = Formulary.load_formula(formula_name, file, formula_contents, readall_namespace, + flags: [], ignore_errors: false) + readall_formula = readall_formula_class.new(formula_name, file, :stable, tap: tap) + readall_formula.to_hash + # TODO: Remove check for MACOS_MODULE_REGEX once the `MacOS` module is undefined on Linux + cache[:valid_formulae][file] = if readall_formula.on_system_blocks_exist? || + formula_contents.match?(MACOS_MODULE_REGEX) + [bottle_tag, *cache[:valid_formulae][file]] else - os_arch_combinations.each do |os, arch| - bottle_tag = Utils::Bottles::Tag.new(system: os, arch: arch) - next unless bottle_tag.valid_combination? + true + end + rescue Interrupt + raise + rescue Exception => e # rubocop:disable Lint/RescueException + onoe "Invalid formula (#{bottle_tag}): #{file}" + $stderr.puts e + success = false + end + success + end - Homebrew::SimulateSystem.with os: os, arch: arch do - success = false unless valid_formulae?(tap, bottle_tag: bottle_tag) - success = false unless valid_casks?(tap, os_name: os, arch: arch) - end + def self.valid_casks?(_tap, os_name: nil, arch: nil) + true + end + + def self.valid_tap?(tap, aliases: false, no_simulate: false, + os_arch_combinations: OnSystem::ALL_OS_ARCH_COMBINATIONS) + success = true + + if aliases + valid_aliases = valid_aliases?(tap.alias_dir, tap.formula_dir) + success = false unless valid_aliases + end + + if no_simulate + success = false unless valid_formulae?(tap) + success = false unless valid_casks?(tap) + else + os_arch_combinations.each do |os, arch| + bottle_tag = Utils::Bottles::Tag.new(system: os, arch: arch) + next unless bottle_tag.valid_combination? + + Homebrew::SimulateSystem.with os: os, arch: arch do + success = false unless valid_formulae?(tap, bottle_tag: bottle_tag) + success = false unless valid_casks?(tap, os_name: os, arch: arch) end end - - success end - private + success + end - def syntax_errors_or_warnings?(filename) - # Retrieve messages about syntax errors/warnings printed to `$stderr`. - _, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false) + private_class_method def self.syntax_errors_or_warnings?(filename) + # Retrieve messages about syntax errors/warnings printed to `$stderr`. + _, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false) - # Ignore unnecessary warning about named capture conflicts. - # See https://bugs.ruby-lang.org/issues/12359. - messages = err.lines - .grep_v(/named capture conflicts a local variable/) - .join + # Ignore unnecessary warning about named capture conflicts. + # See https://bugs.ruby-lang.org/issues/12359. + messages = err.lines + .grep_v(/named capture conflicts a local variable/) + .join - $stderr.print messages + $stderr.print messages - # Only syntax errors result in a non-zero status code. To detect syntax - # warnings we also need to inspect the output to `$stderr`. - !status.success? || !messages.chomp.empty? - end + # Only syntax errors result in a non-zero status code. To detect syntax + # warnings we also need to inspect the output to `$stderr`. + !status.success? || !messages.chomp.empty? end end