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 }