Class: ChkBuild::Build

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/chkbuild/build.rb,
lib/chkbuild/scm/cvs.rb,
lib/chkbuild/scm/git.rb,
lib/chkbuild/scm/svn.rb,
lib/chkbuild/scm/xforge.rb

Defined Under Namespace

Classes: CommandError, GitHub

Constant Summary collapse

RECENT_HTMLTemplate =
<<'End'
<html>
  <head>
    <title><%= h title %></title>
    <meta name="author" content="chkbuild">
    <meta name="generator" content="chkbuild">
  </head>
  <body>
    <h1><%= h title %></h1>
    <p>
      <a href="../">chkbuild</a>
      <a href="summary.html">summary</a>
      <a href="recent.html">recent</a>
      <a href="last.html.gz">last</a>
    </p>
<%= recent_summary.chomp %>
    <hr>
    <p>
      <a href="../">chkbuild</a>
      <a href="summary.html">summary</a>
      <a href="recent.html">recent</a>
      <a href="last.html.gz">last</a>
    </p>
  </body>
</html>
End
LAST_HTMLTemplate =
<<'End'
<html>
  <head>
    <title><%= h title %></title>
    <meta name="author" content="chkbuild">
    <meta name="generator" content="chkbuild">
  </head>
  <body>
    <h1><%= h title %></h1>
    <p>
      <a href="../">chkbuild</a>
      <a href="summary.html">summary</a>
      <a href="recent.html">recent</a>
      <a href="<%=h permalink %>">permalink</a>
% if has_diff
      <a href="<%=h diff_permalink %>">diff</a>
% end
    </p>
    <pre><%= markup log %></pre>
    <hr>
    <p>
      <a href="../">chkbuild</a>
      <a href="summary.html">summary</a>
      <a href="recent.html">recent</a>
      <a href="<%=h permalink %>">permalink</a>
% if has_diff
      <a href="<%=h diff_permalink %>">diff</a>
% end
    </p>
  </body>
</html>
End
SignalNum2Name =
Hash.new('unknown signal')

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util

#atomic_make_file, #force_link, #mkcd, #product, #product_each, #resource_limit, #resource_unlimit, #rproduct, #sha256_digest_file, #simple_hostname, #with_tempfile

Constructor Details

#initialize(target, suffixes, depbuilds) ⇒ Build

Returns a new instance of Build.



51
52
53
54
55
56
57
58
59
60
# File 'lib/chkbuild/build.rb', line 51

def initialize(target, suffixes, depbuilds)
  @target = target
  @suffixes = suffixes
  @depbuilds = depbuilds

  @target_dir = ChkBuild.build_top + self.depsuffixed_name
  @public = ChkBuild.public_top + self.depsuffixed_name
  @public_log = @public+"log"
  @current_txt = @public+"current.txt"
end

Instance Attribute Details

#depbuildsObject (readonly)

Returns the value of attribute depbuilds.



61
62
63
# File 'lib/chkbuild/build.rb', line 61

def depbuilds
  @depbuilds
end

#logfileObject (readonly)

Returns the value of attribute logfile.



238
239
240
# File 'lib/chkbuild/build.rb', line 238

def logfile
  @logfile
end

#suffixesObject (readonly)

Returns the value of attribute suffixes.



61
62
63
# File 'lib/chkbuild/build.rb', line 61

def suffixes
  @suffixes
end

#targetObject (readonly)

Returns the value of attribute target.



61
62
63
# File 'lib/chkbuild/build.rb', line 61

def target
  @target
end

#target_dirObject (readonly)

Returns the value of attribute target_dir.



62
63
64
# File 'lib/chkbuild/build.rb', line 62

def target_dir
  @target_dir
end

Instance Method Details

#buildObject



107
108
109
110
111
112
113
114
# File 'lib/chkbuild/build.rb', line 107

def build
  dep_dirs = []
  @depbuilds.each {|depbuild|
    dep_dirs << "#{depbuild.target.target_name}=#{depbuild.dir}"
  }
  status = self.build_in_child(dep_dirs)
  status.to_i == 0
end

#build_dirObject



290
# File 'lib/chkbuild/build.rb', line 290

def build_dir() @build_dir end

#build_in_child(dep_dirs) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/chkbuild/build.rb', line 116

def build_in_child(dep_dirs)
  if defined? @built_status
    raise "already built"
  end
  branch_info = @suffixes + dep_dirs
  start_time_obj = Time.now
  @start_time = start_time_obj.strftime("%Y%m%dT%H%M%S")
  dir = ChkBuild.build_top + self.depsuffixed_name + @start_time
  r, w = IO.pipe
  r.close_on_exec = true
  w.close_on_exec = true
  pid = fork {
    r.close
    if child_build_wrapper(w, *branch_info)
      exit 0
    else
      exit 1
    end
  }
  w.close
  str = r.read
  r.close
  status = Process.wait2(pid)[1]
  begin
    version = Marshal.load(str)
  rescue ArgumentError
    version = self.suffixed_name
  end
  @built_status = status
  @built_dir = dir
  @built_version = version
  return status
end

#build_time_sequenceObject



87
88
89
90
91
92
# File 'lib/chkbuild/build.rb', line 87

def build_time_sequence
  dirs = @target_dir.entries.map {|e| e.to_s }
  dirs.reject! {|d| /\A\d{8}T\d{6}\z/ !~ d } # year 10000 problem
  dirs.sort!
  dirs
