diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index d57f5ddc3b4a8b42833738bbadcb5d71bc700407..b5299c0384aa2667873ad88de9d77673a86dd540 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 a6305eaaff32285cc60ebcde83f1ecc74f88e311..eddacf08ddea999a8b467c25297ce5a78af5daa4 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 4e63440a6f6e26c79b0ed780d4cc65872a6019a5..daf9dd6d013d45afea7ac5e390884c679110f412 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 30ef41589d9beed1703567e4a1ef4e7b41379413..434dd3ac12d4ab6a1d627eca7d83c662fa3afb04 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 0000000000000000000000000000000000000000..be4c38c641289975930de67e0f30a98b26440b02 --- /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 d503f4acec23eaa3035a3cd66ff3b7cd17ff7e07..bf943637ec90f0b9c5a8667c24fe5c559b9324c0 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