cmd/go: insert goroot to the hash of build cache when the packages include C files

This reverts CL 351851, which itself reverted CL 348991.

The problem with the original CL, as far as I can tell, was due to a
bug in the Go project's builder infrastructure (#33598) and not the
change itself. Once the build infrastructure is fixed, this change
can be resubmitted.

Fixes #48319
Updates #33598

Change-Id: I0fdbcc241eb2bdeb350944aad58bf58774fb591e
Reviewed-on: https://go-review.googlesource.com/c/go/+/353352
Trust: Bryan C. Mills <bcmills@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go
index 99e9294..d4e24d4 100644
--- a/src/cmd/go/internal/work/exec.go
+++ b/src/cmd/go/internal/work/exec.go
@@ -223,18 +223,32 @@
 	// same compiler settings and can reuse each other's results.
 	// If not, the reason is already recorded in buildGcflags.
 	fmt.Fprintf(h, "compile\n")
-	// Only include the package directory if it may affect the output.
-	// We trim workspace paths for all packages when -trimpath is set.
-	// The compiler hides the exact value of $GOROOT
-	// when building things in GOROOT.
-	// Assume b.WorkDir is being trimmed properly.
-	// When -trimpath is used with a package built from the module cache,
-	// use the module path and version instead of the directory.
-	if !p.Goroot && !cfg.BuildTrimpath && !strings.HasPrefix(p.Dir, b.WorkDir) {
+
+	// Include information about the origin of the package that
+	// may be embedded in the debug info for the object file.
+	if cfg.BuildTrimpath {
+		// When -trimpath is used with a package built from the module cache,
+		// its debug information refers to the module path and version
+		// instead of the directory.
+		if p.Module != nil {
+			fmt.Fprintf(h, "module %s@%s\n", p.Module.Path, p.Module.Version)
+		}
+	} else if p.Goroot {
+		// The Go compiler always hides the exact value of $GOROOT
+		// when building things in GOROOT, but the C compiler
+		// merely rewrites GOROOT to GOROOT_FINAL.
+		if len(p.CFiles) > 0 {
+			fmt.Fprintf(h, "goroot %s\n", cfg.GOROOT_FINAL)
+		}
+		// b.WorkDir is always either trimmed or rewritten to
+		// the literal string "/tmp/go-build".
+	} else if !strings.HasPrefix(p.Dir, b.WorkDir) {
+		// -trimpath is not set and no other rewrite rules apply,
+		// so the object file may refer to the absolute directory
+		// containing the package.
 		fmt.Fprintf(h, "dir %s\n", p.Dir)
-	} else if cfg.BuildTrimpath && p.Module != nil {
-		fmt.Fprintf(h, "module %s@%s\n", p.Module.Path, p.Module.Version)
 	}
+
 	if p.Module != nil {
 		fmt.Fprintf(h, "go %s\n", p.Module.GoVersion)
 	}
diff --git a/src/cmd/go/testdata/script/build_issue48319.txt b/src/cmd/go/testdata/script/build_issue48319.txt
new file mode 100644
index 0000000..f58a5fa
--- /dev/null
+++ b/src/cmd/go/testdata/script/build_issue48319.txt
@@ -0,0 +1,153 @@
+[short] skip
+[!cgo] skip
+
+# Set up fresh GOCACHE
+env GOCACHE=$WORK/gocache
+mkdir $GOCACHE
+
+# 1. unset GOROOT_FINAL, Build a simple binary with cgo by origin go.
+# The DW_AT_comp_dir of runtime/cgo should have a prefix with origin goroot.
+env GOROOT_FINAL=
+# If using "go run", it is no debuginfo in binary. So use "go build".
+# And we can check the stderr to judge if the cache of "runtime/cgo"
+# was used or not.
+go build -o binary.exe
+exec ./binary.exe $TESTGO_GOROOT
+stdout 'cgo DW_AT_comp_dir is right in binary'
+
+
+# 2. GOROOT_FINAL will be changed, the runtime/cgo will be rebuild.
+env GOROOT_FINAL=$WORK/gorootfinal
+go build -x -o binary.exe
+stderr '(clang|gcc)( |\.exe).*gcc_.*\.c'
+exec ./binary.exe $GOROOT_FINAL
+stdout 'cgo DW_AT_comp_dir is right in binary'
+
+
+[!symlink] skip
+
+# Symlink the compiler to another path
+env GOROOT=$WORK/goroot
+symlink $GOROOT -> $TESTGO_GOROOT
+
+# 3. GOROOT_FINAL is same with 2, build with the other go
+# the runtime/cgo will not be rebuild.
+go build -x -o binary.exe
+! stderr '(clang|gcc)( |\.exe).*gcc_.*\.c'
+exec ./binary.exe $GOROOT_FINAL
+stdout 'cgo DW_AT_comp_dir is right in binary'
+
+
+# 4. unset GOROOT_FINAL, build with the other go
+# the runtime/cgo will be rebuild.
+env GOROOT_FINAL=
+go build -x -o binary.exe
+stderr '(clang|gcc)( |\.exe).*gcc_.*\.c'
+exec ./binary.exe $GOROOT
+stdout 'cgo DW_AT_comp_dir is right in binary'
+
+-- go.mod --
+module main
+
+go 1.18
+-- main.go --
+package main
+
+import "C"
+import (
+	"debug/dwarf"
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+var _ C.int
+
+func main() {
+	dwarfData, err := readDWARF(os.Args[0])
+	if err != nil {
+		log.Fatal(err)
+	}
+	goroot := filepath.Join(os.Args[1], "src")
+	dwarfReader := dwarfData.Reader()
+	cgopackage := filepath.Join("runtime", "cgo")
+	var hascgo bool
+	for {
+		e, err := dwarfReader.Next()
+		if err != nil {
+			log.Fatal(err)
+		}
+		if e == nil {
+			break
+		}
+		field := e.AttrField(dwarf.AttrCompDir)
+		if field == nil {
+			continue
+		}
+		compdir := field.Val.(string)
+		if strings.HasSuffix(compdir, cgopackage) {
+			hascgo = true
+			if !strings.HasPrefix(compdir, goroot) {
+				fmt.Printf("cgo DW_AT_comp_dir %s contains incorrect path in binary.\n", compdir)
+				return
+			}
+		}
+	}
+	if hascgo {
+		fmt.Println("cgo DW_AT_comp_dir is right in binary")
+	} else {
+		fmt.Println("binary does not contain cgo")
+	}
+}
+-- read_darwin.go --
+package main
+
+import (
+	"debug/dwarf"
+	"debug/macho"
+)
+
+func readDWARF(exePath string) (*dwarf.Data, error) {
+	machoFile, err := macho.Open(exePath)
+	if err != nil {
+		return nil, err
+	}
+	defer machoFile.Close()
+	return machoFile.DWARF()
+}
+-- read_elf.go --
+// +build android dragonfly freebsd illumos linux netbsd openbsd solaris
+
+package main
+
+import (
+	"debug/dwarf"
+	"debug/elf"
+)
+
+func readDWARF(exePath string) (*dwarf.Data, error) {
+	elfFile, err := elf.Open(exePath)
+	if err != nil {
+		return nil, err
+	}
+	defer elfFile.Close()
+	return elfFile.DWARF()
+}
+-- read_windows.go --
+package main
+
+import (
+	"debug/dwarf"
+	"debug/pe"
+)
+
+func readDWARF(exePath string) (*dwarf.Data, error) {
+	peFile, err := pe.Open(exePath)
+	if err != nil {
+		return nil, err
+	}
+	defer peFile.Close()
+	return peFile.DWARF()
+}