end

#catch_error(name = nil) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/chkbuild/build.rb', line 257

def catch_error(name=nil)
  err = nil
  begin
    yield
  rescue Exception => err
  end
  return true unless err
  @errors << err
  @logfile.start_section("#{name} error") if name
  show_backtrace err unless CommandError === err
  GDB.check_core(@build_dir)
  if CommandError === err
    puts "failed(#{err.reason})"
  else
    if err.respond_to? :reason
      puts "failed(#{err.reason} #{err.class})"
    else
      puts "failed(#{err.class})"
    end
  end
  return false
end

#child_build_target(*branch_info) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/chkbuild/build.rb', line 197

def child_build_target(*branch_info)
  opts = @target.opts
  @build_dir = @target_dir + @start_time
  @log_filename = @build_dir + 'log'
  mkcd @target_dir
  raise "already exist: #{@start_time}" if File.exist? @start_time
  Dir.mkdir @start_time # fail if it is already exists.
  Dir.chdir @start_time
  @logfile = ChkBuild::LogFile.write_open(@log_filename, self)
  @logfile.change_default_output
  @public.mkpath
  @public_log.mkpath
  force_link "log", @current_txt
  make_local_tmpdir
  remove_old_build(@start_time, opts.fetch(:old, ChkBuild.num_oldbuilds))
  @logfile.start_section 'start'
  ret = nil
  with_procmemsize(opts) {
    ret = catch_error { @target.build_proc.call(self, *branch_info) }
    output_status_section
    @logfile.start_section 'end'
  }
  force_link @current_txt, @public+'last.txt' if @current_txt.file?
  titlegen = ChkBuild::Title.new(@target, @logfile)
  title_succ = catch_error('run_hooks') { titlegen.run_hooks }
  title = titlegen.make_title
  title << " (titlegen.run_hooks error)" if !title_succ
  Marshal.dump(titlegen.version, @parent_pipe)
  @parent_pipe.close
  @compressed_log_basename = "#{@start_time}.log.txt.gz"
  @compressed_diff_basename = "#{@start_time}.diff.txt.gz"
  compress_file(@log_filename, @public_log+@compressed_log_basename)
  different_sections = make_diff
  update_summary(title, different_sections)
  update_recent
  make_html_log(@log_filename, title, different_sections, @public+"last.html")
  compress_file(@public+"last.html", @public+"last.html.gz")
  ChkBuild.run_upload_hooks(self.suffixed_name)
  ret
end

#child_build_wrapper(parent_pipe, *branch_info) ⇒ Object



182
183
184
185
186
187
188
189
# File 'lib/chkbuild/build.rb', line 182

def child_build_wrapper(parent_pipe, *branch_info)
  ret = ChkBuild.lock_puts(self.depsuffixed_name) {
    @parent_pipe = parent_pipe
    @errors = []
    child_build_target(*branch_info)
  }
  ret
end

#compress_file(src, dst) ⇒ Object



431
432
433
434
435
436
437
# File 'lib/chkbuild/build.rb', line 431

def compress_file(src, dst)
  Zlib::GzipWriter.wrap(open(dst, "w")) {|z|
    open(src) {|f|
      FileUtils.copy_stream(f, z)
    }
  }
end

#cvs(cvsroot, mod, branch, opts = {}) ⇒ Object



28
29
30
31
32
# File 'lib/chkbuild/scm/cvs.rb', line 28

def cvs(cvsroot, mod, branch, opts={})
  network_access {
    cvs_internal(cvsroot, mod, branch, opts)
  }
end

#cvs_internal(cvsroot, mod, branch, opts = {}) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/chkbuild/scm/cvs.rb', line 34

def cvs_internal(cvsroot, mod, branch, opts={})
  opts = opts.dup
  opts[:section] ||= 'cvs'
  working_dir = opts.fetch(:working_dir, mod)
  if !File.exist? "#{ENV['HOME']}/.cvspass"
    opts['ENV:CVS_PASSFILE'] = '/dev/null' # avoid warning
  end
  if File.directory?(working_dir)
    Dir.chdir(working_dir) {
      h1 = cvs_revisions
      self.run("cvs", "-f", "-z3", "-Q", "update", "-kb", "-dP", opts)
      h2 = cvs_revisions
      cvs_print_revisions(h1, h2, opts[:viewvc]||opts[:viewcvs]||opts[:cvsweb])
    }
  else
    h1 = nil
    if File.identical?(@build_dir, '.') &&
       !(ts = build_time_sequence - [@start_time]).empty? &&
       File.directory?(old_working_dir = "#{@target_dir}/#{ts.last}/#{working_dir}")
      Dir.chdir(old_working_dir) {
        h1 = cvs_revisions
      }
    end
    if branch
      self.run("cvs", "-f", "-z3", "-Qd", cvsroot, "co", "-kb", "-d", working_dir, "-Pr", branch, mod, opts)
    else
      self.run("cvs", "-f", "-z3", "-Qd", cvsroot, "co", "-kb", "-d", working_dir, "-P", mod, opts)
    end
    Dir.chdir(working_dir) {
      h2 = cvs_revisions
      cvs_print_revisions(h1, h2, opts[:viewvc]||opts[:viewcvs]||opts[:cvsweb])
    }
  end
end

