Home | History | Annotate | Download | only in bug
      1 // Copyright 2016 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package bug implements the ``go bug'' command.
      6 package bug
      7 
      8 import (
      9 	"bytes"
     10 	"fmt"
     11 	"io"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path/filepath"
     16 	"regexp"
     17 	"runtime"
     18 	"strings"
     19 
     20 	"cmd/go/internal/base"
     21 	"cmd/go/internal/cfg"
     22 	"cmd/go/internal/envcmd"
     23 	"cmd/go/internal/web"
     24 )
     25 
     26 var CmdBug = &base.Command{
     27 	Run:       runBug,
     28 	UsageLine: "bug",
     29 	Short:     "start a bug report",
     30 	Long: `
     31 Bug opens the default browser and starts a new bug report.
     32 The report includes useful system information.
     33 	`,
     34 }
     35 
     36 func init() {
     37 	CmdBug.Flag.BoolVar(&cfg.BuildV, "v", false, "")
     38 }
     39 
     40 func runBug(cmd *base.Command, args []string) {
     41 	var buf bytes.Buffer
     42 	buf.WriteString(bugHeader)
     43 	inspectGoVersion(&buf)
     44 	fmt.Fprint(&buf, "#### System details\n\n")
     45 	fmt.Fprintln(&buf, "```")
     46 	fmt.Fprintf(&buf, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
     47 	env := cfg.CmdEnv
     48 	env = append(env, envcmd.ExtraEnvVars()...)
     49 	for _, e := range env {
     50 		// Hide the TERM environment variable from "go bug".
     51 		// See issue #18128
     52 		if e.Name != "TERM" {
     53 			fmt.Fprintf(&buf, "%s=\"%s\"\n", e.Name, e.Value)
     54 		}
     55 	}
     56 	printGoDetails(&buf)
     57 	printOSDetails(&buf)
     58 	printCDetails(&buf)
     59 	fmt.Fprintln(&buf, "```")
     60 
     61 	body := buf.String()
     62 	url := "https://github.com/golang/go/issues/new?body=" + web.QueryEscape(body)
     63 	if !web.OpenBrowser(url) {
     64 		fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n")
     65 		fmt.Print(body)
     66 	}
     67 }
     68 
     69 const bugHeader = `Please answer these questions before submitting your issue. Thanks!
     70 
     71 #### What did you do?
     72 If possible, provide a recipe for reproducing the error.
     73 A complete runnable program is good.
     74 A link on play.golang.org is best.
     75 
     76 
     77 #### What did you expect to see?
     78 
     79 
     80 #### What did you see instead?
     81 
     82 
     83 `
     84 
     85 func printGoDetails(w io.Writer) {
     86 	printCmdOut(w, "GOROOT/bin/go version: ", filepath.Join(runtime.GOROOT(), "bin/go"), "version")
     87 	printCmdOut(w, "GOROOT/bin/go tool compile -V: ", filepath.Join(runtime.GOROOT(), "bin/go"), "tool", "compile", "-V")
     88 }
     89 
     90 func printOSDetails(w io.Writer) {
     91 	switch runtime.GOOS {
     92 	case "darwin":
     93 		printCmdOut(w, "uname -v: ", "uname", "-v")
     94 		printCmdOut(w, "", "sw_vers")
     95 	case "linux":
     96 		printCmdOut(w, "uname -sr: ", "uname", "-sr")
     97 		printCmdOut(w, "", "lsb_release", "-a")
     98 		printGlibcVersion(w)
     99 	case "openbsd", "netbsd", "freebsd", "dragonfly":
    100 		printCmdOut(w, "uname -v: ", "uname", "-v")
    101 	case "solaris":
    102 		out, err := ioutil.ReadFile("/etc/release")
    103 		if err == nil {
    104 			fmt.Fprintf(w, "/etc/release: %s\n", out)
    105 		} else {
    106 			if cfg.BuildV {
    107 				fmt.Printf("failed to read /etc/release: %v\n", err)
    108 			}
    109 		}
    110 	}
    111 }
    112 
    113 func printCDetails(w io.Writer) {
    114 	printCmdOut(w, "lldb --version: ", "lldb", "--version")
    115 	cmd := exec.Command("gdb", "--version")
    116 	out, err := cmd.Output()
    117 	if err == nil {
    118 		// There's apparently no combination of command line flags
    119 		// to get gdb to spit out its version without the license and warranty.
    120 		// Print up to the first newline.
    121 		fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out))
    122 	} else {
    123 		if cfg.BuildV {
    124 			fmt.Printf("failed to run gdb --version: %v\n", err)
    125 		}
    126 	}
    127 }
    128 
    129 func inspectGoVersion(w io.Writer) {
    130 	data, err := web.Get("https://golang.org/VERSION?m=text")
    131 	if err != nil {
    132 		if cfg.BuildV {
    133 			fmt.Printf("failed to read from golang.org/VERSION: %v\n", err)
    134 		}
    135 		return
    136 	}
    137 
    138 	// golang.org/VERSION currently returns a whitespace-free string,
    139 	// but just in case, protect against that changing.
    140 	// Similarly so for runtime.Version.
    141 	release := string(bytes.TrimSpace(data))
    142 	vers := strings.TrimSpace(runtime.Version())
    143 
    144 	if vers == release {
    145 		// Up to date
    146 		return
    147 	}
    148 
    149 	// Devel version or outdated release. Either way, this request is apropos.
    150 	fmt.Fprintf(w, "#### Does this issue reproduce with the latest release (%s)?\n\n\n", release)
    151 }
    152 
    153 // printCmdOut prints the output of running the given command.
    154 // It ignores failures; 'go bug' is best effort.
    155 func printCmdOut(w io.Writer, prefix, path string, args ...string) {
    156 	cmd := exec.Command(path, args...)
    157 	out, err := cmd.Output()
    158 	if err != nil {
    159 		if cfg.BuildV {
    160 			fmt.Printf("%s %s: %v\n", path, strings.Join(args, " "), err)
    161 		}
    162 		return
    163 	}
    164 	fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
    165 }
    166 
    167 // firstLine returns the first line of a given byte slice.
    168 func firstLine(buf []byte) []byte {
    169 	idx := bytes.IndexByte(buf, '\n')
    170 	if idx > 0 {
    171 		buf = buf[:idx]
    172 	}
    173 	return bytes.TrimSpace(buf)
    174 }
    175 
    176 // printGlibcVersion prints information about the glibc version.
    177 // It ignores failures.
    178 func printGlibcVersion(w io.Writer) {
    179 	tempdir := os.TempDir()
    180 	if tempdir == "" {
    181 		return
    182 	}
    183 	src := []byte(`int main() {}`)
    184 	srcfile := filepath.Join(tempdir, "go-bug.c")
    185 	outfile := filepath.Join(tempdir, "go-bug")
    186 	err := ioutil.WriteFile(srcfile, src, 0644)
    187 	if err != nil {
    188 		return
    189 	}
    190 	defer os.Remove(srcfile)
    191 	cmd := exec.Command("gcc", "-o", outfile, srcfile)
    192 	if _, err = cmd.CombinedOutput(); err != nil {
    193 		return
    194 	}
    195 	defer os.Remove(outfile)
    196 
    197 	cmd = exec.Command("ldd", outfile)
    198 	out, err := cmd.CombinedOutput()
    199 	if err != nil {
    200 		return
    201 	}
    202 	re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`)
    203 	m := re.FindStringSubmatch(string(out))
    204 	if m == nil {
    205 		return
    206 	}
    207 	cmd = exec.Command(m[1])
    208 	out, err = cmd.Output()
    209 	if err != nil {
    210 		return
    211 	}
    212 	fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out))
    213 
    214 	// print another line (the one containing version string) in case of musl libc
    215 	if idx := bytes.IndexByte(out, '\n'); bytes.Index(out, []byte("musl")) != -1 && idx > -1 {
    216 		fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:]))
    217 	}
    218 }
    219