2010-06-23 11:20:47 -07:00
|
|
|
require 'formula'
|
|
|
|
require 'utils'
|
|
|
|
|
2011-03-21 12:54:00 -07:00
|
|
|
# Use "brew audit --strict" to enable even stricter checks.
|
|
|
|
|
|
|
|
def strict?
|
|
|
|
ARGV.flag? "--strict"
|
|
|
|
end
|
|
|
|
|
2010-06-23 11:20:47 -07:00
|
|
|
def ff
|
2010-07-18 13:57:18 -07:00
|
|
|
return Formula.all if ARGV.named.empty?
|
|
|
|
return ARGV.formulae
|
2010-06-23 11:20:47 -07:00
|
|
|
end
|
|
|
|
|
2011-03-10 12:26:29 -08:00
|
|
|
def audit_formula_text name, text
|
2010-08-07 15:23:13 -07:00
|
|
|
problems = []
|
|
|
|
|
2011-06-06 07:25:00 -07:00
|
|
|
if text =~ /<(Formula|AmazonWebServicesFormula)/
|
|
|
|
problems << " * Use a space in class inheritance: class Foo < #{$1}"
|
|
|
|
end
|
2011-03-21 12:57:35 -07:00
|
|
|
|
2010-08-08 10:17:53 -07:00
|
|
|
# Commented-out cmake support from default template
|
2010-11-04 08:11:49 -07:00
|
|
|
if (text =~ /# depends_on 'cmake'/) or (text =~ /# system "cmake/)
|
2010-07-12 10:34:12 -07:00
|
|
|
problems << " * Commented cmake support found."
|
|
|
|
end
|
|
|
|
|
2010-09-07 14:34:39 -07:00
|
|
|
# 2 (or more in an if block) spaces before depends_on, please
|
2010-08-07 15:23:13 -07:00
|
|
|
if text =~ /^\ ?depends_on/
|
2010-07-23 21:31:32 -07:00
|
|
|
problems << " * Check indentation of 'depends_on'."
|
|
|
|
end
|
|
|
|
|
2010-08-15 15:19:19 -07:00
|
|
|
# FileUtils is included in Formula
|
|
|
|
if text =~ /FileUtils\.(\w+)/
|
|
|
|
problems << " * Don't need 'FileUtils.' before #{$1}."
|
|
|
|
end
|
|
|
|
|
2010-09-08 09:07:59 -07:00
|
|
|
# Check for long inreplace block vars
|
|
|
|
if text =~ /inreplace .* do \|(.{2,})\|/
|
|
|
|
problems << " * \"inreplace <filenames> do |s|\" is preferred over \"|#{$1}|\"."
|
|
|
|
end
|
|
|
|
|
2010-09-07 14:34:39 -07:00
|
|
|
# Check for string interpolation of single values.
|
|
|
|
if text =~ /(system|inreplace|gsub!|change_make_var!) .* ['"]#\{(\w+)\}['"]/
|
|
|
|
problems << " * Don't need to interpolate \"#{$2}\" with #{$1}"
|
|
|
|
end
|
|
|
|
|
2010-08-08 10:17:53 -07:00
|
|
|
# Check for string concatenation; prefer interpolation
|
2010-08-07 15:23:13 -07:00
|
|
|
if text =~ /(#\{\w+\s*\+\s*['"][^}]+\})/
|
2010-07-12 10:34:12 -07:00
|
|
|
problems << " * Try not to concatenate paths in string interpolation:\n #{$1}"
|
2010-06-23 11:20:47 -07:00
|
|
|
end
|
|
|
|
|
2010-08-08 10:17:53 -07:00
|
|
|
# Prefer formula path shortcuts in Pathname+
|
2011-09-28 14:28:14 -05:00
|
|
|
if text =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share))}
|
2010-08-08 10:17:53 -07:00
|
|
|
problems << " * \"(#{$1}...#{$2})\" should be \"(#{$3}+...)\""
|
|
|
|
end
|
|
|
|
|
2010-08-15 11:32:45 -07:00
|
|
|
if text =~ %r[((man)\s*\+\s*(['"])(man[1-8])(['"]))]
|
|
|
|
problems << " * \"#{$1}\" should be \"#{$4}\""
|
|
|
|
end
|
|
|
|
|
2010-08-08 10:17:53 -07:00
|
|
|
# Prefer formula path shortcuts in strings
|
2011-09-28 14:28:14 -05:00
|
|
|
if text =~ %r[(\#\{prefix\}/(bin|include|libexec|lib|sbin|share))]
|
2010-08-08 10:17:53 -07:00
|
|
|
problems << " * \"#{$1}\" should be \"\#{#{$2}}\""
|
|
|
|
end
|
|
|
|
|
2010-08-09 11:59:16 -07:00
|
|
|
if text =~ %r[((\#\{prefix\}/share/man/|\#\{man\}/)(man[1-8]))]
|
|
|
|
problems << " * \"#{$1}\" should be \"\#{#{$3}}\""
|
2010-08-08 10:17:53 -07:00
|
|
|
end
|
|
|
|
|
2010-09-08 09:22:48 -07:00
|
|
|
if text =~ %r[((\#\{share\}/(man)))[/'"]]
|
|
|
|
problems << " * \"#{$1}\" should be \"\#{#{$3}}\""
|
|
|
|
end
|
|
|
|
|
2010-08-08 10:17:53 -07:00
|
|
|
if text =~ %r[(\#\{prefix\}/share/(info|man))]
|
|
|
|
problems << " * \"#{$1}\" should be \"\#{#{$2}}\""
|
|
|
|
end
|
|
|
|
|
2010-08-08 18:25:56 -07:00
|
|
|
# Empty checksums
|
2010-09-14 16:13:57 -07:00
|
|
|
if text =~ /md5\s+(\'\'|\"\")/
|
2010-08-08 18:25:56 -07:00
|
|
|
problems << " * md5 is empty"
|
|
|
|
end
|
|
|
|
|
2010-09-14 16:13:57 -07:00
|
|
|
if text =~ /sha1\s+(\'\'|\"\")/
|
|
|
|
problems << " * sha1 is empty"
|
|
|
|
end
|
|
|
|
|
2011-11-02 18:55:27 -05:00
|
|
|
if text =~ /sha256\s+(\'\'|\"\")/
|
|
|
|
problems << " * sha256 is empty"
|
|
|
|
end
|
|
|
|
|
2010-09-13 15:16:09 -07:00
|
|
|
# Commented-out depends_on
|
|
|
|
if text =~ /#\s*depends_on\s+(.+)\s*$/
|
|
|
|
problems << " * Commented-out dep #{$1}."
|
|
|
|
end
|
|
|
|
|
2010-08-10 11:52:03 -07:00
|
|
|
# No trailing whitespace, please
|
2010-10-21 07:51:47 -07:00
|
|
|
if text =~ /(\t|[ ])+$/
|
2010-08-07 15:23:13 -07:00
|
|
|
problems << " * Trailing whitespace was found."
|
|
|
|
end
|
|
|
|
|
2010-10-08 20:11:40 -07:00
|
|
|
if text =~ /if\s+ARGV\.include\?\s+'--HEAD'/
|
|
|
|
problems << " * Use \"if ARGV.build_head?\" instead"
|
|
|
|
end
|
|
|
|
|
2010-10-30 08:26:36 -07:00
|
|
|
if text =~ /make && make/
|
|
|
|
problems << " * Use separate make calls."
|
|
|
|
end
|
|
|
|
|
2011-10-18 10:00:45 -07:00
|
|
|
if text =~ /^[ ]*\t/
|
2011-03-21 13:06:16 -07:00
|
|
|
problems << " * Use spaces instead of tabs for indentation"
|
2011-10-18 10:00:45 -07:00
|
|
|
end
|
2010-10-21 07:51:47 -07:00
|
|
|
|
2011-03-10 12:26:29 -08:00
|
|
|
# Formula depends_on gfortran
|
2011-04-01 12:30:02 -07:00
|
|
|
if text =~ /^\s*depends_on\s*(\'|\")gfortran(\'|\").*/
|
2011-03-10 12:26:29 -08:00
|
|
|
problems << " * Use ENV.fortran during install instead of depends_on 'gfortran'"
|
|
|
|
end unless name == "gfortran" # Gfortran itself has this text in the caveats
|
|
|
|
|
2011-03-21 12:54:00 -07:00
|
|
|
# xcodebuild should specify SYMROOT
|
|
|
|
if text =~ /xcodebuild/ and not text =~ /SYMROOT=/
|
|
|
|
problems << " * xcodebuild should be passed an explicit \"SYMROOT\""
|
|
|
|
end if strict?
|
|
|
|
|
2011-11-18 15:22:04 -06:00
|
|
|
# using ARGV.flag? for formula options is generally a bad thing
|
|
|
|
if text =~ /ARGV\.flag\?/
|
|
|
|
problems << " * Use 'ARGV.include?' instead of 'ARGV.flag?'"
|
|
|
|
end
|
|
|
|
|
2011-11-23 13:38:38 -06:00
|
|
|
# MacPorts patches should specify a revision, not trunk
|
|
|
|
if text =~ %r[macports/trunk]
|
|
|
|
problems << " * MacPorts patches should specify a revision instead of trunk"
|
|
|
|
end
|
|
|
|
|
2010-08-09 11:59:16 -07:00
|
|
|
return problems
|
|
|
|
end
|
|
|
|
|
2010-08-10 13:04:51 -07:00
|
|
|
def audit_formula_options f, text
|
|
|
|
problems = []
|
|
|
|
|
|
|
|
# Find possible options
|
|
|
|
options = []
|
2011-11-18 15:22:04 -06:00
|
|
|
text.scan(/ARGV\.include\?[ ]*\(?(['"])(.+?)\1/) { |m| options << m[1] }
|
2010-08-10 13:04:51 -07:00
|
|
|
options.reject! {|o| o.include? "#"}
|
|
|
|
options.uniq!
|
|
|
|
|
|
|
|
# Find documented options
|
|
|
|
begin
|
|
|
|
opts = f.options
|
|
|
|
documented_options = []
|
|
|
|
opts.each{ |o| documented_options << o[0] }
|
|
|
|
documented_options.reject! {|o| o.include? "="}
|
|
|
|
rescue
|
|
|
|
documented_options = []
|
|
|
|
end
|
|
|
|
|
|
|
|
if options.length > 0
|
|
|
|
options.each do |o|
|
2011-09-21 15:22:05 -05:00
|
|
|
next if o == '--HEAD' || o == '--devel'
|
2010-08-10 13:04:51 -07:00
|
|
|
problems << " * Option #{o} is not documented" unless documented_options.include? o
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if documented_options.length > 0
|
|
|
|
documented_options.each do |o|
|
2011-10-15 02:21:13 +01:00
|
|
|
next if o == '--universal' and text =~ /ARGV\.build_universal\?/
|
2010-08-10 13:04:51 -07:00
|
|
|
problems << " * Option #{o} is unused" unless options.include? o
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return problems
|
|
|
|
end
|
|
|
|
|
2011-06-16 20:15:38 -07:00
|
|
|
def audit_formula_version f, text
|
|
|
|
# Version as defined in the DSL (or nil)
|
|
|
|
version_text = f.class.send('version').to_s
|
2011-06-16 20:35:09 -07:00
|
|
|
|
2011-06-16 20:15:38 -07:00
|
|
|
# Version as determined from the URL
|
|
|
|
version_url = Pathname.new(f.url).version
|
|
|
|
|
|
|
|
if version_url == version_text
|
2011-06-16 20:35:09 -07:00
|
|
|
return [" * version #{version_text} is redundant with version scanned from url"]
|
2011-06-16 20:15:38 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
return []
|
|
|
|
end
|
|
|
|
|
2010-09-09 14:16:05 -07:00
|
|
|
def audit_formula_urls f
|
|
|
|
problems = []
|
|
|
|
|
2011-03-15 21:40:09 -07:00
|
|
|
unless f.homepage =~ %r[^https?://]
|
|
|
|
problems << " * The homepage should start with http or https."
|
|
|
|
end
|
|
|
|
|
2010-09-29 10:21:44 -07:00
|
|
|
urls = [(f.url rescue nil), (f.head rescue nil)].reject {|p| p.nil?}
|
2011-11-15 18:06:26 -06:00
|
|
|
urls.uniq! # head-only formulae result in duplicate entries
|
2010-09-29 10:21:44 -07:00
|
|
|
|
2011-10-15 00:39:05 -05:00
|
|
|
f.mirrors.each do |m|
|
|
|
|
mirror = m.values_at :url
|
|
|
|
urls << (mirror.to_s rescue nil)
|
|
|
|
end
|
|
|
|
|
2010-09-09 14:16:05 -07:00
|
|
|
# Check SourceForge urls
|
2010-09-29 10:21:44 -07:00
|
|
|
urls.each do |p|
|
2010-09-09 14:16:05 -07:00
|
|
|
# Is it a filedownload (instead of svnroot)
|
|
|
|
next if p =~ %r[/svnroot/]
|
|
|
|
next if p =~ %r[svn\.sourceforge]
|
|
|
|
|
|
|
|
# Is it a sourceforge http(s) URL?
|
2011-09-13 23:27:59 -05:00
|
|
|
next unless p =~ %r[^https?://.*\bsourceforge\.]
|
2010-09-09 14:16:05 -07:00
|
|
|
|
2011-10-02 15:46:09 -05:00
|
|
|
if p =~ /(\?|&)use_mirror=/
|
|
|
|
problems << " * Update this url (don't use #{$1}use_mirror)."
|
2010-09-09 14:16:05 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
if p =~ /\/download$/
|
|
|
|
problems << " * Update this url (don't use /download)."
|
|
|
|
end
|
|
|
|
|
|
|
|
if p =~ %r[^http://prdownloads\.]
|
|
|
|
problems << " * Update this url (don't use prdownloads)."
|
|
|
|
end
|
|
|
|
|
|
|
|
if p =~ %r[^http://\w+\.dl\.]
|
|
|
|
problems << " * Update this url (don't use specific dl mirrors)."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-06-13 14:20:55 -07:00
|
|
|
# Check for git:// urls; https:// is preferred.
|
|
|
|
urls.each do |p|
|
|
|
|
if p =~ %r[^git://github\.com/]
|
|
|
|
problems << " * Use https:// URLs for accessing repositories on GitHub."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-09-12 15:26:09 -05:00
|
|
|
# Check GNU urls
|
|
|
|
urls.each do |p|
|
2011-09-13 23:34:30 -05:00
|
|
|
if p =~ %r[^(https?|ftp)://(.+)/gnu/]
|
|
|
|
problems << " * \"ftpmirror.gnu.org\" is preferred for GNU software."
|
2011-09-12 15:26:09 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-09-09 14:16:05 -07:00
|
|
|
return problems
|
|
|
|
end
|
|
|
|
|
2010-08-21 11:55:57 -07:00
|
|
|
def audit_formula_instance f
|
|
|
|
problems = []
|
|
|
|
|
|
|
|
# Don't depend_on aliases; use full name
|
|
|
|
aliases = Formula.aliases
|
|
|
|
f.deps.select {|d| aliases.include? d}.each do |d|
|
|
|
|
problems << " * Dep #{d} is an alias; switch to the real name."
|
|
|
|
end
|
|
|
|
|
2010-09-07 09:23:29 -07:00
|
|
|
# Check for things we don't like to depend on.
|
|
|
|
# We allow non-Homebrew installs whenenever possible.
|
|
|
|
f.deps.each do |d|
|
2011-03-12 17:40:40 -08:00
|
|
|
begin
|
|
|
|
dep_f = Formula.factory d
|
|
|
|
rescue
|
|
|
|
problems << " * Can't find dependency \"#{d}\"."
|
|
|
|
end
|
|
|
|
|
2010-09-07 09:23:29 -07:00
|
|
|
case d
|
|
|
|
when "git"
|
|
|
|
problems << " * Don't use Git as a dependency; we allow non-Homebrew git installs."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-08-21 11:55:57 -07:00
|
|
|
# Google Code homepages should end in a slash
|
|
|
|
if f.homepage =~ %r[^https?://code\.google\.com/p/[^/]+[^/]$]
|
|
|
|
problems << " * Google Code homepage should end with a slash."
|
|
|
|
end
|
|
|
|
|
|
|
|
return problems
|
|
|
|
end
|
|
|
|
|
2011-04-04 16:40:38 -07:00
|
|
|
def audit_formula_caveats f
|
|
|
|
problems = []
|
|
|
|
|
|
|
|
if f.caveats.to_s =~ /^\s*\$\s+/
|
|
|
|
problems << " * caveats should not use '$' prompts in multiline commands."
|
|
|
|
end if strict?
|
|
|
|
|
|
|
|
return problems
|
|
|
|
end
|
|
|
|
|
2010-11-12 21:10:23 -08:00
|
|
|
module Homebrew extend self
|
2010-11-09 13:00:33 +00:00
|
|
|
def audit
|
2011-05-31 13:23:42 -07:00
|
|
|
errors = false
|
|
|
|
|
2010-11-09 13:00:33 +00:00
|
|
|
ff.each do |f|
|
|
|
|
problems = []
|
|
|
|
problems += audit_formula_instance f
|
|
|
|
problems += audit_formula_urls f
|
2011-04-04 16:40:38 -07:00
|
|
|
problems += audit_formula_caveats f
|
2010-11-09 13:00:33 +00:00
|
|
|
|
2011-01-22 09:32:16 -08:00
|
|
|
perms = File.stat(f.path).mode
|
|
|
|
if perms.to_s(8) != "100644"
|
|
|
|
problems << " * permissions wrong; chmod 644 #{f.path}"
|
|
|
|
end
|
|
|
|
|
2010-11-09 13:00:33 +00:00
|
|
|
text = ""
|
|
|
|
File.open(f.path, "r") { |afile| text = afile.read }
|
2010-08-10 11:52:03 -07:00
|
|
|
|
2010-11-09 13:00:33 +00:00
|
|
|
# DATA with no __END__
|
|
|
|
if (text =~ /\bDATA\b/) and not (text =~ /^\s*__END__\s*$/)
|
|
|
|
problems << " * 'DATA' was found, but no '__END__'"
|
|
|
|
end
|
2011-02-20 15:03:15 -08:00
|
|
|
|
2010-11-09 13:00:33 +00:00
|
|
|
problems += [' * invalid or missing version'] if f.version.to_s.empty?
|
2010-08-10 11:52:03 -07:00
|
|
|
|
2010-11-09 13:00:33 +00:00
|
|
|
# Don't try remaining audits on text in __END__
|
|
|
|
text_without_patch = (text.split("__END__")[0]).strip()
|
2010-08-09 11:59:16 -07:00
|
|
|
|
2011-03-10 12:26:29 -08:00
|
|
|
problems += audit_formula_text(f.name, text_without_patch)
|
2010-11-09 13:00:33 +00:00
|
|
|
problems += audit_formula_options(f, text_without_patch)
|
2011-11-21 09:00:34 -08:00
|
|
|
problems += audit_formula_version(f, text_without_patch)
|
2010-11-09 13:00:33 +00:00
|
|
|
|
|
|
|
unless problems.empty?
|
2011-05-31 13:23:42 -07:00
|
|
|
errors = true
|
2010-11-09 13:00:33 +00:00
|
|
|
puts "#{f.name}:"
|
|
|
|
puts problems * "\n"
|
|
|
|
puts
|
|
|
|
end
|
2010-08-09 11:59:16 -07:00
|
|
|
end
|
2011-05-31 13:23:42 -07:00
|
|
|
|
|
|
|
exit 1 if errors
|
2010-06-23 11:20:47 -07:00
|
|
|
end
|
|
|
|
end
|