#cvs_print_changes(h1, h2, viewcvs = nil) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/chkbuild/scm/cvs.rb', line 100

def cvs_print_changes(h1, h2, viewcvs=nil)
  (h1.keys | h2.keys).sort.each {|k|
    f = k.flatten.join('/')
    cvsroot1, repository1, r1 = h1[k] || [nil, nil, 'none']
    cvsroot2, repository2, r2 = h2[k] || [nil, nil, 'none']
    if r1 != r2
      if r1 == 'none'
        line = "ADD"
      elsif r2 == 'none'
        line = "DEL"
      else
        line = "CHG"
      end
      line << " #{f}\t#{r1}->#{r2}"
      if viewcvs
        line << "\t" << cvs_uri(viewcvs, repository1 || repository2, k[1], r1, r2)
      end
      puts line
    end
  }
end

#cvs_print_revisions(h1, h2, viewcvs = nil) ⇒ Object



122
123
124
125
126
127
128
129
130
131
# File 'lib/chkbuild/scm/cvs.rb', line 122

def cvs_print_revisions(h1, h2, viewcvs=nil)
  cvs_print_changes(h1, h2, viewcvs) if h1
  puts 'revisions:'
  h2.keys.sort.each {|k|
    f = k.flatten.join('/')
    cvsroot2, repository2, r2 = h2[k] || [nil, nil, 'none']
    digest = sha256_digest_file(f)
    puts "#{f}\t#{r2}\t#{digest}"
  }
end

#cvs_revisionsObject



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/chkbuild/scm/cvs.rb', line 69

def cvs_revisions
  h = {}
  Dir.glob("**/CVS").each {|cvs_dir|
    cvsroot = IO.read("#{cvs_dir}/Root").chomp
    repository = IO.read("#{cvs_dir}/Repository").chomp
    ds = cvs_dir.split(%r{/})[0...-1]
    IO.foreach("#{cvs_dir}/Entries") {|line|
      h[[ds, $1]] = [cvsroot, repository, $2] if %r{^/([^/]+)/([^/]*)/} =~ line
    }
  }
  h
end

#cvs_uri(viewcvs, repository, filename, r1, r2) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/chkbuild/scm/cvs.rb', line 82

def cvs_uri(viewcvs, repository, filename, r1, r2)
  uri = URI.parse(viewcvs)
  path = uri.path.dup
  path << "/" << Escape.uri_path(repository).to_s if repository != '.'
  path << "/" << Escape.uri_path(filename).to_s
  uri.path = path
  query = (uri.query || '').split(/[;&]/)
  if r1 == 'none'
    query << "rev=#{r2}"
  elsif r2 == 'none'
    query << "rev=#{r1}"
  else
    query << "r1=#{r1}" << "r2=#{r2}"
  end
  uri.query = query.join(';')
  uri.to_s
end

#depsuffixed_nameObject



72
73
74
75
76
77
78
# File 'lib/chkbuild/build.rb', line 72

def depsuffixed_name
  name = self.suffixed_name
  @depbuilds.each {|depbuild|
    name << '_' << depbuild.suffixed_name
  }
  name
end

#different_sections(tmp1, tmp2) ⇒ Object



519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/chkbuild/build.rb', line 519

def different_sections(tmp1, tmp2)
  logfile1 = ChkBuild::LogFile.read_open(tmp1.path)
  logfile2 = ChkBuild::LogFile.read_open(tmp2.path)
  secnames1 = logfile1.secnames
  secnames2 = logfile2.secnames
  different_sections = secnames1 - secnames2
  secnames2.each {|secname|
    if !secnames1.include?(secname)
      different_sections << secname
    elsif logfile1.section_size(secname) != logfile2.section_size(secname)
      different_sections << secname
    elsif logfile1.get_section(secname) != logfile2.get_section(secname)
      different_sections << secname
    end
  }
  different_sections
end

#dirObject



172
173
174
175
# File 'lib/chkbuild/build.rb', line 172

def dir
  return @built_dir if defined? @built_dir
  raise "#{self.suffixed_name}: no dir yet"
end

#git(cloneurl, working_dir, opts = {}) ⇒ Object



57
58
59
60
61
# File 'lib/chkbuild/scm/git.rb', line 57

def git(cloneurl, working_dir, opts={})
  network_access {
    git_internal(cloneurl, working_dir, opts)
  }
end

#git_head_commitObject



146
147
148
149
150
151
152
# File 'lib/chkbuild/scm/git.rb', line 146

def git_head_commit
  IO.popen("git rev-list --max-count=1 HEAD") {|f|
    # <sha1><LF>
    # 4db0223676a371da8c4247d9a853529ef50a3b01
    f.read.chomp
  }
end

#git_internal(cloneurl, working_dir, opts = {}) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/chkbuild/scm/git.rb', line 63

