FormulaInstaller: construct new ARGV from an Options collection

The array of options that is passed to the spawned build process is a
combination of the current ARGV, options passed in by a dependent
formula, and an existing install receipt. The objects that are
interacting here each expect the resulting collection to have certain
properties, and the expectations are not consistent.

Clear up this confusing mess by only dealing with Options collections.
This keeps our representation of options uniform across the codebase.

We can remove BuildOptions dependency on HomebrewArgvExtension, which
allows us to pass any Array-like collection to Tab.create. The only
other site inside of FormulaInstaller that uses the array is the #exec
call, and there it is splatted and thus we can substitute our Options
collection there as well.
This commit is contained in:
Jack Nagel 2013-01-23 00:26:28 -06:00
parent 046d802d09
commit cf08b71bf8
8 changed files with 112 additions and 28 deletions

View File

@ -7,7 +7,7 @@ class BuildOptions
include Enumerable include Enumerable
def initialize args def initialize args
@args = Array.new(args).extend(HomebrewArgvExtension) @args = Options.coerce(args)
@options = Options.new @options = Options.new
end end
@ -37,7 +37,7 @@ class BuildOptions
end end
def include? name def include? name
@args.include? '--' + name args.include? '--' + name
end end
def with? name def with? name
@ -55,11 +55,11 @@ class BuildOptions
end end
def head? def head?
@args.flag? '--HEAD' args.include? '--HEAD'
end end
def devel? def devel?
@args.include? '--devel' args.include? '--devel'
end end
def stable? def stable?
@ -68,21 +68,21 @@ class BuildOptions
# True if the user requested a universal build. # True if the user requested a universal build.
def universal? def universal?
@args.include?('--universal') && has_option?('universal') args.include?('--universal') && has_option?('universal')
end end
# Request a 32-bit only build. # Request a 32-bit only build.
# This is needed for some use-cases though we prefer to build Universal # This is needed for some use-cases though we prefer to build Universal
# when a 32-bit version is needed. # when a 32-bit version is needed.
def build_32_bit? def build_32_bit?
@args.include?('--32-bit') && has_option?('32-bit') args.include?('--32-bit') && has_option?('32-bit')
end end
def used_options def used_options
Options.new((as_flags & @args.options_only).map { |o| Option.new(o) }) Options.new(@options & @args)
end end
def unused_options def unused_options
Options.new((as_flags - @args.options_only).map { |o| Option.new(o) }) Options.new(@options - @args)
end end
end end

View File

@ -138,7 +138,7 @@ module Dependable
end end
def options def options
Options.new((tags - RESERVED_TAGS).map { |o| Option.new(o) }) Options.coerce(tags - RESERVED_TAGS)
end end
end end

View File

@ -690,7 +690,7 @@ private
end end
def build def build
@build ||= BuildOptions.new(ARGV) @build ||= BuildOptions.new(ARGV.options_only)
end end
def url val=nil, specs=nil def url val=nil, specs=nil

View File

@ -17,7 +17,7 @@ class FormulaInstaller
@f = ff @f = ff
@show_header = false @show_header = false
@ignore_deps = ARGV.ignore_deps? || ARGV.interactive? @ignore_deps = ARGV.ignore_deps? || ARGV.interactive?
@options = [] @options = Options.new
@@attempted ||= Set.new @@attempted ||= Set.new
@ -247,6 +247,17 @@ class FormulaInstaller
@build_time ||= Time.now - @start_time unless pour_bottle? or ARGV.interactive? or @start_time.nil? @build_time ||= Time.now - @start_time unless pour_bottle? or ARGV.interactive? or @start_time.nil?
end end
def build_argv
@build_argv ||= begin
opts = Options.coerce(ARGV.options_only)
unless opts.include? '--fresh'
opts.concat(options) # from a dependent formula
opts.concat((tab.used_options rescue [])) # from a previous install
end
opts << '--build-from-source' # don't download bottle
end
end
def build def build
FileUtils.rm Dir["#{HOMEBREW_LOGS}/#{f}/*"] FileUtils.rm Dir["#{HOMEBREW_LOGS}/#{f}/*"]
@ -261,16 +272,6 @@ class FormulaInstaller
# I'm guessing this is not a good way to do this, but I'm no UNIX guru # I'm guessing this is not a good way to do this, but I'm no UNIX guru
ENV['HOMEBREW_ERROR_PIPE'] = write.to_i.to_s ENV['HOMEBREW_ERROR_PIPE'] = write.to_i.to_s
args = ARGV.clone
args.concat(tab.used_options.as_flags) unless tab.nil? or args.include? '--fresh'
# FIXME: enforce the download of the non-bottled package
# in the spawned Ruby process.
args << '--build-from-source'
# Add any options that were passed into this FormulaInstaller instance.
# Usually this represents options being passed by a dependent formula.
args.concat options
args.uniq!
fork do fork do
begin begin
read.close read.close
@ -281,7 +282,7 @@ class FormulaInstaller
'-rbuild', '-rbuild',
'--', '--',
f.path, f.path,
*args.options_only *build_argv
rescue Exception => e rescue Exception => e
Marshal.dump(e, write) Marshal.dump(e, write)
write.close write.close
@ -300,7 +301,7 @@ class FormulaInstaller
raise "Empty installation" if Dir["#{f.prefix}/*"].empty? raise "Empty installation" if Dir["#{f.prefix}/*"].empty?
Tab.create(f, args).write # INSTALL_RECEIPT.json Tab.create(f, build_argv).write # INSTALL_RECEIPT.json
rescue Exception => e rescue Exception => e
ignore_interrupts do ignore_interrupts do

