From d3a2d433564c013409fa9da9f76fd17a1d392a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Fri, 10 Mar 2023 10:45:02 +0200 Subject: [PATCH] releaser This is a work in progress. Next steps: 1. Add instructions to the wiki. 2. Try the new tarball on a real repository. 3. Cut the actual release. Test output for an upcoming `v1.0.2`: $ bazel run //tools/releaser -- -skip_upgrades=true -tag v1.0.2 INFO: Analyzed target //tools/releaser:releaser (1 packages loaded, 29 targets configured). INFO: Found 1 target... Target //tools/releaser:releaser up-to-date: bazel-bin/tools/releaser/releaser_/releaser INFO: Elapsed time: 1.978s, Critical Path: 1.81s INFO: 3 processes: 1 internal, 2 linux-sandbox. INFO: Build completed successfully, 3 total actions INFO: Running command line: bazel-bin/tools/releaser/releaser_/releaser '-skip_upgrades=true' -tag v1.0.2 Running pre-release checks: - SKIPPING: go update commands - gazelle - checking if repository is clean Creating tag v1.0.2 Creating archive bazel-zig-cc-v1.0.2.tar Compressing bazel-zig-cc-v1.0.2.tar Written /code/bazel-zig-cc/bazel-zig-cc-v1.0.2.tar.gz Release boilerplate: ----- load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "bazel-zig-cc", sha256 = "b0e857f8b32062a112305931437c5a7e1762287e27379c6d2d7173f0fa74e270", urls = [ "https://mirror.bazel.build/github.com/uber/bazel-zig-cc/releases/download/v1.0.2/bazel-zig-cc-v1.0.2.tar.gz", "https://github.com/uber/bazel-zig-cc/releases/download/v1.0.2/bazel-zig-cc-v1.0.2.tar.gz", ], ) load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") # Argument-free will pick reasonable defaults. zig_toolchains() # version, url_formats and host_platform_sha256 are can be set for those who # wish to control their Zig SDK version and where it is downloaded from zig_toolchains( version = "<...>", url_formats = [ "https://example.org/zig/zig-{host_platform}-{version}.{_ext}", ], host_platform_sha256 = { ... }, ) --- BUILD | 1 + WORKSPACE | 10 ++ third_party/BUILD.zopfli | 20 ++++ tools/mod-tidy | 2 +- tools/releaser/BUILD | 28 ++++++ tools/releaser/main.go | 186 ++++++++++++++++++++++++++++++++++++ tools/releaser/main_test.go | 36 +++++++ tools/releaser/zopfli.go | 10 ++ 8 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 third_party/BUILD.zopfli create mode 100644 tools/releaser/BUILD create mode 100644 tools/releaser/main.go create mode 100644 tools/releaser/main_test.go create mode 100644 tools/releaser/zopfli.go diff --git a/BUILD b/BUILD index dcedea4..7180402 100644 --- a/BUILD +++ b/BUILD @@ -8,6 +8,7 @@ load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:build_file_name BUILD # gazelle:prefix github.com/uber/bazel-zig-cc # gazelle:exclude tools.go +# gazelle:exclude tools/releaser/zopfli.go gazelle(name = "gazelle") diff --git a/WORKSPACE b/WORKSPACE index e1b4454..b8ad0b4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -82,3 +82,13 @@ http_archive( load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps() + +ZOPFLI_COMMIT = "831773bc28e318b91a3255fa12c9fcde1606058b" + +http_archive( + name = "zopfli", + build_file = "//:third_party/BUILD.zopfli", + sha256 = "dbc695ae30c815973dc38b39e2f95dc2b249263b7222427d96e5e785092b0f78", + strip_prefix = "zopfli-{}".format(ZOPFLI_COMMIT), + urls = ["https://github.com/google/zopfli/archive/{}.tar.gz".format(ZOPFLI_COMMIT)], +) diff --git a/third_party/BUILD.zopfli b/third_party/BUILD.zopfli new file mode 100644 index 0000000..152c3dc --- /dev/null +++ b/third_party/BUILD.zopfli @@ -0,0 +1,20 @@ +# Copyright 2023 Uber Technologies, Inc. +# Licensed under the Apache License, Version 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_zopfli", + srcs = glob( + [ + "go/zopfli/zopfli.go", + "src/zopfli/*.c", + "src/zopfli/*.h", + ], + exclude = ["src/zopfli/zopfli_bin.c"], + ), + cgo = True, + copts = ["-O2"], + importpath = "github.com/google/zopfli/go/zopfli", + visibility = ["//visibility:public"], +) diff --git a/tools/mod-tidy b/tools/mod-tidy index f701f25..063faa8 100755 --- a/tools/mod-tidy +++ b/tools/mod-tidy @@ -6,7 +6,7 @@ set -xeu cd "$(git rev-parse --show-toplevel)" echo "--- go mod tidy" -tools/bazel run @go_sdk//:bin/go -- mod tidy +tools/bazel run @go_sdk//:bin/go -- mod tidy "$@" echo "--- gazelle-update-repos" exec tools/bazel run //:gazelle-update-repos diff --git a/tools/releaser/BUILD b/tools/releaser/BUILD new file mode 100644 index 0000000..05419f6 --- /dev/null +++ b/tools/releaser/BUILD @@ -0,0 +1,28 @@ +# Copyright 2023 Uber Technologies, Inc. +# Licensed under the Apache License, Version 2.0 + +load("//rules:rules_go.bzl", "go_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "releaser_lib", + srcs = [ + "main.go", + "zopfli.go", # keep + ], + importpath = "github.com/uber/bazel-zig-cc/tools/releaser", + visibility = ["//visibility:private"], + deps = ["@zopfli//:go_zopfli"], # keep +) + +go_binary( + name = "releaser", + embed = [":releaser_lib"], + visibility = ["//visibility:public"], +) + +go_test( + name = "releaser_test", + srcs = ["main_test.go"], + embed = [":releaser_lib"], +) diff --git a/tools/releaser/main.go b/tools/releaser/main.go new file mode 100644 index 0000000..89289e2 --- /dev/null +++ b/tools/releaser/main.go @@ -0,0 +1,186 @@ +// Copyright 2023 Uber Technologies, Inc. +// Licensed under the Apache License, Version 2.0 + +// releaser is a tool for managing part of the process to release a new version of bazel-zig-cc. +package main + +import ( + "crypto/sha256" + "errors" + "flag" + "fmt" + "os" + "os/exec" + "path" + "regexp" + "strconv" + "strings" + +) + +var ( + // Paths to be included to the release + _paths = []string{ + "LICENSE", + "NOTICE", + "README.md", + "toolchain/*", + } + + // regexp for valid tags + tagRegexp = regexp.MustCompile(`^v([0-9]+)\\.([0-9]+)(\\.([0-9]+))(-rc([0-9]+))?$`) + + errTag = errors.New("tag accepts the following formats: v1.0.0 v1.0.1-rc1") +) + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func log(msg string, format ...any) { + fmt.Fprintf(flag.CommandLine.Output(), msg+"\n", format...) +} + +func run() error { + var ( + goVersion string + repoRoot string + skipUpgrades bool + tag string + ) + + flag.StringVar(&goVersion, "go_version", "", "go version for go.mod") + flag.StringVar(&repoRoot, "repo_root", os.Getenv("BUILD_WORKSPACE_DIRECTORY"), "root directory of bazel-zig-cc repo") + flag.StringVar(&tag, "tag", "", "tag for this release") + flag.BoolVar(&skipUpgrades, "skip_upgrades", false, "skip upgrade checks (testing only)") + + flag.Usage = func() { + fmt.Fprint(flag.CommandLine.Output(), `usage: bazel run //tools/releaser -- -go_version -tag + +This utility is intended to handle many of the steps to release a new version. + +`) + flag.PrintDefaults() + } + + flag.Parse() + + if tag == "" { + return fmt.Errorf("ERROR: tag is required") + } + + if !tagRegexp.MatchString(tag) { + return errTag + } + + var goVersionArgs []string + if goVersion != "" { + versionParts := strings.Split(goVersion, ".") + if len(versionParts) < 2 { + flag.Usage() + return errors.New("please provide a valid Go version") + } + if minorVersion, err := strconv.Atoi(versionParts[1]); err != nil { + return fmt.Errorf("%q is not a valid Go version", goVersion) + } else if minorVersion > 0 { + versionParts[1] = strconv.Itoa(minorVersion - 1) + } + goVersionArgs = append(goVersionArgs, "-go", goVersion, "-compat", strings.Join(versionParts, ".")) + } + + // external dependency checks + depChecks := [][]string{ + {"go", "get", "-t", "-u", "./..."}, + append([]string{"tools/mod-tidy"}, goVersionArgs...), + } + + // commands that Must Not Fail + cmds := [][]string{ + {"tools/bazel", "run", "//:gazelle"}, + {"git", "diff", "--stat", "--exit-code"}, + {"git", "tag", tag}, + } + + log("Cutting a release:") + if skipUpgrades { + log("SKIPPING: go update commands") + } else { + cmds = append(depChecks, cmds...) + } + + for _, c := range cmds { + cmd := exec.Command(c[0], c[1:]...) + cmd.Dir = repoRoot + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf( + "ERROR: running %s:%w\n%s", + strings.Join(c, " "), + err, + out, + ) + } + } + + log("Creating archive bazel-zig-cc-%s.tar", tag) + + cmd := exec.Command( + "git", + append([]string{"archive", "--format=tar", tag}, _paths...)..., + ) + cmd.Dir = repoRoot + + out, err := cmd.Output() + if err != nil { + var exitError *exec.ExitError + errors.As(err, &exitError) + return fmt.Errorf("ERROR: failed to create git archive: %w\n%s", err, exitError.Stderr) + } + + log("Compressing bazel-zig-cc-%s.tar", tag) + + tgz := Gzip(out) + + fpath := path.Join(repoRoot, fmt.Sprintf("bazel-zig-cc-%s.tar.gz", tag)) + if err := os.WriteFile(fpath, tgz, 0o644); err != nil { + return fmt.Errorf("ERROR: write %q: %w", fpath, err) + } + + log("Wrote %s", fpath) + + shasum := sha256.Sum256(tgz) + + log("Release boilerplate:\n-----\n" + genBoilerplate(tag, fmt.Sprintf("%x", shasum))) + + return nil +} + +func genBoilerplate(version, shasum string) string { + return fmt.Sprintf(`load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "bazel-zig-cc", + sha256 = "%[2]s", + urls = [ + "https://mirror.bazel.build/github.com/uber/bazel-zig-cc/releases/download/%[1]s/bazel-zig-cc-%[1]s.tar.gz", + "https://github.com/uber/bazel-zig-cc/releases/download/%[1]s/bazel-zig-cc-%[1]s.tar.gz", + ], +) + +load("@bazel-zig-cc//toolchain:defs.bzl", zig_toolchains = "toolchains") + +# Argument-free will pick reasonable defaults. +zig_toolchains() + +# version, url_formats and host_platform_sha256 are can be set for those who +# wish to control their Zig SDK version and where it is downloaded from +zig_toolchains( + version = "<...>", + url_formats = [ + "https://example.org/zig/zig-{host_platform}-{version}.{_ext}", + ], + host_platform_sha256 = { ... }, +)`, version, shasum) +} diff --git a/tools/releaser/main_test.go b/tools/releaser/main_test.go new file mode 100644 index 0000000..b1b40ce --- /dev/null +++ b/tools/releaser/main_test.go @@ -0,0 +1,36 @@ +// Copyright 2023 Uber Technologies, Inc. +// Licensed under the Apache License, Version 2.0 +package main + +import ( + "fmt" + "testing" +) + +func TestRegex(t *testing.T) { + tests := []struct { + tag string + good bool + }{ + {good: true, tag: "v1.0.0"}, + {good: true, tag: "v99.99.99"}, + {good: true, tag: "v1.0.1-rc1"}, + {good: true, tag: "v1.0.99-rc99"}, + {good: false, tag: ""}, + {good: false, tag: "v1.0"}, + {good: false, tag: "1.0.0"}, + {good: false, tag: "1.0.99-rc99"}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("tag=%s good=%s", tt.tag, tt.good), func(t *testing.T) { + matched := tagRegexp.MatchString(tt.tag) + + if tt.good && !matched { + t.Errorf("expected %s to be a valid tag, but it was not", tt.tag) + } else if !tt.good && matched { + t.Errorf("expected %s to be an invalida tag, but it was", tt.tag) + } + }) + } +} diff --git a/tools/releaser/zopfli.go b/tools/releaser/zopfli.go new file mode 100644 index 0000000..40a00d5 --- /dev/null +++ b/tools/releaser/zopfli.go @@ -0,0 +1,10 @@ +package main + +import "github.com/google/zopfli/go/zopfli" + +// Gzip compresses a byte array with zopfli. +// +// We use a separate file, because we can make Gazelle ignore it. +func Gzip(in []byte) []byte { + return zopfli.Gzip(in) +}