From 45e9c5538b3956d98ef0337fc19ed8d45eb17090 Mon Sep 17 00:00:00 2001 From: Hana Kim Date: Tue, 23 Oct 2018 17:00:29 -0400 Subject: [PATCH] runtime/debug: add API to read module info in binary When module is enabled, the go tool embeds build information related to the module in the binary including the dependencies and the replace information (See src/cmd/go/internal/modload.PackageBuildInfo). The newly introduced ReadBuildInfo reads the information and makes it accessible programmatically. Update #26404 Change-Id: Ide37022d609b4a8fb6b5ce02afabb73f04fbb532 Reviewed-on: https://go-review.googlesource.com/c/144220 Run-TryBot: Hyang-Ah Hana Kim TryBot-Result: Gobot Gobot Reviewed-by: Russ Cox --- src/cmd/go/testdata/script/mod_modinfo.txt | 40 ++++++++ src/runtime/debug/mod.go | 105 +++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/cmd/go/testdata/script/mod_modinfo.txt create mode 100644 src/runtime/debug/mod.go diff --git a/src/cmd/go/testdata/script/mod_modinfo.txt b/src/cmd/go/testdata/script/mod_modinfo.txt new file mode 100644 index 0000000000000..f8ad18f1368a7 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_modinfo.txt @@ -0,0 +1,40 @@ +# Test to ensure runtime/debug.ReadBuildInfo parses +# the modinfo embedded in a binary by the go tool +# when module is enabled. +env GO111MODULE=on + +cd x +go mod edit -require=rsc.io/quote@v1.5.2 +go mod edit -replace=rsc.io/quote@v1.5.2=rsc.io/quote@v1.0.0 + +go run main.go + +stderr 'Hello, world.' +stderr 'mod\s+x\s+\(devel\)' +stderr 'dep\s+rsc.io/quote\s+v1.5.2\s+' +stderr '=>\s+rsc.io/quote\s+v1.0.0\s+h1:' + +-- x/go.mod -- +module x + +-- x/main.go -- +package main + +import "runtime/debug" +import "rsc.io/quote" + +func main() { + println(quote.Hello()) + + m, ok := debug.ReadBuildInfo() + if !ok { + panic("failed debug.ReadBuildInfo") + } + println("mod", m.Main.Path, m.Main.Version) + for _, d := range m.Deps { + println("dep", d.Path, d.Version, d.Sum) + if r := d.Replace; r != nil { + println("=>", r.Path, r.Version, r.Sum) + } + } +} diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go new file mode 100644 index 0000000000000..2c5aa27b6ed03 --- /dev/null +++ b/src/runtime/debug/mod.go @@ -0,0 +1,105 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package debug + +import ( + "strings" +) + +// set using cmd/go/internal/modload.ModInfoProg +var modinfo string + +// ReadBuildInfo returns the build information embedded +// in the running binary. The information is available only +// in binaries built with module support. +func ReadBuildInfo() (info *BuildInfo, ok bool) { + return readBuildInfo(modinfo) +} + +// BuildInfo represents the build information read from +// the running binary. +type BuildInfo struct { + Path string // The main package path + Main Module // The main module information + Deps []*Module // Module dependencies +} + +// Module represents a module. +type Module struct { + Path string // module path + Version string // module version + Sum string // checksum + Replace *Module // replaced by this module +} + +func readBuildInfo(data string) (*BuildInfo, bool) { + if len(data) < 32 { + return nil, false + } + data = data[16 : len(data)-16] + + const ( + pathLine = "path\t" + modLine = "mod\t" + depLine = "dep\t" + repLine = "=>\t" + ) + + info := &BuildInfo{} + + var line string + // Reverse of cmd/go/internal/modload.PackageBuildInfo + for len(data) > 0 { + i := strings.IndexByte(data, '\n') + if i < 0 { + break + } + line, data = data[:i], data[i+1:] + switch { + case strings.HasPrefix(line, pathLine): + elem := line[len(pathLine):] + info.Path = elem + case strings.HasPrefix(line, modLine): + elem := strings.Split(line[len(modLine):], "\t") + if len(elem) != 3 { + return nil, false + } + info.Main = Module{ + Path: elem[0], + Version: elem[1], + Sum: elem[2], + } + case strings.HasPrefix(line, depLine): + elem := strings.Split(line[len(depLine):], "\t") + if len(elem) != 2 && len(elem) != 3 { + return nil, false + } + sum := "" + if len(elem) == 3 { + sum = elem[2] + } + info.Deps = append(info.Deps, &Module{ + Path: elem[0], + Version: elem[1], + Sum: sum, + }) + case strings.HasPrefix(line, repLine): + elem := strings.Split(line[len(repLine):], "\t") + if len(elem) != 3 { + return nil, false + } + last := len(info.Deps) - 1 + if last < 0 { + return nil, false + } + info.Deps[last].Replace = &Module{ + Path: elem[0], + Version: elem[1], + Sum: elem[2], + } + } + } + return info, true +}