diff options
| author | Jay Conrod <jayconrod@google.com> | 2021-09-30 10:25:49 -0700 |
|---|---|---|
| committer | Jay Conrod <jayconrod@google.com> | 2021-10-14 18:44:08 +0000 |
| commit | 434cdd0337b9e6c7e0c369c9293cc14fd38dc80d (patch) | |
| tree | c3bed1de3166cdfbd3cbb7ea86858c62a5a8945d /src/runtime/debug | |
| parent | 765c9116be44641854f580c19e3589d7b86a3d28 (diff) | |
| download | go-434cdd0337b9e6c7e0c369c9293cc14fd38dc80d.tar.xz | |
debug/buildinfo: new package with Read and ReadFile
These functions provide access to module information stamped into Go
binaries. In the future, they'll provide access to other information
(like VCS info).
These functions are added in a new package instead of runtime/debug
since they use binary parsing packages like debug/elf, which would
make runtime/debug an unacceptably heavy dependency. The types in
runtime/debug are still used; debug/buildinfo uses them via type
aliases.
This information is already available for the running binary through
debug.ReadBuildInfo and for other binaries with 'go version -m', but
until now, there hasn't been a way to get it for other binaries
without installing cmd/go.
This change copies most of the code in cmd/go/internal/version. A
later CL will migrate 'go version -m' to use this package.
For #37475
Fixes #39301
Change-Id: I0fbe0896e04f12ef81c6d79fb61b20daede86159
Reviewed-on: https://go-review.googlesource.com/c/go/+/353887
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Diffstat (limited to 'src/runtime/debug')
| -rw-r--r-- | src/runtime/debug/mod.go | 104 |
1 files changed, 58 insertions, 46 deletions
diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go index 11f995ba75..8c6c48089b 100644 --- a/src/runtime/debug/mod.go +++ b/src/runtime/debug/mod.go @@ -7,7 +7,6 @@ package debug import ( "bytes" "fmt" - "strings" ) // exported from runtime @@ -17,11 +16,19 @@ func modinfo() string // in the running binary. The information is available only // in binaries built with module support. func ReadBuildInfo() (info *BuildInfo, ok bool) { - return readBuildInfo(modinfo()) + data := modinfo() + if len(data) < 32 { + return nil, false + } + data = data[16 : len(data)-16] + bi := &BuildInfo{} + if err := bi.UnmarshalText([]byte(data)); err != nil { + return nil, false + } + return bi, true } -// BuildInfo represents the build information read from -// the running binary. +// BuildInfo represents the build information read from a Go binary. type BuildInfo struct { Path string // The main package path Main Module // The module containing the main package @@ -71,80 +78,85 @@ func (bi *BuildInfo) MarshalText() ([]byte, error) { return buf.Bytes(), nil } -func readBuildInfo(data string) (*BuildInfo, bool) { - if len(data) < 32 { - return nil, false - } - data = data[16 : len(data)-16] +func (bi *BuildInfo) UnmarshalText(data []byte) (err error) { + *bi = BuildInfo{} + lineNum := 1 + defer func() { + if err != nil { + err = fmt.Errorf("could not parse Go build info: line %d: %w", lineNum, err) + } + }() - const ( - pathLine = "path\t" - modLine = "mod\t" - depLine = "dep\t" - repLine = "=>\t" + var ( + pathLine = []byte("path\t") + modLine = []byte("mod\t") + depLine = []byte("dep\t") + repLine = []byte("=>\t") + newline = []byte("\n") + tab = []byte("\t") ) - readEntryFirstLine := func(elem []string) (Module, bool) { + readModuleLine := func(elem [][]byte) (Module, error) { if len(elem) != 2 && len(elem) != 3 { - return Module{}, false + return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem)) } sum := "" if len(elem) == 3 { - sum = elem[2] + sum = string(elem[2]) } return Module{ - Path: elem[0], - Version: elem[1], + Path: string(elem[0]), + Version: string(elem[1]), Sum: sum, - }, true + }, nil } var ( - info = &BuildInfo{} last *Module - line string + line []byte ok bool ) - // Reverse of cmd/go/internal/modload.PackageBuildInfo + // Reverse of BuildInfo.String() for len(data) > 0 { - line, data, ok = strings.Cut(data, "\n") + line, data, ok = bytes.Cut(data, newline) if !ok { break } switch { - case strings.HasPrefix(line, pathLine): + case bytes.HasPrefix(line, pathLine): elem := line[len(pathLine):] - info.Path = elem - case strings.HasPrefix(line, modLine): - elem := strings.Split(line[len(modLine):], "\t") - last = &info.Main - *last, ok = readEntryFirstLine(elem) - if !ok { - return nil, false + bi.Path = string(elem) + case bytes.HasPrefix(line, modLine): + elem := bytes.Split(line[len(modLine):], tab) + last = &bi.Main + *last, err = readModuleLine(elem) + if err != nil { + return err } - case strings.HasPrefix(line, depLine): - elem := strings.Split(line[len(depLine):], "\t") + case bytes.HasPrefix(line, depLine): + elem := bytes.Split(line[len(depLine):], tab) last = new(Module) - info.Deps = append(info.Deps, last) - *last, ok = readEntryFirstLine(elem) - if !ok { - return nil, false + bi.Deps = append(bi.Deps, last) + *last, err = readModuleLine(elem) + if err != nil { + return err } - case strings.HasPrefix(line, repLine): - elem := strings.Split(line[len(repLine):], "\t") + case bytes.HasPrefix(line, repLine): + elem := bytes.Split(line[len(repLine):], tab) if len(elem) != 3 { - return nil, false + return fmt.Errorf("expected 3 columns for replacement; got %d", len(elem)) } if last == nil { - return nil, false + return fmt.Errorf("replacement with no module on previous line") } last.Replace = &Module{ - Path: elem[0], - Version: elem[1], - Sum: elem[2], + Path: string(elem[0]), + Version: string(elem[1]), + Sum: string(elem[2]), } last = nil } + lineNum++ } - return info, true + return nil } |