View File

@ -4,9 +4,8 @@ class Option
attr_reader :name, :description, :flag attr_reader :name, :description, :flag
def initialize(name, description=nil) def initialize(name, description=nil)
@name = name.to_s[/^(?:--)?(.+)$/, 1] @name, @flag = split_name(name)
@description = description.to_s @description = description.to_s
@flag = "--#{@name}"
end end
def to_s def to_s
@ -29,6 +28,19 @@ class Option
def hash def hash
name.hash name.hash
end end
private
def split_name(name)
case name
when /^-[a-zA-Z]$/
[name[1..1], name]
when /^--(.+)$/
[$1, name]
else
[name, "--#{name}"]
end
end
end end
class Options class Options
@ -55,6 +67,10 @@ class Options
Options.new(@options - o) Options.new(@options - o)
end end
def &(o)
Options.new(@options & o)
end
def *(arg) def *(arg)
@options.to_a * arg @options.to_a * arg
end end
@ -71,8 +87,22 @@ class Options
any? { |opt| opt == o || opt.name == o || opt.flag == o } any? { |opt| opt == o || opt.name == o || opt.flag == o }
end end
def concat(o)
o.each { |opt| @options << opt }
self
end
def to_a def to_a
@options.to_a @options.to_a
end end
alias_method :to_ary, :to_a alias_method :to_ary, :to_a
def self.coerce(arg)
case arg
when self then arg
when Option then new << arg
when Array then new(arg.map { |a| Option.new(a.to_s) })
else raise TypeError, "Cannot convert #{arg.inspect} to Options"
end
end
end end

View File

@ -77,11 +77,11 @@ class Tab < OpenStruct
end end
def used_options def used_options
Options.new(super.map { |o| Option.new(o) }) Options.coerce(super)
end end
def unused_options def unused_options
Options.new(super.map { |o| Option.new(o) }) Options.coerce(super)
end end
def options def options

View File

@ -3,7 +3,7 @@ require 'build_options'
class BuildOptionsTests < Test::Unit::TestCase class BuildOptionsTests < Test::Unit::TestCase
def setup def setup
args = %w{--with-foo --with-bar --without-qux}.extend(HomebrewArgvExtension) args = %w{--with-foo --with-bar --without-qux}
@build = BuildOptions.new(args) @build = BuildOptions.new(args)
@build.add("with-foo") @build.add("with-foo")
@build.add("with-bar") @build.add("with-bar")

View File

@ -38,6 +38,12 @@ class OptionTests < Test::Unit::TestCase
assert_empty @option.description assert_empty @option.description
assert_equal "foo", Option.new("foo", "foo").description assert_equal "foo", Option.new("foo", "foo").description
end end
def test_preserves_short_options
option = Option.new("-d")
assert_equal "-d", option.flag
assert_equal "d", option.name
end
end end
class OptionsTests < Test::Unit::TestCase class OptionsTests < Test::Unit::TestCase
@ -87,4 +93,51 @@ class OptionsTests < Test::Unit::TestCase
@options << option @options << option
assert_equal [option], @options.to_ary assert_equal [option], @options.to_ary
end end
def test_concat_array
option = Option.new("foo")
@options.concat([option])
assert @options.include?(option)
assert_equal [option], @options.to_a
end
def test_concat_options
option = Option.new("foo")
opts = Options.new
opts << option
@options.concat(opts)
assert @options.include?(option)
assert_equal [option], @options.to_a
end
def test_concat_returns_self
assert_same @options, (@options.concat([]))
end
def test_intersection
foo, bar, baz = %w{foo bar baz}.map { |o| Option.new(o) }
options = Options.new << foo << bar
@options << foo << baz
assert_equal [foo], (@options & options).to_a
end
def test_coerce_with_options
assert_same @options, Options.coerce(@options)
end
def test_coerce_with_option
option = Option.new("foo")
assert_equal option, Options.coerce(option).to_a.first
end
def test_coerce_with_array
array = %w{--foo --bar}
option1 = Option.new("foo")
option2 = Option.new("bar")
assert_equal [option1, option2].sort, Options.coerce(array).to_a.sort
end
def test_coerce_raises_for_inappropriate_types
assert_raises(TypeError) { Options.coerce(1) }
end
end end