// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package bug implements the “go bug” command. package bug import ( "bytes" "context" "fmt" "io" urlpkg "net/url" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/envcmd" "cmd/go/internal/web" "cmd/go/internal/work" ) var CmdBug = &base.Command{ Run: runBug, UsageLine: "go bug", Short: "start a bug report", Long: ` Bug opens the default browser and starts a new bug report. The report includes useful system information. `, } func init() { CmdBug.Flag.BoolVar(&cfg.BuildV, "v", false, "") base.AddChdirFlag(&CmdBug.Flag) } func runBug(ctx context.Context, cmd *base.Command, args []string) { if len(args) > 0 { base.Fatalf("go: bug takes no arguments") } work.BuildInit() var buf strings.Builder buf.WriteString(bugHeader) printGoVersion(&buf) buf.WriteString("### Does this issue reproduce with the latest release?\n\n\n") printEnvDetails(&buf) buf.WriteString(bugFooter) body := buf.String() url := "https://github.com/golang/go/issues/new?body=" + urlpkg.QueryEscape(body) if !web.OpenBrowser(url) { fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n") fmt.Print(body) } } const bugHeader = ` ` const bugFooter = `### What did you do? ### What did you expect to see? ### What did you see instead? ` func printGoVersion(w io.Writer) { fmt.Fprintf(w, "### What version of Go are you using (`go version`)?\n\n") fmt.Fprintf(w, "
\n")
	fmt.Fprintf(w, "$ go version\n")
	fmt.Fprintf(w, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
	fmt.Fprintf(w, "
\n") fmt.Fprintf(w, "\n") } func printEnvDetails(w io.Writer) { fmt.Fprintf(w, "### What operating system and processor architecture are you using (`go env`)?\n\n") fmt.Fprintf(w, "
go env Output
\n")
	fmt.Fprintf(w, "$ go env\n")
	printGoEnv(w)
	printGoDetails(w)
	printOSDetails(w)
	printCDetails(w)
	fmt.Fprintf(w, "
\n\n") } func printGoEnv(w io.Writer) { env := envcmd.MkEnv() env = append(env, envcmd.ExtraEnvVars()...) env = append(env, envcmd.ExtraEnvVarsCostly()...) envcmd.PrintEnv(w, env, false) } func printGoDetails(w io.Writer) { gocmd := filepath.Join(runtime.GOROOT(), "bin/go") printCmdOut(w, "GOROOT/bin/go version: ", gocmd, "version") printCmdOut(w, "GOROOT/bin/go tool compile -V: ", gocmd, "tool", "compile", "-V") } func printOSDetails(w io.Writer) { switch runtime.GOOS { case "darwin", "ios": printCmdOut(w, "uname -v: ", "uname", "-v") printCmdOut(w, "", "sw_vers") case "linux": printCmdOut(w, "uname -sr: ", "uname", "-sr") printCmdOut(w, "", "lsb_release", "-a") printGlibcVersion(w) case "openbsd", "netbsd", "freebsd", "dragonfly": printCmdOut(w, "uname -v: ", "uname", "-v") case "illumos", "solaris": // Be sure to use the OS-supplied uname, in "/usr/bin": printCmdOut(w, "uname -srv: ", "/usr/bin/uname", "-srv") out, err := os.ReadFile("/etc/release") if err == nil { fmt.Fprintf(w, "/etc/release: %s\n", out) } else { if cfg.BuildV { fmt.Printf("failed to read /etc/release: %v\n", err) } } } } func printCDetails(w io.Writer) { printCmdOut(w, "lldb --version: ", "lldb", "--version") cmd := exec.Command("gdb", "--version") out, err := cmd.Output() if err == nil { // There's apparently no combination of command line flags // to get gdb to spit out its version without the license and warranty. // Print up to the first newline. fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out)) } else { if cfg.BuildV { fmt.Printf("failed to run gdb --version: %v\n", err) } } } // printCmdOut prints the output of running the given command. // It ignores failures; 'go bug' is best effort. func printCmdOut(w io.Writer, prefix, path string, args ...string) { cmd := exec.Command(path, args...) out, err := cmd.Output() if err != nil { if cfg.BuildV { fmt.Printf("%s %s: %v\n", path, strings.Join(args, " "), err) } return } fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out)) } // firstLine returns the first line of a given byte slice. func firstLine(buf []byte) []byte { idx := bytes.IndexByte(buf, '\n') if idx > 0 { buf = buf[:idx] } return bytes.TrimSpace(buf) } // printGlibcVersion prints information about the glibc version. // It ignores failures. func printGlibcVersion(w io.Writer) { tempdir := os.TempDir() if tempdir == "" { return } src := []byte(`int main() {}`) srcfile := filepath.Join(tempdir, "go-bug.c") outfile := filepath.Join(tempdir, "go-bug") err := os.WriteFile(srcfile, src, 0644) if err != nil { return } defer os.Remove(srcfile) cmd := exec.Command("gcc", "-o", outfile, srcfile) if _, err = cmd.CombinedOutput(); err != nil { return } defer os.Remove(outfile) cmd = exec.Command("ldd", outfile) out, err := cmd.CombinedOutput() if err != nil { return } re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`) m := re.FindStringSubmatch(string(out)) if m == nil { return } cmd = exec.Command(m[1]) out, err = cmd.Output() if err != nil { return } fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out)) // print another line (the one containing version string) in case of musl libc if idx := bytes.IndexByte(out, '\n'); bytes.Contains(out, []byte("musl")) && idx > -1 { fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:])) } }