# Regression test for https://go.dev/issue/64423: # # When we parse the version for a Clang binary, we should accept # an arbitrary vendor prefix, which (as of 2023) may be injected # by defining CLANG_VENDOR when building clang itself. # # Since we don't want to actually rebuild the Clang toolchain in # this test, we instead simulate it by injecting a fake "clang" # binary that runs the real one as a subprocess. [!cgo] skip [short] skip 'builds and links a fake clang binary' [!cc:clang] skip 'test is specific to clang version parsing' # Save the location of the real clang command for our fake one to use. go run ./which clang cp stdout $WORK/.realclang # Build a fake clang and ensure that it is the one in $PATH. mkdir $WORK/bin go build -o $WORK/bin/clang$GOEXE ./fakeclang [!GOOS:plan9] env PATH=$WORK${/}bin [GOOS:plan9] env path=$WORK${/}bin # Force CGO_ENABLED=1 so that the following commands should error # out if the fake clang doesn't work. env CGO_ENABLED=1 # The bug in https://go.dev/issue/64423 resulted in cache keys that # didn't contain any information about the C compiler. # Since the bug was in cache key computation, isolate the cache: # if we change the way caching works, we want the test to fail # instead of accidentally reusing the cached information from a # previous test run. env GOCACHE=$WORK${/}.cache mkdir $GOCACHE go build -x runtime/cgo # Tell our fake clang to stop working. # Previously, 'go build -x runtime/cgo' would continue to # succeed because both the broken clang and the non-broken one # resulted in a cache key with no clang version information. env GO_BREAK_CLANG=1 ! go build -x runtime/cgo stderr '# runtime/cgo\nGO_BREAK_CLANG is set' -- go.mod -- module example/issue64423 go 1.20 -- which/main.go -- package main import ( "os" "os/exec" ) func main() { path, err := exec.LookPath(os.Args[1]) if err != nil { panic(err) } os.Stdout.WriteString(path) } -- fakeclang/main.go -- package main import ( "bufio" "bytes" "log" "os" "os/exec" "path/filepath" "strings" ) func main() { if os.Getenv("GO_BREAK_CLANG") != "" { os.Stderr.WriteString("GO_BREAK_CLANG is set\n") os.Exit(1) } b, err := os.ReadFile(filepath.Join(os.Getenv("WORK"), ".realclang")) if err != nil { log.Fatal(err) } clang := string(bytes.TrimSpace(b)) cmd := exec.Command(clang, os.Args[1:]...) cmd.Stdout = os.Stdout stderr, err := cmd.StderrPipe() if err != nil { log.Fatal(err) } if err := cmd.Start(); err != nil { log.Fatal(err) } r := bufio.NewReader(stderr) for { line, err := r.ReadString('\n') if line != "" { if strings.Contains(line, "clang version") { // Simulate a clang version string with an arbitrary vendor prefix. const vendorString = "Gopher Solutions Unlimited " os.Stderr.WriteString(vendorString) } os.Stderr.WriteString(line) } if err != nil { break } } os.Stderr.Close() if err := cmd.Wait(); err != nil { os.Exit(1) } }