diff --git a/.github/homebrew/bits.rb.erb b/.github/homebrew/bits.rb.erb index e0598e7d..a28716cb 100644 --- a/.github/homebrew/bits.rb.erb +++ b/.github/homebrew/bits.rb.erb @@ -8,8 +8,6 @@ class Bits < Formula desc "Datadog Assistant in your terminal" homepage "https://github.com/datadog-labs/bits-cli" - # Shipped bits binaries read this line to offer updates; keep exactly one. - version "<%= values.fetch("version") %>" license "Apache-2.0" on_macos do diff --git a/internal/update/homebrew.go b/internal/update/homebrew.go index 83e0db72..9c475189 100644 --- a/internal/update/homebrew.go +++ b/internal/update/homebrew.go @@ -124,16 +124,24 @@ func detectBackend(executable string) (backend, error) { return h, nil } -// Read only the explicit version declaration in our generated formula. This is -// deliberately not a Ruby parser; unexpected formats fail the check silently. -var formulaVersion = regexp.MustCompile(`(?m)^[\t ]*version[\t ]+"([^"\r\n]+)"[\t ]*\r?$`) +// Read the version from the release download URLs in our generated formula. +// Homebrew infers its formula version from these URLs, so avoid duplicating it +// in an explicit version stanza. This is deliberately not a Ruby parser; +// unexpected formats fail the check silently. +var formulaReleaseVersion = regexp.MustCompile(`(?m)^[\t ]*url[\t ]+"[^"\r\n]*/releases/download/v([^/"\r\n]+)/[^"\r\n]+"[\t ]*\r?$`) func homebrewVersion(raw []byte) (string, error) { - matches := formulaVersion.FindAllSubmatch(raw, -1) - if len(matches) != 1 { - return "", fmt.Errorf("%w: expected one explicit Homebrew version", errInvalidVersion) + matches := formulaReleaseVersion.FindAllSubmatch(raw, -1) + if len(matches) == 0 { + return "", fmt.Errorf("%w: expected one version across Homebrew release URLs", errInvalidVersion) } - latest := "v" + string(matches[0][1]) + version := string(matches[0][1]) + for _, match := range matches[1:] { + if string(match[1]) != version { + return "", fmt.Errorf("%w: mismatched versions across Homebrew release URLs", errInvalidVersion) + } + } + latest := "v" + version if !semver.IsValid(latest) { return "", fmt.Errorf("%w: Homebrew version %q", errInvalidVersion, latest) } diff --git a/internal/update/update_test.go b/internal/update/update_test.go index ef83e136..c9242eb4 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -223,7 +223,7 @@ func TestHomebrewFetch(t *testing.T) { body, want string invalid bool }{ - {"success", 200, ` version "1.3.0"`, "v1.3.0", false}, + {"success", 200, ` url "https://github.com/datadog-labs/bits-cli/releases/download/v1.3.0/bits_1.3.0_linux_amd64.tar.gz"`, "v1.3.0", false}, {"server", 500, "", "", false}, {"missing", 404, "", "", false}, {"redirect", 302, "", "", false}, @@ -394,13 +394,12 @@ func TestHomebrewUpgrade(t *testing.T) { func TestHomebrewVersion(t *testing.T) { for _, tc := range []struct{ name, formula, want string }{ - {"generated format", "class Bits < Formula\n version \"1.2.3\"\n on_macos do\n url \"https://example.com/v9.9.9/bits.tar.gz\"\n end\nend\n", "v1.2.3"}, - {"windows newlines", " version \"1.2.3\"\r\n", "v1.2.3"}, + {"generated format", "class Bits < Formula\n on_macos do\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_amd64.tar.gz\"\n end\nend\n", "v1.2.3"}, + {"windows newlines", " url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_linux_amd64.tar.gz\"\r\n", "v1.2.3"}, {"missing", "class Bits < Formula\nend\n", ""}, - {"comment", " # version \"1.2.3\"\n", ""}, - {"duplicate", " version \"1.2.3\"\n version \"1.2.4\"\n", ""}, - {"expression", " version ENV.fetch(\"VERSION\")\n", ""}, - {"interpolation", " version \"#{version}\"\n", ""}, + {"comment", " # url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n", ""}, + {"mismatched release URLs", " url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.4/bits_1.2.4_linux_amd64.tar.gz\"\n", ""}, + {"invalid version", " url \"https://github.com/datadog-labs/bits-cli/releases/download/vnot-a-version/bits.tar.gz\"\n", ""}, } { t.Run(tc.name, func(t *testing.T) { got, err := homebrewVersion([]byte(tc.formula)) diff --git a/scripts/ciworkflow_test.go b/scripts/ciworkflow_test.go index fdd3e80d..6ecf2d6a 100644 --- a/scripts/ciworkflow_test.go +++ b/scripts/ciworkflow_test.go @@ -290,8 +290,8 @@ func TestHomebrewTemplate(t *testing.T) { if err != nil { t.Fatalf("render formula: %v\n%s", err, formula) } - if got := strings.Count(string(formula), "\n version \"1.2.3\"\n"); got != 1 { - t.Fatalf("expected one explicit version declaration, got %d", got) + if strings.Contains(string(formula), "\n version ") { + t.Fatal("formula should let Homebrew infer the version from its release URLs") } for _, platform := range platforms { url := `url "https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_` + platform + `.tar.gz"` diff --git a/scripts/try-update.sh b/scripts/try-update.sh index 74267086..e2d1cb89 100755 --- a/scripts/try-update.sh +++ b/scripts/try-update.sh @@ -64,7 +64,7 @@ chmod +x "$PREFIX/bin/brew" # Start without a cache so the first launch performs the real check. rm -f "$STATE" -echo "--- serving $URL ($(grep -m1 '^ *version ' "$PREFIX/www/Formula/bits.rb" | xargs))" +echo "--- serving $URL ($(grep -m1 'releases/download/v' "$PREFIX/www/Formula/bits.rb" | sed -E 's|.*releases/download/(v[^/]+)/.*|\1|'))" while true; do echo "--- launching $("$PREFIX/bin/bits" --version)"