github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/cmd/gno/bug.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"net/url"
     7  	"os/exec"
     8  	"runtime"
     9  	"runtime/debug"
    10  	"strings"
    11  	"text/template"
    12  	"time"
    13  
    14  	"github.com/gnolang/gno/tm2/pkg/commands"
    15  )
    16  
    17  // NOTE: keep in sync with .github/ISSUE_TEMPLATE/BUG-REPORT.md
    18  const bugTmpl = `## [Subject of the issue]
    19  
    20  ### Description
    21  
    22  Describe your issue in as much detail as possible here
    23  
    24  ### Your environment
    25  
    26  * go version {{.GoVersion}} {{.Os}}/{{.Arch}}
    27  * gno commit that causes this issue: {{.Commit}}
    28  
    29  ### Steps to reproduce
    30  
    31  * Tell us how to reproduce this issue
    32  * Where the issue is, if you know
    33  * Which commands triggered the issue, if any
    34  
    35  ### Expected behaviour
    36  
    37  Tell us what should happen
    38  
    39  ### Actual behaviour
    40  
    41  Tell us what happens instead
    42  
    43  ### Logs
    44  
    45  Please paste any logs here that demonstrate the issue, if they exist
    46  
    47  ### Proposed solution
    48  
    49  If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it
    50  
    51  `
    52  
    53  type bugCfg struct {
    54  	skipBrowser bool
    55  }
    56  
    57  func newBugCmd(io commands.IO) *commands.Command {
    58  	cfg := &bugCfg{}
    59  	return commands.NewCommand(
    60  		commands.Metadata{
    61  			Name:       "bug",
    62  			ShortUsage: "bug",
    63  			ShortHelp:  "Start a bug report",
    64  		},
    65  		cfg,
    66  		func(_ context.Context, args []string) error {
    67  			return execBug(cfg, args, io)
    68  		},
    69  	)
    70  }
    71  
    72  func (c *bugCfg) RegisterFlags(fs *flag.FlagSet) {
    73  	fs.BoolVar(
    74  		&c.skipBrowser,
    75  		"skip-browser",
    76  		false,
    77  		"do not open the browser",
    78  	)
    79  }
    80  
    81  func execBug(cfg *bugCfg, args []string, io commands.IO) error {
    82  	if len(args) != 0 {
    83  		return flag.ErrHelp
    84  	}
    85  
    86  	bugReportEnv := struct {
    87  		Os, Arch, GoVersion, Commit string
    88  	}{
    89  		runtime.GOOS,
    90  		runtime.GOARCH,
    91  		runtime.Version(),
    92  		getCommitHash(),
    93  	}
    94  
    95  	var buf strings.Builder
    96  	tmpl, err := template.New("bug.tmpl").Parse(bugTmpl)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	tmpl.Execute(&buf, bugReportEnv)
   101  
   102  	body := buf.String()
   103  	url := "https://github.com/gnolang/gno/issues/new?body=" + url.QueryEscape(body)
   104  
   105  	if !cfg.skipBrowser && openBrowser(url) {
   106  		return nil
   107  	}
   108  
   109  	io.Println("Please file a new issue at github.com/gnolang/gno/issues/new using this template:")
   110  	io.Println()
   111  	io.Println(body)
   112  
   113  	return nil
   114  }
   115  
   116  // openBrowser opens a default web browser with the specified URL.
   117  func openBrowser(url string) bool {
   118  	var cmdArgs []string
   119  	switch runtime.GOOS {
   120  	case "windows":
   121  		cmdArgs = []string{"cmd", "/c", "start", url}
   122  	case "darwin":
   123  		cmdArgs = []string{"/usr/bin/open", url}
   124  	default: // "linux"
   125  		cmdArgs = []string{"xdg-open", url}
   126  	}
   127  
   128  	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
   129  	if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) {
   130  		return true
   131  	}
   132  
   133  	return false
   134  }
   135  
   136  // getCommitHash returns the commit hash from build info, or an
   137  // empty string if not found.
   138  func getCommitHash() string {
   139  	if info, ok := debug.ReadBuildInfo(); ok {
   140  		for _, setting := range info.Settings {
   141  			if setting.Key == "vcs.revision" {
   142  				return setting.Value
   143  			}
   144  		}
   145  	}
   146  	return ""
   147  }
   148  
   149  // appearsSuccessful reports whether the command appears to have run successfully.
   150  // If the command runs longer than the timeout, it's deemed successful.
   151  // If the command runs within the timeout, it's deemed successful if it exited cleanly.
   152  // Note: Taken from Go's `internal/browser“
   153  func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool {
   154  	errc := make(chan error, 1)
   155  	go func() {
   156  		errc <- cmd.Wait()
   157  	}()
   158  
   159  	select {
   160  	case <-time.After(timeout):
   161  		return true
   162  	case err := <-errc:
   163  		return err == nil
   164  	}
   165  }