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,57 @@
1
+ # Copyright (C) 2006,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
+ module ChkBuild
26
+ LOCK_PATH = ChkBuild.build_top + '.lock'
27
+
28
+ def self.lock_start
29
+ if !defined?(@lock_io)
30
+ @lock_io = LOCK_PATH.open(File::WRONLY|File::CREAT)
31
+ end
32
+ if @lock_io.flock(File::LOCK_EX|File::LOCK_NB) == false
33
+ raise "another chkbuild is running."
34
+ end
35
+ @lock_io.truncate(0)
36
+ @lock_io.sync = true
37
+ @lock_io.close_on_exec = true
38
+ @lock_io.puts "locked pid:#{$$}"
39
+ lock_pid = $$
40
+ at_exit {
41
+ @lock_io.puts "exit pid:#{$$}" if $$ == lock_pid
42
+ }
43
+ end
44
+
45
+ def self.lock_puts(mesg)
46
+ if block_given?
47
+ t1 = Time.now
48
+ @lock_io.print "#{t1.iso8601} #{mesg}"
49
+ ret = yield
50
+ t2 = Time.now
51
+ @lock_io.puts "\t#{t2-t1}"
52
+ ret
53
+ else
54
+ @lock_io.puts "#{Time.now.iso8601} #{mesg}"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,230 @@
1
+ # Copyright (C) 2006,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 'time'
26
+
27
+ module ChkBuild
28
+ end
29
+
30
+ class ChkBuild::LogFile
31
+ InitialMark = '=='
32
+
33
+ def self.write_open(filename, build)
34
+ logfile = self.new(filename, true)
35
+ logfile.start_section build.depsuffixed_name
36
+ logfile.with_default_output {
37
+ system("uname -a")
38
+ section_started = false
39
+ build.traverse_depbuild {|depbuild|
40
+ if !section_started
41
+ logfile.start_section 'dependencies'
42
+ section_started = true
43
+ end
44
+ if depbuild.suffixed_name == depbuild.version
45
+ puts "#{depbuild.suffixed_name} #{depbuild.start_time}"
46
+ else
47
+ puts "#{depbuild.suffixed_name} #{depbuild.start_time} (#{depbuild.version})"
48
+ end
49
+ }
50
+ }
51
+ logfile
52
+ end
53
+
54
+ def dependencies
55
+ return [] unless log = self.get_section('dependencies')
56
+ r = []
57
+ log.each_line {|line|
58
+ if /^(\S+) (\d+T\d+) \((.*)\)$/ =~ line
59
+ r << [$1, $2, $3]
60
+ elsif /^(\S+) (\d+T\d+)$/ =~ line
61
+ r << [$1, $2, $1]
62
+ end
63
+ }
64
+ r
65
+ end
66
+
67
+ def depsuffixed_name
68
+ return @depsuffixed_name if defined? @depsuffixed_name
69
+ if /\A\S+\s+(\S+)/ =~ self.get_all_log
70
+ return @depsuffixed_name = $1
71
+ end
72
+ raise "unexpected log format"
73
+ end
74
+
75
+ def suffixed_name() depsuffixed_name.sub(/_.*/, '') end
76
+ def target_name() suffixed_name.sub(/-.*/, '') end
77
+ def suffixes() suffixed_name.split(/-/)[1..-1] end
78
+
79
+ def self.read_open(filename)
80
+ self.new(filename, false)
81
+ end
82
+
83
+ def initialize(filename, writemode)
84
+ @writemode = writemode
85
+ mode = writemode ? File::RDWR|File::CREAT|File::APPEND : File::RDONLY
86
+ @filename = filename
87
+ @io = File.open(filename, mode)
88
+ @io.set_encoding("ascii-8bit") if @io.respond_to? :set_encoding
89
+ @io.sync = true
90
+ @mark = read_separator
91
+ @sections = detect_sections
92
+ end
93
+
94
+ def read_separator
95
+ mark = nil
96
+ if @io.stat.size != 0
97
+ @io.rewind
98
+ mark = @io.gets[/\A\S+/]
99
+ end
100
+ mark || InitialMark
101
+ end
102
+ private :read_separator
103
+
104
+ def detect_sections
105
+ ret = {}
106
+ @io.rewind
107
+ pat = /\A#{Regexp.quote @mark} /
108
+ @io.each {|line|
109
+ if pat =~ line
110
+ epos = @io.pos
111
+ spos = epos - line.length
112
+ secname = $'.chomp.sub(/#.*/, '').strip
113
+ ret[secname] = spos
114
+ end
115
+ }
116
+ ret
117
+ end
118
+ private :detect_sections
119
+
120
+ # logfile.with_default_output { ... }
121
+ def with_default_output
122
+ raise "not opened for writing" if !@writemode
123
+ File.open(@filename, File::WRONLY|File::APPEND) {|f|
124
+ STDERR.tmp_reopen(f) {
125
+ STDERR.sync = true
126
+ STDOUT.tmp_reopen(f) {
127
+ STDOUT.sync = true
128
+ yield
129
+ }
130
+ }
131
+ }
132
+ end
133
+
134
+ def change_default_output
135
+ raise "not opened for writing" if !@writemode
136
+ STDOUT.reopen(@save_io = File.for_fd(@io.fileno, File::WRONLY|File::APPEND))
137
+ STDERR.reopen(STDOUT)
138
+ STDOUT.sync = true
139
+ STDERR.sync = true
140
+ end
141
+
142
+ # start_section returns the (unique) section name.
143
+ def start_section(secname)
144
+ @io.flush
145
+ if 0 < @io.stat.size
146
+ @io.seek(-1, IO::SEEK_END)
147
+ if @io.read != "\n"
148
+ @io.write "\n"
149
+ end
150
+ end
151
+ spos = @io.pos
152
+ secname = secname.strip
153
+ if @sections[secname]
154
+ i = 2
155
+ while @sections["#{secname}(#{i})"]
156
+ i += 1
157
+ end
158
+ secname = "#{secname}(#{i})"
159
+ end
160
+ @sections[secname] = spos
161
+ @io.write "#{@mark} #{secname} \# #{Time.now.iso8601}\n"
162
+ secname
163
+ end
164
+
165
+ def secnames
166
+ @sections.keys.sort_by {|secname| @sections[secname] }
167
+ end
168
+
169
+ def each_secname(&block)
170
+ @sections.keys.sort_by {|secname| @sections[secname] }.each(&block)
171
+ end
172
+
173
+ def section_size(secname)
174
+ spos = @sections[secname]
175
+ raise ArgumentError, "no section : #{secname.inspect}" if !spos
176
+ epos = @sections.values.reject {|pos| pos <= spos }.min
177
+ epos = @io.stat.size if !epos
178
+ epos - spos
179
+ end
180
+
181
+ def get_section(secname)
182
+ spos = @sections[secname]
183
+ return nil if !spos
184
+ @io.seek spos
185
+ @io.gets("\n#{@mark} ").chomp("#{@mark} ").sub(/\A.*\n/, '')
186
+ end
187
+
188
+ def get_all_log
189
+ @io.rewind
190
+ @io.read
191
+ end
192
+
193
+ def modify_section(secname, data)
194
+ raise "not opened for writing" if !@writemode
195
+ spos = @sections[secname]
196
+ raise ArgumentError, "no section: #{secname.inspect}" if !spos
197
+ data += "\n" if /\n\z/ !~ data
198
+ old = nil
199
+ File.open(@filename, File::RDWR) {|f|
200
+ f.seek spos
201
+ rest = f.read
202
+ if /\n#{Regexp.quote @mark} / =~ rest
203
+ epos = $~.begin(0) + 1
204
+ curr = rest[0...epos]
205
+ rest = rest[epos..-1]
206
+ else
207
+ curr = rest
208
+ rest = ''
209
+ end
210
+ if /\n/ =~ curr
211
+ secline = $` + $&
212
+ old = $'
213
+ else
214
+ secline = curr + "\n"
215
+ old = ''
216
+ end
217
+ f.seek spos
218
+ f.print secline, data, rest
219
+ f.flush
220
+ f.truncate(f.pos)
221
+ }
222
+ off = data.length - old.length
223
+ @sections.each_pair {|n, pos|
224
+ if spos < pos
225
+ @sections[n] = pos + off
226
+ end
227
+ }
228
+ nil
229
+ end
230
+ end
@@ -0,0 +1,138 @@
1
+ # Copyright (C) 2006 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 'pathname'
26
+ require 'optparse'
27
+
28
+ module ChkBuild
29
+ TOP_DIRECTORY = Pathname.getwd
30
+ def ChkBuild.build_top() TOP_DIRECTORY+"tmp/build" end
31
+ def ChkBuild.public_top() TOP_DIRECTORY+"tmp/public_html" end
32
+
33
+ def ChkBuild.main_help(status=true)
34
+ if File.executable? $0
35
+ command = $0
36
+ else
37
+ require 'rbconfig'
38
+ ruby = "#{Config::CONFIG["bindir"]}/#{Config::CONFIG["ruby_install_name"]}"
39
+ command = "#{ruby} #{$0}"
40
+ end
41
+ print <<"End"
42
+ usage:
43
+ #{command} [build [--procmemsize]]
44
+ #{command} list
45
+ #{command} title [depsuffixed_name...]
46
+ #{command} logdiff [depsuffixed_name [date1 [date2]]]
47
+ End
48
+ exit status
49
+ end
50
+
51
+ @target_list = []
52
+ def ChkBuild.main_build
53
+ o = OptionParser.new
54
+ o.def_option('--procmemsize') {
55
+ @target_list.each {|t|
56
+ t.update_option(:procmemsize => true)
57
+ }
58
+ }
59
+ o.parse!
60
+ begin
61
+ Process.setpriority(Process::PRIO_PROCESS, 0, 10)
62
+ rescue Errno::EACCES # already niced to 11 or more
63
+ end
64
+ File.umask(002)
65
+ STDIN.reopen("/dev/null", "r")
66
+ STDOUT.sync = true
67
+ ChkBuild.build_top.mkpath
68
+ ChkBuild.lock_start
69
+ @target_list.each {|t|
70
+ t.make_result
71
+ }
72
+ end
73
+
74
+ def ChkBuild.def_target(target_name, *args, &block)
75
+ t = ChkBuild::Target.new(target_name, *args, &block)
76
+ @target_list << t
77
+ t
78
+ end
79
+
80
+ def ChkBuild.main_list
81
+ @target_list.each {|t|
82
+ t.each_build_obj {|build|
83
+ puts build.depsuffixed_name
84
+ }
85
+ }
86
+ end
87
+
88
+ def ChkBuild.main_title
89
+ @target_list.each {|t|
90
+ t.each_build_obj {|build|
91
+ next if !ARGV.empty? && !ARGV.include?(build.depsuffixed_name)
92
+ last_txt = ChkBuild.public_top + build.depsuffixed_name + 'last.txt'
93
+ if last_txt.exist?
94
+ logfile = ChkBuild::LogFile.read_open(last_txt)
95
+ title = ChkBuild::Title.new(t, logfile)
96
+ title.run_hooks
97
+ puts "#{build.depsuffixed_name}:\t#{title.make_title}"
98
+ end
99
+ }
100
+ }
101
+ end
102
+
103
+ def ChkBuild.main_logdiff
104
+ depsuffixed_name, arg_t1, arg_t2 = ARGV
105
+ @target_list.each {|t|
106
+ t.each_build_obj {|build|
107
+ next if depsuffixed_name && build.depsuffixed_name != depsuffixed_name
108
+ ts = build.log_time_sequence
109
+ raise "no log: #{build.depsuffixed_name}/#{arg_t1}" if arg_t1 and !ts.include?(arg_t1)
110
+ raise "no log: #{build.depsuffixed_name}/#{arg_t2}" if arg_t2 and !ts.include?(arg_t2)
111
+ if ts.length < 2
112
+ puts "#{build.depsuffixed_name}: less than 2 logs"
113
+ next
114
+ end
115
+ t1 = arg_t1 || ts[-2]
116
+ t2 = arg_t2 || ts[-1]
117
+ puts "#{build.depsuffixed_name}: #{t1}->#{t2}"
118
+ build.output_diff(t1, t2, STDOUT)
119
+ puts
120
+ }
121
+ }
122
+ end
123
+
124
+ def ChkBuild.main
125
+ ARGV.unshift 'build' if ARGV.empty?
126
+ subcommand = ARGV.shift
127
+ case subcommand
128
+ when 'help', '-h' then ChkBuild.main_help
129
+ when 'build' then ChkBuild.main_build
130
+ when 'list' then ChkBuild.main_list
131
+ when 'title' then ChkBuild.main_title
132
+ when 'logdiff' then ChkBuild.main_logdiff
133
+ else
134
+ puts "unexpected subcommand: #{subcommand}"
135
+ exit 1
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright (C) 2006 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
+ module ChkBuild
26
+ @default_options = {
27
+ :num_oldbuilds => 3,
28
+ :limit_cpu => 3600 * 4,
29
+ :limit_stack => 1024 * 1024 * 40,
30
+ :limit_data => 1024 * 1024 * 100,
31
+ :limit_as => 1024 * 1024 * 100
32
+ }
33
+
34
+ def self.get_options
35
+ @default_options.dup
36
+ end
37
+
38
+ def self.num_oldbuilds
39
+ @default_options[:num_oldbuilds]
40
+ end
41
+ def self.num_oldbuilds=(val)
42
+ @default_options[:num_oldbuilds] = val
43
+ end
44
+
45
+ def self.limit(hash)
46
+ hash.each {|k, v|
47
+ s = "limit_#{k}".intern
48
+ raise "unexpected resource name: #{k}" if !@default_options[s]
49
+ @default_options[s] = v
50
+ }
51
+ end
52
+
53
+ def self.get_limit
54
+ ret = {}
55
+ @default_options.each {|k, v|
56
+ next if /\Alimit_/ !~ k.to_s
57
+ s = $'.intern
58
+ ret[s] = v
59
+ }
60
+ ret
61
+ end
62
+ end