+pkg debug/buildinfo, func Read(io.ReaderAt) (*debug.BuildInfo, error)
+pkg debug/buildinfo, func ReadFile(string) (*debug.BuildInfo, error)
+pkg debug/buildinfo, type BuildInfo = debug.BuildInfo
pkg runtime/debug, method (*BuildInfo) MarshalText() ([]byte, error)
+pkg runtime/debug, method (*BuildInfo) UnmarshalText() ([]byte, error)
pkg syscall (darwin-amd64), func RecvfromInet4(int, []uint8, int, *SockaddrInet4) (int, error)
pkg syscall (darwin-amd64), func RecvfromInet6(int, []uint8, int, *SockaddrInet6) (int, error)
pkg syscall (darwin-amd64), func SendtoInet4(int, []uint8, int, SockaddrInet4) error
--- /dev/null
+// Copyright 2021 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 buildinfo provides access to information embedded in a Go binary
+// about how it was built. This includes the Go toolchain version, and the
+// set of modules used (for binaries built in module mode).
+//
+// Build information is available for the currently running binary in
+// runtime/debug.ReadBuildInfo.
+package buildinfo
+
+import (
+ "bytes"
+ "debug/elf"
+ "debug/macho"
+ "debug/pe"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "internal/xcoff"
+ "io"
+ "io/fs"
+ "os"
+ "runtime/debug"
+)
+
+// Type alias for build info. We cannot move the types here, since
+// runtime/debug would need to import this package, which would make it
+// a much larger dependency.
+type BuildInfo = debug.BuildInfo
+
+var (
+ // errUnrecognizedFormat is returned when a given executable file doesn't
+ // appear to be in a known format, or it breaks the rules of that format,
+ // or when there are I/O errors reading the file.
+ errUnrecognizedFormat = errors.New("unrecognized file format")
+
+ // errNotGoExe is returned when a given executable file is valid but does
+ // not contain Go build information.
+ errNotGoExe = errors.New("not a Go executable")
+
+ // The build info blob left by the linker is identified by
+ // a 16-byte header, consisting of buildInfoMagic (14 bytes),
+ // the binary's pointer size (1 byte),
+ // and whether the binary is big endian (1 byte).
+ buildInfoMagic = []byte("\xff Go buildinf:")
+)
+
+// ReadFile returns build information embedded in a Go binary
+// file at the given path. Most information is only available for binaries built
+// with module support.
+func ReadFile(name string) (info *BuildInfo, err error) {
+ defer func() {
+ if pathErr := (*fs.PathError)(nil); errors.As(err, &pathErr) {
+ err = fmt.Errorf("could not read Go build info: %w", err)
+ } else if err != nil {
+ err = fmt.Errorf("could not read Go build info from %s: %w", name, err)
+ }
+ }()
+
+ f, err := os.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return Read(f)
+}
+
+// Read returns build information embedded in a Go binary file
+// accessed through the given ReaderAt. Most information is only available for
+// binaries built with module support.
+func Read(r io.ReaderAt) (*BuildInfo, error) {
+ _, mod, err := readRawBuildInfo(r)
+ if err != nil {
+ return nil, err
+ }
+ bi := &BuildInfo{}
+ if err := bi.UnmarshalText([]byte(mod)); err != nil {
+ return nil, err
+ }
+ return bi, nil
+}
+
+type exe interface {
+ // ReadData reads and returns up to size bytes starting at virtual address addr.
+ ReadData(addr, size uint64) ([]byte, error)
+
+ // DataStart returns the virtual address of the segment or section that
+ // should contain build information. This is either a specially named section
+ // or the first writable non-zero data segment.
+ DataStart() uint64
+}
+
+// readRawBuildInfo extracts the Go toolchain version and module information
+// strings from a Go binary. On success, vers should be non-empty. mod
+// is empty if the binary was not built with modules enabled.
+func readRawBuildInfo(r io.ReaderAt) (vers, mod string, err error) {
+ // Read the first bytes of the file to identify the format, then delegate to
+ // a format-specific function to load segment and section headers.
+ ident := make([]byte, 16)
+ if n, err := r.ReadAt(ident, 0); n < len(ident) || err != nil {
+ return "", "", errUnrecognizedFormat
+ }
+
+ var x exe
+ switch {
+ case bytes.HasPrefix(ident, []byte("\x7FELF")):
+ f, err := elf.NewFile(r)
+ if err != nil {
+ return "", "", errUnrecognizedFormat
+ }
+ x = &elfExe{f}
+ case bytes.HasPrefix(ident, []byte("MZ")):
+ f, err := pe.NewFile(r)
+ if err != nil {
+ return "", "", errUnrecognizedFormat
+ }
+ x = &peExe{f}
+ case bytes.HasPrefix(ident, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(ident[1:], []byte("\xFA\xED\xFE")):
+ f, err := macho.NewFile(r)
+ if err != nil {
+ return "", "", errUnrecognizedFormat
+ }
+ x = &machoExe{f}
+ case bytes.HasPrefix(ident, []byte{0x01, 0xDF}) || bytes.HasPrefix(ident, []byte{0x01, 0xF7}):
+ f, err := xcoff.NewFile(r)
+ if err != nil {
+ return "", "", errUnrecognizedFormat
+ }
+ x = &xcoffExe{f}
+ default:
+ return "", "", errUnrecognizedFormat
+ }
+
+ // Read the first 64kB of dataAddr to find the build info blob.
+ // On some platforms, the blob will be in its own section, and DataStart
+ // returns the address of that section. On others, it's somewhere in the
+ // data segment; the linker puts it near the beginning.
+ // See cmd/link/internal/ld.Link.buildinfo.
+ dataAddr := x.DataStart()
+ data, err := x.ReadData(dataAddr, 64*1024)
+ if err != nil {
+ return "", "", err
+ }
+ const (
+ buildInfoAlign = 16
+ buildinfoSize = 32
+ )
+ for ; !bytes.HasPrefix(data, buildInfoMagic); data = data[buildInfoAlign:] {
+ if len(data) < 32 {
+ return "", "", errNotGoExe
+ }
+ }
+
+ // Decode the blob.
+ // The first 14 bytes are buildInfoMagic.
+ // The next two bytes indicate pointer size in bytes (4 or 8) and endianness
+ // (0 for little, 1 for big).
+ // Two virtual addresses to Go strings follow that: runtime.buildVersion,
+ // and runtime.modinfo.
+ // On 32-bit platforms, the last 8 bytes are unused.
+ ptrSize := int(data[14])
+ bigEndian := data[15] != 0
+ var bo binary.ByteOrder
+ if bigEndian {
+ bo = binary.BigEndian
+ } else {
+ bo = binary.LittleEndian
+ }
+ var readPtr func([]byte) uint64
+ if ptrSize == 4 {
+ readPtr = func(b []byte) uint64 { return uint64(bo.Uint32(b)) }
+ } else {
+ readPtr = bo.Uint64
+ }
+ vers = readString(x, ptrSize, readPtr, readPtr(data[16:]))
+ if vers == "" {
+ return "", "", errNotGoExe
+ }
+ mod = readString(x, ptrSize, readPtr, readPtr(data[16+ptrSize:]))
+ if len(mod) >= 33 && mod[len(mod)-17] == '\n' {
+ // Strip module framing: sentinel strings delimiting the module info.
+ // These are cmd/go/internal/modload.infoStart and infoEnd.
+ mod = mod[16 : len(mod)-16]
+ } else {
+ mod = ""
+ }
+
+ return vers, mod, nil
+}
+
+// readString returns the string at address addr in the executable x.
+func readString(x exe, ptrSize int, readPtr func([]byte) uint64, addr uint64) string {
+ hdr, err := x.ReadData(addr, uint64(2*ptrSize))
+ if err != nil || len(hdr) < 2*ptrSize {
+ return ""
+ }
+ dataAddr := readPtr(hdr)
+ dataLen := readPtr(hdr[ptrSize:])
+ data, err := x.ReadData(dataAddr, dataLen)
+ if err != nil || uint64(len(data)) < dataLen {
+ return ""
+ }
+ return string(data)
+}
+
+// elfExe is the ELF implementation of the exe interface.
+type elfExe struct {
+ f *elf.File
+}
+
+func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) {
+ for _, prog := range x.f.Progs {
+ if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 {
+ n := prog.Vaddr + prog.Filesz - addr
+ if n > size {
+ n = size
+ }
+ data := make([]byte, n)
+ _, err := prog.ReadAt(data, int64(addr-prog.Vaddr))
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+ }
+ }
+ return nil, errUnrecognizedFormat
+}
+
+func (x *elfExe) DataStart() uint64 {
+ for _, s := range x.f.Sections {
+ if s.Name == ".go.buildinfo" {
+ return s.Addr
+ }
+ }
+ for _, p := range x.f.Progs {
+ if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W {
+ return p.Vaddr
+ }
+ }
+ return 0
+}
+
+// peExe is the PE (Windows Portable Executable) implementation of the exe interface.
+type peExe struct {
+ f *pe.File
+}
+
+func (x *peExe) imageBase() uint64 {
+ switch oh := x.f.OptionalHeader.(type) {
+ case *pe.OptionalHeader32:
+ return uint64(oh.ImageBase)
+ case *pe.OptionalHeader64:
+ return oh.ImageBase
+ }
+ return 0
+}
+
+func (x *peExe) ReadData(addr, size uint64) ([]byte, error) {
+ addr -= x.imageBase()
+ for _, sect := range x.f.Sections {
+ if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
+ n := uint64(sect.VirtualAddress+sect.Size) - addr
+ if n > size {
+ n = size
+ }
+ data := make([]byte, n)
+ _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
+ if err != nil {
+ return nil, errUnrecognizedFormat
+ }
+ return data, nil
+ }
+ }
+ return nil, errUnrecognizedFormat
+}
+
+func (x *peExe) DataStart() uint64 {
+ // Assume data is first writable section.
+ const (
+ IMAGE_SCN_CNT_CODE = 0x00000020
+ IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
+ IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
+ IMAGE_SCN_MEM_EXECUTE = 0x20000000
+ IMAGE_SCN_MEM_READ = 0x40000000
+ IMAGE_SCN_MEM_WRITE = 0x80000000
+ IMAGE_SCN_MEM_DISCARDABLE = 0x2000000
+ IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000
+ IMAGE_SCN_ALIGN_32BYTES = 0x600000
+ )
+ for _, sect := range x.f.Sections {
+ if sect.VirtualAddress != 0 && sect.Size != 0 &&
+ sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE {
+ return uint64(sect.VirtualAddress) + x.imageBase()
+ }
+ }
+ return 0
+}
+
+// machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface.
+type machoExe struct {
+ f *macho.File
+}
+
+func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) {
+ for _, load := range x.f.Loads {
+ seg, ok := load.(*macho.Segment)
+ if !ok {
+ continue
+ }
+ if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 {
+ if seg.Name == "__PAGEZERO" {
+ continue
+ }
+ n := seg.Addr + seg.Filesz - addr
+ if n > size {
+ n = size
+ }
+ data := make([]byte, n)
+ _, err := seg.ReadAt(data, int64(addr-seg.Addr))
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+ }
+ }
+ return nil, errUnrecognizedFormat
+}
+
+func (x *machoExe) DataStart() uint64 {
+ // Look for section named "__go_buildinfo".
+ for _, sec := range x.f.Sections {
+ if sec.Name == "__go_buildinfo" {
+ return sec.Addr
+ }
+ }
+ // Try the first non-empty writable segment.
+ const RW = 3
+ for _, load := range x.f.Loads {
+ seg, ok := load.(*macho.Segment)
+ if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW {
+ return seg.Addr
+ }
+ }
+ return 0
+}
+
+// xcoffExe is the XCOFF (AIX eXtended COFF) implementation of the exe interface.
+type xcoffExe struct {
+ f *xcoff.File
+}
+
+func (x *xcoffExe) ReadData(addr, size uint64) ([]byte, error) {
+ for _, sect := range x.f.Sections {
+ if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) {
+ n := uint64(sect.VirtualAddress+sect.Size) - addr
+ if n > size {
+ n = size
+ }
+ data := make([]byte, n)
+ _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress)))
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+ }
+ }
+ return nil, fmt.Errorf("address not mapped")
+}
+
+func (x *xcoffExe) DataStart() uint64 {
+ return x.f.SectionByType(xcoff.STYP_DATA).VirtualAddress
+}
--- /dev/null
+// Copyright 2021 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 buildinfo_test
+
+import (
+ "bytes"
+ "debug/buildinfo"
+ "flag"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+var flagAll = flag.Bool("all", false, "test all supported GOOS/GOARCH platforms, instead of only the current platform")
+
+// TestReadFile confirms that ReadFile can read build information from binaries
+// on supported target platforms. It builds a trivial binary on the current
+// platforms (or all platforms if -all is set) in various configurations and
+// checks that build information can or cannot be read.
+func TestReadFile(t *testing.T) {
+ if testing.Short() {
+ t.Skip("test requires compiling and linking, which may be slow")
+ }
+ testenv.MustHaveGoBuild(t)
+
+ type platform struct{ goos, goarch string }
+ platforms := []platform{
+ {"aix", "ppc64"},
+ {"darwin", "amd64"},
+ {"darwin", "arm64"},
+ {"linux", "386"},
+ {"linux", "amd64"},
+ {"windows", "386"},
+ {"windows", "amd64"},
+ }
+ runtimePlatform := platform{runtime.GOOS, runtime.GOARCH}
+ haveRuntimePlatform := false
+ for _, p := range platforms {
+ if p == runtimePlatform {
+ haveRuntimePlatform = true
+ break
+ }
+ }
+ if !haveRuntimePlatform {
+ platforms = append(platforms, runtimePlatform)
+ }
+
+ buildWithModules := func(t *testing.T, goos, goarch string) string {
+ dir := t.TempDir()
+ gomodPath := filepath.Join(dir, "go.mod")
+ gomodData := []byte("module example.com/m\ngo 1.18\n")
+ if err := os.WriteFile(gomodPath, gomodData, 0666); err != nil {
+ t.Fatal(err)
+ }
+ helloPath := filepath.Join(dir, "hello.go")
+ helloData := []byte("package main\nfunc main() {}\n")
+ if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
+ t.Fatal(err)
+ }
+ outPath := filepath.Join(dir, path.Base(t.Name()))
+ cmd := exec.Command("go", "build", "-o="+outPath)
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "GO111MODULE=on", "GOOS="+goos, "GOARCH="+goarch)
+ stderr := &bytes.Buffer{}
+ cmd.Stderr = stderr
+ if err := cmd.Run(); err != nil {
+ t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
+ }
+ return outPath
+ }
+
+ buildWithGOPATH := func(t *testing.T, goos, goarch string) string {
+ gopathDir := t.TempDir()
+ pkgDir := filepath.Join(gopathDir, "src/example.com/m")
+ if err := os.MkdirAll(pkgDir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ helloPath := filepath.Join(pkgDir, "hello.go")
+ helloData := []byte("package main\nfunc main() {}\n")
+ if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
+ t.Fatal(err)
+ }
+ outPath := filepath.Join(gopathDir, path.Base(t.Name()))
+ cmd := exec.Command("go", "build", "-o="+outPath)
+ cmd.Dir = pkgDir
+ cmd.Env = append(os.Environ(), "GO111MODULE=off", "GOPATH="+gopathDir, "GOOS="+goos, "GOARCH="+goarch)
+ stderr := &bytes.Buffer{}
+ cmd.Stderr = stderr
+ if err := cmd.Run(); err != nil {
+ t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
+ }
+ return outPath
+ }
+
+ damageBuildInfo := func(t *testing.T, name string) {
+ data, err := os.ReadFile(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ i := bytes.Index(data, []byte("\xff Go buildinf:"))
+ if i < 0 {
+ t.Fatal("Go buildinf not found")
+ }
+ data[i+2] = 'N'
+ if err := os.WriteFile(name, data, 0666); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ cases := []struct {
+ name string
+ build func(t *testing.T, goos, goarch string) string
+ want string
+ wantErr string
+ }{
+ {
+ name: "doesnotexist",
+ build: func(t *testing.T, goos, goarch string) string {
+ return "doesnotexist.txt"
+ },
+ wantErr: "doesnotexist",
+ },
+ {
+ name: "empty",
+ build: func(t *testing.T, _, _ string) string {
+ dir := t.TempDir()
+ name := filepath.Join(dir, "empty")
+ if err := os.WriteFile(name, nil, 0666); err != nil {
+ t.Fatal(err)
+ }
+ return name
+ },
+ wantErr: "unrecognized file format",
+ },
+ {
+ name: "valid_modules",
+ build: buildWithModules,
+ want: "path\texample.com/m\n" +
+ "mod\texample.com/m\t(devel)\t\n",
+ },
+ {
+ name: "invalid_modules",
+ build: func(t *testing.T, goos, goarch string) string {
+ name := buildWithModules(t, goos, goarch)
+ damageBuildInfo(t, name)
+ return name
+ },
+ wantErr: "not a Go executable",
+ },
+ {
+ name: "valid_gopath",
+ build: buildWithGOPATH,
+ want: "",
+ },
+ {
+ name: "invalid_gopath",
+ build: func(t *testing.T, goos, goarch string) string {
+ name := buildWithGOPATH(t, goos, goarch)
+ damageBuildInfo(t, name)
+ return name
+ },
+ wantErr: "not a Go executable",
+ },
+ }
+
+ for _, p := range platforms {
+ p := p
+ t.Run(p.goos+"_"+p.goarch, func(t *testing.T) {
+ if p != runtimePlatform && !*flagAll {
+ t.Skipf("skipping platforms other than %s_%s because -all was not set", runtimePlatform.goos, runtimePlatform.goarch)
+ }
+ for _, tc := range cases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ name := tc.build(t, p.goos, p.goarch)
+ if info, err := buildinfo.ReadFile(name); err != nil {
+ if tc.wantErr == "" {
+ t.Fatalf("unexpected error: %v", err)
+ } else if errMsg := err.Error(); !strings.Contains(errMsg, tc.wantErr) {
+ t.Fatalf("got error %q; want error containing %q", errMsg, tc.wantErr)
+ }
+ } else {
+ if tc.wantErr != "" {
+ t.Fatalf("unexpected success; want error containing %q", tc.wantErr)
+ } else if got, err := info.MarshalText(); err != nil {
+ t.Fatalf("unexpected error marshaling BuildInfo: %v", err)
+ } else {
+ got := string(got)
+ if got != tc.want {
+ t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want)
+ }
+ }
+ }
+ })
+ }
+ })
+ }
+}
mime/quotedprintable,
net/internal/socktest,
net/url,
- runtime/debug,
runtime/trace,
text/scanner,
text/tabwriter;
# executable parsing
FMT, encoding/binary, compress/zlib
+ < runtime/debug
< debug/dwarf
< debug/elf, debug/gosym, debug/macho, debug/pe, debug/plan9obj, internal/xcoff
+ < debug/buildinfo
< DEBUG;
# go parser and friends.
FMT, flag, math/rand
< testing/quick;
- FMT, flag, runtime/debug, runtime/trace, internal/sysinfo, math/rand
+ FMT, DEBUG, flag, runtime/trace, internal/sysinfo, math/rand
< testing;
FMT, crypto/sha256, encoding/json, go/ast, go/parser, go/token, math/rand, encoding/hex, crypto/sha256
import (
"bytes"
"fmt"
- "strings"
)
// exported from runtime
// 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
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
}