Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions tool/internal/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ var flagsWithPathValues = map[string]bool{
"-toolexec": true,
}

// consumeCFlagPositional consumes -C only when it appears as the first
// argument in args, matching Go toolchain semantics (see handleChdirFlag).
// Returns ("", args) if -C is not present at position 0.
func consumeCFlagPositional(args []string) (string, []string) {
if len(args) == 0 {
return "", args
}
if strings.HasPrefix(args[0], "-C=") {
return strings.TrimPrefix(args[0], "-C="), args[1:]
}
if args[0] == "-C" && len(args) > 1 {
return args[1], args[2:]
}
Comment on lines +85 to +90
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return "", args
}


// GetBuildPackages loads all packages from the otelc go build/install or otelc setup command arguments.
// Returns a list of loaded packages. If no package patterns are found in args,
// defaults to loading the current directory package.
Expand Down Expand Up @@ -147,6 +164,15 @@ func Setup(ctx context.Context, cmd *cli.Command) error {
args = cmd.Args().Tail() // trim build/install
}

// Honor -C <dir>: consume only when it is the first argument, matching
// Go toolchain semantics. os.Chdir does not affect the parent shell.
if dir, rest := consumeCFlagPositional(args); dir != "" {
if err := os.Chdir(dir); err != nil {
return ex.Wrapf(err, "changing to -C directory %s", dir)
}
args = rest
}

logger := util.LoggerFromContext(ctx)

if isSetup() {
Expand Down Expand Up @@ -340,6 +366,12 @@ func BuildWithToolexec(ctx context.Context, cmd *cli.Command) error {
args := cmd.Args().Slice()
logger := util.LoggerFromContext(ctx)

// Setup already called os.Chdir for -C; strip it from the tail so it is
// not forwarded to the underlying "go build -toolexec=..." invocation.
// consumeCFlagPositional only removes -C at position 0 of the slice, which
// matches what Go's handleChdirFlag accepts.
_, tail := consumeCFlagPositional(args[1:])

// Add -toolexec=otelc to the original build command and run it
execPath, err := os.Executable()
if err != nil {
Expand All @@ -355,8 +387,8 @@ func BuildWithToolexec(ctx context.Context, cmd *cli.Command) error {
newArgs = append(newArgs, "-work")
// Add "-toolexec=..."
newArgs = append(newArgs, insert)
// Add the rest
newArgs = append(newArgs, args[1:]...)
// Add the rest (without -C which was already consumed by Setup)
newArgs = append(newArgs, tail...)
logger.InfoContext(ctx, "Running go build with toolexec", "args", newArgs)

// Tell the sub-process the working directory
Expand Down
109 changes: 109 additions & 0 deletions tool/internal/setup/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,112 @@ func TestExtractBuildFlags(t *testing.T) {
})
}
}

func TestConsumeCFlagPositional(t *testing.T) {
tests := []struct {
name string
args []string
expectedDir string
expectedArgs []string
}{
{
name: "space-separated form",
args: []string{"-C", "/some/dir", "./..."},
expectedDir: "/some/dir",
expectedArgs: []string{"./..."},
},
{
name: "equals form",
args: []string{"-C=/some/dir", "./..."},
expectedDir: "/some/dir",
expectedArgs: []string{"./..."},
},
{
name: "not at position 0 - not consumed",
args: []string{"-v", "-C", "/some/dir", "./..."},
expectedDir: "",
expectedArgs: []string{"-v", "-C", "/some/dir", "./..."},
},
{
name: "not present",
args: []string{"./..."},
expectedDir: "",
expectedArgs: []string{"./..."},
},
{
name: "empty args",
args: []string{},
expectedDir: "",
expectedArgs: []string{},
},
{
name: "-C at end with no value - not consumed",
args: []string{"-C"},
expectedDir: "",
expectedArgs: []string{"-C"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir, rest := consumeCFlagPositional(tt.args)
if dir != tt.expectedDir {
t.Errorf("dir = %q, want %q", dir, tt.expectedDir)
}
if !slices.Equal(rest, tt.expectedArgs) {
t.Errorf("rest = %v, want %v", rest, tt.expectedArgs)
}
})
}
}

func TestGetPackagesWithCFlag(t *testing.T) {
// Create a Go module in a separate temp directory.
moduleDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(moduleDir, "cmd"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(moduleDir, "cmd", "main.go"),
[]byte("package main\n\nfunc main() {}\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"),
[]byte("module cflagmodule\n\ngo 1.21\n"), 0o644); err != nil {
t.Fatal(err)
}

// Simulate what Setup() does: consume -C, chdir, strip from args.
args := []string{"-C", moduleDir, "./cmd"}
dir, rest := consumeCFlagPositional(args)
if dir == "" {
t.Fatal("expected -C to be consumed")
}

orig, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
defer os.Chdir(orig) //nolint:errcheck

if err := os.Chdir(dir); err != nil {
t.Fatalf("chdir failed: %v", err)
}

pkgs, err := getBuildPackages(t.Context(), rest)
if err != nil {
t.Fatalf("getBuildPackages failed: %v", err)
}
if len(pkgs) == 0 {
t.Fatal("expected at least one package")
}

found := false
for _, pkg := range pkgs {
if strings.Contains(pkg.ID, "cflagmodule/cmd") {
found = true
break
}
}
if !found {
t.Errorf("cflagmodule/cmd not found in packages: %v", pkgs)
}
}
Loading