github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/cmd/install/install.go (about)

     1  package install
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"strings"
    13  )
    14  
    15  type packageInfo struct {
    16  	ImportPath  string
    17  	Name        string
    18  	Imports     []string
    19  	GoFiles     []string
    20  	Standard    bool
    21  	Stale       bool
    22  	StaleReason string
    23  }
    24  
    25  // IsStdPkg returns whether the package of path is in std library.
    26  func IsStdPkg(path string) bool {
    27  	// cf. https://golang.org/src/cmd/go/internal/load/pkg.go
    28  	i := strings.Index(path, "/")
    29  	if i < 0 {
    30  		i = len(path)
    31  	}
    32  	elem := path[:i]
    33  	return !strings.Contains(elem, ".")
    34  }
    35  
    36  func soFileName(path string) string {
    37  	return "lib" + strings.Replace(path, "/", "-", -1) + ".so"
    38  }
    39  
    40  func pkgDir(lgopath string) string {
    41  	return path.Join(lgopath, "pkg")
    42  }
    43  
    44  func IsSOInstalled(lgopath string, pkg string) bool {
    45  	_, err := os.Stat(path.Join(pkgDir(lgopath), soFileName(pkg)))
    46  	os.IsNotExist(err)
    47  	return err == nil
    48  }
    49  
    50  type SOInstaller struct {
    51  	cache  map[string]*packageInfo
    52  	pkgDir string
    53  }
    54  
    55  func NewSOInstaller(lgopath string) *SOInstaller {
    56  	return &SOInstaller{
    57  		cache:  make(map[string]*packageInfo),
    58  		pkgDir: pkgDir(lgopath),
    59  	}
    60  }
    61  
    62  // TODO: File bugs to explain reasons.
    63  var knownIncompatiblePkgs = map[string]bool{
    64  	"golang.org/x/sys/plan9":                    true,
    65  	"github.com/derekparker/delve/cmd/dlv/cmds": true,
    66  	// Workaround for https://github.com/yunabe/lgo/issues/51
    67  	// Some libraries under k8s.io depend on this package indirectly by PopAny.
    68  	// https://godoc.org/k8s.io/apimachinery/pkg/util/sets#Byte.PopAny
    69  	"k8s.io/apimachinery/pkg/util/sets": true,
    70  }
    71  
    72  // Install
    73  func (si *SOInstaller) Install(patterns ...string) error {
    74  	pkgs, err := si.getPackageList(patterns...)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	dry := walker{si: si, dryRun: true}
    79  	for _, pkg := range pkgs {
    80  		dry.walk(pkg.ImportPath)
    81  	}
    82  	w := walker{
    83  		si:          si,
    84  		progressAll: len(dry.visited),
    85  		logger: func(s string) {
    86  			fmt.Fprintln(os.Stderr, s)
    87  		},
    88  		stdout: os.Stdout,
    89  		stderr: os.Stderr,
    90  	}
    91  	ok := true
    92  	for _, pkg := range pkgs {
    93  		if cok := w.walk(pkg.ImportPath); !cok {
    94  			ok = false
    95  		}
    96  	}
    97  	if !ok {
    98  		return errors.New("failed to install .so files")
    99  	}
   100  	return nil
   101  }
   102  
   103  // getPackage returns the packageInfo for the path.
   104  func (si *SOInstaller) getPackage(path string) (*packageInfo, error) {
   105  	if pkg, ok := si.cache[path]; ok {
   106  		return pkg, nil
   107  	}
   108  	pkgs, err := si.getPackageList(path)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	if len(pkgs) == 0 {
   113  		panic("pkgs must not be empty")
   114  	}
   115  	return pkgs[0], nil
   116  }
   117  
   118  func (si *SOInstaller) getPackageList(args ...string) (infos []*packageInfo, err error) {
   119  	cmd := exec.Command("go", append([]string{"list", "-json"}, args...)...)
   120  	cmd.Stderr = os.Stderr
   121  	// We do not need to close r (https://golang.org/pkg/os/exec/#Cmd.StdoutPipe).
   122  	r, err := cmd.StdoutPipe()
   123  	if err != nil {
   124  		return nil, fmt.Errorf("failed to pipe stdout: %v", err)
   125  	}
   126  	if err := cmd.Start(); err != nil {
   127  		return nil, fmt.Errorf("failed to run go list: %v", err)
   128  	}
   129  	defer func() {
   130  		if werr := cmd.Wait(); werr != nil && err == nil {
   131  			err = fmt.Errorf("failed to wait go list: %v", werr)
   132  		}
   133  	}()
   134  	br := bufio.NewReader(r)
   135  	dec := json.NewDecoder(br)
   136  	for {
   137  		var info packageInfo
   138  		err := dec.Decode(&info)
   139  		if err == io.EOF {
   140  			break
   141  		}
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		infos = append(infos, &info)
   146  		si.cache[info.ImportPath] = &info
   147  	}
   148  	return infos, nil
   149  }
   150  
   151  type walker struct {
   152  	si      *SOInstaller
   153  	visited map[string]bool
   154  
   155  	progressAll    int
   156  	progress       int
   157  	dryRun         bool
   158  	logger         func(string)
   159  	stdout, stderr io.Writer
   160  }
   161  
   162  func (w *walker) logf(format string, a ...interface{}) {
   163  	if w.logger == nil {
   164  		return
   165  	}
   166  	pre := ""
   167  	if w.progressAll > 0 {
   168  		pre = fmt.Sprintf("(%d/%d) ", w.progress, w.progressAll)
   169  	}
   170  	w.logger(pre + fmt.Sprintf(format, a...))
   171  }
   172  
   173  func (w *walker) walk(path string) bool {
   174  	if path == "C" || IsStdPkg(path) {
   175  		// Ignore "C" import and std packages.
   176  		return true
   177  	}
   178  	if w.visited[path] {
   179  		return true
   180  	}
   181  	pkg, err := w.si.getPackage(path)
   182  	if err != nil {
   183  		w.logf("failed to get package info for %q: %v", path, err)
   184  		return false
   185  	}
   186  	if pkg.Name == "main" {
   187  		return true
   188  	}
   189  	if w.visited == nil {
   190  		w.visited = make(map[string]bool)
   191  	}
   192  	w.visited[path] = true
   193  	depok := true
   194  	for _, im := range pkg.Imports {
   195  		if ok := w.walk(im); !ok {
   196  			depok = false
   197  		}
   198  	}
   199  	w.progress++
   200  	if !depok {
   201  		w.logf("skipped %q due to failures in dependencies", path)
   202  		return false
   203  	}
   204  	if w.dryRun {
   205  		return true
   206  	}
   207  	if knownIncompatiblePkgs[path] {
   208  		w.logf("skipped %q: known incompatible package", path)
   209  		return true
   210  	}
   211  	cmd := exec.Command("go", "install", "-buildmode=shared", "-linkshared", "-pkgdir", w.si.pkgDir, path)
   212  	cmd.Stdout = w.stdout
   213  	cmd.Stderr = w.stderr
   214  	if err := cmd.Run(); err != nil {
   215  		w.logf("failed to install %q: %v", path, err)
   216  		return false
   217  	}
   218  	w.logf("installed %q", path)
   219  	return true
   220  }