Skip to content

Commit 45e9c55

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 978cfa8 commit 45e9c55

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Test to ensure runtime/debug.ReadBuildInfo parses
2+
# the modinfo embedded in a binary by the go tool
3+
# when module is enabled.
4+
env GO111MODULE=on
5+
6+
cd x
7+
go mod edit -require=rsc.io/[email protected]
8+
go mod edit -replace=rsc.io/[email protected]=rsc.io/[email protected]
9+
10+
go run main.go
11+
12+
stderr 'Hello, world.'
13+
stderr 'mod\s+x\s+\(devel\)'
14+
stderr 'dep\s+rsc.io/quote\s+v1.5.2\s+'
15+
stderr '=>\s+rsc.io/quote\s+v1.0.0\s+h1:'
16+
17+
-- x/go.mod --
18+
module x
19+
20+
-- x/main.go --
21+
package main
22+
23+
import "runtime/debug"
24+
import "rsc.io/quote"
25+
26+
func main() {
27+
println(quote.Hello())
28+
29+
m, ok := debug.ReadBuildInfo()
30+
if !ok {
31+
panic("failed debug.ReadBuildInfo")
32+
}
33+
println("mod", m.Main.Path, m.Main.Version)
34+
for _, d := range m.Deps {
35+
println("dep", d.Path, d.Version, d.Sum)
36+
if r := d.Replace; r != nil {
37+
println("=>", r.Path, r.Version, r.Sum)
38+
}
39+
}
40+
}

src/runtime/debug/mod.go

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package debug
6+
7+
import (
8+
"strings"
9+
)
10+
11+
// set using cmd/go/internal/modload.ModInfoProg
12+
var modinfo string
13+
14+
// ReadBuildInfo returns the build information embedded
15+
// in the running binary. The information is available only
16+
// in binaries built with module support.
17+
func ReadBuildInfo() (info *BuildInfo, ok bool) {
18+
return readBuildInfo(modinfo)
19+
}
20+
21+
// BuildInfo represents the build information read from
22+
// the running binary.
23+
type BuildInfo struct {
24+
Path string // The main package path
25+
Main Module // The main module information
26+
Deps []*Module // Module dependencies
27+
}
28+
29+
// Module represents a module.
30+
type Module struct {
31+
Path string // module path
32+
Version string // module version
33+
Sum string // checksum
34+
Replace *Module // replaced by this module
35+
}
36+
37+
func readBuildInfo(data string) (*BuildInfo, bool) {
38+
if len(data) < 32 {
39+
return nil, false
40+
}
41+
data = data[16 : len(data)-16]
42+
43+
const (
44+
pathLine = "path\t"
45+
modLine = "mod\t"
46+
depLine = "dep\t"
47+
repLine = "=>\t"
48+
)
49+
50+
info := &BuildInfo{}
51+
52+
var line string
53+
// Reverse of cmd/go/internal/modload.PackageBuildInfo
54+
for len(data) > 0 {
55+
i := strings.IndexByte(data, '\n')
56+
if i < 0 {
57+
break
58+
}
59+
line, data = data[:i], data[i+1:]
60+
switch {
61+
case strings.HasPrefix(line, pathLine):
62+
elem := line[len(pathLine):]
63+
info.Path = elem
64+
case strings.HasPrefix(line, modLine):
65+
elem := strings.Split(line[len(modLine):], "\t")
66+
if len(elem) != 3 {
67+
return nil, false
68+
}
69+
info.Main = Module{
70+
Path: elem[0],
71+
Version: elem[1],
72+
Sum: elem[2],
73+
}
74+
case strings.HasPrefix(line, depLine):
75+
elem := strings.Split(line[len(depLine):], "\t")
76+
if len(elem) != 2 && len(elem) != 3 {
77+
return nil, false
78+
}
79+
sum := ""
80+
if len(elem) == 3 {
81+
sum = elem[2]
82+
}
83+
info.Deps = append(info.Deps, &Module{
84+
Path: elem[0],
85+
Version: elem[1],
86+
Sum: sum,
87+
})
88+
case strings.HasPrefix(line, repLine):
89+
elem := strings.Split(line[len(repLine):], "\t")
90+
if len(elem) != 3 {
91+
return nil, false
92+
}
93+
last := len(info.Deps) - 1
94+
if last < 0 {
95+
return nil, false
96+
}
97+
info.Deps[last].Replace = &Module{
98+
Path: elem[0],
99+
Version: elem[1],
100+
Sum: elem[2],
101+
}
102+
}
103+
}
104+
return info, true
105+
}

0 commit comments

Comments
 (0)