aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Nagel2013-04-13 17:40:12 -0500
committerJack Nagel2013-04-13 17:40:12 -0500
commit563d8be6bde6eaddcf4eb8d4fb9a10ed0af7ddb7 (patch)
tree26d2ddf590ec47e3a51ebd056863a5d5e5aa9c8f
parent5ba5e215363d0f4737f9004f6e699a2b18eb0761 (diff)
downloadhomebrew-563d8be6bde6eaddcf4eb8d4fb9a10ed0af7ddb7.tar.bz2
Improved formula attribute validation
The initializer for Formula does a number of validations, but it does them in a weird order, and some attributes aren't validated under certain circumstances. This became even more of a mess when most software package attributes were moved into the SoftwareSpec class. This commit removes the last vestiges of storing these attributes as instance variables. In particular, it eliminates #set_instance_variable and #validate_variable, replacing them with methods that operate on SoftwareSpec instances, and generate more useful errors. Doing these validations unconditionally in the initializer means we bail out much earlier if the formula has invalid attributes or is not fully specified, and no longer need to validate in #prefix. Technically we don't need to validate in #brew either, but we continue to do so anyway as a safety measure, and because we cannot enforce calls to super in subclasses.
-rw-r--r--Library/Homebrew/exceptions.rb11
-rw-r--r--Library/Homebrew/formula.rb129
-rw-r--r--Library/Homebrew/formula_specialties.rb7
-rw-r--r--Library/Homebrew/test/test_formula.rb19
-rw-r--r--Library/Homebrew/test/test_formula_validation.rb69
-rw-r--r--Library/Homebrew/test/test_versions.rb8
6 files changed, 148 insertions, 95 deletions
diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb
index d57f5ddc3..b5299c038 100644
--- a/Library/Homebrew/exceptions.rb
+++ b/Library/Homebrew/exceptions.rb
@@ -22,6 +22,17 @@ class NoSuchKegError < RuntimeError
end
end
+class FormulaValidationError < StandardError
+ attr_reader :attr
+
+ def initialize(attr, value)
+ @attr = attr
+ msg = "invalid attribute: #{attr}"
+ msg << " (#{value.inspect})" unless value.empty?
+ super msg
+ end
+end
+
class FormulaUnavailableError < RuntimeError
attr_reader :name
attr_accessor :dependent
diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index a6305eaaf..eddacf08d 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -24,44 +24,31 @@ class Formula
# Homebrew determines the name
def initialize name='__UNKNOWN__', path=nil
- set_instance_variable :homepage
- set_instance_variable :stable
- set_instance_variable :bottle
- set_instance_variable :devel
- set_instance_variable :head
-
@name = name
- validate_variable :name
-
- # If a checksum or version was set in the DSL, but no stable URL
- # was defined, make @stable nil and save callers some trouble
- @stable = nil if @stable and @stable.url.nil?
-
- # Ensure the bottle URL is set. If it does not have a checksum,
- # then a bottle is not available for the current platform.
- if @bottle and not (@bottle.checksum.nil? or @bottle.checksum.empty?)
- @bottle.url ||= bottle_url(self)
- if @bottle.cat_without_underscores
- @bottle.url.gsub!(MacOS.cat.to_s, MacOS.cat_without_underscores.to_s)
+ # If we got an explicit path, use that, else determine from the name
+ @path = path.nil? ? self.class.path(name) : Pathname.new(path)
+ @homepage = self.class.homepage
+
+ set_spec :stable
+ set_spec :devel
+ set_spec :head
+ set_spec :bottle do |bottle|
+ # Ensure the bottle URL is set. If it does not have a checksum,
+ # then a bottle is not available for the current platform.
+ # TODO: push this down into Bottle; we can pass the formula instance
+ # into a validation method on the bottle instance.
+ unless bottle.checksum.nil? || bottle.checksum.empty?
+ @bottle = bottle
+ bottle.url ||= bottle_url(self)
+ if bottle.cat_without_underscores
+ bottle.url.gsub!(MacOS.cat.to_s,
+ MacOS.cat_without_underscores.to_s)
+ end
end
- else
- @bottle = nil
end
- @active_spec = if @head and ARGV.build_head? then @head # --HEAD
- elsif @devel and ARGV.build_devel? then @devel # --devel
- elsif @bottle and install_bottle?(self) then @bottle # bottle available
- elsif @stable.nil? and @head then @head # head-only
- else @stable # default
- end
-
- @version = @active_spec.version
- validate_variable :version if @version
-
- raise "No url provided for formula #{name}" if @active_spec.url.nil?
-
- # If we got an explicit path, use that, else determine from the name
- @path = path.nil? ? self.class.path(name) : Pathname.new(path)
+ @active_spec = determine_active_spec
+ validate_attributes :url, :name, :version
@downloader = download_strategy.new(name, @active_spec)
# Combine DSL `option` and `def options`
@@ -73,10 +60,36 @@ class Formula
@pin = FormulaPin.new(self)
end
- def url; @active_spec.url; end
- def version; @active_spec.version; end
- def specs; @active_spec.specs; end
- def mirrors; @active_spec.mirrors; end
+ def set_spec(name)
+ spec = self.class.send(name)
+ return if spec.nil?
+ if block_given? && yield(spec) || !spec.url.nil?
+ instance_variable_set("@#{name}", spec)
+ end
+ end
+
+ def determine_active_spec
+ case
+ when @head && ARGV.build_head? then @head # --HEAD
+ when @devel && ARGV.build_devel? then @devel # --devel
+ when @bottle && install_bottle?(self) then @bottle # bottle available
+ when @stable.nil? && @head then @head # head-only
+ else @stable
+ end
+ end
+
+ def validate_attributes(*attrs)
+ attrs.each do |attr|
+ if (value = send(attr).to_s).empty? || value =~ /\s/
+ raise FormulaValidationError.new(attr, value)
+ end
+ end
+ end
+
+ def url; active_spec.url; end
+ def version; active_spec.version; end
+ def specs; active_spec.specs; end
+ def mirrors; active_spec.mirrors; end
# if the dir is there, but it's empty we consider it not installed
def installed?
@@ -100,21 +113,21 @@ class Formula
end
def linked_keg
- HOMEBREW_REPOSITORY/'Library/LinkedKegs'/@name
+ HOMEBREW_REPOSITORY/'Library/LinkedKegs'/name
end
def installed_prefix
- devel_prefix = unless @devel.nil?
- HOMEBREW_CELLAR/@name/@devel.version
+ devel_prefix = unless devel.nil?
+ HOMEBREW_CELLAR/name/devel.version
end
- head_prefix = unless @head.nil?
- HOMEBREW_CELLAR/@name/@head.version
+ head_prefix = unless head.nil?
+ HOMEBREW_CELLAR/name/head.version
end
- if @active_spec == @head || @head and head_prefix.directory?
+ if active_spec == head || head and head_prefix.directory?
head_prefix
- elsif @active_spec == @devel || @devel and devel_prefix.directory?
+ elsif active_spec == devel || devel and devel_prefix.directory?
devel_prefix
else
prefix
@@ -127,9 +140,7 @@ class Formula
end
def prefix
- validate_variable :name
- validate_variable :version
- HOMEBREW_CELLAR/@name/@version
+ HOMEBREW_CELLAR/name/version
end
def rack; prefix.parent end
@@ -174,11 +185,11 @@ class Formula
def opt_prefix; HOMEBREW_PREFIX/:opt/name end
def download_strategy
- @active_spec.download_strategy
+ active_spec.download_strategy
end
def cached_download
- @downloader.cached_location
+ downloader.cached_location
end
# Can be overridden to selectively disable bottles from formulae.
@@ -237,8 +248,7 @@ class Formula
# yields self with current working directory set to the uncompressed tarball
def brew
- validate_variable :name
- validate_variable :version
+ validate_attributes :name, :version
stage do
begin
@@ -599,12 +609,12 @@ class Formula
def fetch
# Ensure the cache exists
HOMEBREW_CACHE.mkpath
- return @downloader.fetch, @downloader
+ return downloader.fetch, downloader
end
# For FormulaInstaller.
def verify_download_integrity fn
- @active_spec.verify_download_integrity(fn)
+ active_spec.verify_download_integrity(fn)
end
def test
@@ -655,17 +665,6 @@ class Formula
end
end
- def validate_variable name
- v = instance_variable_get("@#{name}")
- raise "Invalid @#{name}" if v.to_s.empty? or v.to_s =~ /\s/
- end
-
- def set_instance_variable(type)
- return if instance_variable_defined? "@#{type}"
- class_value = self.class.send(type)
- instance_variable_set("@#{type}", class_value) if class_value
- end
-
def self.method_added method
case method
when :brew
diff --git a/Library/Homebrew/formula_specialties.rb b/Library/Homebrew/formula_specialties.rb
index 4e63440a6..daf9dd6d0 100644
--- a/Library/Homebrew/formula_specialties.rb
+++ b/Library/Homebrew/formula_specialties.rb
@@ -10,10 +10,9 @@ end
# See flac.rb for an example
class GithubGistFormula < ScriptFileFormula
def initialize name='__UNKNOWN__', path=nil
- super name, path
- @stable.version(File.basename(File.dirname(url))[0,6])
- @version = @active_spec.version
- validate_variable :version
+ url = self.class.stable.url
+ self.class.stable.version(File.basename(File.dirname(url))[0,6])
+ super
end
end
diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 30ef41589..434dd3ac1 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -1,10 +1,6 @@
require 'testing_env'
require 'test/testball'
-class MostlyAbstractFormula < Formula
- url ''
-end
-
class FormulaTests < Test::Unit::TestCase
include VersionAssertions
@@ -58,19 +54,6 @@ class FormulaTests < Test::Unit::TestCase
assert_equal 'FooBar', Formula.class_s('foo_bar')
end
- def test_cant_override_brew
- assert_raises(RuntimeError) do
- Class.new(Formula) { def brew; end }
- end
- end
-
- def test_abstract_formula
- f=MostlyAbstractFormula.new
- assert_equal '__UNKNOWN__', f.name
- assert_raises(RuntimeError) { f.prefix }
- shutup { assert_raises(RuntimeError) { f.brew } }
- end
-
def test_mirror_support
f = Class.new(Formula) do
url "file:///#{TEST_FOLDER}/bad_url/testball-0.1.tbz"
@@ -286,7 +269,7 @@ class FormulaTests < Test::Unit::TestCase
f << %{
require 'formula'
class #{Formula.class_s(foobar)} < Formula
- url ''
+ url 'foo-1.0'
def initialize(*args)
@homepage = 'http://example.com/'
super
diff --git a/Library/Homebrew/test/test_formula_validation.rb b/Library/Homebrew/test/test_formula_validation.rb
new file mode 100644
index 000000000..be4c38c64
--- /dev/null
+++ b/Library/Homebrew/test/test_formula_validation.rb
@@ -0,0 +1,69 @@
+require 'testing_env'
+require 'formula'
+
+class FormulaValidationTests < Test::Unit::TestCase
+ def formula(*args, &block)
+ Class.new(Formula, &block).new(*args)
+ end
+
+ def assert_invalid(attr, &block)
+ e = assert_raises(FormulaValidationError, &block)
+ assert_equal attr, e.attr
+ end
+
+ def test_cant_override_brew
+ assert_raises(RuntimeError) do
+ Class.new(Formula) { def brew; end }
+ end
+ end
+
+ def test_validates_name
+ assert_invalid :name do
+ formula "name with spaces" do
+ url "foo"
+ version "1.0"
+ end
+ end
+ end
+
+ def test_validates_url
+ assert_invalid :url do
+ formula do
+ url ""
+ version "1"
+ end
+ end
+ end
+
+ def test_validates_version
+ assert_invalid :version do
+ formula do
+ url "foo"
+ version "version with spaces"
+ end
+ end
+
+ assert_invalid :version do
+ formula do
+ url "foo"
+ version ""
+ end
+ end
+ end
+
+ def test_validates_when_initialize_overridden
+ assert_invalid :name do
+ formula do
+ def initialize; end
+ end.brew {}
+ end
+ end
+
+ def test_head_only_valid
+ assert_nothing_raised do
+ formula do
+ head "foo"
+ end
+ end
+ end
+end
diff --git a/Library/Homebrew/test/test_versions.rb b/Library/Homebrew/test/test_versions.rb
index d503f4ace..bf943637e 100644
--- a/Library/Homebrew/test/test_versions.rb
+++ b/Library/Homebrew/test/test_versions.rb
@@ -50,14 +50,6 @@ class VersionParsingTests < Test::Unit::TestCase
assert_version_nil 'foo'
end
- def test_bad_version
- assert_raises(RuntimeError) do
- Class.new(TestBall) do
- version "versions can't have spaces"
- end.new
- end
- end
-
def test_version_all_dots
assert_version_detected '1.14', 'http://example.com/foo.bar.la.1.14.zip'
end