Mon, 15 Dec 2025 10:59:58 +0100
add buildenv remote build.sh execution
| build.template | file | annotate | diff | comparison | revisions | |
| src/main.go | file | annotate | diff | comparison | revisions | |
| testconfig.xml | file | annotate | diff | comparison | revisions |
--- a/build.template Sun Dec 14 16:51:29 2025 +0100 +++ b/build.template Mon Dec 15 10:59:58 2025 +0100 @@ -1,5 +1,7 @@ #!/bin/sh +pwd + echo {{ .Path }} echo TODO
--- a/src/main.go Sun Dec 14 16:51:29 2025 +0100 +++ b/src/main.go Mon Dec 15 10:59:58 2025 +0100 @@ -3,38 +3,45 @@ import ( "bytes" "encoding/xml" + "errors" "fmt" "log" "os" "os/exec" "path" + "strings" "text/template" ) type Config struct { - XMLName xml.Name `xml:"config"` - BuildEnv []struct { - Name string `xml:"name,attr"` - Host string `xml:"host"` - User string `xml:"user"` - } `xml:"buildenv"` + XMLName xml.Name `xml:"config"` + BuildEnv []BuildEnv `xml:"buildenv"` + BuildEnvMap map[string]BuildEnv + + Repository []Repository `xml:"repository"` +} + +type BuildEnv struct { + Name string `xml:"name,attr"` + Host string `xml:"host"` + User string `xml:"user"` +} - Repository []struct { - Path string `xml:"path"` - BuildEnvs string `xml:"buildenvs"` - Build []struct { - IsPlatform string `xml:"isplatform,attr"` - NotPlatform string `xml:"not,attr"` - Test []string `xml:"test"` - Var []struct { - Name string `xml:"name,attr"` - Value string `xml:",chardata"` - } - Configure string `xml:"configure"` - Compile string `xml:"compile"` - Check string `xml:"check"` - } `xml:"build"` - } `xml:"repository"` +type Repository struct { + Path string `xml:"path"` + BuildEnvs string `xml:"buildenvs"` + Build []struct { + IsPlatform string `xml:"isplatform,attr"` + NotPlatform string `xml:"not,attr"` + Test []string `xml:"test"` + Var []struct { + Name string `xml:"name,attr"` + Value string `xml:",chardata"` + } + Configure string `xml:"configure"` + Compile string `xml:"compile"` + Check string `xml:"check"` + } `xml:"build"` } func main() { @@ -47,6 +54,10 @@ if err := xml.Unmarshal(data, &config); err != nil { log.Fatal(err) } + config.BuildEnvMap = make(map[string]BuildEnv) + for _, env := range config.BuildEnv { + config.BuildEnvMap[env.Name] = env + } templateStr, err := os.ReadFile("build.template") if err != nil { @@ -63,87 +74,158 @@ log.Fatal(err) } - tmpDir := "tmp" + tmp := "tmp" for _, repo := range config.Repository { - // create a build directory, that contains the repository source - // and build scripts - buildPath := path.Join(tmpDir, "build") - - if err = os.RemoveAll(buildPath); err != nil { - log.Fatal(err) - } - if err = os.RemoveAll(path.Join(tmpDir, "build.tar")); err != nil { - log.Fatal(err) - } - if err = os.RemoveAll(path.Join(tmpDir, "build.tar.gz")); err != nil { - log.Fatal(err) - } - - err = os.Mkdir(buildPath, 0755) - if err != nil { - log.Fatal(err) - } - - // update repository and copy it to the build directory - cmd := exec.Command("hg", "pull") - var outb, errb bytes.Buffer - cmd.Dir = repo.Path - cmd.Stdout = &outb - cmd.Stderr = &errb - err = cmd.Run() - if err != nil { - log.Print("hg: ", errb.String()) + if err = create_repo_archive(&repo, tpl, tmp); err != nil { log.Fatal(err) } - outb.Reset() - errb.Reset() - cmd = exec.Command("hg", "update") - cmd.Dir = repo.Path - cmd.Stdout = &outb - cmd.Stderr = &errb - err = cmd.Run() - if err != nil { - log.Print("hg: ", errb.String()) + if err = exec_buildenvs(config, &repo, tmp); err != nil { log.Fatal(err) } + } +} - // copy repository to the tmp build directory - cmd = exec.Command("cp", "-r", repo.Path, path.Join(buildPath, "src")) - err = cmd.Run() - if err != nil { - log.Fatal(err) +func create_repo_archive(repo *Repository, tpl *template.Template, tmp_dir string) error { + // create a build directory, that contains the repository source + // and build scripts + buildPath := path.Join(tmp_dir, "build") + + if err := os.RemoveAll(buildPath); err != nil { + log.Print("remove build dir failed: ", err) + return err + } + if err := os.RemoveAll(path.Join(tmp_dir, "build.tar")); err != nil { + log.Print("remove build.tar failed") + return err + } + if err := os.RemoveAll(path.Join(tmp_dir, "build.tar.gz")); err != nil { + log.Print("remove build.tar.gz failed") + return err + } + + err := os.Mkdir(buildPath, 0755) + if err != nil { + log.Print("mkdir build dir failed: ", err) + return err + } + + // update repository and copy it to the build directory + cmd := exec.Command("hg", "pull") + var outb, errb bytes.Buffer + cmd.Dir = repo.Path + cmd.Stdout = &outb + cmd.Stderr = &errb + err = cmd.Run() + if err != nil { + log.Print("hg: ", errb.String()) + return err + } + + outb.Reset() + errb.Reset() + cmd = exec.Command("hg", "update") + cmd.Dir = repo.Path + cmd.Stdout = &outb + cmd.Stderr = &errb + err = cmd.Run() + if err != nil { + log.Print("hg: ", errb.String()) + return err + } + + // copy repository to the tmp_dir build directory + cmd = exec.Command("cp", "-r", repo.Path, path.Join(buildPath, "src")) + err = cmd.Run() + if err != nil { + log.Print("cannot copy the repository to the build dir") + return err + } + + // create build script + file, err := os.Create(path.Join(buildPath, "build.sh")) + if err != nil { + log.Print("cannot create build.sh") + return err + } + defer file.Close() + + err = tpl.Execute(file, repo) + if err != nil { + log.Print("cannot execute build.sh template") + return err + } + file.Chmod(0755) + + // create tarball + cmd = exec.Command("tar", "cvf", "../build.tar", "src", "build.sh") + cmd.Dir = buildPath + if err := cmd.Run(); err != nil { + log.Print("tar error") + return err + } + + cmd = exec.Command("gzip", path.Join(tmp_dir, "build.tar")) + if err := cmd.Run(); err != nil { + log.Print("gzip error") + return err + } + + return nil +} + +func host_is_available(host string) bool { + cmd := exec.Command("ping", "-c", "1", host) + if err := cmd.Run(); err != nil { + return false + } + return true +} + +func exec_buildenvs(config *Config, repo *Repository, tmp_dir string) error { + if len(repo.BuildEnvs) == 0 { + log.Print("repo %s has no buildenvs", repo.Path) + return nil + } + buildenvs := strings.Split(repo.BuildEnvs, ",") + for _, buildenv := range buildenvs { + env, ok := config.BuildEnvMap[buildenv] + if !ok { + return errors.New("unknown build env: " + buildenv) } - - // create build script - file, err := os.Create(path.Join(buildPath, "build.sh")) - if err != nil { - log.Fatal(err) + if !host_is_available(env.Host) { + log.Print("skip unavailable host ", env.Host) + return nil } - err = tpl.Execute(file, repo) - - if err != nil { - log.Fatal(err) - } - file.Close() + // steps: + // upload build.tar.gz + // extract build.tar.gz on the buildenv host and run build.sh - // create tarball - cmd = exec.Command("tar", "cvf", "../build.tar", "src", "build.sh") - cmd.Dir = buildPath + // upload using scp + cmd := exec.Command("scp", path.Join(tmp_dir, "build.tar.gz"), env.Host+":") if err := cmd.Run(); err != nil { - log.Print("tar error") - log.Fatal(err) + log.Print("cannot copy build env to ", env.Host) + continue } - fmt.Println(path.Join(buildPath, "build.tar")) - cmd = exec.Command("gzip", path.Join(tmpDir, "build.tar")) - if err := cmd.Run(); err != nil { - log.Print("gzip error") - log.Fatal(err) + // run build.sh + build_dir := "build" + 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) + log.Printf("host: %s: command: %s", env.Host, remote_command) + + var outb, errb bytes.Buffer + cmd = exec.Command("ssh", "-t", env.Host, remote_command) + cmd.Stdout = &outb + cmd.Stderr = &errb + err := cmd.Run() + log.Printf("host: %s: stdout: %s", env.Host, outb.String()) + if err != nil { + log.Print("cannot execute build.sh on buildenv host ", env.Host) + log.Print("stderr: ", errb.String()) } + } - // TODO: upload build.tar.gz to buildenvs and execute the build.sh script remotely - } + return nil }