def git_internal(cloneurl, working_dir, opts={})
  urigen = nil
  opts = opts.dup
  opts[:section] ||= 'git'
  if opts[:github]
    urigen = GitHub.new(*opts[:github])
  end
  if shared_dir = opts[:shared_gitdir]
    opts_shared = opts.dup
    opts_shared[:section] += "(shared)"
    Dir.chdir(shared_dir) {
      if File.directory?(working_dir) && File.exist?("#{working_dir}/.git")
 Dir.chdir(working_dir) {
   git_logfile(opts_shared) {|opts2|
     self.run("git", "pull", opts2)
   }
 }
	else
 FileUtils.rm_rf(working_dir) if File.exist?(working_dir)
 pdir = File.dirname(working_dir)
 FileUtils.mkdir_p(pdir) if !File.directory?(pdir)
 git_logfile(opts_shared) {|opts2|
   self.run "git", "clone", "-q", cloneurl, working_dir, opts2
 }
	end
	cloneurl = "#{shared_dir}/#{working_dir}"
    }
  end
  if File.exist?(working_dir) && File.exist?("#{working_dir}/.git")
    Dir.chdir(working_dir) {
      old_head = git_head_commit
      git_logfile(opts) {|opts2|
        self.run "git", "pull", opts2
      }
      logs = git_oneline_logs(old_head)
      git_print_logs(old_head, logs, urigen)
    }
  else
    FileUtils.rm_rf(working_dir) if File.exist?(working_dir)
    pdir = File.dirname(working_dir)
    FileUtils.mkdir_p(pdir) if !File.directory?(pdir)
    old_head = nil
    if File.identical?(self.build_dir, '.') &&
       !(ts = self.build_time_sequence - [self.start_time]).empty? &&
       File.directory?(old_working_dir = self.target_dir + ts.last + working_dir)
      Dir.chdir(old_working_dir) {
        old_head = git_head_commit
      }
    end
    git_logfile(opts) {|opts2|
      self.run "git", "clone", "-q", cloneurl, working_dir, opts2
    }
    Dir.chdir(working_dir) {
      logs = git_oneline_logs(old_head)
      git_print_logs(old_head, logs, urigen)
    }
  end
end

#git_logfile(opts) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/chkbuild/scm/git.rb', line 41

def git_logfile(opts)
  git_with_file("git.log.") {|errfile|
    opts2 = opts.dup
    opts2[:stderr] = errfile
    begin
      yield opts2
    ensure
      if File.exist?(errfile)
        errcontent = File.read(errfile)
        errcontent.gsub!(/^.*[\r\e].*\n/, "")
        puts errcontent if !errcontent.empty?
      end
    end
  }
end

#git_oneline_logs(old_head = nil) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/chkbuild/scm/git.rb', line 128

def git_oneline_logs(old_head=nil)
  result = []
  if old_head
    command = "git log --pretty=oneline #{old_head}..HEAD"
  else
    command = "git log --pretty=oneline --max-count=1"
  end
  IO.popen(command) {|f|
    f.each_line {|line|
      # <sha1><sp><title line>
      if /\A([0-9a-fA-F]+)\s+(.*)/ =~ line
        result << [$1, $2]
      end
    }
  }
  result
end

#git_parse_status(f) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/chkbuild/scm/git.rb', line 167

def git_parse_status(f)
  h = {}
  f.each_line("\0") {|line|
    # <mode> SP <type> SP <object> TAB <file>\0
    # 100644 blob 9518934185ea26856cf1bcdf75f7cc51fcd82534    core/array/allocate_spec.rb
    if /\A\d+ [^ ]+ ([0-9a-fA-F]+)\t([^\0]+)\0\z/ =~ line
      rev = $1
      path = $2
      h[path] = rev
    end
  }
  h
end

#git_path_sort(ary) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/chkbuild/scm/git.rb', line 181

def git_path_sort(ary)
  ary.sort_by {|path|
    path.gsub(%r{([^/]+)(/|\z)}) {
      if $2 == ""
        if $1 == '.'
          "A"
        else
          "B#{$1}"
        end
      else
        "C#{$1}\0"
      end
    }
  }
end

#git_print_logs(old_head, logs, urigen = nil) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/chkbuild/scm/git.rb', line 209

def git_print_logs(old_head, logs, urigen=nil)
  if !old_head
    puts "last commit:"
  end
  logs.each {|commit_hash, title_line|
    if urigen
      commit = urigen.commit_uri(commit_hash)
    else
      commit = commit_hash
    end
    line = "COMMIT #{title_line}\t#{commit}"
    puts line
  }
end

#git_revisionsObject



154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/chkbuild/scm/git.rb', line 154

def git_revisions
  h = IO.popen("git ls-tree -z -r HEAD") {|f|
    git_parse_status(f)
  }
  IO.popen("git rev-list --max-count=1 HEAD") {|f|
    # <sha1><LF>
    # 4db0223676a371da8c4247d9a853529ef50a3b01
    commit_hash = f.read.chomp
    h[nil] = commit_hash
  }
  h
end

#git_with_file(basename) {|name| ... } ⇒ Object

Yields:

  • (name)


33
34
35
36
37
38
39
# File 'lib/chkbuild/scm/git.rb', line 33

def git_with_file(basename)
  n = 1
  until !File.exist?(name = "#{self.build_dir}/#{basename}#{n}")
    n += 1
  end
  yield name
end

#github(user, project, working_dir, opts = {}) ⇒ Object



122
123
124
125
126
# File 'lib/chkbuild/scm/git.rb', line 122

def github(user, project, working_dir, opts={})
  opts = opts.dup
  opts[:github] = [user, project]
  git("git://github.com/#{user}/#{project}.git", working_dir, opts)
end

#gnu_savannah_cvs(proj, mod, branch, opts = {}) ⇒ Object



28
29
30
31
32
# File 'lib/chkbuild/scm/xforge.rb', line 28

