github.com/anthonymayer/glide@v0.0.0-20160224162501-bff8b50d232e/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, 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 for _, tt := range tgs { 48 49 // split the tag combination to look at permutations. 50 ts := strings.Split(tt, ",") 51 var ttgs []string 52 var arch string 53 var ops string 54 for _, ttt := range ts { 55 dirty := false 56 if strings.HasPrefix(ttt, "!") { 57 dirty = true 58 ttt = strings.TrimPrefix(ttt, "!") 59 } 60 if isSupportedOs(ttt) { 61 if dirty { 62 ops = getOsValue(ttt) 63 } else { 64 ops = ttt 65 } 66 } else if isSupportedArch(ttt) { 67 if dirty { 68 arch = getArchValue(ttt) 69 } else { 70 arch = ttt 71 } 72 } else { 73 if !dirty { 74 ttgs = append(ttgs, ttt) 75 } 76 } 77 } 78 79 // Handle the case where there are no tags but we need to iterate 80 // on something. 81 if len(ttgs) == 0 { 82 ttgs = append(ttgs, "") 83 } 84 85 b, err := util.GetBuildContext() 86 if err != nil { 87 return []string{}, err 88 } 89 90 // Make sure use all files is off 91 b.UseAllFiles = false 92 93 // Set the OS and Arch for this pass 94 b.GOARCH = arch 95 b.GOOS = ops 96 b.BuildTags = ttgs 97 msg.Debug("Scanning with Arch(%s), OS(%s), and Build Tags(%v)", arch, ops, ttgs) 98 99 pk, err := b.ImportDir(path, 0) 100 101 // If there are no buildable souce with this permutation we skip it. 102 if err != nil && strings.HasPrefix(err.Error(), "no buildable Go source files in") { 103 continue 104 } else if err != nil && strings.HasPrefix(err.Error(), "found packages ") { 105 // A permutation may cause multiple packages to appear. For example, 106 // an example file with an ignore build tag. If this happens we 107 // ignore it. 108 // TODO(mattfarina): Find a better way. 109 msg.Debug("Found multiple packages while scanning %s: %s", path, err) 110 continue 111 } else if err != nil { 112 msg.Debug("Problem parsing package at %s for %s %s", path, ops, arch) 113 return []string{}, err 114 } 115 116 for _, dep := range pk.Imports { 117 found := false 118 for _, p := range pkgs { 119 if p == dep { 120 found = true 121 } 122 } 123 if !found { 124 pkgs = append(pkgs, dep) 125 } 126 } 127 } 128 129 return pkgs, nil 130 } 131 132 func readBuildTags(p string) ([]string, error) { 133 _, err := os.Stat(p) 134 if err != nil { 135 return []string{}, err 136 } 137 138 d, err := os.Open(p) 139 if err != nil { 140 return []string{}, err 141 } 142 143 objects, err := d.Readdir(-1) 144 if err != nil { 145 return []string{}, err 146 } 147 148 var tags []string 149 for _, obj := range objects { 150 151 // only process Go files 152 if strings.HasSuffix(obj.Name(), ".go") { 153 fp := filepath.Join(p, obj.Name()) 154 155 co, err := readGoContents(fp) 156 if err != nil { 157 return []string{}, err 158 } 159 160 // Only look at places where we had a code comment. 161 if len(co) > 0 { 162 t := findTags(co) 163 for _, tg := range t { 164 found := false 165 for _, tt := range tags { 166 if tt == tg { 167 found = true 168 } 169 } 170 if !found { 171 tags = append(tags, tg) 172 } 173 } 174 } 175 } 176 } 177 178 return tags, nil 179 } 180 181 // Read contents of a Go file up to the package declaration. This can be used 182 // to find the the build tags. 183 func readGoContents(fp string) ([]byte, error) { 184 f, err := os.Open(fp) 185 defer f.Close() 186 if err != nil { 187 return []byte{}, err 188 } 189 190 var s scanner.Scanner 191 s.Init(f) 192 var tok rune 193 var pos scanner.Position 194 for tok != scanner.EOF { 195 tok = s.Scan() 196 197 // Getting the token text will skip comments by default. 198 tt := s.TokenText() 199 // build tags will not be after the package declaration. 200 if tt == "package" { 201 pos = s.Position 202 break 203 } 204 } 205 206 buf := bytes.NewBufferString("") 207 f.Seek(0, 0) 208 _, err = io.CopyN(buf, f, int64(pos.Offset)) 209 if err != nil { 210 return []byte{}, err 211 } 212 213 return buf.Bytes(), nil 214 } 215 216 // From a byte slice of a Go file find the tags. 217 func findTags(co []byte) []string { 218 p := co 219 var tgs []string 220 for len(p) > 0 { 221 line := p 222 if i := bytes.IndexByte(line, '\n'); i >= 0 { 223 line, p = line[:i], p[i+1:] 224 } else { 225 p = p[len(p):] 226 } 227 line = bytes.TrimSpace(line) 228 // Only look at comment lines that are well formed in the Go style 229 if bytes.HasPrefix(line, []byte("//")) { 230 line = bytes.TrimSpace(line[len([]byte("//")):]) 231 if len(line) > 0 && line[0] == '+' { 232 f := strings.Fields(string(line)) 233 234 // We've found a +build tag line. 235 if f[0] == "+build" { 236 for _, tg := range f[1:] { 237 tgs = append(tgs, tg) 238 } 239 } 240 } 241 } 242 } 243 244 return tgs 245 } 246 247 // Get an OS value that's not the one passed in. 248 func getOsValue(n string) string { 249 for _, o := range osList { 250 if o != n { 251 return o 252 } 253 } 254 255 return n 256 } 257 258 func isSupportedOs(n string) bool { 259 for _, o := range osList { 260 if o == n { 261 return true 262 } 263 } 264 265 return false 266 } 267 268 // Get an Arch value that's not the one passed in. 269 func getArchValue(n string) string { 270 for _, o := range archList { 271 if o != n { 272 return o 273 } 274 } 275 276 return n 277 } 278 279 func isSupportedArch(n string) bool { 280 for _, o := range archList { 281 if o == n { 282 return true 283 } 284 } 285 286 return false 287 }