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,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (C) 2006,2009 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ require 'chkbuild'
28
+
29
+ ChkBuild.limit(:data=>1024*1024*800, :as=>1024*1024*800)
30
+
31
+ ChkBuild::Ruby.def_target(
32
+ ["yarv"],
33
+ :separated_srcdir => true)
34
+
35
+ ChkBuild.main
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("apr") {|b,|
6
+ Dir.chdir("..") {
7
+ b.svn("http://svn.apache.org/repos/asf", "apr/apr/trunk", 'apr',
8
+ :viewvc=>"http://svn.apache.org/viewvc")
9
+ }
10
+ }
11
+
12
+ ChkBuild.main
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("catcherr-fails",
6
+ %w[true false],
7
+ %w[true false]) {|b, *commands|
8
+ b.catch_error { b.run(commands[0]) }
9
+ b.catch_error { b.run(commands[1]) }
10
+ }
11
+
12
+ ChkBuild.def_target("catcherr-branch",
13
+ %w[true false],
14
+ %w[true false],
15
+ %w[true false],
16
+ %w[true false]) {|b, *commands|
17
+ r1 = b.catch_error { b.run(commands[0]) }
18
+ r1 && b.catch_error { b.run(commands[1]) }
19
+ r1 && b.catch_error { b.run(commands[2]) }
20
+ r1 && b.catch_error { b.run(commands[3]) }
21
+ }
22
+
23
+ ChkBuild.main
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ COMBFAIL1 = ChkBuild.def_target("combfail1",
6
+ ["succ", "fail"]) {|b, suffix|
7
+ case suffix
8
+ when "succ" then command = "true"
9
+ when "fail" then command = "false"
10
+ end
11
+ b.run(command)
12
+ }
13
+
14
+ ChkBuild.def_target("combfail2", COMBFAIL1) {|b, cf1|
15
+ combfail2_dir = b.build_dir
16
+ /\Acombfail1=/ =~ cf1
17
+ combfail1_dir = $'
18
+ b.run(*%W(echo #{combfail1_dir} #{combfail2_dir}))
19
+ }
20
+
21
+ ChkBuild.main
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("coretest") {|b|
6
+ FileUtils.cp(`which ruby`.chomp, "ruby")
7
+ b.run("./ruby", "-e", <<'End')
8
+ Process.setrlimit(Process::RLIMIT_CORE, Process::RLIM_INFINITY, Process::RLIM_INFINITY)
9
+ trap("SEGV", "DEFAULT")
10
+ Process.kill("SEGV", $$)
11
+ End
12
+ }
13
+
14
+ ChkBuild.main
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ def coredump(b)
6
+ b.run("./ruby", "-e", <<'End')
7
+ Process.setrlimit(Process::RLIMIT_CORE, Process::RLIM_INFINITY, Process::RLIM_INFINITY)
8
+ trap("SEGV", "DEFAULT")
9
+ Process.kill("SEGV", $$)
10
+ End
11
+ end
12
+
13
+ ChkBuild.def_target("core2test") {|b|
14
+ FileUtils.cp(`which ruby`.chomp, "ruby")
15
+ b.catch_error { coredump(b) }
16
+ b.catch_error { coredump(b) }
17
+ }
18
+
19
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("date") {|b|
6
+ b.run("date")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ Dep1 = ChkBuild.def_target("dep1",
6
+ ["dev", "stable"]) {|b, suffix|
7
+ b.catch_error { raise "foo error" }
8
+ }
9
+
10
+ ChkBuild.def_target("dep2",
11
+ ["dev", "stable"],
12
+ Dep1) {
13
+ |b, suffix, dep1|
14
+ p b.suffixed_name
15
+ }
16
+
17
+ ChkBuild.main
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ Dep1 = ChkBuild.def_target("lib1", ["dev", "stable"]) {|b|
6
+ }
7
+
8
+ Dep2 = ChkBuild.def_target("lib2", ["dev", "stable"]) {|b|
9
+ }
10
+
11
+ ChkBuild.def_target("app", ["dev", "stable"], Dep1, Dep2) {|b, suffix, lib1, lib2|
12
+ }
13
+
14
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("echo") {|b|
6
+ b.run("echo", "f&<>oo http://foo.example.net/bar?a=b&c=d baz")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("env") {|b|
6
+ b.run("sh", "-c", "echo $AAA", "ENV:AAA"=>"foooo")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("error") {|b|
6
+ x
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ t = ChkBuild.def_target("fail") {|b|
6
+ b.run("echo", "[bug]")
7
+ b.run("false")
8
+ }
9
+
10
+ t.add_title_hook(nil) {|title, log|
11
+ mark = ''
12
+ mark << "[BUG]" if /\[BUG\]/i =~ log
13
+ mark << "[SEGV]" if /Segmentation fault/i =~ log
14
+ mark << "[FATAL]" if /\[FATAL\]/i =~ log
15
+ title.update_title(:mark, mark)
16
+ }
17
+
18
+ ChkBuild.main
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ t = ChkBuild.def_target("fmesg") {|b|
6
+ b.run("echo", "this fails")
7
+ if /this fails/ =~ b.logfile.get_section('echo')
8
+ raise ChkBuild::Build::CommandError.new(0, "echo")
9
+ end
10
+ }
11
+
12
+ t.add_failure_hook('echo') {|log|
13
+ 'FAIL'
14
+ }
15
+
16
+ ChkBuild.main
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ gcc_v = ChkBuild.def_target("gcc-v") {|b|
6
+ b.run("gcc", '-v', :section=>'version')
7
+ }
8
+
9
+ gcc_v.add_title_hook('version') {|title, log|
10
+ if /^gcc version (.*)$/ =~ log
11
+ title.update_title(:gcc_version, "(gcc #{$1})")
12
+ end
13
+ }
14
+
15
+ ChkBuild.main
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("git") {|b,|
6
+ opts = {:shared_gitdir=>ChkBuild.build_top}
7
+ #b.git("git://github.com/brixen/rubyspec.git", 'tst-rubyspec', opts)
8
+ b.github("brixen", "rubyspec", "tst-rubyspec2", opts)
9
+ }
10
+
11
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("leave-proc") {|b|
6
+ b.run("sleep 1000 &")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("limit") {|b|
6
+ b.run("sh", "-c", "ulimit -a", :rlimit_data => 800*1024)
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("make-failure") {|b|
6
+ b.make("nonexisting-target")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ t = ChkBuild.def_target("network-error") {|b|
6
+ b.network_access {
7
+ if rand < 0.5
8
+ b.run("false")
9
+ else
10
+ b.run("true")
11
+ end
12
+ }
13
+ b.run("true")
14
+ }
15
+
16
+ ChkBuild.main
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ t = ChkBuild.def_target("savannah") {|b|
6
+ b.gnu_savannah_cvs('config', 'config', nil)
7
+ b.run("config/config.guess")
8
+ }
9
+
10
+ t.add_title_hook('config.guess') {|title, log|
11
+ title.update_title(:status, log.chomp[/[^\n]*\z/])
12
+ }
13
+
14
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("sleep") {|b|
6
+ b.run("sleep", "60")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("timeout", :timeout=>Time.now+4) {|b|
6
+ b.run("sleep", "60")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("timeout2", :timeout=>'3s') {|b|
6
+ b.catch_error { b.run("sleep", "60") }
7
+ b.catch_error { b.run("sleep", "60") }
8
+ }
9
+
10
+ ChkBuild.main
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.def_target("timeout3", :timeout=>'10s', :output_interval_timeout=>'3s') {|b|
6
+ b.run("sleep", "60")
7
+ }
8
+
9
+ ChkBuild.main
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ ChkBuild.add_upload_hook {|name|
6
+ p "#{ChkBuild.public_top}/#{name}"
7
+ }
8
+
9
+ ChkBuild.def_target("uptest", ["dev", "stable"]) {|b,|
10
+ b.run("echo", "uptest")
11
+ }
12
+
13
+ ChkBuild.main
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'chkbuild'
4
+
5
+ warn = ChkBuild.def_target("warn") {|b|
6
+ b.run("echo", "warn")
7
+ }
8
+
9
+ warn.add_title_hook('echo') {|title, log|
10
+ title.update_title(:version, "foo")
11
+ }
12
+
13
+ ChkBuild.main
@@ -0,0 +1,572 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (C) 2005 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ # usage: setup/upload-rsync-ssh [remoteuser@]remotehost [remote-dir [chkbuild-user]]
28
+ #
29
+ # setup/upload-rsync-ssh setups upload configuration.
30
+ # 1. generate ssh key dedicated for the upload.
31
+ # localhost:~/.ssh/chkbuild-localhost-remotehost
32
+ # localhost:~/.ssh/chkbuild-localhost-remotehost.pub
33
+ # 2. ssh client setup
34
+ # localhost:~chkbuild-user/.ssh/chkbuild-localhost-remotehost
35
+ # localhost:~chkbuild-user/.ssh/known_hosts
36
+ # 2. setup rsync server via ssh on remotehost.
37
+ # remotehost:~remoteuser/.ssh/chkbuild-rsyncd-localhost-remotehost.conf
38
+ # remotehost:~remoteuser/.ssh/authorized_keys
39
+ # remotehost:~remoteuser/public_html/chkbuild/localhost/
40
+
41
+ # You need an user dedicated to chkbuild before using setup/upload-rsync-ssh.
42
+ # Assume the login name of the dedicated user is chkbuild and
43
+ # your login name is luser.
44
+ #
45
+ # /home/chkbuild user=luser group=chkbuild mode=2750
46
+ # /home/chkbuild/build user=luser group=chkbuild mode=2775
47
+ # /home/chkbuild/public_html user=luser group=chkbuild mode=2775
48
+ #
49
+ # # adduser --disabled-login --no-create-home --shell /home/luser/chkbuild/start-build chkbuild
50
+ # # usermod -G ...,chkbuild luser
51
+ # # cd /home
52
+ # # mkdir chkbuild chkbuild/build chkbuild/public_html
53
+ # # chown luser chkbuild
54
+ # # chown chkbuild:luser chkbuild/build chkbuild/public_html
55
+ # # chmod 2775 chkbuild/build chkbuild/public_html
56
+ # # setup/upload-rsync-ssh chkbuild remote-user@remotehost
57
+ # ...
58
+ # rsync target: remote-user@remotehost::upload
59
+ #
60
+ # Note that setup/upload-rsync-ssh invokes ssh twice.
61
+
62
+ require 'etc'
63
+ require 'socket'
64
+ require 'fileutils'
65
+
66
+ local_host = Socket.gethostname
67
+ DEFAULT_CHKBUILD_UESR = 'chkbuild'
68
+ DEFAULT_REMOTE_DIR = "public_html/chkbuild/#{local_host}"
69
+
70
+ def usage(status)
71
+ puts <<"End"
72
+ usage: setup/upload-rsync-ssh [remoteuser@]remotehost [remote-dir [chkbuild-user]]
73
+ default:
74
+ remote-dir = #{DEFAULT_REMOTE_DIR.inspect}
75
+ chkbuild-user = #{DEFAULT_CHKBUILD_UESR.inspect}
76
+ End
77
+ exit status
78
+ end
79
+
80
+ if ARGV.length < 1 || 3 < ARGV.length
81
+ puts "setup/upload-rsync-ssh needs exactly 1 to 3 arguments"
82
+ usage(1)
83
+ end
84
+
85
+ remote_info, remote_dir, chkbuild_user = ARGV
86
+ remote_dir ||= DEFAULT_REMOTE_DIR
87
+ chkbuild_user ||= DEFAULT_CHKBUILD_UESR
88
+
89
+ local_user = Etc.getpwuid
90
+
91
+ if /@/ =~ remote_info
92
+ remote_user = $`
93
+ remote_host = $'
94
+ else
95
+ remote_user = local_user.name
96
+ remote_host = remote_info
97
+ end
98
+
99
+ begin
100
+ chkbuild_user = Etc.getpwnam(chkbuild_user)
101
+ rescue ArgumentError
102
+ puts "no such user: #{chkbuild_user.inspect}"
103
+ exit 1
104
+ end
105
+
106
+ begin
107
+ chkbuild_group = Etc.getgrgid(chkbuild_user.gid)
108
+ rescue ArgumentError
109
+ puts "no group which gid is #{chkbuild_user.gid}"
110
+ exit 1
111
+ end
112
+
113
+ if !chkbuild_group.mem.include?(local_user.name)
114
+ puts "#{local_user.name.inspect} is not a member of group #{chkbuild_group.name.inspect}"
115
+ exit 1
116
+ end
117
+
118
+ if !Process.groups.include?(chkbuild_user.gid)
119
+ puts "process supplementary groups doesn't contain #{chkbuild_group.name.inspect}"
120
+ exit 1
121
+ end
122
+
123
+ if local_user.uid == chkbuild_user.uid
124
+ puts "same uid #{local_user.name.inspect} and #{chkbuild_user.name}: #{local_user.uid}"
125
+ exit 1
126
+ end
127
+
128
+ # xxx: gid check?
129
+
130
+ class FileMap
131
+ def initialize(filemap)
132
+ @paths = []
133
+ @filemap = {}
134
+ filemap.each_line {|line|
135
+ next if /\A\s*\z/ =~ line
136
+ path, *spec_list = line.split(/\s+/)
137
+ if %r{/\z} =~ path
138
+ spec_list << "dir"
139
+ path.sub!(%r{/\z}, '')
140
+ end
141
+ specs = {}
142
+ spec_list.each {|spec|
143
+ if /=/ =~ spec
144
+ key = $`
145
+ val = $'
146
+ case key
147
+ when /\Auser\z/
148
+ val = val.to_i
149
+ when /\Agroup\z/
150
+ val = val.to_i
151
+ when /\Amode\z/
152
+ mode, mask = val.split(%r{/})
153
+ val = [mode.oct, mask.oct]
154
+ end
155
+ specs[key] = val
156
+ else
157
+ specs[spec] = true
158
+ end
159
+ }
160
+ if @filemap[path]
161
+ raise ArgumentError, "duplicate path: #{path.inspect}"
162
+ end
163
+ @paths << path
164
+ @filemap[path] = specs
165
+ }
166
+ @filemap.each {|path, specs|
167
+ dir = File.dirname(path)
168
+ while @filemap.include? dir
169
+ @filemap[dir]['dir'] = true
170
+ dir = File.dirname(dir)
171
+ end
172
+ }
173
+ end
174
+
175
+ def creatable_target?(specs, parent_specs, parent)
176
+ parent_uid = nil
177
+ parent_gid = nil
178
+ parent_mode = nil
179
+ parent_mask = nil
180
+ if parent_specs
181
+ parent_specs.each {|key, val|
182
+ case key
183
+ when /\Auser\z/ then parent_uid = val
184
+ when /\Agroup\z/ then parent_gid = val
185
+ when /\Amode\z/ then parent_mode, parent_mask = val
186
+ when /\Afile\z/ then return false
187
+ end
188
+ }
189
+ else
190
+ begin
191
+ parent_stat = File.stat(parent)
192
+ return false unless parent_stat.directory?
193
+ parent_uid = parent_stat.uid
194
+ parent_gid = parent_stat.gid
195
+ parent_mode = parent_stat.mode
196
+ parent_mask = 07777
197
+ rescue Errno::ENOENT
198
+ end
199
+ end
200
+ if parent_uid && Process.uid == parent_uid
201
+ unless parent_mask & 0300 == 0300 && parent_mode & 0300 == 0300
202
+ return false
203
+ end
204
+ elsif parent_uid && parent_gid && (Process.gid == parent_gid || Process.groups.include?(parent_gid))
205
+ unless parent_mask & 0030 == 0030 && parent_mode & 0030 == 0030
206
+ return false
207
+ end
208
+ elsif parent_uid && parent_gid
209
+ unless parent_mask & 0003 == 0003 && parent_mode & 0003 == 0003
210
+ return false
211
+ end
212
+ else
213
+ return false
214
+ end
215
+ specs.each {|key, val|
216
+ case key
217
+ when /\Auser\z/
218
+ return false if Process.uid != val
219
+ when /\Agroup\z/
220
+ unless Process.gid == val ||
221
+ Process.groups.include?(val) ||
222
+ (parent_mask && parent_gid && parent_mask & 02000 != 0 && parent_mode & 02000 != 0 && parent_gid == val)
223
+ return false
224
+ end
225
+ end
226
+ }
227
+ return true
228
+ end
229
+
230
+ def check
231
+ stats = {}
232
+ errors = {}
233
+ fixable = {}
234
+ @paths.each {|path|
235
+ specs = @filemap[path]
236
+ errors[path] ||= []
237
+ fixable[path] ||= []
238
+ begin
239
+ stat = File.stat(path)
240
+ rescue Errno::ENOENT
241
+ parent = File.dirname(path)
242
+ if creatable_target?(specs, @filemap[parent], parent)
243
+ fixable[path] = specs
244
+ else
245
+ errors[path] << 'not-exist'
246
+ end
247
+ end
248
+ if stat
249
+ stats[path] = stat
250
+ specs.each {|key, val|
251
+ case key
252
+ when /\Auser\z/
253
+ errors[path] << [key, val, stat] if stat.uid != val
254
+ when /\Agroup\z/
255
+ if stat.gid != val
256
+ if specs['user'] == Process.uid && Process.groups.include?(val)
257
+ fixable[path] << [key, val, stat]
258
+ else
259
+ errors[path] << [key, val, stat]
260
+ end
261
+ end
262
+ when /\Amode\z/
263
+ mode, mask = val
264
+ if stat.mode & mask != mode
265
+ if specs['user'] == Process.uid
266
+ fixable[path] << [key, val, stat]
267
+ else
268
+ errors[path] << [key, val, stat]
269
+ end
270
+ end
271
+ when /\Afile\z/
272
+ errors[path] << [key, val, stat] if !stat.file?
273
+ when /\Adir\z/
274
+ errors[path] << [key, val, stat] if !stat.directory?
275
+ else
276
+ raise "unexpected file spec: #{key.inspect} (#{path.inspect})"
277
+ end
278
+ }
279
+ end
280
+ errors.delete(path) if errors[path].empty?
281
+ fixable.delete(path) if fixable[path].empty?
282
+ }
283
+ if errors.empty?
284
+ @stats = stats
285
+ @fixable = fixable
286
+ true
287
+ else
288
+ @errors = errors
289
+ false
290
+ end
291
+ end
292
+ attr_reader :stats
293
+
294
+ def inspect_uid(uid)
295
+ begin
296
+ info = Etc.getpwuid(uid)
297
+ rescue ArgumentError
298
+ uid.to_s
299
+ end
300
+ "#{uid} (#{info.name})"
301
+ end
302
+
303
+ def inspect_gid(gid)
304
+ begin
305
+ info = Etc.getgrgid(gid)
306
+ rescue ArgumentError
307
+ gid.to_s
308
+ end
309
+ "#{gid} (#{info.name})"
310
+ end
311
+
312
+ def print_errors
313
+ @errors.keys.sort.each {|path|
314
+ @errors[path].each {|errinfo|
315
+ key, val, *rest = errinfo
316
+ case key
317
+ when /\Auser\z/
318
+ stat, = rest
319
+ puts "#{path.inspect}: uid expected #{inspect_uid val} but #{inspect_uid stat.uid}"
320
+ when /\Agroup\z/
321
+ stat, = rest
322
+ puts "#{path.inspect}: gid expected #{inspect_gid val} but #{inspect_gid stat.gid}"
323
+ when %r{\Amode\z}
324
+ mode, mask = val
325
+ stat, = rest
326
+ puts "#{path.inspect}: mode expected #{"%o" % mode} but #{"%o" % (stat.mode & mask)} (mask=#{"%o" % mask})"
327
+ when /\Afile\z/
328
+ stat, = rest
329
+ puts "#{path.inspect}: regular file expected but #{stat.ftype}"
330
+ when /\Adir\z/
331
+ stat, = rest
332
+ puts "#{path.inspect}: directory expected but #{stat.ftype}"
333
+ when /\Anot-exist\z/
334
+ puts "#{path.inspect}: not exist"
335
+ else
336
+ raise "unexpected file spec: #{key.inspect} (#{path.inspect})"
337
+ end
338
+ }
339
+ }
340
+ end
341
+
342
+ def fix_permission(path, uid, gid, mode, mask, stat=nil)
343
+ if !stat
344
+ begin
345
+ stat = File.stat(path)
346
+ rescue Errno::ENOENT
347
+ puts "#{path.inspect}: not exist."
348
+ exit 1
349
+ end
350
+ end
351
+ if uid && stat.uid != uid
352
+ puts "#{path.inspect}: uid expected #{inspect_uid uid} but #{inspect_uid stat.uid}"
353
+ exit 1
354
+ end
355
+ if gid && stat.gid != gid
356
+ File.chown(-1, gid, path)
357
+ end
358
+ if mode && stat.mode & mask != mode
359
+ File.chmod(stat.mode & ~mask | mode, path)
360
+ end
361
+ stat = File.stat(path)
362
+ if gid && stat.gid != gid
363
+ puts "#{path.inspect}: gid expected #{inspect_uid gid} but #{inspect_gid stat.gid}"
364
+ exit 1
365
+ end
366
+ if mode && stat.mode & mask != mode
367
+ puts "#{path.inspect}: mode expected #{"%o" % gid} but #{"%o" % stat.gid}"
368
+ exit 1
369
+ end
370
+ end
371
+
372
+ def fix_directories
373
+ dirs = @paths.reject {|path| !@filemap[path]['dir'] }
374
+ dirs.each {|dir|
375
+ uid = @filemap[dir]['user']
376
+ gid = @filemap[dir]['group']
377
+ mode, mask = @filemap[dir]['mode']
378
+ stat = @stats[dir]
379
+ if !stat
380
+ Dir.mkdir(dir, mode)
381
+ stat = File.stat(dir)
382
+ end
383
+ fix_permission(dir, uid, gid, mode, mask, stat)
384
+ }
385
+ end
386
+
387
+ def fix_files
388
+ errors = 0
389
+ paths = @paths.reject {|path| @filemap[path]['dir'] }
390
+ paths.each {|path|
391
+ uid = @filemap[path]['user']
392
+ gid = @filemap[path]['group']
393
+ mode, mask = @filemap[path]['mode']
394
+ begin
395
+ stat = File.stat(path)
396
+ rescue Errno::ENOENT
397
+ puts "#{path.inspect}: not generated."
398
+ exit 1
399
+ end
400
+ fix_permission(path, uid, gid, mode, mask, stat)
401
+ }
402
+ end
403
+
404
+ end
405
+
406
+ cdir = chkbuild_user.dir
407
+ cuid = chkbuild_user.uid
408
+ cgid = chkbuild_user.gid
409
+ ldir = local_user.dir
410
+ luid = local_user.uid
411
+ lgid = local_user.gid
412
+
413
+ if cdir == ldir
414
+ puts "same home directory for #{local_user.name.inspect} and #{chkbuild_user.name.inspect}: #{cdir.inspect}"
415
+ exit 1
416
+ end
417
+
418
+ host_pair = "#{local_host}-#{remote_host}"
419
+ filemap = FileMap.new(<<"End")
420
+ #{cdir} user=#{luid} group=#{cgid} mode=2750/7777
421
+ #{cdir}/.ssh user=#{luid} group=#{cgid} mode=2750/7777
422
+ #{cdir}/.ssh/chkbuild-#{host_pair} user=#{luid} group=#{cgid} mode=640/777 file
423
+ #{cdir}/.ssh/known_hosts user=#{luid} group=#{cgid} mode=640/777 file
424
+ #{cdir}/build/ user=#{luid} group=#{cgid} mode=2775/7777
425
+ #{cdir}/public_html/ user=#{luid} group=#{cgid} mode=2775/7777
426
+
427
+ #{ldir}/.ssh/chkbuild-#{host_pair} user=#{luid} group=#{lgid} mode=400/777 file
428
+ #{ldir}/.ssh/chkbuild-#{host_pair}.pub user=#{luid} group=#{lgid} mode=444/777 file
429
+ End
430
+
431
+ unless filemap.check
432
+ filemap.print_errors
433
+ exit 1
434
+ end
435
+
436
+ stats = filemap.stats
437
+
438
+ local_secret_key = "#{ldir}/.ssh/chkbuild-#{host_pair}"
439
+ local_public_key = "#{ldir}/.ssh/chkbuild-#{host_pair}.pub"
440
+ if stats[local_secret_key] && !stats[local_public_key]
441
+ puts "#{local_public_key.inspect}: local ssh secret key exists but no public key"
442
+ exit 1
443
+ end
444
+ if !stats[local_secret_key] && stats[local_public_key]
445
+ puts "#{local_secret_key.inspect}: local ssh public key exists but no secret key"
446
+ exit 1
447
+ end
448
+
449
+ chkbuild_secret_key = "#{cdir}/.ssh/chkbuild-#{host_pair}"
450
+ if stats[chkbuild_secret_key]
451
+ if !stats[local_secret_key]
452
+ puts "#{chkbuild_secret_key.inspect} exists but not #{local_secret_key.inspect}"
453
+ exit 1
454
+ end
455
+ if !FileUtils.compare_file(chkbuild_secret_key, local_secret_key)
456
+ puts "different content: #{chkbuild_secret_key.inspect} and #{local_secret_key.inspect}"
457
+ exit 1
458
+ end
459
+ end
460
+
461
+ puts "trying to check rsync & ssh #{remote_user}@#{remote_host} possible..."
462
+ output = `ssh #{remote_user}@#{remote_host} rsync --version`
463
+ if $?.to_i != 0
464
+ puts "ssh/rsync failed."
465
+ exit 1
466
+ end
467
+ if /rsync\s+version\s+([\d.]+)/ !~ output
468
+ puts "unexpected ssh/rsync output:"
469
+ puts "--------------------------------"
470
+ puts output
471
+ puts "--------------------------------"
472
+ exit 1
473
+ end
474
+ rsync_version = $1
475
+ if (rsync_version.split(/\./).map {|n| n.to_i } <=> [2,6,3]) < 0
476
+ puts "rsync in #{remote_host.inspect} is too old: #{rsync_version} (must be newer than or equal 2.6.3)"
477
+ exit 1
478
+ end
479
+
480
+ ######################################################################
481
+
482
+ filemap.fix_directories
483
+
484
+ if !stats[local_secret_key]
485
+ system "ssh-keygen", "-t", "dsa", "-N", "", "-f", local_secret_key, "-C", "chkbuild@#{local_host}@#{remote_host}"
486
+ exit 1 if $?.to_i != 0
487
+ end
488
+ begin
489
+ File.unlink chkbuild_secret_key
490
+ rescue Errno::ENOENT
491
+ end
492
+ FileUtils.copy_file(local_secret_key, chkbuild_secret_key)
493
+
494
+ chkbuild_known_hosts = "#{cdir}/.ssh/known_hosts"
495
+ local_known_hosts = "#{ldir}/.ssh/known_hosts"
496
+ known_hosts_line = nil
497
+ if File.exist? local_known_hosts
498
+ IO.foreach(local_known_hosts) {|line|
499
+ next if /\A#/ =~ line || /\A\s*\z/ =~ line
500
+ if /\A\S+/ =~ line
501
+ rest = $'
502
+ hostnames = $&.split(/,/)
503
+ if hostnames.include? remote_host
504
+ addrs = [remote_host]
505
+ Socket.getaddrinfo(remote_host, "ssh").each {|addrfamily, port, host, addr|
506
+ if addrfamily == 'AF_INET'
507
+ addrs << addr
508
+ end
509
+ }
510
+ addrs.uniq!
511
+ known_hosts_line = "#{addrs.join(',')}#{rest}"
512
+ break
513
+ end
514
+ end
515
+ }
516
+ end
517
+ open(chkbuild_known_hosts, File::RDWR|File::CREAT) {|f|
518
+ if known_hosts_line
519
+ f.each_line {|line|
520
+ if line == known_hosts_line
521
+ known_hosts_line = nil
522
+ end
523
+ }
524
+ f.puts known_hosts_line if known_hosts_line
525
+ end
526
+ }
527
+
528
+ filemap.fix_files
529
+
530
+ public_key = File.read(local_public_key).chomp
531
+
532
+ puts "setup remote files using ssh #{remote_user}@#{remote_host}..."
533
+ IO.popen("ssh #{remote_user}@#{remote_host} sh -s", "w") {|f|
534
+ commands = <<'End'
535
+ [ -d .ssh ] || (
536
+ mkdir .ssh
537
+ chmod 700 .ssh
538
+ )
539
+ End
540
+ d = []
541
+ remote_dir.scan(%r{[^/]+}) {|n|
542
+ next if /\A\.\.?\z/ =~ n
543
+ d << n
544
+ dir = d.join('/')
545
+ commands << <<"End"
546
+ [ -d #{dir} ] || (
547
+ mkdir #{dir}
548
+ chmod 755 #{dir}
549
+ )
550
+ End
551
+ }
552
+ dir = d.join('/')
553
+ commands << <<"End"
554
+ cat <<E > .ssh/chkbuild-rsyncd-#{host_pair}.conf
555
+ [upload]
556
+ path = $HOME/#{dir}
557
+ use chroot = no
558
+ read only = no
559
+ write only = yes
560
+ E
561
+ [ -f .ssh/authorized_keys ] || touch .ssh/authorized_keys
562
+ cp .ssh/authorized_keys .ssh/authorized_keys.bak &&
563
+ cat - .ssh/authorized_keys <<E | sort | uniq > .ssh/authorized_keys.new &&
564
+ command="`which rsync` --server --daemon --config=$HOME/.ssh/chkbuild-rsyncd-#{host_pair}.conf .",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}
565
+ E
566
+ mv .ssh/authorized_keys.new .ssh/authorized_keys
567
+ End
568
+ f << commands
569
+ }
570
+
571
+ puts "rsync target: #{remote_user}@#{remote_host}::upload"
572
+