From 1514030f0cb9d7c66ee13d63bbf3305c6f2ad62f Mon Sep 17 00:00:00 2001 From: infogulch Date: Wed, 5 Jun 2024 17:34:55 -0500 Subject: [PATCH] Use cue tool as a makefile --- .github/workflows/ci.yaml | 46 +++------ .github/workflows/release.sh | 30 ------ go.work | 5 - make_tool.cue | 182 +++++++++++++++++++++++++++++++++++ test/go.mod | 5 - test/go.sum | 2 - test/test.go | 141 --------------------------- 7 files changed, 195 insertions(+), 216 deletions(-) delete mode 100755 .github/workflows/release.sh delete mode 100644 go.work create mode 100644 make_tool.cue delete mode 100644 test/go.mod delete mode 100644 test/go.sum delete mode 100644 test/test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 53b6881..c818aee 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,19 +14,7 @@ jobs: with: go-version: '1.22' - uses: gacts/install-hurl@v1 - - - name: Build - run: go build -v ./... - - - name: Run Go Tests - run: go test -v ./... - - - name: Run Integration Tests - run: go run . - working-directory: ./test - - - name: Build binaries for all platforms - run: .github/workflows/release.sh + - uses: cue-lang/setup-cue@v1.0.0 - name: Login to Docker Hub uses: docker/login-action@v3 @@ -34,33 +22,25 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Test docker - run: | - set -ex - docker build -t test --target test --build-arg LDFLAGS="${{ env.LDFLAGS }}" .. - docker run --rm --name test -p 8080:80 test & - go run . hurl - docker stop test - working-directory: ./test + - run: cue cmd ci - - name: Build docker and push - uses: docker/build-push-action@v5 + - uses: actions/upload-artifact@v4 + with: + name: xtemplate-amd64-linux + path: 'dist/xtemplate-amd64-linux/*' + + - uses: actions/upload-artifact@v4 with: - context: . - push: true - build-args: | - LDFLAGS=${{ env.LDFLAGS }} - tags: | - infogulch/xtemplate:${{ env.VERSION }} - ${{ startsWith(github.ref, 'refs/tags/v') && 'infogulch/xtemplate:latest' || null }} + name: xtemplate-amd64-darwin + path: 'dist/xtemplate-amd64-darwin/*' - uses: actions/upload-artifact@v4 with: - name: xtemplate-dist - path: 'dist/*' + name: xtemplate-amd64-windows + path: 'dist/xtemplate-amd64-windows/*' - name: Release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v1 with: - files: 'dist/*' + files: 'dist/*.zip' diff --git a/.github/workflows/release.sh b/.github/workflows/release.sh deleted file mode 100755 index 6bfd97a..0000000 --- a/.github/workflows/release.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -ex - -go tool dist list - -rm -rf dist - -GITVER="$(git describe --exact-match --tags --match="v*" 2> /dev/null || git rev-parse --short HEAD)" -VERSION="$(go list -f '{{.Version}}' -m github.com/infogulch/xtemplate@$GITVER)" -LDFLAGS="-X 'github.com/infogulch/xtemplate/app.version=$VERSION'" - -echo "VERSION=$VERSION" >> $GITHUB_ENV -echo "LDFLAGS=$LDFLAGS" >> $GITHUB_ENV - -GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -buildmode exe -o ./dist/xtemplate-amd64-linux/xtemplate ./cmd -GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -buildmode exe -o ./dist/xtemplate-amd64-darwin/xtemplate ./cmd -GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -buildmode exe -o ./dist/xtemplate-amd64-windows/xtemplate.exe ./cmd - -cd dist - -printf '%s\n' * | while read D; do - cp ../README.md ../LICENSE "$D" - tar czvf "${D}_$VERSION.tar.gz" "$D/" - zip -r9 "${D}_$VERSION.zip" "$D/" - rm -r "$D" -done - -cd - - -ls -lh dist/* diff --git a/go.work b/go.work deleted file mode 100644 index e632013..0000000 --- a/go.work +++ /dev/null @@ -1,5 +0,0 @@ -go 1.22.1 - -use . - -use ./test diff --git a/make_tool.cue b/make_tool.cue new file mode 100644 index 0000000..70e1ce0 --- /dev/null +++ b/make_tool.cue @@ -0,0 +1,182 @@ +package xtemplate + +import ( + "tool/exec" + "strings" + "tool/file" + "tool/cli" + "list" +) + +whencuefmtimports: cli.Print + +meta: { + vars: { + rootdir: strings.TrimSpace(_commands.reporoot.stdout) + testdir: "\(rootdir)/test" + gitver: strings.TrimSpace(_commands.gitver.stdout) + version: strings.TrimSpace(_commands.version.stdout) + ldflags: "-X 'github.com/infogulch/xtemplate/app.version=\(version)'" + latest: strings.TrimSpace(_commands.latest.stdout) + } + _commands: { + reporoot: exec.Run & { + cmd: ["bash", "-c", "git rev-parse --show-toplevel; >&2 echo reporoot"] + stdout: string + } + gitver: exec.Run & { + cmd: ["bash", "-c", "git describe --exact-match --tags --match='v*' 2> /dev/null || git rev-parse --short HEAD; >&2 echo gitver"] + dir: vars.rootdir + stdout: string + } + version: exec.Run & { + cmd: ["bash", "-c", "go list -f {{.Version}} -m github.com/infogulch/xtemplate@\(vars.gitver); >&2 echo version"] + dir: vars.rootdir + stdout: string + } + latest: exec.Run & { + cmd: ["bash", "-c", "git tag -l --sort -version:refname | head -n 1; >&2 echo latest"] + dir: vars.rootdir + stdout: string + } + } +} + +command: build: { + cfg: meta + + gobuild: exec.Run & { + OutFile: string | *"xtemplate" + env: {[string]: string} + cmd: ["go", "build", "-ldflags", cfg.vars.ldflags, "-buildmode", "exe", "-o", OutFile, "./cmd"] + dir: cfg.vars.rootdir + success: true + } +} + +command: run: { + cfg: meta + + gobuild: command.build & {gobuild: {OutFile: "\(cfg.vars.testdir)/xtemplate"}} + rmdataw: file.RemoveAll & {path: "\(cfg.vars.testdir)/dataw"} + mkdataw: file.Mkdir & {path: "\(cfg.vars.testdir)/dataw", $dep: rmdataw.$done} + mklog: file.Create & {filename: "\(cfg.vars.testdir)/xtemplate.log", contents: ""} + + ready: exec.Run & { + $dep: mklog.$done + cmd: ["bash", "-c", "grep -q 'starting server' <(tail -f xtemplate.log)"] + dir: cfg.vars.testdir + } + + start: exec.Run & { + $dep: mkdataw.$done && mklog.$done && gobuild.$done + cmd: ["bash", "-c", "./xtemplate --loglevel -4 -d DB:sql:sqlite3:file:./dataw/test.sqlite -d FS:fs:./data --config-file config.json >xtemplate.log 2>&1"] + dir: cfg.vars.testdir + } +} + +command: test: { + cfg: meta + + list: file.Glob & {glob: "\(cfg.vars.testdir)/tests/*.hurl"} + hurl: exec.Run & { + port: int | *8080 + cmd: ["hurl", "--continue-on-error", "--test", "--report-html", "report", "--connect-to", "localhost:8080:localhost:\(port)"] + list.files + dir: cfg.vars.testdir + } +} + +command: gotest: { + cfg: meta + + gotest: exec.Run & { + cmd: "go test -v ./..." + dir: cfg.vars.rootdir + } +} + +command: run_test: { + run: command.run & {start: mustSucceed: false} + + test: command.test & {hurl: {$dep: run.ready.$done}} + + kill: exec.Run & {$dep: test.hurl.$done, cmd: "pkill xtemplate"} // better way? +} + +command: dist: { + cfg: meta + + rmdist: file.RemoveAll & {path: "\(cfg.vars.rootdir)/dist"} + + oses: ["linux", "darwin", "windows"] + arches: ["amd64"] + matrix: [for os in oses for arch in arches {GOOS: os, GOARCH: arch}] + + for env in matrix { + (env.GOOS + "_" + env.GOARCH): { + dir: "\(cfg.vars.rootdir)/dist/xtemplate-\(env.GOARCH)-\(env.GOOS)" + exe: string | *"xtemplate" + if env.GOOS == "windows" { + exe: "xtemplate.exe" + } + + mkdir: file.MkdirAll & {path: dir, $dep: rmdist.$done} + build: command.build.gobuild & {env: env, OutFile: "\(dir)/\(exe)", $dep: mkdir.$done} + cp: exec.Run & {cmd: ["cp", "README.md", "LICENSE", "\(dir)"], $dep: mkdir.$done} + // tar: exec.Run & {cmd: ["tar", "czf", "\(dir)_\(cfg.vars.version).tar.gz", "-C", dir, "."], $dep: cp.$done && build.$done} + zip: exec.Run & {cmd: ["zip", "-jqr6", "\(dir)_\(cfg.vars.version).zip", dir], $dep: cp.$done && build.$done} + // rm: file.RemoveAll & {path: dir, $dep: zip.$done} + } + } + + wait: {$dep: and([for name, step in command.dist if name =~ "_" {step.$done}])} +} + +command: test_docker: { + cfg: meta + + build: exec.Run & { + cmd: ["docker", "build", "-t", "xtemplate-test", "--target", "test", "--build-arg", "LDFLAGS=\(cfg.vars.ldflags)", "."] + dir: cfg.vars.rootdir + } + run: exec.Run & { + cmd: ["bash", "-c", "docker run -d --rm --name xtemplate-test -p 8081:80 xtemplate-test"] + $dep: build.$done + } + ready: exec.Run & { + cmd: ["bash", "-c", "grep -q 'starting server' <(docker logs -f xtemplate-test)"] + $dep: run.$done + } + test: command.test & {hurl: {port: 8081, $dep: ready.$done}} + stop: exec.Run & {cmd: "docker stop xtemplate-test", $dep: test.hurl.$done} // be nice if we can always run this even if previous steps fail +} + +command: build_docker: { + cfg: meta + + tags: [...string] | *["infogulch/xtemplate:\(cfg.vars.version)"] + if cfg.vars.version == cfg.vars.latest { + tags: ["infogulch/xtemplate:\(cfg.vars.version)", "infogulch/xtemplate:latest"] + } + + build: exec.Run & { + cmd: ["docker", "build"] + list.FlattenN([for t in tags {["-t", t]}], 1) + ["--build-arg", "LDFLAGS=\(cfg.vars.ldflags)", "."] + dir: cfg.vars.rootdir + } +} + +command: ci: { + gotest: command.gotest + run_test: command.run_test + + dist: command.dist & {rmdist: $dep: run_test.kill.$done} + + test_docker: command.test_docker + + build_docker: command.build_docker & {build: $dep: test_docker.stop.$done} + + push: exec.Run & { + cmd: ["docker", "push"] + build_docker.tags + $dep: build_docker.build.$done + } +} diff --git a/test/go.mod b/test/go.mod deleted file mode 100644 index ce1ef80..0000000 --- a/test/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/infogulch/xtemplate/test - -go 1.22.1 - -require github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601 diff --git a/test/go.sum b/test/go.sum deleted file mode 100644 index 11b0a3b..0000000 --- a/test/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601 h1:qip+zkEa+Gwd/M0r6/mLk3Y4tcBCnzVhF1pNK8IzlQM= -github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601/go.mod h1:t4xAP6TrFpgvp/U7szp27rYrXXVu9yn3WhNylBvnfb8= diff --git a/test/test.go b/test/test.go deleted file mode 100644 index c8a3dea..0000000 --- a/test/test.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "fmt" - "io/fs" - "os" - "os/exec" - "os/signal" - "path/filepath" - "runtime" - "strings" - "sync" - "syscall" - "time" - - "github.com/Hellseher/go-shellquote" -) - -func main() { - _, file, _, _ := runtime.Caller(0) - testdir := filepath.Dir(file) - logpath := filepath.Join(testdir, "xtemplate.log") - log := try(os.Create(logpath))("open log file") - try(log.Seek(0, 0))("seek to beginning") - defer log.Close() - - // recreate the rw directory - { - path := filepath.Join(testdir, "dataw") - try0(os.RemoveAll(path), "delete dataw dir") - try0(os.Mkdir(path, os.ModeDir|os.ModePerm), "create dataw dir") - } - - defer func() { - if err := recover(); err != nil { - fmt.Printf("exiting because: %v\n", err) - fmt.Printf("server logs: %s\n", logpath) - } - }() - - command := "" - if len(os.Args) > 1 { - command = os.Args[1] - } - if command == "hurl" { - goto hurl - } - - // Build xtemplate - { - args := split(`go build -o xtemplate ../cmd`) - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - cmd.Dir = testdir - try0(cmd.Run(), "go build") - fmt.Println("~ Build ~") - } - - // Run xtemplate, wait until its ready, exit test if it fails early - { - args := split(`./xtemplate --loglevel -4 -d DB:sql:sqlite3:file:./dataw/test.sqlite -d FS:fs:./data --config-file config.json`) - cmd := exec.Command(args[0], args[1:]...) - cmd.Dir = testdir - - cmd.Stdout = log - cmd.Stderr = log - - try0(cmd.Start(), "start xtemplate") - defer kill(cmd) - - go func() { - try0(cmd.Wait(), "wait for xtemplate") - time.Sleep(time.Second) - panic("xtemplate exited") - }() - - waitUntilFileContainsString(logpath, "starting server") - - fmt.Println("~ Run xtemplate ~") - } - -hurl: - { - dir := filepath.Join(testdir, "tests") - files := try(fs.Glob(os.DirFS(dir), "*.hurl"))("glob files") - args := append(split("hurl --continue-on-error --test --report-html report"), files...) - cmd := exec.Command(args[0], args[1:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = dir - defer kill(cmd) - try0(cmd.Run(), "run hurl") - fmt.Println("~ Run hurl ~") - } - - if command == "start" { - // block until ^C or xtemplate exits - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - fmt.Println("waiting for signal") - <-sigs - } -} - -func split(a string) []string { return try(shellquote.Split(a))("split args") } - -func kill(c *exec.Cmd) { - err := c.Process.Kill() - if err != nil && err != os.ErrProcessDone { - panic(fmt.Sprintf("failed to kill %s: %v", c.Path, err)) - } -} - -func try[T any](t T, err error) func(string) T { - return func(desc string) T { - try0(err, desc) - return t - } -} - -func try0(err error, desc string) { - if err != nil { - panic(fmt.Sprintf("failed to %s: %v\n", desc, err)) - } -} - -func waitUntilFileContainsString(filename string, needle string) { - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - for { - if strings.Contains(string(try(os.ReadFile(filename))("read file")), needle) { - wg.Done() - break - } - time.Sleep(10 * time.Millisecond) - } - }() - wg.Wait() -}