2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2015-05-24 16:14:44 +01:00
|
|
|
require "formula"
|
2016-01-14 13:33:56 +08:00
|
|
|
require "formula_versions"
|
2017-12-03 14:02:55 +01:00
|
|
|
require "utils/curl"
|
2015-05-24 16:14:44 +01:00
|
|
|
require "extend/ENV"
|
|
|
|
require "formula_cellar_checks"
|
2015-05-31 18:40:28 +08:00
|
|
|
require "cmd/search"
|
2018-06-05 23:19:18 -04:00
|
|
|
require "style"
|
2015-07-09 15:28:27 +01:00
|
|
|
require "date"
|
2017-03-18 17:02:08 +02:00
|
|
|
require "missing_formula"
|
2017-02-02 21:25:29 +00:00
|
|
|
require "digest"
|
2019-04-17 18:25:08 +09:00
|
|
|
require "cli/parser"
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2014-06-18 22:41:47 -05:00
|
|
|
module Homebrew
|
2016-09-26 01:44:51 +02:00
|
|
|
module_function
|
|
|
|
|
2018-07-30 18:25:38 +05:30
|
|
|
def audit_args
|
|
|
|
Homebrew::CLI::Parser.new do
|
2018-09-08 22:21:04 +05:30
|
|
|
usage_banner <<~EOS
|
2019-08-06 14:17:17 -04:00
|
|
|
`audit` [<options>] [<formula>]
|
2018-06-28 09:28:19 +05:30
|
|
|
|
2019-01-30 21:33:03 +00:00
|
|
|
Check <formula> for Homebrew coding style violations. This should be run before
|
2019-08-20 00:04:14 -04:00
|
|
|
submitting a new formula. If no <formula> are provided, check all locally
|
|
|
|
available formulae. Will exit with a non-zero status if any errors are
|
2018-10-08 22:49:03 -04:00
|
|
|
found, which can be useful for implementing pre-commit hooks.
|
2018-06-28 09:28:19 +05:30
|
|
|
EOS
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--strict",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Run additional style checks, including RuboCop style checks."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--online",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Run additional slower style checks that require a network connection."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--new-formula",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Run various additional style checks to determine if a new formula is eligible "\
|
|
|
|
"for Homebrew. This should be used when creating new formula and implies "\
|
|
|
|
"`--strict` and `--online`."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--fix",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Fix style violations automatically using RuboCop's auto-correct feature."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--display-cop-names",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Include the RuboCop cop name for each violation in the output."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "--display-filename",
|
2019-08-06 14:22:24 -04:00
|
|
|
description: "Prefix every line of output with the file or formula name being audited, to "\
|
2019-04-30 08:44:35 +01:00
|
|
|
"make output easy to grep."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch "-D", "--audit-debug",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Enable debugging and profiling of audit methods."
|
2018-09-22 09:31:30 +05:30
|
|
|
comma_array "--only",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Specify a comma-separated <method> list to only run the methods named "\
|
|
|
|
"`audit_`<method>."
|
2018-09-22 09:31:30 +05:30
|
|
|
comma_array "--except",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Specify a comma-separated <method> list to skip running the methods named "\
|
|
|
|
"`audit_`<method>."
|
2018-09-22 09:31:30 +05:30
|
|
|
comma_array "--only-cops",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Specify a comma-separated <cops> list to check for violations of only the listed "\
|
|
|
|
"RuboCop cops."
|
2018-09-22 09:31:30 +05:30
|
|
|
comma_array "--except-cops",
|
2019-04-30 08:44:35 +01:00
|
|
|
description: "Specify a comma-separated <cops> list to skip checking for violations of the listed "\
|
|
|
|
"RuboCop cops."
|
2018-09-22 09:31:30 +05:30
|
|
|
switch :verbose
|
|
|
|
switch :debug
|
2018-10-08 22:49:03 -04:00
|
|
|
conflicts "--only", "--except"
|
2019-12-11 14:59:47 -05:00
|
|
|
conflicts "--only-cops", "--except-cops", "--strict"
|
|
|
|
conflicts "--only-cops", "--except-cops", "--only"
|
2018-03-25 17:48:22 +05:30
|
|
|
end
|
2018-07-30 18:25:38 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def audit
|
|
|
|
audit_args.parse
|
2018-02-04 22:09:35 +05:30
|
|
|
|
2017-09-02 12:38:18 +05:30
|
|
|
Homebrew.auditing = true
|
2018-02-04 22:09:35 +05:30
|
|
|
inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug?
|
2016-04-18 17:39:21 -04:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
formula_count = 0
|
|
|
|
problem_count = 0
|
2018-08-26 19:33:19 +05:30
|
|
|
corrected_problem_count = 0
|
2018-05-22 18:16:46 +05:30
|
|
|
new_formula_problem_count = 0
|
2018-02-04 22:09:35 +05:30
|
|
|
new_formula = args.new_formula?
|
|
|
|
strict = new_formula || args.strict?
|
|
|
|
online = new_formula || args.online?
|
2015-07-09 12:31:17 +01:00
|
|
|
|
2013-08-19 13:03:41 -05:00
|
|
|
ENV.activate_extensions!
|
2013-05-07 18:39:45 -05:00
|
|
|
ENV.setup_build_environment
|
|
|
|
|
2020-03-04 17:28:24 +00:00
|
|
|
if args.no_named?
|
2016-04-18 17:39:21 -04:00
|
|
|
ff = Formula
|
|
|
|
files = Tap.map(&:formula_dir)
|
2012-08-07 01:37:46 -05:00
|
|
|
else
|
2020-03-04 17:28:24 +00:00
|
|
|
ff = args.resolved_formulae
|
2020-04-14 14:21:17 +01:00
|
|
|
files = args.formulae_paths
|
2016-04-18 17:39:21 -04:00
|
|
|
end
|
2016-08-16 17:00:31 +01:00
|
|
|
|
2018-02-04 22:09:35 +05:30
|
|
|
only_cops = args.only_cops
|
|
|
|
except_cops = args.except_cops
|
2019-10-04 23:39:11 +02:00
|
|
|
options = { fix: args.fix? }
|
2017-05-03 11:33:00 +05:30
|
|
|
|
2018-02-04 22:09:35 +05:30
|
|
|
if only_cops
|
2017-05-03 11:33:00 +05:30
|
|
|
options[:only_cops] = only_cops
|
2018-02-04 22:09:35 +05:30
|
|
|
elsif args.new_formula?
|
2017-07-28 05:01:01 +05:30
|
|
|
nil
|
2018-02-04 22:09:35 +05:30
|
|
|
elsif except_cops
|
2017-05-03 11:33:00 +05:30
|
|
|
options[:except_cops] = except_cops
|
|
|
|
elsif !strict
|
2017-07-30 22:01:07 +05:30
|
|
|
options[:only_cops] = [:FormulaAudit]
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2014-12-27 12:38:04 +00:00
|
|
|
|
2017-04-23 04:09:13 +05:30
|
|
|
# Check style in a single batch run up front for performance
|
2018-06-05 23:19:18 -04:00
|
|
|
style_results = Style.check_style_json(files, options)
|
2017-03-29 02:07:53 +05:30
|
|
|
|
2018-04-30 01:22:04 +05:30
|
|
|
new_formula_problem_lines = []
|
2018-01-07 13:36:16 +10:00
|
|
|
ff.sort.each do |f|
|
2018-10-30 23:44:14 +05:30
|
|
|
only = only_cops ? ["style"] : args.only
|
|
|
|
options = { new_formula: new_formula, strict: strict, online: online, only: only, except: args.except }
|
2017-03-29 02:07:53 +05:30
|
|
|
options[:style_offenses] = style_results.file_offenses(f.path)
|
2019-09-02 10:48:19 +01:00
|
|
|
options[:display_cop_names] = args.display_cop_names?
|
|
|
|
|
2016-04-18 17:39:21 -04:00
|
|
|
fa = FormulaAuditor.new(f, options)
|
2012-08-07 01:37:46 -05:00
|
|
|
fa.audit
|
2018-05-22 17:02:20 +05:30
|
|
|
next if fa.problems.empty? && fa.new_formula_problems.empty?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2016-08-25 07:00:39 -07:00
|
|
|
fa.problems
|
2016-04-18 17:39:21 -04:00
|
|
|
formula_count += 1
|
|
|
|
problem_count += fa.problems.size
|
2018-04-30 01:22:04 +05:30
|
|
|
problem_lines = format_problem_lines(fa.problems)
|
2018-08-28 23:09:44 +05:30
|
|
|
corrected_problem_count = options[:style_offenses].count(&:corrected?)
|
2018-04-30 01:22:04 +05:30
|
|
|
new_formula_problem_lines = format_problem_lines(fa.new_formula_problems)
|
2018-02-04 22:09:35 +05:30
|
|
|
if args.display_filename?
|
2016-05-16 18:46:47 +01:00
|
|
|
puts problem_lines.map { |s| "#{f.path}: #{s}" }
|
2016-05-11 09:19:45 -07:00
|
|
|
else
|
2016-05-16 18:46:47 +01:00
|
|
|
puts "#{f.full_name}:", problem_lines.map { |s| " #{s}" }
|
2016-05-11 09:19:45 -07:00
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
|
|
|
|
2018-05-21 15:36:26 +01:00
|
|
|
created_pr_comment = false
|
|
|
|
if new_formula && !new_formula_problem_lines.empty?
|
2018-04-30 01:22:04 +05:30
|
|
|
begin
|
2019-02-19 13:11:32 +00:00
|
|
|
created_pr_comment = true if GitHub.create_issue_comment(new_formula_problem_lines.join("\n"))
|
2018-07-23 19:32:07 +01:00
|
|
|
rescue *GitHub.api_errors => e
|
|
|
|
opoo "Unable to create issue comment: #{e.message}"
|
2018-04-30 01:22:04 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-21 15:36:26 +01:00
|
|
|
unless created_pr_comment
|
2018-05-22 18:16:46 +05:30
|
|
|
new_formula_problem_count += new_formula_problem_lines.size
|
2018-04-30 01:22:04 +05:30
|
|
|
puts new_formula_problem_lines.map { |s| " #{s}" }
|
|
|
|
end
|
|
|
|
|
2018-05-22 18:16:46 +05:30
|
|
|
total_problems_count = problem_count + new_formula_problem_count
|
2018-09-17 20:11:11 +02:00
|
|
|
problem_plural = "#{total_problems_count} #{"problem".pluralize(total_problems_count)}"
|
|
|
|
formula_plural = "#{formula_count} #{"formula".pluralize(formula_count)}"
|
|
|
|
corrected_problem_plural = "#{corrected_problem_count} #{"problem".pluralize(corrected_problem_count)}"
|
2018-08-26 19:33:19 +05:30
|
|
|
errors_summary = "#{problem_plural} in #{formula_plural} detected"
|
2019-02-19 13:11:32 +00:00
|
|
|
errors_summary += ", #{corrected_problem_plural} corrected" if corrected_problem_count.positive?
|
2018-05-22 18:16:46 +05:30
|
|
|
|
2018-05-22 14:52:02 +01:00
|
|
|
if problem_count.positive? ||
|
|
|
|
(new_formula_problem_count.positive? && !created_pr_comment)
|
|
|
|
ofail errors_summary
|
|
|
|
end
|
2018-04-30 01:22:04 +05:30
|
|
|
end
|
2016-09-22 20:12:28 +02:00
|
|
|
|
2018-04-30 01:22:04 +05:30
|
|
|
def format_problem_lines(problems)
|
2019-01-22 13:30:24 +00:00
|
|
|
problems.uniq.map { |p| "* #{p.chomp.gsub("\n", "\n ")}" }
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
class FormulaText
|
|
|
|
def initialize(path)
|
|
|
|
@text = path.open("rb", &:read)
|
|
|
|
@lines = @text.lines.to_a
|
|
|
|
end
|
2010-07-23 21:31:32 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def without_patch
|
|
|
|
@text.split("\n__END__").first
|
|
|
|
end
|
2011-11-29 19:37:39 -06:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def data?
|
|
|
|
/^[^#]*\bDATA\b/ =~ @text
|
|
|
|
end
|
2010-08-15 15:19:19 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def end?
|
|
|
|
/^__END__$/ =~ @text
|
|
|
|
end
|
2010-09-08 09:07:59 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def trailing_newline?
|
|
|
|
/\Z\n/ =~ @text
|
|
|
|
end
|
2015-02-24 15:54:31 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def =~(other)
|
|
|
|
other =~ @text
|
|
|
|
end
|
2015-06-08 18:57:17 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def include?(s)
|
|
|
|
@text.include? s
|
|
|
|
end
|
2016-07-13 18:28:40 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def line_number(regex, skip = 0)
|
|
|
|
index = @lines.drop(skip).index { |line| line =~ regex }
|
|
|
|
index ? index + 1 : nil
|
|
|
|
end
|
2016-08-25 07:00:39 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def reverse_line_number(regex)
|
|
|
|
index = @lines.reverse.index { |line| line =~ regex }
|
|
|
|
index ? @lines.count - index : nil
|
|
|
|
end
|
2016-08-25 07:00:39 -07:00
|
|
|
end
|
2010-09-07 14:34:39 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
class FormulaAuditor
|
|
|
|
include FormulaCellarChecks
|
|
|
|
|
2018-04-30 01:22:04 +05:30
|
|
|
attr_reader :formula, :text, :problems, :new_formula_problems
|
2012-08-07 01:37:46 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def initialize(formula, options = {})
|
|
|
|
@formula = formula
|
2018-10-19 11:16:04 +10:00
|
|
|
@versioned_formula = formula.versioned_formula?
|
2018-10-24 13:54:05 +10:00
|
|
|
@new_formula_inclusive = options[:new_formula]
|
2018-10-19 11:16:04 +10:00
|
|
|
@new_formula = options[:new_formula] && !@versioned_formula
|
2018-04-22 17:27:44 +02:00
|
|
|
@strict = options[:strict]
|
|
|
|
@online = options[:online]
|
|
|
|
@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]
|
2020-01-08 15:38:48 -05:00
|
|
|
# Allow the formula tap to be set as homebrew/core, for testing purposes
|
2018-10-11 17:37:43 +10:00
|
|
|
@core_tap = formula.tap&.core_tap? || options[:core_tap]
|
2018-04-22 17:27:44 +02:00
|
|
|
@problems = []
|
2018-04-30 01:22:04 +05:30
|
|
|
@new_formula_problems = []
|
2018-04-22 17:27:44 +02:00
|
|
|
@text = FormulaText.new(formula.path)
|
|
|
|
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact
|
2016-04-18 17:39:21 -04:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_style
|
|
|
|
return unless @style_offenses
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
@style_offenses.each do |offense|
|
|
|
|
problem offense.to_s(display_cop_name: @display_cop_names)
|
|
|
|
end
|
2016-08-25 07:00:39 -07:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_file
|
|
|
|
actual_mode = formula.path.stat.mode
|
2020-02-24 00:24:23 +00:00
|
|
|
# Check that the file is world-readable.
|
|
|
|
if actual_mode & 0444 != 0444
|
|
|
|
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
|
2018-09-02 20:14:54 +01:00
|
|
|
actual: actual_mode & 0777,
|
2020-02-24 00:24:23 +00:00
|
|
|
wanted: "+r",
|
|
|
|
path: formula.path)
|
|
|
|
end
|
|
|
|
# Check that the file is user-writeable.
|
|
|
|
if actual_mode & 0200 != 0200
|
|
|
|
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
|
|
|
|
actual: actual_mode & 0777,
|
|
|
|
wanted: "u+w",
|
|
|
|
path: formula.path)
|
|
|
|
end
|
|
|
|
# Check that the file is *not* other-writeable.
|
|
|
|
if actual_mode & 0002 == 002
|
|
|
|
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
|
|
|
|
actual: actual_mode & 0777,
|
|
|
|
wanted: "o-w",
|
2018-09-02 20:14:54 +01:00
|
|
|
path: formula.path)
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2016-08-25 07:00:39 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "'DATA' was found, but no '__END__'" if text.data? && !text.end?
|
2016-08-25 07:00:39 -07:00
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "'__END__' was found, but 'DATA' is not used" if text.end? && !text.data?
|
2016-08-25 07:00:39 -07:00
|
|
|
|
2019-10-13 19:26:39 +01:00
|
|
|
if text.to_s.match?(/inreplace [^\n]* do [^\n]*\n[^\n]*\.gsub![^\n]*\n\ *end/m)
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "'inreplace ... do' was used for a single substitution (use the non-block form instead)."
|
|
|
|
end
|
|
|
|
|
|
|
|
problem "File should end with a newline" unless text.trailing_newline?
|
2016-08-25 07:00:39 -07:00
|
|
|
|
2018-07-17 06:48:31 -04:00
|
|
|
if formula.core_formula? && @versioned_formula
|
2018-04-22 17:27:44 +02:00
|
|
|
unversioned_formula = begin
|
|
|
|
# build this ourselves as we want e.g. homebrew/core to be present
|
|
|
|
full_name = if formula.tap
|
|
|
|
"#{formula.tap}/#{formula.name}"
|
|
|
|
else
|
|
|
|
formula.name
|
|
|
|
end
|
|
|
|
Formulary.factory(full_name.gsub(/@.*$/, "")).path
|
|
|
|
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
|
2020-03-29 19:36:51 +01:00
|
|
|
elsif Homebrew.args.build_stable? && formula.stable? &&
|
2018-05-12 14:35:50 -04:00
|
|
|
!(versioned_formulae = formula.versioned_formulae).empty?
|
2018-04-22 17:27:44 +02:00
|
|
|
versioned_aliases = formula.aliases.grep(/.@\d/)
|
2018-05-12 14:35:50 -04:00
|
|
|
_, last_alias_version = versioned_formulae.map(&:name).last.split("@")
|
2018-04-22 17:27:44 +02:00
|
|
|
major, minor, = formula.version.to_s.split(".")
|
|
|
|
alias_name_major = "#{formula.name}@#{major}"
|
|
|
|
alias_name_major_minor = "#{alias_name_major}.#{minor}"
|
|
|
|
alias_name = if last_alias_version.split(".").length == 1
|
|
|
|
alias_name_major
|
2017-04-22 13:00:36 +01:00
|
|
|
else
|
2018-04-22 17:27:44 +02:00
|
|
|
alias_name_major_minor
|
2017-04-22 13:00:36 +01:00
|
|
|
end
|
2018-04-22 17:27:44 +02:00
|
|
|
valid_alias_names = [alias_name_major, alias_name_major_minor]
|
2017-05-09 15:10:29 +01:00
|
|
|
|
2018-10-11 17:37:43 +10:00
|
|
|
unless @core_tap
|
2018-04-22 17:27:44 +02:00
|
|
|
versioned_aliases.map! { |a| "#{formula.tap}/#{a}" }
|
|
|
|
valid_alias_names.map! { |a| "#{formula.tap}/#{a}" }
|
|
|
|
end
|
2017-05-17 15:18:59 -04:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
valid_versioned_aliases = versioned_aliases & valid_alias_names
|
|
|
|
invalid_versioned_aliases = versioned_aliases - valid_alias_names
|
|
|
|
|
|
|
|
if valid_versioned_aliases.empty?
|
|
|
|
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
|
2017-05-09 15:10:29 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
unless invalid_versioned_aliases.empty?
|
2017-10-15 02:28:32 +02:00
|
|
|
problem <<~EOS
|
2018-04-22 17:27:44 +02:00
|
|
|
Formula has invalid versioned aliases:
|
|
|
|
#{invalid_versioned_aliases.join("\n ")}
|
2017-05-09 15:10:29 +01:00
|
|
|
EOS
|
2017-03-26 20:36:37 +01:00
|
|
|
end
|
2017-05-09 15:10:29 +01:00
|
|
|
end
|
2017-02-23 09:14:54 +00:00
|
|
|
end
|
2015-05-31 18:40:28 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def self.aliases
|
|
|
|
# core aliases + tap alias names + tap alias full name
|
|
|
|
@aliases ||= Formula.aliases + Formula.tap_aliases
|
2016-11-20 00:40:54 -05:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_formula_name
|
|
|
|
return unless @strict
|
2018-10-11 17:37:43 +10:00
|
|
|
return unless @core_tap
|
2015-05-31 18:40:28 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
name = formula.name
|
2015-08-09 14:48:12 +03:00
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "'#{name}' is blacklisted from homebrew/core." if MissingFormula.blacklisted_reason(name)
|
2015-09-07 18:54:44 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if Formula.aliases.include? name
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Formula name conflicts with existing aliases in homebrew/core."
|
2018-04-22 17:27:44 +02:00
|
|
|
return
|
|
|
|
end
|
2015-05-31 18:40:28 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if oldname = CoreTap.instance.formula_renames[name]
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "'#{name}' is reserved as the old name of #{oldname} in homebrew/core."
|
2018-04-22 17:27:44 +02:00
|
|
|
return
|
|
|
|
end
|
2014-10-17 00:11:46 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return if formula.core_formula?
|
|
|
|
return unless Formula.core_names.include?(name)
|
|
|
|
|
|
|
|
problem "Formula name conflicts with existing core formula."
|
|
|
|
end
|
|
|
|
|
2020-04-11 14:43:38 +01:00
|
|
|
USES_FROM_MACOS_WHITELIST = %w[
|
|
|
|
apr
|
|
|
|
apr-util
|
|
|
|
openblas
|
|
|
|
openssl@1.1
|
|
|
|
].freeze
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_deps
|
|
|
|
@specs.each do |spec|
|
|
|
|
# Check for things we don't like to depend on.
|
|
|
|
# We allow non-Homebrew installs whenever possible.
|
|
|
|
spec.deps.each do |dep|
|
|
|
|
begin
|
|
|
|
dep_f = dep.to_formula
|
|
|
|
rescue TapFormulaUnavailableError
|
|
|
|
# Don't complain about missing cross-tap dependencies
|
|
|
|
next
|
|
|
|
rescue FormulaUnavailableError
|
|
|
|
problem "Can't find dependency #{dep.name.inspect}."
|
|
|
|
next
|
|
|
|
rescue TapFormulaAmbiguityError
|
|
|
|
problem "Ambiguous dependency #{dep.name.inspect}."
|
|
|
|
next
|
|
|
|
rescue TapFormulaWithOldnameAmbiguityError
|
|
|
|
problem "Ambiguous oldname dependency #{dep.name.inspect}."
|
|
|
|
next
|
|
|
|
end
|
2015-08-09 14:48:12 +03:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if dep_f.oldname && dep.name.split("/").last == dep_f.oldname
|
|
|
|
problem "Dependency '#{dep.name}' was renamed; use new name '#{dep_f.name}'."
|
|
|
|
end
|
2014-10-17 00:07:35 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if self.class.aliases.include?(dep.name) &&
|
|
|
|
(dep_f.core_formula? || !dep_f.versioned_formula?)
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Dependency '#{dep.name}' from homebrew/core is an alias; " \
|
|
|
|
"use the canonical name '#{dep.to_formula.full_name}'."
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2017-03-19 20:45:21 +02:00
|
|
|
|
2019-03-02 12:59:26 -08:00
|
|
|
if @new_formula &&
|
2020-04-13 14:19:58 +10:00
|
|
|
dep_f.keg_only? &&
|
2020-04-11 14:15:42 +01:00
|
|
|
dep_f.keg_only_reason.provided_by_macos? &&
|
|
|
|
dep_f.keg_only_reason.applicable? &&
|
2020-04-11 14:43:38 +01:00
|
|
|
!USES_FROM_MACOS_WHITELIST.include?(dep.name)
|
2018-09-02 16:15:09 +01:00
|
|
|
new_formula_problem(
|
2019-10-10 17:29:57 +02:00
|
|
|
"Dependency '#{dep.name}' is provided by macOS; " \
|
|
|
|
"please replace 'depends_on' with 'uses_from_macos'.",
|
2018-09-02 16:15:09 +01:00
|
|
|
)
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
dep.options.each do |opt|
|
2019-01-22 13:30:24 +00:00
|
|
|
next if @core_tap
|
2018-04-22 17:27:44 +02:00
|
|
|
next if dep_f.option_defined?(opt)
|
2018-09-02 20:14:54 +01:00
|
|
|
next if dep_f.requirements.find do |r|
|
2018-04-22 17:27:44 +02:00
|
|
|
if r.recommended?
|
|
|
|
opt.name == "with-#{r.name}"
|
|
|
|
elsif r.optional?
|
|
|
|
opt.name == "without-#{r.name}"
|
|
|
|
end
|
2014-10-17 00:07:35 -05:00
|
|
|
end
|
2018-04-22 17:27:44 +02:00
|
|
|
|
|
|
|
problem "Dependency #{dep} does not define option #{opt.name.inspect}"
|
2014-02-16 22:35:14 +00:00
|
|
|
end
|
2017-10-07 00:31:28 +02:00
|
|
|
|
2020-01-08 12:12:26 +00:00
|
|
|
problem "Don't use git as a dependency (it's always available)" if @new_formula && dep.name == "git"
|
2013-01-23 00:26:31 -06:00
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "Dependency '#{dep.name}' is marked as :run. Remove :run; it is a no-op." if dep.tags.include?(:run)
|
2018-05-22 19:39:10 +10:00
|
|
|
|
2018-10-11 17:37:43 +10:00
|
|
|
next unless @core_tap
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-05-22 19:39:10 +10:00
|
|
|
if dep.tags.include?(:recommended) || dep.tags.include?(:optional)
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Formulae in homebrew/core should not have optional or recommended dependencies"
|
2018-05-22 19:39:10 +10:00
|
|
|
end
|
2018-03-19 10:11:08 +00:00
|
|
|
end
|
2018-07-11 23:21:41 +10:00
|
|
|
|
2018-10-11 17:37:43 +10:00
|
|
|
next unless @core_tap
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-07-11 23:21:41 +10:00
|
|
|
if spec.requirements.map(&:recommended?).any? || spec.requirements.map(&:optional?).any?
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Formulae in homebrew/core should not have optional or recommended requirements"
|
2018-07-11 23:21:41 +10:00
|
|
|
end
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
|
|
|
end
|
2018-03-19 10:11:08 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_conflicts
|
|
|
|
formula.conflicts.each do |c|
|
2019-10-13 10:03:26 +01:00
|
|
|
Formulary.factory(c.name)
|
|
|
|
rescue TapFormulaUnavailableError
|
|
|
|
# Don't complain about missing cross-tap conflicts.
|
|
|
|
next
|
|
|
|
rescue FormulaUnavailableError
|
|
|
|
problem "Can't find conflicting formula #{c.name.inspect}."
|
|
|
|
rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
|
|
|
|
problem "Ambiguous conflicting formula #{c.name.inspect}."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
|
|
|
end
|
2010-09-13 15:16:09 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_keg_only_style
|
|
|
|
return unless formula.keg_only?
|
|
|
|
|
|
|
|
whitelist = %w[
|
|
|
|
Apple
|
|
|
|
macOS
|
|
|
|
OS
|
|
|
|
Homebrew
|
|
|
|
Xcode
|
|
|
|
GPG
|
|
|
|
GNOME
|
|
|
|
BSD
|
|
|
|
Firefox
|
|
|
|
].freeze
|
|
|
|
|
|
|
|
reason = formula.keg_only_reason.to_s
|
|
|
|
# Formulae names can legitimately be uppercase/lowercase/both.
|
|
|
|
name = Regexp.new(formula.name, Regexp::IGNORECASE)
|
|
|
|
reason.sub!(name, "")
|
2018-09-17 19:44:12 +02:00
|
|
|
first_word = reason.split.first
|
2018-04-22 17:27:44 +02:00
|
|
|
|
|
|
|
if reason =~ /\A[A-Z]/ && !reason.start_with?(*whitelist)
|
|
|
|
problem <<~EOS
|
|
|
|
'#{first_word}' from the keg_only reason should be '#{first_word.downcase}'.
|
|
|
|
EOS
|
2013-01-03 11:22:31 -08:00
|
|
|
end
|
2010-08-07 15:23:13 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless reason.end_with?(".")
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "keg_only reason should not end with a period."
|
2017-04-28 04:53:52 +01:00
|
|
|
end
|
|
|
|
|
2018-12-09 19:31:19 +00:00
|
|
|
def audit_postgresql
|
|
|
|
return unless formula.name == "postgresql"
|
2019-01-22 13:30:24 +00:00
|
|
|
return unless @core_tap
|
|
|
|
|
2018-12-09 19:31:19 +00:00
|
|
|
major_version = formula.version
|
|
|
|
.to_s
|
|
|
|
.split(".")
|
|
|
|
.first
|
|
|
|
.to_i
|
|
|
|
previous_major_version = major_version - 1
|
|
|
|
previous_formula_name = "postgresql@#{previous_major_version}"
|
|
|
|
begin
|
|
|
|
Formula[previous_formula_name]
|
|
|
|
rescue FormulaUnavailableError
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Versioned #{previous_formula_name} in homebrew/core must be created for " \
|
2018-12-09 19:31:19 +00:00
|
|
|
"`brew-postgresql-upgrade-database` and `pg_upgrade` to work."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-04-11 14:43:38 +01:00
|
|
|
VERSIONED_KEG_ONLY_WHITELIST = %w[
|
|
|
|
autoconf@2.13
|
|
|
|
bash-completion@2
|
|
|
|
gnupg@1.4
|
|
|
|
lua@5.1
|
|
|
|
numpy@1.16
|
|
|
|
libsigc++@2
|
|
|
|
].freeze
|
|
|
|
|
2018-08-26 13:57:21 +10:00
|
|
|
def audit_versioned_keg_only
|
2018-10-19 11:16:04 +10:00
|
|
|
return unless @versioned_formula
|
2018-08-26 13:57:21 +10:00
|
|
|
return unless @core_tap
|
|
|
|
|
2018-12-07 10:27:17 +00:00
|
|
|
if formula.keg_only?
|
2020-04-11 14:15:42 +01:00
|
|
|
return if formula.keg_only_reason.versioned_formula?
|
2018-12-07 10:27:17 +00:00
|
|
|
if formula.name.start_with?("openssl", "libressl") &&
|
2020-04-11 14:15:42 +01:00
|
|
|
formula.keg_only_reason.provided_by_macos?
|
2018-12-07 10:27:17 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
2018-08-26 13:57:21 +10:00
|
|
|
|
2020-04-11 14:43:38 +01:00
|
|
|
return if VERSIONED_KEG_ONLY_WHITELIST.include?(formula.name) || formula.name.start_with?("gcc@")
|
2018-08-26 13:57:21 +10:00
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Versioned formulae in homebrew/core should use `keg_only :versioned_formula`"
|
2018-08-26 13:57:21 +10:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_homepage
|
|
|
|
homepage = formula.homepage
|
2017-04-28 04:53:52 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return if homepage.nil? || homepage.empty?
|
2014-09-14 15:43:20 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless @online
|
2017-02-26 16:49:09 +11:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless DevelopmentTools.curl_handles_most_https_certificates?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if http_content_problem = curl_check_http_content(homepage,
|
2019-04-30 08:44:35 +01:00
|
|
|
user_agents: [:browser, :default],
|
|
|
|
check_content: true,
|
|
|
|
strict: @strict)
|
2018-04-22 17:27:44 +02:00
|
|
|
problem http_content_problem
|
|
|
|
end
|
|
|
|
end
|
2016-12-23 20:29:56 +00:00
|
|
|
|
2018-10-24 13:54:05 +10:00
|
|
|
def audit_bottle_spec
|
|
|
|
# special case: new versioned formulae should be audited
|
|
|
|
return unless @new_formula_inclusive
|
|
|
|
return unless @core_tap
|
|
|
|
|
|
|
|
return if formula.bottle_disabled?
|
|
|
|
|
|
|
|
return unless formula.bottle_defined?
|
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
new_formula_problem "New formulae in homebrew/core should not have a `bottle do` block"
|
2018-10-24 13:54:05 +10:00
|
|
|
end
|
|
|
|
|
2018-07-04 13:19:16 +10:00
|
|
|
def audit_bottle_disabled
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless formula.bottle_disabled?
|
2018-07-04 13:19:16 +10:00
|
|
|
return if formula.bottle_unneeded?
|
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "Unrecognized bottle modifier" unless formula.bottle_disable_reason.valid?
|
2019-01-22 13:30:24 +00:00
|
|
|
|
|
|
|
return unless @core_tap
|
2019-02-19 13:12:52 +00:00
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Formulae in homebrew/core should not use `bottle :disabled`"
|
2018-07-04 13:19:16 +10:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_github_repository
|
2019-07-02 14:50:02 +02:00
|
|
|
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*})
|
|
|
|
return if user.nil?
|
2015-07-08 14:22:44 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
begin
|
|
|
|
metadata = GitHub.repository(user, repo)
|
|
|
|
rescue GitHub::HTTPNotFoundError
|
|
|
|
return
|
|
|
|
end
|
2015-07-08 15:19:36 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return if metadata.nil?
|
2015-07-08 15:19:36 +01:00
|
|
|
|
2018-04-30 01:22:04 +05:30
|
|
|
new_formula_problem "GitHub fork (not canonical repository)" if metadata["fork"]
|
2019-01-22 13:30:24 +00:00
|
|
|
if (metadata["forks_count"] < 30) && (metadata["subscribers_count"] < 30) &&
|
2018-06-01 08:30:03 +10:00
|
|
|
(metadata["stargazers_count"] < 75)
|
|
|
|
new_formula_problem "GitHub repository not notable enough (<30 forks, <30 watchers and <75 stars)"
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2016-03-13 01:40:00 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return if Date.parse(metadata["created_at"]) <= (Date.today - 30)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-30 01:22:04 +05:30
|
|
|
new_formula_problem "GitHub repository too new (<30 days old)"
|
2015-07-08 14:22:44 +01:00
|
|
|
end
|
|
|
|
|
2019-07-02 14:50:02 +02:00
|
|
|
def audit_gitlab_repository
|
|
|
|
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*})
|
|
|
|
return if user.nil?
|
|
|
|
|
|
|
|
out, _, status= curl_output("--request", "GET", "https://gitlab.com/api/v4/projects/#{user}%2F#{repo}")
|
|
|
|
return unless status.success?
|
|
|
|
|
|
|
|
metadata = JSON.parse(out)
|
|
|
|
return if metadata.nil?
|
|
|
|
|
|
|
|
new_formula_problem "GitLab fork (not canonical repository)" if metadata["fork"]
|
|
|
|
if (metadata["forks_count"] < 30) && (metadata["star_count"] < 75)
|
|
|
|
new_formula_problem "GitLab repository not notable enough (<30 forks and <75 stars)"
|
|
|
|
end
|
|
|
|
|
|
|
|
return if Date.parse(metadata["created_at"]) <= (Date.today - 30)
|
|
|
|
|
|
|
|
new_formula_problem "GitLab repository too new (<30 days old)"
|
|
|
|
end
|
|
|
|
|
2019-09-05 20:27:00 +02:00
|
|
|
def audit_bitbucket_repository
|
|
|
|
user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*})
|
|
|
|
return if user.nil?
|
|
|
|
|
|
|
|
api_url = "https://api.bitbucket.org/2.0/repositories/#{user}/#{repo}"
|
|
|
|
out, _, status= curl_output("--request", "GET", api_url)
|
|
|
|
return unless status.success?
|
|
|
|
|
|
|
|
metadata = JSON.parse(out)
|
|
|
|
return if metadata.nil?
|
|
|
|
|
|
|
|
new_formula_problem "Uses deprecated mercurial support in Bitbucket" if metadata["scm"] == "hg"
|
|
|
|
|
2019-11-04 10:37:00 +11:00
|
|
|
new_formula_problem "Bitbucket fork (not canonical repository)" unless metadata["parent"].nil?
|
2019-09-05 20:27:00 +02:00
|
|
|
|
|
|
|
if Date.parse(metadata["created_on"]) >= (Date.today - 30)
|
|
|
|
new_formula_problem "Bitbucket repository too new (<30 days old)"
|
|
|
|
end
|
|
|
|
|
|
|
|
forks_out, _, forks_status= curl_output("--request", "GET", "#{api_url}/forks")
|
|
|
|
return unless forks_status.success?
|
|
|
|
|
|
|
|
watcher_out, _, watcher_status= curl_output("--request", "GET", "#{api_url}/watchers")
|
|
|
|
return unless watcher_status.success?
|
|
|
|
|
|
|
|
forks_metadata = JSON.parse(forks_out)
|
|
|
|
return if forks_metadata.nil?
|
|
|
|
|
|
|
|
watcher_metadata = JSON.parse(watcher_out)
|
|
|
|
return if watcher_metadata.nil?
|
|
|
|
|
|
|
|
return if (forks_metadata["size"] < 30) && (watcher_metadata["size"] < 75)
|
|
|
|
|
|
|
|
new_formula_problem "Bitbucket repository not notable enough (<30 forks and <75 watchers)"
|
|
|
|
end
|
|
|
|
|
2019-07-02 14:50:02 +02:00
|
|
|
def get_repo_data(regex)
|
|
|
|
return unless @core_tap
|
|
|
|
return unless @online
|
|
|
|
return unless @new_formula
|
|
|
|
|
|
|
|
_, user, repo = *regex.match(formula.stable.url) if formula.stable
|
|
|
|
_, user, repo = *regex.match(formula.homepage) unless user
|
|
|
|
return if !user || !repo
|
|
|
|
|
|
|
|
repo.gsub!(/.git$/, "")
|
|
|
|
|
|
|
|
[user, repo]
|
|
|
|
end
|
|
|
|
|
2020-04-11 14:43:38 +01:00
|
|
|
VERSIONED_HEAD_SPEC_WHITELIST = %w[
|
|
|
|
bash-completion@2
|
|
|
|
imagemagick@6
|
|
|
|
].freeze
|
|
|
|
|
|
|
|
THROTTLED_BLACKLIST = {
|
|
|
|
"aws-sdk-cpp" => "10",
|
|
|
|
"awscli@1" => "10",
|
|
|
|
"quicktype" => "10",
|
|
|
|
"vim" => "50",
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
UNSTABLE_WHITELIST = {
|
|
|
|
"aalib" => "1.4rc",
|
|
|
|
"automysqlbackup" => "3.0-rc",
|
|
|
|
"aview" => "1.3.0rc",
|
|
|
|
"elm-format" => "0.6.0-alpha",
|
|
|
|
"ftgl" => "2.1.3-rc",
|
|
|
|
"hidapi" => "0.8.0-rc",
|
|
|
|
"libcaca" => "0.99b",
|
|
|
|
"premake" => "4.4-beta",
|
|
|
|
"pwnat" => "0.3-beta",
|
|
|
|
"recode" => "3.7-beta",
|
|
|
|
"speexdsp" => "1.2rc",
|
|
|
|
"sqoop" => "1.4.",
|
|
|
|
"tcptraceroute" => "1.5beta",
|
|
|
|
"tiny-fugue" => "5.0b",
|
|
|
|
"vbindiff" => "3.0_beta",
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
GNOME_DEVEL_WHITELIST = {
|
|
|
|
"libart" => "2.3",
|
|
|
|
"gtk-mac-integration" => "2.1",
|
|
|
|
"gtk-doc" => "1.31",
|
|
|
|
"gcab" => "1.3",
|
|
|
|
"libepoxy" => "1.5",
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
# version_prefix = stable_version_string.sub(/\d+$/, "")
|
|
|
|
# version_prefix = stable_version_string.split(".")[0..1].join(".")
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_specs
|
2019-01-22 13:30:24 +00:00
|
|
|
problem "Head-only (no stable download)" if head_only?(formula)
|
|
|
|
problem "Devel-only (no stable download)" if devel_only?(formula)
|
2016-01-14 13:33:56 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
%w[Stable Devel HEAD].each do |name|
|
|
|
|
spec_name = name.downcase.to_sym
|
|
|
|
next unless spec = formula.send(spec_name)
|
2017-05-10 20:45:34 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
ra = ResourceAuditor.new(spec, spec_name, online: @online, strict: @strict).audit
|
|
|
|
problems.concat ra.problems.map { |problem| "#{name}: #{problem}" }
|
2017-05-10 20:45:34 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
spec.resources.each_value do |resource|
|
2019-11-26 00:58:35 +00:00
|
|
|
problem "Resource name should be different from the formula name" if resource.name == formula.name
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
ra = ResourceAuditor.new(resource, spec_name, online: @online, strict: @strict).audit
|
|
|
|
problems.concat ra.problems.map { |problem|
|
|
|
|
"#{name} resource #{resource.name.inspect}: #{problem}"
|
|
|
|
}
|
|
|
|
end
|
2016-08-18 12:55:19 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
next if spec.patches.empty?
|
2018-05-23 07:36:59 +10:00
|
|
|
next unless @new_formula
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-09-02 16:15:09 +01:00
|
|
|
new_formula_problem(
|
|
|
|
"Formulae should not require patches to build. " \
|
|
|
|
"Patches should be submitted and accepted upstream first.",
|
|
|
|
)
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2016-11-13 13:35:25 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
%w[Stable Devel].each do |name|
|
|
|
|
next unless spec = formula.send(name.downcase)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
version = spec.version
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "#{name}: version (#{version}) is set to a string without a digit" if version.to_s !~ /\d/
|
2018-04-22 17:27:44 +02:00
|
|
|
if version.to_s.start_with?("HEAD")
|
|
|
|
problem "#{name}: non-HEAD version name (#{version}) should not begin with HEAD"
|
|
|
|
end
|
|
|
|
end
|
2016-11-13 13:35:25 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if formula.stable && formula.devel
|
|
|
|
if formula.devel.version < formula.stable.version
|
|
|
|
problem "devel version #{formula.devel.version} is older than stable version #{formula.stable.version}"
|
|
|
|
elsif formula.devel.version == formula.stable.version
|
|
|
|
problem "stable and devel versions are identical"
|
|
|
|
end
|
2017-04-23 18:56:22 +01:00
|
|
|
end
|
2016-11-15 09:02:50 +00:00
|
|
|
|
2019-01-22 13:30:24 +00:00
|
|
|
return unless @core_tap
|
2018-10-11 23:07:51 +10:00
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "Formulae in homebrew/core should not have a `devel` spec" if formula.devel
|
2019-01-22 13:30:24 +00:00
|
|
|
|
2019-03-02 10:56:27 +01:00
|
|
|
if formula.head && @versioned_formula
|
2018-10-11 23:07:51 +10:00
|
|
|
head_spec_message = "Formulae should not have a `HEAD` spec"
|
2020-04-11 14:43:38 +01:00
|
|
|
problem head_spec_message unless VERSIONED_HEAD_SPEC_WHITELIST.include?(formula.name)
|
2016-11-13 13:35:25 +00:00
|
|
|
end
|
2017-04-23 18:56:22 +01:00
|
|
|
|
2020-04-11 14:43:38 +01:00
|
|
|
THROTTLED_BLACKLIST.each do |f, v|
|
2018-06-15 20:07:02 -04:00
|
|
|
next if formula.stable.nil?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-06-07 16:34:39 +10:00
|
|
|
version = formula.stable.version.to_s.split(".").last.to_i
|
2020-04-11 14:43:38 +01:00
|
|
|
if f == formula.name && version.modulo(v.to_i).nonzero?
|
|
|
|
problem "should only be updated every #{v} releases on multiples of #{v}"
|
2018-06-07 16:34:39 +10:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
stable = formula.stable
|
2019-06-10 09:01:58 +01:00
|
|
|
return unless stable
|
|
|
|
return unless stable.url
|
|
|
|
|
|
|
|
stable_version_string = stable.version.to_s
|
|
|
|
stable_url_version = Version.parse(stable.url)
|
|
|
|
_, stable_url_minor_version, = stable_url_version.to_s
|
|
|
|
.split(".", 3)
|
|
|
|
.map(&:to_i)
|
|
|
|
|
2020-04-12 14:51:49 +01:00
|
|
|
case (url = stable.url)
|
2018-04-22 17:27:44 +02:00
|
|
|
when /[\d\._-](alpha|beta|rc\d)/
|
|
|
|
matched = Regexp.last_match(1)
|
2019-06-10 09:01:58 +01:00
|
|
|
version_prefix = stable_version_string.sub(/\d+$/, "")
|
2020-04-11 14:43:38 +01:00
|
|
|
return if UNSTABLE_WHITELIST[formula.name] == version_prefix
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Stable version URLs should not contain #{matched}"
|
|
|
|
when %r{download\.gnome\.org/sources}, %r{ftp\.gnome\.org/pub/GNOME/sources}i
|
2019-06-10 09:01:58 +01:00
|
|
|
version_prefix = stable_version_string.split(".")[0..1].join(".")
|
2020-04-11 14:43:38 +01:00
|
|
|
return if GNOME_DEVEL_WHITELIST[formula.name] == version_prefix
|
2019-06-10 09:01:58 +01:00
|
|
|
return if stable_url_version < Version.create("1.0")
|
|
|
|
return if stable_url_minor_version.even?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2019-06-10 09:01:58 +01:00
|
|
|
problem "#{stable.version} is a development release"
|
|
|
|
when %r{isc.org/isc/bind\d*/}i
|
|
|
|
return if stable_url_minor_version.even?
|
2019-05-30 09:34:27 +02:00
|
|
|
|
2019-06-10 09:01:58 +01:00
|
|
|
problem "#{stable.version} is a development release"
|
2020-04-12 14:51:49 +01:00
|
|
|
when %r{^https://github.com/([\w-]+)/([\w-]+)/}
|
|
|
|
owner = Regexp.last_match(1)
|
|
|
|
repo = Regexp.last_match(2)
|
|
|
|
tag = url.match(%r{^https://github\.com/[\w-]+/[\w-]+/archive/([^/]+)\.(tar\.gz|zip)$})
|
|
|
|
.to_a
|
|
|
|
.second
|
|
|
|
tag ||= url.match(%r{^https://github\.com/[\w-]+/[\w-]+/releases/download/([^/]+)/})
|
|
|
|
.to_a
|
|
|
|
.second
|
|
|
|
|
2020-04-02 00:13:58 +09:00
|
|
|
begin
|
2020-04-12 14:51:49 +01:00
|
|
|
if (release = GitHub.open_api("#{GitHub::API_URL}/repos/#{owner}/#{repo}/releases/tags/#{tag}"))
|
|
|
|
problem "#{tag} is a GitHub prerelease" if release["prerelease"]
|
2020-04-02 00:13:58 +09:00
|
|
|
end
|
2020-04-12 14:51:49 +01:00
|
|
|
rescue GitHub::HTTPNotFoundError
|
|
|
|
# No-op if we can't find the release.
|
|
|
|
nil
|
2020-03-28 16:57:47 +09:00
|
|
|
end
|
2019-05-30 09:34:27 +02:00
|
|
|
end
|
2016-10-30 13:49:55 -04:00
|
|
|
end
|
2016-09-22 20:12:28 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_revision_and_version_scheme
|
|
|
|
return unless formula.tap # skip formula not from core or any taps
|
|
|
|
return unless formula.tap.git? # git log is required
|
|
|
|
return if @new_formula
|
|
|
|
|
|
|
|
fv = FormulaVersions.new(formula)
|
2017-04-26 11:33:13 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
previous_version_and_checksum = fv.previous_version_and_checksum("origin/master")
|
|
|
|
[:stable, :devel].each do |spec_sym|
|
|
|
|
next unless spec = formula.send(spec_sym)
|
|
|
|
next unless previous_version_and_checksum[spec_sym][:version] == spec.version
|
|
|
|
next if previous_version_and_checksum[spec_sym][:checksum] == spec.checksum
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-09-02 16:15:09 +01:00
|
|
|
problem(
|
|
|
|
"#{spec_sym}: sha256 changed without the version also changing; " \
|
|
|
|
"please create an issue upstream to rule out malicious " \
|
|
|
|
"circumstances and to find out why the file changed.",
|
|
|
|
)
|
2017-04-26 11:33:13 +01:00
|
|
|
end
|
2017-04-23 18:56:22 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
attributes = [:revision, :version_scheme]
|
|
|
|
attributes_map = fv.version_attributes_map(attributes, "origin/master")
|
|
|
|
|
|
|
|
current_version_scheme = formula.version_scheme
|
|
|
|
[:stable, :devel].each do |spec|
|
|
|
|
spec_version_scheme_map = attributes_map[:version_scheme][spec]
|
|
|
|
next if spec_version_scheme_map.empty?
|
|
|
|
|
|
|
|
version_schemes = spec_version_scheme_map.values.flatten
|
|
|
|
max_version_scheme = version_schemes.max
|
|
|
|
max_version = spec_version_scheme_map.select do |_, version_scheme|
|
|
|
|
version_scheme.first == max_version_scheme
|
|
|
|
end.keys.max
|
|
|
|
|
|
|
|
if max_version_scheme && current_version_scheme < max_version_scheme
|
|
|
|
problem "version_scheme should not decrease (from #{max_version_scheme} to #{current_version_scheme})"
|
|
|
|
end
|
|
|
|
|
|
|
|
if max_version_scheme && current_version_scheme >= max_version_scheme &&
|
|
|
|
current_version_scheme > 1 &&
|
|
|
|
!version_schemes.include?(current_version_scheme - 1)
|
|
|
|
problem "version_schemes should only increment by 1"
|
|
|
|
end
|
|
|
|
|
|
|
|
formula_spec = formula.send(spec)
|
|
|
|
next unless formula_spec
|
|
|
|
|
|
|
|
spec_version = formula_spec.version
|
|
|
|
next unless max_version
|
|
|
|
next if spec_version >= max_version
|
|
|
|
|
|
|
|
above_max_version_scheme = current_version_scheme > max_version_scheme
|
2018-06-06 23:34:19 -04:00
|
|
|
map_includes_version = spec_version_scheme_map.key?(spec_version)
|
2018-04-22 17:27:44 +02:00
|
|
|
next if !current_version_scheme.zero? &&
|
|
|
|
(above_max_version_scheme || map_includes_version)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "#{spec} version should not decrease (from #{max_version} to #{spec_version})"
|
2016-01-15 16:17:14 +08:00
|
|
|
end
|
2016-01-14 13:33:56 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
current_revision = formula.revision
|
|
|
|
revision_map = attributes_map[:revision][:stable]
|
|
|
|
if formula.stable && !revision_map.empty?
|
|
|
|
stable_revisions = revision_map[formula.stable.version]
|
|
|
|
stable_revisions ||= []
|
|
|
|
max_revision = stable_revisions.max || 0
|
|
|
|
|
|
|
|
if current_revision < max_revision
|
|
|
|
problem "revision should not decrease (from #{max_revision} to #{current_revision})"
|
2016-11-02 13:23:38 -04:00
|
|
|
end
|
2018-04-22 17:27:44 +02:00
|
|
|
|
|
|
|
stable_revisions -= [formula.revision]
|
|
|
|
if !current_revision.zero? && stable_revisions.empty? &&
|
|
|
|
revision_map.keys.length > 1
|
|
|
|
problem "'revision #{formula.revision}' should be removed"
|
|
|
|
elsif current_revision > 1 &&
|
|
|
|
current_revision != max_revision &&
|
|
|
|
!stable_revisions.include?(current_revision - 1)
|
|
|
|
problem "revisions should only increment by 1"
|
|
|
|
end
|
|
|
|
elsif !current_revision.zero? # head/devel-only formula
|
|
|
|
problem "'revision #{current_revision}' should be removed"
|
2016-10-24 15:07:49 +01:00
|
|
|
end
|
|
|
|
end
|
2013-07-16 23:15:22 -05:00
|
|
|
|
2018-04-22 17:27:44 +02: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?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
bin_names += dir.children.map(&:basename).map(&:to_s)
|
|
|
|
end
|
|
|
|
bin_names.each do |name|
|
|
|
|
["system", "shell_output", "pipe_output"].each do |cmd|
|
2019-10-13 19:26:39 +01:00
|
|
|
if text.to_s.match?(/test do.*#{cmd}[\(\s]+['"]#{Regexp.escape(name)}[\s'"]/m)
|
2019-04-08 12:47:15 -04:00
|
|
|
problem %Q(fully scope test #{cmd} calls, e.g. #{cmd} "\#{bin}/#{name}")
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-04-18 08:17:24 +01:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_lines
|
|
|
|
text.without_patch.split("\n").each_with_index do |line, lineno|
|
|
|
|
line_problems(line, lineno + 1)
|
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def line_problems(line, _lineno)
|
|
|
|
# Check for string interpolation of single values.
|
|
|
|
if line =~ /(system|inreplace|gsub!|change_make_var!).*[ ,]"#\{([\w.]+)\}"/
|
|
|
|
problem "Don't need to interpolate \"#{Regexp.last_match(2)}\" with #{Regexp.last_match(1)}"
|
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
# Check for string concatenation; prefer interpolation
|
|
|
|
if line =~ /(#\{\w+\s*\+\s*['"][^}]+\})/
|
|
|
|
problem "Try not to concatenate paths in string interpolation:\n #{Regexp.last_match(1)}"
|
|
|
|
end
|
2012-06-18 19:58:35 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
# Prefer formula path shortcuts in Pathname+
|
|
|
|
if line =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share|Frameworks)[/'"])}
|
2018-09-02 16:15:09 +01:00
|
|
|
problem(
|
|
|
|
"\"(#{Regexp.last_match(1)}...#{Regexp.last_match(2)})\" should" \
|
|
|
|
" be \"(#{Regexp.last_match(3).downcase}+...)\"",
|
|
|
|
)
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Use separate make calls" if line.include?("make && make")
|
2015-05-24 16:27:54 +08:00
|
|
|
|
2019-11-03 18:25:06 +01:00
|
|
|
if line =~ /JAVA_HOME/i &&
|
|
|
|
[formula.name, *formula.deps.map(&:name)].none? { |name| name.match?(/^openjdk(@|$)/) } &&
|
|
|
|
formula.requirements.none? { |req| req.is_a?(JavaRequirement) }
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Use `depends_on :java` to set JAVA_HOME"
|
|
|
|
end
|
2014-12-27 14:01:43 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless @strict
|
2018-01-09 15:26:54 +00:00
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "`env :userpaths` in formulae is deprecated" if line.include?("env :userpaths")
|
2016-01-09 16:32:23 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if line =~ /system ((["'])[^"' ]*(?:\s[^"' ]*)+\2)/
|
|
|
|
bad_system = Regexp.last_match(1)
|
|
|
|
unless %w[| < > & ; *].any? { |c| bad_system.include? c }
|
|
|
|
good_system = bad_system.gsub(" ", "\", \"")
|
|
|
|
problem "Use `system #{good_system}` instead of `system #{bad_system}` "
|
|
|
|
end
|
|
|
|
end
|
2016-01-09 16:32:23 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "`#{Regexp.last_match(1)}` is now unnecessary" if line =~ /(require ["']formula["'])/
|
|
|
|
|
2019-10-13 19:26:39 +01:00
|
|
|
if line.match?(%r{#\{share\}/#{Regexp.escape(formula.name)}[/'"]})
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Use \#{pkgshare} instead of \#{share}/#{formula.name}"
|
|
|
|
end
|
|
|
|
|
2019-01-22 13:30:24 +00:00
|
|
|
if !@core_tap && line =~ /depends_on .+ if build\.with(out)?\?\(?["']\w+["']\)?/
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "`Use :optional` or `:recommended` instead of `#{Regexp.last_match(0)}`"
|
|
|
|
end
|
2016-09-22 20:12:28 +02:00
|
|
|
|
2019-11-13 15:52:58 -05:00
|
|
|
if line =~ %r{share(\s*[/+]\s*)(['"])#{Regexp.escape(formula.name)}(?:\2|/)}
|
|
|
|
problem "Use pkgshare instead of (share#{Regexp.last_match(1)}\"#{formula.name}\")"
|
|
|
|
end
|
2019-01-22 13:30:24 +00:00
|
|
|
|
|
|
|
return unless @core_tap
|
|
|
|
|
2020-01-08 15:38:48 -05:00
|
|
|
problem "`env :std` in homebrew/core formulae is deprecated" if line.include?("env :std")
|
2018-04-07 20:41:25 +01:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_reverse_migration
|
2018-10-11 17:37:43 +10:00
|
|
|
# Only enforce for new formula being re-added to core
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless @strict
|
2018-10-11 17:37:43 +10:00
|
|
|
return unless @core_tap
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless formula.tap.tap_migrations.key?(formula.name)
|
2013-07-16 20:38:50 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
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
|
2015-06-17 04:58:32 +01:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_prefix_has_contents
|
|
|
|
return unless formula.prefix.directory?
|
|
|
|
return unless Keg.new(formula.prefix).empty_installation?
|
2015-02-02 22:53:52 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
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
|
2015-02-02 22:53:52 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def quote_dep(dep)
|
|
|
|
dep.is_a?(Symbol) ? dep.inspect : "'#{dep}'"
|
|
|
|
end
|
2013-07-15 19:29:08 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def problem_if_output(output)
|
|
|
|
problem(output) if output
|
2017-04-18 08:17:24 +01:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit
|
|
|
|
only_audits = @only
|
|
|
|
except_audits = @except
|
2010-11-09 13:00:33 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
methods.map(&:to_s).grep(/^audit_/).each do |audit_method_name|
|
|
|
|
name = audit_method_name.gsub(/^audit_/, "")
|
|
|
|
if only_audits
|
|
|
|
next unless only_audits.include?(name)
|
|
|
|
elsif except_audits
|
|
|
|
next if except_audits.include?(name)
|
|
|
|
end
|
|
|
|
send(audit_method_name)
|
|
|
|
end
|
|
|
|
end
|
2011-05-31 13:23:42 -07:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
private
|
2014-11-12 21:30:09 -06:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def problem(p)
|
|
|
|
@problems << p
|
2018-04-30 01:22:04 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def new_formula_problem(p)
|
|
|
|
@new_formula_problems << p
|
2018-04-22 17:27:44 +02:00
|
|
|
end
|
2015-01-24 23:36:33 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def head_only?(formula)
|
|
|
|
formula.head && formula.devel.nil? && formula.stable.nil?
|
|
|
|
end
|
2013-09-18 18:08:50 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def devel_only?(formula)
|
|
|
|
formula.devel && formula.stable.nil?
|
|
|
|
end
|
2013-09-18 18:08:50 -05:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
class ResourceAuditor
|
|
|
|
attr_reader :name, :version, :checksum, :url, :mirrors, :using, :specs, :owner
|
|
|
|
attr_reader :spec_name, :problems
|
2013-09-18 18:08:50 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def initialize(resource, spec_name, options = {})
|
|
|
|
@name = resource.name
|
|
|
|
@version = resource.version
|
|
|
|
@checksum = resource.checksum
|
|
|
|
@url = resource.url
|
|
|
|
@mirrors = resource.mirrors
|
|
|
|
@using = resource.using
|
|
|
|
@specs = resource.specs
|
|
|
|
@owner = resource.owner
|
|
|
|
@spec_name = spec_name
|
|
|
|
@online = options[:online]
|
|
|
|
@strict = options[:strict]
|
|
|
|
@problems = []
|
2013-09-18 18:08:50 -05:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit
|
|
|
|
audit_version
|
|
|
|
audit_download_strategy
|
|
|
|
audit_urls
|
|
|
|
self
|
2013-09-18 18:08:50 -05:00
|
|
|
end
|
2015-04-19 14:31:19 +08:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_version
|
|
|
|
if version.nil?
|
|
|
|
problem "missing version"
|
2018-09-14 17:02:19 +01:00
|
|
|
elsif version.blank?
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "version is set to an empty string"
|
|
|
|
elsif !version.detected_from_url?
|
|
|
|
version_text = version
|
|
|
|
version_url = Version.detect(url, specs)
|
|
|
|
if version_url.to_s == version_text.to_s && version.instance_of?(Version)
|
|
|
|
problem "version #{version_text} is redundant with version scanned from URL"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "version #{version} should not have a leading 'v'" if version.to_s.start_with?("v")
|
2013-09-18 18:08:50 -05:00
|
|
|
|
2019-10-13 19:26:39 +01:00
|
|
|
return unless version.to_s.match?(/_\d+$/)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "version #{version} should not end with an underline and a number"
|
2014-12-18 18:04:22 -05:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_download_strategy
|
|
|
|
if url =~ %r{^(cvs|bzr|hg|fossil)://} || url =~ %r{^(svn)\+http://}
|
|
|
|
problem "Use of the #{$&} scheme is deprecated, pass `:using => :#{Regexp.last_match(1)}` instead"
|
|
|
|
end
|
2015-03-07 15:02:25 +00:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
url_strategy = DownloadStrategyDetector.detect(url)
|
|
|
|
|
|
|
|
if using == :git || url_strategy == GitDownloadStrategy
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "Git should specify :revision when a :tag is specified." if specs[:tag] && !specs[:revision]
|
2015-03-07 15:02:25 +00:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless using
|
2013-09-18 18:08:50 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if using == :cvs
|
|
|
|
mod = specs[:module]
|
2014-12-22 00:43:02 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Redundant :module value in URL" if mod == name
|
2014-12-22 00:43:02 -05:00
|
|
|
|
2019-10-13 19:26:39 +01:00
|
|
|
if url.match?(%r{:[^/]+$})
|
2018-04-22 17:27:44 +02:00
|
|
|
mod = url.split(":").last
|
2014-12-22 00:43:02 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
if mod == name
|
|
|
|
problem "Redundant CVS module appended to URL"
|
|
|
|
else
|
|
|
|
problem "Specify CVS module as `:module => \"#{mod}\"` instead of appending it to the URL"
|
|
|
|
end
|
2014-12-22 00:43:02 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
return unless url_strategy == DownloadStrategyDetector.detect("", using)
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
problem "Redundant :using value in URL"
|
|
|
|
end
|
2013-09-18 18:08:50 -05:00
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def self.curl_openssl_and_deps
|
|
|
|
@curl_openssl_and_deps ||= begin
|
|
|
|
formulae_names = ["curl", "openssl"]
|
|
|
|
formulae_names += formulae_names.flat_map do |f|
|
|
|
|
Formula[f].recursive_dependencies.map(&:name)
|
|
|
|
end
|
|
|
|
formulae_names.uniq
|
|
|
|
rescue FormulaUnavailableError
|
|
|
|
[]
|
2017-09-14 19:58:37 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def audit_urls
|
|
|
|
return unless @online
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2019-01-27 16:25:40 +00:00
|
|
|
urls = [url] + mirrors
|
2018-04-22 17:27:44 +02:00
|
|
|
urls.each do |url|
|
|
|
|
next if !@strict && mirrors.include?(url)
|
|
|
|
|
|
|
|
strategy = DownloadStrategyDetector.detect(url, using)
|
|
|
|
if strategy <= CurlDownloadStrategy && !url.start_with?("file")
|
|
|
|
# A `brew mirror`'ed URL is usually not yet reachable at the time of
|
|
|
|
# pull request.
|
2019-10-13 19:26:39 +01:00
|
|
|
next if url.match?(%r{^https://dl.bintray.com/homebrew/mirror/})
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2019-01-28 08:10:37 +01:00
|
|
|
if http_content_problem = curl_check_http_content(url)
|
2018-04-22 17:27:44 +02:00
|
|
|
problem http_content_problem
|
|
|
|
end
|
|
|
|
elsif strategy <= GitDownloadStrategy
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "The URL #{url} is not a valid git URL" unless Utils.git_remote_exists? url
|
2018-04-22 17:27:44 +02:00
|
|
|
elsif strategy <= SubversionDownloadStrategy
|
|
|
|
next unless DevelopmentTools.subversion_handles_most_https_certificates?
|
|
|
|
next unless Utils.svn_available?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2019-02-19 13:11:32 +00:00
|
|
|
problem "The URL #{url} is not a valid svn URL" unless Utils.svn_remote_exists? url
|
2016-12-10 14:20:47 +00:00
|
|
|
end
|
2016-12-08 21:41:24 +00:00
|
|
|
end
|
2017-02-02 21:25:29 +00:00
|
|
|
end
|
|
|
|
|
2018-04-22 17:27:44 +02:00
|
|
|
def problem(text)
|
|
|
|
@problems << text
|
|
|
|
end
|
2013-09-18 18:08:50 -05:00
|
|
|
end
|
|
|
|
end
|