Skip to content

Commit

Permalink
cmd/cgo, cmd/go: add cgo -ldflags option, use it in cmd/go
Browse files Browse the repository at this point in the history
This will automatically use a response file if ldflags is long,
avoiding "argument list too long" errors with a very large CGO_LDFLAGS.

Fixes #66456

Change-Id: I5f9ee86e03b4e6d6430f7f9d8357ef37a9c22465
Reviewed-on: https://go-review.googlesource.com/c/go/+/584655
Reviewed-by: Michael Matloob <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
Commit-Queue: Ian Lance Taylor <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Auto-Submit: Ian Lance Taylor <[email protected]>
  • Loading branch information
ianlancetaylor authored and gopherbot committed May 13, 2024
1 parent f449452 commit a32e94d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/cmd/cgo/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ The following options are available when running cgo directly:
Write out input file in Go syntax replacing C package
names with real values. Used to generate files in the
syscall package when bootstrapping a new target.
-ldflags flags
Flags to pass to the C linker. The cmd/go tool uses
this to pass in the flags in the CGO_LDFLAGS variable.
-objdir directory
Put all generated files in directory.
-srcdir directory
Expand Down
10 changes: 6 additions & 4 deletions src/cmd/cgo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ var objDir = flag.String("objdir", "", "object directory")
var importPath = flag.String("importpath", "", "import path of package being built (for comments in generated files)")
var exportHeader = flag.String("exportheader", "", "where to write export header if any exported functions")

var ldflags = flag.String("ldflags", "", "flags to pass to C linker")

var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo")
var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo")
Expand Down Expand Up @@ -328,11 +330,11 @@ func main() {
os.Exit(2)
}

// Record CGO_LDFLAGS from the environment for external linking.
if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" {
args, err := splitQuoted(ldflags)
// Record linker flags for external linking.
if *ldflags != "" {
args, err := splitQuoted(*ldflags)
if err != nil {
fatalf("bad CGO_LDFLAGS: %q (%s)", ldflags, err)
fatalf("bad -ldflags option: %q (%s)", *ldflags, err)
}
p.addToFlag("LDFLAGS", args)
}
Expand Down
13 changes: 10 additions & 3 deletions src/cmd/go/internal/work/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2812,20 +2812,27 @@ func (b *Builder) cgo(a *Action, cgoExe, objdir string, pcCFLAGS, pcLDFLAGS, cgo
cgoflags = append(cgoflags, "-import_syscall=false")
}

// Update $CGO_LDFLAGS with p.CgoLDFLAGS.
// cgoLDFLAGS, which includes p.CgoLDFLAGS, can be very long.
// Pass it to cgo on the command line, so that we use a
// response file if necessary.
//
// These flags are recorded in the generated _cgo_gotypes.go file
// using //go:cgo_ldflag directives, the compiler records them in the
// object file for the package, and then the Go linker passes them
// along to the host linker. At this point in the code, cgoLDFLAGS
// consists of the original $CGO_LDFLAGS (unchecked) and all the
// flags put together from source code (checked).
cgoenv := b.cCompilerEnv()
var ldflagsOption []string
if len(cgoLDFLAGS) > 0 {
flags := make([]string, len(cgoLDFLAGS))
for i, f := range cgoLDFLAGS {
flags[i] = strconv.Quote(f)
}
cgoenv = append(cgoenv, "CGO_LDFLAGS="+strings.Join(flags, " "))
ldflagsOption = []string{"-ldflags=" + strings.Join(flags, " ")}

// Remove CGO_LDFLAGS from the environment.
cgoenv = append(cgoenv, "CGO_LDFLAGS=")
}

if cfg.BuildToolchainName == "gccgo" {
Expand Down Expand Up @@ -2863,7 +2870,7 @@ func (b *Builder) cgo(a *Action, cgoExe, objdir string, pcCFLAGS, pcLDFLAGS, cgo
cgoflags = append(cgoflags, "-trimpath", strings.Join(trimpath, ";"))
}

if err := sh.run(p.Dir, p.ImportPath, cgoenv, cfg.BuildToolexec, cgoExe, "-objdir", objdir, "-importpath", p.ImportPath, cgoflags, "--", cgoCPPFLAGS, cgoCFLAGS, cgofiles); err != nil {
if err := sh.run(p.Dir, p.ImportPath, cgoenv, cfg.BuildToolexec, cgoExe, "-objdir", objdir, "-importpath", p.ImportPath, cgoflags, ldflagsOption, "--", cgoCPPFLAGS, cgoCFLAGS, cgofiles); err != nil {
return nil, nil, err
}
outGo = append(outGo, gofiles...)
Expand Down
46 changes: 46 additions & 0 deletions src/cmd/go/testdata/script/cgo_long_cmd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Issue #66456

[!cgo] skip
[GOOS:windows] skip
[GOOS:plan9] skip

# Generate a file with a very long #cgo LDFLAGS line.
# This used to cause "go build" to fail with "argument list too long".
go generate

# Build with the generated file.
go build

-- go.mod --
module cgolongcmd

go 1.22
-- generate.go --
//go:build ignore

package main

import (
"fmt"
"log"
"os"
"bytes"
)

func main() {
var buf bytes.Buffer
buf.WriteString("package p\n")
buf.WriteString("// #cgo LDFLAGS:")
for i := range 10000 {
fmt.Fprintf(&buf, " -Wl,-rpath,/nonexistentpath/%d", i)
}
buf.WriteString("\n")
buf.WriteString(`import "C"`+"\n")
if err := os.WriteFile("generated.go", buf.Bytes(), 0o644); err != nil {
log.Fatal(err)
}
}
-- gen.go --
package p

//go:generate go run generate.go

0 comments on commit a32e94d

Please sign in to comment.