Merge pull request #3835 from GauthamGoli/bot-comment

audit:  `--new-formula`, Don't fail CI instead comment on PR about audit violations
This commit is contained in:
Mike McQuaid 2018-05-22 10:26:13 +01:00 committed by GitHub
commit 3794d53575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 19 deletions

View File

@ -117,6 +117,7 @@ module Homebrew
# Check style in a single batch run up front for performance # Check style in a single batch run up front for performance
style_results = check_style_json(files, options) style_results = check_style_json(files, options)
new_formula_problem_lines = []
ff.sort.each do |f| ff.sort.each do |f|
options = { new_formula: new_formula, strict: strict, online: online, only: args.only, except: args.except } options = { new_formula: new_formula, strict: strict, online: online, only: args.only, except: args.except }
options[:style_offenses] = style_results.file_offenses(f.path) options[:style_offenses] = style_results.file_offenses(f.path)
@ -127,7 +128,8 @@ module Homebrew
fa.problems fa.problems
formula_count += 1 formula_count += 1
problem_count += fa.problems.size problem_count += fa.problems.size
problem_lines = fa.problems.map { |p| "* #{p.chomp.gsub("\n", "\n ")}" } problem_lines = format_problem_lines(fa.problems)
new_formula_problem_lines = format_problem_lines(fa.new_formula_problems)
if args.display_filename? if args.display_filename?
puts problem_lines.map { |s| "#{f.path}: #{s}" } puts problem_lines.map { |s| "#{f.path}: #{s}" }
else else
@ -135,9 +137,29 @@ module Homebrew
end end
end end
return if problem_count.zero? created_pr_comment = false
if new_formula && !new_formula_problem_lines.empty?
begin
if GitHub.create_issue_comment(new_formula_problem_lines.join("\n"))
created_pr_comment = true
end
rescue *GitHub.api_errors
nil
end
end
ofail "#{Formatter.pluralize(problem_count, "problem")} in #{Formatter.pluralize(formula_count, "formula")}" unless created_pr_comment
problem_count += new_formula_problem_lines.size
puts new_formula_problem_lines.map { |s| " #{s}" }
end
errors_summary = "#{Formatter.pluralize(problem_count, "problem")} in #{Formatter.pluralize(formula_count, "formula")}"
return if problem_count.zero?
ofail errors_summary unless strict
end
def format_problem_lines(problems)
problems.map { |p| "* #{p.chomp.gsub("\n", "\n ")}" }
end end
class FormulaText class FormulaText
@ -184,7 +206,7 @@ module Homebrew
class FormulaAuditor class FormulaAuditor
include FormulaCellarChecks include FormulaCellarChecks
attr_reader :formula, :text, :problems attr_reader :formula, :text, :problems, :new_formula_problems
def initialize(formula, options = {}) def initialize(formula, options = {})
@formula = formula @formula = formula
@ -199,6 +221,7 @@ module Homebrew
# Allow the actual official-ness of a formula to be overridden, for testing purposes # Allow the actual official-ness of a formula to be overridden, for testing purposes
@official_tap = formula.tap&.official? || options[:official_tap] @official_tap = formula.tap&.official? || options[:official_tap]
@problems = [] @problems = []
@new_formula_problems = []
@text = FormulaText.new(formula.path) @text = FormulaText.new(formula.path)
@specs = %w[stable devel head].map { |s| formula.send(s) }.compact @specs = %w[stable devel head].map { |s| formula.send(s) }.compact
end end
@ -206,6 +229,10 @@ module Homebrew
def audit_style def audit_style
return unless @style_offenses return unless @style_offenses
@style_offenses.each do |offense| @style_offenses.each do |offense|
if offense.cop_name.start_with?("NewFormulaAudit")
new_formula_problem offense.to_s(display_cop_name: @display_cop_names)
next
end
problem offense.to_s(display_cop_name: @display_cop_names) problem offense.to_s(display_cop_name: @display_cop_names)
end end
end end
@ -358,7 +385,7 @@ module Homebrew
if @new_formula && dep_f.keg_only_reason && if @new_formula && dep_f.keg_only_reason &&
!["openssl", "apr", "apr-util"].include?(dep.name) && !["openssl", "apr", "apr-util"].include?(dep.name) &&
[:provided_by_macos, :provided_by_osx].include?(dep_f.keg_only_reason.reason) [:provided_by_macos, :provided_by_osx].include?(dep_f.keg_only_reason.reason)
problem "Dependency '#{dep.name}' may be unnecessary as it is provided by macOS; try to build this formula without it." new_formula_problem "Dependency '#{dep.name}' may be unnecessary as it is provided by macOS; try to build this formula without it."
end end
dep.options.each do |opt| dep.options.each do |opt|
@ -473,15 +500,15 @@ module Homebrew
return if metadata.nil? return if metadata.nil?
problem "GitHub fork (not canonical repository)" if metadata["fork"] new_formula_problem "GitHub fork (not canonical repository)" if metadata["fork"]
if formula&.tap&.core_tap? && if formula&.tap&.core_tap? &&
(metadata["forks_count"] < 20) && (metadata["subscribers_count"] < 20) && (metadata["forks_count"] < 20) && (metadata["subscribers_count"] < 20) &&
(metadata["stargazers_count"] < 50) (metadata["stargazers_count"] < 50)
problem "GitHub repository not notable enough (<20 forks, <20 watchers and <50 stars)" new_formula_problem "GitHub repository not notable enough (<20 forks, <20 watchers and <50 stars)"
end end
return if Date.parse(metadata["created_at"]) <= (Date.today - 30) return if Date.parse(metadata["created_at"]) <= (Date.today - 30)
problem "GitHub repository too new (<30 days old)" new_formula_problem "GitHub repository too new (<30 days old)"
end end
def audit_specs def audit_specs
@ -508,8 +535,7 @@ module Homebrew
end end
next if spec.patches.empty? next if spec.patches.empty?
next unless @new_formula new_formula_problem "Formulae should not require patches to build. Patches should be submitted and accepted upstream first."
problem "New formulae should not require patches to build. Patches should be submitted and accepted upstream first."
end end
%w[Stable Devel].each do |name| %w[Stable Devel].each do |name|
@ -532,7 +558,7 @@ module Homebrew
end end
if @new_formula && formula.head if @new_formula && formula.head
problem "New formulae should not have a HEAD spec" new_formula_problem "Formulae should not have a HEAD spec"
end end
unstable_whitelist = %w[ unstable_whitelist = %w[
@ -813,6 +839,10 @@ module Homebrew
@problems << p @problems << p
end end
def new_formula_problem(p)
@new_formula_problems << p
end
def head_only?(formula) def head_only?(formula)
formula.head && formula.devel.nil? && formula.stable.nil? formula.head && formula.devel.nil? && formula.stable.nil?
end end

View File

@ -86,9 +86,6 @@ module Homebrew
ENV[env] = homebrew_env ENV[env] = homebrew_env
end end
gh_api_errors = [GitHub::AuthenticationFailedError, GitHub::HTTPNotFoundError,
GitHub::RateLimitExceededError, GitHub::Error, JSON::ParserError].freeze
formula = ARGV.formulae.first formula = ARGV.formulae.first
if formula if formula
@ -291,7 +288,7 @@ module Homebrew
response = GitHub.create_fork(formula.tap.full_name) response = GitHub.create_fork(formula.tap.full_name)
# GitHub API responds immediately but fork takes a few seconds to be ready. # GitHub API responds immediately but fork takes a few seconds to be ready.
sleep 3 sleep 3
rescue *gh_api_errors => e rescue *GitHub.api_errors => e
formula.path.atomic_write(backup_file) unless @bump_args.dry_run? formula.path.atomic_write(backup_file) unless @bump_args.dry_run?
odie "Unable to fork: #{e.message}!" odie "Unable to fork: #{e.message}!"
end end
@ -327,7 +324,7 @@ module Homebrew
else else
exec_browser url exec_browser url
end end
rescue *gh_api_errors => e rescue *GitHub.api_errors => e
odie "Unable to open pull request: #{e.message}!" odie "Unable to open pull request: #{e.message}!"
end end
end end

View File

@ -281,7 +281,7 @@ module Homebrew
fa.audit_deps fa.audit_deps
end end
its(:problems) { are_expected.to match([/unnecessary/]) } its(:new_formula_problems) { are_expected.to match([/unnecessary/]) }
end end
end end
end end

View File

@ -10,6 +10,8 @@ module GitHub
CREATE_ISSUE_FORK_OR_PR_SCOPES = ["public_repo"].freeze CREATE_ISSUE_FORK_OR_PR_SCOPES = ["public_repo"].freeze
ALL_SCOPES = (CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES).freeze ALL_SCOPES = (CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES).freeze
ALL_SCOPES_URL = Formatter.url("https://github.com/settings/tokens/new?scopes=#{ALL_SCOPES.join(",")}&description=Homebrew").freeze ALL_SCOPES_URL = Formatter.url("https://github.com/settings/tokens/new?scopes=#{ALL_SCOPES.join(",")}&description=Homebrew").freeze
PR_ENV_KEY = "HOMEBREW_NEW_FORMULA_PULL_REQUEST_URL".freeze
PR_ENV = ENV[PR_ENV_KEY]
Error = Class.new(RuntimeError) Error = Class.new(RuntimeError)
HTTPNotFoundError = Class.new(Error) HTTPNotFoundError = Class.new(Error)
@ -254,14 +256,14 @@ module GitHub
end end
def create_fork(repo) def create_fork(repo)
url = "https://api.github.com/repos/#{repo}/forks" url = "#{API_URL}/repos/#{repo}/forks"
data = {} data = {}
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
open_api(url, data: data, scopes: scopes) open_api(url, data: data, scopes: scopes)
end end
def create_pull_request(repo, title, head, base, body) def create_pull_request(repo, title, head, base, body)
url = "https://api.github.com/repos/#{repo}/pulls" url = "#{API_URL}/repos/#{repo}/pulls"
data = { title: title, head: head, base: base, body: body } data = { title: title, head: head, base: base, body: body }
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
open_api(url, data: data, scopes: scopes) open_api(url, data: data, scopes: scopes)
@ -291,4 +293,38 @@ module GitHub
uri.query = query_string(*queries, **qualifiers) uri.query = query_string(*queries, **qualifiers)
open_api(uri) { |json| json.fetch("items", []) } open_api(uri) { |json| json.fetch("items", []) }
end end
def create_issue_comment(body)
return false unless PR_ENV
_, user, repo, pr = *PR_ENV.match(HOMEBREW_PULL_OR_COMMIT_URL_REGEX)
if !user || !repo || !pr
opoo <<-EOS.undent
#{PR_ENV_KEY} set but regex matched:
user: #{user.inspect}, repo: #{repo.inspect}, pr: #{pr.inspect}
EOS
return false
end
url = "#{API_URL}/repos/#{user}/#{repo}/issues/#{pr}/comments"
data = { "body" => body }
if issue_comment_exists?(user, repo, pr, body)
ohai "Skipping: identical comment exists on #{PR_ENV}."
return true
end
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
open_api(url, data: data, scopes: scopes)
end
def issue_comment_exists?(user, repo, pr, body)
url = "#{API_URL}/repos/#{user}/#{repo}/issues/#{pr}/comments"
comments = open_api(url)
return unless comments
comments.any? { |comment| comment["body"].eql?(body) }
end
def api_errors
[GitHub::AuthenticationFailedError, GitHub::HTTPNotFoundError,
GitHub::RateLimitExceededError, GitHub::Error, JSON::ParserError].freeze
end
end end