Fixed reopening of stdout and stderr.
[ruby-vorbistagger.git] / rake / configuretask.rb
1 #
2 # Copyright (c) 2005, 2006 Tilman Sauerbeck (tilman at code-monkey de)
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23 require "rake/tasklib"
24 require "rake/clean"
25 require "yaml"
26 require "fileutils"
27
28 module Rake
29         class ConfigureTask < TaskLib
30                 CACHE_FILE = ".configure_state.yaml"
31
32                 attr_reader :tests
33
34                 def initialize # :yield: self
35                         @tests = TestList.new(load_tests || [])
36
37                         yield self if block_given?
38
39                         define
40                 end
41
42                 # returns the test with the specified name
43                 def [](name)
44                         @tests.find { |t| t.name == name }
45                 end
46
47                 def method_missing(m)
48                         self[m.to_s]
49                 end
50
51                 private
52                 def load_tests
53                         r = YAML.load(File.read(CACHE_FILE)) rescue nil
54
55                         r.is_a?(TestList) ? r : nil
56                 end
57
58                 def define
59                         desc "Remove configure results"
60                         task :clobber_configure do
61                                 FileUtils::Verbose.rm_f(CACHE_FILE)
62                         end
63
64                         task :clobber => :clobber_configure
65
66                         desc "Configure this package"
67                         task :configure => [CACHE_FILE]
68
69                         file CACHE_FILE do
70                                 @tests.each do |t|
71                                         t.on_checking.reverse_each { |b| b.call }
72
73                                         if t.invoke
74                                                 t.on_success.reverse_each { |b| b.call }
75                                         else
76                                                 t.on_failure.reverse_each { |b| b.call }
77                                         end
78                                 end
79
80                                 # store the test results in CACHE_FILE
81                                 File.open(CACHE_FILE, "w") { |f| YAML.dump(@tests, f) }
82                         end
83                 end
84
85                 class TestList < Array
86                         def initialize(stored_tests)
87                                 @stored_tests = stored_tests
88                         end
89
90                         def <<(arg)
91                                 assign_result(arg)
92                                 super
93                         end
94
95                         def push(*args)
96                                 args.each { |a| assign_result(a) }
97                                 super
98                         end
99
100                         def unshift(arg)
101                                 assign_result(arg)
102                                 super
103                         end
104
105                         private
106                         def assign_result(test)
107                                 st = @stored_tests.find { |st| st.name == test.name }
108                                 test.result = st.result unless st.nil?
109                         end
110                 end
111
112                 class Test
113                         attr_reader :name, :on_checking, :on_success, :on_failure
114                         attr_accessor :result
115
116                         def initialize(name, opts = {}) # :yield: self
117                                 @name = name
118                                 @opts = opts
119
120                                 @result = nil
121                                 @on_checking = []
122                                 @on_success = []
123                                 @on_failure = []
124
125                                 if opts[:is_critical]
126                                         @on_failure << lambda { raise }
127                                 end
128
129                                 yield self if block_given?
130                         end
131
132                         def to_yaml_properties
133                                 ["@name", "@result"]
134                         end
135
136                         def invoke
137                         end
138
139                         protected
140                         def can_exec_binary?(bin)
141                                 fork do
142                                         STDOUT.reopen("/dev/null")
143                                         STDERR.reopen("/dev/null")
144
145                                         begin
146                                                 exec(bin)
147                                         rescue SystemCallError
148                                                 exit 255
149                                         ensure
150                                                 tf.close
151                                         end
152                                 end
153
154                                 Process.wait
155
156                                 $?.exitstatus != 255
157                         end
158                 end
159
160                 class FooConfigTest < Test
161                         def initialize(name, opts = {})
162                                 super
163
164                                 @result = {}
165                                 @command = "#{name}-config"
166
167                                 @on_checking << lambda do
168                                         print "checking for #{name}... "
169                                         STDOUT.flush
170                                 end
171
172                                 @on_success << lambda { puts "yes (#{version})" }
173                                 @on_failure << lambda { puts "no" }
174                         end
175
176                         def method_missing(m)
177                                 @result[m]
178                         end
179
180                         def invoke
181                                 return false unless can_exec_command?
182
183                                 [:version, :cflags, :libs].each do |f|
184                                         @result[f] = lookup_flags(f)
185                                 end
186
187                                 true
188                         end
189
190                         protected
191                         def lookup_flags(f)
192                                 tmp = `#{@command} --#{f}`.strip
193                                 $?.exitstatus.zero? ? tmp : nil
194                         end
195
196                         private
197                         def can_exec_command?
198                                 can_exec_binary?(@command)
199                         end
200                 end
201
202                 class PkgConfigTest < FooConfigTest
203                         def initialize(name, opts = {})
204                                 super
205
206                                 @command = "pkg-config"
207                         end
208
209                         protected
210                         def lookup_flags(f)
211                                 f = :modversion if f == :version
212
213                                 tmp = `#{@command} --silence-errors --#{f} #{@name}`.
214                                       strip.tr("\n", "/")
215                                 $?.exitstatus.zero? ? tmp : nil
216                         end
217                 end
218
219                 class CompileTest < Test
220                         TMP_FILE = ".compile_test"
221
222                         def CompileTest.cflags
223                                 @@cflags
224                         end
225
226                         def CompileTest.cflags=(f)
227                                 @@cflags = f
228                         end
229
230                         def initialize(name, code, opts = {})
231                                 super(name, opts)
232
233                                 @code = code
234                         end
235
236                         def invoke
237                                 @result = false
238
239                                 cc = ENV["CC"] || "cc"
240                                 flags = (ENV["CFLAGS"] || "").dup
241                                 flags << " -I" + Config::CONFIG["archdir"]
242
243                                 unless @opts[:try_link]
244                                         flags << " -c"
245                                 end
246
247                                 File.open(TMP_FILE + ".c", "w") do |f|
248                                         f << @code << "\n"
249                                 end
250
251                                 `#{cc} #{flags} #{TMP_FILE}.c -o #{TMP_FILE}.o > /dev/null 2>&1`
252                                 @result = $?.exitstatus.zero?
253                         ensure
254                                 FileUtils.rm_f("#{TMP_FILE}.c")
255                                 FileUtils.rm_f("#{TMP_FILE}.o")
256                         end
257                 end
258
259                 class HaveFuncTest < CompileTest
260                         def initialize(name, includes = [], opts = {})
261                                 super(name, assemble_code(name, includes), opts)
262
263                                 @on_checking << lambda do
264                                         print "checking for #{name}... "
265                                         STDOUT.flush
266                                 end
267
268                                 @on_success << lambda { puts "yes" }
269                                 @on_failure << lambda { puts "no" }
270                         end
271
272                         private
273                         def assemble_code(func, includes)
274                                 header = includes.inject("") do |a, h|
275                                         a << "#include <#{h}>\n"
276                                 end
277
278                                 body =<<EOF
279 int main () {
280         void *foo = (void *) #{func};
281         foo = (void *) 0;
282         return 0;
283 }
284 EOF
285
286                                 header + body
287                         end
288                 end
289         end
290 end