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.
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