From b319d3a232fdecf5a6f7589ed00ffffea1834ec9 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 1 Sep 2026 08:03:09 +0200 Subject: [PATCH 1/2] [go] Auto-detect bundler package name --- go/cmd/bundler/main.go | 60 +++++++++++++++++++++++++++++++++---- go/cmd/bundler/main_test.go | 44 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/go/cmd/bundler/main.go b/go/cmd/bundler/main.go index fb11ff8699..9211695a44 100644 --- a/go/cmd/bundler/main.go +++ b/go/cmd/bundler/main.go @@ -19,6 +19,8 @@ import ( "encoding/json" "flag" "fmt" + "go/parser" + "go/token" "io" "net/http" "os" @@ -33,10 +35,11 @@ import ( const ( // Keep these URLs centralized so reviewers can verify all outbound calls in one place. - sdkModule = "github.com/github/copilot-sdk/go" - packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json" - tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz" - licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz" + sdkModule = "github.com/github/copilot-sdk/go" + packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json" + tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz" + licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz" + defaultPackageName = "main" ) // Platform info: npm package suffix, binary name @@ -89,6 +92,11 @@ func main() { return } + pkgName, err := detectPackageName(*output) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to detect package name: %v; using package %s\n", err, pkgName) + } + fmt.Printf("Building bundle for %s (CLI version %s)\n", *platform, version) bundle, err := buildBundle(info, version, outputPath, goos) @@ -137,7 +145,7 @@ func main() { muslBundle.wrapperHash, muslBundle.assetsArtifactPath, muslBundle.assetsHash, - "main", + pkgName, ); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) @@ -193,6 +201,48 @@ func validPlatforms() []string { return result } +// detectPackageName reads package clauses from the output directory. +// It returns defaultPackageName with an error when detection fails. +func detectPackageName(dir string) (string, error) { + if dir == "" { + dir = "." + } + + entries, err := os.ReadDir(dir) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to read package directory %q: %w", dir, err) + } + + packageName := "" + for _, entry := range entries { + name := entry.Name() + if entry.IsDir() || !strings.HasSuffix(name, ".go") || + strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || + strings.HasSuffix(name, "_test.go") || strings.HasPrefix(name, "zcopilot_") { + continue + } + + path := filepath.Join(dir, name) + file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.PackageClauseOnly) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to parse package clause in %q: %w", path, err) + } + + if packageName == "" { + packageName = file.Name.Name + continue + } + if packageName != file.Name.Name { + return defaultPackageName, fmt.Errorf("multiple packages %q and %q found in %q", packageName, file.Name.Name, dir) + } + } + + if packageName == "" { + return defaultPackageName, fmt.Errorf("no Go package found in %q", dir) + } + return packageName, nil +} + // detectCLIVersion detects the CLI version by: // 1. Running "go list -m" to get the copilot-sdk version from the user's go.mod // 2. Fetching the package-lock.json from the SDK repo at that version diff --git a/go/cmd/bundler/main_test.go b/go/cmd/bundler/main_test.go index 0a7f1c3876..e1e2db28b1 100644 --- a/go/cmd/bundler/main_test.go +++ b/go/cmd/bundler/main_test.go @@ -101,6 +101,50 @@ func readTarGz(t *testing.T, path string) map[string]string { } } +func TestDetectPackageName(t *testing.T) { + dir := t.TempDir() + files := map[string]string{ + "app.go": "package application\n", + "app_test.go": "package application_test\n", + "zcopilot_linux_amd64.go": "package main\n", + "_ignored.go": "package ignored\n", + "zcopilot_inprocess_linux.go": "package main\n", + } + for name, content := range files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + got, err := detectPackageName(dir) + if err != nil { + t.Fatal(err) + } + if got != "application" { + t.Fatalf("detectPackageName() = %q, want %q", got, "application") + } +} + +func TestDetectPackageNameFallsBackForMultiplePackages(t *testing.T) { + dir := t.TempDir() + for name, content := range map[string]string{ + "one.go": "package one\n", + "two.go": "package two\n", + } { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + got, err := detectPackageName(dir) + if err == nil { + t.Fatal("detectPackageName() succeeded for a directory containing multiple packages") + } + if got != defaultPackageName { + t.Fatalf("detectPackageName() = %q, want fallback %q", got, defaultPackageName) + } +} + func TestGenerateGoFileEmbedsRuntimeWrapperPair(t *testing.T) { dir := t.TempDir() binaryPath := filepath.Join(dir, "copilot.zst") From 8ff81bd168abcc7793bf812f365fbc04045a9b47 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Tue, 1 Sep 2026 09:51:14 +0200 Subject: [PATCH 2/2] [go] Respect target constraints in package detection --- go/cmd/bundler/main.go | 21 +++++++++++++++++---- go/cmd/bundler/main_test.go | 28 ++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/go/cmd/bundler/main.go b/go/cmd/bundler/main.go index 9211695a44..e7bbd7a64e 100644 --- a/go/cmd/bundler/main.go +++ b/go/cmd/bundler/main.go @@ -19,6 +19,7 @@ import ( "encoding/json" "flag" "fmt" + "go/build" "go/parser" "go/token" "io" @@ -92,7 +93,7 @@ func main() { return } - pkgName, err := detectPackageName(*output) + pkgName, err := detectPackageName(*output, goos, goarch) if err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to detect package name: %v; using package %s\n", err, pkgName) } @@ -201,9 +202,10 @@ func validPlatforms() []string { return result } -// detectPackageName reads package clauses from the output directory. -// It returns defaultPackageName with an error when detection fails. -func detectPackageName(dir string) (string, error) { +// detectPackageName reads package clauses from files that match the target +// platform and build constraints. It returns defaultPackageName with an error +// when detection fails. +func detectPackageName(dir, goos, goarch string) (string, error) { if dir == "" { dir = "." } @@ -213,6 +215,10 @@ func detectPackageName(dir string) (string, error) { return defaultPackageName, fmt.Errorf("failed to read package directory %q: %w", dir, err) } + buildContext := build.Default + buildContext.GOOS = goos + buildContext.GOARCH = goarch + packageName := "" for _, entry := range entries { name := entry.Name() @@ -221,6 +227,13 @@ func detectPackageName(dir string) (string, error) { strings.HasSuffix(name, "_test.go") || strings.HasPrefix(name, "zcopilot_") { continue } + matches, err := buildContext.MatchFile(dir, name) + if err != nil { + return defaultPackageName, fmt.Errorf("failed to evaluate build constraints in %q: %w", filepath.Join(dir, name), err) + } + if !matches { + continue + } path := filepath.Join(dir, name) file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.PackageClauseOnly) diff --git a/go/cmd/bundler/main_test.go b/go/cmd/bundler/main_test.go index e1e2db28b1..f41afe43f5 100644 --- a/go/cmd/bundler/main_test.go +++ b/go/cmd/bundler/main_test.go @@ -104,8 +104,10 @@ func readTarGz(t *testing.T, path string) map[string]string { func TestDetectPackageName(t *testing.T) { dir := t.TempDir() files := map[string]string{ - "app.go": "package application\n", + "app_linux.go": "package application\n", "app_test.go": "package application_test\n", + "app_windows.go": "package windowsapplication\n", + "tagged.go": "//go:build windows\n\npackage windowsapplication\n", "zcopilot_linux_amd64.go": "package main\n", "_ignored.go": "package ignored\n", "zcopilot_inprocess_linux.go": "package main\n", @@ -116,12 +118,22 @@ func TestDetectPackageName(t *testing.T) { } } - got, err := detectPackageName(dir) - if err != nil { - t.Fatal(err) - } - if got != "application" { - t.Fatalf("detectPackageName() = %q, want %q", got, "application") + for _, test := range []struct { + goos string + want string + }{ + {goos: "linux", want: "application"}, + {goos: "windows", want: "windowsapplication"}, + } { + t.Run(test.goos, func(t *testing.T) { + got, err := detectPackageName(dir, test.goos, "amd64") + if err != nil { + t.Fatal(err) + } + if got != test.want { + t.Fatalf("detectPackageName() = %q, want %q", got, test.want) + } + }) } } @@ -136,7 +148,7 @@ func TestDetectPackageNameFallsBackForMultiplePackages(t *testing.T) { } } - got, err := detectPackageName(dir) + got, err := detectPackageName(dir, "linux", "amd64") if err == nil { t.Fatal("detectPackageName() succeeded for a directory containing multiple packages") }