2010-06-23 11:20:47 -07:00
|
|
|
require 'formula'
|
|
|
|
require 'utils'
|
2013-08-19 12:32:57 -05:00
|
|
|
require 'extend/ENV'
|
2013-07-15 19:29:08 -07:00
|
|
|
require 'formula_cellar_checks'
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
module Homebrew extend self
|
|
|
|
def audit
|
|
|
|
formula_count = 0
|
|
|
|
problem_count = 0
|
|
|
|
|
2013-08-19 13:03:41 -05:00
|
|
|
ENV.activate_extensions!
|
2013-05-07 18:39:45 -05:00
|
|
|
ENV.setup_build_environment
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
ff = if ARGV.named.empty?
|
2012-08-21 11:39:45 -04:00
|
|
|
Formula
|
2012-08-07 01:37:46 -05:00
|
|
|
else
|
|
|
|
ARGV.formulae
|
|
|
|
end
|
|
|
|
|
|
|
|
ff.each do |f|
|
|
|
|
fa = FormulaAuditor.new f
|
|
|
|
fa.audit
|
|
|
|
|
|
|
|
unless fa.problems.empty?
|
|
|
|
puts "#{f.name}:"
|
|
|
|
fa.problems.each { |p| puts " * #{p}" }
|
|
|
|
puts
|
|
|
|
formula_count += 1
|
|
|
|
problem_count += fa.problems.size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
unless problem_count.zero?
|
|
|
|
ofail "#{problem_count} problems in #{formula_count} formulae"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
|
|
|
|
class Module
|
|
|
|
def redefine_const(name, value)
|
|
|
|
__send__(:remove_const, name) if const_defined?(name)
|
|
|
|
const_set(name, value)
|
|
|
|
end
|
|
|
|
end
|
2010-06-23 11:20:47 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Formula extensions for auditing
|
|
|
|
class Formula
|
|
|
|
def head_only?
|
|
|
|
@head and @stable.nil?
|
2011-06-06 07:25:00 -07:00
|
|
|
end
|
2011-03-21 12:57:35 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def text
|
|
|
|
@text ||= FormulaText.new(@path)
|
2010-07-12 10:34:12 -07:00
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-07-12 10:34:12 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
class FormulaText
|
|
|
|
def initialize path
|
|
|
|
@text = path.open('r') { |f| f.read }
|
2010-07-23 21:31:32 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def without_patch
|
|
|
|
@text.split("__END__")[0].strip()
|
2011-11-29 19:37:39 -06:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def has_DATA?
|
|
|
|
/\bDATA\b/ =~ @text
|
2010-08-15 15:19:19 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def has_END?
|
|
|
|
/^__END__$/ =~ @text
|
2010-09-08 09:07:59 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def has_trailing_newline?
|
2013-02-21 09:56:30 +01:00
|
|
|
/\Z\n/ =~ @text
|
2010-09-07 14:34:39 -07:00
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-09-07 14:34:39 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
class FormulaAuditor
|
2013-07-15 19:29:08 -07:00
|
|
|
include FormulaCellarChecks
|
|
|
|
|
2013-02-17 22:52:39 -06:00
|
|
|
attr_reader :f, :text, :problems
|
2012-08-07 01:37:46 -05:00
|
|
|
|
|
|
|
BUILD_TIME_DEPS = %W[
|
|
|
|
autoconf
|
|
|
|
automake
|
|
|
|
boost-build
|
|
|
|
bsdmake
|
|
|
|
cmake
|
|
|
|
imake
|
2013-02-03 14:41:00 -06:00
|
|
|
intltool
|
2012-08-07 01:37:46 -05:00
|
|
|
libtool
|
|
|
|
pkg-config
|
|
|
|
scons
|
|
|
|
smake
|
2012-09-05 21:12:08 -07:00
|
|
|
swig
|
2012-08-07 01:37:46 -05:00
|
|
|
]
|
|
|
|
|
|
|
|
def initialize f
|
|
|
|
@f = f
|
|
|
|
@problems = []
|
|
|
|
@text = f.text.without_patch
|
2013-04-06 22:10:33 -05:00
|
|
|
@specs = %w{stable devel head}.map { |s| f.send(s) }.compact
|
2012-08-07 01:37:46 -05:00
|
|
|
|
|
|
|
# We need to do this in case the formula defines a patch that uses DATA.
|
|
|
|
f.class.redefine_const :DATA, ""
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_file
|
|
|
|
unless f.path.stat.mode.to_s(8) == "100644"
|
|
|
|
problem "Incorrect file permissions: chmod 644 #{f.path}"
|
|
|
|
end
|
2010-06-23 11:20:47 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
if f.text.has_DATA? and not f.text.has_END?
|
|
|
|
problem "'DATA' was found, but no '__END__'"
|
|
|
|
end
|
2010-08-08 10:17:53 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
if f.text.has_END? and not f.text.has_DATA?
|
|
|
|
problem "'__END__' was found, but 'DATA' is not used"
|
|
|
|
end
|
2010-08-15 11:32:45 -07:00
|
|
|
|
2013-02-21 09:56:30 +01:00
|
|
|
unless f.text.has_trailing_newline?
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "File should end with a newline"
|
|
|
|
end
|
2010-08-08 10:17:53 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def audit_deps
|
|
|
|
# Don't depend_on aliases; use full name
|
2013-05-07 18:40:14 -05:00
|
|
|
@@aliases ||= Formula.aliases
|
|
|
|
f.deps.select { |d| @@aliases.include? d.name }.each do |d|
|
2013-08-14 21:30:23 -07:00
|
|
|
real_name = d.to_formula.name
|
|
|
|
problem "Dependency '#{d}' is an alias; use the canonical name '#{real_name}'."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-09-08 09:22:48 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check for things we don't like to depend on.
|
|
|
|
# We allow non-Homebrew installs whenever possible.
|
2013-01-23 00:26:31 -06:00
|
|
|
f.deps.each do |dep|
|
2012-08-07 01:37:46 -05:00
|
|
|
begin
|
2013-01-23 00:26:31 -06:00
|
|
|
dep_f = dep.to_formula
|
2013-04-06 22:10:43 -05:00
|
|
|
rescue FormulaUnavailableError
|
2013-02-02 00:54:32 -06:00
|
|
|
problem "Can't find dependency #{dep.name.inspect}."
|
2013-06-26 18:08:54 -05:00
|
|
|
next
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-08-08 10:17:53 -07:00
|
|
|
|
2013-01-23 00:26:31 -06:00
|
|
|
dep.options.reject do |opt|
|
2014-02-16 22:35:14 +00:00
|
|
|
next true if dep_f.build.has_option?(opt.name)
|
|
|
|
dep_f.requirements.detect do |r|
|
|
|
|
if r.tags.include? :recommended
|
|
|
|
opt.name == "with-#{r.name}"
|
|
|
|
elsif r.tags.include? :optional
|
|
|
|
opt.name == "without-#{r.name}"
|
|
|
|
end
|
|
|
|
end
|
2013-01-23 00:26:31 -06:00
|
|
|
end.each do |opt|
|
|
|
|
problem "Dependency #{dep} does not define option #{opt.name.inspect}"
|
|
|
|
end
|
|
|
|
|
|
|
|
case dep.name
|
2013-04-06 22:11:02 -05:00
|
|
|
when *BUILD_TIME_DEPS
|
2013-11-04 12:00:06 -06:00
|
|
|
next if dep.build? or dep.run?
|
2013-06-27 21:11:16 -05:00
|
|
|
problem %{#{dep} dependency should be "depends_on '#{dep}' => :build"}
|
2013-06-04 11:15:30 -05:00
|
|
|
when "git", "ruby", "emacs", "mercurial"
|
2012-08-07 01:37:46 -05:00
|
|
|
problem <<-EOS.undent
|
2013-01-23 00:26:31 -06:00
|
|
|
Don't use #{dep} as a dependency. We allow non-Homebrew
|
|
|
|
#{dep} installations.
|
2012-08-07 01:37:46 -05:00
|
|
|
EOS
|
|
|
|
when 'gfortran'
|
2013-06-23 20:40:00 -07:00
|
|
|
problem "Use `depends_on :fortran` instead of `depends_on 'gfortran'`"
|
2012-08-07 01:37:46 -05:00
|
|
|
when 'open-mpi', 'mpich2'
|
|
|
|
problem <<-EOS.undent
|
|
|
|
There are multiple conflicting ways to install MPI. Use an MPIDependency:
|
2013-06-26 22:08:54 -05:00
|
|
|
depends_on :mpi => [<lang list>]
|
2012-08-07 01:37:46 -05:00
|
|
|
Where <lang list> is a comma delimited list that can include:
|
2013-06-26 22:08:54 -05:00
|
|
|
:cc, :cxx, :f77, :f90
|
2012-08-07 01:37:46 -05:00
|
|
|
EOS
|
|
|
|
end
|
|
|
|
end
|
2010-09-13 15:16:09 -07:00
|
|
|
end
|
|
|
|
|
2013-01-03 11:22:31 -08:00
|
|
|
def audit_conflicts
|
2013-06-09 13:44:59 -05:00
|
|
|
f.conflicts.each do |c|
|
2013-01-03 11:22:31 -08:00
|
|
|
begin
|
2013-06-09 13:44:59 -05:00
|
|
|
Formula.factory(c.name)
|
2013-02-17 22:54:27 -06:00
|
|
|
rescue FormulaUnavailableError
|
2013-06-09 13:44:59 -05:00
|
|
|
problem "Can't find conflicting formula #{c.name.inspect}."
|
2013-01-03 11:22:31 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2010-08-07 15:23:13 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def audit_urls
|
|
|
|
unless f.homepage =~ %r[^https?://]
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "The homepage should start with http or https (url is #{f.homepage})."
|
|
|
|
end
|
|
|
|
|
|
|
|
# Check for http:// GitHub homepage urls, https:// is preferred.
|
|
|
|
# Note: only check homepages that are repo pages, not *.github.com hosts
|
|
|
|
if f.homepage =~ %r[^http://github\.com/]
|
|
|
|
problem "Use https:// URLs for homepages on GitHub (url is #{f.homepage})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-10-08 20:11:40 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Google Code homepages should end in a slash
|
|
|
|
if f.homepage =~ %r[^https?://code\.google\.com/p/[^/]+[^/]$]
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Google Code homepage should end with a slash (url is #{f.homepage})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-10-30 08:26:36 -07:00
|
|
|
|
2013-04-06 22:10:33 -05:00
|
|
|
urls = @specs.map(&:url)
|
2010-10-21 07:51:47 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check GNU urls; doesn't apply to mirrors
|
2013-05-27 18:31:18 -05:00
|
|
|
urls.grep(%r[^(?:https?|ftp)://(?!alpha).+/gnu/]) do |u|
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "\"ftpmirror.gnu.org\" is preferred for GNU software (url is #{u})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2011-03-21 12:54:00 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# the rest of the checks apply to mirrors as well
|
2013-04-06 22:10:33 -05:00
|
|
|
urls.concat(@specs.map(&:mirrors).flatten)
|
2011-11-18 15:22:04 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check SourceForge urls
|
|
|
|
urls.each do |p|
|
2013-07-12 23:00:08 -07:00
|
|
|
# Skip if the URL looks like a SVN repo
|
2012-08-07 01:37:46 -05:00
|
|
|
next if p =~ %r[/svnroot/]
|
|
|
|
next if p =~ %r[svn\.sourceforge]
|
2011-12-08 22:16:19 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Is it a sourceforge http(s) URL?
|
2013-09-22 22:06:53 -07:00
|
|
|
next unless p =~ %r[^https?://.*\bsourceforge\.com]
|
2011-12-08 22:16:19 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
if p =~ /(\?|&)use_mirror=/
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Don't use #{$1}use_mirror in SourceForge urls (url is #{p})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-06-17 17:59:30 -05:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
if p =~ /\/download$/
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Don't use /download in SourceForge urls (url is #{p})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-06-17 18:01:22 -05:00
|
|
|
|
2013-06-30 12:07:18 -07:00
|
|
|
if p =~ %r[^https?://sourceforge\.]
|
|
|
|
problem "Use http://downloads.sourceforge.net to get geolocation (url is #{p})."
|
|
|
|
end
|
|
|
|
|
|
|
|
if p =~ %r[^https?://prdownloads\.]
|
2013-01-21 10:33:56 +01:00
|
|
|
problem "Don't use prdownloads in SourceForge urls (url is #{p}).\n" +
|
2013-03-10 10:46:46 +01:00
|
|
|
"\tSee: http://librelist.com/browser/homebrew/2011/1/12/prdownloads-is-bad/"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-08-09 11:59:16 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
if p =~ %r[^http://\w+\.dl\.]
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Don't use specific dl mirrors in SourceForge urls (url is #{p})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
end
|
2010-09-09 14:16:05 -07:00
|
|
|
|
2013-03-10 10:46:46 +01:00
|
|
|
# Check for git:// GitHub repo urls, https:// is preferred.
|
2013-05-27 18:31:18 -05:00
|
|
|
urls.grep(%r[^git://[^/]*github\.com/]) do |u|
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Use https:// URLs for accessing GitHub repositories (url is #{u})."
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2013-02-03 14:54:18 -06:00
|
|
|
|
2013-03-10 10:46:46 +01:00
|
|
|
# Check for http:// GitHub repo urls, https:// is preferred.
|
2013-05-27 18:31:18 -05:00
|
|
|
urls.grep(%r[^http://github\.com/.*\.git$]) do |u|
|
2013-03-10 10:46:46 +01:00
|
|
|
problem "Use https:// URLs for accessing GitHub repositories (url is #{u})."
|
2013-01-21 10:33:56 +01:00
|
|
|
end
|
2013-03-10 10:46:46 +01:00
|
|
|
|
2013-03-15 15:18:22 -07:00
|
|
|
# Use new-style archive downloads
|
2013-07-23 11:21:37 -05:00
|
|
|
urls.select { |u| u =~ %r[https://.*/(?:tar|zip)ball/] && u !~ %r[\.git$] }.each do |u|
|
2013-03-15 15:18:22 -07:00
|
|
|
problem "Use /archive/ URLs for GitHub tarballs (url is #{u})."
|
|
|
|
end
|
2011-03-15 21:40:09 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def audit_specs
|
|
|
|
problem "Head-only (no stable download)" if f.head_only?
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2013-09-18 18:50:23 -05:00
|
|
|
%w[Stable Devel HEAD].each do |name|
|
|
|
|
next unless spec = f.send(name.downcase)
|
|
|
|
|
2013-09-18 18:08:50 -05:00
|
|
|
ra = ResourceAuditor.new(spec).audit
|
2013-09-18 18:50:23 -05:00
|
|
|
problems.concat ra.problems.map { |problem| "#{name}: #{problem}" }
|
2013-09-18 18:22:00 -05:00
|
|
|
|
|
|
|
spec.resources.each_value do |resource|
|
2013-09-18 18:50:23 -05:00
|
|
|
ra = ResourceAuditor.new(resource).audit
|
|
|
|
problems.concat ra.problems.map { |problem|
|
|
|
|
"#{name} resource #{resource.name.inspect}: #{problem}"
|
|
|
|
}
|
2013-09-18 18:22:00 -05:00
|
|
|
end
|
2013-05-27 22:24:22 -07:00
|
|
|
end
|
2011-11-30 13:14:24 -06:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def audit_patches
|
2013-05-07 18:39:45 -05:00
|
|
|
Patches.new(f.patches).select(&:external?).each do |p|
|
|
|
|
case p.url
|
2013-11-25 13:22:03 -06:00
|
|
|
when %r[raw\.github\.com], %r[gist\.github\.com/raw], %r[gist\.github\.com/.+/raw$]
|
2013-05-07 18:39:45 -05:00
|
|
|
unless p.url =~ /[a-fA-F0-9]{40}/
|
|
|
|
problem "GitHub/Gist patches should specify a revision:\n#{p.url}"
|
2012-09-06 20:47:05 -05:00
|
|
|
end
|
2013-05-07 18:39:45 -05:00
|
|
|
when %r[macports/trunk]
|
|
|
|
problem "MacPorts patches should specify a revision instead of trunk:\n#{p.url}"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-09-09 14:16:05 -07:00
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-09-09 14:16:05 -07:00
|
|
|
|
2013-07-16 23:15:22 -05:00
|
|
|
def audit_text
|
2014-02-25 07:36:47 -08:00
|
|
|
if text =~ /system\s+['"]scons/
|
2014-02-25 20:51:16 -08:00
|
|
|
problem "use \"scons *args\" instead of \"system 'scons', *args\""
|
2014-02-25 07:36:47 -08:00
|
|
|
end
|
|
|
|
|
2013-07-23 11:21:37 -05:00
|
|
|
if text =~ /system\s+['"]xcodebuild/ && text !~ /SYMROOT=/
|
2013-07-16 23:15:22 -05:00
|
|
|
problem "xcodebuild should be passed an explicit \"SYMROOT\""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_line(line)
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /<(Formula|AmazonWebServicesFormula|ScriptFileFormula|GithubGistFormula)/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Use a space in class inheritance: class Foo < #{$1}"
|
2010-09-09 14:16:05 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Commented-out cmake support from default template
|
2013-07-16 21:39:46 -05:00
|
|
|
if line =~ /# system "cmake/
|
2013-01-04 09:30:57 -08:00
|
|
|
problem "Commented cmake call found"
|
2010-09-09 14:16:05 -07:00
|
|
|
end
|
|
|
|
|
2013-07-03 09:20:41 -07:00
|
|
|
# Comments from default template
|
2013-07-16 21:39:46 -05:00
|
|
|
if line =~ /# PLEASE REMOVE/
|
2013-07-05 12:05:29 -07:00
|
|
|
problem "Please remove default template comments"
|
|
|
|
end
|
2013-07-16 21:39:46 -05:00
|
|
|
if line =~ /# if this fails, try separate make\/make install steps/
|
2013-07-03 09:20:41 -07:00
|
|
|
problem "Please remove default template comments"
|
|
|
|
end
|
2013-07-16 21:39:46 -05:00
|
|
|
if line =~ /# if your formula requires any X11\/XQuartz components/
|
2013-07-05 12:05:29 -07:00
|
|
|
problem "Please remove default template comments"
|
|
|
|
end
|
2013-07-16 21:39:46 -05:00
|
|
|
if line =~ /# if your formula's build system can't parallelize/
|
2013-07-03 09:20:41 -07:00
|
|
|
problem "Please remove default template comments"
|
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# FileUtils is included in Formula
|
2013-12-04 20:07:27 -08:00
|
|
|
# encfs modifies a file with this name, so check for some leading characters
|
|
|
|
if line =~ /[^'"\/]FileUtils\.(\w+)/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Don't need 'FileUtils.' before #{$1}."
|
|
|
|
end
|
2010-09-09 14:16:05 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check for long inreplace block vars
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /inreplace .* do \|(.{2,})\|/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"inreplace <filenames> do |s|\" is preferred over \"|#{$1}|\"."
|
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check for string interpolation of single values.
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /(system|inreplace|gsub!|change_make_var!).*[ ,]"#\{([\w.]+)\}"/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Don't need to interpolate \"#{$2}\" with #{$1}"
|
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Check for string concatenation; prefer interpolation
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /(#\{\w+\s*\+\s*['"][^}]+\})/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Try not to concatenate paths in string interpolation:\n #{$1}"
|
2012-04-05 21:12:02 -05:00
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Prefer formula path shortcuts in Pathname+
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share)[/'"])}
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"(#{$1}...#{$2})\" should be \"(#{$3}+...)\""
|
|
|
|
end
|
2012-06-18 19:58:35 -05:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r[((man)\s*\+\s*(['"])(man[1-8])(['"]))]
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"#{$1}\" should be \"#{$4}\""
|
|
|
|
end
|
2012-04-05 21:12:02 -05:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Prefer formula path shortcuts in strings
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r[(\#\{prefix\}/(bin|include|libexec|lib|sbin|share))]
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"#{$1}\" should be \"\#{#{$2}}\""
|
2012-04-05 21:12:02 -05:00
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r[((\#\{prefix\}/share/man/|\#\{man\}/)(man[1-8]))]
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"#{$1}\" should be \"\#{#{$3}}\""
|
|
|
|
end
|
2012-01-25 22:41:53 -06:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r[((\#\{share\}/(man)))[/'"]]
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"#{$1}\" should be \"\#{#{$3}}\""
|
|
|
|
end
|
2010-08-21 11:55:57 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r[(\#\{prefix\}/share/(info|man))]
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "\"#{$1}\" should be \"\#{#{$2}}\""
|
|
|
|
end
|
2010-08-21 11:55:57 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Commented-out depends_on
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /#\s*depends_on\s+(.+)\s*$/
|
2012-09-13 07:14:45 -07:00
|
|
|
problem "Commented-out dep #{$1}"
|
2010-09-07 09:23:29 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# No trailing whitespace, please
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /[\t ]+$/
|
2012-09-13 07:14:45 -07:00
|
|
|
problem "Trailing whitespace was found"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-08-21 11:55:57 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /if\s+ARGV\.include\?\s+'--(HEAD|devel)'/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Use \"if ARGV.build_#{$1.downcase}?\" instead"
|
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /make && make/
|
2012-09-13 07:14:45 -07:00
|
|
|
problem "Use separate make calls"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-03-17 19:49:49 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /^[ ]*\t/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Use spaces instead of tabs for indentation"
|
|
|
|
end
|
2011-05-31 13:23:42 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /ENV\.x11/
|
2012-09-03 19:18:58 -07:00
|
|
|
problem "Use \"depends_on :x11\" instead of \"ENV.x11\""
|
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
# Avoid hard-coding compilers
|
2013-10-16 08:36:32 +01:00
|
|
|
unless f.name == 'go' # Go needs to set CC for cgo support.
|
|
|
|
if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?(gcc|llvm-gcc|clang)['" ]}
|
|
|
|
problem "Use \"\#{ENV.cc}\" instead of hard-coding \"#{$3}\""
|
|
|
|
end
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-08-10 11:52:03 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ %r{(system|ENV\[.+\]\s?=)\s?['"](/usr/bin/)?((g|llvm-g|clang)\+\+)['" ]}
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Use \"\#{ENV.cxx}\" instead of hard-coding \"#{$3}\""
|
|
|
|
end
|
2011-02-20 15:03:15 -08:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /system\s+['"](env|export)/
|
2012-08-07 01:37:46 -05:00
|
|
|
problem "Use ENV instead of invoking '#{$1}' to modify the environment"
|
|
|
|
end
|
2012-01-22 22:32:15 -06:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /version == ['"]HEAD['"]/
|
2012-08-22 20:59:43 -07:00
|
|
|
problem "Use 'build.head?' instead of inspecting 'version'"
|
|
|
|
end
|
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /build\.include\?\s+['"]\-\-(.*)['"]/
|
2012-09-13 07:14:45 -07:00
|
|
|
problem "Reference '#{$1}' without dashes"
|
2012-08-25 09:36:01 -07:00
|
|
|
end
|
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /build\.with\?\s+['"]-?-?with-(.*)['"]/
|
2013-01-21 10:33:56 +01:00
|
|
|
problem "No double 'with': Use `build.with? '#{$1}'` to check for \"--with-#{$1}\""
|
|
|
|
end
|
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /build\.without\?\s+['"]-?-?without-(.*)['"]/
|
2013-01-21 10:33:56 +01:00
|
|
|
problem "No double 'without': Use `build.without? '#{$1}'` to check for \"--without-#{$1}\""
|
|
|
|
end
|
|
|
|
|
2014-02-16 22:10:22 +00:00
|
|
|
# Mongo writes out a Ruby script that uses ARGV
|
|
|
|
# Python formulae need ARGV for Requirements
|
|
|
|
unless f.name == 'mongodb' || f.name == "pyobject3"
|
2013-07-07 11:56:11 -07:00
|
|
|
if line =~ /ARGV\.(?!(debug\?|verbose\?|value[\(\s]))/
|
|
|
|
problem "Use build instead of ARGV to check options"
|
|
|
|
end
|
2012-08-22 20:59:43 -07:00
|
|
|
end
|
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /def options/
|
2012-09-13 07:14:45 -07:00
|
|
|
problem "Use new-style option definitions"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2012-09-04 18:18:14 -05:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /MACOS_VERSION/
|
2012-09-04 18:18:14 -05:00
|
|
|
problem "Use MacOS.version instead of MACOS_VERSION"
|
|
|
|
end
|
2012-09-10 16:40:13 -05:00
|
|
|
|
2013-04-06 22:11:26 -05:00
|
|
|
cats = %w{leopard snow_leopard lion mountain_lion}.join("|")
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /MacOS\.(?:#{cats})\?/
|
2013-04-06 22:11:26 -05:00
|
|
|
problem "\"#{$&}\" is deprecated, use a comparison to MacOS.version instead"
|
2012-09-10 16:40:13 -05:00
|
|
|
end
|
2012-09-13 07:14:45 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /skip_clean\s+:all/
|
2014-02-23 12:09:28 -08:00
|
|
|
problem "`skip_clean :all` is deprecated; brew no longer strips symbols\n" +
|
|
|
|
"\tPass explicit paths to prevent Homebrew from removing empty folders."
|
2012-09-13 07:14:45 -07:00
|
|
|
end
|
2013-01-27 14:27:32 -08:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /depends_on [A-Z][\w:]+\.new$/
|
2013-04-06 22:11:26 -05:00
|
|
|
problem "`depends_on` can take requirement classes instead of instances"
|
2013-01-27 14:27:32 -08:00
|
|
|
end
|
2013-04-22 15:06:42 -05:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /^def (\w+).*$/
|
2013-04-22 15:06:42 -05:00
|
|
|
problem "Define method #{$1.inspect} in the class body, not at the top-level"
|
|
|
|
end
|
2013-06-23 20:40:00 -07:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /ENV.fortran/
|
2013-06-23 20:40:00 -07:00
|
|
|
problem "Use `depends_on :fortran` instead of `ENV.fortran`"
|
|
|
|
end
|
2013-07-16 20:38:50 -05:00
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /depends_on :(.+) (if.+|unless.+)$/
|
2013-07-16 20:38:50 -05:00
|
|
|
audit_conditional_dep($1.to_sym, $2, $&)
|
|
|
|
end
|
|
|
|
|
2013-07-16 21:25:02 -05:00
|
|
|
if line =~ /depends_on ['"](.+)['"] (if.+|unless.+)$/
|
2013-07-16 20:38:50 -05:00
|
|
|
audit_conditional_dep($1, $2, $&)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_conditional_dep(dep, condition, line)
|
2013-07-23 11:21:37 -05:00
|
|
|
quoted_dep = quote_dep(dep)
|
|
|
|
dep = Regexp.escape(dep.to_s)
|
|
|
|
|
2013-07-16 20:38:50 -05:00
|
|
|
case condition
|
|
|
|
when /if build\.include\? ['"]with-#{dep}['"]$/, /if build\.with\? ['"]#{dep}['"]$/
|
2013-07-23 11:21:37 -05:00
|
|
|
problem %{Replace #{line.inspect} with "depends_on #{quoted_dep} => :optional"}
|
2013-07-16 20:38:50 -05:00
|
|
|
when /unless build\.include\? ['"]without-#{dep}['"]$/, /unless build\.without\? ['"]#{dep}['"]$/
|
2013-07-23 11:21:37 -05:00
|
|
|
problem %{Replace #{line.inspect} with "depends_on #{quoted_dep} => :recommended"}
|
2013-07-16 20:38:50 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def quote_dep(dep)
|
|
|
|
Symbol === dep ? dep.inspect : "'#{dep}'"
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-08-09 11:59:16 -07:00
|
|
|
|
2013-07-15 19:29:08 -07:00
|
|
|
def audit_check_output warning_and_description
|
|
|
|
return unless warning_and_description
|
2013-09-07 17:38:15 +01:00
|
|
|
warning, description = *warning_and_description
|
|
|
|
problem "#{warning}\n#{description}"
|
2013-07-15 19:29:08 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def audit_installed
|
|
|
|
audit_check_output(check_manpages)
|
|
|
|
audit_check_output(check_infopages)
|
|
|
|
audit_check_output(check_jars)
|
|
|
|
audit_check_output(check_non_libraries)
|
|
|
|
audit_check_output(check_non_executables(f.bin))
|
2013-11-10 12:51:24 -08:00
|
|
|
audit_check_output(check_generic_executables(f.bin))
|
2013-07-15 19:29:08 -07:00
|
|
|
audit_check_output(check_non_executables(f.sbin))
|
2013-11-10 12:51:24 -08:00
|
|
|
audit_check_output(check_generic_executables(f.sbin))
|
2013-07-15 19:29:08 -07:00
|
|
|
end
|
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def audit
|
|
|
|
audit_file
|
|
|
|
audit_specs
|
|
|
|
audit_urls
|
|
|
|
audit_deps
|
2013-01-03 11:22:31 -08:00
|
|
|
audit_conflicts
|
2012-08-07 01:37:46 -05:00
|
|
|
audit_patches
|
2013-07-16 23:15:22 -05:00
|
|
|
audit_text
|
|
|
|
text.each_line { |line| audit_line(line) }
|
2013-07-15 19:29:08 -07:00
|
|
|
audit_installed
|
2012-08-07 01:37:46 -05:00
|
|
|
end
|
2010-11-09 13:00:33 +00:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
private
|
2011-05-31 13:23:42 -07:00
|
|
|
|
2012-08-07 01:37:46 -05:00
|
|
|
def problem p
|
|
|
|
@problems << p
|
2010-06-23 11:20:47 -07:00
|
|
|
end
|
|
|
|
end
|
2013-09-18 18:08:50 -05:00
|
|
|
|
|
|
|
class ResourceAuditor
|
|
|
|
attr_reader :problems
|
|
|
|
attr_reader :version, :checksum, :using, :specs, :url
|
|
|
|
|
|
|
|
def initialize(resource)
|
|
|
|
@version = resource.version
|
|
|
|
@checksum = resource.checksum
|
|
|
|
@url = resource.url
|
|
|
|
@using = resource.using
|
|
|
|
@specs = resource.specs
|
|
|
|
@problems = []
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit
|
|
|
|
audit_version
|
|
|
|
audit_checksum
|
|
|
|
audit_download_strategy
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_version
|
2013-11-26 20:35:07 -06:00
|
|
|
if version.nil?
|
|
|
|
problem "missing version"
|
|
|
|
elsif version.to_s.empty?
|
|
|
|
problem "version is set to an empty string"
|
2013-09-18 18:08:50 -05:00
|
|
|
elsif not 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
|
|
|
|
|
|
|
|
if version.to_s =~ /^v/
|
|
|
|
problem "version #{version} should not have a leading 'v'"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_checksum
|
|
|
|
return unless checksum
|
|
|
|
|
|
|
|
case checksum.hash_type
|
|
|
|
when :md5
|
|
|
|
problem "MD5 checksums are deprecated, please use SHA1 or SHA256"
|
|
|
|
return
|
|
|
|
when :sha1 then len = 40
|
|
|
|
when :sha256 then len = 64
|
|
|
|
end
|
|
|
|
|
|
|
|
if checksum.empty?
|
|
|
|
problem "#{checksum.hash_type} is empty"
|
|
|
|
else
|
|
|
|
problem "#{checksum.hash_type} should be #{len} characters" unless checksum.hexdigest.length == len
|
|
|
|
problem "#{checksum.hash_type} contains invalid characters" unless checksum.hexdigest =~ /^[a-fA-F0-9]+$/
|
|
|
|
problem "#{checksum.hash_type} should be lowercase" unless checksum.hexdigest == checksum.hexdigest.downcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def audit_download_strategy
|
|
|
|
return unless using
|
|
|
|
|
|
|
|
url_strategy = DownloadStrategyDetector.detect(url)
|
|
|
|
using_strategy = DownloadStrategyDetector.detect('', using)
|
|
|
|
|
|
|
|
if url_strategy == using_strategy
|
|
|
|
problem "redundant :using specification in URL"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def problem text
|
|
|
|
@problems << text
|
|
|
|
end
|
|
|
|
end
|