From 32d5fd7edf582509bf6f2546c260c226f677f8f9 Mon Sep 17 00:00:00 2001 From: Will Roden Date: Fri, 24 Nov 2023 13:32:34 -0600 Subject: [PATCH] add bootstrap command --- .github/workflows/ci.yml | 2 +- README.md | 1 + cmd/bindown/bootstrap.go | 51 +++++++++++++ cmd/bindown/bootstrap_test.go | 29 +++++++ cmd/bindown/cli.go | 2 + docs/clihelp.txt | 1 + .../{want.txt => bootstrap-bindown.sh} | 70 +++++++++++------ .../testdata/build-bootstrapper/checksums.txt | 22 ++++++ internal/build-bootstrapper/build.go | 76 +++++++++---------- internal/build-bootstrapper/build_test.go | 33 -------- script/lint | 4 +- 11 files changed, 189 insertions(+), 102 deletions(-) create mode 100644 cmd/bindown/bootstrap.go create mode 100644 cmd/bindown/bootstrap_test.go rename internal/bindown/testdata/build-bootstrapper/{want.txt => bootstrap-bindown.sh} (72%) create mode 100644 internal/bindown/testdata/build-bootstrapper/checksums.txt delete mode 100644 internal/build-bootstrapper/build_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40bcc579..8dc6c58a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,5 +116,5 @@ jobs: GH_TOKEN: ${{ github.token }} run: | script/bindown -q install gh - go run ./internal/build-bootstrapper -tag "$RELEASE_TAG" > bootstrap-bindown.sh + script/bindown bootstrap --tag "$RELEASE_TAG" --output bootstrap-bindown.sh bin/gh release upload "$RELEASE_TAG" bootstrap-bindown.sh diff --git a/README.md b/README.md index a88819bc..459905fb 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,7 @@ Commands: checksums init create an empty config file cache clear clear the cache + bootstrap create bootstrap script for bindown version show bindown version install-completions install shell completions diff --git a/cmd/bindown/bootstrap.go b/cmd/bindown/bootstrap.go new file mode 100644 index 00000000..639a6e37 --- /dev/null +++ b/cmd/bindown/bootstrap.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + bootstrapper "github.com/willabides/bindown/v4/internal/build-bootstrapper" +) + +func defaultBootstrapTag() string { + if Version == "unknown" { + return "" + } + return "v" + Version +} + +type bootstrapCmd struct { + Tag string `kong:"hidden,default=${bootstrap_tag_default}"` + BaseURL string `kong:"hidden,name='base-url',default='https://github.com'"` + Output string `kong:"help='output file, writes to stdout if not set',type='path'"` +} + +func (c *bootstrapCmd) Run(ctx *runContext) error { + if c.Tag == "" { + return fmt.Errorf("version is required") + } + tag := c.Tag + if !strings.HasPrefix(tag, "v") { + tag = "v" + tag + } + opts := bootstrapper.BuildOpts{BaseURL: c.BaseURL} + content, err := bootstrapper.Build(tag, &opts) + if err != nil { + return err + } + if c.Output == "" { + fmt.Fprint(ctx.stdout, content) + return nil + } + err = os.MkdirAll(filepath.Dir(c.Output), 0o755) + if err != nil { + return err + } + err = os.WriteFile(c.Output, []byte(content), 0o755) + if err != nil { + return err + } + return nil +} diff --git a/cmd/bindown/bootstrap_test.go b/cmd/bindown/bootstrap_test.go new file mode 100644 index 00000000..bcaec79b --- /dev/null +++ b/cmd/bindown/bootstrap_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/willabides/bindown/v4/internal/testutil" +) + +func Test_bootstrapCmd(t *testing.T) { + output := filepath.Join(t.TempDir(), "foo", "bootstrap.sh") + runner := newCmdRunner(t) + + server := testutil.ServeFile( + t, + testdataPath("build-bootstrapper/checksums.txt"), + "/WillAbides/bindown/releases/download/v4.8.0/checksums.txt", + "", + ) + want, err := os.ReadFile(testdataPath("build-bootstrapper/bootstrap-bindown.sh")) + require.NoError(t, err) + result := runner.run("bootstrap", "--output", output, "--tag", "4.8.0", "--base-url", server.URL) + result.assertState(resultState{}) + got, err := os.ReadFile(output) + require.NoError(t, err) + require.Equal(t, string(want), string(got)) +} diff --git a/cmd/bindown/cli.go b/cmd/bindown/cli.go index e6f6bc5a..26e0a86f 100644 --- a/cmd/bindown/cli.go +++ b/cmd/bindown/cli.go @@ -40,6 +40,7 @@ var kongVars = kong.Vars{ "install_to_cache_help": `install to cache instead of install dir`, "install_wrapper_help": `install a wrapper script instead of the binary`, "install_bindown_help": `path to bindown executable to use in wrapper`, + "bootstrap_tag_default": defaultBootstrapTag(), } type rootCmd struct { @@ -59,6 +60,7 @@ type rootCmd struct { Checksums checksumsCmd `kong:"cmd,help='manage checksums'"` Init initCmd `kong:"cmd,help='create an empty config file'"` Cache cacheCmd `kong:"cmd,help='manage the cache'"` + Bootstrap bootstrapCmd `kong:"cmd,help='create bootstrap script for bindown'"` Version versionCmd `kong:"cmd,help='show bindown version'"` InstallCompletions kongplete.InstallCompletions `kong:"cmd,help=${config_install_completions_help}"` diff --git a/docs/clihelp.txt b/docs/clihelp.txt index 186dc686..d889b773 100644 --- a/docs/clihelp.txt +++ b/docs/clihelp.txt @@ -39,6 +39,7 @@ Commands: checksums init create an empty config file cache clear clear the cache + bootstrap create bootstrap script for bindown version show bindown version install-completions install shell completions diff --git a/internal/bindown/testdata/build-bootstrapper/want.txt b/internal/bindown/testdata/build-bootstrapper/bootstrap-bindown.sh similarity index 72% rename from internal/bindown/testdata/build-bootstrapper/want.txt rename to internal/bindown/testdata/build-bootstrapper/bootstrap-bindown.sh index 11be4e21..2e8cbaee 100644 --- a/internal/bindown/testdata/build-bootstrapper/want.txt +++ b/internal/bindown/testdata/build-bootstrapper/bootstrap-bindown.sh @@ -2,25 +2,31 @@ set -e -TAG="v3.15.5" +TAG="v4.8.0" CHECKSUMS=" -00e936e28e816ac4f05e3f9e47aad3771df3a847fcbea3f3bbe33159508b478d bindown_3.15.5_darwin_arm64 -07568b436622d3a28cbd4e9a81c6b619e4e3d9533e8f65184f0e9cf3f82df620 bindown_3.15.5_linux_arm64.tar.gz -0c06832f8a795e99dfebe550482cf790c9a7cca304635a8393dfba0eb7c1c872 bindown_3.15.5_windows_arm64.tar.gz -1727fb1a696e46432ac6110f73524e5ae87d0a136e2c649049e3886db9260331 bindown_3.15.5_windows_amd64.exe -25a9b2bf3e7ae99935cd7d93034a5166192120d9e8a572f4b38a124baa62e274 bindown_3.15.5_windows_arm64.exe -43c099b123585df691a0018ebd63def5c2876f64fe6fe2e736792e359ac489b9 bindown_3.15.5_linux_amd64 -59266496ab2410582726d11bb8204cc11ba69e0333d9d6b016f912d19ab30885 bindown_3.15.5_darwin_amd64 -6334cf81db488a4ac217c1799d165a6a96dd74a76cc08e080487fbb32d15fdf6 bindown_3.15.5_linux_amd64.tar.gz -72cdb65fb8361c0bc9cb85fd7768e75cb831f3527365ae348e62bba9ca44d6c6 bindown_3.15.5_linux_arm64 -87d9f9b81010e83ca16115634503071673f3d898e073326524e795e50b37e690 bindown_3.15.5_windows_386.exe -90a6d0fb9fd59004aa80dc0738773b59e4d4c3ed7428996695583c1eee5cc18b bindown_3.15.5_windows_386.tar.gz -b1099c0f3558ccc70a06d5c33b2172b3cc911a13b58388e7d6573c7ca01471be bindown_3.15.5_windows_amd64.tar.gz -c4a5f4f80c5729bf56b9448798ae3aebb61104bf7bb46f6b04adaf0c650cd4e2 bindown_3.15.5_darwin_amd64.tar.gz -cdb3c98fa5bee90e6c5c3e3e8f2214513f1a7bad1ade2663180ac1cce3ef0701 bindown_3.15.5_darwin_arm64.tar.gz -d3b824e6b1bab234ac76a00e5d073fca647e917efd05df3fe620e9771a74d037 bindown_3.15.5_linux_386.tar.gz -e2a62bfb094bd973d9e215f520effbab236ef7acdc9b5d592be688f624a30454 bindown_3.15.5_linux_386 +26fcbc738bf9bb910b070f236816b2dfe5bc9589be3a578135f37d950ebaf771 bindown_4.8.0_freebsd_amd64.tar.gz +2fa6460ebe8d7c6be33576acf5b63f7208780af72d758e82313f6c5772e097d5 bindown_4.8.0_linux_386.tar.gz +32e3fbfaecf41a1b2bced22c1842b3905f4e6de1e879a4db68402799c206415d bindown_4.8.0_windows_386.exe +335802ed91fa6f040e10570479a6c817c7e42bd57fe98c959890a821099d3e1f bindown_4.8.0_freebsd_arm64 +372846f7edd9d93df0cb17889790f595f17cb083e093f3e6437d34e3253fd378 bindown_4.8.0_windows_amd64.exe +40acf94b7c69e5d4101cb46ea99641d302ff23579cd7ead29a5abfceb1a5d9ba bindown_4.8.0_linux_arm64.tar.gz +66aca230d9aea549ecd3647082b63752f5bb5307ef6954a08cc0eaf9c70723f1 bindown_4.8.0_windows_amd64.tar.gz +752c78a926be1287312eea3c831e841e223de4780d1e4a8a813432d0a73f890b bindown_4.8.0_linux_amd64.tar.gz +7f1f1c883beceb6ec3919484511fb20c3ceb41088e814d6fc234b015e98b78d9 bindown_4.8.0_darwin_arm64 +7fdfbc007c0c285a498bf251bd4ab7469f334752581b45fda5ad6450ddd23377 bindown_4.8.0_windows_arm64.exe +95764bf76b54d5b13b9b8a801635d82447ee349c3545145ddd8a0a84246d66e2 bindown_4.8.0_freebsd_arm64.tar.gz +966087f13a6cf82804456119289ab982f2eee3ad04d8d4fb6ce74bd7eabdf74e bindown_4.8.0_windows_386.tar.gz +9b29e37ba273bc0dca9c8227ee4b58153289073ede7d900e9c84ae3c71f3dff5 bindown_4.8.0_windows_arm64.tar.gz +a625900e52f4413bee3863062463cc24f9c0669841fd6bc9979ee599edd88f3e bindown_4.8.0_freebsd_amd64 +ba09df557edc4499f41ddadc26369d7f70ed20bfb8310662f1290e6a355343e8 bindown_4.8.0_darwin_amd64.tar.gz +cd7b917d2737fe9fa087aea172d9b581757e9b300fa1d1dbd83c1b765be05bdb bindown_4.8.0_freebsd_386.tar.gz +d5d35274d4eab337c107940fc5b326c51f5bfd70d00924c79011684e2a0d4f22 bindown_4.8.0_freebsd_386 +d71d6c436ad33bb3aa01468698b86d5423127a19f9b1c664e346cc502501d415 bindown_4.8.0_darwin_arm64.tar.gz +d9361698bc1571c34915496da9c624e89fa12d87731711efd2cbbc9136c6fa85 bindown_4.8.0_darwin_amd64 +d93eae8638b96682d0e9b55bcbe92fecb296afd442e0526cc94ce0160c108c13 bindown_4.8.0_linux_arm64 +ec3d19abd00fbf099a98edb64c569842fa5b909222fb10da86d668f5597885be bindown_4.8.0_linux_amd64 +fa7e87f49aa30e42485431bd9dd021a32924ab11e4d39065533e9bccce182de4 bindown_4.8.0_linux_386 " cat /dev/null << EOF @@ -242,8 +248,6 @@ usage() { this=$1 cat << EOT Usage: $this [-b bindir] [-d] - -Usage: $this [-b] bindir [-d] -b sets bindir or installation directory, Defaults to ./bin -d turns on debug logging @@ -267,6 +271,14 @@ parse_args() { shift $((OPTIND - 1)) } +bindown_name() { + if [ "$OS" = "windows" ]; then + echo bindown.exe + else + echo bindown + fi +} + execute() { tmpdir=$(mktemp -d) echo "$CHECKSUMS" > "${tmpdir}/checksums.txt" @@ -276,15 +288,17 @@ execute() { srcdir="${tmpdir}" (cd "${tmpdir}" && untar "${TARBALL}") test ! -d "${BINDIR}" && install -d "${BINDIR}" - binexe="bindown" - if [ "$OS" = "windows" ]; then - binexe="${binexe}.exe" - fi - install "${srcdir}/${binexe}" "${BINDIR}/" - log_info "installed ${BINDIR}/${binexe}" + install "${srcdir}/$(bindown_name)" "${BINDIR}/" + log_info "installed ${BINDIR}/$(bindown_name)" rm -rf "${tmpdir}" } +already_installed() { + VERSION="$1" + [ -f "${BINDIR}/$(bindown_name)" ] && + "${BINDIR}/$(bindown_name)" version 2> /dev/null | grep -q "$VERSION" +} + OS=$(uname_os) ARCH=$(uname_arch) @@ -294,6 +308,12 @@ uname_arch_check "$ARCH" parse_args "$@" VERSION=${TAG#v} + +if already_installed "$VERSION"; then + log_debug "bindown ${VERSION} is already installed" + exit 0 +fi + NAME=bindown_${VERSION}_${OS}_${ARCH} TARBALL=${NAME}.${FORMAT} TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL} diff --git a/internal/bindown/testdata/build-bootstrapper/checksums.txt b/internal/bindown/testdata/build-bootstrapper/checksums.txt new file mode 100644 index 00000000..4fee2684 --- /dev/null +++ b/internal/bindown/testdata/build-bootstrapper/checksums.txt @@ -0,0 +1,22 @@ +26fcbc738bf9bb910b070f236816b2dfe5bc9589be3a578135f37d950ebaf771 bindown_4.8.0_freebsd_amd64.tar.gz +2fa6460ebe8d7c6be33576acf5b63f7208780af72d758e82313f6c5772e097d5 bindown_4.8.0_linux_386.tar.gz +32e3fbfaecf41a1b2bced22c1842b3905f4e6de1e879a4db68402799c206415d bindown_4.8.0_windows_386.exe +335802ed91fa6f040e10570479a6c817c7e42bd57fe98c959890a821099d3e1f bindown_4.8.0_freebsd_arm64 +372846f7edd9d93df0cb17889790f595f17cb083e093f3e6437d34e3253fd378 bindown_4.8.0_windows_amd64.exe +40acf94b7c69e5d4101cb46ea99641d302ff23579cd7ead29a5abfceb1a5d9ba bindown_4.8.0_linux_arm64.tar.gz +66aca230d9aea549ecd3647082b63752f5bb5307ef6954a08cc0eaf9c70723f1 bindown_4.8.0_windows_amd64.tar.gz +752c78a926be1287312eea3c831e841e223de4780d1e4a8a813432d0a73f890b bindown_4.8.0_linux_amd64.tar.gz +7f1f1c883beceb6ec3919484511fb20c3ceb41088e814d6fc234b015e98b78d9 bindown_4.8.0_darwin_arm64 +7fdfbc007c0c285a498bf251bd4ab7469f334752581b45fda5ad6450ddd23377 bindown_4.8.0_windows_arm64.exe +95764bf76b54d5b13b9b8a801635d82447ee349c3545145ddd8a0a84246d66e2 bindown_4.8.0_freebsd_arm64.tar.gz +966087f13a6cf82804456119289ab982f2eee3ad04d8d4fb6ce74bd7eabdf74e bindown_4.8.0_windows_386.tar.gz +9b29e37ba273bc0dca9c8227ee4b58153289073ede7d900e9c84ae3c71f3dff5 bindown_4.8.0_windows_arm64.tar.gz +a625900e52f4413bee3863062463cc24f9c0669841fd6bc9979ee599edd88f3e bindown_4.8.0_freebsd_amd64 +ba09df557edc4499f41ddadc26369d7f70ed20bfb8310662f1290e6a355343e8 bindown_4.8.0_darwin_amd64.tar.gz +cd7b917d2737fe9fa087aea172d9b581757e9b300fa1d1dbd83c1b765be05bdb bindown_4.8.0_freebsd_386.tar.gz +d5d35274d4eab337c107940fc5b326c51f5bfd70d00924c79011684e2a0d4f22 bindown_4.8.0_freebsd_386 +d71d6c436ad33bb3aa01468698b86d5423127a19f9b1c664e346cc502501d415 bindown_4.8.0_darwin_arm64.tar.gz +d9361698bc1571c34915496da9c624e89fa12d87731711efd2cbbc9136c6fa85 bindown_4.8.0_darwin_amd64 +d93eae8638b96682d0e9b55bcbe92fecb296afd442e0526cc94ce0160c108c13 bindown_4.8.0_linux_arm64 +ec3d19abd00fbf099a98edb64c569842fa5b909222fb10da86d668f5597885be bindown_4.8.0_linux_amd64 +fa7e87f49aa30e42485431bd9dd021a32924ab11e4d39065533e9bccce182de4 bindown_4.8.0_linux_386 diff --git a/internal/build-bootstrapper/build.go b/internal/build-bootstrapper/build.go index 7e1f18ce..a853cafb 100644 --- a/internal/build-bootstrapper/build.go +++ b/internal/build-bootstrapper/build.go @@ -1,14 +1,13 @@ -package main +package bootstrapper import ( "bytes" "embed" - "flag" + "errors" "fmt" "io" "net/http" - "os/exec" - "path/filepath" + "runtime" "strings" "text/template" ) @@ -16,27 +15,28 @@ import ( //go:embed assets/* var assets embed.FS -func build(tag, repoRoot string) (_ string, errOut error) { - bindownCmd := exec.Command("script/bindown", "install", "shfmt", "shellcheck") - bindownCmd.Dir = repoRoot - err := bindownCmd.Run() - if err != nil { - return "", err +type BuildOpts struct { + BaseURL string // defaults to https://github.com +} + +// Build builds a bootstrapper for the given tag +func Build(tag string, opts *BuildOpts) (_ string, errOut error) { + if opts == nil { + opts = &BuildOpts{} + } + baseURL := opts.BaseURL + if baseURL == "" { + baseURL = "https://github.com" } checksumsURL := fmt.Sprintf( - `https://github.com/WillAbides/bindown/releases/download/%s/checksums.txt`, - tag, + `%s/WillAbides/bindown/releases/download/%s/checksums.txt`, + baseURL, tag, ) resp, err := http.Get(checksumsURL) if err != nil { return "", err } - defer func() { - err = resp.Body.Close() - if errOut == nil { - errOut = err - } - }() + defer func() { errOut = errors.Join(errOut, resp.Body.Close()) }() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("got status %d from %s", resp.StatusCode, checksumsURL) } @@ -70,32 +70,24 @@ func build(tag, repoRoot string) (_ string, errOut error) { if err != nil { return "", err } - shfmtCmd := exec.Command(filepath.Join(repoRoot, "bin", "shfmt"), "-i", "2", "-ci", "-sr", "-") - shfmtCmd.Stdin = &tmplOut - formatted, err := shfmtCmd.Output() - if err != nil { - return "", err + out := strings.TrimSpace(tmplOut.String()) + "\n" + if runtime.GOOS == "windows" { + out = windowsLineEndings(out) } - shellcheckCmd := exec.Command(filepath.Join(repoRoot, "bin", "shellcheck"), "--shell", "sh", "-") - shellcheckCmd.Stdin = bytes.NewReader(formatted) - err = shellcheckCmd.Run() - if err != nil { - return "", err - } - return string(formatted), nil + return out, nil } -func main() { - var tag, repoRoot string - flag.StringVar(&tag, "tag", "", "tag to build") - flag.StringVar(&repoRoot, "repo-root", ".", "path to bindown repo root") - flag.Parse() - if tag == "" { - panic("tag is required") - } - got, err := build(tag, repoRoot) - if err != nil { - panic(err) +func windowsLineEndings(in string) string { + buf := bytes.NewBuffer(make([]byte, 0, len(in))) + for i := 0; i < len(in); i++ { + if in[i] != '\n' { + buf.WriteByte(in[i]) + continue + } + if i == 0 || in[i-1] != '\r' { + buf.WriteByte('\r') + } + buf.WriteByte('\n') } - fmt.Println(got) + return buf.String() } diff --git a/internal/build-bootstrapper/build_test.go b/internal/build-bootstrapper/build_test.go deleted file mode 100644 index 47be4573..00000000 --- a/internal/build-bootstrapper/build_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBuild(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - // This doesn't work on windows, and for now I don't care because it is only used in CI - if runtime.GOOS == "windows" { - t.Skip("skipping on windows") - } - origConfig, err := os.ReadFile(filepath.FromSlash("../../bindown.yml")) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError( - t, - os.WriteFile(filepath.FromSlash("../../bindown.yml"), origConfig, 0o644), - ) - }) - got, err := build("v4.0.0", filepath.FromSlash("../../")) - require.NoError(t, err) - want, err := os.ReadFile(filepath.FromSlash("testdata/want.txt")) - require.NoError(t, err) - require.Equal(t, string(want), got) -} diff --git a/script/lint b/script/lint index 16106418..ab934764 100755 --- a/script/lint +++ b/script/lint @@ -4,9 +4,11 @@ set -e CDPATH="" cd -- "$(dirname -- "$0")/.." -script/bindown -q install golangci-lint shellcheck +script/bindown -q install golangci-lint shellcheck shfmt bin/golangci-lint run # Don't check bootstrap-bindown.sh because it's dynamically generated find script -type f -not -name 'bootstrap-bindown.sh' -print0 | xargs -0 bin/shellcheck bin/shellcheck -s sh internal/build-bootstrapper/assets/*.sh +bin/shellcheck -s sh internal/bindown/testdata/build-bootstrapper/bootstrap-bindown.sh +bin/shfmt -i 2 -ci -sr --diff internal/bindown/testdata/build-bootstrapper/bootstrap-bindown.sh