yugui-chkbuild 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/README.ja.rd +191 -0
  2. data/Rakefile +56 -0
  3. data/VERSION +1 -0
  4. data/bin/last-build +28 -0
  5. data/bin/start-build +37 -0
  6. data/chkbuild.gemspec +107 -0
  7. data/core_ext/io.rb +17 -0
  8. data/core_ext/string.rb +10 -0
  9. data/lib/chkbuild.rb +45 -0
  10. data/lib/chkbuild/build.rb +718 -0
  11. data/lib/chkbuild/lock.rb +57 -0
  12. data/lib/chkbuild/logfile.rb +230 -0
  13. data/lib/chkbuild/main.rb +138 -0
  14. data/lib/chkbuild/options.rb +62 -0
  15. data/lib/chkbuild/scm/cvs.rb +132 -0
  16. data/lib/chkbuild/scm/git.rb +223 -0
  17. data/lib/chkbuild/scm/svn.rb +215 -0
  18. data/lib/chkbuild/scm/xforge.rb +33 -0
  19. data/lib/chkbuild/target.rb +180 -0
  20. data/lib/chkbuild/targets/gcc.rb +94 -0
  21. data/lib/chkbuild/targets/ruby.rb +456 -0
  22. data/lib/chkbuild/title.rb +107 -0
  23. data/lib/chkbuild/upload.rb +66 -0
  24. data/lib/misc/escape.rb +535 -0
  25. data/lib/misc/gdb.rb +74 -0
  26. data/lib/misc/timeoutcom.rb +174 -0
  27. data/lib/misc/udiff.rb +244 -0
  28. data/lib/misc/util.rb +232 -0
  29. data/sample/build-autoconf-ruby +69 -0
  30. data/sample/build-gcc-ruby +43 -0
  31. data/sample/build-ruby +37 -0
  32. data/sample/build-ruby2 +36 -0
  33. data/sample/build-svn +55 -0
  34. data/sample/build-yarv +35 -0
  35. data/sample/test-apr +12 -0
  36. data/sample/test-catcherr +23 -0
  37. data/sample/test-combfail +21 -0
  38. data/sample/test-core +14 -0
  39. data/sample/test-core2 +19 -0
  40. data/sample/test-date +9 -0
  41. data/sample/test-dep +17 -0
  42. data/sample/test-depver +14 -0
  43. data/sample/test-echo +9 -0
  44. data/sample/test-env +9 -0
  45. data/sample/test-error +9 -0
  46. data/sample/test-fail +18 -0
  47. data/sample/test-fmesg +16 -0
  48. data/sample/test-gcc-v +15 -0
  49. data/sample/test-git +11 -0
  50. data/sample/test-leave-proc +9 -0
  51. data/sample/test-limit +9 -0
  52. data/sample/test-make +9 -0
  53. data/sample/test-neterr +16 -0
  54. data/sample/test-savannah +14 -0
  55. data/sample/test-sleep +9 -0
  56. data/sample/test-timeout +9 -0
  57. data/sample/test-timeout2 +10 -0
  58. data/sample/test-timeout3 +9 -0
  59. data/sample/test-upload +13 -0
  60. data/sample/test-warn +13 -0
  61. data/setup/upload-rsync-ssh +572 -0
  62. data/test/misc/test-escape.rb +17 -0
  63. data/test/misc/test-logfile.rb +108 -0
  64. data/test/misc/test-timeoutcom.rb +23 -0
  65. data/test/test_helper.rb +9 -0
  66. metadata +123 -0
@@ -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