Skip to content

Commit 7bf46c1

Browse files
dgoelDhiraj Goeljayconrod
authored
Pass large ldflags to cgo via response file instead of env variable. (#4386)
**What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** Pass ldflags to cgo tool via a response file instead of `CGO_LDFLAGS` env variable for go 1.23+. This avoids the "argument list too long" error, which can happen if `CGO_LDFLAGS` is so large that it exceeds the system limits. Support to accept ldflags via file was added to `go` more than a year ago. References: - https://go-review.googlesource.com/c/go/+/584655 - Discussion: golang/go#66456 **Which issues(s) does this PR fix?** Fixes #2654 --------- Co-authored-by: Dhiraj Goel <[email protected]> Co-authored-by: Jay Conrod <[email protected]>
1 parent 98940c3 commit 7bf46c1

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

go/tools/builders/cgo2.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"os"
2929
"path/filepath"
3030
"runtime"
31+
"strconv"
3132
"strings"
3233
)
3334

@@ -95,7 +96,39 @@ func cgo2(goenv *env, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSr
9596
}
9697
}
9798
combinedLdFlags = append(combinedLdFlags, defaultLdFlags()...)
98-
os.Setenv("CGO_LDFLAGS", strings.Join(combinedLdFlags, " "))
99+
100+
// go 1.23+ supports ldflags file.
101+
// https://go-review.googlesource.com/c/go/+/584655
102+
canUseLdflagsFile, err := onVersionOrHigher(23)
103+
if err != nil {
104+
return "", nil, nil, err
105+
}
106+
107+
var ldflagsFile *os.File
108+
combinedLdFlagsStr := strings.Join(combinedLdFlags, " ")
109+
if canUseLdflagsFile {
110+
// Write linker flags to a temporary file instead of pasing via env variable.
111+
// This avoids "argument list too long" error with extremely large CGO_LDFLAGS
112+
// that can exceed system limits.
113+
// Future versions of `go` may remove support for CGO_LDFLAGS entirely, so
114+
// use file even for small ldflags.
115+
// https://go-review.googlesource.com/c/go/+/596615
116+
ldflagsFile, err = os.CreateTemp("", "cgo-ldflags-*.txt")
117+
if err != nil {
118+
return "", nil, nil, fmt.Errorf("failed to create temporary file for ldflags: %w", err)
119+
}
120+
defer os.Remove(ldflagsFile.Name())
121+
if _, err := ldflagsFile.WriteString(combinedLdFlagsStr); err != nil {
122+
ldflagsFile.Close()
123+
return "", nil, nil, fmt.Errorf("failed to write ldflags to temporary file: %w", err)
124+
}
125+
if err := ldflagsFile.Close(); err != nil {
126+
return "", nil, nil, fmt.Errorf("failed to close temporary ldflags file: %w", err)
127+
}
128+
} else {
129+
// Fallback to env variable for backwards compatibility with older `go` versions.
130+
os.Setenv("CGO_LDFLAGS", combinedLdFlagsStr)
131+
}
99132

100133
// If cgo sources are in different directories, gather them into a temporary
101134
// directory so we can use -srcdir.
@@ -142,6 +175,10 @@ func cgo2(goenv *env, goSrcs, cgoSrcs, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSr
142175
}
143176
// Trim the execroot from the //line comments emitted by cgo.
144177
args := goenv.goTool("cgo", "-srcdir", srcDir, "-objdir", workDir, "-trimpath", execRoot)
178+
if ldflagsFile != nil {
179+
// The "@" prefix tells cgo to read arguments from the file.
180+
args = append(args, "-ldflags", "@"+ldflagsFile.Name())
181+
}
145182
if packagePath != "" {
146183
args = append(args, "-importpath", packagePath)
147184
}
@@ -438,3 +475,18 @@ func copyOrLinkFile(inPath, outPath string) error {
438475
return linkFile(inPath, outPath)
439476
}
440477
}
478+
479+
func onVersionOrHigher(version int) (bool, error) {
480+
v := runtime.Version()
481+
m := versionExp.FindStringSubmatch(v)
482+
if len(m) != 2 {
483+
return false, fmt.Errorf("failed to match against Go version %q", v)
484+
}
485+
mvStr := m[1]
486+
mv, err := strconv.Atoi(mvStr)
487+
if err != nil {
488+
return false, fmt.Errorf("convert minor version %q to int: %w", mvStr, err)
489+
}
490+
491+
return mv >= version, nil
492+
}

tests/core/cgo/BUILD.bazel

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,30 @@ go_binary(
331331
cgo = True,
332332
)
333333

334+
LARGE_EXT_LDFLAGS = [
335+
flag
336+
for i in range(10000)
337+
for flag in [
338+
"-extldflags",
339+
"-Wl,-rpath,pathtolib{}".format(i),
340+
]
341+
]
342+
343+
go_binary(
344+
name = "binary_with_rpath",
345+
srcs = [
346+
"bar.cc",
347+
"bar.go",
348+
],
349+
out = "binary_with_rpath",
350+
cgo = True,
351+
gc_linkopts = LARGE_EXT_LDFLAGS,
352+
target_compatible_with = [
353+
"@platforms//os:linux",
354+
"@platforms//os:macos",
355+
],
356+
)
357+
334358
go_binary(
335359
name = "cc_deps",
336360
srcs = ["bar.go"],

0 commit comments

Comments
 (0)