diff --git a/pkg/build/builders/go.go b/pkg/build/builders/go.go index 5dede8c..b417954 100644 --- a/pkg/build/builders/go.go +++ b/pkg/build/builders/go.go @@ -3,6 +3,7 @@ package builders import ( "context" + "os" "runtime" "strings" @@ -193,6 +194,8 @@ func (b *GoBuilder) resolveGarbleCli(paths ...string) (string, error) { "/opt/homebrew/bin/garble", } + paths = append(paths, garbleInstallPaths()...) + if home := core.Env("HOME"); home != "" { paths = append(paths, ax.Join(home, "go", "bin", "garble")) } @@ -206,6 +209,27 @@ func (b *GoBuilder) resolveGarbleCli(paths ...string) (string, error) { return command, nil } +// garbleInstallPaths returns the standard Go install locations for garble. +func garbleInstallPaths() []string { + var paths []string + + if gobin := core.Env("GOBIN"); gobin != "" { + paths = append(paths, ax.Join(gobin, "garble")) + } + + if gopath := core.Env("GOPATH"); gopath != "" { + for _, root := range strings.Split(gopath, string(os.PathListSeparator)) { + root = strings.TrimSpace(root) + if root == "" { + continue + } + paths = append(paths, ax.Join(root, "bin", "garble")) + } + } + + return paths +} + // hasVersionLDFlag reports whether a version linker flag is already present. func hasVersionLDFlag(ldflags []string) bool { for _, flag := range ldflags { diff --git a/pkg/build/builders/go_test.go b/pkg/build/builders/go_test.go index 208d654..1b99931 100644 --- a/pkg/build/builders/go_test.go +++ b/pkg/build/builders/go_test.go @@ -100,6 +100,69 @@ exec go "$@" require.NoError(t, err) } +func setupFakeGoBinary(t *testing.T, binDir string) { + t.Helper() + + goScript := `#!/bin/sh +set -eu + +log_file="${GO_BUILD_LOG_FILE:-}" +if [ -n "$log_file" ]; then + printf '%s\n' "$@" > "$log_file" +fi + +env_log_file="${GO_BUILD_ENV_LOG_FILE:-}" +if [ -n "$env_log_file" ]; then + env | sort > "$env_log_file" +fi + +if [ "${GOARCH:-}" = "invalid_arch" ]; then + exit 1 +fi + +if [ -f main.go ] && grep -q "not valid go code" main.go; then + exit 1 +fi + +output="" +previous="" +for argument in "$@"; do + if [ "$previous" = "-o" ]; then + output="$argument" + break + fi + previous="$argument" +done + +if [ -n "$output" ]; then + mkdir -p "$(dirname "$output")" + printf 'fake binary\n' > "$output" + chmod +x "$output" +fi +` + + err := ax.WriteFile(ax.Join(binDir, "go"), []byte(goScript), 0o755) + require.NoError(t, err) +} + +func setupFakeGarbleBinary(t *testing.T, binDir string) { + t.Helper() + + garbleScript := `#!/bin/sh +set -eu + +log_file="${GARBLE_LOG_FILE:-}" +if [ -n "$log_file" ]; then + printf '%s\n' "$@" > "$log_file" +fi + +exec go "$@" +` + + err := ax.WriteFile(ax.Join(binDir, "garble"), []byte(garbleScript), 0o755) + require.NoError(t, err) +} + func TestGo_GoBuilderName_Good(t *testing.T) { builder := NewGoBuilder() assert.Equal(t, "go", builder.Name()) @@ -568,6 +631,50 @@ func TestGo_GoBuilderBuild_Good(t *testing.T) { assert.Contains(t, args, ".") }) + t.Run("finds garble in GOBIN when it is not on PATH", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("garble test helper uses a shell script") + } + + goDir := t.TempDir() + setupFakeGoBinary(t, goDir) + t.Setenv("PATH", goDir+string(os.PathListSeparator)+"/usr/bin"+string(os.PathListSeparator)+"/bin") + + garbleDir := t.TempDir() + setupFakeGarbleBinary(t, garbleDir) + t.Setenv("GOBIN", garbleDir) + + projectDir := setupGoTestProject(t) + outputDir := t.TempDir() + logDir := t.TempDir() + logPath := ax.Join(logDir, "garble-gobin.log") + + t.Setenv("GARBLE_LOG_FILE", logPath) + + builder := NewGoBuilder() + cfg := &build.Config{ + FS: io.Local, + ProjectDir: projectDir, + OutputDir: outputDir, + Name: "obfuscated-gobin", + Obfuscate: true, + } + targets := []build.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}} + + artifacts, err := builder.Build(context.Background(), cfg, targets) + require.NoError(t, err) + require.Len(t, artifacts, 1) + assert.FileExists(t, artifacts[0].Path) + + content, err := ax.ReadFile(logPath) + require.NoError(t, err) + + args := strings.Split(strings.TrimSpace(string(content)), "\n") + require.NotEmpty(t, args) + assert.Equal(t, "build", args[0]) + assert.Contains(t, args, "-trimpath") + }) + t.Run("builds the configured main package path", func(t *testing.T) { projectDir := setupGoTestProject(t) err := ax.MkdirAll(ax.Join(projectDir, "cmd", "myapp"), 0755)