Skip to content

Commit

Permalink
cmd/errtrace: Uniquely identify the binary for toolexec version (#93)
Browse files Browse the repository at this point in the history
The toolexec version should be bumped on any change to the code
rewriting. Doing this manually is error-prone, so instead, use a string
that uniquely identifies the errtrace binary. Note that every time this
version changes, it means the build cache can't be used so it's
important to use a stable value.

We default to the git SHA in the debug build info (vcs.revision) if the
binary is build from a clean state (vcs.modified=false).

Otherwise, we use the MD5 of the binary being executed. This is more
expensive than the SHA to compute, so we prefer the SHA.
  • Loading branch information
prashantv authored Feb 18, 2024
1 parent 02aad23 commit 0d9b854
Showing 1 changed file with 53 additions and 2 deletions.
55 changes: 53 additions & 2 deletions cmd/errtrace/toolexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package main

import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
"strings"

"braces.dev/errtrace"
Expand Down Expand Up @@ -39,6 +42,11 @@ func (cmd *mainCmd) handleToolExec(args []string) (exitCode int, handled bool) {
}

func (cmd *mainCmd) toolExecVersion(args []string) int {
version, err := binaryVersion()
if err != nil {
fmt.Fprintf(cmd.Stderr, "errtrace version failed: %v", err)
}

tool := exec.Command(args[0], args[1:]...)
var stdout bytes.Buffer
tool.Stdout = &stdout
Expand All @@ -52,8 +60,7 @@ func (cmd *mainCmd) toolExecVersion(args []string) int {
return 1
}

// TODO: This version number should change whenever the rewriting changes.
fmt.Fprintf(cmd.Stdout, "%s-errtrace1\n", strings.TrimSpace(stdout.String()))
fmt.Fprintf(cmd.Stdout, "%s-errtrace-%s\n", strings.TrimSpace(stdout.String()), version)
return 0
}

Expand Down Expand Up @@ -173,3 +180,47 @@ func (cmd *mainCmd) runOriginal(args []string) (exitCode int) {

return 0
}

// binaryVersion returns a string that uniquely identifies the binary.
// We prefer to use the VCS info embedded in the build if possible
// falling back to the MD5 of the binary.
func binaryVersion() (string, error) {
sha, ok := readBuildSHA()
if ok {
return sha, nil
}

exe, err := os.Executable()
if err != nil {
return "", errtrace.Wrap(err)
}

contents, err := os.ReadFile(exe)
if err != nil {
return "", errtrace.Wrap(err)
}

binaryHash := md5.Sum(contents)
return hex.EncodeToString(binaryHash[:]), nil
}

// readBuildSHA returns the VCS SHA, if it's from an unmodified VCS state.
func readBuildSHA() (_ string, ok bool) {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return "", false
}

var sha string
for _, s := range buildInfo.Settings {
switch s.Key {
case "vcs.revision":
sha = s.Value
case "vcs.modified":
if s.Value != "false" {
return "", false
}
}
}
return sha, sha != ""
}

0 comments on commit 0d9b854

Please sign in to comment.