Skip to content

Commit

Permalink
buildinfo parser: handle version strings from Go dev builds
Browse files Browse the repository at this point in the history
With the various possible version strings in play, trying to parse
this line is a bit haphazard. We really only need to extract the
executable path, so I've switched to using a regex. That comes at
a cost of less semantic error returns, but as it happens we weren't
receiving these error lines anyway.. they are printed to stderr,
and we only capture stdout from `go version -m ...`. There is the
option to capture stderr as well, but in a way only dealing with
stdout makes life easier as there is less of a chance we mishandle
an error line, etc.

The `verifyExtracted` function already picks up on any paths not
parsed from the output - I've adjusted the error handling slightly so
it's more obvious what has caused the issue.

Resolves #10
  • Loading branch information
Nicholas Jones committed Aug 20, 2021
1 parent 12863f4 commit 0688ce1
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 29 deletions.
23 changes: 6 additions & 17 deletions internal/buildinfo/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package buildinfo

import (
"fmt"
"regexp"
"strings"

"github.com/uw-labs/lichen/internal/model"
)

var goVersionRgx = regexp.MustCompile(`^(.*?): (?:(?:devel )?go[0-9]+|devel \+[0-9a-f]+)`)

// Parse parses build info details as returned by `go version -m [bin ...]`
func Parse(info string) ([]model.BuildInfo, error) {
var (
Expand All @@ -23,28 +26,14 @@ func Parse(info string) ([]model.BuildInfo, error) {

// start of new build info output
if !strings.HasPrefix(l, "\t") {
parts := strings.Split(l, ":")
if len(parts) < 2 {
return nil, fmt.Errorf("invalid version line: %s", l)
}
version := strings.TrimSpace(parts[len(parts)-1])
path := strings.Join(parts[:len(parts)-1], ":")
switch {
case version == "not executable file":
return nil, fmt.Errorf("%s is not an executable", parts[0])
case version == "unrecognized executable format":
return nil, fmt.Errorf("%s has an unrecognized executable format", parts[0])
case version == "go version not found":
return nil, fmt.Errorf("%s does not appear to be a Go compiled binary", parts[0])
case strings.HasPrefix(version, "go"):
// sensible looking
default:
matches := goVersionRgx.FindStringSubmatch(l)
if len(matches) != 2 {
return nil, fmt.Errorf("unrecognised version line: %s", l)
}
if current.Path != "" {
results = append(results, current)
}
current = model.BuildInfo{Path: path}
current = model.BuildInfo{Path: matches[1]}
continue
}

Expand Down
41 changes: 31 additions & 10 deletions internal/buildinfo/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,43 @@ func TestParse(t *testing.T) {
},
},
{
name: "not executable file",
input: `/tmp/lichen: not executable file`,
expectedErr: "/tmp/lichen is not an executable",
name: "development version (pre-go1.17)",
input: `/tmp/lichen: devel +01821137c2 Sat Apr 3 01:45:17 2021 +0000`,
expected: []model.BuildInfo{
{
Path: "/tmp/lichen",
},
},
},
{
name: "development version (current)",
input: `/tmp/lichen: devel go1.18-0c83e01e0c Wed Aug 18 15:11:52 2021 +0000`,
expected: []model.BuildInfo{
{
Path: "/tmp/lichen",
},
},
},
{
name: "unrecognised exe file",
input: `/tmp/lichen: unrecognized executable format`,
expectedErr: "/tmp/lichen has an unrecognized executable format",
name: "development version (old)",
input: `/tmp/lichen: devel +b7a85e0003 linux/amd64`,
expected: []model.BuildInfo{
{
Path: "/tmp/lichen",
},
},
},
{
name: "go version not found",
input: `/tmp/lichen: go version not found`,
expectedErr: "/tmp/lichen does not appear to be a Go compiled binary",
name: "windows development version",
input: `C:\lichen.exe: devel go1.18-0c83e01e0c Wed Aug 18 15:11:52 2021 +0000`,
expected: []model.BuildInfo{
{
Path: `C:\lichen.exe`,
},
},
},
{
name: "invalid",
name: "unrecognised line",
input: `/tmp/lichen: invalid`,
expectedErr: "unrecognised version line: /tmp/lichen: invalid",
},
Expand Down
4 changes: 2 additions & 2 deletions internal/module/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Extract(ctx context.Context, paths ...string) ([]model.BuildInfo, error) {
return nil, err
}
if err := verifyExtracted(parsed, paths); err != nil {
return nil, fmt.Errorf("could not extract module information from binaries: %v", paths)
return nil, fmt.Errorf("could not extract module information: %w", err)
}
return parsed, nil
}
Expand All @@ -37,7 +37,7 @@ func verifyExtracted(extracted []model.BuildInfo, requested []string) (err error
}
for _, path := range requested {
if _, found := buildInfos[path]; !found {
err = multierror.Append(err, fmt.Errorf("modules could not be obtained from %s", path))
err = multierror.Append(err, fmt.Errorf("modules could not be obtained from %[1]s (hint: run `go version -m %[1]q`)", path))
}
}
return
Expand Down

0 comments on commit 0688ce1

Please sign in to comment.