Skip to content

Commit 442b400

Browse files
authored
Add tests for cgo ldflags response-file encoding for Go 1.27 (#4656)
This adds tests for the cgo argfile change for GCC compatible file parser in 1.27+ Bonus: fixes a toolchain constraint issue for windows
1 parent 7fb2bde commit 442b400

9 files changed

Lines changed: 242 additions & 4 deletions

File tree

go/private/platforms.bzl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,13 @@ def _generate_platforms():
179179
))
180180
if (goos, goarch) in CGO_GOOS_GOARCH:
181181
# On Windows, Bazel will pick an MSVC toolchain unless we
182-
# specifically request mingw or msys.
183-
mingw = ["@bazel_tools//tools/cpp:mingw"] if goos == "windows" else []
182+
# specifically request mingw or msys. Bazel's built-in C++
183+
# configuration and rules_cc use separate constraint settings, so
184+
# include both constraints to support either toolchain source.
185+
mingw = [
186+
"@bazel_tools//tools/cpp:mingw",
187+
"@rules_cc//cc/private/toolchain:mingw",
188+
] if goos == "windows" else []
184189
platforms.append(struct(
185190
name = goos + "_" + goarch + "_cgo",
186191
goos = goos,

go/tools/builders/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ load("//go:def.bzl", "go_binary", "go_source", "go_test")
22
load("//go/private:common.bzl", "RULES_GO_STDLIB_PREFIX")
33
load("//go/private/rules:transition.bzl", "go_reset_target")
44

5+
go_test(
6+
name = "cgo_response_test",
7+
size = "small",
8+
srcs = [
9+
"cgo_response.go",
10+
"cgo_response_test.go",
11+
],
12+
)
13+
514
go_test(
615
name = "filter_test",
716
size = "small",
@@ -103,6 +112,7 @@ filegroup(
103112
"builder.go",
104113
"cc.go",
105114
"cgo2.go",
115+
"cgo_response.go",
106116
"compilepkg.go",
107117
"constants.go",
108118
"cover.go",

go/tools/builders/cgo2.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,7 @@ func copyOrLinkFile(inPath, outPath string) error {
530530
func formatLdFlagsFileContent(flags string) string {
531531
shouldEscape, _ := onVersionOrHigher(27)
532532
if shouldEscape {
533-
escaped := strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(flags)
534-
return `"` + escaped + `"` + "\n"
533+
return encodeResponseFileArg(flags) + "\n"
535534
}
536535
return flags
537536
}

go/tools/builders/cgo_response.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2026 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import "strings"
18+
19+
// encodeResponseFileArg encodes one argv entry using Go 1.27's
20+
// GCC-compatible response-file format.
21+
func encodeResponseFileArg(arg string) string {
22+
if arg == "" {
23+
return `""`
24+
}
25+
if !strings.ContainsAny(arg, " \t\n\r'\"\\$`") {
26+
return arg
27+
}
28+
29+
var b strings.Builder
30+
b.WriteByte('"')
31+
for _, r := range arg {
32+
switch r {
33+
case '\\':
34+
b.WriteString(`\\`)
35+
case '"':
36+
b.WriteString(`\"`)
37+
case '$':
38+
b.WriteString(`\$`)
39+
case '`':
40+
b.WriteString("\\`")
41+
default:
42+
b.WriteRune(r)
43+
}
44+
}
45+
b.WriteByte('"')
46+
return b.String()
47+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2026 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import "testing"
18+
19+
func TestEncodeResponseFileArg(t *testing.T) {
20+
for _, tc := range []struct {
21+
name string
22+
arg string
23+
want string
24+
}{
25+
{
26+
name: "empty",
27+
arg: "",
28+
want: `""`,
29+
},
30+
{
31+
name: "unchanged without special characters",
32+
arg: "-pthread",
33+
want: "-pthread",
34+
},
35+
{
36+
name: "keeps whitespace in one argument",
37+
arg: "-target x86_64-linux-gnu --sysroot=/dev/null",
38+
want: `"-target x86_64-linux-gnu --sysroot=/dev/null"`,
39+
},
40+
{
41+
name: "escapes special characters",
42+
arg: `-Wl,-rpath,$ORIGIN -X "quoted" C:\tmp\lib`,
43+
want: `"-Wl,-rpath,\$ORIGIN -X \"quoted\" C:\\tmp\\lib"`,
44+
},
45+
} {
46+
t.Run(tc.name, func(t *testing.T) {
47+
if got := encodeResponseFileArg(tc.arg); got != tc.want {
48+
t.Fatalf("encodeResponseFileArg(%q) = %q; want %q", tc.arg, got, tc.want)
49+
}
50+
})
51+
}
52+
}

tests/integration/README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Contents
1515
.. Child list start
1616
1717
* `Gazelle functionality <gazelle/README.rst>`_
18+
* `Go 1.27 cgo linker flags <cgo_ldflags/README.rst>`_
1819
* `Popular repository tests <popular_repos/README.rst>`_
1920
* `Reproducibility <reproducibility/README.rst>`_
2021
* `Functionality related to @go_googleapis <googleapis/README.rst>`_
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test")
2+
3+
go_bazel_test(
4+
name = "cgo_ldflags_test",
5+
size = "medium",
6+
srcs = ["cgo_ldflags_test.go"],
7+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Go 1.27 cgo linker flags
2+
=========================
3+
4+
Verifies that a cgo target built with Go 1.27rc1 and a C/C++ toolchain builds
5+
correctly when it receives multiple linker flags.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2026 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cgo_ldflags_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
21+
)
22+
23+
func TestMain(m *testing.M) {
24+
bazel_testing.TestMain(m, bazel_testing.Args{
25+
Main: `
26+
-- BUILD.bazel --
27+
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
28+
load("@rules_cc//cc:cc_library.bzl", "cc_library")
29+
30+
cc_library(
31+
name = "answer",
32+
srcs = ["answer.c"],
33+
hdrs = ["answer.h"],
34+
)
35+
36+
go_binary(
37+
name = "cgo_ldflags",
38+
srcs = ["main.go"],
39+
cdeps = [":answer"],
40+
cgo = True,
41+
# Go 1.27 expands response files before parsing cgo flags. Keep multiple
42+
# linker flags here so an unquoted response file fails the build.
43+
clinkopts = [
44+
"-g",
45+
"-v",
46+
],
47+
pure = "off",
48+
)
49+
50+
-- answer.h --
51+
int answer(void);
52+
53+
-- answer.c --
54+
#include "answer.h"
55+
56+
int answer(void) {
57+
return 42;
58+
}
59+
60+
-- main.go --
61+
package main
62+
63+
/*
64+
#include "answer.h"
65+
*/
66+
import "C"
67+
68+
import (
69+
"fmt"
70+
"runtime"
71+
)
72+
73+
func main() {
74+
if got := runtime.Version(); got != "go1.27rc1" {
75+
panic(fmt.Sprintf("built with %s, want go1.27rc1", got))
76+
}
77+
if got := int(C.answer()); got != 42 {
78+
panic(fmt.Sprintf("C answer = %d, want 42", got))
79+
}
80+
}
81+
`,
82+
ModuleFileSuffix: `
83+
bazel_dep(name = "rules_cc", version = "0.1.5")
84+
85+
cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
86+
use_repo(cc_configure, "local_config_cc")
87+
88+
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
89+
go_sdk.download(
90+
name = "go_1_27_rc1",
91+
sdks = {
92+
"darwin_amd64": ["go1.27rc1.darwin-amd64.tar.gz", "ec2abfa675a1882a13fd72035bdc50635b5b4a2b424c1f692a5737b0a333a322"],
93+
"darwin_arm64": ["go1.27rc1.darwin-arm64.tar.gz", "7b7b4d66fc0a7bc8e57b3602340dfef46d4f5f95fc5d30e5c5af6a671830de54"],
94+
"linux_amd64": ["go1.27rc1.linux-amd64.tar.gz", "102a6055d682b1f233bc1741122cc6fddae7a7dded1305fbcc30079984187144"],
95+
"linux_arm64": ["go1.27rc1.linux-arm64.tar.gz", "e9338b657430c7c32fffec696ce7d7b0286f99b65f8f90a2f5a6781a34f34928"],
96+
"windows_amd64": ["go1.27rc1.windows-amd64.zip", "10d2c755c76ca94008558bb9ec93154529ea1c0a38abe938d9b6406a9d18ffe3"],
97+
},
98+
version = "1.27rc1",
99+
)
100+
`,
101+
})
102+
}
103+
104+
func TestGo127CgoWithMultipleLinkerFlags(t *testing.T) {
105+
if err := bazel_testing.RunBazel(
106+
"run",
107+
"--@io_bazel_rules_go//go/toolchain:sdk_version=1.27rc1",
108+
"//:cgo_ldflags",
109+
); err != nil {
110+
t.Fatal(err)
111+
}
112+
}

0 commit comments

Comments
 (0)