internal/buildbinary: add buildbinary pkg
The buildbinary package finds and builds all binaries in a given module.
This will be used to do binary vs source mode comparisons for govulncheck scan.
Change-Id: I6a3db78cc3b632df09d97771edd8ad9419e8206f
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/506518
Run-TryBot: Maceo Thompson <maceothompson@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/buildbinary/bin.go b/internal/buildbinary/bin.go
new file mode 100644
index 0000000..3db7682
--- /dev/null
+++ b/internal/buildbinary/bin.go
@@ -0,0 +1,60 @@
+// Copyright 2022 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 buildbinary
+
+import (
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/pkgsite-metrics/internal/derrors"
+)
+
+// TODO: Consider making struct if we want to pass successful binaries & still
+// be aware if building others failed for some reason.
+
+// FindAndBuildBinaries finds and builds all possible binaries from a given module.
+func FindAndBuildBinaries(modulePath string) (binaries []string, err error) {
+ defer derrors.Wrap(&err, "FindAndBuildBinaries")
+ buildTargets, err := findBinaries(modulePath)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, target := range buildTargets {
+ path, err := runBuild(modulePath, target)
+ if err != nil {
+ return nil, err
+ }
+ binaries = append(binaries, path)
+ }
+ return binaries, nil
+}
+
+// runBuild takes a given module and import path and attempts to build a binary
+func runBuild(modulePath, importPath string) (binaryPath string, err error) {
+ cmd := exec.Command("go", "build", "-C", modulePath, importPath)
+ if err = cmd.Run(); err != nil {
+ return "", err
+ }
+ binaryPath = filepath.Join(modulePath, filepath.Base(importPath))
+ return binaryPath, nil
+}
+
+// findBinaries finds all packages that compile to binaries in a given directory
+// and returns a list of those package's import paths.
+func findBinaries(dir string) (buildTargets []string, err error) {
+ // Running go list with the given arguments only prints the import paths of
+ // packages with package "main", that is packages that could potentially
+ // be built into binaries.
+ cmd := exec.Command("go", "list", "-f", `{{ if eq .Name "main" }} {{ .ImportPath }} {{end}}`, "./...")
+ cmd.Dir = dir
+ out, err := cmd.Output()
+ if err != nil {
+ return nil, err
+ }
+
+ return strings.Fields(string(out)), nil
+}
diff --git a/internal/buildbinary/bin_test.go b/internal/buildbinary/bin_test.go
new file mode 100644
index 0000000..f1561c4
--- /dev/null
+++ b/internal/buildbinary/bin_test.go
@@ -0,0 +1,45 @@
+// Copyright 2022 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 buildbinary
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func less(a, b string) bool {
+ return a < b
+}
+
+func TestFindBinaries(t *testing.T) {
+ tests := []struct {
+ name string
+ dir string
+ want []string
+ wantErr bool
+ }{
+ {
+ name: "local test",
+ dir: "../testdata/module",
+ want: []string{"golang.org/vuln"},
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := findBinaries(tt.dir)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("findBinaries() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+
+ if diff := cmp.Diff(tt.want, got, cmpopts.SortSlices(less)); diff != "" {
+ t.Errorf("mismatch (-want, +got):%s", diff)
+ }
+ })
+ }
+}