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 0000000000..f8ad18f136 --- /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 0000000000..2c5aa27b6e --- /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 +}