daedalus-core 0.0.1
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.
- checksums.yaml +15 -0
- data/LICENSE +22 -0
- data/README.md +13 -0
- data/daedalus-core.gemspec +28 -0
- data/lib/daedalus.rb +1016 -0
- data/lib/daedalus/dependency_grapher.rb +448 -0
- data/lib/daedalus/version.rb +3 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTZjNzQ4ODQ3NDM2MDY4NGY2NjdmMjljYTFlMGY5YmQ3OTA4NDUzYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
Y2E3YzA2Y2ZjZDU0ZGUyYmM4MzQzNGVkZGI0MjZhOTkyYmUxODgxMQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZDI2OGRkNTIxZGI1ZmExOGNhOTVmYjY5NzRlZjM2ZDdmZTk0NjFkZTUyZWVi
|
10
|
+
ZjgyNmE3YjAzOTc0YzUwNzRmYjgwNzkwYTZiZmI3ZTYxNGJjYjBjOWM5YmEx
|
11
|
+
N2MxM2VlMTc1YTliMTViMTNkMGE2NzY1NDMzNjJlMTU2NTY1MjQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjdkYzU2NGQxNWQ4NDcxYjQzMjZmMWY2NTU2ZjI5ZDc4NDZiZWI0OWE1MDkx
|
14
|
+
NjNkZjgyMjkxNGIyY2FmNTcwYjk5NDA5MWM0ZGE4OTM2MmZjOTc5Mzk5MmZj
|
15
|
+
N2Q4OTc2NmFhYWU5YjViNDRhNmRjMTk1YzA0ODViZThiZjc1MTg=
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brian Shirai. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
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
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Daedalus
|
2
|
+
|
3
|
+
Daedalus |ˈdedl-əs| Greek Mythology
|
4
|
+
a craftsman, considered the inventor of carpentry, who is said to have
|
5
|
+
built the labyrinth for Minos, king of Crete. Minos imprisoned him and
|
6
|
+
his son Icarus, but they escaped using wings that Daedalus made and
|
7
|
+
fastened with wax. Icarus, however, flew too near the sun and was killed.
|
8
|
+
|
9
|
+
|
10
|
+
In other words, he built things for a living.
|
11
|
+
|
12
|
+
Daedalus is an extraction from the Rubinius build system. Very few of the
|
13
|
+
features have been implemented at this point. It is very very alpha.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'daedalus/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "daedalus-core"
|
6
|
+
gem.version = "#{Daedalus::VERSION}"
|
7
|
+
gem.authors = ["Brian Shirai"]
|
8
|
+
gem.email = ["brixen@gmail.com"]
|
9
|
+
gem.homepage = "https://github.com/rubinius/daedalus-core"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) unless File.extname(f) == ".bat" }.compact
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.summary = <<-EOS
|
16
|
+
Daedalus is a build system extracted from Rubinius.
|
17
|
+
EOS
|
18
|
+
gem.has_rdoc = true
|
19
|
+
gem.extra_rdoc_files = %w[ README.md LICENSE ]
|
20
|
+
gem.rubygems_version = %q{1.3.5}
|
21
|
+
|
22
|
+
gem.rdoc_options << '--title' << 'Daedalus Gem' <<
|
23
|
+
'--main' << 'README' <<
|
24
|
+
'--line-numbers'
|
25
|
+
|
26
|
+
gem.add_development_dependency "rake", "~> 0.9"
|
27
|
+
gem.add_development_dependency "rspec", "~> 2.8"
|
28
|
+
end
|
data/lib/daedalus.rb
ADDED
@@ -0,0 +1,1016 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
=begin
|
3
|
+
|
4
|
+
Daedalus |ˈdedl-əs| Greek Mythology
|
5
|
+
a craftsman, considered the inventor of carpentry, who is said to have
|
6
|
+
built the labyrinth for Minos, king of Crete. Minos imprisoned him and
|
7
|
+
his son Icarus, but they escaped using wings that Daedalus made and
|
8
|
+
fastened with wax. Icarus, however, flew too near the sun and was killed.
|
9
|
+
|
10
|
+
|
11
|
+
In other words, he built things for a living.
|
12
|
+
|
13
|
+
=end
|
14
|
+
|
15
|
+
require 'digest/sha1'
|
16
|
+
require 'thread'
|
17
|
+
require 'daedalus/dependency_grapher'
|
18
|
+
|
19
|
+
module Daedalus
|
20
|
+
|
21
|
+
class Logger
|
22
|
+
def initialize(level=3)
|
23
|
+
@count = 0
|
24
|
+
@total = nil
|
25
|
+
@level = level
|
26
|
+
|
27
|
+
@thread_count = 0
|
28
|
+
@count_mutex = Mutex.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def thread_id
|
32
|
+
id = Thread.current[:build_id]
|
33
|
+
unless id
|
34
|
+
@count_mutex.synchronize do
|
35
|
+
@thread_count += 1
|
36
|
+
id = Thread.current[:build_id] = @thread_count
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return id
|
41
|
+
end
|
42
|
+
|
43
|
+
def start(count)
|
44
|
+
@count = 0
|
45
|
+
@total = count
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop
|
49
|
+
@total = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def inc!
|
53
|
+
@count += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def show(kind, cmd)
|
57
|
+
if @total
|
58
|
+
STDOUT.puts "[%3d/%3d] #{kind} #{cmd}" % [@count, @total]
|
59
|
+
else
|
60
|
+
STDOUT.puts "#{thread_id}: #{kind} #{cmd}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def command(cmd)
|
65
|
+
system cmd
|
66
|
+
if $?.exitstatus != 0
|
67
|
+
STDOUT.puts "Error: #{cmd}"
|
68
|
+
raise "Error compiling"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def verbose(str)
|
73
|
+
if @level >= 5
|
74
|
+
STDOUT.puts "daedalus: #{str}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def info(str)
|
79
|
+
if @level >= 3
|
80
|
+
STDOUT.puts "daedalus: #{str}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class FancyLogger < Logger
|
86
|
+
HEADER = "Daedalus building:"
|
87
|
+
|
88
|
+
def initialize(level=0)
|
89
|
+
super 0
|
90
|
+
end
|
91
|
+
|
92
|
+
def start(count)
|
93
|
+
super
|
94
|
+
STDOUT.sync = true
|
95
|
+
STDOUT.puts HEADER
|
96
|
+
end
|
97
|
+
|
98
|
+
def show(kind, cmd)
|
99
|
+
@count += 1
|
100
|
+
perc = (100 * (@count.to_f / @total)).to_i
|
101
|
+
bar_size = (30 * (@count.to_f / @total)).to_i
|
102
|
+
|
103
|
+
bar = "#{'=' * bar_size}#{' ' * (30 - bar_size)}"
|
104
|
+
|
105
|
+
if cmd.size > 38
|
106
|
+
cmd = "..#{cmd[-38,38]}"
|
107
|
+
else
|
108
|
+
cmd = cmd.ljust(40)
|
109
|
+
end
|
110
|
+
|
111
|
+
STDOUT.print "\r[%3d%% #{bar}] #{kind} #{cmd}" % perc.to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
def command(cmd)
|
115
|
+
output = IO.popen "sh -c '#{cmd} 2>&1'", "r"
|
116
|
+
|
117
|
+
begin
|
118
|
+
str = output.read
|
119
|
+
rescue Exception
|
120
|
+
Process.kill 'SIGINT', output.pid
|
121
|
+
STDOUT.puts "\nInterrupt compiling."
|
122
|
+
raise "Stopped compiling"
|
123
|
+
end
|
124
|
+
|
125
|
+
Process.wait output.pid
|
126
|
+
|
127
|
+
if $?.exitstatus != 0
|
128
|
+
STDOUT.puts "Error compiling: #{cmd}"
|
129
|
+
STDOUT.puts "Output:\n#{str}"
|
130
|
+
raise "Error compiling"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def stop
|
135
|
+
super
|
136
|
+
STDOUT.puts
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class Compiler
|
141
|
+
def initialize(cc, cxx, ldshared, ldsharedxx, logger, blueprint)
|
142
|
+
@cc = cc
|
143
|
+
@cxx = cxx
|
144
|
+
@ldshared = ldshared
|
145
|
+
@ldsharedxx = ldsharedxx
|
146
|
+
@cflags = []
|
147
|
+
@cxxflags = []
|
148
|
+
@ldflags = []
|
149
|
+
@libraries = []
|
150
|
+
@log = logger
|
151
|
+
@blueprint = blueprint
|
152
|
+
|
153
|
+
@mod_times = Hash.new do |h,k|
|
154
|
+
h[k] = (File.exists?(k) ? File.mtime(k) : Time.at(0))
|
155
|
+
end
|
156
|
+
|
157
|
+
@sha1_mtimes = {}
|
158
|
+
@sha1s = Hash.new do |h,k|
|
159
|
+
if File.exists?(k)
|
160
|
+
@log.verbose "computing SHA1: #{k}"
|
161
|
+
@sha1_mtimes[k] = File.mtime(k)
|
162
|
+
h[k] = Digest::SHA1.file(k).hexdigest
|
163
|
+
else
|
164
|
+
h[k] = ""
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
@mtime_only = false
|
169
|
+
end
|
170
|
+
|
171
|
+
attr_accessor :mtime_only
|
172
|
+
|
173
|
+
def header_directories
|
174
|
+
dirs = []
|
175
|
+
@cflags.each do |fl|
|
176
|
+
fl.split(/\s+/).each do |part|
|
177
|
+
if part.index("-I") == 0
|
178
|
+
dirs << part[2..-1]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
dirs
|
183
|
+
end
|
184
|
+
|
185
|
+
attr_reader :cc, :cxx, :ldshared, :ldsharedxx, :path, :cflags, :cxxflags, :ldflags, :log
|
186
|
+
|
187
|
+
def add_library(lib)
|
188
|
+
if f = lib.cflags
|
189
|
+
@cflags = f + @cflags
|
190
|
+
@cflags.uniq!
|
191
|
+
end
|
192
|
+
|
193
|
+
if f = lib.ldflags
|
194
|
+
@ldflags = f + @ldflags
|
195
|
+
@ldflags.uniq!
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def mtime(path)
|
200
|
+
@mod_times[path]
|
201
|
+
end
|
202
|
+
|
203
|
+
def sha1(path)
|
204
|
+
if @sha1s.key?(path)
|
205
|
+
if File.mtime(path) > @sha1_mtimes[path]
|
206
|
+
@sha1s.delete(path)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
@sha1s[path]
|
211
|
+
end
|
212
|
+
|
213
|
+
def compile(source, object)
|
214
|
+
if source =~ /\.cpp$/
|
215
|
+
cxx_compile(source, object)
|
216
|
+
else
|
217
|
+
c_compile(source, object)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def c_compile(source, object)
|
222
|
+
@log.show "CC", source
|
223
|
+
@log.command "#{@cc} #{@cflags.join(' ')} -c -o #{object} #{source}"
|
224
|
+
end
|
225
|
+
|
226
|
+
def cxx_compile(source, object)
|
227
|
+
@log.show "CXX", source
|
228
|
+
@log.command "#{@cxx} #{@cflags.join(' ')} #{@cxxflags.join(' ')} -c -o #{object} #{source}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def link(path, files)
|
232
|
+
@log.show "LD", path
|
233
|
+
@log.command "#{@cxx} -o #{path} #{files.join(' ')} #{@libraries.join(' ')} #{@ldflags.join(' ')}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def ar(library, objects)
|
237
|
+
@log.show "AR", library
|
238
|
+
@log.command "ar rv #{library} #{objects.join(' ')}"
|
239
|
+
@log.command "ranlib #{library}"
|
240
|
+
end
|
241
|
+
|
242
|
+
def link_shared(library, objects)
|
243
|
+
@log.show "LDSHARED", library
|
244
|
+
@log.command "#{@ldsharedxx} #{objects.join(' ')} -o #{library}"
|
245
|
+
end
|
246
|
+
|
247
|
+
def calculate_deps(path)
|
248
|
+
dirs = header_directories() + ["/usr/include"]
|
249
|
+
flags = @cflags.join(' ')
|
250
|
+
begin
|
251
|
+
dep = DependencyGrapher.new @cc, [path], dirs, flags
|
252
|
+
dep.process
|
253
|
+
|
254
|
+
# This is a quick work-around for a craptastic bug that I can't figure
|
255
|
+
# out. Sometimes this raises an exception saying it can't find a file
|
256
|
+
# which is pretty obviously there. I've been unable to figure out
|
257
|
+
# what causes this and thus how to fix.
|
258
|
+
#
|
259
|
+
# So as a temporary measure, if an exception is raised, I'm going to
|
260
|
+
# just do it again. Previous results have shown that this should
|
261
|
+
# work the 2nd time even though the first time failed.
|
262
|
+
#
|
263
|
+
# I know this sounds silly, but we need some fix for this.
|
264
|
+
rescue Exception
|
265
|
+
dep = DependencyGrapher.new @cc, [path], dirs, flags
|
266
|
+
dep.process
|
267
|
+
end
|
268
|
+
|
269
|
+
dep.sources.first.dependencies.sort
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class Path
|
274
|
+
def initialize(path)
|
275
|
+
@path = path
|
276
|
+
|
277
|
+
if File.exists?(data_path)
|
278
|
+
begin
|
279
|
+
File.open data_path, "rb" do |f|
|
280
|
+
@data = Marshal.load(f.read)
|
281
|
+
end
|
282
|
+
rescue
|
283
|
+
STDERR.puts "WARNING: Path#initialize: load '#{data_path}' failed"
|
284
|
+
@data = {}
|
285
|
+
end
|
286
|
+
else
|
287
|
+
@data = {}
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
attr_reader :data, :path
|
292
|
+
|
293
|
+
def basename
|
294
|
+
File.basename @path
|
295
|
+
end
|
296
|
+
|
297
|
+
def artifacts_path
|
298
|
+
dir = File.join File.dirname(@path), "artifacts"
|
299
|
+
Dir.mkdir dir unless File.directory?(dir)
|
300
|
+
return dir
|
301
|
+
end
|
302
|
+
|
303
|
+
def data_path
|
304
|
+
File.join artifacts_path, "#{basename}.data"
|
305
|
+
end
|
306
|
+
|
307
|
+
def save!
|
308
|
+
File.open(data_path, "wb") do |f|
|
309
|
+
f << Marshal.dump(data)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
class SourceFile < Path
|
315
|
+
|
316
|
+
def initialize(path)
|
317
|
+
super
|
318
|
+
@static_deps = []
|
319
|
+
@autogen_builder = nil
|
320
|
+
end
|
321
|
+
|
322
|
+
def depends_on(static_deps)
|
323
|
+
@static_deps = static_deps
|
324
|
+
end
|
325
|
+
|
326
|
+
def autogenerate(&builder)
|
327
|
+
@autogen_builder = builder
|
328
|
+
end
|
329
|
+
|
330
|
+
def object_path
|
331
|
+
File.join artifacts_path, "#{basename}.o"
|
332
|
+
end
|
333
|
+
|
334
|
+
def dependencies(ctx)
|
335
|
+
deps = @data[:deps]
|
336
|
+
|
337
|
+
if ctx.sha1(@path) != @data[:dep_sha1] or !deps
|
338
|
+
deps = recalc_depedencies(ctx)
|
339
|
+
end
|
340
|
+
|
341
|
+
return deps + @static_deps
|
342
|
+
end
|
343
|
+
|
344
|
+
def recalc_depedencies(ctx)
|
345
|
+
deps = ctx.calculate_deps(@path)
|
346
|
+
|
347
|
+
@data[:dep_sha1] = ctx.sha1(@path)
|
348
|
+
@data[:deps] = deps
|
349
|
+
|
350
|
+
return deps
|
351
|
+
end
|
352
|
+
|
353
|
+
def sha1(ctx)
|
354
|
+
sha1 = Digest::SHA1.new
|
355
|
+
sha1 << ctx.sha1(@path)
|
356
|
+
|
357
|
+
begin
|
358
|
+
dependencies(ctx).each do |d|
|
359
|
+
sha1 << ctx.sha1(d)
|
360
|
+
end
|
361
|
+
rescue StandardError
|
362
|
+
recalc_depedencies(ctx)
|
363
|
+
|
364
|
+
sha1 = Digest::SHA1.new
|
365
|
+
sha1 << ctx.sha1(@path)
|
366
|
+
|
367
|
+
dependencies(ctx).each do |d|
|
368
|
+
begin
|
369
|
+
sha1 << ctx.sha1(d)
|
370
|
+
rescue StandardError
|
371
|
+
raise "Unable to find dependency '#{d}' from #{@path}"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
sha1.hexdigest
|
377
|
+
end
|
378
|
+
|
379
|
+
def newer_dependencies(ctx)
|
380
|
+
dependencies(ctx).find_all do |x|
|
381
|
+
ctx.mtime(x) > ctx.mtime(object_path)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def out_of_date?(ctx)
|
386
|
+
unless File.exists?(@path)
|
387
|
+
return true if @autogen_builder
|
388
|
+
raise Errno::ENOENT, "Missing #{@path}"
|
389
|
+
end
|
390
|
+
|
391
|
+
return true unless File.exists?(object_path)
|
392
|
+
|
393
|
+
return true if ctx.mtime_only and ctx.mtime(@path) > ctx.mtime(object_path)
|
394
|
+
|
395
|
+
return true unless @data[:sha1] == sha1(ctx)
|
396
|
+
return false
|
397
|
+
end
|
398
|
+
|
399
|
+
def consider(ctx, tasks)
|
400
|
+
tasks << self if out_of_date?(ctx)
|
401
|
+
end
|
402
|
+
|
403
|
+
def build(ctx)
|
404
|
+
ctx.log.inc!
|
405
|
+
|
406
|
+
if @autogen_builder
|
407
|
+
ctx.log.show "GN", @path
|
408
|
+
@autogen_builder.call(ctx.log)
|
409
|
+
end
|
410
|
+
|
411
|
+
@data[:sha1] = sha1(ctx)
|
412
|
+
ctx.compile path, object_path
|
413
|
+
save!
|
414
|
+
end
|
415
|
+
|
416
|
+
def clean
|
417
|
+
File.unlink object_path if File.exists?(object_path)
|
418
|
+
File.unlink data_path if File.exists?(data_path)
|
419
|
+
|
420
|
+
Dir.rmdir artifacts_path if Dir.entries(artifacts_path).empty?
|
421
|
+
end
|
422
|
+
|
423
|
+
def describe(ctx)
|
424
|
+
if !File.exists?(object_path)
|
425
|
+
puts "#{@path}: unbuilt"
|
426
|
+
else
|
427
|
+
if @data[:sha1] != sha1(ctx)
|
428
|
+
puts "#{@path}: out-of-date"
|
429
|
+
end
|
430
|
+
|
431
|
+
deps = newer_dependencies(ctx)
|
432
|
+
|
433
|
+
unless deps.empty?
|
434
|
+
puts "#{@path}: dependencies changed"
|
435
|
+
deps.each do |d|
|
436
|
+
puts " - #{d}"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def info(ctx)
|
443
|
+
puts @path
|
444
|
+
puts " object: #{object_path}"
|
445
|
+
puts " last hash: #{@data[:sha1]}"
|
446
|
+
puts " curr hash: #{sha1(ctx)}"
|
447
|
+
|
448
|
+
puts " dependencies:"
|
449
|
+
dependencies(ctx).each do |x|
|
450
|
+
puts " #{x}"
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
class ExternalLibrary
|
456
|
+
def initialize(path)
|
457
|
+
@path = path
|
458
|
+
|
459
|
+
@cflags = nil
|
460
|
+
@ldflags = nil
|
461
|
+
@objects = nil
|
462
|
+
|
463
|
+
@build_dir = path
|
464
|
+
@builder = nil
|
465
|
+
@data = nil
|
466
|
+
end
|
467
|
+
|
468
|
+
attr_accessor :path, :cflags, :ldflags, :objects
|
469
|
+
|
470
|
+
def to_build(&blk)
|
471
|
+
@builder = blk
|
472
|
+
|
473
|
+
@data_file = "#{@build_dir}.data"
|
474
|
+
|
475
|
+
if File.exists?(@data_file)
|
476
|
+
begin
|
477
|
+
File.open @data_file, "rb" do |f|
|
478
|
+
@data = Marshal.load(f.read)
|
479
|
+
end
|
480
|
+
rescue
|
481
|
+
STDERR.puts "WARNING: ExternalLibrary#to_build: load '#{data_path}' failed"
|
482
|
+
@data = {}
|
483
|
+
end
|
484
|
+
else
|
485
|
+
@data = {}
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
490
|
+
def file(f)
|
491
|
+
File.join(@path, f)
|
492
|
+
end
|
493
|
+
|
494
|
+
def sha1
|
495
|
+
sha1 = Digest::SHA1.new
|
496
|
+
|
497
|
+
Dir["#{@build_dir}/*"].each do |f|
|
498
|
+
sha1.file(f) if File.file?(f)
|
499
|
+
end
|
500
|
+
|
501
|
+
Dir["#{@build_dir}/**/*"].each do |f|
|
502
|
+
sha1.file(f) if File.file?(f)
|
503
|
+
end
|
504
|
+
|
505
|
+
sha1.hexdigest
|
506
|
+
end
|
507
|
+
|
508
|
+
def have_objects
|
509
|
+
return true unless @objects
|
510
|
+
@objects.each do |o|
|
511
|
+
return false unless File.exists?(o)
|
512
|
+
end
|
513
|
+
|
514
|
+
return true
|
515
|
+
end
|
516
|
+
|
517
|
+
def out_of_date?(ctx)
|
518
|
+
return true unless have_objects
|
519
|
+
return false unless @builder
|
520
|
+
return false if @data and @data[:sha1] == sha1
|
521
|
+
return true
|
522
|
+
end
|
523
|
+
|
524
|
+
def consider(ctx, tasks)
|
525
|
+
tasks.pre << self if out_of_date?(ctx)
|
526
|
+
end
|
527
|
+
|
528
|
+
def build(ctx)
|
529
|
+
raise "Unable to build" unless @builder
|
530
|
+
|
531
|
+
ctx.log.inc!
|
532
|
+
|
533
|
+
ctx.log.show "LB", @build_dir
|
534
|
+
|
535
|
+
Dir.chdir(@build_dir) do
|
536
|
+
@builder.call(ctx.log)
|
537
|
+
end
|
538
|
+
|
539
|
+
@data[:sha1] = sha1()
|
540
|
+
|
541
|
+
File.open(@data_file, "wb") do |f|
|
542
|
+
f << Marshal.dump(@data)
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def describe(ctx)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
class Library
|
551
|
+
attr_reader :sources
|
552
|
+
|
553
|
+
def initialize(path, base, compiler)
|
554
|
+
@base = base
|
555
|
+
@compiler = compiler
|
556
|
+
|
557
|
+
@directory = File.dirname path
|
558
|
+
@library = File.basename path
|
559
|
+
@sources = []
|
560
|
+
|
561
|
+
yield self if block_given?
|
562
|
+
|
563
|
+
source_files "#{path}.c" if @sources.empty?
|
564
|
+
end
|
565
|
+
|
566
|
+
def path
|
567
|
+
File.join @base, @directory, library
|
568
|
+
end
|
569
|
+
|
570
|
+
def name
|
571
|
+
File.join @directory, library
|
572
|
+
end
|
573
|
+
|
574
|
+
def source_files(*patterns)
|
575
|
+
Dir.chdir @base do
|
576
|
+
patterns.each do |t|
|
577
|
+
Dir[t].each do |f|
|
578
|
+
@sources << SourceFile.new(f)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def object_files
|
585
|
+
@sources.map { |s| s.object_path }
|
586
|
+
end
|
587
|
+
|
588
|
+
def out_of_date?(compiler)
|
589
|
+
Dir.chdir @base do
|
590
|
+
return true unless File.exists? name
|
591
|
+
@sources.each do |s|
|
592
|
+
return true unless File.exists? s.object_path
|
593
|
+
return true if File.mtime(s.object_path) > File.mtime(name)
|
594
|
+
end
|
595
|
+
@sources.any? { |s| s.out_of_date? compiler }
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def consider(compiler, tasks)
|
600
|
+
tasks.pre << self if out_of_date? compiler
|
601
|
+
end
|
602
|
+
|
603
|
+
def clean
|
604
|
+
Dir.chdir @base do
|
605
|
+
@sources.each { |s| s.clean }
|
606
|
+
File.delete name if File.exists? name
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
class StaticLibrary < Library
|
612
|
+
def library
|
613
|
+
"#{@library}.a"
|
614
|
+
end
|
615
|
+
|
616
|
+
def build(compiler)
|
617
|
+
Dir.chdir @base do
|
618
|
+
# TODO: out of date checking should be subsumed in building
|
619
|
+
@sources.each { |s| s.build @compiler if s.out_of_date? @compiler }
|
620
|
+
@compiler.ar name, object_files
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
class SharedLibrary < Library
|
626
|
+
def library
|
627
|
+
"#{@library}.#{RbConfig::CONFIG["DLEXT"]}"
|
628
|
+
end
|
629
|
+
|
630
|
+
def build(compiler)
|
631
|
+
Dir.chdir @base do
|
632
|
+
# TODO: out of date checking should be subsumed in building
|
633
|
+
@sources.each { |s| s.build @compiler if s.out_of_date? @compiler }
|
634
|
+
@compiler.link_shared name, object_files
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# The purpose of a LibraryGroup is to combine multiple static and shared
|
640
|
+
# libraries into a unit. Static libraries are used to statically link a
|
641
|
+
# program, while shared libraries may be dynamically loaded by that program
|
642
|
+
# or another program.
|
643
|
+
#
|
644
|
+
# NOTE: The current protocol for getting a list of static libraries is the
|
645
|
+
# #objects method. This should be changed when reworking Daedalus.
|
646
|
+
class LibraryGroup
|
647
|
+
attr_accessor :cflags, :ldflags
|
648
|
+
|
649
|
+
def initialize(base, compiler)
|
650
|
+
@base = base
|
651
|
+
@static_libraries = []
|
652
|
+
@shared_libraries = []
|
653
|
+
@compiler = Compiler.new(compiler.cc,
|
654
|
+
compiler.cxx,
|
655
|
+
compiler.ldshared,
|
656
|
+
compiler.ldsharedxx,
|
657
|
+
compiler.log, nil)
|
658
|
+
|
659
|
+
yield self
|
660
|
+
|
661
|
+
compiler.add_library self
|
662
|
+
|
663
|
+
@compiler.cflags.concat cflags if cflags
|
664
|
+
@compiler.ldflags.concat ldflags if ldflags
|
665
|
+
end
|
666
|
+
|
667
|
+
def depends_on(file, command)
|
668
|
+
# TODO: HACK, the agony, this should be implicit
|
669
|
+
unless File.exists? File.join(@base, file)
|
670
|
+
raise "library group #{@base} depends on #{file}, please run #{command}"
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
# TODO: change the way files are sorted
|
675
|
+
def path
|
676
|
+
@base
|
677
|
+
end
|
678
|
+
|
679
|
+
def static_library(path, &block)
|
680
|
+
@static_libraries << StaticLibrary.new(path, @base, @compiler, &block)
|
681
|
+
end
|
682
|
+
|
683
|
+
def shared_library(path, &block)
|
684
|
+
@shared_libraries << SharedLibrary.new(path, @base, @compiler, &block)
|
685
|
+
end
|
686
|
+
|
687
|
+
# TODO: Fix this protocol
|
688
|
+
def objects
|
689
|
+
@static_libraries.map { |l| l.path }
|
690
|
+
end
|
691
|
+
|
692
|
+
def libraries
|
693
|
+
@static_libraries + @shared_libraries
|
694
|
+
end
|
695
|
+
|
696
|
+
def consider(compiler, tasks)
|
697
|
+
# TODO: Note we are using @compiler, not compiler. There should not be a
|
698
|
+
# global compiler. There should be a global configuration object that is
|
699
|
+
# specialized by specific libraries as needed.
|
700
|
+
libraries.each { |l| l.consider @compiler, tasks }
|
701
|
+
end
|
702
|
+
|
703
|
+
def clean
|
704
|
+
libraries.each { |l| l.clean }
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
class Program < Path
|
709
|
+
def initialize(path, files)
|
710
|
+
super path
|
711
|
+
@files = files.sort_by { |x| x.path }
|
712
|
+
end
|
713
|
+
|
714
|
+
def objects
|
715
|
+
# This partitions the list into .o's first and .a's last. This
|
716
|
+
# is because gcc on some platforms require that static libraries
|
717
|
+
# be linked last. This is because gcc builds a list of undefined
|
718
|
+
# symbols, and then when it hits a .a, looks in the archive
|
719
|
+
# to try and resolve those symbols. So if a .o needs something
|
720
|
+
# from a .a, the .a MUST come after the .o
|
721
|
+
objects = []
|
722
|
+
archives = []
|
723
|
+
|
724
|
+
@files.each do |x|
|
725
|
+
if x.respond_to? :object_path
|
726
|
+
if File.extname(x.object_path) == ".a"
|
727
|
+
archives << x.object_path
|
728
|
+
else
|
729
|
+
objects << x.object_path
|
730
|
+
end
|
731
|
+
else
|
732
|
+
x.objects.each do |obj|
|
733
|
+
if File.extname(obj) == ".a"
|
734
|
+
archives << obj
|
735
|
+
else
|
736
|
+
objects << obj
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
objects.sort + archives
|
743
|
+
end
|
744
|
+
|
745
|
+
def consider(ctx, tasks)
|
746
|
+
@files.each { |x| x.consider(ctx, tasks) }
|
747
|
+
tasks.post << self unless tasks.empty? and File.exists?(@path)
|
748
|
+
end
|
749
|
+
|
750
|
+
def build(ctx)
|
751
|
+
ctx.log.inc!
|
752
|
+
ctx.link @path, objects
|
753
|
+
end
|
754
|
+
|
755
|
+
def clean
|
756
|
+
@files.each do |f|
|
757
|
+
f.clean if f.respond_to? :clean
|
758
|
+
end
|
759
|
+
|
760
|
+
File.unlink @path if File.exists?(@path)
|
761
|
+
File.unlink data_path if File.exists?(data_path)
|
762
|
+
Dir.rmdir artifacts_path if Dir.entries(artifacts_path).empty?
|
763
|
+
end
|
764
|
+
|
765
|
+
def describe(ctx)
|
766
|
+
puts "Program: #{@path}"
|
767
|
+
|
768
|
+
@files.each do |f|
|
769
|
+
f.describe(ctx)
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
def file_info(ctx, files)
|
774
|
+
files.each do |n|
|
775
|
+
obj = @files.find { |x| x.path == n }
|
776
|
+
if obj
|
777
|
+
obj.info(ctx)
|
778
|
+
else
|
779
|
+
puts "Unable to find file: #{n}"
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
class Tasks
|
786
|
+
def initialize
|
787
|
+
@pre = []
|
788
|
+
@default = []
|
789
|
+
@post = []
|
790
|
+
end
|
791
|
+
|
792
|
+
attr_reader :pre, :default, :post
|
793
|
+
|
794
|
+
def <<(obj)
|
795
|
+
@default << obj
|
796
|
+
end
|
797
|
+
|
798
|
+
def empty?
|
799
|
+
@pre.empty? and @default.empty? and @post.empty?
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
class TaskRunner
|
804
|
+
def initialize(compiler, tasks, max=nil)
|
805
|
+
@max = TaskRunner.detect_cpus
|
806
|
+
@tasks = tasks
|
807
|
+
@compiler = compiler
|
808
|
+
|
809
|
+
calculate_max(max)
|
810
|
+
end
|
811
|
+
|
812
|
+
def calculate_max(max)
|
813
|
+
cpus = TaskRunner.detect_cpus
|
814
|
+
|
815
|
+
case max
|
816
|
+
when nil # auto
|
817
|
+
case cpus
|
818
|
+
when 1, 2
|
819
|
+
@max = cpus
|
820
|
+
when 4
|
821
|
+
@max = 3
|
822
|
+
else
|
823
|
+
@max = 4
|
824
|
+
end
|
825
|
+
when Fixnum
|
826
|
+
@max = max
|
827
|
+
when "cpu"
|
828
|
+
@max = cpus
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def self.detect_cpus
|
833
|
+
if RUBY_PLATFORM =~ /windows/
|
834
|
+
return 1
|
835
|
+
else
|
836
|
+
if RUBY_PLATFORM =~ /bsd/
|
837
|
+
key = 'NPROCESSORS_CONF'
|
838
|
+
else
|
839
|
+
key = '_NPROCESSORS_CONF'
|
840
|
+
end
|
841
|
+
count = `getconf #{key} 2>&1`.to_i
|
842
|
+
return 1 if $?.exitstatus != 0
|
843
|
+
return count
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
def start
|
848
|
+
linear_tasks @tasks.pre
|
849
|
+
perform_tasks @tasks.default
|
850
|
+
linear_tasks @tasks.post
|
851
|
+
end
|
852
|
+
|
853
|
+
def linear_tasks(tasks)
|
854
|
+
tasks.each do |task|
|
855
|
+
task.build @compiler
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
859
|
+
def perform_tasks(tasks)
|
860
|
+
count = tasks.size
|
861
|
+
|
862
|
+
puts "Running #{count} tasks using #{@max} parallel threads"
|
863
|
+
start = Time.now
|
864
|
+
|
865
|
+
queue = Queue.new
|
866
|
+
threads = []
|
867
|
+
|
868
|
+
@max.times do
|
869
|
+
threads << Thread.new {
|
870
|
+
while true
|
871
|
+
task = queue.pop
|
872
|
+
break unless task
|
873
|
+
task.build @compiler
|
874
|
+
end
|
875
|
+
}
|
876
|
+
end
|
877
|
+
|
878
|
+
sync = []
|
879
|
+
|
880
|
+
queue_tasks(queue, tasks, sync)
|
881
|
+
|
882
|
+
# Kill off the builders
|
883
|
+
threads.each do |t|
|
884
|
+
queue << nil
|
885
|
+
end
|
886
|
+
|
887
|
+
threads.each do |t|
|
888
|
+
t.join
|
889
|
+
end
|
890
|
+
|
891
|
+
sync.each do |task|
|
892
|
+
task.build @compiler
|
893
|
+
end
|
894
|
+
|
895
|
+
puts "Build time: #{Time.now - start} seconds"
|
896
|
+
end
|
897
|
+
|
898
|
+
def queue_tasks(queue, tasks, sync)
|
899
|
+
tasks.each do |task|
|
900
|
+
if task.kind_of? Array
|
901
|
+
queue_tasks queue, task[1..-1], sync
|
902
|
+
sync << task[0]
|
903
|
+
else
|
904
|
+
queue.push task
|
905
|
+
end
|
906
|
+
end
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
class Blueprint
|
911
|
+
def initialize
|
912
|
+
@programs = []
|
913
|
+
@compiler = nil
|
914
|
+
end
|
915
|
+
|
916
|
+
def external_lib(path)
|
917
|
+
ex = ExternalLibrary.new(path)
|
918
|
+
yield ex
|
919
|
+
ex
|
920
|
+
end
|
921
|
+
|
922
|
+
def library_group(path, &block)
|
923
|
+
LibraryGroup.new(path, @compiler, &block)
|
924
|
+
end
|
925
|
+
|
926
|
+
def gcc!(cc, cxx, ldshared, ldsharedxx)
|
927
|
+
@compiler = Compiler.new(cc,
|
928
|
+
cxx,
|
929
|
+
ldshared,
|
930
|
+
ldsharedxx,
|
931
|
+
Logger.new, self)
|
932
|
+
end
|
933
|
+
|
934
|
+
def source_files(*patterns)
|
935
|
+
files = []
|
936
|
+
|
937
|
+
patterns.each do |t|
|
938
|
+
Dir[t].each do |f|
|
939
|
+
files << SourceFile.new(f)
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
files
|
944
|
+
end
|
945
|
+
|
946
|
+
def source_file(file)
|
947
|
+
sf = SourceFile.new(file)
|
948
|
+
yield sf if block_given?
|
949
|
+
sf
|
950
|
+
end
|
951
|
+
|
952
|
+
def program(name, *files)
|
953
|
+
@programs << Program.new(name, files)
|
954
|
+
end
|
955
|
+
|
956
|
+
def build(targets=[], jobs=nil)
|
957
|
+
if !targets.empty?
|
958
|
+
@programs.each do |x|
|
959
|
+
if targets.include? x.path
|
960
|
+
tasks = Tasks.new
|
961
|
+
x.consider @compiler, tasks
|
962
|
+
|
963
|
+
if tasks.empty?
|
964
|
+
@compiler.log.info "Nothing to do for #{x.path}"
|
965
|
+
else
|
966
|
+
tr = TaskRunner.new @compiler, tasks, jobs
|
967
|
+
tr.start
|
968
|
+
end
|
969
|
+
end
|
970
|
+
end
|
971
|
+
else
|
972
|
+
@programs.each { |x| x.build @compiler }
|
973
|
+
end
|
974
|
+
end
|
975
|
+
|
976
|
+
def clean(targets=[])
|
977
|
+
if !targets.empty?
|
978
|
+
@programs.each do |x|
|
979
|
+
if targets.include? x.path
|
980
|
+
x.clean
|
981
|
+
end
|
982
|
+
end
|
983
|
+
else
|
984
|
+
@programs.each { |x| x.clean }
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
def describe(targets=[])
|
989
|
+
if !targets.empty?
|
990
|
+
@programs.each do |x|
|
991
|
+
if targets.include? x.path
|
992
|
+
x.describe @compiler
|
993
|
+
end
|
994
|
+
end
|
995
|
+
else
|
996
|
+
@programs.each { |x| x.describe @compiler }
|
997
|
+
end
|
998
|
+
end
|
999
|
+
|
1000
|
+
def file_info(files)
|
1001
|
+
@programs.each do |x|
|
1002
|
+
x.file_info @compiler, files
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def self.blueprint
|
1008
|
+
b = Blueprint.new
|
1009
|
+
yield b
|
1010
|
+
b
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
def self.load(file)
|
1014
|
+
eval File.read(file)
|
1015
|
+
end
|
1016
|
+
end
|