]> Cypherpunks repositories - gostls13.git/commitdiff
runtime/debug: add API to read module info in binary
authorHana Kim <hyangah@gmail.com>
Tue, 23 Oct 2018 21:00:29 +0000 (17:00 -0400)
committerHyang-Ah Hana Kim <hyangah@gmail.com>
Tue, 13 Nov 2018 17:16:48 +0000 (17:16 +0000)
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 <hyangah@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
src/cmd/go/testdata/script/mod_modinfo.txt [new file with mode: 0644]
src/runtime/debug/mod.go [new file with mode: 0644]

diff --git a/src/cmd/go/testdata/script/mod_modinfo.txt b/src/cmd/go/testdata/script/mod_modinfo.txt
new file mode 100644 (file)
index 0000000..f8ad18f
--- /dev/null
@@ -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 (file)
index 0000000..2c5aa27
--- /dev/null
@@ -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
+}