| 1 package main |
1 package main |
| 2 |
2 |
| 3 import ( |
3 import ( |
| 4 "bytes" |
4 "bytes" |
| 5 "encoding/xml" |
5 "encoding/xml" |
| |
6 "errors" |
| 6 "fmt" |
7 "fmt" |
| 7 "log" |
8 "log" |
| 8 "os" |
9 "os" |
| 9 "os/exec" |
10 "os/exec" |
| 10 "path" |
11 "path" |
| |
12 "strings" |
| 11 "text/template" |
13 "text/template" |
| 12 ) |
14 ) |
| 13 |
15 |
| 14 type Config struct { |
16 type Config struct { |
| 15 XMLName xml.Name `xml:"config"` |
17 XMLName xml.Name `xml:"config"` |
| 16 BuildEnv []struct { |
18 BuildEnv []BuildEnv `xml:"buildenv"` |
| 17 Name string `xml:"name,attr"` |
19 BuildEnvMap map[string]BuildEnv |
| 18 Host string `xml:"host"` |
20 |
| 19 User string `xml:"user"` |
21 Repository []Repository `xml:"repository"` |
| 20 } `xml:"buildenv"` |
22 } |
| 21 |
23 |
| 22 Repository []struct { |
24 type BuildEnv struct { |
| 23 Path string `xml:"path"` |
25 Name string `xml:"name,attr"` |
| 24 BuildEnvs string `xml:"buildenvs"` |
26 Host string `xml:"host"` |
| 25 Build []struct { |
27 User string `xml:"user"` |
| 26 IsPlatform string `xml:"isplatform,attr"` |
28 } |
| 27 NotPlatform string `xml:"not,attr"` |
29 |
| 28 Test []string `xml:"test"` |
30 type Repository struct { |
| 29 Var []struct { |
31 Path string `xml:"path"` |
| 30 Name string `xml:"name,attr"` |
32 BuildEnvs string `xml:"buildenvs"` |
| 31 Value string `xml:",chardata"` |
33 Build []struct { |
| 32 } |
34 IsPlatform string `xml:"isplatform,attr"` |
| 33 Configure string `xml:"configure"` |
35 NotPlatform string `xml:"not,attr"` |
| 34 Compile string `xml:"compile"` |
36 Test []string `xml:"test"` |
| 35 Check string `xml:"check"` |
37 Var []struct { |
| 36 } `xml:"build"` |
38 Name string `xml:"name,attr"` |
| 37 } `xml:"repository"` |
39 Value string `xml:",chardata"` |
| |
40 } |
| |
41 Configure string `xml:"configure"` |
| |
42 Compile string `xml:"compile"` |
| |
43 Check string `xml:"check"` |
| |
44 } `xml:"build"` |
| 38 } |
45 } |
| 39 |
46 |
| 40 func main() { |
47 func main() { |
| 41 data, err := os.ReadFile("testconfig.xml") |
48 data, err := os.ReadFile("testconfig.xml") |
| 42 if err != nil { |
49 if err != nil { |
| 61 err = os.Mkdir("tmp", 0755) |
72 err = os.Mkdir("tmp", 0755) |
| 62 if err != nil && !os.IsExist(err) { |
73 if err != nil && !os.IsExist(err) { |
| 63 log.Fatal(err) |
74 log.Fatal(err) |
| 64 } |
75 } |
| 65 |
76 |
| 66 tmpDir := "tmp" |
77 tmp := "tmp" |
| 67 |
78 |
| 68 for _, repo := range config.Repository { |
79 for _, repo := range config.Repository { |
| 69 // create a build directory, that contains the repository source |
80 if err = create_repo_archive(&repo, tpl, tmp); err != nil { |
| 70 // and build scripts |
|
| 71 buildPath := path.Join(tmpDir, "build") |
|
| 72 |
|
| 73 if err = os.RemoveAll(buildPath); err != nil { |
|
| 74 log.Fatal(err) |
81 log.Fatal(err) |
| 75 } |
82 } |
| 76 if err = os.RemoveAll(path.Join(tmpDir, "build.tar")); err != nil { |
83 |
| |
84 if err = exec_buildenvs(config, &repo, tmp); err != nil { |
| 77 log.Fatal(err) |
85 log.Fatal(err) |
| 78 } |
86 } |
| 79 if err = os.RemoveAll(path.Join(tmpDir, "build.tar.gz")); err != nil { |
87 } |
| 80 log.Fatal(err) |
88 } |
| 81 } |
89 |
| 82 |
90 func create_repo_archive(repo *Repository, tpl *template.Template, tmp_dir string) error { |
| 83 err = os.Mkdir(buildPath, 0755) |
91 // create a build directory, that contains the repository source |
| 84 if err != nil { |
92 // and build scripts |
| 85 log.Fatal(err) |
93 buildPath := path.Join(tmp_dir, "build") |
| 86 } |
94 |
| 87 |
95 if err := os.RemoveAll(buildPath); err != nil { |
| 88 // update repository and copy it to the build directory |
96 log.Print("remove build dir failed: ", err) |
| 89 cmd := exec.Command("hg", "pull") |
97 return err |
| |
98 } |
| |
99 if err := os.RemoveAll(path.Join(tmp_dir, "build.tar")); err != nil { |
| |
100 log.Print("remove build.tar failed") |
| |
101 return err |
| |
102 } |
| |
103 if err := os.RemoveAll(path.Join(tmp_dir, "build.tar.gz")); err != nil { |
| |
104 log.Print("remove build.tar.gz failed") |
| |
105 return err |
| |
106 } |
| |
107 |
| |
108 err := os.Mkdir(buildPath, 0755) |
| |
109 if err != nil { |
| |
110 log.Print("mkdir build dir failed: ", err) |
| |
111 return err |
| |
112 } |
| |
113 |
| |
114 // update repository and copy it to the build directory |
| |
115 cmd := exec.Command("hg", "pull") |
| |
116 var outb, errb bytes.Buffer |
| |
117 cmd.Dir = repo.Path |
| |
118 cmd.Stdout = &outb |
| |
119 cmd.Stderr = &errb |
| |
120 err = cmd.Run() |
| |
121 if err != nil { |
| |
122 log.Print("hg: ", errb.String()) |
| |
123 return err |
| |
124 } |
| |
125 |
| |
126 outb.Reset() |
| |
127 errb.Reset() |
| |
128 cmd = exec.Command("hg", "update") |
| |
129 cmd.Dir = repo.Path |
| |
130 cmd.Stdout = &outb |
| |
131 cmd.Stderr = &errb |
| |
132 err = cmd.Run() |
| |
133 if err != nil { |
| |
134 log.Print("hg: ", errb.String()) |
| |
135 return err |
| |
136 } |
| |
137 |
| |
138 // copy repository to the tmp_dir build directory |
| |
139 cmd = exec.Command("cp", "-r", repo.Path, path.Join(buildPath, "src")) |
| |
140 err = cmd.Run() |
| |
141 if err != nil { |
| |
142 log.Print("cannot copy the repository to the build dir") |
| |
143 return err |
| |
144 } |
| |
145 |
| |
146 // create build script |
| |
147 file, err := os.Create(path.Join(buildPath, "build.sh")) |
| |
148 if err != nil { |
| |
149 log.Print("cannot create build.sh") |
| |
150 return err |
| |
151 } |
| |
152 defer file.Close() |
| |
153 |
| |
154 err = tpl.Execute(file, repo) |
| |
155 if err != nil { |
| |
156 log.Print("cannot execute build.sh template") |
| |
157 return err |
| |
158 } |
| |
159 file.Chmod(0755) |
| |
160 |
| |
161 // create tarball |
| |
162 cmd = exec.Command("tar", "cvf", "../build.tar", "src", "build.sh") |
| |
163 cmd.Dir = buildPath |
| |
164 if err := cmd.Run(); err != nil { |
| |
165 log.Print("tar error") |
| |
166 return err |
| |
167 } |
| |
168 |
| |
169 cmd = exec.Command("gzip", path.Join(tmp_dir, "build.tar")) |
| |
170 if err := cmd.Run(); err != nil { |
| |
171 log.Print("gzip error") |
| |
172 return err |
| |
173 } |
| |
174 |
| |
175 return nil |
| |
176 } |
| |
177 |
| |
178 func host_is_available(host string) bool { |
| |
179 cmd := exec.Command("ping", "-c", "1", host) |
| |
180 if err := cmd.Run(); err != nil { |
| |
181 return false |
| |
182 } |
| |
183 return true |
| |
184 } |
| |
185 |
| |
186 func exec_buildenvs(config *Config, repo *Repository, tmp_dir string) error { |
| |
187 if len(repo.BuildEnvs) == 0 { |
| |
188 log.Print("repo %s has no buildenvs", repo.Path) |
| |
189 return nil |
| |
190 } |
| |
191 buildenvs := strings.Split(repo.BuildEnvs, ",") |
| |
192 for _, buildenv := range buildenvs { |
| |
193 env, ok := config.BuildEnvMap[buildenv] |
| |
194 if !ok { |
| |
195 return errors.New("unknown build env: " + buildenv) |
| |
196 } |
| |
197 if !host_is_available(env.Host) { |
| |
198 log.Print("skip unavailable host ", env.Host) |
| |
199 return nil |
| |
200 } |
| |
201 |
| |
202 // steps: |
| |
203 // upload build.tar.gz |
| |
204 // extract build.tar.gz on the buildenv host and run build.sh |
| |
205 |
| |
206 // upload using scp |
| |
207 cmd := exec.Command("scp", path.Join(tmp_dir, "build.tar.gz"), env.Host+":") |
| |
208 if err := cmd.Run(); err != nil { |
| |
209 log.Print("cannot copy build env to ", env.Host) |
| |
210 continue |
| |
211 } |
| |
212 |
| |
213 // run build.sh |
| |
214 build_dir := "build" |
| |
215 remote_command := fmt.Sprintf("sh -c 'rm -Rf %s; mkdir -p %s; gzip -dc build.tar.gz | tar xf - -C %s ; (cd %s; ./build.sh)'", build_dir, build_dir, build_dir, build_dir) |
| |
216 log.Printf("host: %s: command: %s", env.Host, remote_command) |
| |
217 |
| 90 var outb, errb bytes.Buffer |
218 var outb, errb bytes.Buffer |
| 91 cmd.Dir = repo.Path |
219 cmd = exec.Command("ssh", "-t", env.Host, remote_command) |
| 92 cmd.Stdout = &outb |
220 cmd.Stdout = &outb |
| 93 cmd.Stderr = &errb |
221 cmd.Stderr = &errb |
| 94 err = cmd.Run() |
222 err := cmd.Run() |
| |
223 log.Printf("host: %s: stdout: %s", env.Host, outb.String()) |
| 95 if err != nil { |
224 if err != nil { |
| 96 log.Print("hg: ", errb.String()) |
225 log.Print("cannot execute build.sh on buildenv host ", env.Host) |
| 97 log.Fatal(err) |
226 log.Print("stderr: ", errb.String()) |
| 98 } |
227 } |
| 99 |
228 } |
| 100 outb.Reset() |
229 |
| 101 errb.Reset() |
230 return nil |
| 102 cmd = exec.Command("hg", "update") |
231 } |
| 103 cmd.Dir = repo.Path |
|
| 104 cmd.Stdout = &outb |
|
| 105 cmd.Stderr = &errb |
|
| 106 err = cmd.Run() |
|
| 107 if err != nil { |
|
| 108 log.Print("hg: ", errb.String()) |
|
| 109 log.Fatal(err) |
|
| 110 } |
|
| 111 |
|
| 112 // copy repository to the tmp build directory |
|
| 113 cmd = exec.Command("cp", "-r", repo.Path, path.Join(buildPath, "src")) |
|
| 114 err = cmd.Run() |
|
| 115 if err != nil { |
|
| 116 log.Fatal(err) |
|
| 117 } |
|
| 118 |
|
| 119 // create build script |
|
| 120 file, err := os.Create(path.Join(buildPath, "build.sh")) |
|
| 121 if err != nil { |
|
| 122 log.Fatal(err) |
|
| 123 } |
|
| 124 |
|
| 125 err = tpl.Execute(file, repo) |
|
| 126 |
|
| 127 if err != nil { |
|
| 128 log.Fatal(err) |
|
| 129 } |
|
| 130 file.Close() |
|
| 131 |
|
| 132 // create tarball |
|
| 133 cmd = exec.Command("tar", "cvf", "../build.tar", "src", "build.sh") |
|
| 134 cmd.Dir = buildPath |
|
| 135 if err := cmd.Run(); err != nil { |
|
| 136 log.Print("tar error") |
|
| 137 log.Fatal(err) |
|
| 138 } |
|
| 139 |
|
| 140 fmt.Println(path.Join(buildPath, "build.tar")) |
|
| 141 cmd = exec.Command("gzip", path.Join(tmpDir, "build.tar")) |
|
| 142 if err := cmd.Run(); err != nil { |
|
| 143 log.Print("gzip error") |
|
| 144 log.Fatal(err) |
|
| 145 } |
|
| 146 |
|
| 147 // TODO: upload build.tar.gz to buildenvs and execute the build.sh script remotely |
|
| 148 } |
|
| 149 } |
|