github.com/liuweiccy/glide@v0.12.3/dependency/scan.go (about) 1 package dependency 2 3 import ( 4 "bytes" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "text/scanner" 10 11 "github.com/Masterminds/glide/msg" 12 "github.com/Masterminds/glide/util" 13 ) 14 15 var osList []string 16 var archList []string 17 18 func init() { 19 // The supported systems are listed in 20 // https://github.com/golang/go/blob/master/src/go/build/syslist.go 21 // The lists are not exported so we need to duplicate them here. 22 osListString := "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows" 23 osList = strings.Split(osListString, " ") 24 25 archListString := "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc s390 s390x sparc sparc64" 26 archList = strings.Split(archListString, " ") 27 } 28 29 // IterativeScan attempts to obtain a list of imported dependencies from a 30 // package. This scanning is different from ImportDir as part of the go/build 31 // package. It looks over different permutations of the supported OS/Arch to 32 // try and find all imports. This is different from setting UseAllFiles to 33 // true on the build Context. It scopes down to just the supported OS/Arch. 34 // 35 // Note, there are cases where multiple packages are in the same directory. This 36 // usually happens with an example that has a main package and a +build tag 37 // of ignore. This is a bit of a hack. It causes UseAllFiles to have errors. 38 func IterativeScan(path string) ([]string, []string, error) { 39 40 // TODO(mattfarina): Add support for release tags. 41 42 tgs, _ := readBuildTags(path) 43 // Handle the case of scanning with no tags 44 tgs = append(tgs, "") 45 46 var pkgs []string 47 var testPkgs []string 48 for _, tt := range tgs { 49 50 // split the tag combination to look at permutations. 51 ts := strings.Split(tt, ",") 52 var ttgs []string 53 var arch string 54 var ops string 55 for _, ttt := range ts { 56 dirty := false 57 if strings.HasPrefix(ttt, "!") { 58 dirty = true 59 ttt = strings.TrimPrefix(ttt, "!") 60 } 61 if isSupportedOs(ttt) { 62 if dirty { 63 ops = getOsValue(ttt) 64 } else { 65 ops = ttt 66 } 67 } else if isSupportedArch(ttt) { 68 if dirty { 69 arch = getArchValue(ttt) 70 } else { 71 arch = ttt 72 } 73 } else { 74 if !dirty { 75 ttgs = append(ttgs, ttt) 76 } 77 } 78 } 79 80 // Handle the case where there are no tags but we need to iterate 81 // on something. 82 if len(ttgs) == 0 { 83 ttgs = append(ttgs, "") 84 } 85 86 b, err := util.GetBuildContext() 87 if err != nil { 88 return []string{}, []string{}, err 89 } 90 91 // Make sure use all files is off 92 b.UseAllFiles = false 93 94 // Set the OS and Arch for this pass 95 b.GOARCH = arch 96 b.GOOS = ops 97 b.BuildTags = ttgs 98 msg.Debug("Scanning with Arch(%s), OS(%s), and Build Tags(%v)", arch, ops, ttgs) 99 100 pk, err := b.ImportDir(path, 0) 101 102 // If there are no buildable souce with this permutation we skip it. 103 if err != nil && strings.HasPrefix(err.Error(), "no buildable Go source files in") { 104 continue 105 } else if err != nil && strings.HasPrefix(err.Error(), "found packages ") { 106 // A permutation may cause multiple packages to appear. For example, 107 // an example file with an ignore build tag. If this happens we 108 // ignore it. 109 // TODO(mattfarina): Find a better way. 110 msg.Debug("Found multiple packages while scanning %s: %s", path, err) 111 continue 112 } else if err != nil { 113 msg.Debug("Problem parsing package at %s for %s %s", path, ops, arch) 114 return []string{}, []string{}, err 115 } 116 117 for _, dep := range pk.Imports { 118 found := false 119 for _, p := range pkgs { 120 if p == dep { 121 found = true 122 } 123 } 124 if !found { 125 pkgs = append(pkgs, dep) 126 } 127 } 128 129 for _, dep := range pk.TestImports { 130 found := false 131 for _, p := range pkgs { 132 if p == dep { 133 found = true 134 } 135 } 136 if !found { 137 testPkgs = append(testPkgs, dep) 138 } 139 } 140 141 for _, dep := range pk.XTestImports { 142 found := false 143 for _, p := range pkgs { 144 if p == dep { 145 found = true 146 } 147 } 148 if !found { 149 testPkgs = append(testPkgs, dep) 150 } 151 } 152 } 153 154 return pkgs, testPkgs, nil 155 } 156 157 func readBuildTags(p string) ([]string, error) { 158 _, err := os.Stat(p) 159 if err != nil { 160 return []string{}, err 161 } 162 163 d, err := os.Open(p) 164 if err != nil { 165 return []string{}, err 166 } 167 168 objects, err := d.Readdir(-1) 169 if err != nil { 170 return []string{}, err 171 } 172 173 var tags []string 174 for _, obj := range objects { 175 176 // only process Go files 177 if strings.HasSuffix(obj.Name(), ".go") { 178 fp := filepath.Join(p, obj.Name()) 179 180 co, err := readGoContents(fp) 181 if err != nil { 182 return []string{}, err 183 } 184 185 // Only look at places where we had a code comment. 186 if len(co) > 0 { 187 t := findTags(co) 188 for _, tg := range t { 189 found := false 190 for _, tt := range tags { 191 if tt == tg { 192 found = true 193 } 194 } 195 if !found { 196 tags = append(tags, tg) 197 } 198 } 199 } 200 } 201 } 202 203 return tags, nil 204 } 205 206 // Read contents of a Go file up to the package declaration. This can be used 207 // to find the the build tags. 208 func readGoContents(fp string) ([]byte, error) { 209 f, err := os.Open(fp) 210 defer f.Close() 211 if err != nil { 212 return []byte{}, err 213 } 214 215 var s scanner.Scanner 216 s.Init(f) 217 var tok rune 218 var pos scanner.Position 219 for tok != scanner.EOF { 220 tok = s.Scan() 221 222 // Getting the token text will skip comments by default. 223 tt := s.TokenText() 224 // build tags will not be after the package declaration. 225 if tt == "package" { 226 pos = s.Position 227 break 228 } 229 } 230 231 buf := bytes.NewBufferString("") 232 f.Seek(0, 0) 233 _, err = io.CopyN(buf, f, int64(pos.Offset)) 234 if err != nil { 235 return []byte{}, err 236 } 237 238 return buf.Bytes(), nil 239 } 240 241 // From a byte slice of a Go file find the tags. 242 func findTags(co []byte) []string { 243 p := co 244 var tgs []string 245 for len(p) > 0 { 246 line := p 247 if i := bytes.IndexByte(line, '\n'); i >= 0 { 248 line, p = line[:i], p[i+1:] 249 } else { 250 p = p[len(p):] 251 } 252 line = bytes.TrimSpace(line) 253 // Only look at comment lines that are well formed in the Go style 254 if bytes.HasPrefix(line, []byte("//")) { 255 line = bytes.TrimSpace(line[len([]byte("//")):]) 256 if len(line) > 0 && line[0] == '+' { 257 f := strings.Fields(string(line)) 258 259 // We've found a +build tag line. 260 if f[0] == "+build" { 261 for _, tg := range f[1:] { 262 tgs = append(tgs, tg) 263 } 264 } 265 } 266 } 267 } 268 269 return tgs 270 } 271 272 // Get an OS value that's not the one passed in. 273 func getOsValue(n string) string { 274 for _, o := range osList { 275 if o != n { 276 return o 277 } 278 } 279 280 return n 281 } 282 283 func isSupportedOs(n string) bool { 284 for _, o := range osList { 285 if o == n { 286 return true 287 } 288 } 289 290 return false 291 } 292 293 // Get an Arch value that's not the one passed in. 294 func getArchValue(n string) string { 295 for _, o := range archList { 296 if o != n { 297 return o 298 } 299 } 300 301 return n 302 } 303 304 func isSupportedArch(n string) bool { 305 for _, o := range archList { 306 if o == n { 307 return true 308 } 309 } 310 311 return false 312 }