def gnu_savannah_cvs(proj, mod, branch, opts={})
  opts = opts.dup
  opts[:viewcvs] ||= "http://savannah.gnu.org/cgi-bin/viewcvs/#{proj}?diff_format=u"
  self.cvs(":pserver:[email protected]:/sources/#{proj}", mod, branch, opts)
end

#log_time_sequenceObject



94
95
96
97
98
99
100
101
102
103
# File 'lib/chkbuild/build.rb', line 94

def log_time_sequence
  return [] if !@public_log.directory?
  names = @public_log.entries.map {|e| e.to_s }
  result = []
  names.each {|n|
    result << $1 if /\A(\d{8}T\d{6})(?:\.log)?\.txt\.gz\z/ =~ n
  }
  result.sort!
  result
end

#make(*args) ⇒ Object



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# File 'lib/chkbuild/build.rb', line 699

def make(*args)
  opts = {}
  opts = args.pop if Hash === args.last
  opts = opts.dup
  opts[:alt_commands] = ['make']

  make_opts, targets = args.partition {|a| /=/ =~ a }
  if targets.empty?
    opts[:section] ||= 'make'
    self.run("gmake", *(make_opts + [opts]))
  else
    targets.each {|target|
      h = opts.dup
      h[:reason] = target
      h[:section] ||= target
      self.run("gmake", target, *(make_opts + [h]))
    }
  end
end

#make_diffObject



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/chkbuild/build.rb', line 444

def make_diff
  time2 = @start_time
  entries = Dir.entries(@public_log)
  time_seq = []
  entries.each {|f|
    if /\A(\d{8}T\d{6})(?:\.log)?\.txt\.gz\z/ =~ f # year 10000 problem
      time_seq << $1
    end
  }
  time_seq.sort!
  time_seq.delete time2
  while !time_seq.empty? &&
        open_gziped_log(time_seq.last) {|f|
          neterror = false
          f.each_line {|line|
            line.force_encoding("ascii-8bit") if line.respond_to? :force_encoding
            if /\A== neterror / =~ line
              neterror = true
              break
            end
          }
          if neterror
            true
          else
            false
          end
        }
    time_seq.pop
  end
  return nil if time_seq.empty?
  time1 = time_seq.last
  different_sections = nil
  output_path = @public_log+@compressed_diff_basename
  Zlib::GzipWriter.wrap(open(output_path, "w")) {|z|
    different_sections = output_diff(time1, time2, z)
  }
  if !different_sections
    output_path.unlink
    return nil
  end
  return different_sections
end

#make_diff_content(time) ⇒ Object



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/chkbuild/build.rb', line 537

def make_diff_content(time)
  times = [time]
  uncompressed = Tempfile.open("#{time}.u.")
  open_gziped_log(time) {|z|
    FileUtils.copy_stream(z, uncompressed)
  }
  uncompressed.flush
  logfile = ChkBuild::LogFile.read_open(uncompressed.path)
  logfile.dependencies.each {|dep_suffixed_name, dep_time, dep_version|
    times << dep_time
  }
  pat = Regexp.union(*times.uniq)
  tmp = Tempfile.open("#{time}.d.")
  open_gziped_log(time) {|z|
    z.each_line {|line|
      line = line.gsub(pat, '<buildtime>')
      @target.each_diff_preprocess_hook {|block|
        catch_error(block.to_s) { line = block.call(line) }
      }
      tmp << line
    }
  }
  tmp.flush
  tmp
end

#make_html_log(log_filename, title, has_diff, dst) ⇒ Object



422
423
424
425
426
427
428
429
# File 'lib/chkbuild/build.rb', line 422

def make_html_log(log_filename, title, has_diff, dst)
  log = File.read(log_filename)
  log.force_encoding("ascii-8bit") if log.respond_to? :force_encoding
  permalink = "log/#{@compressed_log_basename}"
  diff_permalink = "log/#{@compressed_diff_basename}"
  content = ERB.new(LAST_HTMLTemplate, nil, '%').result(binding)
  atomic_make_file(dst, content)
end

#make_local_tmpdirObject



191
192
193
194
195
# File 'lib/chkbuild/build.rb', line 191

def make_local_tmpdir
  tmpdir = @build_dir + 'tmp'
  tmpdir.mkpath
  ENV['TMPDIR'] = tmpdir.to_s
end

#markup(str) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
# File 'lib/chkbuild/build.rb', line 377

def markup(str)
  result = ''
  i = 0
  str.scan(/#{URI.regexp(['http'])}/o) {
    result << h(str[i...$~.begin(0)]) if i < $~.begin(0)
    result << "<a href=\"#{h $&}\">#{h $&}</a>"
    i = $~.end(0)
  }
  result << h(str[i...str.length]) if i < str.length
  result
end

#network_access(name = nil) ⇒ Object



280
281
282
283
284
285
286
287
288
# File 'lib/chkbuild/build.rb', line 280

def network_access(name=nil)
  begin
    yield
  ensure
    if err = $!
      @logfile.start_section("neterror")
    end
  end
end

#open_gziped_log(time, &block) ⇒ Object



598
599
600
601
602
603
604
605
# File 'lib/chkbuild/build.rb', line 598

def open_gziped_log(time, &block)
  if File.file?(@public_log+"#{time}.log.txt.gz")
    filename = @public_log+"#{time}.log.txt.gz"
  else
    filename = @public_log+"#{time}.txt.gz"
  end
  Zlib::GzipReader.wrap(open(filename), &block)
