github.com/rgonomic/rgo@v0.2.2-0.20220708095818-4747f0905d6e/internal/mod/module.go (about)

     1  // Copyright ©2019 The rgonomic 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 mod
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  )
    17  
    18  // Module returns module information for the given directory path.
    19  // If the path is in the standard library, the Info Path field will
    20  // be GOROOT and Version will be the Go version.
    21  func Module(path string) (Info, error) {
    22  	args := []string{"list", "-json"}
    23  	if path == "." {
    24  		args = append(args, "-m")
    25  	} else {
    26  		args = append(args, path)
    27  	}
    28  	cmd := exec.Command("go", args...)
    29  	var buf, errbuf bytes.Buffer
    30  	cmd.Stdout = &buf
    31  	cmd.Stderr = &errbuf
    32  	err := cmd.Run()
    33  	if err != nil {
    34  		stderr := strings.TrimSpace(errbuf.String())
    35  		if stderr == "" {
    36  			return Info{}, err
    37  		}
    38  		return Info{}, fmt.Errorf("%s: %w", stderr, err)
    39  	}
    40  	var m struct {
    41  		Module   Info
    42  		Standard bool
    43  	}
    44  	err = json.Unmarshal(buf.Bytes(), &m)
    45  	if err != nil {
    46  		return Info{}, err
    47  	}
    48  	if !m.Standard {
    49  		return m.Module, nil
    50  	}
    51  
    52  	buf.Reset()
    53  	errbuf.Reset()
    54  	cmd = exec.Command("go", "env", "GOROOT")
    55  	cmd.Stdout = &buf
    56  	cmd.Stderr = &errbuf
    57  	err = cmd.Run()
    58  	if err != nil {
    59  		stderr := strings.TrimSpace(errbuf.String())
    60  		if stderr == "" {
    61  			return Info{}, err
    62  		}
    63  		return Info{}, fmt.Errorf("%s: %w", stderr, err)
    64  	}
    65  	root := strings.TrimSpace(buf.String())
    66  	info := Info{
    67  		Path: filepath.Join(root, "src", path),
    68  		Dir:  root,
    69  	}
    70  
    71  	buf.Reset()
    72  	errbuf.Reset()
    73  	cmd = exec.Command("go", "version")
    74  	cmd.Stdout = &buf
    75  	cmd.Stderr = &errbuf
    76  	err = cmd.Run()
    77  	if err != nil {
    78  		stderr := strings.TrimSpace(errbuf.String())
    79  		if stderr == "" {
    80  			return Info{}, err
    81  		}
    82  		return Info{}, fmt.Errorf("%s: %w", stderr, err)
    83  	}
    84  	version := strings.Fields(strings.TrimPrefix(buf.String(), "go version go"))[0]
    85  	info.Version = "v" + version
    86  
    87  	return info, nil
    88  }
    89  
    90  // Info holds module information. It is a subset of the information
    91  // returned by "go list -json" in the Module struct. See go help list
    92  // for details.
    93  type Info struct {
    94  	Path    string // module path
    95  	Version string // module version
    96  	Dir     string // directory holding files for this module, if any
    97  	GoMod   string // path to go.mod file for this module, if any
    98  }
    99  
   100  // Root returns the root directory of the module in the given path and
   101  // whether a go.mod file can be found. It returns an error if the go
   102  // tool is not running is module-aware mode.
   103  func Root(path string) (root string, ok bool, err error) {
   104  	cmd := exec.Command("go", "env", "GOMOD")
   105  	cmd.Dir = path
   106  	var buf bytes.Buffer
   107  	cmd.Stdout = &buf
   108  	err = cmd.Run()
   109  	if err != nil {
   110  		return "", false, err
   111  	}
   112  	gomod := strings.TrimSpace(buf.String())
   113  	if gomod == "" {
   114  		return "", false, errors.New("go tool not running in module mode")
   115  	}
   116  	if gomod == os.DevNull {
   117  		return path, false, nil
   118  	}
   119  	return filepath.Dir(gomod), true, nil
   120  }