yugui-chkbuild 0.1.2
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/README.ja.rd +191 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/bin/last-build +28 -0
- data/bin/start-build +37 -0
- data/chkbuild.gemspec +107 -0
- data/core_ext/io.rb +17 -0
- data/core_ext/string.rb +10 -0
- data/lib/chkbuild.rb +45 -0
- data/lib/chkbuild/build.rb +718 -0
- data/lib/chkbuild/lock.rb +57 -0
- data/lib/chkbuild/logfile.rb +230 -0
- data/lib/chkbuild/main.rb +138 -0
- data/lib/chkbuild/options.rb +62 -0
- data/lib/chkbuild/scm/cvs.rb +132 -0
- data/lib/chkbuild/scm/git.rb +223 -0
- data/lib/chkbuild/scm/svn.rb +215 -0
- data/lib/chkbuild/scm/xforge.rb +33 -0
- data/lib/chkbuild/target.rb +180 -0
- data/lib/chkbuild/targets/gcc.rb +94 -0
- data/lib/chkbuild/targets/ruby.rb +456 -0
- data/lib/chkbuild/title.rb +107 -0
- data/lib/chkbuild/upload.rb +66 -0
- data/lib/misc/escape.rb +535 -0
- data/lib/misc/gdb.rb +74 -0
- data/lib/misc/timeoutcom.rb +174 -0
- data/lib/misc/udiff.rb +244 -0
- data/lib/misc/util.rb +232 -0
- data/sample/build-autoconf-ruby +69 -0
- data/sample/build-gcc-ruby +43 -0
- data/sample/build-ruby +37 -0
- data/sample/build-ruby2 +36 -0
- data/sample/build-svn +55 -0
- data/sample/build-yarv +35 -0
- data/sample/test-apr +12 -0
- data/sample/test-catcherr +23 -0
- data/sample/test-combfail +21 -0
- data/sample/test-core +14 -0
- data/sample/test-core2 +19 -0
- data/sample/test-date +9 -0
- data/sample/test-dep +17 -0
- data/sample/test-depver +14 -0
- data/sample/test-echo +9 -0
- data/sample/test-env +9 -0
- data/sample/test-error +9 -0
- data/sample/test-fail +18 -0
- data/sample/test-fmesg +16 -0
- data/sample/test-gcc-v +15 -0
- data/sample/test-git +11 -0
- data/sample/test-leave-proc +9 -0
- data/sample/test-limit +9 -0
- data/sample/test-make +9 -0
- data/sample/test-neterr +16 -0
- data/sample/test-savannah +14 -0
- data/sample/test-sleep +9 -0
- data/sample/test-timeout +9 -0
- data/sample/test-timeout2 +10 -0
- data/sample/test-timeout3 +9 -0
- data/sample/test-upload +13 -0
- data/sample/test-warn +13 -0
- data/setup/upload-rsync-ssh +572 -0
- data/test/misc/test-escape.rb +17 -0
- data/test/misc/test-logfile.rb +108 -0
- data/test/misc/test-timeoutcom.rb +23 -0
- data/test/test_helper.rb +9 -0
- metadata +123 -0
data/lib/chkbuild.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright (C) 2006,2008,2009 Tanaka Akira <akr@fsij.org>
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
5
|
+
#
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
7
|
+
# list of conditions and the following disclaimer.
|
8
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer in the documentation
|
10
|
+
# and/or other materials provided with the distribution.
|
11
|
+
# 3. The name of the author may not be used to endorse or promote products
|
12
|
+
# derived from this software without specific prior written permission.
|
13
|
+
#
|
14
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
15
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
16
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
17
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
18
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
19
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
20
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
21
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
22
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
23
|
+
# OF SUCH DAMAGE.
|
24
|
+
|
25
|
+
Encoding.default_external = "ASCII-8BIT" if defined?(Encoding.default_external = nil)
|
26
|
+
|
27
|
+
Dir.glob( File.join(File.expand_path("../../core_ext", __FILE__), '*.rb') ) do |path|
|
28
|
+
require path
|
29
|
+
end
|
30
|
+
$: << File.expand_path("../misc", __FILE__)
|
31
|
+
|
32
|
+
require 'chkbuild/main'
|
33
|
+
require 'chkbuild/lock'
|
34
|
+
require 'chkbuild/scm/cvs'
|
35
|
+
require 'chkbuild/scm/svn'
|
36
|
+
require 'chkbuild/scm/git'
|
37
|
+
require 'chkbuild/scm/xforge'
|
38
|
+
require "util"
|
39
|
+
require 'chkbuild/target'
|
40
|
+
require 'chkbuild/build'
|
41
|
+
|
42
|
+
module ChkBuild
|
43
|
+
autoload :Ruby, 'chkbuild/targets/ruby'
|
44
|
+
autoload :GCC, 'chkbuild/targets/gcc'
|
45
|
+
end
|
@@ -0,0 +1,718 @@
|
|
1
|
+
# Copyright (C) 2006,2007,2008,2009 Tanaka Akira <akr@fsij.org>
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
5
|
+
#
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
7
|
+
# list of conditions and the following disclaimer.
|
8
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer in the documentation
|
10
|
+
# and/or other materials provided with the distribution.
|
11
|
+
# 3. The name of the author may not be used to endorse or promote products
|
12
|
+
# derived from this software without specific prior written permission.
|
13
|
+
#
|
14
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
15
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
16
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
17
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
18
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
19
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
20
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
21
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
22
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
23
|
+
# OF SUCH DAMAGE.
|
24
|
+
|
25
|
+
require 'fileutils'
|
26
|
+
require 'time'
|
27
|
+
require 'zlib'
|
28
|
+
require "erb"
|
29
|
+
include ERB::Util
|
30
|
+
require "uri"
|
31
|
+
require "tempfile"
|
32
|
+
require "pathname"
|
33
|
+
|
34
|
+
require 'escape'
|
35
|
+
require 'timeoutcom'
|
36
|
+
require 'gdb'
|
37
|
+
require "udiff"
|
38
|
+
require "util"
|
39
|
+
|
40
|
+
module ChkBuild
|
41
|
+
end
|
42
|
+
require 'chkbuild/options'
|
43
|
+
require 'chkbuild/target'
|
44
|
+
require 'chkbuild/title'
|
45
|
+
require "chkbuild/logfile"
|
46
|
+
require 'chkbuild/upload'
|
47
|
+
|
48
|
+
class ChkBuild::Build
|
49
|
+
include Util
|
50
|
+
|
51
|
+
def initialize(target, suffixes, depbuilds)
|
52
|
+
@target = target
|
53
|
+
@suffixes = suffixes
|
54
|
+
@depbuilds = depbuilds
|
55
|
+
|
56
|
+
@target_dir = ChkBuild.build_top + self.depsuffixed_name
|
57
|
+
@public = ChkBuild.public_top + self.depsuffixed_name
|
58
|
+
@public_log = @public+"log"
|
59
|
+
@current_txt = @public+"current.txt"
|
60
|
+
end
|
61
|
+
attr_reader :target, :suffixes, :depbuilds
|
62
|
+
attr_reader :target_dir
|
63
|
+
|
64
|
+
def suffixed_name
|
65
|
+
name = @target.target_name.dup
|
66
|
+
@suffixes.each {|suffix|
|
67
|
+
name << '-' << suffix
|
68
|
+
}
|
69
|
+
name
|
70
|
+
end
|
71
|
+
|
72
|
+
def depsuffixed_name
|
73
|
+
name = self.suffixed_name
|
74
|
+
@depbuilds.each {|depbuild|
|
75
|
+
name << '_' << depbuild.suffixed_name
|
76
|
+
}
|
77
|
+
name
|
78
|
+
end
|
79
|
+
|
80
|
+
def traverse_depbuild(&block)
|
81
|
+
@depbuilds.each {|depbuild|
|
82
|
+
yield depbuild
|
83
|
+
depbuild.traverse_depbuild(&block)
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_time_sequence
|
88
|
+
dirs = @target_dir.entries.map {|e| e.to_s }
|
89
|
+
dirs.reject! {|d| /\A\d{8}T\d{6}\z/ !~ d } # year 10000 problem
|
90
|
+
dirs.sort!
|
91
|
+
dirs
|
92
|
+
end
|
93
|
+
|
94
|
+
def log_time_sequence
|
95
|
+
return [] if !@public_log.directory?
|
96
|
+
names = @public_log.entries.map {|e| e.to_s }
|
97
|
+
result = []
|
98
|
+
names.each {|n|
|
99
|
+
result << $1 if /\A(\d{8}T\d{6})(?:\.log)?\.txt\.gz\z/ =~ n
|
100
|
+
}
|
101
|
+
result.sort!
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
################
|
106
|
+
|
107
|
+
def build
|
108
|
+
dep_dirs = []
|
109
|
+
@depbuilds.each {|depbuild|
|
110
|
+
dep_dirs << "#{depbuild.target.target_name}=#{depbuild.dir}"
|
111
|
+
}
|
112
|
+
status = self.build_in_child(dep_dirs)
|
113
|
+
status.to_i == 0
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_in_child(dep_dirs)
|
117
|
+
if defined? @built_status
|
118
|
+
raise "already built"
|
119
|
+
end
|
120
|
+
branch_info = @suffixes + dep_dirs
|
121
|
+
start_time_obj = Time.now
|
122
|
+
@start_time = start_time_obj.strftime("%Y%m%dT%H%M%S")
|
123
|
+
dir = ChkBuild.build_top + self.depsuffixed_name + @start_time
|
124
|
+
r, w = IO.pipe
|
125
|
+
r.close_on_exec = true
|
126
|
+
w.close_on_exec = true
|
127
|
+
pid = fork {
|
128
|
+
r.close
|
129
|
+
if child_build_wrapper(w, *branch_info)
|
130
|
+
exit 0
|
131
|
+
else
|
132
|
+
exit 1
|
133
|
+
end
|
134
|
+
}
|
135
|
+
w.close
|
136
|
+
str = r.read
|
137
|
+
r.close
|
138
|
+
status = Process.wait2(pid)[1]
|
139
|
+
begin
|
140
|
+
version = Marshal.load(str)
|
141
|
+
rescue ArgumentError
|
142
|
+
version = self.suffixed_name
|
143
|
+
end
|
144
|
+
@built_status = status
|
145
|
+
@built_dir = dir
|
146
|
+
@built_version = version
|
147
|
+
return status
|
148
|
+
end
|
149
|
+
|
150
|
+
def start_time
|
151
|
+
return @start_time if defined? @start_time
|
152
|
+
raise "#{self.suffixed_name}: no start_time yet"
|
153
|
+
end
|
154
|
+
|
155
|
+
def success?
|
156
|
+
if defined? @built_status
|
157
|
+
if @built_status.to_i == 0
|
158
|
+
true
|
159
|
+
else
|
160
|
+
false
|
161
|
+
end
|
162
|
+
else
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def status
|
168
|
+
return @built_status if defined? @built_status
|
169
|
+
raise "#{self.suffixed_name}: no status yet"
|
170
|
+
end
|
171
|
+
|
172
|
+
def dir
|
173
|
+
return @built_dir if defined? @built_dir
|
174
|
+
raise "#{self.suffixed_name}: no dir yet"
|
175
|
+
end
|
176
|
+
|
177
|
+
def version
|
178
|
+
return @built_version if defined? @built_version
|
179
|
+
raise "#{self.suffixed_name}: no version yet"
|
180
|
+
end
|
181
|
+
|
182
|
+
def child_build_wrapper(parent_pipe, *branch_info)
|
183
|
+
ret = ChkBuild.lock_puts(self.depsuffixed_name) {
|
184
|
+
@parent_pipe = parent_pipe
|
185
|
+
@errors = []
|
186
|
+
child_build_target(*branch_info)
|
187
|
+
}
|
188
|
+
ret
|
189
|
+
end
|
190
|
+
|
191
|
+
def make_local_tmpdir
|
192
|
+
tmpdir = @build_dir + 'tmp'
|
193
|
+
tmpdir.mkpath
|
194
|
+
ENV['TMPDIR'] = tmpdir.to_s
|
195
|
+
end
|
196
|
+
|
197
|
+
def child_build_target(*branch_info)
|
198
|
+
opts = @target.opts
|
199
|
+
@build_dir = @target_dir + @start_time
|
200
|
+
@log_filename = @build_dir + 'log'
|
201
|
+
mkcd @target_dir
|
202
|
+
raise "already exist: #{@start_time}" if File.exist? @start_time
|
203
|
+
Dir.mkdir @start_time # fail if it is already exists.
|
204
|
+
Dir.chdir @start_time
|
205
|
+
@logfile = ChkBuild::LogFile.write_open(@log_filename, self)
|
206
|
+
@logfile.change_default_output
|
207
|
+
@public.mkpath
|
208
|
+
@public_log.mkpath
|
209
|
+
force_link "log", @current_txt
|
210
|
+
make_local_tmpdir
|
211
|
+
remove_old_build(@start_time, opts.fetch(:old, ChkBuild.num_oldbuilds))
|
212
|
+
@logfile.start_section 'start'
|
213
|
+
ret = nil
|
214
|
+
with_procmemsize(opts) {
|
215
|
+
ret = catch_error { @target.build_proc.call(self, *branch_info) }
|
216
|
+
output_status_section
|
217
|
+
@logfile.start_section 'end'
|
218
|
+
}
|
219
|
+
force_link @current_txt, @public+'last.txt' if @current_txt.file?
|
220
|
+
titlegen = ChkBuild::Title.new(@target, @logfile)
|
221
|
+
title_succ = catch_error('run_hooks') { titlegen.run_hooks }
|
222
|
+
title = titlegen.make_title
|
223
|
+
title << " (titlegen.run_hooks error)" if !title_succ
|
224
|
+
Marshal.dump(titlegen.version, @parent_pipe)
|
225
|
+
@parent_pipe.close
|
226
|
+
@compressed_log_basename = "#{@start_time}.log.txt.gz"
|
227
|
+
@compressed_diff_basename = "#{@start_time}.diff.txt.gz"
|
228
|
+
compress_file(@log_filename, @public_log+@compressed_log_basename)
|
229
|
+
different_sections = make_diff
|
230
|
+
update_summary(title, different_sections)
|
231
|
+
update_recent
|
232
|
+
make_html_log(@log_filename, title, different_sections, @public+"last.html")
|
233
|
+
compress_file(@public+"last.html", @public+"last.html.gz")
|
234
|
+
ChkBuild.run_upload_hooks(self.suffixed_name)
|
235
|
+
ret
|
236
|
+
end
|
237
|
+
|
238
|
+
attr_reader :logfile
|
239
|
+
|
240
|
+
def with_procmemsize(opts)
|
241
|
+
if opts[:procmemsize]
|
242
|
+
current_pid = $$
|
243
|
+
procmemsize_pid = fork { exec *%W[procmemsize -p #{current_pid}] }
|
244
|
+
ret = yield
|
245
|
+
Process.kill :TERM, procmemsize_pid
|
246
|
+
Process.wait procmemsize_pid
|
247
|
+
else
|
248
|
+
ret = yield
|
249
|
+
end
|
250
|
+
ret
|
251
|
+
end
|
252
|
+
|
253
|
+
def output_status_section
|
254
|
+
@logfile.start_section 'success' if @errors.empty?
|
255
|
+
end
|
256
|
+
|
257
|
+
def catch_error(name=nil)
|
258
|
+
err = nil
|
259
|
+
begin
|
260
|
+
yield
|
261
|
+
rescue Exception => err
|
262
|
+
end
|
263
|
+
return true unless err
|
264
|
+
@errors << err
|
265
|
+
@logfile.start_section("#{name} error") if name
|
266
|
+
show_backtrace err unless CommandError === err
|
267
|
+
GDB.check_core(@build_dir)
|
268
|
+
if CommandError === err
|
269
|
+
puts "failed(#{err.reason})"
|
270
|
+
else
|
271
|
+
if err.respond_to? :reason
|
272
|
+
puts "failed(#{err.reason} #{err.class})"
|
273
|
+
else
|
274
|
+
puts "failed(#{err.class})"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
|
280
|
+
def network_access(name=nil)
|
281
|
+
begin
|
282
|
+
yield
|
283
|
+
ensure
|
284
|
+
if err = $!
|
285
|
+
@logfile.start_section("neterror")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def build_dir() @build_dir end
|
291
|
+
|
292
|
+
def remove_old_build(current, num)
|
293
|
+
dirs = build_time_sequence
|
294
|
+
dirs.delete current
|
295
|
+
return if dirs.length <= num
|
296
|
+
dirs[-num..-1] = []
|
297
|
+
dirs.each {|d|
|
298
|
+
(@target_dir+d).rmtree
|
299
|
+
}
|
300
|
+
end
|
301
|
+
|
302
|
+
def update_summary(title, different_sections)
|
303
|
+
start_time = @start_time
|
304
|
+
if different_sections
|
305
|
+
if different_sections.empty?
|
306
|
+
diff_txt = "diff"
|
307
|
+
else
|
308
|
+
diff_txt = "diff:#{different_sections.join(',')}"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
open(@public+"summary.txt", "a") {|f|
|
312
|
+
f.print "#{start_time} #{title}"
|
313
|
+
f.print " (#{diff_txt})" if diff_txt
|
314
|
+
f.puts
|
315
|
+
}
|
316
|
+
open(@public+"summary.html", "a") {|f|
|
317
|
+
if f.stat.size == 0
|
318
|
+
f.puts "<title>#{h self.depsuffixed_name} build summary</title>"
|
319
|
+
f.puts "<h1>#{h self.depsuffixed_name} build summary</h1>"
|
320
|
+
f.puts "<p><a href=\"../\">chkbuild</a></p>"
|
321
|
+
end
|
322
|
+
f.print "<a href=\"log/#{h @compressed_log_basename}\" name=\"#{start_time}\">#{h start_time}</a> #{h title}"
|
323
|
+
f.print " (<a href=\"log/#{h @compressed_diff_basename}\">#{h diff_txt}</a>)" if diff_txt
|
324
|
+
f.puts "<br>"
|
325
|
+
}
|
326
|
+
end
|
327
|
+
|
328
|
+
RECENT_HTMLTemplate = <<'End'
|
329
|
+
<html>
|
330
|
+
<head>
|
331
|
+
<title><%= h title %></title>
|
332
|
+
<meta name="author" content="chkbuild">
|
333
|
+
<meta name="generator" content="chkbuild">
|
334
|
+
</head>
|
335
|
+
<body>
|
336
|
+
<h1><%= h title %></h1>
|
337
|
+
<p>
|
338
|
+
<a href="../">chkbuild</a>
|
339
|
+
<a href="summary.html">summary</a>
|
340
|
+
<a href="recent.html">recent</a>
|
341
|
+
<a href="last.html.gz">last</a>
|
342
|
+
</p>
|
343
|
+
<%= recent_summary.chomp %>
|
344
|
+
<hr>
|
345
|
+
<p>
|
346
|
+
<a href="../">chkbuild</a>
|
347
|
+
<a href="summary.html">summary</a>
|
348
|
+
<a href="recent.html">recent</a>
|
349
|
+
<a href="last.html.gz">last</a>
|
350
|
+
</p>
|
351
|
+
</body>
|
352
|
+
</html>
|
353
|
+
End
|
354
|
+
|
355
|
+
def update_recent
|
356
|
+
start_time = @start_time
|
357
|
+
summary_path = @public+"summary.html"
|
358
|
+
lines = []
|
359
|
+
summary_path.open {|f|
|
360
|
+
while l = f.gets
|
361
|
+
lines << l
|
362
|
+
lines.shift if 10 < lines.length
|
363
|
+
end
|
364
|
+
}
|
365
|
+
while !lines.empty? && /\A<a / !~ lines[0]
|
366
|
+
lines.shift
|
367
|
+
end
|
368
|
+
title = "#{self.depsuffixed_name} recent build summary"
|
369
|
+
|
370
|
+
recent_summary = lines.reverse.join
|
371
|
+
content = ERB.new(RECENT_HTMLTemplate).result(binding)
|
372
|
+
|
373
|
+
recent_path = @public+"recent.html"
|
374
|
+
atomic_make_file(recent_path, content)
|
375
|
+
end
|
376
|
+
|
377
|
+
def markup(str)
|
378
|
+
result = ''
|
379
|
+
i = 0
|
380
|
+
str.scan(/#{URI.regexp(['http'])}/o) {
|
381
|
+
result << h(str[i...$~.begin(0)]) if i < $~.begin(0)
|
382
|
+
result << "<a href=\"#{h $&}\">#{h $&}</a>"
|
383
|
+
i = $~.end(0)
|
384
|
+
}
|
385
|
+
result << h(str[i...str.length]) if i < str.length
|
386
|
+
result
|
387
|
+
end
|
388
|
+
|
389
|
+
LAST_HTMLTemplate = <<'End'
|
390
|
+
<html>
|
391
|
+
<head>
|
392
|
+
<title><%= h title %></title>
|
393
|
+
<meta name="author" content="chkbuild">
|
394
|
+
<meta name="generator" content="chkbuild">
|
395
|
+
</head>
|
396
|
+
<body>
|
397
|
+
<h1><%= h title %></h1>
|
398
|
+
<p>
|
399
|
+
<a href="../">chkbuild</a>
|
400
|
+
<a href="summary.html">summary</a>
|
401
|
+
<a href="recent.html">recent</a>
|
402
|
+
<a href="<%=h permalink %>">permalink</a>
|
403
|
+
% if has_diff
|
404
|
+
<a href="<%=h diff_permalink %>">diff</a>
|
405
|
+
% end
|
406
|
+
</p>
|
407
|
+
<pre><%= markup log %></pre>
|
408
|
+
<hr>
|
409
|
+
<p>
|
410
|
+
<a href="../">chkbuild</a>
|
411
|
+
<a href="summary.html">summary</a>
|
412
|
+
<a href="recent.html">recent</a>
|
413
|
+
<a href="<%=h permalink %>">permalink</a>
|
414
|
+
% if has_diff
|
415
|
+
<a href="<%=h diff_permalink %>">diff</a>
|
416
|
+
% end
|
417
|
+
</p>
|
418
|
+
</body>
|
419
|
+
</html>
|
420
|
+
End
|
421
|
+
|
422
|
+
def make_html_log(log_filename, title, has_diff, dst)
|
423
|
+
log = File.read(log_filename)
|
424
|
+
log.force_encoding("ascii-8bit") if log.respond_to? :force_encoding
|
425
|
+
permalink = "log/#{@compressed_log_basename}"
|
426
|
+
diff_permalink = "log/#{@compressed_diff_basename}"
|
427
|
+
content = ERB.new(LAST_HTMLTemplate, nil, '%').result(binding)
|
428
|
+
atomic_make_file(dst, content)
|
429
|
+
end
|
430
|
+
|
431
|
+
def compress_file(src, dst)
|
432
|
+
Zlib::GzipWriter.wrap(open(dst, "w")) {|z|
|
433
|
+
open(src) {|f|
|
434
|
+
FileUtils.copy_stream(f, z)
|
435
|
+
}
|
436
|
+
}
|
437
|
+
end
|
438
|
+
|
439
|
+
def show_backtrace(err=$!)
|
440
|
+
puts "|#{err.message} (#{err.class})"
|
441
|
+
err.backtrace.each {|pos| puts "| #{pos}" }
|
442
|
+
end
|
443
|
+
|
444
|
+
def make_diff
|
445
|
+
time2 = @start_time
|
446
|
+
entries = Dir.entries(@public_log)
|
447
|
+
time_seq = []
|
448
|
+
entries.each {|f|
|
449
|
+
if /\A(\d{8}T\d{6})(?:\.log)?\.txt\.gz\z/ =~ f # year 10000 problem
|
450
|
+
time_seq << $1
|
451
|
+
end
|
452
|
+
}
|
453
|
+
time_seq.sort!
|
454
|
+
time_seq.delete time2
|
455
|
+
while !time_seq.empty? &&
|
456
|
+
open_gziped_log(time_seq.last) {|f|
|
457
|
+
neterror = false
|
458
|
+
f.each_line {|line|
|
459
|
+
line.force_encoding("ascii-8bit") if line.respond_to? :force_encoding
|
460
|
+
if /\A== neterror / =~ line
|
461
|
+
neterror = true
|
462
|
+
break
|
463
|
+
end
|
464
|
+
}
|
465
|
+
if neterror
|
466
|
+
true
|
467
|
+
else
|
468
|
+
false
|
469
|
+
end
|
470
|
+
}
|
471
|
+
time_seq.pop
|
472
|
+
end
|
473
|
+
return nil if time_seq.empty?
|
474
|
+
time1 = time_seq.last
|
475
|
+
different_sections = nil
|
476
|
+
output_path = @public_log+@compressed_diff_basename
|
477
|
+
Zlib::GzipWriter.wrap(open(output_path, "w")) {|z|
|
478
|
+
different_sections = output_diff(time1, time2, z)
|
479
|
+
}
|
480
|
+
if !different_sections
|
481
|
+
output_path.unlink
|
482
|
+
return nil
|
483
|
+
end
|
484
|
+
return different_sections
|
485
|
+
end
|
486
|
+
|
487
|
+
def output_diff(t1, t2, out)
|
488
|
+
has_change_line = output_change_lines(t2, out)
|
489
|
+
tmp1 = make_diff_content(t1)
|
490
|
+
tmp2 = make_diff_content(t2)
|
491
|
+
tmp1, tmp2 = sort_diff_content(t1, tmp1, t2, tmp2)
|
492
|
+
header = "--- #{t1}\n+++ #{t2}\n"
|
493
|
+
has_diff = has_change_line | UDiff.diff(tmp1.path, tmp2.path, out, header)
|
494
|
+
return nil if !has_diff
|
495
|
+
ret = []
|
496
|
+
ret << 'src' if has_change_line
|
497
|
+
ret.concat different_sections(tmp1, tmp2)
|
498
|
+
ret
|
499
|
+
end
|
500
|
+
|
501
|
+
def output_change_lines(t2, out)
|
502
|
+
has_diff = false
|
503
|
+
open_gziped_log(t2) {|f|
|
504
|
+
has_change_line = false
|
505
|
+
f.each {|line|
|
506
|
+
if ChkBuild::Target::CHANGE_LINE_PAT =~ line
|
507
|
+
out.puts line
|
508
|
+
has_change_line = true
|
509
|
+
end
|
510
|
+
}
|
511
|
+
if has_change_line
|
512
|
+
out.puts
|
513
|
+
has_diff = true
|
514
|
+
end
|
515
|
+
}
|
516
|
+
has_diff
|
517
|
+
end
|
518
|
+
|
519
|
+
def different_sections(tmp1, tmp2)
|
520
|
+
logfile1 = ChkBuild::LogFile.read_open(tmp1.path)
|
521
|
+
logfile2 = ChkBuild::LogFile.read_open(tmp2.path)
|
522
|
+
secnames1 = logfile1.secnames
|
523
|
+
secnames2 = logfile2.secnames
|
524
|
+
different_sections = secnames1 - secnames2
|
525
|
+
secnames2.each {|secname|
|
526
|
+
if !secnames1.include?(secname)
|
527
|
+
different_sections << secname
|
528
|
+
elsif logfile1.section_size(secname) != logfile2.section_size(secname)
|
529
|
+
different_sections << secname
|
530
|
+
elsif logfile1.get_section(secname) != logfile2.get_section(secname)
|
531
|
+
different_sections << secname
|
532
|
+
end
|
533
|
+
}
|
534
|
+
different_sections
|
535
|
+
end
|
536
|
+
|
537
|
+
def make_diff_content(time)
|
538
|
+
times = [time]
|
539
|
+
uncompressed = Tempfile.open("#{time}.u.")
|
540
|
+
open_gziped_log(time) {|z|
|
541
|
+
FileUtils.copy_stream(z, uncompressed)
|
542
|
+
}
|
543
|
+
uncompressed.flush
|
544
|
+
logfile = ChkBuild::LogFile.read_open(uncompressed.path)
|
545
|
+
logfile.dependencies.each {|dep_suffixed_name, dep_time, dep_version|
|
546
|
+
times << dep_time
|
547
|
+
}
|
548
|
+
pat = Regexp.union(*times.uniq)
|
549
|
+
tmp = Tempfile.open("#{time}.d.")
|
550
|
+
open_gziped_log(time) {|z|
|
551
|
+
z.each_line {|line|
|
552
|
+
line = line.gsub(pat, '<buildtime>')
|
553
|
+
@target.each_diff_preprocess_hook {|block|
|
554
|
+
catch_error(block.to_s) { line = block.call(line) }
|
555
|
+
}
|
556
|
+
tmp << line
|
557
|
+
}
|
558
|
+
}
|
559
|
+
tmp.flush
|
560
|
+
tmp
|
561
|
+
end
|
562
|
+
|
563
|
+
def sort_diff_content(time1, tmp1, time2, tmp2)
|
564
|
+
pat = @target.diff_preprocess_sort_pattern
|
565
|
+
return tmp1, tmp2 if !pat
|
566
|
+
|
567
|
+
h1, h2 = [tmp1, tmp2].map {|tmp|
|
568
|
+
h = {}
|
569
|
+
tmp.rewind
|
570
|
+
tmp.gather_each(pat) {|lines|
|
571
|
+
next unless 1 < lines.length && pat =~ lines.first
|
572
|
+
h[$&] = Digest::SHA256.hexdigest(lines.sort.join(''))
|
573
|
+
}
|
574
|
+
h
|
575
|
+
}
|
576
|
+
|
577
|
+
h0 = {}
|
578
|
+
h1.each_key {|k| h0[k] = true if h1[k] == h2[k] }
|
579
|
+
|
580
|
+
newtmp1, newtmp2 = [[time1, tmp1], [time2, tmp2]].map {|time, tmp|
|
581
|
+
newtmp = Tempfile.open("#{time}.d.")
|
582
|
+
tmp.rewind
|
583
|
+
tmp.gather_each(pat) {|lines|
|
584
|
+
if 1 < lines.length && pat =~ lines.first && h0[$&]
|
585
|
+
newtmp.print lines.sort.join('')
|
586
|
+
else
|
587
|
+
newtmp.print lines.join('')
|
588
|
+
end
|
589
|
+
}
|
590
|
+
tmp.close(true)
|
591
|
+
newtmp.rewind
|
592
|
+
newtmp
|
593
|
+
}
|
594
|
+
|
595
|
+
return newtmp1, newtmp2
|
596
|
+
end
|
597
|
+
|
598
|
+
def open_gziped_log(time, &block)
|
599
|
+
if File.file?(@public_log+"#{time}.log.txt.gz")
|
600
|
+
filename = @public_log+"#{time}.log.txt.gz"
|
601
|
+
else
|
602
|
+
filename = @public_log+"#{time}.txt.gz"
|
603
|
+
end
|
604
|
+
Zlib::GzipReader.wrap(open(filename), &block)
|
605
|
+
end
|
606
|
+
|
607
|
+
class CommandError < StandardError
|
608
|
+
def initialize(status, reason, message=reason)
|
609
|
+
super message
|
610
|
+
@reason = reason
|
611
|
+
@status = status
|
612
|
+
end
|
613
|
+
|
614
|
+
attr_accessor :reason
|
615
|
+
end
|
616
|
+
def run(command, *args, &block)
|
617
|
+
opts = @target.opts.dup
|
618
|
+
opts.update args.pop if Hash === args.last
|
619
|
+
|
620
|
+
if opts.include?(:section)
|
621
|
+
secname = opts[:section]
|
622
|
+
else
|
623
|
+
secname = opts[:reason] || File.basename(command)
|
624
|
+
end
|
625
|
+
@logfile.start_section(secname) if secname
|
626
|
+
|
627
|
+
if !opts.include?(:output_interval_timeout)
|
628
|
+
opts[:output_interval_timeout] = '10min'
|
629
|
+
end
|
630
|
+
|
631
|
+
puts "+ #{Escape.shell_command [command, *args]}"
|
632
|
+
pos = STDOUT.pos
|
633
|
+
begin
|
634
|
+
command_status = TimeoutCommand.timeout_command(opts.fetch(:timeout, '1h'), STDERR, opts) {
|
635
|
+
run_in_child(opts, command, *args)
|
636
|
+
}
|
637
|
+
ensure
|
638
|
+
exc = $!
|
639
|
+
if exc && secname
|
640
|
+
class << exc
|
641
|
+
attr_accessor :reason
|
642
|
+
end
|
643
|
+
exc.reason = secname
|
644
|
+
end
|
645
|
+
end
|
646
|
+
begin
|
647
|
+
if command_status.exitstatus != 0
|
648
|
+
if command_status.exited?
|
649
|
+
puts "exit #{command_status.exitstatus}"
|
650
|
+
elsif command_status.signaled?
|
651
|
+
puts "signal #{SignalNum2Name[command_status.termsig]} (#{command_status.termsig})"
|
652
|
+
elsif command_status.stopped?
|
653
|
+
puts "stop #{SignalNum2Name[command_status.stopsig]} (#{command_status.stopsig})"
|
654
|
+
else
|
655
|
+
p command_status
|
656
|
+
end
|
657
|
+
raise CommandError.new(command_status, opts.fetch(:section, command))
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
def run_in_child(opts, command, *args)
|
663
|
+
opts.each {|k, v|
|
664
|
+
next if /\AENV:/ !~ k.to_s
|
665
|
+
ENV[$'] = v
|
666
|
+
}
|
667
|
+
if Process.respond_to? :setrlimit
|
668
|
+
resource_unlimit(:RLIMIT_CORE)
|
669
|
+
limit = ChkBuild.get_limit
|
670
|
+
opts.each {|k, v| limit[$'.intern] = v if /\Ar?limit_/ =~ k.to_s }
|
671
|
+
resource_limit(:RLIMIT_CPU, limit.fetch(:cpu))
|
672
|
+
resource_limit(:RLIMIT_STACK, limit.fetch(:stack))
|
673
|
+
resource_limit(:RLIMIT_DATA, limit.fetch(:data))
|
674
|
+
resource_limit(:RLIMIT_AS, limit.fetch(:as))
|
675
|
+
#system('sh', '-c', "ulimit -a")
|
676
|
+
end
|
677
|
+
alt_commands = opts.fetch(:alt_commands, [])
|
678
|
+
if opts.include?(:stdout)
|
679
|
+
STDOUT.reopen(opts[:stdout], "w")
|
680
|
+
end
|
681
|
+
if opts.include?(:stderr)
|
682
|
+
STDERR.reopen(opts[:stderr], "w")
|
683
|
+
end
|
684
|
+
begin
|
685
|
+
exec [command, command], *args
|
686
|
+
rescue Errno::ENOENT
|
687
|
+
if !alt_commands.empty?
|
688
|
+
command = alt_commands.shift
|
689
|
+
retry
|
690
|
+
else
|
691
|
+
raise
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
SignalNum2Name = Hash.new('unknown signal')
|
697
|
+
Signal.list.each {|name, num| SignalNum2Name[num] = "SIG#{name}" }
|
698
|
+
|
699
|
+
def make(*args)
|
700
|
+
opts = {}
|
701
|
+
opts = args.pop if Hash === args.last
|
702
|
+
opts = opts.dup
|
703
|
+
opts[:alt_commands] = ['make']
|
704
|
+
|
705
|
+
make_opts, targets = args.partition {|a| /=/ =~ a }
|
706
|
+
if targets.empty?
|
707
|
+
opts[:section] ||= 'make'
|
708
|
+
self.run("gmake", *(make_opts + [opts]))
|
709
|
+
else
|
710
|
+
targets.each {|target|
|
711
|
+
h = opts.dup
|
712
|
+
h[:reason] = target
|
713
|
+
h[:section] ||= target
|
714
|
+
self.run("gmake", target, *(make_opts + [h]))
|
715
|
+
}
|
716
|
+
end
|
717
|
+
end
|
718
|
+
end
|