end

#output_change_lines(t2, out) ⇒ Object



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/chkbuild/build.rb', line 501

def output_change_lines(t2, out)
  has_diff = false
  open_gziped_log(t2) {|f|
    has_change_line = false
    f.each {|line|
      if ChkBuild::Target::CHANGE_LINE_PAT =~ line
        out.puts line
        has_change_line = true
      end
    }
    if has_change_line
      out.puts
      has_diff = true
    end
  }
  has_diff
end

#output_diff(t1, t2, out) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/chkbuild/build.rb', line 487

def output_diff(t1, t2, out)
  has_change_line = output_change_lines(t2, out)
  tmp1 = make_diff_content(t1)
  tmp2 = make_diff_content(t2)
  tmp1, tmp2 = sort_diff_content(t1, tmp1, t2, tmp2)
  header = "--- #{t1}\n+++ #{t2}\n"
  has_diff = has_change_line | UDiff.diff(tmp1.path, tmp2.path, out, header)
  return nil if !has_diff
  ret = []
  ret << 'src' if has_change_line
  ret.concat different_sections(tmp1, tmp2)
  ret 
end

#output_status_sectionObject



253
254
255
# File 'lib/chkbuild/build.rb', line 253

def output_status_section
  @logfile.start_section 'success' if @errors.empty?
end

#remove_old_build(current, num) ⇒ Object



292
293
294
295
296
297
298
299
300
# File 'lib/chkbuild/build.rb', line 292

def remove_old_build(current, num)
  dirs = build_time_sequence
  dirs.delete current
  return if dirs.length <= num
  dirs[-num..-1] = []
  dirs.each {|d|
    (@target_dir+d).rmtree
  }
end

#run(command, *args, &block) ⇒ Object



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
# File 'lib/chkbuild/build.rb', line 616

def run(command, *args, &block)
  opts = @target.opts.dup
  opts.update args.pop if Hash === args.last

  if opts.include?(:section)
    secname = opts[:section]
  else
    secname = opts[:reason] || File.basename(command)
  end
  @logfile.start_section(secname) if secname

  if !opts.include?(:output_interval_timeout)
    opts[:output_interval_timeout] = '10min'
  end

  puts "+ #{Escape.shell_command [command, *args]}"
  pos = STDOUT.pos
  begin
    command_status = TimeoutCommand.timeout_command(opts.fetch(:timeout, '1h'), STDERR, opts) {
      run_in_child(opts, command, *args)
    }
  ensure
    exc = $!
    if exc && secname
      class << exc
        attr_accessor :reason
      end
      exc.reason = secname
    end
  end
  begin
    if command_status.exitstatus != 0
      if command_status.exited?
        puts "exit #{command_status.exitstatus}"
      elsif command_status.signaled?
        puts "signal #{SignalNum2Name[command_status.termsig]} (#{command_status.termsig})"
      elsif command_status.stopped?
        puts "stop #{SignalNum2Name[command_status.stopsig]} (#{command_status.stopsig})"
      else
        p command_status
      end
      raise CommandError.new(command_status, opts.fetch(:section, command))
    end
  end
end

#run_in_child(opts, command, *args) ⇒ Object



662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/chkbuild/build.rb', line 662

