Only pass a single argument to Kernel.exec.
[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 "tempfile"
27 require "fileutils"
28
29 module Rake
30         class ConfigureTask < TaskLib
31                 CACHE_FILE = ".configure_state.yaml"
32
33                 attr_reader :tests
34
35                 def initialize # :yield: self
36                         @tests = TestList.new(load_tests || [])
37
38                         yield self if block_given?
39
40                         define
41                 end
42
43                 # returns the test with the specified name
44                 def [](name)
45                         @tests.find { |t| t.name == name }
46                 end
47
48                 def method_missing(m)
49                         self[m.to_s]
50                 end
51
52                 private
53                 def load_tests
54                         r = YAML.load(File.read(CACHE_FILE)) rescue nil
55
56                         r.is_a?(TestList) ? r : nil
57                 end
58
59                 def define
60                         desc "Remove configure results"
61                         task :clobber_configure do
62                                 FileUtils::Verbose.rm_f(CACHE_FILE)
63                         end
64
65                         task :clobber => :clobber_configure
66
67                         desc "Configure this package"
68                         task :configure => [CACHE_FILE]
69
70                         file CACHE_FILE do
71                                 @tests.each do |t|
72                                         t.on_checking.reverse_each { |b| b.call }
73
74                                         if t.invoke
75                                                 t.on_success.reverse_each { |b| b.call }
76                                         else
77                                                 t.on_failure.reverse_each { |b| b.call }
78                                         end
79                                 end
80
81                                 # store the test results in CACHE_FILE
82                                 File.open(CACHE_FILE, "w") { |f| YAML.dump(@tests, f) }
83                         end
84                 end
85
86                 class TestList < Array
87                         def initialize(stored_tests)
88                                 @stored_tests = stored_tests
89                         end
90
91                         def <<(arg)
92                                 assign_result(arg)
93                                 super
94                         end
95
96                         def push(*args)
97                                 args.each { |a| assign_result(a) }
98                                 super
99                         end
100
101                         def unshift(arg)
102                                 assign_result(arg)
103                                 super
104                         end
105
106                         private
107                         def assign_result(test)
108                                 st = @stored_tests.find { |st| st.name == test.name }
109                                 test.result = st.result unless st.nil?
110                         end
111                 end
112
113                 class Test
114                         attr_reader :name, :on_checking, :on_success, :on_failure
115                         attr_accessor :result
116
117                         def initialize(name, opts = {}) # :yield: self
118                                 @name = name
119                                 @opts = opts
120
121                                 @result = nil
122                                 @on_checking = []
123                                 @on_success = []
124                                 @on_failure = []
125
126                                 if opts[:is_critical]
127                                         @on_failure << lambda { raise }
128                                 end
129
130                                 yield self if block_given?
131                         end
132
133                         def to_yaml_properties
134                                 ["@name", "@result"]
135                         end
136
137                         def invoke
138                         end
139
140                         protected
141                         def can_exec_binary?(bin)
142                                 fork do
143                                         tf = Tempfile.open("configuretask")
144                                         STDOUT.reopen(tf)
145                                         STDERR.reopen(tf)
146
147                                         begin
148                                                 exec(bin)
149                                         rescue SystemCallError
150                                                 exit 255
151                                         ensure
152                                                 tf.close
153                                         end
154                                 end
155
156                                 Process.wait
157
158                                 $?.exitstatus != 255
159                         end
160                 end
161
162                 class FooConfigTest < Test
163                         def initialize(name, opts = {})
164                                 super
165
166                                 @result = {}
167                                 @command = "#{name}-config"
168
169                                 @on_checking << lambda do
170                                         print "checking for #{name}... "
171                                         STDOUT.flush
172                                 end
173
174                                 @on_success << lambda { puts "yes (#{version})" }
175                                 @on_failure << lambda { puts "no" }
176                         end
177
178                         def method_missing(m)
179                                 @result[m]
180                         end
181
182                         def invoke
183                                 return false unless can_exec_command?
184
185                                 [:version, :cflags, :libs].each do |f|
186                                         @result[f] = lookup_flags(f)
187                                 end
188
189                                 true
190                         end
191
192                         protected
193                         def lookup_flags(f)
194                                 tmp = `#{@command} --#{f}`.strip
195                                 $?.exitstatus.zero? ? tmp : nil
196                         end
197
198                         private
199                         def can_exec_command?
200                                 can_exec_binary?(@command)
201                         end
202                 end
203
204                 class PkgConfigTest < FooConfigTest
205                         def initialize(name, opts = {})
206                                 super
207
208                                 @command = "pkg-config"
209                         end
210
211                         protected
212                         def lookup_flags(f)
213                                 f = :modversion if f == :version
214
215                                 tmp = `#{@command} --silence-errors --#{f} #{@name}`.
216                                       strip.tr("\n", "/")
217                                 $?.exitstatus.zero? ? tmp : nil
218                         end
219                 end
220
221                 class CompileTest < Test
222                         TMP_FILE = ".compile_test"
223
224                         def CompileTest.cflags
225                                 @@cflags
226                         end
227
228                         def CompileTest.cflags=(f)
229                                 @@cflags = f
230                         end
231
232                         def initialize(name, code, opts = {})
233                                 super(name, opts)
234
235                                 @code = code
236                         end
237
238                         def invoke
239                                 @result = false
240
241                                 cc = ENV["CC"] || "cc"
242                                 flags = (ENV["CFLAGS"] || "").dup
243                                 flags << " -I" + Config::CONFIG["archdir"]
244
245                                 unless @opts[:try_link]
246                                         flags << " -c"
247                                 end
248
249                                 File.open(TMP_FILE + ".c", "w") do |f|
250                                         f << @code << "\n"
251                                 end
252
253                                 `#{cc} #{flags} #{TMP_FILE}.c -o #{TMP_FILE}.o > /dev/null 2>&1`
254                                 @result = $?.exitstatus.zero?
255                         ensure
256                                 FileUtils.rm_f("#{TMP_FILE}.c")
257                                 FileUtils.rm_f("#{TMP_FILE}.o")
258                         end
259                 end
260
261                 class HaveFuncTest < CompileTest
262                         def initialize(name, includes = [], opts = {})
263                                 super(name, assemble_code(name, includes), opts)
264
265                                 @on_checking << lambda do
266                                         print "checking for #{name}... "
267                                         STDOUT.flush
268                                 end
269
270                                 @on_success << lambda { puts "yes" }
271                                 @on_failure << lambda { puts "no" }
272                         end
273
274                         private
275                         def assemble_code(func, includes)
276                                 header = includes.inject("") do |a, h|
277                                         a << "#include <#{h}>\n"
278                                 end
279
280                                 body =<<EOF
281 int main () {
282         void *foo = (void *) #{func};
283         foo = (void *) 0;
284         return 0;
285 }
286 EOF
287
288                                 header + body
289                         end
290                 end
291         end
292 end