+#
+# Copyright (c) 2005, 2006 Tilman Sauerbeck (tilman at code-monkey de)
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+require "rake/tasklib"
+require "rake/clean"
+require "yaml"
+require "fileutils"
+
+module Rake
+ class ConfigureTask < TaskLib
+ CACHE_FILE = ".configure_state.yaml"
+
+ attr_reader :tests
+
+ def initialize # :yield: self
+ @tests = TestList.new(load_tests || [])
+
+ yield self if block_given?
+
+ define
+ end
+
+ # returns the test with the specified name
+ def [](name)
+ @tests.find { |t| t.name == name }
+ end
+
+ def method_missing(m)
+ self[m.to_s]
+ end
+
+ private
+ def load_tests
+ r = YAML.load(File.read(CACHE_FILE)) rescue nil
+
+ r.is_a?(TestList) ? r : nil
+ end
+
+ def define
+ desc "Remove configure results"
+ task :clobber_configure do
+ FileUtils::Verbose.rm_f(CACHE_FILE)
+ end
+
+ task :clobber => :clobber_configure
+
+ desc "Configure this package"
+ task :configure => [CACHE_FILE]
+
+ file CACHE_FILE do
+ @tests.each do |t|
+ t.on_checking.reverse_each { |b| b.call }
+
+ if t.invoke
+ t.on_success.reverse_each { |b| b.call }
+ else
+ t.on_failure.reverse_each { |b| b.call }
+ end
+ end
+
+ # store the test results in CACHE_FILE
+ File.open(CACHE_FILE, "w") { |f| YAML.dump(@tests, f) }
+ end
+ end
+
+ class TestList < Array
+ def initialize(stored_tests)
+ @stored_tests = stored_tests
+ end
+
+ def <<(arg)
+ assign_result(arg)
+ super
+ end
+
+ def push(*args)
+ args.each { |a| assign_result(a) }
+ super
+ end
+
+ def unshift(arg)
+ assign_result(arg)
+ super
+ end
+
+ private
+ def assign_result(test)
+ st = @stored_tests.find { |st| st.name == test.name }
+ test.result = st.result unless st.nil?
+ end
+ end
+
+ class Test
+ attr_reader :name, :on_checking, :on_success, :on_failure
+ attr_accessor :result
+
+ def initialize(name, opts = {}) # :yield: self
+ @name = name
+ @opts = opts
+
+ @result = nil
+ @on_checking = []
+ @on_success = []
+ @on_failure = []
+
+ if opts[:is_critical]
+ @on_failure << lambda { raise }
+ end
+
+ yield self if block_given?
+ end
+
+ def to_yaml_properties
+ ["@name", "@result"]
+ end
+
+ def invoke
+ end
+
+ protected
+ def can_exec_binary?(bin)
+ fork do
+ STDOUT.reopen("/dev/null")
+ STDERR.reopen("/dev/null")
+
+ begin
+ exec(bin)
+ rescue SystemCallError
+ exit 255
+ end
+ end
+
+ Process.wait
+
+ $?.exitstatus != 255
+ end
+ end
+
+ class FooConfigTest < Test
+ def initialize(name, opts = {})
+ super
+
+ @result = {}
+ @command = "#{name}-config"
+
+ @on_checking << lambda do
+ print "checking for #{name}... "
+ STDOUT.flush
+ end
+
+ @on_success << lambda { puts "yes (#{version})" }
+ @on_failure << lambda { puts "no" }
+ end
+
+ def method_missing(m)
+ @result[m]
+ end
+
+ def invoke
+ return false unless can_exec_command?
+
+ begin
+ [:version, :cflags, :libs].each do |f|
+ @result[f] = lookup_flags(f)
+ end
+ rescue Exception
+ @result.clear
+ end
+
+ !@result.empty?
+ end
+
+ protected
+ def lookup_flags(f)
+ tmp = `#{@command} --#{f}`.strip
+
+ raise unless $?.exitstatus.zero?
+ tmp
+ end
+
+ private
+ def can_exec_command?
+ can_exec_binary?(@command)
+ end
+ end
+
+ class PkgConfigTest < FooConfigTest
+ def initialize(name, opts = {})
+ super
+
+ @command = "pkg-config"
+ end
+
+ protected
+ def lookup_flags(f)
+ f = :modversion if f == :version
+
+ tmp = `#{@command} --silence-errors --#{f} #{@name}`.
+ strip.tr("\n", "/")
+
+ raise unless $?.exitstatus.zero?
+ tmp
+ end
+ end
+
+ class CompileTest < Test
+ TMP_FILE = ".compile_test"
+
+ def CompileTest.cflags
+ @@cflags
+ end
+
+ def CompileTest.cflags=(f)
+ @@cflags = f
+ end
+
+ def initialize(name, code, opts = {})
+ super(name, opts)
+
+ @code = code
+ end
+
+ def invoke
+ @result = false
+
+ cc = ENV["CC"] || "cc"
+ flags = (ENV["CFLAGS"] || "").dup
+ flags << " -I" + Config::CONFIG["archdir"]
+
+ unless @opts[:try_link]
+ flags << " -c"
+ end
+
+ File.open(TMP_FILE + ".c", "w") do |f|
+ f << @code << "\n"
+ end
+
+ `#{cc} #{flags} #{TMP_FILE}.c -o #{TMP_FILE}.o > /dev/null 2>&1`
+ @result = $?.exitstatus.zero?
+ ensure
+ FileUtils.rm_f("#{TMP_FILE}.c")
+ FileUtils.rm_f("#{TMP_FILE}.o")
+ end
+ end
+
+ class HaveFuncTest < CompileTest
+ def initialize(name, includes = [], opts = {})
+ super(name, assemble_code(name, includes), opts)
+
+ @on_checking << lambda do
+ print "checking for #{name}... "
+ STDOUT.flush
+ end
+
+ @on_success << lambda { puts "yes" }
+ @on_failure << lambda { puts "no" }
+ end
+
+ private
+ def assemble_code(func, includes)
+ header = includes.inject("") do |a, h|
+ a << "#include <#{h}>\n"
+ end
+
+ body =<<EOF
+int main () {
+ void *foo = (void *) #{func};
+ foo = (void *) 0;
+ return 0;
+}
+EOF
+
+ header + body
+ end
+ end
+ end
+end