bake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ require 'bake/string_utils.rb'
2
+
3
+ module Bake
4
+ class Context
5
+ def initialize(scheme_loader)
6
+ @scheme_loader = scheme_loader
7
+ @current = nil
8
+ end
9
+
10
+ def context_eval(target, str = nil, file = nil, &block)
11
+ old = @current
12
+ begin
13
+ @current = target
14
+ if str
15
+ instance_eval(str, file ? file : '')
16
+ else
17
+ instance_eval(&block)
18
+ end
19
+ ensure
20
+ @current = old
21
+ end
22
+ end
23
+
24
+ def using(scheme_and_toolset)
25
+ scheme_name, toolset_name = scheme_and_toolset.to_s.split('.')
26
+ toolset_name = 'default' if !toolset_name
27
+ scheme = @scheme_loader.scheme(scheme_name)
28
+ raise "invalid scheme '#{scheme_name}'" if !scheme
29
+ scheme.toolset = toolset_name
30
+ end
31
+
32
+ def glob(*args)
33
+ has_options = args.last.respond_to?(:to_hash)
34
+ options = has_options ? args.pop.to_hash : {}
35
+ exclude = array_opt(options, :exclude)
36
+ files = []
37
+ args.each do |pat|
38
+ matches = Dir[pat]
39
+ matches.each do |file|
40
+ if !exclude.find { |exc| File.fnmatch(exc, file) }
41
+ files.push(file)
42
+ end
43
+ end
44
+ end
45
+ if block_given?
46
+ for i in 0...files.size
47
+ yield(files[i])
48
+ end
49
+ end
50
+ return files
51
+ end
52
+
53
+ def macro(name, &block)
54
+ raise "no block given for macro '#{name}'" if !block
55
+ @current.opt(:macros => { :name => name, :block => block })
56
+ end
57
+
58
+ def method_missing(method, *args, &block)
59
+ @scheme_loader.schemes.each do |name, scheme|
60
+ if scheme.has_constructor?(method)
61
+ target = scheme.construct(method, @current, *args)
62
+ context_eval(target, &block) if block
63
+ return target
64
+ end
65
+ end
66
+ return object_method_missing(method, *args, &block) if !@current
67
+ macro = @current[:macros].find { |macro| macro[:name] == method }
68
+ if macro
69
+ return instance_exec(*args, &macro[:block])
70
+ end
71
+ if @current.respond_to?(method)
72
+ return @current.send(method, *args, &block)
73
+ end
74
+ raise "undefined symbol '#{method}'"
75
+ end
76
+
77
+ private
78
+ def array_opt(options, name)
79
+ opt = options[name]
80
+ return [] if !opt
81
+ if opt.respond_to?(:to_ary)
82
+ opt = opt.to_ary
83
+ else
84
+ opt = [ opt ]
85
+ end
86
+ end
87
+
88
+ # took this implementation from http://tinyurl.com/gzrtn
89
+ def instance_exec(*args, &block)
90
+ mname = "__instance_exec_#{Thread.current.object_id.abs}"
91
+ class << self; self end.class_eval{ define_method(mname, &block) }
92
+ begin
93
+ ret = send(mname, *args)
94
+ ensure
95
+ class << self; self end.class_eval{ undef_method(mname) } rescue nil
96
+ end
97
+ ret
98
+ end
99
+ end
100
+ end
101
+
@@ -0,0 +1,460 @@
1
+ require 'bake/toolset'
2
+ require 'bake/target'
3
+ require 'fileutils'
4
+ require 'set'
5
+
6
+ class CppToolsetBase < Bake::Toolset
7
+ def build(target)
8
+ if stale?(target)
9
+ FileUtils.mkdir_p(target[:outdir])
10
+ if target.is_a?(Cpp::Object)
11
+ compile(target)
12
+ # refresh the .includes file
13
+ includes = target.includes
14
+ filename = "#{target[:outdir]}/#{target.src.basename}.includes"
15
+ File.open(filename, "w") do |file|
16
+ includes.each { |inc| file.puts(inc) }
17
+ end
18
+ elsif target.is_a?(Cpp::Library)
19
+ ar(target)
20
+ elsif target.is_a?(Cpp::Executable)
21
+ link(target)
22
+ elsif target.is_a?(Cpp::Runner)
23
+ run(target)
24
+ else
25
+ raise "unknown Cpp target of class '#{target.class}"
26
+ end
27
+ end
28
+ end
29
+
30
+ def stale?(target)
31
+ t = Time.now
32
+ output_files(target).each do |out|
33
+ return true if !File.exists?(out)
34
+ out_time = File.mtime(out)
35
+ t = out_time if out_time < t
36
+ end
37
+ if target.is_a?(Cpp::Object)
38
+ return true if File.mtime(target.src) > t
39
+ target.includes.each { |file| return true if File.mtime(file) > t }
40
+ elsif target.is_a?(Cpp::Runner)
41
+ return File.mtime(output_files(target.deps[0])[0]) > t
42
+ else
43
+ is_linked = target.is_a?(Cpp::Executable) ||
44
+ (target.is_a?(Cpp::Library) && target[:libtype] == 'dynamic')
45
+ target.children.each do |child|
46
+ output_files(child).each do |file|
47
+ return true if File.mtime(file) > t
48
+ end
49
+ end
50
+ target.deps.each do |dep|
51
+ if is_linked && dep.is_a?(Cpp::Library) &&
52
+ dep[:libtype] != 'dynamic'
53
+ output_files(dep).each do |file|
54
+ return true if File.mtime(file) > t
55
+ end
56
+ end
57
+ end
58
+ end
59
+ return false
60
+ end
61
+
62
+ def clean(target)
63
+ output_files(target).each { |file| FileUtils.rm_f(file) }
64
+ if target.is_a?(Cpp::Object)
65
+ filename = "#{target[:outdir]}/#{target.src.basename}.includes"
66
+ FileUtils.rm_f(filename)
67
+ end
68
+ end
69
+
70
+ def output_files(target)
71
+ out = output(target)
72
+ return [] if !out
73
+ return out.to_a if out.respond_to?(:to_a)
74
+ return [ out ]
75
+ end
76
+ end
77
+
78
+ class GccToolsetBase < CppToolsetBase
79
+ def output(target)
80
+ if target.is_a?(Cpp::Object)
81
+ return obj_fn(target)
82
+ elsif target.is_a?(Cpp::Library)
83
+ return lib_fn(target)
84
+ elsif target.is_a?(Cpp::Executable)
85
+ return exe_fn(target)
86
+ elsif target.is_a?(Cpp::Runner)
87
+ return runner_fn(target)
88
+ end
89
+ return nil
90
+ end
91
+
92
+ def compile(obj)
93
+ src = obj.src
94
+ output = obj_fn(obj)
95
+ flags = build_flags('-I', obj[:incdirs] + obj[:sysincdirs]) +
96
+ build_flags('-D', obj[:defines])
97
+ flags += obj[:cflags].join(' ')
98
+ flags += ' -fno-rtti' if !obj[:rtti?]
99
+ flags += ' -O3' if obj[:optimizations?]
100
+ flags += ' -g' if obj[:debug_symbols?]
101
+ sh("g++ -c -o #{output} #{flags} #{src}")
102
+ end
103
+
104
+ def ar(lib)
105
+ obj_str, lib_str, flags = process_inputs(lib)
106
+
107
+ output = lib_fn(lib)
108
+ if lib[:libtype] == 'dynamic'
109
+ sh("g++ #{flags} -shared -Wl,-soname,#{output} -o #{output} #{obj_str} #{lib_str}")
110
+ else
111
+ sh("ar rcs #{output} #{obj_str}")
112
+ end
113
+ end
114
+
115
+ def link(exe)
116
+ obj_str, lib_str, flags = process_inputs(exe)
117
+
118
+ output = exe_fn(exe)
119
+ sh("g++ #{flags} -o #{output} #{obj_str} #{lib_str}")
120
+ end
121
+
122
+ def run(runner)
123
+ sh(exe_fn(runner.deps.first))
124
+ FileUtils.touch(runner_fn(runner))
125
+ end
126
+
127
+ private
128
+ def build_flags(flag, args)
129
+ return args.inject('') { |str, arg| str + "#{flag}#{arg} " }
130
+ end
131
+
132
+ def obj_fn(obj)
133
+ src = obj.src
134
+ basename = File.basename(src, File.extname(src)) + '.o'
135
+ return File.join(obj[:outdir], basename)
136
+ end
137
+
138
+ def lib_fn(lib)
139
+ if lib[:libtype] == 'dynamic'
140
+ return File.join(lib[:outdir], "#{lib.name}.so")
141
+ end
142
+ return File.join(lib[:outdir], "lib#{lib.name}.a")
143
+ end
144
+
145
+ def exe_fn(exe)
146
+ return File.join(exe[:outdir], exe.name)
147
+ end
148
+
149
+ def runner_fn(runner)
150
+ return "#{exe_fn(runner.deps.first)}.success"
151
+ end
152
+
153
+ def process_inputs(target)
154
+ output = output(target)
155
+ obj_str = ''
156
+ lib_str = ''
157
+ libdirs = []
158
+ (target.children + target.deps).each do |input|
159
+ if input.is_a?(Cpp::Object)
160
+ fn = obj_fn(input)
161
+ obj_str += fn + ' '
162
+ elsif input.is_a?(Cpp::SystemLibrary)
163
+ lib_str += '-l' + input.file + ' '
164
+ elsif input.is_a?(Cpp::Library)
165
+ fn = lib_fn(input)
166
+ if input[:libtype] == 'dynamic'
167
+ obj_str += fn + ' '
168
+ else
169
+ lib_str += '-l' + input.name + ' '
170
+ libdir = File.dirname(fn)
171
+ libdirs << libdir if !libdirs.include?(libdir)
172
+ end
173
+ end
174
+ end
175
+
176
+ libdirs.concat(target[:libdirs])
177
+ flags = build_flags('-L', libdirs)
178
+
179
+ return [ obj_str, lib_str, flags ]
180
+ end
181
+ end
182
+
183
+ module Cpp
184
+ class Darwin < GccToolsetBase
185
+ def ar(lib)
186
+ obj_str, lib_str, flags = process_inputs(lib)
187
+
188
+ output = lib_fn(lib)
189
+ if lib[:libtype] == 'dynamic'
190
+ sh("g++ #{flags} -dynamiclib -o #{output} #{obj_str} #{lib_str}")
191
+ else
192
+ sh("ar rcs #{output} #{obj_str}")
193
+ end
194
+ end
195
+
196
+ private
197
+ def lib_fn(lib)
198
+ if lib[:libtype] == 'dynamic'
199
+ return File.join(lib[:outdir], "#{lib.name}.dylib")
200
+ end
201
+ return File.join(lib[:outdir], "lib#{lib.name}.a")
202
+ end
203
+ end
204
+
205
+ class Gcc < GccToolsetBase
206
+ end
207
+
208
+ class Msvc6 < CppToolsetBase
209
+ def output(target)
210
+ if target.is_a?(Object)
211
+ return obj_fn(target)
212
+ elsif target.is_a?(Library)
213
+ return lib_fn(target)
214
+ elsif target.is_a?(Executable)
215
+ return exe_fn(target)
216
+ end
217
+ return nil
218
+ end
219
+
220
+ def compile(obj)
221
+ src = obj.src
222
+ output = obj_fn(obj)
223
+ flags = build_flags('/I', obj[:incdirs] + obj[:sysincdirs]) +
224
+ build_flags('/D', obj[:defines])
225
+ flags += obj[:cflags].join(' ')
226
+ flags += ' /MD'
227
+ flags += ' /EHsc' if obj[:exceptions?]
228
+ flags += ' /GR' if obj[:rtti?]
229
+ flags += obj[:optimizations?] ? ' /O2' : ' /Od'
230
+ flags += ' /Z7' if obj[:debug_symbols?]
231
+ sh("cl /nologo #{flags} /c /Fo#{output} /Tp#{src}")
232
+ end
233
+
234
+ def ar(lib)
235
+ obj_str, flags = process_inputs(lib)
236
+
237
+ output = lib_fn(lib)
238
+ if lib[:libtype] == 'dynamic'
239
+ flags += ' /debug' if lib[:debug_symbols?]
240
+ sh("link /nologo /dll #{flags} /out:#{output[1]} #{obj_str}")
241
+ else
242
+ sh("lib /nologo /out:#{output} #{obj_str}")
243
+ end
244
+ end
245
+
246
+ def link(exe)
247
+ obj_str, flags = process_inputs(exe)
248
+
249
+ output = exe_fn(exe)
250
+ flags += ' /debug' if exe[:debug_symbols?]
251
+ sh("link /nologo /subsystem:console #{flags} /out:#{output} #{obj_str}")
252
+ end
253
+
254
+ def run(runner)
255
+ sh(exe_fn(runner.deps.first))
256
+ FileUtils.touch(runner_fn(runner))
257
+ end
258
+
259
+ private
260
+ def build_flags(flag, args)
261
+ return args.inject('') { |str, arg| str + "#{flag}#{arg} " }
262
+ end
263
+
264
+ def obj_fn(obj)
265
+ src = obj.src
266
+ basename = File.basename(src, File.extname(src)) + '.obj'
267
+ return File.join(obj[:outdir], basename)
268
+ end
269
+
270
+ def lib_fn(lib)
271
+ if lib[:libtype] == 'dynamic'
272
+ return [File.join(lib[:outdir], "#{lib.name}.lib"),
273
+ File.join(lib[:outdir], "#{lib.name}.dll"),
274
+ File.join(lib[:outdir], "#{lib.name}.exp")]
275
+ end
276
+ return File.join(lib[:outdir], "#{lib.name}.lib")
277
+ end
278
+
279
+ def exe_fn(exe)
280
+ return File.join(exe[:outdir], exe.name + '.exe')
281
+ end
282
+
283
+ def runner_fn(runner)
284
+ return "#{exe_fn(runner.deps.first)}.success"
285
+ end
286
+
287
+ def process_inputs(target)
288
+ output = output(target)
289
+ obj_str = ''
290
+ libdirs = []
291
+ static_lib = target.is_a?(Library) && target[:libtype] == 'static'
292
+ (target.children + target.deps).each do |input|
293
+ if input.is_a?(Object)
294
+ fn = obj_fn(input)
295
+ obj_str += fn + ' '
296
+ elsif input.is_a?(SystemLibrary)
297
+ if !static_lib
298
+ obj_str += input.file + '.lib '
299
+ end
300
+ elsif input.is_a?(Library)
301
+ if !static_lib
302
+ if input[:libtype] == 'dynamic'
303
+ fn = lib_fn(input)[0]
304
+ else
305
+ fn = lib_fn(input)
306
+ end
307
+ libdir = File.dirname(fn)
308
+ libdirs << libdir if !libdirs.include?(libdir)
309
+ obj_str += File.basename(fn) + ' '
310
+ end
311
+ end
312
+ end
313
+
314
+ if !static_lib
315
+ target[:libs].each { |lib| obj_str += lib + '.lib ' }
316
+ end
317
+ libdirs.concat(target[:libdirs])
318
+ flags = build_flags('/LIBPATH:', libdirs)
319
+
320
+ return [ obj_str, flags ]
321
+ end
322
+ end
323
+
324
+ class Library < Bake::Target
325
+ ACCESSORS = :lib
326
+
327
+ def initialize(parent, name)
328
+ super(parent)
329
+ @name = name
330
+ default(:libtype => 'static')
331
+ default(:debug_symbols? => false)
332
+ end
333
+
334
+ def src(*args)
335
+ return args.flatten.collect { |file| Cpp::Object.new(self, file) }
336
+ end
337
+ end
338
+
339
+ class SystemLibrary < Bake::Target
340
+ ACCESSORS = :syslib
341
+
342
+ attr_reader :file
343
+
344
+ def initialize(parent, name, file)
345
+ super(parent)
346
+ @name = name
347
+ @file = file
348
+ opt(:built? => true)
349
+ end
350
+ end
351
+
352
+ class Executable < Bake::Target
353
+ ACCESSORS = :exe
354
+
355
+ def initialize(parent, name)
356
+ super(parent)
357
+ @name = name
358
+ default(:debug_symbols? => false)
359
+ end
360
+
361
+ def src(*args)
362
+ return args.flatten.collect { |file| Cpp::Object.new(self, file) }
363
+ end
364
+ end
365
+
366
+ class Source < Bake::Target
367
+ attr_reader :output
368
+
369
+ def initialize(parent, src)
370
+ super(parent)
371
+ @output = src
372
+ opt(:built? => true)
373
+ end
374
+ end
375
+
376
+ class Object < Bake::Target
377
+ ACCESSORS = :obj
378
+
379
+ def initialize(parent, src = nil)
380
+ super(parent)
381
+ return if !src
382
+ if src.instance_of?(String)
383
+ children << Source.new(self, src)
384
+ else
385
+ children << src
386
+ end
387
+ default(:exceptions? => true)
388
+ default(:rtti? => true)
389
+ default(:optimizations? => false)
390
+ default(:multithreaded? => true)
391
+ end
392
+
393
+ def src
394
+ return children.first.output
395
+ end
396
+
397
+ def includes(recalc = false)
398
+ incfile = "#{get(:outdir)}/#{src}.includes"
399
+ if !@includes || recalc
400
+ if File.exists?(incfile)
401
+ @includes = load_includes(incfile)
402
+ else
403
+ @includes = calc_includes(src, Set.new)
404
+ end
405
+ end
406
+ return @includes
407
+ end
408
+
409
+ private
410
+ def load_includes(incfile)
411
+ includes = Set.new
412
+ file = File.open(incfile)
413
+ while line = file.gets
414
+ line.chomp!
415
+ includes << line if !line.empty?
416
+ end
417
+ file.close
418
+ return includes
419
+ end
420
+
421
+ def calc_includes(filename, include_fns)
422
+ new_include_fns = Set.new
423
+ File.open(filename) do |file|
424
+ incdirs = get(:incdirs)
425
+ while line = file.gets
426
+ if line =~ /\s*#\s*include\s+("|<)([^"]+)("|>)/
427
+ inc = $2
428
+ incdirs.each do |include_dir|
429
+ fn = File.join(include_dir, inc)
430
+ break if include_fns.include?(fn)
431
+ if File.exists?(fn)
432
+ new_include_fns << fn
433
+ include_fns << fn
434
+ break
435
+ end
436
+ end
437
+ end
438
+ end
439
+ end
440
+
441
+ new_include_fns.each do |inc|
442
+ calc_includes(inc, include_fns)
443
+ end
444
+ return include_fns
445
+ end
446
+ end
447
+
448
+ class Runner < Bake::Target
449
+ ACCESSORS = :run
450
+
451
+ def initialize(parent, exe)
452
+ super(parent)
453
+ target = dep(exe)[0]
454
+ if !target.is_a?(Executable)
455
+ raise "target '#{target.name}' is not an executable"
456
+ end
457
+ end
458
+ end
459
+ end
460
+