Skip to content

Commit 9023ca1

Browse files
committed
cmd/go: don't run cgo compiler version probe under -toolexec
The -toolexec build flag wraps each toolchain program (compile, asm, link, ...) in a user-supplied program, e.g. to profile them or swap in alternates. To know when to rebuild a cgo package, the go command records the C, C++, and Fortran compiler versions by probing each compiler: cc -### -x c -c - That probe was run under -toolexec, but the real cgo compiles never are, so the wrapper saw the probe and none of the actual compiles. Probe under -toolexec only for gccgo, a Go toolchain compiler treated like cmd/compile. Leave the C, C++, and Fortran probes unwrapped. Fixes #64580
1 parent 5703e10 commit 9023ca1

3 files changed

Lines changed: 45 additions & 10 deletions

File tree

src/cmd/go/internal/work/buildid.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (b *Builder) toolID(name string) string {
193193
//
194194
// For these tools we have no -V=full option to dump the build ID,
195195
// but we can run the tool with -v -### to reliably get the compiler proper
196-
// and hash that. That will work in the presence of -toolexec.
196+
// and hash that.
197197
//
198198
// In order to get reproducible builds for released compilers, we
199199
// detect a released compiler by the absence of "experimental" in the
@@ -203,7 +203,14 @@ func (b *Builder) toolID(name string) string {
203203
// The caller assumes that stat of the exe can be used, combined with the id,
204204
// to detect changes in the underlying compiler. The returned exe can be empty,
205205
// which means to rely only on the id.
206-
func (b *Builder) gccToolID(name, language string) (id, exe string, err error) {
206+
//
207+
// useToolexec reports whether the version probe should be run under
208+
// cfg.BuildToolexec. It is true only for gccgo, which is treated as a Go
209+
// toolchain compiler (like cmd/compile) and so is probed under -toolexec. It is
210+
// false for the C, C++, and Fortran compilers driven by cgo: those are not
211+
// otherwise invoked under -toolexec, so probing them under -toolexec would be
212+
// inconsistent (see go.dev/issue/64580).
213+
func (b *Builder) gccToolID(name, language string, useToolexec bool) (id, exe string, err error) {
207214
//TODO: Use par.Cache instead of a mutex and a map. See Builder.toolID.
208215
key := name + "." + language
209216
b.id.Lock()
@@ -218,7 +225,10 @@ func (b *Builder) gccToolID(name, language string) (id, exe string, err error) {
218225
// Invoke the driver with -### to see the subcommands and the
219226
// version strings. Use -x to set the language. Pretend to
220227
// compile an empty file on standard input.
221-
cmdline := str.StringList(cfg.BuildToolexec, name, "-###", "-x", language, "-c", "-")
228+
cmdline := str.StringList(name, "-###", "-x", language, "-c", "-")
229+
if useToolexec {
230+
cmdline = str.StringList(cfg.BuildToolexec, cmdline)
231+
}
222232
cmd := exec.Command(cmdline[0], cmdline[1:]...)
223233
// Force untranslated output so that we see the string "version".
224234
cmd.Env = append(os.Environ(), "LC_ALL=C")

src/cmd/go/internal/work/exec.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,15 +322,15 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
322322
fmt.Fprintf(h, "CC=%q %q %q %q\n", ccExe, cppflags, cflags, ldflags)
323323
// Include the C compiler tool ID so that if the C
324324
// compiler changes we rebuild the package.
325-
if ccID, _, err := b.gccToolID(ccExe[0], "c"); err == nil {
325+
if ccID, _, err := b.gccToolID(ccExe[0], "c", false); err == nil {
326326
fmt.Fprintf(h, "CC ID=%q\n", ccID)
327327
} else {
328328
fmt.Fprintf(h, "CC ID ERROR=%q\n", err)
329329
}
330330
if len(p.CXXFiles)+len(p.SwigCXXFiles) > 0 {
331331
cxxExe := b.cxxExe()
332332
fmt.Fprintf(h, "CXX=%q %q\n", cxxExe, cxxflags)
333-
if cxxID, _, err := b.gccToolID(cxxExe[0], "c++"); err == nil {
333+
if cxxID, _, err := b.gccToolID(cxxExe[0], "c++", false); err == nil {
334334
fmt.Fprintf(h, "CXX ID=%q\n", cxxID)
335335
} else {
336336
fmt.Fprintf(h, "CXX ID ERROR=%q\n", err)
@@ -339,7 +339,7 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
339339
if len(p.FFiles) > 0 {
340340
fcExe := b.fcExe()
341341
fmt.Fprintf(h, "FC=%q %q\n", fcExe, fflags)
342-
if fcID, _, err := b.gccToolID(fcExe[0], "f95"); err == nil {
342+
if fcID, _, err := b.gccToolID(fcExe[0], "f95", false); err == nil {
343343
fmt.Fprintf(h, "FC ID=%q\n", fcID)
344344
} else {
345345
fmt.Fprintf(h, "FC ID ERROR=%q\n", err)
@@ -395,15 +395,15 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID {
395395
}
396396

397397
case "gccgo":
398-
id, _, err := b.gccToolID(BuildToolchain.compiler(), "go")
398+
id, _, err := b.gccToolID(BuildToolchain.compiler(), "go", true)
399399
if err != nil {
400400
base.Fatalf("%v", err)
401401
}
402402
fmt.Fprintf(h, "compile %s %q %q\n", id, forcedGccgoflags, p.Internal.Gccgoflags)
403403
fmt.Fprintf(h, "pkgpath %s\n", gccgoPkgpath(p))
404404
fmt.Fprintf(h, "ar %q\n", BuildToolchain.(gccgoToolchain).ar())
405405
if len(p.SFiles) > 0 {
406-
id, _, _ = b.gccToolID(BuildToolchain.compiler(), "assembler-with-cpp")
406+
id, _, _ = b.gccToolID(BuildToolchain.compiler(), "assembler-with-cpp", true)
407407
// Ignore error; different assembler versions
408408
// are unlikely to make any difference anyhow.
409409
fmt.Fprintf(h, "asm %q\n", id)
@@ -1614,7 +1614,7 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
16141614
// Or external linker settings and flags?
16151615

16161616
case "gccgo":
1617-
id, _, err := b.gccToolID(BuildToolchain.linker(), "go")
1617+
id, _, err := b.gccToolID(BuildToolchain.linker(), "go", true)
16181618
if err != nil {
16191619
base.Fatalf("%v", err)
16201620
}
@@ -2743,7 +2743,7 @@ func (b *Builder) gccCompilerID(compiler string) (id cache.ActionID, ok bool) {
27432743
// The first one is the compiler executable we invoke.
27442744
// The second is the underlying compiler as reported by -v -###
27452745
// (see b.gccToolID implementation in buildid.go).
2746-
toolID, exe2, err := b.gccToolID(compiler, "c")
2746+
toolID, exe2, err := b.gccToolID(compiler, "c", false)
27472747
if err != nil {
27482748
return cache.ActionID{}, false
27492749
}

src/cmd/go/testdata/script/toolexec.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ go build ./cmd/mytool
99
# TODO(#27628): This should not be necessary.
1010
env GOCACHE=$WORK/gocache
1111

12+
# mytool records here if it is ever run for a C-family compiler's -### probe.
13+
env TOOLEXEC_CDRIVER_LOG=$WORK/cdriver.log
14+
1215
# Build the main package with our toolexec program. For each action, it will
1316
# print the tool's name and the TOOLEXEC_IMPORTPATH value. We expect to compile
1417
# each package once, and link the main package once.
@@ -24,6 +27,11 @@ stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main/withasm"$'
2427
stderr -count=1 '^compile'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main"$'
2528
stderr -count=1 '^link'${GOEXE}' TOOLEXEC_IMPORTPATH="test/main"$'
2629

30+
# The C compiler used by cgo is not run under -toolexec, so the -### probe
31+
# used to compute its tool ID must not be run under -toolexec either.
32+
# See go.dev/issue/64580.
33+
[cgo] ! exists $WORK/cdriver.log
34+
2735
# Test packages are a little bit trickier.
2836
# We have four variants of test/main, as reported by 'go list -test':
2937
#
@@ -112,6 +120,23 @@ func main() {
112120
toolName := filepath.Base(tool)
113121
if len(args) > 0 && args[0] == "-V=full" {
114122
// We can't alter the version output.
123+
} else if len(args) > 0 && args[0] == "-###" {
124+
// The C, C++, and Fortran compilers driven by cgo are probed with
125+
// -### to compute their tool ID. They are not otherwise run under
126+
// -toolexec, so they must not be probed under -toolexec either.
127+
// See go.dev/issue/64580.
128+
//
129+
// The probe's output is captured by the go command rather than
130+
// streamed, so record the invocation in a file the test can inspect.
131+
if log := os.Getenv("TOOLEXEC_CDRIVER_LOG"); log != "" {
132+
f, err := os.OpenFile(log, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666)
133+
if err != nil {
134+
fmt.Fprintln(os.Stderr, err)
135+
os.Exit(1)
136+
}
137+
fmt.Fprintf(f, "%s -###\n", toolName)
138+
f.Close()
139+
}
115140
} else {
116141
// Print which tool we're running, and on what package.
117142
fmt.Fprintf(os.Stdout, "%s TOOLEXEC_IMPORTPATH=%q\n", toolName, os.Getenv("TOOLEXEC_IMPORTPATH"))

0 commit comments

Comments
 (0)