| 
									
										
										
										
											2023-03-25 08:36:56 -07:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 12:31:02 +01:00
										 |  |  | require "deprecate_disable" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | require "formula_text_auditor" | 
					
						
							|  |  |  | require "resource_auditor" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Homebrew | 
					
						
							|  |  |  |   # Auditor for checking common violations in {Formula}e. | 
					
						
							|  |  |  |   # | 
					
						
							|  |  |  |   # @api private | 
					
						
							|  |  |  |   class FormulaAuditor | 
					
						
							|  |  |  |     include FormulaCellarChecks | 
					
						
							| 
									
										
										
										
											2023-09-04 22:17:57 -04:00
										 |  |  |     include Utils::Curl | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     attr_reader :formula, :text, :problems, :new_formula_problems | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def initialize(formula, options = {}) | 
					
						
							|  |  |  |       @formula = formula | 
					
						
							|  |  |  |       @versioned_formula = formula.versioned_formula? | 
					
						
							|  |  |  |       @new_formula_inclusive = options[:new_formula] | 
					
						
							|  |  |  |       @new_formula = options[:new_formula] && !@versioned_formula | 
					
						
							|  |  |  |       @strict = options[:strict] | 
					
						
							|  |  |  |       @online = options[:online] | 
					
						
							|  |  |  |       @git = options[:git] | 
					
						
							|  |  |  |       @display_cop_names = options[:display_cop_names] | 
					
						
							|  |  |  |       @only = options[:only] | 
					
						
							|  |  |  |       @except = options[:except] | 
					
						
							|  |  |  |       # Accept precomputed style offense results, for efficiency | 
					
						
							|  |  |  |       @style_offenses = options[:style_offenses] | 
					
						
							|  |  |  |       # Allow the formula tap to be set as homebrew/core, for testing purposes | 
					
						
							|  |  |  |       @core_tap = formula.tap&.core_tap? || options[:core_tap] | 
					
						
							|  |  |  |       @problems = [] | 
					
						
							|  |  |  |       @new_formula_problems = [] | 
					
						
							|  |  |  |       @text = FormulaTextAuditor.new(formula.path) | 
					
						
							|  |  |  |       @specs = %w[stable head].map { |s| formula.send(s) }.compact | 
					
						
							|  |  |  |       @spdx_license_data = options[:spdx_license_data] | 
					
						
							|  |  |  |       @spdx_exception_data = options[:spdx_exception_data] | 
					
						
							| 
									
										
										
										
											2023-06-20 22:36:15 +08:00
										 |  |  |       @tap_audit = options[:tap_audit] | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       @previous_committed = {} | 
					
						
							|  |  |  |       @newest_committed = {} | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_style | 
					
						
							|  |  |  |       return unless @style_offenses | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @style_offenses.each do |offense| | 
					
						
							|  |  |  |         cop_name = "#{offense.cop_name}: " if @display_cop_names | 
					
						
							| 
									
										
										
										
											2023-05-19 16:59:14 +02:00
										 |  |  |         message = "#{cop_name}#{offense.message}" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-19 16:59:14 +02:00
										 |  |  |         problem message, location: offense.location, corrected: offense.corrected? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_file | 
					
						
							|  |  |  |       if formula.core_formula? && @versioned_formula | 
					
						
							| 
									
										
										
										
											2022-09-02 23:02:07 -07:00
										 |  |  |         unversioned_name = formula.name.gsub(/@.*$/, "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # ignore when an unversioned formula doesn't exist after an explicit rename | 
					
						
							|  |  |  |         return if formula.tap.formula_renames.key?(unversioned_name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # build this ourselves as we want e.g. homebrew/core to be present | 
					
						
							|  |  |  |         full_name = "#{formula.tap}/#{unversioned_name}" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         unversioned_formula = begin | 
					
						
							| 
									
										
										
										
											2022-09-02 23:02:07 -07:00
										 |  |  |           Formulary.factory(full_name).path | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         rescue FormulaUnavailableError, TapFormulaAmbiguityError, | 
					
						
							|  |  |  |                TapFormulaWithOldnameAmbiguityError | 
					
						
							|  |  |  |           Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         unless unversioned_formula.exist? | 
					
						
							|  |  |  |           unversioned_name = unversioned_formula.basename(".rb") | 
					
						
							|  |  |  |           problem "#{formula} is versioned but no #{unversioned_name} formula exists" | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |       elsif formula.stable? && | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             !@versioned_formula && | 
					
						
							|  |  |  |             (versioned_formulae = formula.versioned_formulae - [formula]) && | 
					
						
							|  |  |  |             versioned_formulae.present? | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |         versioned_aliases, unversioned_aliases = formula.aliases.partition { |a| a =~ /.@\d/ } | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         _, last_alias_version = versioned_formulae.map(&:name).last.split("@") | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         alias_name_major = "#{formula.name}@#{formula.version.major}" | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |         alias_name_major_minor = "#{formula.name}@#{formula.version.major_minor}" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         alias_name = if last_alias_version.split(".").length == 1
 | 
					
						
							|  |  |  |           alias_name_major | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           alias_name_major_minor | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |         valid_main_alias_names = [alias_name_major, alias_name_major_minor].uniq | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Also accept versioned aliases with names of other aliases, but do not require them. | 
					
						
							|  |  |  |         valid_other_alias_names = unversioned_aliases.flat_map do |name| | 
					
						
							|  |  |  |           %W[
 | 
					
						
							|  |  |  |             #{name}@#{formula.version.major} | 
					
						
							|  |  |  |             #{name}@#{formula.version.major_minor} | 
					
						
							|  |  |  |           ].uniq | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         unless @core_tap | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |           [versioned_aliases, valid_main_alias_names, valid_other_alias_names].each do |array| | 
					
						
							|  |  |  |             array.map! { |a| "#{formula.tap}/#{a}" } | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |         valid_versioned_aliases = versioned_aliases & valid_main_alias_names | 
					
						
							|  |  |  |         invalid_versioned_aliases = versioned_aliases - valid_main_alias_names - valid_other_alias_names | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         latest_versioned_formula = versioned_formulae.map(&:name).first | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-21 14:29:36 +00:00
										 |  |  |         if valid_versioned_aliases.empty? && alias_name != latest_versioned_formula | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           if formula.tap | 
					
						
							|  |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Formula has other versions so create a versioned alias: | 
					
						
							|  |  |  |                 cd #{formula.tap.alias_dir} | 
					
						
							|  |  |  |                 ln -s #{formula.path.to_s.gsub(formula.tap.path, "..")} #{alias_name} | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             problem "Formula has other versions so create an alias named #{alias_name}." | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if invalid_versioned_aliases.present? | 
					
						
							|  |  |  |           problem <<~EOS | 
					
						
							|  |  |  |             Formula has invalid versioned aliases: | 
					
						
							|  |  |  |               #{invalid_versioned_aliases.join("\n  ")} | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-09-04 13:12:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-04 14:35:59 +01:00
										 |  |  |       return if !formula.core_formula? || formula.path == formula.tap.new_formula_path(formula.name) | 
					
						
							| 
									
										
										
										
											2023-09-04 13:12:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       problem <<~EOS | 
					
						
							|  |  |  |         Formula is in wrong path: | 
					
						
							|  |  |  |           Expected: #{formula.tap.new_formula_path(formula.name)} | 
					
						
							|  |  |  |             Actual: #{formula.path} | 
					
						
							|  |  |  |       EOS | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def self.aliases | 
					
						
							|  |  |  |       # core aliases + tap alias names + tap alias full name | 
					
						
							|  |  |  |       @aliases ||= Formula.aliases + Formula.tap_aliases | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 20:14:51 +01:00
										 |  |  |     SYNCED_VERSIONS_FORMULAE_FILE = "synced_versions_formulae.json" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_synced_versions_formulae | 
					
						
							| 
									
										
										
										
											2021-12-13 16:44:33 +00:00
										 |  |  |       return unless formula.tap | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 20:14:51 +01:00
										 |  |  |       synced_versions_formulae_file = formula.tap.path/SYNCED_VERSIONS_FORMULAE_FILE | 
					
						
							|  |  |  |       return unless synced_versions_formulae_file.file? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       name = formula.name | 
					
						
							|  |  |  |       version = formula.version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       synced_versions_formulae = JSON.parse(synced_versions_formulae_file.read) | 
					
						
							|  |  |  |       synced_versions_formulae.each do |synced_version_formulae| | 
					
						
							|  |  |  |         next unless synced_version_formulae.include? name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         synced_version_formulae.each do |synced_formula| | 
					
						
							|  |  |  |           next if synced_formula == name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (synced_version = Formulary.factory(synced_formula).version) != version | 
					
						
							|  |  |  |             problem "Version of `#{synced_formula}` (#{synced_version}) should match version of `#{name}` (#{version})" | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         break | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     def audit_formula_name | 
					
						
							| 
									
										
										
										
											2021-05-27 17:55:28 -07:00
										 |  |  |       name = formula.name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "Formula name '#{name}' must not contain uppercase letters." if name != name.downcase | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       return unless @strict | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "'#{name}' is not allowed in homebrew/core." if MissingFormula.disallowed_reason(name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if Formula.aliases.include? name | 
					
						
							|  |  |  |         problem "Formula name conflicts with existing aliases in homebrew/core." | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-12 18:33:37 +05:30
										 |  |  |       if (oldname = CoreTap.instance.formula_renames[name]) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         problem "'#{name}' is reserved as the old name of #{oldname} in homebrew/core." | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return if formula.core_formula? | 
					
						
							|  |  |  |       return unless Formula.core_names.include?(name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "Formula name conflicts with existing core formula." | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     PERMITTED_LICENSE_MISMATCHES = { | 
					
						
							|  |  |  |       "AGPL-3.0" => ["AGPL-3.0-only", "AGPL-3.0-or-later"], | 
					
						
							|  |  |  |       "GPL-2.0"  => ["GPL-2.0-only",  "GPL-2.0-or-later"], | 
					
						
							|  |  |  |       "GPL-3.0"  => ["GPL-3.0-only",  "GPL-3.0-or-later"], | 
					
						
							|  |  |  |       "LGPL-2.1" => ["LGPL-2.1-only", "LGPL-2.1-or-later"], | 
					
						
							|  |  |  |       "LGPL-3.0" => ["LGPL-3.0-only", "LGPL-3.0-or-later"], | 
					
						
							|  |  |  |     }.freeze | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_license | 
					
						
							|  |  |  |       if formula.license.present? | 
					
						
							|  |  |  |         licenses, exceptions = SPDX.parse_license_expression formula.license | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-13 19:47:46 +08:00
										 |  |  |         sspl_licensed = licenses.any? { |license| license.to_s.start_with?("SSPL") } | 
					
						
							| 
									
										
										
										
											2022-09-13 19:22:11 +08:00
										 |  |  |         if sspl_licensed && @core_tap | 
					
						
							|  |  |  |           problem <<~EOS | 
					
						
							|  |  |  |             Formula #{formula.name} is SSPL-licensed. Software under the SSPL must not be packaged in homebrew/core. | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         non_standard_licenses = licenses.reject { |license| SPDX.valid_license? license } | 
					
						
							|  |  |  |         if non_standard_licenses.present? | 
					
						
							|  |  |  |           problem <<~EOS | 
					
						
							|  |  |  |             Formula #{formula.name} contains non-standard SPDX licenses: #{non_standard_licenses}. | 
					
						
							|  |  |  |             For a list of valid licenses check: #{Formatter.url("https://spdx.org/licenses/")} | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if @strict | 
					
						
							|  |  |  |           deprecated_licenses = licenses.select do |license| | 
					
						
							|  |  |  |             SPDX.deprecated_license? license | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           if deprecated_licenses.present? | 
					
						
							|  |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Formula #{formula.name} contains deprecated SPDX licenses: #{deprecated_licenses}. | 
					
						
							|  |  |  |               You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`). | 
					
						
							|  |  |  |               For a list of valid licenses check: #{Formatter.url("https://spdx.org/licenses/")} | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         invalid_exceptions = exceptions.reject { |exception| SPDX.valid_license_exception? exception } | 
					
						
							|  |  |  |         if invalid_exceptions.present? | 
					
						
							|  |  |  |           problem <<~EOS | 
					
						
							|  |  |  |             Formula #{formula.name} contains invalid or deprecated SPDX license exceptions: #{invalid_exceptions}. | 
					
						
							|  |  |  |             For a list of valid license exceptions check: | 
					
						
							|  |  |  |               #{Formatter.url("https://spdx.org/licenses/exceptions-index.html")} | 
					
						
							|  |  |  |           EOS | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return unless @online | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) | 
					
						
							|  |  |  |         return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         github_license = GitHub.get_repo_license(user, repo) | 
					
						
							|  |  |  |         return unless github_license | 
					
						
							|  |  |  |         return if (licenses + ["NOASSERTION"]).include?(github_license) | 
					
						
							|  |  |  |         return if PERMITTED_LICENSE_MISMATCHES[github_license]&.any? { |license| licenses.include? license } | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |         return if formula.tap&.audit_exception :permitted_formula_license_mismatches, formula.name | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         problem "Formula license #{licenses} does not match GitHub license #{Array(github_license)}." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       elsif @new_formula && @core_tap | 
					
						
							|  |  |  |         problem "Formulae in homebrew/core must specify a license." | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_deps | 
					
						
							|  |  |  |       @specs.each do |spec| | 
					
						
							|  |  |  |         # Check for things we don't like to depend on. | 
					
						
							|  |  |  |         # We allow non-Homebrew installs whenever possible. | 
					
						
							| 
									
										
										
										
											2023-06-19 06:03:31 +01:00
										 |  |  |         spec.declared_deps.each do |dep| | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           begin | 
					
						
							|  |  |  |             dep_f = dep.to_formula | 
					
						
							|  |  |  |           rescue TapFormulaUnavailableError | 
					
						
							|  |  |  |             # Don't complain about missing cross-tap dependencies | 
					
						
							|  |  |  |             next | 
					
						
							|  |  |  |           rescue FormulaUnavailableError | 
					
						
							| 
									
										
										
										
											2021-01-24 21:55:35 -05:00
										 |  |  |             problem "Can't find dependency '#{dep.name.inspect}'." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             next | 
					
						
							|  |  |  |           rescue TapFormulaAmbiguityError | 
					
						
							| 
									
										
										
										
											2021-01-24 21:55:35 -05:00
										 |  |  |             problem "Ambiguous dependency '#{dep.name.inspect}'." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             next | 
					
						
							|  |  |  |           rescue TapFormulaWithOldnameAmbiguityError | 
					
						
							| 
									
										
										
										
											2021-01-24 21:55:35 -05:00
										 |  |  |             problem "Ambiguous oldname dependency '#{dep.name.inspect}'." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             next | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-06 16:47:09 +01:00
										 |  |  |           if dep_f.oldnames.include?(dep.name.split("/").last) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             problem "Dependency '#{dep.name}' was renamed; use new name '#{dep_f.name}'." | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if @core_tap && | 
					
						
							|  |  |  |              @new_formula && | 
					
						
							| 
									
										
										
										
											2023-07-07 23:45:03 +01:00
										 |  |  |              !dep.uses_from_macos? && | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |              dep_f.keg_only? && | 
					
						
							|  |  |  |              dep_f.keg_only_reason.provided_by_macos? && | 
					
						
							|  |  |  |              dep_f.keg_only_reason.applicable? && | 
					
						
							| 
									
										
										
										
											2021-04-08 22:58:13 +02:00
										 |  |  |              formula.requirements.none?(LinuxRequirement) && | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |              !formula.tap&.audit_exception(:provided_by_macos_depends_on_allowlist, dep.name) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |             new_formula_problem( | 
					
						
							|  |  |  |               "Dependency '#{dep.name}' is provided by macOS; " \ | 
					
						
							|  |  |  |               "please replace 'depends_on' with 'uses_from_macos'.", | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           dep.options.each do |opt| | 
					
						
							|  |  |  |             next if @core_tap | 
					
						
							|  |  |  |             next if dep_f.option_defined?(opt) | 
					
						
							|  |  |  |             next if dep_f.requirements.find do |r| | 
					
						
							|  |  |  |               if r.recommended? | 
					
						
							|  |  |  |                 opt.name == "with-#{r.name}" | 
					
						
							|  |  |  |               elsif r.optional? | 
					
						
							|  |  |  |                 opt.name == "without-#{r.name}" | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-24 21:55:35 -05:00
										 |  |  |             problem "Dependency '#{dep}' does not define option #{opt.name.inspect}" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-24 21:55:35 -05:00
										 |  |  |           problem "Don't use 'git' as a dependency (it's always available)" if @new_formula && dep.name == "git" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |           problem "Dependency '#{dep.name}' is marked as :run. Remove :run; it is a no-op." if dep.tags.include?(:run) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           next unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 00:57:33 +00:00
										 |  |  |           if dep_f.tap.nil? | 
					
						
							|  |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Dependency '#{dep.name}' does not exist in any tap. | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           elsif !dep_f.tap.core_tap? | 
					
						
							| 
									
										
										
										
											2022-02-10 06:57:46 +08:00
										 |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Dependency '#{dep.name}' is not in homebrew/core. Formulae in homebrew/core | 
					
						
							|  |  |  |               should not have dependencies in external taps. | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-19 03:00:55 +08:00
										 |  |  |           if dep_f.deprecated? && !formula.deprecated? && !formula.disabled? | 
					
						
							|  |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Dependency '#{dep.name}' is deprecated but has un-deprecated dependents. Either | 
					
						
							| 
									
										
										
										
											2022-07-29 20:10:05 +08:00
										 |  |  |               un-deprecate '#{dep.name}' or deprecate it and all of its dependents. | 
					
						
							| 
									
										
										
										
											2022-01-19 03:00:55 +08:00
										 |  |  |             EOS | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-07 23:47:28 +01:00
										 |  |  |           if dep_f.disabled? && !formula.disabled? | 
					
						
							|  |  |  |             problem <<~EOS | 
					
						
							|  |  |  |               Dependency '#{dep.name}' is disabled but has un-disabled dependents. Either | 
					
						
							|  |  |  |               un-disable '#{dep.name}' or disable it and all of its dependents. | 
					
						
							|  |  |  |             EOS | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 10:24:35 +01:00
										 |  |  |           # we want to allow uses_from_macos for aliases but not bare dependencies | 
					
						
							| 
									
										
										
										
											2023-06-19 06:03:31 +01:00
										 |  |  |           if self.class.aliases.include?(dep.name) && !dep.uses_from_macos? | 
					
						
							| 
									
										
										
										
											2020-12-24 13:59:59 +00:00
										 |  |  |             problem "Dependency '#{dep.name}' is an alias; use the canonical name '#{dep.to_formula.full_name}'." | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           if dep.tags.include?(:recommended) || dep.tags.include?(:optional) | 
					
						
							|  |  |  |             problem "Formulae in homebrew/core should not have optional or recommended dependencies" | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         next unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if spec.requirements.map(&:recommended?).any? || spec.requirements.map(&:optional?).any? | 
					
						
							|  |  |  |           problem "Formulae in homebrew/core should not have optional or recommended requirements" | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |       return if formula.tap&.audit_exception :versioned_dependencies_conflicts_allowlist, formula.name | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       # The number of conflicts on Linux is absurd. | 
					
						
							|  |  |  |       # TODO: remove this and check these there too. | 
					
						
							| 
									
										
										
										
											2022-07-28 14:52:19 -04:00
										 |  |  |       return if Homebrew::SimulateSystem.simulating_or_running_on_linux? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-27 14:44:12 +08:00
										 |  |  |       # Skip the versioned dependencies conflict audit for *-staging branches. | 
					
						
							|  |  |  |       # This will allow us to migrate dependents of formulae like Python or OpenSSL | 
					
						
							|  |  |  |       # gradually over separate PRs which target a *-staging branch. See: | 
					
						
							|  |  |  |       #   https://github.com/Homebrew/homebrew-core/pull/134260 | 
					
						
							|  |  |  |       ignore_formula_conflict, staging_formula = | 
					
						
							|  |  |  |         if @tap_audit && (github_event_path = ENV.fetch("GITHUB_EVENT_PATH", nil)).present? | 
					
						
							|  |  |  |           event_payload = JSON.parse(File.read(github_event_path)) | 
					
						
							|  |  |  |           base_info = event_payload.dig("pull_request", "base").to_h # handle `nil` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # We need to read the head ref from `GITHUB_EVENT_PATH` because | 
					
						
							|  |  |  |           # `git branch --show-current` returns `master` on PR branches. | 
					
						
							|  |  |  |           staging_branch = base_info["ref"]&.end_with?("-staging") | 
					
						
							|  |  |  |           homebrew_owned_repo = base_info.dig("repo", "owner", "login") == "Homebrew" | 
					
						
							|  |  |  |           homebrew_core_pr = base_info.dig("repo", "name") == "homebrew-core" | 
					
						
							|  |  |  |           # Support staging branches named `formula-staging` or `formula@version-staging`. | 
					
						
							|  |  |  |           base_formula = base_info["ref"]&.split(/-|@/, 2)&.first | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           [staging_branch && homebrew_owned_repo && homebrew_core_pr, base_formula] | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2023-06-10 00:46:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       recursive_runtime_formulae = formula.runtime_formula_dependencies(undeclared: false) | 
					
						
							|  |  |  |       version_hash = {} | 
					
						
							|  |  |  |       version_conflicts = Set.new | 
					
						
							|  |  |  |       recursive_runtime_formulae.each do |f| | 
					
						
							|  |  |  |         name = f.name | 
					
						
							|  |  |  |         unversioned_name, = name.split("@") | 
					
						
							| 
									
										
										
										
											2023-06-27 14:44:12 +08:00
										 |  |  |         next if ignore_formula_conflict && unversioned_name == staging_formula | 
					
						
							| 
									
										
										
										
											2022-08-18 15:27:23 +08:00
										 |  |  |         # Allow use of the full versioned name (e.g. `python@3.99`) or an unversioned alias (`python`). | 
					
						
							|  |  |  |         next if formula.tap&.audit_exception :versioned_formula_dependent_conflicts_allowlist, name | 
					
						
							|  |  |  |         next if formula.tap&.audit_exception :versioned_formula_dependent_conflicts_allowlist, unversioned_name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         version_hash[unversioned_name] ||= Set.new | 
					
						
							|  |  |  |         version_hash[unversioned_name] << name | 
					
						
							|  |  |  |         next if version_hash[unversioned_name].length < 2
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         version_conflicts += version_hash[unversioned_name] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return if version_conflicts.empty? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 12:31:02 +01:00
										 |  |  |       return if formula.disabled? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return if formula.deprecated? && | 
					
						
							| 
									
										
										
										
											2023-12-03 21:59:03 -05:00
										 |  |  |                 formula.deprecation_reason != DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS[:versioned_formula] | 
					
						
							| 
									
										
										
										
											2020-11-24 12:31:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       problem <<~EOS | 
					
						
							|  |  |  |         #{formula.full_name} contains conflicting version recursive dependencies: | 
					
						
							|  |  |  |           #{version_conflicts.to_a.join ", "} | 
					
						
							|  |  |  |         View these with `brew deps --tree #{formula.full_name}`. | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_conflicts | 
					
						
							| 
									
										
										
										
											2021-06-18 16:40:12 +01:00
										 |  |  |       tap = formula.tap | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |       formula.conflicts.each do |conflict| | 
					
						
							|  |  |  |         conflicting_formula = Formulary.factory(conflict.name) | 
					
						
							| 
									
										
										
										
											2021-06-18 16:40:12 +01:00
										 |  |  |         next if tap != conflicting_formula.tap | 
					
						
							| 
									
										
										
										
											2021-06-14 14:14:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-18 16:40:12 +01:00
										 |  |  |         problem "Formula should not conflict with itself" if formula == conflicting_formula | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 14:01:53 -07:00
										 |  |  |         if T.must(tap).formula_renames.key?(conflict.name) || T.must(tap).aliases.include?(conflict.name) | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  |           problem "Formula conflict should be declared using " \ | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |                   "canonical name (#{conflicting_formula.name}) instead of #{conflict.name}" | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-25 08:36:56 -07:00
										 |  |  |         reverse_conflict_found = T.let(false, T::Boolean) | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |         conflicting_formula.conflicts.each do |reverse_conflict| | 
					
						
							|  |  |  |           reverse_conflict_formula = Formulary.factory(reverse_conflict.name) | 
					
						
							| 
									
										
										
										
											2023-07-24 14:01:53 -07:00
										 |  |  |           if T.must(tap).formula_renames.key?(reverse_conflict.name) || | 
					
						
							|  |  |  |              T.must(tap).aliases.include?(reverse_conflict.name) | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  |             problem "Formula #{conflicting_formula.name} conflict should be declared using " \ | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |                     "canonical name (#{reverse_conflict_formula.name}) instead of #{reverse_conflict.name}" | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |           reverse_conflict_found ||= reverse_conflict_formula == formula | 
					
						
							| 
									
										
										
										
											2021-06-16 22:52:09 +01:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |         unless reverse_conflict_found | 
					
						
							| 
									
										
										
										
											2021-06-14 14:14:36 +01:00
										 |  |  |           problem "Formula #{conflicting_formula.name} should also have a conflict declared with #{formula.name}" | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       rescue TapFormulaUnavailableError | 
					
						
							|  |  |  |         # Don't complain about missing cross-tap conflicts. | 
					
						
							|  |  |  |         next | 
					
						
							|  |  |  |       rescue FormulaUnavailableError | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |         problem "Can't find conflicting formula #{conflict.name.inspect}." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError | 
					
						
							| 
									
										
										
										
											2021-06-18 16:57:53 +01:00
										 |  |  |         problem "Ambiguous conflicting formula #{conflict.name.inspect}." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 13:22:00 +08:00
										 |  |  |     def audit_gcc_dependency | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  |       return unless Homebrew::SimulateSystem.simulating_or_running_on_linux? | 
					
						
							|  |  |  |       return unless linux_only_gcc_dep?(formula) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "Formulae in homebrew/core should not have a Linux-only dependency on GCC." | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     def audit_postgresql | 
					
						
							| 
									
										
										
										
											2023-04-18 15:06:50 -07:00
										 |  |  |       return if formula.name != "postgresql" | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       major_version = formula.version.major.to_i | 
					
						
							|  |  |  |       previous_major_version = major_version - 1
 | 
					
						
							|  |  |  |       previous_formula_name = "postgresql@#{previous_major_version}" | 
					
						
							|  |  |  |       begin | 
					
						
							|  |  |  |         Formula[previous_formula_name] | 
					
						
							|  |  |  |       rescue FormulaUnavailableError | 
					
						
							|  |  |  |         problem "Versioned #{previous_formula_name} in homebrew/core must be created for " \ | 
					
						
							| 
									
										
										
										
											2021-02-01 21:26:24 +09:00
										 |  |  |                 "`brew postgresql-upgrade-database` and `pg_upgrade` to work." | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-13 17:13:50 +01:00
										 |  |  |     def audit_glibc | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							| 
									
										
										
										
											2022-07-29 14:06:51 -07:00
										 |  |  |       return if formula.name != "glibc" | 
					
						
							| 
									
										
										
										
											2022-08-23 12:42:02 +01:00
										 |  |  |       # Also allow LINUX_GLIBC_NEXT_CI_VERSION for when we're upgrading. | 
					
						
							|  |  |  |       return if [OS::LINUX_GLIBC_CI_VERSION, OS::LINUX_GLIBC_NEXT_CI_VERSION].include?(formula.version.to_s) | 
					
						
							| 
									
										
										
										
											2021-02-13 17:13:50 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-23 12:42:02 +01:00
										 |  |  |       problem "The glibc version must be #{OS::LINUX_GLIBC_CI_VERSION}, as needed by our CI on Linux. " \ | 
					
						
							|  |  |  |               "The glibc formula is for users who have a system glibc with a lower version, " \ | 
					
						
							|  |  |  |               "which allows them to use our Linux bottles, which were compiled against system glibc on CI." | 
					
						
							| 
									
										
										
										
											2021-02-13 17:13:50 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-03 00:33:27 +05:30
										 |  |  |     ELASTICSEARCH_KIBANA_RELICENSED_VERSION = "7.11" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_elasticsearch_kibana | 
					
						
							|  |  |  |       return if formula.name != "elasticsearch" && formula.name != "kibana" | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  |       return if formula.version < Version.new(ELASTICSEARCH_KIBANA_RELICENSED_VERSION) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "Elasticsearch and Kibana were relicensed to a non-open-source license from version 7.11. " \ | 
					
						
							|  |  |  |               "They must not be upgraded to version 7.11 or newer." | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-08 22:29:19 +08:00
										 |  |  |     # https://www.hashicorp.com/license-faq#products-covered-by-bsl | 
					
						
							|  |  |  |     HASHICORP_RELICENSED_FORMULAE_VERSIONS = { | 
					
						
							|  |  |  |       "terraform"         => "1.6", | 
					
						
							|  |  |  |       "packer"            => "1.10", | 
					
						
							|  |  |  |       "vault"             => "1.15", | 
					
						
							|  |  |  |       "boundary"          => "0.14", | 
					
						
							|  |  |  |       "consul"            => "1.17", | 
					
						
							|  |  |  |       "nomad"             => "1.7", | 
					
						
							|  |  |  |       "waypoint"          => "0.12", | 
					
						
							|  |  |  |       "vagrant"           => "2.4", | 
					
						
							|  |  |  |       "vagrant-compleion" => "2.4", | 
					
						
							|  |  |  |     }.freeze | 
					
						
							| 
									
										
										
										
											2023-09-06 21:26:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-08 22:29:19 +08:00
										 |  |  |     def audit_hashicorp_formulae | 
					
						
							|  |  |  |       return unless HASHICORP_RELICENSED_FORMULAE_VERSIONS.key? formula.name | 
					
						
							| 
									
										
										
										
											2023-09-06 21:26:39 +02:00
										 |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-08 22:29:19 +08:00
										 |  |  |       relicensed_version = Version.new(HASHICORP_RELICENSED_FORMULAE_VERSIONS[formula.name]) | 
					
						
							|  |  |  |       return if formula.version < relicensed_version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "#{formula.name} was relicensed to a non-open-source license from version #{relicensed_version}. " \ | 
					
						
							|  |  |  |               "It must not be upgraded to version #{relicensed_version} or newer." | 
					
						
							| 
									
										
										
										
											2023-09-06 21:26:39 +02:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-18 14:46:13 +00:00
										 |  |  |     def audit_keg_only_reason | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  |       return unless formula.keg_only? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-22 13:54:43 +00:00
										 |  |  |       keg_only_message = text.to_s.match(/keg_only\s+["'](.*)["']/)&.captures&.first | 
					
						
							| 
									
										
										
										
											2023-03-18 14:46:13 +00:00
										 |  |  |       return unless keg_only_message&.include?("HOMEBREW_PREFIX") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-18 15:27:35 +00:00
										 |  |  |       problem "`keg_only` reason should not include `HOMEBREW_PREFIX` as it creates confusing `brew info` output." | 
					
						
							| 
									
										
										
										
											2023-03-18 14:46:13 +00:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     def audit_versioned_keg_only | 
					
						
							|  |  |  |       return unless @versioned_formula | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if formula.keg_only? | 
					
						
							|  |  |  |         return if formula.keg_only_reason.versioned_formula? | 
					
						
							| 
									
										
										
										
											2021-07-13 21:20:49 +09:00
										 |  |  |         return if formula.name.start_with?("openssl", "libressl") && formula.keg_only_reason.by_macos? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |       return if formula.tap&.audit_exception :versioned_keg_only_allowlist, formula.name | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       problem "Versioned formulae in homebrew/core should use `keg_only :versioned_formula`" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_homepage | 
					
						
							|  |  |  |       homepage = formula.homepage | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 17:04:59 +00:00
										 |  |  |       return if homepage.blank? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return unless @online | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |       return if formula.tap&.audit_exception :cert_error_allowlist, formula.name, homepage | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return unless DevelopmentTools.curl_handles_most_https_certificates? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-01 22:22:19 -05:00
										 |  |  |       use_homebrew_curl = [:stable, :head].any? do |spec_name| | 
					
						
							|  |  |  |         next false unless (spec = formula.send(spec_name)) | 
					
						
							| 
									
										
										
										
											2021-08-20 10:14:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-01 22:22:19 -05:00
										 |  |  |         spec.using == :homebrew_curl | 
					
						
							| 
									
										
										
										
											2021-08-20 10:14:50 +02:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-04 22:17:57 -04:00
										 |  |  |       if (http_content_problem = curl_check_http_content( | 
					
						
							|  |  |  |         homepage, | 
					
						
							|  |  |  |         SharedAudits::URL_TYPE_HOMEPAGE, | 
					
						
							|  |  |  |         user_agents:       [:browser, :default], | 
					
						
							|  |  |  |         check_content:     true, | 
					
						
							|  |  |  |         strict:            @strict, | 
					
						
							|  |  |  |         use_homebrew_curl: use_homebrew_curl, | 
					
						
							|  |  |  |       )) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         problem http_content_problem | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_bottle_spec | 
					
						
							|  |  |  |       # special case: new versioned formulae should be audited | 
					
						
							|  |  |  |       return unless @new_formula_inclusive | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return unless formula.bottle_defined? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new_formula_problem "New formulae in homebrew/core should not have a `bottle do` block" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_github_repository_archived | 
					
						
							| 
									
										
										
										
											2021-02-03 10:12:36 -05:00
										 |  |  |       return if formula.deprecated? || formula.disabled? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @online | 
					
						
							|  |  |  |       return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       metadata = SharedAudits.github_repo_data(user, repo) | 
					
						
							|  |  |  |       return if metadata.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "GitHub repo is archived" if metadata["archived"] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_gitlab_repository_archived | 
					
						
							| 
									
										
										
										
											2021-02-03 10:12:36 -05:00
										 |  |  |       return if formula.deprecated? || formula.disabled? | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @online | 
					
						
							|  |  |  |       return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       metadata = SharedAudits.gitlab_repo_data(user, repo) | 
					
						
							|  |  |  |       return if metadata.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem "GitLab repo is archived" if metadata["archived"] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_github_repository | 
					
						
							|  |  |  |       user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if @new_formula | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       warning = SharedAudits.github(user, repo) | 
					
						
							|  |  |  |       return if warning.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new_formula_problem warning | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_gitlab_repository | 
					
						
							|  |  |  |       user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if @new_formula | 
					
						
							|  |  |  |       return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       warning = SharedAudits.gitlab(user, repo) | 
					
						
							|  |  |  |       return if warning.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new_formula_problem warning | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_bitbucket_repository | 
					
						
							|  |  |  |       user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) if @new_formula | 
					
						
							|  |  |  |       return if user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       warning = SharedAudits.bitbucket(user, repo) | 
					
						
							|  |  |  |       return if warning.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       new_formula_problem warning | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_repo_data(regex) | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  |       return unless @online | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       _, user, repo = *regex.match(formula.stable.url) if formula.stable | 
					
						
							|  |  |  |       _, user, repo = *regex.match(formula.homepage) unless user | 
					
						
							|  |  |  |       _, user, repo = *regex.match(formula.head.url) if !user && formula.head | 
					
						
							|  |  |  |       return if !user || !repo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       repo.delete_suffix!(".git") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       [user, repo] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_specs | 
					
						
							|  |  |  |       problem "Head-only (no stable download)" if head_only?(formula) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       %w[Stable HEAD].each do |name| | 
					
						
							|  |  |  |         spec_name = name.downcase.to_sym | 
					
						
							| 
									
										
										
										
											2021-02-12 18:33:37 +05:30
										 |  |  |         next unless (spec = formula.send(spec_name)) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-23 15:33:44 +05:30
										 |  |  |         except = @except.to_a | 
					
						
							|  |  |  |         if spec_name == :head && | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |            formula.tap&.audit_exception(:head_non_default_branch_allowlist, formula.name, spec.specs[:branch]) | 
					
						
							| 
									
										
										
										
											2021-07-23 15:33:44 +05:30
										 |  |  |           except << "head_branch" | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-15 09:55:28 -04:00
										 |  |  |         ra = ResourceAuditor.new( | 
					
						
							|  |  |  |           spec, spec_name, | 
					
						
							| 
									
										
										
										
											2021-07-26 12:39:25 +02:00
										 |  |  |           online: @online, strict: @strict, only: @only, except: except, | 
					
						
							|  |  |  |           use_homebrew_curl: spec.using == :homebrew_curl | 
					
						
							| 
									
										
										
										
											2021-06-15 09:55:28 -04:00
										 |  |  |         ).audit | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         ra.problems.each do |message| | 
					
						
							|  |  |  |           problem "#{name}: #{message}" | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         spec.resources.each_value do |resource| | 
					
						
							|  |  |  |           problem "Resource name should be different from the formula name" if resource.name == formula.name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-15 09:55:28 -04:00
										 |  |  |           ra = ResourceAuditor.new( | 
					
						
							|  |  |  |             resource, spec_name, | 
					
						
							| 
									
										
										
										
											2021-10-30 20:04:13 +08:00
										 |  |  |             online: @online, strict: @strict, only: @only, except: @except, | 
					
						
							|  |  |  |             use_homebrew_curl: resource.using == :homebrew_curl | 
					
						
							| 
									
										
										
										
											2021-06-15 09:55:28 -04:00
										 |  |  |           ).audit | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           ra.problems.each do |message| | 
					
						
							|  |  |  |             problem "#{name} resource #{resource.name.inspect}: #{message}" | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         next if spec.patches.empty? | 
					
						
							| 
									
										
										
										
											2021-01-04 14:07:21 -05:00
										 |  |  |         next if !@new_formula || !@core_tap | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         new_formula_problem( | 
					
						
							|  |  |  |           "Formulae should not require patches to build. " \ | 
					
						
							|  |  |  |           "Patches should be submitted and accepted upstream first.", | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if formula.head && @versioned_formula && | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |          !formula.tap&.audit_exception(:versioned_head_spec_allowlist, formula.name) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         problem "Versioned formulae should not have a `HEAD` spec" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       stable = formula.stable | 
					
						
							|  |  |  |       return unless stable | 
					
						
							|  |  |  |       return unless stable.url | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-13 21:55:47 +09:00
										 |  |  |       version = stable.version | 
					
						
							|  |  |  |       problem "Stable: version (#{version}) is set to a string without a digit" if version.to_s !~ /\d/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       stable_version_string = version.to_s | 
					
						
							|  |  |  |       if stable_version_string.start_with?("HEAD") | 
					
						
							|  |  |  |         problem "Stable: non-HEAD version name (#{stable_version_string}) should not begin with HEAD" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       stable_url_version = Version.parse(stable.url) | 
					
						
							|  |  |  |       stable_url_minor_version = stable_url_version.minor.to_i | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       formula_suffix = stable.version.patch.to_i | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |       throttled_rate = formula.tap&.audit_exception(:throttled_formulae, formula.name) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       if throttled_rate && formula_suffix.modulo(throttled_rate).nonzero? | 
					
						
							|  |  |  |         problem "should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case (url = stable.url) | 
					
						
							|  |  |  |       when /[\d._-](alpha|beta|rc\d)/ | 
					
						
							|  |  |  |         matched = Regexp.last_match(1) | 
					
						
							|  |  |  |         version_prefix = stable_version_string.sub(/\d+$/, "") | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |         return if formula.tap&.audit_exception :unstable_allowlist, formula.name, version_prefix | 
					
						
							|  |  |  |         return if formula.tap&.audit_exception :unstable_devel_allowlist, formula.name, version_prefix | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         problem "Stable version URLs should not contain #{matched}" | 
					
						
							|  |  |  |       when %r{download\.gnome\.org/sources}, %r{ftp\.gnome\.org/pub/GNOME/sources}i | 
					
						
							|  |  |  |         version_prefix = stable.version.major_minor | 
					
						
							| 
									
										
										
										
											2021-10-04 21:45:20 -04:00
										 |  |  |         return if formula.tap&.audit_exception :gnome_devel_allowlist, formula.name, version_prefix | 
					
						
							| 
									
										
										
										
											2023-07-06 16:47:09 +01:00
										 |  |  |         return if stable_url_version < Version.new("1.0") | 
					
						
							| 
									
										
										
										
											2021-03-29 16:37:23 -04:00
										 |  |  |         # All minor versions are stable in the new GNOME version scheme (which starts at version 40.0) | 
					
						
							|  |  |  |         # https://discourse.gnome.org/t/new-gnome-versioning-scheme/4235 | 
					
						
							| 
									
										
										
										
											2023-07-06 16:47:09 +01:00
										 |  |  |         return if stable_url_version >= Version.new("40.0") | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         return if stable_url_minor_version.even? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         problem "#{stable.version} is a development release" | 
					
						
							|  |  |  |       when %r{isc.org/isc/bind\d*/}i | 
					
						
							|  |  |  |         return if stable_url_minor_version.even? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         problem "#{stable.version} is a development release" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       when %r{https?://gitlab\.com/([\w-]+)/([\w-]+)} | 
					
						
							|  |  |  |         owner = Regexp.last_match(1) | 
					
						
							|  |  |  |         repo = Regexp.last_match(2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         tag = SharedAudits.gitlab_tag_from_url(url) | 
					
						
							|  |  |  |         tag ||= stable.specs[:tag] | 
					
						
							|  |  |  |         tag ||= stable.version | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if @online | 
					
						
							|  |  |  |           error = SharedAudits.gitlab_release(owner, repo, tag, formula: formula) | 
					
						
							|  |  |  |           problem error if error | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       when %r{^https://github.com/([\w-]+)/([\w-]+)} | 
					
						
							|  |  |  |         owner = Regexp.last_match(1) | 
					
						
							|  |  |  |         repo = Regexp.last_match(2) | 
					
						
							|  |  |  |         tag = SharedAudits.github_tag_from_url(url) | 
					
						
							|  |  |  |         tag ||= formula.stable.specs[:tag] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if @online | 
					
						
							|  |  |  |           error = SharedAudits.github_release(owner, repo, tag, formula: formula) | 
					
						
							|  |  |  |           problem error if error | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 14:53:35 -05:00
										 |  |  |     def audit_stable_version | 
					
						
							|  |  |  |       return unless @git | 
					
						
							|  |  |  |       return unless formula.tap # skip formula not from core or any taps | 
					
						
							|  |  |  |       return unless formula.tap.git? # git log is required | 
					
						
							|  |  |  |       return if formula.stable.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       current_version = formula.stable.version | 
					
						
							|  |  |  |       current_version_scheme = formula.version_scheme | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       previous_committed, newest_committed = committed_version_info | 
					
						
							| 
									
										
										
										
											2023-12-13 14:53:35 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       if !newest_committed[:version].nil? && | 
					
						
							|  |  |  |          current_version < newest_committed[:version] && | 
					
						
							|  |  |  |          current_version_scheme == previous_committed[:version_scheme] | 
					
						
							|  |  |  |         problem "stable version should not decrease (from #{newest_committed[:version]} to #{current_version})" | 
					
						
							| 
									
										
										
										
											2023-12-13 14:53:35 -05:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     def audit_revision_and_version_scheme | 
					
						
							| 
									
										
										
										
											2022-02-09 04:24:58 -05:00
										 |  |  |       new_formula_problem("New formulae should not define a revision.") if @new_formula && !formula.revision.zero? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       return unless @git | 
					
						
							|  |  |  |       return unless formula.tap # skip formula not from core or any taps | 
					
						
							|  |  |  |       return unless formula.tap.git? # git log is required | 
					
						
							|  |  |  |       return if formula.stable.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       current_version = formula.stable.version | 
					
						
							|  |  |  |       current_version_scheme = formula.version_scheme | 
					
						
							|  |  |  |       current_revision = formula.revision | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       previous_committed, newest_committed = committed_version_info | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       unless previous_committed[:version_scheme].nil? | 
					
						
							|  |  |  |         if current_version_scheme < previous_committed[:version_scheme] | 
					
						
							|  |  |  |           problem "version_scheme should not decrease (from #{previous_committed[:version_scheme]} " \ | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |                   "to #{current_version_scheme})" | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |         elsif current_version_scheme > (previous_committed[:version_scheme] + 1) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |           problem "version_schemes should only increment by 1" | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       if (previous_committed[:version] != newest_committed[:version] || | 
					
						
							|  |  |  |          current_version != newest_committed[:version]) && | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |          !current_revision.zero? && | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |          current_revision == newest_committed[:revision] && | 
					
						
							|  |  |  |          current_revision == previous_committed[:revision] | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         problem "'revision #{current_revision}' should be removed" | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  |       elsif current_version == previous_committed[:version] && | 
					
						
							|  |  |  |             !previous_committed[:revision].nil? && | 
					
						
							|  |  |  |             current_revision < previous_committed[:revision] | 
					
						
							|  |  |  |         problem "revision should not decrease (from #{previous_committed[:revision]} to #{current_revision})" | 
					
						
							|  |  |  |       elsif newest_committed[:revision] && | 
					
						
							|  |  |  |             current_revision > (newest_committed[:revision] + 1) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         problem "revisions should only increment by 1" | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-19 13:19:40 -05:00
										 |  |  |     def audit_unconfirmed_checksum_change | 
					
						
							|  |  |  |       return unless @git | 
					
						
							|  |  |  |       return unless formula.tap # skip formula not from core or any taps | 
					
						
							|  |  |  |       return unless formula.tap.git? # git log is required | 
					
						
							|  |  |  |       return if formula.stable.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       current_version = formula.stable.version | 
					
						
							|  |  |  |       current_checksum = formula.stable.checksum | 
					
						
							|  |  |  |       current_url = formula.stable.url | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       _, newest_committed = committed_version_info | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if current_version == newest_committed[:version] && | 
					
						
							|  |  |  |          current_url == newest_committed[:url] && | 
					
						
							|  |  |  |          current_checksum != newest_committed[:checksum] && | 
					
						
							|  |  |  |          current_checksum.present? && newest_committed[:checksum].present? | 
					
						
							|  |  |  |         problem( | 
					
						
							|  |  |  |           "stable sha256 changed without the url/version also changing; " \ | 
					
						
							|  |  |  |           "please create an issue upstream to rule out malicious " \ | 
					
						
							|  |  |  |           "circumstances and to find out why the file changed.", | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     def audit_text | 
					
						
							|  |  |  |       bin_names = Set.new | 
					
						
							|  |  |  |       bin_names << formula.name | 
					
						
							|  |  |  |       bin_names += formula.aliases | 
					
						
							|  |  |  |       [formula.bin, formula.sbin].each do |dir| | 
					
						
							|  |  |  |         next unless dir.exist? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 02:52:30 +00:00
										 |  |  |         bin_names += dir.children.map { |child| child.basename.to_s } | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |       end | 
					
						
							|  |  |  |       shell_commands = ["system", "shell_output", "pipe_output"] | 
					
						
							|  |  |  |       bin_names.each do |name| | 
					
						
							|  |  |  |         shell_commands.each do |cmd| | 
					
						
							|  |  |  |           if text.to_s.match?(/test do.*#{cmd}[(\s]+['"]#{Regexp.escape(name)}[\s'"]/m) | 
					
						
							|  |  |  |             problem %Q(fully scope test #{cmd} calls, e.g. #{cmd} "\#{bin}/#{name}") | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_reverse_migration | 
					
						
							|  |  |  |       # Only enforce for new formula being re-added to core | 
					
						
							|  |  |  |       return unless @strict | 
					
						
							|  |  |  |       return unless @core_tap | 
					
						
							|  |  |  |       return unless formula.tap.tap_migrations.key?(formula.name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem <<~EOS | 
					
						
							|  |  |  |         #{formula.name} seems to be listed in tap_migrations.json! | 
					
						
							|  |  |  |         Please remove #{formula.name} from present tap & tap_migrations.json | 
					
						
							|  |  |  |         before submitting it to Homebrew/homebrew-#{formula.tap.repo}. | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit_prefix_has_contents | 
					
						
							|  |  |  |       return unless formula.prefix.directory? | 
					
						
							|  |  |  |       return unless Keg.new(formula.prefix).empty_installation? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       problem <<~EOS | 
					
						
							|  |  |  |         The installation seems to be empty. Please ensure the prefix | 
					
						
							|  |  |  |         is set correctly and expected files are installed. | 
					
						
							|  |  |  |         The prefix configure/make argument may be case-sensitive. | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def quote_dep(dep) | 
					
						
							|  |  |  |       dep.is_a?(Symbol) ? dep.inspect : "'#{dep}'" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def problem_if_output(output) | 
					
						
							|  |  |  |       problem(output) if output | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def audit | 
					
						
							|  |  |  |       only_audits = @only | 
					
						
							|  |  |  |       except_audits = @except | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name| | 
					
						
							|  |  |  |         name = audit_method_name.delete_prefix("audit_") | 
					
						
							| 
									
										
										
										
											2021-06-15 09:55:28 -04:00
										 |  |  |         next if only_audits&.exclude?(name) | 
					
						
							|  |  |  |         next if except_audits&.include?(name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |         send(audit_method_name) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-19 16:59:14 +02:00
										 |  |  |     def problem(message, location: nil, corrected: false) | 
					
						
							|  |  |  |       @problems << ({ message: message, location: location, corrected: corrected }) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-19 16:59:14 +02:00
										 |  |  |     def new_formula_problem(message, location: nil, corrected: false) | 
					
						
							|  |  |  |       @new_formula_problems << ({ message: message, location: location, corrected: corrected }) | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def head_only?(formula) | 
					
						
							|  |  |  |       formula.head && formula.stable.nil? | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2022-08-22 14:54:52 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def linux_only_gcc_dep?(formula) | 
					
						
							| 
									
										
										
										
											2022-08-24 13:22:00 +08:00
										 |  |  |       odie "`#linux_only_gcc_dep?` works only on Linux!" if Homebrew::SimulateSystem.simulating_or_running_on_macos? | 
					
						
							| 
									
										
										
										
											2022-08-25 20:44:27 +08:00
										 |  |  |       return false if formula.deps.map(&:name).exclude?("gcc") | 
					
						
							| 
									
										
										
										
											2022-08-22 14:54:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 20:44:27 +08:00
										 |  |  |       variations = formula.to_hash_with_variations["variations"] | 
					
						
							| 
									
										
										
										
											2022-08-24 15:12:58 +08:00
										 |  |  |       # The formula has no variations, so all OS-version-arch triples depend on GCC. | 
					
						
							| 
									
										
										
										
											2022-08-24 15:09:53 +08:00
										 |  |  |       return false if variations.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-13 22:35:08 +02:00
										 |  |  |       MacOSVersion::SYMBOLS.keys.product(OnSystem::ARCH_OPTIONS).each do |os, arch| | 
					
						
							|  |  |  |         bottle_tag = Utils::Bottles::Tag.new(system: os, arch: arch) | 
					
						
							|  |  |  |         next unless bottle_tag.valid_combination? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         variation_dependencies = variations.dig(bottle_tag.to_sym, "dependencies") | 
					
						
							|  |  |  |         # This variation either: | 
					
						
							|  |  |  |         #   1. does not exist | 
					
						
							|  |  |  |         #   2. has no variation-specific dependencies | 
					
						
							|  |  |  |         # In either case, it matches Linux. We must check for `nil` because an empty | 
					
						
							|  |  |  |         # array indicates that this variation does not depend on GCC. | 
					
						
							|  |  |  |         return false if variation_dependencies.nil? | 
					
						
							|  |  |  |         # We found a non-Linux variation that depends on GCC. | 
					
						
							|  |  |  |         return false if variation_dependencies.include?("gcc") | 
					
						
							| 
									
										
										
										
											2022-08-24 20:34:43 +08:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2022-08-22 14:54:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 20:34:43 +08:00
										 |  |  |       true | 
					
						
							| 
									
										
										
										
											2022-08-22 14:54:52 +08:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-12-13 17:35:02 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def committed_version_info | 
					
						
							|  |  |  |       return [] unless @git | 
					
						
							|  |  |  |       return [] unless formula.tap # skip formula not from core or any taps | 
					
						
							|  |  |  |       return [] unless formula.tap.git? # git log is required | 
					
						
							|  |  |  |       return [] if formula.stable.blank? | 
					
						
							|  |  |  |       return [@previous_committed, @newest_committed] if @previous_committed.present? || @newest_committed.present? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       current_version = formula.stable.version | 
					
						
							|  |  |  |       current_revision = formula.revision | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       fv = FormulaVersions.new(formula) | 
					
						
							|  |  |  |       fv.rev_list("origin/HEAD") do |revision, path| | 
					
						
							|  |  |  |         begin | 
					
						
							|  |  |  |           fv.formula_at_revision(revision, path) do |f| | 
					
						
							|  |  |  |             stable = f.stable | 
					
						
							|  |  |  |             next if stable.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             @previous_committed[:version] = stable.version | 
					
						
							|  |  |  |             @previous_committed[:checksum] = stable.checksum | 
					
						
							|  |  |  |             @previous_committed[:version_scheme] = f.version_scheme | 
					
						
							|  |  |  |             @previous_committed[:revision] = f.revision | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             @newest_committed[:version] ||= @previous_committed[:version] | 
					
						
							|  |  |  |             @newest_committed[:checksum] ||= @previous_committed[:checksum] | 
					
						
							|  |  |  |             @newest_committed[:revision] ||= @previous_committed[:revision] | 
					
						
							|  |  |  |             @newest_committed[:url] ||= stable.url | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         rescue MacOSVersion::Error | 
					
						
							|  |  |  |           break | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         break if @previous_committed[:version] && current_version != @previous_committed[:version] | 
					
						
							|  |  |  |         break if @previous_committed[:revision] && current_revision != @previous_committed[:revision] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       @previous_committed.compact! | 
					
						
							|  |  |  |       @newest_committed.compact! | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       [@previous_committed, @newest_committed] | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-11-18 10:25:12 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | end |