bake 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+