github.com/julianthome/gore@v0.0.0-20231109011145-b3a6bbe6fe55/gomod.go (about)

     1  package gore
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"go/build"
     7  	"io"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  func (s *Session) initGoMod() error {
    19  	tempModule := filepath.Base(s.tempDir)
    20  	goModPath := filepath.Join(s.tempDir, "go.mod")
    21  	directives := s.listModuleDirectives()
    22  	mod := "module " + tempModule + "\n" + strings.Join(directives, "\n")
    23  	return os.WriteFile(goModPath, []byte(mod), 0o644)
    24  }
    25  
    26  func (s *Session) listModuleDirectives() []string {
    27  	var directives []string
    28  	for i, pp := range printerPkgs {
    29  		if pp.path == "fmt" {
    30  			continue
    31  		}
    32  		// Check local module caches.
    33  		found := lookupGoModule(pp.path, pp.version)
    34  		if found {
    35  			for _, r := range pp.requires {
    36  				if !lookupGoModule(r.path, r.version) {
    37  					found = false
    38  					break
    39  				}
    40  			}
    41  		}
    42  		if found || canAccessGoproxy() {
    43  			// Specifying the version of the printer package improves startup
    44  			// performance by skipping module version fetching. Also allows to
    45  			// use gore in offline environment.
    46  			directives = append(directives, "require "+pp.path+" "+pp.version)
    47  			for _, r := range pp.requires {
    48  				directives = append(directives, "require "+r.path+" "+r.version)
    49  			}
    50  		} else {
    51  			// If there is no module cache and no network connection, use fmt package.
    52  			printerPkgs = printerPkgs[i+1:]
    53  		}
    54  		// only the first printer is checked (assuming printerPkgs[1] is fmt)
    55  		break
    56  	}
    57  	modules, err := goListAll()
    58  	if err != nil {
    59  		return directives
    60  	}
    61  	for _, m := range modules {
    62  		if m.Main || m.Replace != nil {
    63  			directives = append(directives, "replace "+m.Path+" => "+strconv.Quote(m.Dir))
    64  			s.requiredModules = append(s.requiredModules, m.Path)
    65  		}
    66  	}
    67  	return directives
    68  }
    69  
    70  type goModule struct {
    71  	Path, Dir, Version string
    72  	Main               bool
    73  	Replace            *goModule
    74  }
    75  
    76  func goListAll() ([]*goModule, error) {
    77  	cmd := exec.Command("go", "list", "-json", "-m", "all")
    78  	out, err := cmd.Output()
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	d := json.NewDecoder(bytes.NewReader(out))
    83  	var ms []*goModule
    84  	for {
    85  		m := new(goModule)
    86  		if err := d.Decode(m); err != nil {
    87  			if err == io.EOF {
    88  				return ms, nil
    89  			}
    90  			return nil, err
    91  		}
    92  		ms = append(ms, m)
    93  	}
    94  }
    95  
    96  func lookupGoModule(pkg, version string) bool {
    97  	modDir := filepath.Join(build.Default.GOPATH, "pkg/mod", pkg+"@"+version)
    98  	fi, err := os.Stat(modDir)
    99  	return err == nil && fi.IsDir()
   100  }
   101  
   102  func canAccessGoproxy() bool {
   103  	var host string
   104  	if u, err := url.Parse(getGoproxy()); err != nil {
   105  		host = "proxy.golang.org"
   106  	} else {
   107  		host = u.Hostname()
   108  	}
   109  	addr := net.JoinHostPort(host, "80")
   110  	dialer := net.Dialer{Timeout: 5 * time.Second}
   111  	conn, err := dialer.Dial("tcp", addr)
   112  	if err != nil {
   113  		return false
   114  	}
   115  	defer conn.Close()
   116  	return true
   117  }
   118  
   119  func getGoproxy() string {
   120  	if goproxy := os.Getenv("GOPROXY"); goproxy != "" {
   121  		return goproxy
   122  	}
   123  	return "https://proxy.golang.org/"
   124  }