def run_in_child(opts, command, *args)
  opts.each {|k, v|
    next if /\AENV:/ !~ k.to_s
    ENV[$'] = v
  }
  if Process.respond_to? :setrlimit
    resource_unlimit(:RLIMIT_CORE)
    limit = ChkBuild.get_limit
    opts.each {|k, v| limit[$'.intern] = v if /\Ar?limit_/ =~ k.to_s }
    resource_limit(:RLIMIT_CPU, limit.fetch(:cpu))
    resource_limit(:RLIMIT_STACK, limit.fetch(:stack))
    resource_limit(:RLIMIT_DATA, limit.fetch(:data))
    resource_limit(:RLIMIT_AS, limit.fetch(:as))
    #system('sh', '-c', "ulimit -a")
  end
  alt_commands = opts.fetch(:alt_commands, [])
  if opts.include?(:stdout)
    STDOUT.reopen(opts[:stdout], "w")
  end
  if opts.include?(:stderr)
    STDERR.reopen(opts[:stderr], "w")
  end
  begin
    exec [command, command], *args
  rescue Errno::ENOENT
    if !alt_commands.empty?
      command = alt_commands.shift
      retry
    else
      raise
    end
  end
end

#show_backtrace(err = $!) ⇒ Object



439
440
441
442
# File 'lib/chkbuild/build.rb', line 439

def show_backtrace(err=$!)
  puts "|#{err.message} (#{err.class})"
  err.backtrace.each {|pos| puts "| #{pos}" }
end

#sort_diff_content(time1, tmp1, time2, tmp2) ⇒ Object



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
# File 'lib/chkbuild/build.rb', line 563

def sort_diff_content(time1, tmp1, time2, tmp2)
  pat = @target.diff_preprocess_sort_pattern
  return tmp1, tmp2 if !pat

  h1, h2 = [tmp1, tmp2].map {|tmp|
    h = {}
    tmp.rewind
    tmp.gather_each(pat) {|lines|
      next unless 1 < lines.length && pat =~ lines.first
      h[$&] = Digest::SHA256.hexdigest(lines.sort.join(''))
    }
    h
  }

  h0 = {}
  h1.each_key {|k| h0[k] = true if h1[k] == h2[k]  }

  newtmp1, newtmp2 = [[time1, tmp1], [time2, tmp2]].map {|time, tmp|
    newtmp = Tempfile.open("#{time}.d.")
    tmp.rewind
    tmp.gather_each(pat) {|lines|
      if 1 < lines.length && pat =~ lines.first && h0[$&]
        newtmp.print lines.sort.join('')
      else
        newtmp.print lines.join('')
      end
    }
    tmp.close(true)
    newtmp.rewind
    newtmp
  }

  return newtmp1, newtmp2
end

#start_timeObject



150
151
152
153
# File 'lib/chkbuild/build.rb', line 150

def start_time
  return @start_time if defined? @start_time
  raise "#{self.suffixed_name}: no start_time yet"
end

#statusObject



167
168
169
170
# File 'lib/chkbuild/build.rb', line 167

def status
  return @built_status if defined? @built_status
  raise "#{self.suffixed_name}: no status yet"
end

#success?Boolean

Returns:

  • (Boolean)


155
156
157
158
159
160
161
162
163
164
165
# File 'lib/chkbuild/build.rb', line 155

def success?
  if defined? @built_status
    if @built_status.to_i == 0
      true
    else
      false
    end
  else
    nil
  end
end

#suffixed_nameObject



64
65
66
67
68
69
70
# File 'lib/chkbuild/build.rb', line 64

def suffixed_name
  name = @target.target_name.dup
  @suffixes.each {|suffix|
    name << '-' << suffix
  }
  name
end

#svn(svnroot, rep_dir, working_dir, opts = {}) ⇒ Object



71
72
73
74
75
# File 'lib/chkbuild/scm/svn.rb', line 71

def svn(svnroot, rep_dir, working_dir, opts={})
  network_access {
    svn_internal(svnroot, rep_dir, working_dir, opts)
  }
end

#svn_diff_uri(viewvc, d, f, r1, r2) ⇒ Object



166
167
168
169
# File 'lib/chkbuild/scm/svn.rb', line 166

def svn_diff_uri(viewvc, d, f, r1, r2)
  return nil if !viewvc
  viewvc.diff_uri(d, f, r1, r2)
end

#svn_dir_uri(viewvc, d, f, r) ⇒ Object



161
162
163
164
# File 'lib/chkbuild/scm/svn.rb', line 161

def svn_dir_uri(viewvc, d, f, r)
  return nil if !viewvc
  viewvc.dir_uri(d, f, r)
end

#svn_internal(svnroot, rep_dir, working_dir, opts = {}) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/chkbuild/scm/svn.rb', line 77

def svn_internal(svnroot, rep_dir, working_dir, opts={})
  url = svnroot + '/' + rep_dir
  opts = opts.dup
  opts[:section] ||= 'svn'
  if opts[:viewvc]||opts[:viewcvs]
    viewvc = ChkBuild::ViewVC.new(opts[:viewvc]||opts[:viewcvs], opts[:viewcvs]!=nil)
  else
    viewvc = nil
  end
  if File.exist?(working_dir) && File.exist?("#{working_dir}/.svn")
    Dir.chdir(working_dir) {
      self.run "svn", "cleanup", opts
      opts[:section] = nil
      h1 = svn_revisions
      self.run "svn", "update", "-q", opts
      h2 = svn_revisions
      svn_print_changes(h1, h2, viewvc, rep_dir)
    }
  else
    if File.exist?(working_dir)
      FileUtils.rm_rf(working_dir)
    end
    h1 = h2 = nil
    if File.identical?(self.build_dir, '.') &&
       !(ts = self.build_time_sequence - [self.start_time]).empty? &&
       File.directory?(old_working_dir = self.target_dir + ts.last + working_dir)
      Dir.chdir(old_working_dir) {
        h1 = svn_revisions
      }
    end
    self.run "svn", "checkout", "-q", url, working_dir, opts
    Dir.chdir(working_dir) {
      h2 = svn_revisions
      svn_print_changes(h1, h2, viewvc, rep_dir) if h1
    }
  end
end

#svn_markup_uri(viewvc, d, f, r) ⇒ Object



156
157
158
159
# File 'lib/chkbuild/scm/svn.rb', line 156

def svn_markup_uri(viewvc, d, f, r)
  return nil if !viewvc
  viewvc.markup_uri(d, f, r)
end

#svn_parse_status(f) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/chkbuild/scm/svn.rb', line 121

def svn_parse_status(f)
  h = {}
  f.each {|line|
    if /\d+\s+(\d+)\s+\S+\s+(.+)/ =~ line
      rev = $1.to_i
      path = $2
      dir = File.directory?(path)
      path << '/' if dir && path != '.'
      h[path] = [rev, dir]
    end
  }
  h
end

#svn_path_sort(ary) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/chkbuild/scm/svn.rb', line 135

def svn_path_sort(ary)
  ary.sort_by {|path|
    path.gsub(%r{([^/]+)(/|\z)}) {
      if $2 == ""
        if $1 == '.'
          "A"
        else
          "B#{$1}"
        end
      else
        "C#{$1}\0"
      end
    }
  }
end

#svn_print_add_line(f, r, uri) ⇒ Object



209
210
211
212
213
# File 'lib/chkbuild/scm/svn.rb', line 209

def svn_print_add_line(f, r, uri)
  line = "ADD #{f}\tnone->#{r}"
  line << "\t" << uri.to_s if uri
  puts line
end

#svn_print_changes(h1, h2, viewvc = nil, rep_dir = nil) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/chkbuild/scm/svn.rb', line 171

def svn_print_changes(h1, h2, viewvc=nil, rep_dir=nil)
  top_r1, _ = h1['.']
  top_r2, _ = h2['.']
  h1.delete '.'
  h2.delete '.'
  return if top_r1 == top_r2
  svn_print_chg_line('.', top_r1, top_r2, svn_rev_uri(viewvc, top_r2))
  svn_path_sort(h1.keys|h2.keys).each {|f|
    r1, d1 = h1[f] || ['none', nil]
    r2, d2 = h2[f] || ['none', nil]
    next if r1 == r2 # no changes
    next if d1 && d2 # skip directory changes
    if !d1 && !d2 && r1 != 'none' && r2 != 'none'
      svn_print_chg_line(f, r1, r2,
        svn_diff_uri(viewvc, rep_dir, f, top_r1, top_r2))
    else
      svn_print_del_line(f, r1,
        d1 ? svn_dir_uri(viewvc, rep_dir, f, top_r1) :
             svn_markup_uri(viewvc, rep_dir, f, top_r1)) if r1 != 'none'
      svn_print_add_line(f, r2,
        d2 ? svn_dir_uri(viewvc, rep_dir, f, top_r2) :
             svn_markup_uri(viewvc, rep_dir, f, top_r2)) if r2 != 'none'
    end
  }
end

#svn_print_chg_line(f, r1, r2, uri) ⇒ Object



197
198
199
200
201
# File 'lib/chkbuild/scm/svn.rb', line 197

def svn_print_chg_line(f, r1, r2, uri)
  line = "CHG #{f}\t#{r1}->#{r2}"
  line << "\t" << uri.to_s if uri
  puts line
end

#svn_print_del_line(f, r, uri) ⇒ Object



203
204
205
206
207
# File 'lib/chkbuild/scm/svn.rb', line 203

def svn_print_del_line(f, r, uri)
  line = "DEL #{f}\t#{r}->none"
  line << "\t" << uri.to_s if uri
  puts line
end

#svn_rev_uri(viewvc, r) ⇒ Object



151
152
153
154
# File 'lib/chkbuild/scm/svn.rb', line 151

def svn_rev_uri(viewvc, r)
  return nil if !viewvc
  viewvc.rev_uri(r)
end

#svn_revisionsObject



115
116
117
118
119
# File 'lib/chkbuild/scm/svn.rb', line 115

def svn_revisions
  IO.popen("svn status -v") {|f|
    svn_parse_status(f)
  }
end

#traverse_depbuild(&block) ⇒ Object



80
81
82
83
84
85
# File 'lib/chkbuild/build.rb', line 80

def traverse_depbuild(&block)
  @depbuilds.each {|depbuild|
    yield depbuild
    depbuild.traverse_depbuild(&block)
  }
end

#update_recentObject



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/chkbuild/build.rb', line 355

def update_recent
  start_time = @start_time
  summary_path = @public+"summary.html"
  lines = []
  summary_path.open {|f|
    while l = f.gets
      lines << l
      lines.shift if 10 < lines.length
    end
  }
  while !lines.empty? && /\A<a / !~ lines[0]
    lines.shift
  end
  title = "#{self.depsuffixed_name} recent build summary"

  recent_summary = lines.reverse.join
  content = ERB.new(RECENT_HTMLTemplate).result(binding)

  recent_path = @public+"recent.html"
  atomic_make_file(recent_path, content)
end

#update_summary(title, different_sections) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/chkbuild/build.rb', line 302

def update_summary(title, different_sections)
  start_time = @start_time
  if different_sections
    if different_sections.empty?
      diff_txt = "diff"
    else
      diff_txt = "diff:#{different_sections.join(',')}"
    end
  end
  open(@public+"summary.txt", "a") {|f|
    f.print "#{start_time} #{title}"
    f.print " (#{diff_txt})" if diff_txt
    f.puts
  }
  open(@public+"summary.html", "a") {|f|
    if f.stat.size == 0
      f.puts "<title>#{h self.depsuffixed_name} build summary</title>"
      f.puts "<h1>#{h self.depsuffixed_name} build summary</h1>"
      f.puts "<p><a href=\"../\">chkbuild</a></p>"
    end
    f.print "<a href=\"log/#{h @compressed_log_basename}\" name=\"#{start_time}\">#{h start_time}</a> #{h title}"
    f.print " (<a href=\"log/#{h @compressed_diff_basename}\">#{h diff_txt}</a>)" if diff_txt
    f.puts "<br>"
  }
end

#versionObject



177
178
179
180
# File 'lib/chkbuild/build.rb', line 177

def version
  return @built_version if defined? @built_version
  raise "#{self.suffixed_name}: no version yet"
end

#with_procmemsize(opts) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/chkbuild/build.rb', line 240

def with_procmemsize(opts)
  if opts[:procmemsize]
    current_pid = $$
    procmemsize_pid = fork { exec *%W[procmemsize -p #{current_pid}] }
    ret = yield
    Process.kill :TERM, procmemsize_pid
    Process.wait procmemsize_pid
  else
    ret = yield
  end
  ret
end