cmd/link: quote PE .def library name

For Windows c-shared builds, peCreateExportFile writes a .def file
containing a LIBRARY directive. When the output name has a trailing dot,
as can happen for c-shared builds without an explicit -o, the directive
was emitted as:

	LIBRARY mypackage.

Some GNU ld versions reject that as invalid .def syntax.

Emit the library name as a quoted .def string instead:

	LIBRARY "mypackage."

This keeps ordinary output names working while allowing names with a
trailing dot.

Fixes #78238.

Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest,gotip-windows-arm64
Change-Id: I45ddc09856016a0df233f73b8ec33ac7069a2aec
Reviewed-on: https://go-review.googlesource.com/c/go/+/793280
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/src/cmd/link/internal/ld/ld_test.go b/src/cmd/link/internal/ld/ld_test.go
index a2a3d18..3cddcf7 100644
--- a/src/cmd/link/internal/ld/ld_test.go
+++ b/src/cmd/link/internal/ld/ld_test.go
@@ -199,6 +199,49 @@
 	})
 }
 
+func TestWindowsBuildmodeCSharedTrailingDotOutput(t *testing.T) {
+	if runtime.GOOS != "windows" {
+		t.Skip("skipping windows only test")
+	}
+
+	t.Parallel()
+	testenv.MustHaveGoBuild(t)
+	testenv.MustHaveCGO(t)
+	testenv.MustHaveBuildMode(t, "c-shared")
+
+	dir := t.TempDir()
+	srcfile := filepath.Join(dir, "test.go")
+	objfile := filepath.Join(dir, "mypackage.")
+	linktmp := filepath.Join(dir, "linktmp")
+	if err := os.Mkdir(linktmp, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.WriteFile(srcfile, []byte(`package main
+import "C"
+
+//export Hello
+func Hello() {}
+
+func main() {}
+`), 0666); err != nil {
+		t.Fatal(err)
+	}
+
+	argv := []string{"build", "-buildmode=c-shared", "-o", objfile, "-ldflags", "-tmpdir=" + linktmp, srcfile}
+	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
+	if err != nil {
+		t.Fatalf("build failure: %s\n%s\n", err, string(out))
+	}
+
+	def, err := os.ReadFile(filepath.Join(linktmp, "export_file.def"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want := []byte("LIBRARY \"mypackage.\"\n"); !bytes.HasPrefix(def, want) {
+		t.Fatalf("export_file.def begins with %q, want %q", def, want)
+	}
+}
+
 func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) {
 	t.Parallel()
 	testenv.MustHaveGoBuild(t)
diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go
index 8a01d1f..8376dde 100644
--- a/src/cmd/link/internal/ld/pe.go
+++ b/src/cmd/link/internal/ld/pe.go
@@ -1920,7 +1920,7 @@
 	var buf bytes.Buffer
 
 	if ctxt.BuildMode == BuildModeCShared {
-		fmt.Fprintf(&buf, "LIBRARY %s\n", libName)
+		fmt.Fprintf(&buf, "LIBRARY %q\n", libName)
 	}
 	buf.WriteString("EXPORTS\n")