metabuild 0.3
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.
- data/Manifest +4 -0
- data/README +5 -0
- data/Rakefile +11 -0
- data/lib/metabuild.rb +1016 -0
- data/metabuild.gemspec +30 -0
- metadata +72 -0
data/Manifest
ADDED
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('metabuild', '0.3') do |s|
|
6
|
+
s.author = "F. Brault"
|
7
|
+
s.email = "castorpilot@yahoo.com"
|
8
|
+
s.description = "MetaBuild is a ruby make-like tool that builds components. It supports tracking of dependencies among components, and a hierarchical reporting facility."
|
9
|
+
s.url = "http://metabuild.rubyforge.org/"
|
10
|
+
s.project = "metabuild"
|
11
|
+
end
|
data/lib/metabuild.rb
ADDED
@@ -0,0 +1,1016 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# MetaBuild is a ruby "make-like" tool that builds components.
|
5
|
+
#
|
6
|
+
# Author:: F. Brault
|
7
|
+
# Copyright:: Copyright (c) 2010 F. Brault
|
8
|
+
# License:: GPL
|
9
|
+
#
|
10
|
+
# This program is free software: you can redistribute it and/or modify
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
12
|
+
# the Free Software Foundation, either version 3 of the License, or
|
13
|
+
# (at your option) any later version.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
+
# GNU General Public License for more details.
|
19
|
+
#
|
20
|
+
# You should have received a copy of the GNU General Public License
|
21
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
22
|
+
#
|
23
|
+
# For further information, see http://rubyforge.org/projects/metabuild/
|
24
|
+
#
|
25
|
+
|
26
|
+
require 'optparse'
|
27
|
+
require 'fileutils'
|
28
|
+
require 'digest/sha1'
|
29
|
+
require 'forwardable'
|
30
|
+
require 'tempfile'
|
31
|
+
|
32
|
+
include FileUtils
|
33
|
+
|
34
|
+
# This module defines a "meta-make" system in ruby.
|
35
|
+
# The goal is to create build directive for a component,
|
36
|
+
# along with different targets an depedencies among them ( like in Make).
|
37
|
+
# A hierarchical reporting tool is included.
|
38
|
+
module Metabuild
|
39
|
+
|
40
|
+
CACHE = ENV.fetch("RBUILD_CACHE", "/work/common/build_cache")
|
41
|
+
|
42
|
+
DEFAULT_TAGS = [
|
43
|
+
lambda {`uname -p`},
|
44
|
+
lambda {`uname -o`},
|
45
|
+
lambda {File.read("/etc/fedora-release")},
|
46
|
+
lambda {s = ""; 1.upto(ARGV.length-1) {|i| s += ARGV[i]};s}
|
47
|
+
]
|
48
|
+
|
49
|
+
DEFAULT_OPTIONS = {
|
50
|
+
"clone" => [".", "Path of the SCM clone"],
|
51
|
+
"cache" => ["no", "Use caching to speed up builds"],
|
52
|
+
"dependency" => ["yes", "Track dependencies between targets"],
|
53
|
+
"workspace" => ENV.fetch("WORKSPACE", Dir.pwd),
|
54
|
+
"logtype" => "text",
|
55
|
+
"logfile" => "",
|
56
|
+
"dep_graph" => ["no", "Output Dependency graph in Graphviz dot format"],
|
57
|
+
"parallel" => ["no", "Run threads in parallel. Make sure that your dependency graph can handle this !"],
|
58
|
+
}
|
59
|
+
|
60
|
+
# Function to extend fileutils
|
61
|
+
|
62
|
+
# helper function that does (rm -rf dir; mkdir dir; cd dir)
|
63
|
+
def create_goto_dir!(dir)
|
64
|
+
rm_rf dir
|
65
|
+
mkdir_p dir
|
66
|
+
cd dir
|
67
|
+
end
|
68
|
+
|
69
|
+
# helper function that does goes into dir if it exists, or create it and goes into it
|
70
|
+
def cd!(dir)
|
71
|
+
mkdir_p dir unless File.directory? dir
|
72
|
+
cd dir
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# Mother class for all SCM access, "virtual"
|
77
|
+
class SCM
|
78
|
+
|
79
|
+
attr_accessor :path
|
80
|
+
|
81
|
+
def initialize(path)
|
82
|
+
@path = path
|
83
|
+
end
|
84
|
+
|
85
|
+
def cksum
|
86
|
+
raise "Should be implemented in child class"
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_diff
|
90
|
+
raise "Should be implemented in child class"
|
91
|
+
end
|
92
|
+
|
93
|
+
def apply_patch(patch)
|
94
|
+
raise "Should be implemented in child class"
|
95
|
+
end
|
96
|
+
|
97
|
+
def clone(path)
|
98
|
+
raise "Should be implemented in child class"
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# Git access, uses Grit ruby lib
|
104
|
+
class Git < SCM
|
105
|
+
|
106
|
+
def initialize(path)
|
107
|
+
super
|
108
|
+
end
|
109
|
+
|
110
|
+
# Like running "git diff" in a git repo
|
111
|
+
def get_diff
|
112
|
+
`cd #{path} && git diff`
|
113
|
+
end
|
114
|
+
|
115
|
+
def apply_patch(patch)
|
116
|
+
pipe = IO.popen("git apply -")
|
117
|
+
pipe.puts patch
|
118
|
+
pipe.close_write
|
119
|
+
end
|
120
|
+
|
121
|
+
def clone(path)
|
122
|
+
raise "unable to clone #{path}" unless system("git clone " + path)
|
123
|
+
end
|
124
|
+
|
125
|
+
# id is the SHA1 of head + of any diff (locals)
|
126
|
+
def cksum
|
127
|
+
`cd #{path};git log --pretty=format:'%H' | head -n 1`.to_i(16) ^ Digest::SHA1.hexdigest(get_diff).to_i(16)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# Mother class for reporting. Defines a simple text-based logfile
|
133
|
+
class Log
|
134
|
+
|
135
|
+
attr_accessor :name
|
136
|
+
|
137
|
+
def initialize(name)
|
138
|
+
@name = name
|
139
|
+
@subscript = (defined? $subscript_log and not $subscript_log.nil?) ? true : false
|
140
|
+
end
|
141
|
+
|
142
|
+
# Helper while waiting for Ruby 1.9 and the "env" option in _system_
|
143
|
+
def env_system(cmd, env)
|
144
|
+
str = ""
|
145
|
+
env.each { |k, v| str += "export #{k}=#{v};"}
|
146
|
+
system(str + cmd)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Run a shell command, log if it fails (override in child class)
|
150
|
+
def run(command, env, fail_msg)
|
151
|
+
env_system(command, env)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Run a shell command, no logging (override in child class)
|
155
|
+
def silent(command, env)
|
156
|
+
env_system(command, env)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Run a shell command. Log success +and+ failure (override in child class)
|
160
|
+
def valid(command, env, fail_msg, success_msg)
|
161
|
+
env_system(command, env)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Spawn with env
|
165
|
+
def env_spawn(cmd, env, fail_msg, success_msg)
|
166
|
+
str = ""
|
167
|
+
env.each { |k, v| str += "export #{k}=#{v};"}
|
168
|
+
pid = spawn(str + cmd)
|
169
|
+
return [pid, fail_msg]
|
170
|
+
end
|
171
|
+
|
172
|
+
def parallel_wait(p)
|
173
|
+
pid, stat = Process::waitpid2(p[0])
|
174
|
+
failure p[1] unless stat.success?
|
175
|
+
end
|
176
|
+
|
177
|
+
# Run another ruby build script
|
178
|
+
# Be careful with global variables !!!
|
179
|
+
# By default, the default options values are transmitted to the subscript
|
180
|
+
def script(script, args, options, fail_msg, use_default)
|
181
|
+
$subscript_argv = args.split
|
182
|
+
options.each {|k,v| $subscript_argv.push "--#{k}=#{v}" if (DEFAULT_OPTIONS.has_key?(k) and not args =~ /--#{k}=/)} if use_default
|
183
|
+
$subscript_log = ""
|
184
|
+
begin
|
185
|
+
load(script, true)
|
186
|
+
rescue Exception => exception
|
187
|
+
integrate $subscript_log if $subscript_log != ""
|
188
|
+
if exception.kind_of? SystemExit
|
189
|
+
failure fail_msg unless exception.success?
|
190
|
+
else
|
191
|
+
failure(fail_msg + "(#{exception.class}: #{exception.message})\n" + exception.backtrace.join("\n"))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
integrate $subscript_log
|
195
|
+
$subscript_log = nil
|
196
|
+
$subscript_argv = nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def failure(f)
|
200
|
+
exit false
|
201
|
+
end
|
202
|
+
|
203
|
+
def integrate log
|
204
|
+
raise "Should be implemented in child class"
|
205
|
+
end
|
206
|
+
|
207
|
+
def finish
|
208
|
+
exit true
|
209
|
+
end
|
210
|
+
|
211
|
+
def status
|
212
|
+
false
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
# Simple log that just prints out text (for console builds)
|
218
|
+
class LogText < Log
|
219
|
+
|
220
|
+
attr_reader :name, :success, :fail
|
221
|
+
|
222
|
+
def initialize(name)
|
223
|
+
super(name)
|
224
|
+
@fail, @success = [], []
|
225
|
+
@subscripts = []
|
226
|
+
@report_done = false
|
227
|
+
end
|
228
|
+
|
229
|
+
def add_fail(msg)
|
230
|
+
@fail.push msg
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_success(msg)
|
234
|
+
@success.push msg
|
235
|
+
end
|
236
|
+
|
237
|
+
def report
|
238
|
+
return if @report_done # prevent reporting several times
|
239
|
+
if @subscript
|
240
|
+
$subscript_log = self
|
241
|
+
else
|
242
|
+
output
|
243
|
+
end
|
244
|
+
@report_done = true
|
245
|
+
end
|
246
|
+
|
247
|
+
def output
|
248
|
+
puts "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
249
|
+
puts @name
|
250
|
+
puts "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
251
|
+
puts "Ran the following subscripts :"
|
252
|
+
@subscripts.each {|s| puts s}
|
253
|
+
puts "----------------------------------------------"
|
254
|
+
puts "Sucess : "
|
255
|
+
@success.each {|s| puts s}
|
256
|
+
puts "----------------------------------------------"
|
257
|
+
puts "Fail : "
|
258
|
+
@fail.each {|s| puts s}
|
259
|
+
puts "----------------------------------------------"
|
260
|
+
end
|
261
|
+
|
262
|
+
def status
|
263
|
+
@fail.empty?
|
264
|
+
end
|
265
|
+
|
266
|
+
# Run a shell command, log if it fails
|
267
|
+
def run(command, env, fail_msg)
|
268
|
+
failure fail_msg unless env_system(command, env)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Run a shell command, no logging
|
272
|
+
def silent(command, env)
|
273
|
+
stop unless env_system(command, env)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Run a shell command. Log success +and+ failure
|
277
|
+
def valid(command, env, fail_msg, success_msg)
|
278
|
+
if env_system(command, env)
|
279
|
+
add_success success_msg if success_msg != ""
|
280
|
+
else
|
281
|
+
add_fail fail_msg
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def integrate log
|
286
|
+
@subscripts.push log.name
|
287
|
+
log.success.each {|s| @success.push("[#{log.name}] " + s)}
|
288
|
+
log.fail.each {|f| @fail.push("[#{log.name}] " + f)}
|
289
|
+
end
|
290
|
+
|
291
|
+
# Stop script, adding a message to the log
|
292
|
+
def failure(msg)
|
293
|
+
add_fail msg
|
294
|
+
stop
|
295
|
+
end
|
296
|
+
|
297
|
+
# Output log and exit with error exit status
|
298
|
+
def stop
|
299
|
+
report
|
300
|
+
exit false
|
301
|
+
end
|
302
|
+
|
303
|
+
def finish
|
304
|
+
report
|
305
|
+
exit status
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
# Html log. For reporting in continuous integration systems, for example.
|
311
|
+
class LogHtml < Log
|
312
|
+
|
313
|
+
attr_reader :name, :html, :success, :fail
|
314
|
+
|
315
|
+
def initialize(name, file)
|
316
|
+
super(name)
|
317
|
+
@file = file
|
318
|
+
@fail, @success = [], []
|
319
|
+
@subscripts = []
|
320
|
+
@report_done = false
|
321
|
+
@html = ""
|
322
|
+
@has_bash = system("bash --version > /dev/null")
|
323
|
+
end
|
324
|
+
|
325
|
+
def add_fail(cmd, msg, output)
|
326
|
+
@fail.push [cmd, msg, output]
|
327
|
+
end
|
328
|
+
|
329
|
+
def add_success(cmd, msg, output)
|
330
|
+
@success.push [cmd, msg, output]
|
331
|
+
end
|
332
|
+
|
333
|
+
def report
|
334
|
+
return if @report_done # prevent reporting several times
|
335
|
+
fill_html
|
336
|
+
if @subscript
|
337
|
+
$subscript_log = self
|
338
|
+
else
|
339
|
+
output
|
340
|
+
end
|
341
|
+
@report_done = true
|
342
|
+
end
|
343
|
+
|
344
|
+
def fill_html
|
345
|
+
|
346
|
+
top = "<div class='raised'>\n" +
|
347
|
+
"<b class='b1'></b><b class='b2'></b><b class='b3'></b><b class='b4'></b><div class='boxcontent'>\n"
|
348
|
+
bottom = "</div><b class='b4b'></b><b class='b3b'></b><b class='b2b'></b><b class='b1b'></b></div>\n"
|
349
|
+
red = "<img src='data:image/png;base64,
|
350
|
+
iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAIAAABL1vtsAAAAAXNSR0IArs4c6QAAAWdJREFUOMul
|
351
|
+
VD1zgkAUfF48nDmTIZPEZM6CxgZrCitqKwsaK3+elY2NjY2NFhZQyy8AE5jMkMSLQgIpjjn8jKBb
|
352
|
+
Hrf79i3vXsk0TbgO5cOjUhRVXFdyHMl1se8DQIJxSGlI6YbSqFY7I4EYk+fz+8mk4jjScok9j0ts
|
353
|
+
KA3r9e9G473dZs3mTsntRspB8DgaPYzHZLE46jkm5KPV8judQNePuCgHwdNw+DwY8MpHgRiTZzPe
|
354
|
+
nVBB2bfp9H++SIrY9ku/L5wiflq17Tx8obJ9HwHADWO3lnWq/1Md3VkWse1UAjFWLcIXKpyVuuB6
|
355
|
+
hSBYaRY5U9hLJMviSiA+fIdjexaChQDglxCmqkUlBAvxsV3tjn0eCFbq4kvTWBGVmJBPTctcJBiv
|
356
|
+
VPWt282ZyN59JFQDXc+jkmDMVPW11xOus5f6I8u+YSQYX/7YuYpnGCGll6+cw8UneR4AxJJUYPHx
|
357
|
+
mmtFWStKzr/zB1QX2NNv3dXcAAAAAElFTkSuQmCC'>"
|
358
|
+
green = "<img src='data:image/png;base64,
|
359
|
+
iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAIAAABL1vtsAAAAAXNSR0IArs4c6QAAAWhJREFUOMul
|
360
|
+
lMGKwjAQhsdUUlq2UCIs23oVb148eNUH8AHWJ9wHWB+gexXx0lvx2mQRHIQslgaFPaRkS+tqiv8x
|
361
|
+
zf/1ZzIzve12C8+p3z6STiFc5BQFRekUABBcvUixWLGoZMHVe4DgFJMw3QV7TlG4NUTJYsWmcrQ4
|
362
|
+
TWLF/kVkfr4ebJIw5RQbuaSfZ36e+fm3i8vjbHwe3kBkfv7x+pWEqf7zTXGKn4ONdIrVYW4oxHzT
|
363
|
+
/7/jN4mSMF0PNiYpMac2/jrF3CcAIFzU9bN/SE5xF+yFixWCU+zkNxTtIgAgKGpeJwkXhUFIp7Cs
|
364
|
+
QvOlTS2eFNHN127bhzIuAgCRYlHJuiKikkWKVYhYsUbb28i4iOZN5agTRY+czl7VYnGaLE4Ty4o0
|
365
|
+
7hNDXR5nNhTtXx5nJvXfpI7Pw9VhHly99rDX82v/7WHXlPfL/K1knVZOr707zeLjFH/6BQC8XLzY
|
366
|
+
fvFVPXMe1qPe1y/ZDt7tn1jpQgAAAABJRU5ErkJggg=='>"
|
367
|
+
|
368
|
+
unless @subscript
|
369
|
+
@html << "<html>\n"
|
370
|
+
@html << "<head>\n"
|
371
|
+
@html << "<script type='text/javascript'>\n"
|
372
|
+
@html << "function toggleMe(a){\n"
|
373
|
+
@html << "var e=document.getElementById(a);\n"
|
374
|
+
@html << "if(!e)return true;\n"
|
375
|
+
@html << "if(e.style.display=='none'){\n"
|
376
|
+
@html << "e.style.display='block'\n"
|
377
|
+
@html << "} else {\n"
|
378
|
+
@html << "e.style.display='none'\n"
|
379
|
+
@html << "}\n"
|
380
|
+
@html << "return true;\n"
|
381
|
+
@html << "}\n"
|
382
|
+
@html << "</script>\n"
|
383
|
+
@html << "<style type='text/css'>\n"
|
384
|
+
@html << "A"
|
385
|
+
@html << "{"
|
386
|
+
@html << " color:white;"
|
387
|
+
@html << "}"
|
388
|
+
@html << "A:link {color:#000000}"
|
389
|
+
@html << "A:visited {color:#ffffff}"
|
390
|
+
@html << "A:active {color:#ffffff}"
|
391
|
+
@html << "A:hover {background-color: black; color: white}"
|
392
|
+
@html << "div.header"
|
393
|
+
@html << "{"
|
394
|
+
@html << " text-align: center;"
|
395
|
+
@html << " margin-left: 100px;"
|
396
|
+
@html << " margin-right: 100px;"
|
397
|
+
@html << " margin-bottom: 50px;"
|
398
|
+
@html << " float: center;"
|
399
|
+
@html << "}"
|
400
|
+
@html << "div.content"
|
401
|
+
@html << "{"
|
402
|
+
@html << " float:center;"
|
403
|
+
@html << " margin-left:100px;"
|
404
|
+
@html << " margin-right:100px;"
|
405
|
+
@html << "}"
|
406
|
+
@html << "div.output"
|
407
|
+
@html << "{"
|
408
|
+
@html << " float:center;"
|
409
|
+
@html << " margin-left:100px;"
|
410
|
+
@html << " margin-right:100px;"
|
411
|
+
@html << " border-style: dashed;"
|
412
|
+
@html << "}"
|
413
|
+
@html << ".raised {"
|
414
|
+
@html << " background:transparent; "
|
415
|
+
@html << " width:100%;"
|
416
|
+
@html << "}"
|
417
|
+
@html << ".raised h1, .raised p {"
|
418
|
+
@html << " margin:0 10px;"
|
419
|
+
@html << " }"
|
420
|
+
@html << ".raised h1 {"
|
421
|
+
@html << " font-size:2em; "
|
422
|
+
@html << " }"
|
423
|
+
@html << ".raised p {"
|
424
|
+
@html << " padding-bottom:0.5em;"
|
425
|
+
@html << " }"
|
426
|
+
@html << ".raised .b1, .raised .b2, .raised .b3, .raised .b4, .raised .b1b, .raised .b2b, .raised .b3b, .raised .b4b {"
|
427
|
+
@html << " display:block; "
|
428
|
+
@html << " overflow:hidden;"
|
429
|
+
@html << " font-size:1px;"
|
430
|
+
@html << " }"
|
431
|
+
@html << ".raised .b1, .raised .b2, .raised .b3, .raised .b1b, .raised .b2b, .raised .b3b {"
|
432
|
+
@html << " height:1px;"
|
433
|
+
@html << " }"
|
434
|
+
@html << ".raised .b2 {"
|
435
|
+
@html << " background:#ccc; "
|
436
|
+
@html << " border-left:1px solid #fff; "
|
437
|
+
@html << " border-right:1px solid #eee;"
|
438
|
+
@html << " }"
|
439
|
+
@html << ".raised .b3 {"
|
440
|
+
@html << " background:#ccc; "
|
441
|
+
@html << " border-left:1px solid #fff; "
|
442
|
+
@html << " border-right:1px solid #ddd;"
|
443
|
+
@html << " }"
|
444
|
+
@html << ".raised .b4 {"
|
445
|
+
@html << " background:#ccc; "
|
446
|
+
@html << " border-left:1px solid #fff; "
|
447
|
+
@html << " border-right:1px solid #aaa;"
|
448
|
+
@html << " }"
|
449
|
+
@html << ".raised .b4b {"
|
450
|
+
@html << " background:#ccc; "
|
451
|
+
@html << " border-left:1px solid #eee; "
|
452
|
+
@html << " border-right:1px solid #999;"
|
453
|
+
@html << " }"
|
454
|
+
@html << ".raised .b3b {"
|
455
|
+
@html << " background:#ccc; "
|
456
|
+
@html << " border-left:1px solid #ddd; "
|
457
|
+
@html << " border-right:1px solid #999;"
|
458
|
+
@html << " }"
|
459
|
+
@html << ".raised .b2b {"
|
460
|
+
@html << " background:#ccc; "
|
461
|
+
@html << " border-left:1px solid #aaa; "
|
462
|
+
@html << " border-right:1px solid #999;"
|
463
|
+
@html << " }"
|
464
|
+
@html << ".raised .b1 {"
|
465
|
+
@html << " margin:0 5px; "
|
466
|
+
@html << " background:#fff;"
|
467
|
+
@html << " }"
|
468
|
+
@html << ".raised .b2, .raised .b2b {"
|
469
|
+
@html << " margin:0 3px; "
|
470
|
+
@html << " border-width:0 2px;"
|
471
|
+
@html << " }"
|
472
|
+
@html << ".raised .b3, .raised .b3b {"
|
473
|
+
@html << " margin:0 2px;"
|
474
|
+
@html << " }"
|
475
|
+
@html << ".raised .b4, .raised .b4b {"
|
476
|
+
@html << " height:2px; margin:0 1px;"
|
477
|
+
@html << " }"
|
478
|
+
@html << ".raised .b1b {"
|
479
|
+
@html << " margin:0 5px; background:#999;"
|
480
|
+
@html << " }"
|
481
|
+
@html << ".raised .boxcontent {"
|
482
|
+
@html << " display:block; "
|
483
|
+
@html << " background:#ccc; "
|
484
|
+
@html << " border-left:1px solid #fff; "
|
485
|
+
@html << " border-right:1px solid #999;"
|
486
|
+
@html << " }"
|
487
|
+
@html << "</style>\n"
|
488
|
+
@html << "</head><body bgcolor='grey'>\n"
|
489
|
+
end
|
490
|
+
|
491
|
+
@html << "<div class='header'>\n"
|
492
|
+
@html << top
|
493
|
+
@html << "<h1>#{@name} "
|
494
|
+
if status
|
495
|
+
@html << green + "</h1><br>\n"
|
496
|
+
else
|
497
|
+
@html << red + "</h1><br>\n"
|
498
|
+
end
|
499
|
+
@html << bottom + "</div>"
|
500
|
+
|
501
|
+
@html << "<div class='content'>\n"
|
502
|
+
@html << top
|
503
|
+
@html << "<p><h2>Subscript run :</h2><br>\n"
|
504
|
+
id = (rand() * 500).to_i # FIXME
|
505
|
+
@subscripts.each do |s|
|
506
|
+
name, status, html = s[0], s[1], s[2]
|
507
|
+
stat = status ? green : red
|
508
|
+
@html << "<div id='para#{id}' style='display:none'>\n"
|
509
|
+
@html << "<div class='output'>\n"
|
510
|
+
@html << html
|
511
|
+
@html << "</div>"
|
512
|
+
@html << "</div>\n"
|
513
|
+
@html << "<br>#{stat} #{name}<input type='button' class='button' onclick='return toggleMe(\"para#{id}\")' value='Show Html log'>\n"
|
514
|
+
id += 1
|
515
|
+
end
|
516
|
+
@html << "<br></p>\n"
|
517
|
+
|
518
|
+
@html << "<p><h2>Tests :</h2><br>\n"
|
519
|
+
|
520
|
+
@fail.each do |s|
|
521
|
+
cmd, msg, output = s[0], s[1], s[2]
|
522
|
+
@html << "<div id='para#{id}' style='display:none'>\n"
|
523
|
+
@html << "<div class='output'>\n"
|
524
|
+
@html << "<pre>" + output + "</pre>"
|
525
|
+
@html << "</div>"
|
526
|
+
@html << "</div>\n"
|
527
|
+
@html << "<br>#{red} #{msg}\n"
|
528
|
+
@html << "<input type='button' class='button' onclick='return toggleMe(\"para#{id}\")' value='Show output'>\n"
|
529
|
+
id += 1
|
530
|
+
end
|
531
|
+
|
532
|
+
@success.each do |s|
|
533
|
+
cmd, msg, output = s[0], s[1], s[2]
|
534
|
+
@html << "<div id='para#{id}' style='display:none'>\n"
|
535
|
+
@html << "<div class='output'>\n"
|
536
|
+
@html << "<pre>" + output + "</pre>"
|
537
|
+
@html << "</div>"
|
538
|
+
@html << "</div>\n"
|
539
|
+
@html << "<br>#{green} #{msg}\n"
|
540
|
+
@html << "<input type='button' class='button' onclick='return toggleMe(\"para#{id}\")' value='Show output'>\n"
|
541
|
+
id += 1
|
542
|
+
end
|
543
|
+
|
544
|
+
@html << "<br></p>\n"
|
545
|
+
|
546
|
+
@html << bottom + "</div>"
|
547
|
+
|
548
|
+
unless @subscript
|
549
|
+
@html << "</body></html>\n"
|
550
|
+
end
|
551
|
+
|
552
|
+
end
|
553
|
+
|
554
|
+
def output
|
555
|
+
File.open(@file, "w") do |f|
|
556
|
+
f.puts @html
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
def status
|
561
|
+
@fail.empty?
|
562
|
+
end
|
563
|
+
|
564
|
+
def setup_script(cmd, env)
|
565
|
+
file = Tempfile.new("output")
|
566
|
+
file.close
|
567
|
+
script = Tempfile.new("script")
|
568
|
+
if @has_bash
|
569
|
+
script.puts "exec > >(tee #{file.path}) 2>&1" # Redirect output to tmpfile + tee
|
570
|
+
else
|
571
|
+
script.puts "exec > #{file.path} 2>&1" # Redirect output to tmpfile
|
572
|
+
end
|
573
|
+
env.each { |k, v| script.puts "export #{k}=#{v}"}
|
574
|
+
script.puts cmd
|
575
|
+
script.close
|
576
|
+
return script.path, file.path
|
577
|
+
end
|
578
|
+
|
579
|
+
def env_system(cmd, env)
|
580
|
+
script, file = setup_script(cmd, env)
|
581
|
+
puts "@@@@@@@@@@@ Running #{cmd} @@@@@@@@@@@@@"
|
582
|
+
shell = @has_bash ? "bash " : "sh "
|
583
|
+
success = system(shell + script)
|
584
|
+
output = File.new(file).read.strip
|
585
|
+
File.delete script
|
586
|
+
File.delete file
|
587
|
+
return success, output
|
588
|
+
end
|
589
|
+
|
590
|
+
def env_spawn(cmd, env, fail_msg = "", success_msg = "")
|
591
|
+
script, file, shell = setup_script(cmd, env)
|
592
|
+
puts "@@@@@@@@@@@ Running #{cmd} in background @@@@@@@@@@@@@"
|
593
|
+
pid = spawn(shell + script)
|
594
|
+
return [pid, file, script, cmd, fail_msg, success_msg]
|
595
|
+
end
|
596
|
+
|
597
|
+
def parallel_wait(p)
|
598
|
+
pid, file, script, cmd, fail_msg, success_msg= p[0], p[1], p[2], p[3], p[4], p[5]
|
599
|
+
pid, stat = Process::waitpid2(pid)
|
600
|
+
success = stat.success?
|
601
|
+
output = File.new(file).read.strip
|
602
|
+
File.delete script
|
603
|
+
File.delete file
|
604
|
+
if success
|
605
|
+
add_success cmd, success_msg, output
|
606
|
+
else
|
607
|
+
add_fail cmd, fail_msg, output
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
|
612
|
+
# Run a shell command, log if it fails
|
613
|
+
def run(command, env, fail_msg)
|
614
|
+
success, output= env_system(command, env)
|
615
|
+
unless success
|
616
|
+
add_fail command, fail_msg, output
|
617
|
+
stop
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
# Run a shell command, no logging
|
622
|
+
def silent(command, env)
|
623
|
+
success, output = env_system(command, env)
|
624
|
+
stop unless success
|
625
|
+
end
|
626
|
+
|
627
|
+
# Run a shell command. Log success +and+ failure
|
628
|
+
def valid(command, env, fail_msg, success_msg)
|
629
|
+
success, output = env_system(command, env)
|
630
|
+
if success
|
631
|
+
add_success command, success_msg, output
|
632
|
+
else
|
633
|
+
add_fail command, fail_msg, output
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
def integrate log
|
638
|
+
if log.kind_of? LogHtml
|
639
|
+
@subscripts.push [log.name, log.status, log.html]
|
640
|
+
else
|
641
|
+
@subscripts.push [log.name, log.status, ""]
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
# Stop script, adding a message to the log
|
646
|
+
def failure(msg, cmd = "", output = "")
|
647
|
+
add_fail cmd, msg, output
|
648
|
+
stop
|
649
|
+
end
|
650
|
+
|
651
|
+
# Output log and exit with error exit status
|
652
|
+
def stop
|
653
|
+
report
|
654
|
+
exit false
|
655
|
+
end
|
656
|
+
|
657
|
+
def finish
|
658
|
+
report
|
659
|
+
exit status
|
660
|
+
end
|
661
|
+
|
662
|
+
end
|
663
|
+
# A Target is really at the heart of a build. It handles dependencies, caching ...
|
664
|
+
class Target
|
665
|
+
|
666
|
+
attr_accessor :name, :repo, :tags, :results, :depends, :block, :done
|
667
|
+
|
668
|
+
def initialize(name, repo, depends = [], results = [], tags = [])
|
669
|
+
@name, @repo, @tags, @results, @depends = name, repo, tags, results, depends
|
670
|
+
@done = false
|
671
|
+
@block = lambda { raise "Target #{@name} has empty code block !" }
|
672
|
+
end
|
673
|
+
|
674
|
+
def to_s
|
675
|
+
@name
|
676
|
+
end
|
677
|
+
|
678
|
+
def add_result res
|
679
|
+
@results.push res
|
680
|
+
end
|
681
|
+
|
682
|
+
def add_dep dep
|
683
|
+
if dep.kind_of? Array
|
684
|
+
@depends = @depends | dep
|
685
|
+
else
|
686
|
+
@depends.push dep
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
def remove_dep dep
|
691
|
+
@depends.delete dep
|
692
|
+
end
|
693
|
+
|
694
|
+
def each_dep
|
695
|
+
@depends.each do |d|
|
696
|
+
yield d
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
def each_result
|
701
|
+
@results.each do |r|
|
702
|
+
yield r
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
# The checksum is a mix of the repository checksum + the default tags + local tags
|
707
|
+
# Default tags include processor type, kernel name, etc.
|
708
|
+
def cksum
|
709
|
+
DEFAULT_TAGS.concat(@tags).inject(@repo.cksum) { |sum, tag|
|
710
|
+
Digest::SHA1.hexdigest(tag.call).to_i(16) ^ sum
|
711
|
+
}.to_s(16)
|
712
|
+
end
|
713
|
+
|
714
|
+
def check_cache
|
715
|
+
File.exist?(CACHE + cksum + ".tgz")
|
716
|
+
end
|
717
|
+
|
718
|
+
def get_cache
|
719
|
+
cmd = "tar zxf " + CACHE + "/" + cksum + ".tgz"
|
720
|
+
raise "Error getting target #{@name} in cache" unless system(cmd)
|
721
|
+
end
|
722
|
+
|
723
|
+
def update_cache
|
724
|
+
cmd = ":;" + @results.inject("tar zcf " + CACHE + "/" + cksum + ".tgz "){|s, r| s += "#{r} "}
|
725
|
+
raise "Error tar for target #{@name}" unless system(cmd)
|
726
|
+
end
|
727
|
+
|
728
|
+
# Run a block that defines all the steps of the build.
|
729
|
+
# Use caching if asked to.
|
730
|
+
# +Note+ : Whatever the directory changes happening in the block,
|
731
|
+
# when the block exits, we are in the same directory as when it started
|
732
|
+
def run(use_cache = false)
|
733
|
+
return if @done
|
734
|
+
old_dir = pwd
|
735
|
+
if use_cache
|
736
|
+
if check_cache
|
737
|
+
puts "getting from cache"
|
738
|
+
get_cache
|
739
|
+
else
|
740
|
+
block.call self
|
741
|
+
update_cache
|
742
|
+
end
|
743
|
+
else
|
744
|
+
block.call self
|
745
|
+
end
|
746
|
+
cd old_dir
|
747
|
+
@done = true
|
748
|
+
end
|
749
|
+
|
750
|
+
# Run according to dependency list, from bottom to top
|
751
|
+
def propagate_run(use_cache=false, parallel=false)
|
752
|
+
if parallel
|
753
|
+
each_dep do |d|
|
754
|
+
fork {d.propagate_run(use_cache, parallel)}
|
755
|
+
end
|
756
|
+
Process.waitall unless @depends.empty?
|
757
|
+
else
|
758
|
+
each_dep do |d|
|
759
|
+
d.propagate_run(use_cache, parallel)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
run(use_cache)
|
763
|
+
end
|
764
|
+
|
765
|
+
def propagate_disable
|
766
|
+
each_dep do |d|
|
767
|
+
d.done = true
|
768
|
+
d.propagate_disable
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
def output_graph
|
773
|
+
each_dep do |d|
|
774
|
+
puts "#{@name} -> #{d.name};"
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
end
|
779
|
+
|
780
|
+
# A class for parsing command line options
|
781
|
+
class Options
|
782
|
+
|
783
|
+
extend Forwardable
|
784
|
+
attr_reader :opts, :rest, :from_targets
|
785
|
+
def_delegators :@opts, :[], :fetch, :each
|
786
|
+
|
787
|
+
# By default, the following options are set : --help, and DEFAULT_OPTIONS.
|
788
|
+
# So caching and target dependencies tracking are disabled by default.
|
789
|
+
# h is a hashmap of tuples option_name => default_value
|
790
|
+
def initialize(h)
|
791
|
+
@opts = {}
|
792
|
+
@help = false
|
793
|
+
hash = DEFAULT_OPTIONS.merge(h)
|
794
|
+
@parser = OptionParser.new
|
795
|
+
@parser.on("--help") {|val| @help = true}
|
796
|
+
@from_targets = []
|
797
|
+
@parser.on("--from=[target]", "Disable everything below target in dep tree. Only with dependency=yes") {|val| @from_targets.push val}
|
798
|
+
hash.each do |opt, default|
|
799
|
+
if default.kind_of? Array
|
800
|
+
@parser.on("--#{opt}=[#{default[0]}]", default[1]) {|val| @opts[opt] = val}
|
801
|
+
else
|
802
|
+
@parser.on("--#{opt}=[#{default}]") {|val| @opts[opt] = val}
|
803
|
+
end
|
804
|
+
end
|
805
|
+
if defined? $subscript_argv and not $subscript_argv.nil?
|
806
|
+
argv = $subscript_argv
|
807
|
+
else
|
808
|
+
argv = ARGV
|
809
|
+
end
|
810
|
+
@rest = @parser.parse(*argv)
|
811
|
+
hash.each do |opt, default|
|
812
|
+
default = default[0] if default.kind_of? Array
|
813
|
+
@opts[opt] = default unless @opts.has_key? opt
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
def help?
|
818
|
+
@help
|
819
|
+
end
|
820
|
+
|
821
|
+
def help
|
822
|
+
@parser.to_s
|
823
|
+
end
|
824
|
+
|
825
|
+
end
|
826
|
+
|
827
|
+
# The builder class is responsible for triggerring targets and reporting failures or
|
828
|
+
# exceptions via the logging facilities
|
829
|
+
class Builder
|
830
|
+
|
831
|
+
extend Forwardable
|
832
|
+
attr_accessor :name, :targets, :default_targets, :env
|
833
|
+
def_delegators :@log, :finish
|
834
|
+
|
835
|
+
def initialize(name, options = nil, targets = [])
|
836
|
+
@name, @options, @targets = name, options, targets
|
837
|
+
raise "Builder needs Options (none given)!" if options.nil?
|
838
|
+
title = "Report for #{name}"
|
839
|
+
@log = case @options.fetch("logtype", "text")
|
840
|
+
when "text" then LogText.new(title)
|
841
|
+
when "html"
|
842
|
+
raise "Need a logfile for html reports" if @options["logfile"].nil? or @options["logfile"].empty?
|
843
|
+
LogHtml.new(title, @options["logfile"])
|
844
|
+
else raise "Unknown log type #{@options["logtype"]}"
|
845
|
+
end
|
846
|
+
@default_targets = []
|
847
|
+
@env = {"WORKSPACE" => @options.fetch("workspace", Dir.pwd)}
|
848
|
+
@subprocesses = []
|
849
|
+
end
|
850
|
+
|
851
|
+
def logtitle=(title)
|
852
|
+
@log.name = title
|
853
|
+
end
|
854
|
+
|
855
|
+
def logtitle
|
856
|
+
@log.name
|
857
|
+
end
|
858
|
+
|
859
|
+
# Run targets according to what the user specified in ARGV
|
860
|
+
# Dependencies among targets can be propagated or not (default = no)
|
861
|
+
# Outpout log and stop
|
862
|
+
def launch
|
863
|
+
begin
|
864
|
+
|
865
|
+
if @options.help?
|
866
|
+
puts @options.help
|
867
|
+
puts "Default targets are built if no targets are given as argument. You can specify any of the targets listed below"
|
868
|
+
puts "Default targets :"
|
869
|
+
@default_targets.each { |t| puts t.name }
|
870
|
+
puts "The following targets are defined :"
|
871
|
+
@targets.each { |t| puts t.name }
|
872
|
+
exit
|
873
|
+
end
|
874
|
+
|
875
|
+
if @options.fetch("dep_graph", "no").downcase == "yes"
|
876
|
+
output_graph
|
877
|
+
end
|
878
|
+
|
879
|
+
@options.from_targets.each do |targ|
|
880
|
+
get_target(targ).propagate_disable
|
881
|
+
end
|
882
|
+
|
883
|
+
use_cache = (@options.fetch("cache", "no").downcase == "yes")
|
884
|
+
parallel = (@options.fetch("parallel", "no").downcase == "yes")
|
885
|
+
targs = @options.rest.empty? ? @default_targets.map {|t| t.to_s} : @options.rest
|
886
|
+
if @options.fetch("dependency", "yes").downcase != "yes"
|
887
|
+
targs.each { |t| targ = get_target(t); targ.run(use_cache) unless targ.nil? }
|
888
|
+
else
|
889
|
+
targs.each { |t| targ = get_target(t); targ.propagate_run(use_cache, parallel) unless targ.nil? }
|
890
|
+
end
|
891
|
+
@log.finish
|
892
|
+
rescue Exception => exception
|
893
|
+
# Uses @report_done in Log to prevent reporting several times
|
894
|
+
if exception.kind_of? SystemExit
|
895
|
+
@log.failure "Error : target exited with failure code" unless exception.success?
|
896
|
+
else
|
897
|
+
@log.failure("Error in target : (#{exception.class}: #{exception.message})\n" + exception.backtrace.join("\n"))
|
898
|
+
end
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
# Output dependencies between targets in Graphviz dot format
|
903
|
+
def output_graph
|
904
|
+
puts "digraph Dependencies {"
|
905
|
+
@targets.each {|t| puts "node [shape=ellipse];#{t.name};"}
|
906
|
+
@targets.each {|t| t.output_graph}
|
907
|
+
puts "}"
|
908
|
+
exit
|
909
|
+
end
|
910
|
+
|
911
|
+
# Run another ruby build script
|
912
|
+
# Be careful with global variables !!!
|
913
|
+
# By default, the default options values are transmitted to the subscript
|
914
|
+
def script(script, args, fail_msg = "FAILED running #{script} #{args}", use_default = true)
|
915
|
+
@log.script(script, args, @options, fail_msg, use_default)
|
916
|
+
end
|
917
|
+
|
918
|
+
# Run a shell command, giving it (optional) env variables (a hashmap), and (optional) failure message
|
919
|
+
# Only log failures.
|
920
|
+
# :call-seq:
|
921
|
+
# run(:cmd => "cmd", :env => env, :msg => "msg") -> nil
|
922
|
+
# run("cmd") -> nil
|
923
|
+
def run(h)
|
924
|
+
if h.kind_of? String
|
925
|
+
@log.run(h, @env, "#{h} FAIL")
|
926
|
+
else
|
927
|
+
raise "run command needs a non nil command" if h[:cmd].nil?
|
928
|
+
@log.run(h[:cmd], @env.merge(h.fetch(:env, @env)), h.fetch(:msg, "#{h[:cmd]} FAIL"))
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
# Run a shell command, no logging. Give it (optional) env variables (hashmap)
|
933
|
+
# :call-seq:
|
934
|
+
# silent(:cmd => "cmd", :env => env) -> nil
|
935
|
+
# silent("cmd") -> nil
|
936
|
+
def silent(h)
|
937
|
+
if h.kind_of? String
|
938
|
+
@log.silent(h, @env)
|
939
|
+
else
|
940
|
+
raise "silent command needs a non nil command" if h[:cmd].nil?
|
941
|
+
@log.silent(h[:cmd], @env.merge(h.fetch(:env, {})))
|
942
|
+
end
|
943
|
+
end
|
944
|
+
|
945
|
+
# Run a shell command, giving it (optional) env variables (a hashmap), and (optional) failure message
|
946
|
+
# Log success +and+ failure.
|
947
|
+
# :call-seq:
|
948
|
+
# valid(:cmd => "cmd", :env => env, :fail_msg => "msg", :success_msg => "msg") -> nil
|
949
|
+
# valid("cmd") -> nil
|
950
|
+
def valid(h)
|
951
|
+
if h.kind_of? String
|
952
|
+
@log.valid(h, @env, "#{h} FAIL", "#{h} SUCCESS")
|
953
|
+
else
|
954
|
+
raise "valid command needs a non nil command" if h[:cmd].nil?
|
955
|
+
@log.valid(h[:cmd], @env.merge(h.fetch(:env, {})), h.fetch(:fail_msg, "#{h[:cmd]} FAIL"), h.fetch(:success_msg, "#{h[:cmd]} SUCCESS"))
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# Run a shell command in subprocess, giving it (optional) env variables (a hashmap), and (optional) failure message
|
960
|
+
# Use parallel_wait on the returned object _P_ .
|
961
|
+
# :call-seq:
|
962
|
+
# parallel_valid(:cmd => "cmd", :env => env, :fail_msg => "msg", :success_msg => "msg") -> P
|
963
|
+
# parallel_valid("cmd") -> P
|
964
|
+
def parallel_valid(h)
|
965
|
+
if h.kind_of? String
|
966
|
+
p = @log.env_spawn(h, @env, "#{h} FAIL", "#{h} SUCCESS")
|
967
|
+
else
|
968
|
+
raise "valid command needs a non nil command" if h[:cmd].nil?
|
969
|
+
p = @log.env_spawn(h[:cmd], @env.merge(h.fetch(:env, {})), h.fetch(:fail_msg, "#{h[:cmd]} FAIL"), h.fetch(:success_msg, "#{h[:cmd]} SUCCESS"))
|
970
|
+
end
|
971
|
+
@subprocess.push p
|
972
|
+
return p
|
973
|
+
end
|
974
|
+
|
975
|
+
# Wait for a subprocess, Log success +and+ failure (like valid)
|
976
|
+
def parallel_wait(p)
|
977
|
+
@log.parallel_wait(p)
|
978
|
+
@subprocess.delete(p)
|
979
|
+
end
|
980
|
+
|
981
|
+
# Wait for a all subprocess, Log success +and+ failure (like valid)
|
982
|
+
def parallel_waitall
|
983
|
+
@subprocess.each do |p|
|
984
|
+
@log.parallel_wait(p)
|
985
|
+
end
|
986
|
+
@subprocess = []
|
987
|
+
end
|
988
|
+
|
989
|
+
# Add target, either a single one, or an array of targets
|
990
|
+
def add_target(target)
|
991
|
+
if target.kind_of? Array
|
992
|
+
@targets = @targets | target
|
993
|
+
else
|
994
|
+
@targets.push target
|
995
|
+
end
|
996
|
+
end
|
997
|
+
|
998
|
+
def remove_target(target)
|
999
|
+
@targets.delete target
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def get_target(name)
|
1003
|
+
@targets.each do |t|
|
1004
|
+
return t if t.name == name
|
1005
|
+
end
|
1006
|
+
raise "Unkonwn target #{name} for build #{@name}"
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# Set a block of code to be run as target, catching doing logging
|
1010
|
+
def target(target, &block)
|
1011
|
+
get_target(target).block = block
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
end
|
data/metabuild.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{metabuild}
|
5
|
+
s.version = "0.3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["F. Brault"]
|
9
|
+
s.date = %q{2010-05-20}
|
10
|
+
s.description = %q{MetaBuild is a ruby make-like tool that builds components. It supports tracking of dependencies among components, and a hierarchical reporting facility.}
|
11
|
+
s.email = %q{castorpilot@yahoo.com}
|
12
|
+
s.extra_rdoc_files = ["README", "lib/metabuild.rb"]
|
13
|
+
s.files = ["Manifest", "README", "Rakefile", "lib/metabuild.rb", "metabuild.gemspec"]
|
14
|
+
s.homepage = %q{http://metabuild.rubyforge.org/}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Metabuild", "--main", "README"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{metabuild}
|
18
|
+
s.rubygems_version = %q{1.3.6}
|
19
|
+
s.summary = %q{MetaBuild is a ruby make-like tool that builds components. It supports tracking of dependencies among components, and a hierarchical reporting facility.}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: metabuild
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
version: "0.3"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- F. Brault
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-05-20 00:00:00 +02:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: MetaBuild is a ruby make-like tool that builds components. It supports tracking of dependencies among components, and a hierarchical reporting facility.
|
21
|
+
email: castorpilot@yahoo.com
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files:
|
27
|
+
- README
|
28
|
+
- lib/metabuild.rb
|
29
|
+
files:
|
30
|
+
- Manifest
|
31
|
+
- README
|
32
|
+
- Rakefile
|
33
|
+
- lib/metabuild.rb
|
34
|
+
- metabuild.gemspec
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://metabuild.rubyforge.org/
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --line-numbers
|
42
|
+
- --inline-source
|
43
|
+
- --title
|
44
|
+
- Metabuild
|
45
|
+
- --main
|
46
|
+
- README
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 2
|
63
|
+
version: "1.2"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: metabuild
|
67
|
+
rubygems_version: 1.3.6
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: MetaBuild is a ruby make-like tool that builds components. It supports tracking of dependencies among components, and a hierarchical reporting facility.
|
71
|
+
test_files: []
|
72
|
+
|