Source file src/cmd/go/internal/bug/bug.go

     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  	"context"
    11  	"fmt"
    12  	"io"
    13  	urlpkg "net/url"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"regexp"
    18  	"runtime"
    19  	"strings"
    20  
    21  	"cmd/go/internal/base"
    22  	"cmd/go/internal/cfg"
    23  	"cmd/go/internal/envcmd"
    24  	"cmd/go/internal/modload"
    25  	"cmd/go/internal/web"
    26  	"cmd/go/internal/work"
    27  )
    28  
    29  var CmdBug = &base.Command{
    30  	Run:       runBug,
    31  	UsageLine: "go bug",
    32  	Short:     "start a bug report",
    33  	Long: `
    34  Bug opens the default browser and starts a new bug report.
    35  The report includes useful system information.
    36  	`,
    37  }
    38  
    39  func init() {
    40  	CmdBug.Flag.BoolVar(&cfg.BuildV, "v", false, "")
    41  	base.AddChdirFlag(&CmdBug.Flag)
    42  }
    43  
    44  func runBug(ctx context.Context, cmd *base.Command, args []string) {
    45  	moduleLoaderState := modload.NewState()
    46  	if len(args) > 0 {
    47  		base.Fatalf("go: bug takes no arguments")
    48  	}
    49  	work.BuildInit(moduleLoaderState)
    50  
    51  	var buf strings.Builder
    52  	buf.WriteString(bugHeader)
    53  	printGoVersion(&buf)
    54  	buf.WriteString("### Does this issue reproduce with the latest release?\n\n\n")
    55  	printEnvDetails(moduleLoaderState, &buf)
    56  	buf.WriteString(bugFooter)
    57  
    58  	body := buf.String()
    59  	url := "https://github.com/golang/go/issues/new?body=" + urlpkg.QueryEscape(body)
    60  	if !web.OpenBrowser(url) {
    61  		fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n")
    62  		fmt.Print(body)
    63  	}
    64  }
    65  
    66  const bugHeader = `<!-- Please answer these questions before submitting your issue. Thanks! -->
    67  
    68  `
    69  const bugFooter = `### What did you do?
    70  
    71  <!--
    72  If possible, provide a recipe for reproducing the error.
    73  A complete runnable program is good.
    74  A link on go.dev/play is best.
    75  -->
    76  
    77  
    78  
    79  ### What did you expect to see?
    80  
    81  
    82  
    83  ### What did you see instead?
    84  
    85  `
    86  
    87  func printGoVersion(w io.Writer) {
    88  	fmt.Fprintf(w, "### What version of Go are you using (`go version`)?\n\n")
    89  	fmt.Fprintf(w, "<pre>\n")
    90  	fmt.Fprintf(w, "$ go version\n")
    91  	fmt.Fprintf(w, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
    92  	fmt.Fprintf(w, "</pre>\n")
    93  	fmt.Fprintf(w, "\n")
    94  }
    95  
    96  func printEnvDetails(loaderstate *modload.State, w io.Writer) {
    97  	fmt.Fprintf(w, "### What operating system and processor architecture are you using (`go env`)?\n\n")
    98  	fmt.Fprintf(w, "<details><summary><code>go env</code> Output</summary><br><pre>\n")
    99  	fmt.Fprintf(w, "$ go env\n")
   100  	printGoEnv(loaderstate, w)
   101  	printGoDetails(w)
   102  	printOSDetails(w)
   103  	printCDetails(w)
   104  	fmt.Fprintf(w, "</pre></details>\n\n")
   105  }
   106  
   107  func printGoEnv(loaderstate *modload.State, w io.Writer) {
   108  	env := envcmd.MkEnv()
   109  	env = append(env, envcmd.ExtraEnvVars(loaderstate)...)
   110  	env = append(env, envcmd.ExtraEnvVarsCostly(loaderstate)...)
   111  	envcmd.PrintEnv(w, env, false)
   112  }
   113  
   114  func printGoDetails(w io.Writer) {
   115  	gocmd := filepath.Join(runtime.GOROOT(), "bin/go")
   116  	printCmdOut(w, "GOROOT/bin/go version: ", gocmd, "version")
   117  	printCmdOut(w, "GOROOT/bin/go tool compile -V: ", gocmd, "tool", "compile", "-V")
   118  }
   119  
   120  func printOSDetails(w io.Writer) {
   121  	switch runtime.GOOS {
   122  	case "darwin", "ios":
   123  		printCmdOut(w, "uname -v: ", "uname", "-v")
   124  		printCmdOut(w, "", "sw_vers")
   125  	case "linux":
   126  		printCmdOut(w, "uname -sr: ", "uname", "-sr")
   127  		printCmdOut(w, "", "lsb_release", "-a")
   128  		printGlibcVersion(w)
   129  	case "openbsd", "netbsd", "freebsd", "dragonfly":
   130  		printCmdOut(w, "uname -v: ", "uname", "-v")
   131  	case "illumos", "solaris":
   132  		// Be sure to use the OS-supplied uname, in "/usr/bin":
   133  		printCmdOut(w, "uname -srv: ", "/usr/bin/uname", "-srv")
   134  		out, err := os.ReadFile("/etc/release")
   135  		if err == nil {
   136  			fmt.Fprintf(w, "/etc/release: %s\n", out)
   137  		} else {
   138  			if cfg.BuildV {
   139  				fmt.Printf("failed to read /etc/release: %v\n", err)
   140  			}
   141  		}
   142  	}
   143  }
   144  
   145  func printCDetails(w io.Writer) {
   146  	printCmdOut(w, "lldb --version: ", "lldb", "--version")
   147  	cmd := exec.Command("gdb", "--version")
   148  	out, err := cmd.Output()
   149  	if err == nil {
   150  		// There's apparently no combination of command line flags
   151  		// to get gdb to spit out its version without the license and warranty.
   152  		// Print up to the first newline.
   153  		fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out))
   154  	} else {
   155  		if cfg.BuildV {
   156  			fmt.Printf("failed to run gdb --version: %v\n", err)
   157  		}
   158  	}
   159  }
   160  
   161  // printCmdOut prints the output of running the given command.
   162  // It ignores failures; 'go bug' is best effort.
   163  func printCmdOut(w io.Writer, prefix, path string, args ...string) {
   164  	cmd := exec.Command(path, args...)
   165  	out, err := cmd.Output()
   166  	if err != nil {
   167  		if cfg.BuildV {
   168  			fmt.Printf("%s %s: %v\n", path, strings.Join(args, " "), err)
   169  		}
   170  		return
   171  	}
   172  	fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
   173  }
   174  
   175  // firstLine returns the first line of a given byte slice.
   176  func firstLine(buf []byte) []byte {
   177  	idx := bytes.IndexByte(buf, '\n')
   178  	if idx > 0 {
   179  		buf = buf[:idx]
   180  	}
   181  	return bytes.TrimSpace(buf)
   182  }
   183  
   184  // printGlibcVersion prints information about the glibc version.
   185  // It ignores failures.
   186  func printGlibcVersion(w io.Writer) {
   187  	tempdir := os.TempDir()
   188  	if tempdir == "" {
   189  		return
   190  	}
   191  	src := []byte(`int main() {}`)
   192  	srcfile := filepath.Join(tempdir, "go-bug.c")
   193  	outfile := filepath.Join(tempdir, "go-bug")
   194  	err := os.WriteFile(srcfile, src, 0644)
   195  	if err != nil {
   196  		return
   197  	}
   198  	defer os.Remove(srcfile)
   199  	cmd := exec.Command("gcc", "-o", outfile, srcfile)
   200  	if _, err = cmd.CombinedOutput(); err != nil {
   201  		return
   202  	}
   203  	defer os.Remove(outfile)
   204  
   205  	cmd = exec.Command("ldd", outfile)
   206  	out, err := cmd.CombinedOutput()
   207  	if err != nil {
   208  		return
   209  	}
   210  	re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`)
   211  	m := re.FindStringSubmatch(string(out))
   212  	if m == nil {
   213  		return
   214  	}
   215  	cmd = exec.Command(m[1])
   216  	out, err = cmd.Output()
   217  	if err != nil {
   218  		return
   219  	}
   220  	fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out))
   221  
   222  	// print another line (the one containing version string) in case of musl libc
   223  	if idx := bytes.IndexByte(out, '\n'); bytes.Contains(out, []byte("musl")) && idx > -1 {
   224  		fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:]))
   225  	}
   226  }
   227  

View as plain text