| // 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" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| "strings" |
| |
| "cmd/go/internal/base" |
| "cmd/go/internal/cfg" |
| "cmd/go/internal/envcmd" |
| "cmd/go/internal/web" |
| ) |
| |
| var CmdBug = &base.Command{ |
| Run: runBug, |
| UsageLine: "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, "") |
| } |
| |
| func runBug(cmd *base.Command, args []string) { |
| var buf bytes.Buffer |
| buf.WriteString(bugHeader) |
| inspectGoVersion(&buf) |
| fmt.Fprint(&buf, "#### System details\n\n") |
| fmt.Fprintln(&buf, "```") |
| fmt.Fprintf(&buf, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) |
| env := cfg.CmdEnv |
| env = append(env, envcmd.ExtraEnvVars()...) |
| for _, e := range env { |
| // Hide the TERM environment variable from "go bug". |
| // See issue #18128 |
| if e.Name != "TERM" { |
| fmt.Fprintf(&buf, "%s=\"%s\"\n", e.Name, e.Value) |
| } |
| } |
| printGoDetails(&buf) |
| printOSDetails(&buf) |
| printCDetails(&buf) |
| fmt.Fprintln(&buf, "```") |
| |
| body := buf.String() |
| url := "https://github.com/golang/go/issues/new?body=" + web.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 = `Please answer these questions before submitting your issue. Thanks! |
| |
| #### What did you do? |
| If possible, provide a recipe for reproducing the error. |
| A complete runnable program is good. |
| A link on play.golang.org is best. |
| |
| |
| #### What did you expect to see? |
| |
| |
| #### What did you see instead? |
| |
| |
| ` |
| |
| func printGoDetails(w io.Writer) { |
| printCmdOut(w, "GOROOT/bin/go version: ", filepath.Join(runtime.GOROOT(), "bin/go"), "version") |
| printCmdOut(w, "GOROOT/bin/go tool compile -V: ", filepath.Join(runtime.GOROOT(), "bin/go"), "tool", "compile", "-V") |
| } |
| |
| func printOSDetails(w io.Writer) { |
| switch runtime.GOOS { |
| case "darwin": |
| 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 "solaris": |
| out, err := ioutil.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) |
| } |
| } |
| } |
| |
| func inspectGoVersion(w io.Writer) { |
| data, err := web.Get("https://golang.org/VERSION?m=text") |
| if err != nil { |
| if cfg.BuildV { |
| fmt.Printf("failed to read from golang.org/VERSION: %v\n", err) |
| } |
| return |
| } |
| |
| // golang.org/VERSION currently returns a whitespace-free string, |
| // but just in case, protect against that changing. |
| // Similarly so for runtime.Version. |
| release := string(bytes.TrimSpace(data)) |
| vers := strings.TrimSpace(runtime.Version()) |
| |
| if vers == release { |
| // Up to date |
| return |
| } |
| |
| // Devel version or outdated release. Either way, this request is apropos. |
| fmt.Fprintf(w, "#### Does this issue reproduce with the latest release (%s)?\n\n\n", release) |
| } |
| |
| // 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 := ioutil.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.Index(out, []byte("musl")) != -1 && idx > -1 { |
| fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:])) |
| } |
| } |