github.com/seh/gb@v0.4.4-0.20160724065125-065d2b2b1ba1/internal/importer/pkg.go (about) 1 package importer 2 3 import ( 4 "bufio" 5 "go/ast" // for build.Default 6 "go/parser" 7 "go/token" 8 "os" 9 "path/filepath" 10 "sort" 11 "strconv" 12 "strings" 13 14 "github.com/pkg/errors" 15 ) 16 17 var knownOS = map[string]bool{ 18 "android": true, 19 "darwin": true, 20 "dragonfly": true, 21 "freebsd": true, 22 "linux": true, 23 "nacl": true, 24 "netbsd": true, 25 "openbsd": true, 26 "plan9": true, 27 "solaris": true, 28 "windows": true, 29 } 30 31 var knownArch = map[string]bool{ 32 "386": true, 33 "amd64": true, 34 "amd64p32": true, 35 "arm": true, 36 "armbe": true, 37 "arm64": true, 38 "arm64be": true, 39 "mips": true, 40 "mipsle": true, 41 "mips64": true, 42 "mips64le": true, 43 "mips64p32": true, 44 "mips64p32le": true, 45 "ppc": true, 46 "ppc64": true, 47 "ppc64le": true, 48 "s390": true, 49 "s390x": true, 50 "sparc": true, 51 "sparc64": true, 52 } 53 54 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH 55 // suffix which does not match the current system. 56 // The recognized name formats are: 57 // 58 // name_$(GOOS).* 59 // name_$(GOARCH).* 60 // name_$(GOOS)_$(GOARCH).* 61 // name_$(GOOS)_test.* 62 // name_$(GOARCH)_test.* 63 // name_$(GOOS)_$(GOARCH)_test.* 64 // 65 // An exception: if GOOS=android, then files with GOOS=linux are also matched. 66 func goodOSArchFile(goos, goarch, name string, allTags map[string]bool) bool { 67 // Before Go 1.4, a file called "linux.go" would be equivalent to having a 68 // build tag "linux" in that file. For Go 1.4 and beyond, we require this 69 // auto-tagging to apply only to files with a non-empty prefix, so 70 // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating 71 // systems, such as android, to arrive without breaking existing code with 72 // innocuous source code in "android.go". The easiest fix: cut everything 73 // in the name before the initial _. 74 i := strings.Index(name, "_") 75 if i < 0 { 76 return true 77 } 78 name = name[i:] // ignore everything before first _ 79 80 // strip extension 81 if dot := strings.Index(name, "."); dot != -1 { 82 name = name[:dot] 83 } 84 85 l := strings.Split(name, "_") 86 if n := len(l); n > 0 && l[n-1] == "test" { 87 l = l[:n-1] 88 } 89 n := len(l) 90 switch { 91 case n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]]: 92 if allTags != nil { 93 allTags[l[n-2]] = true 94 allTags[l[n-1]] = true 95 } 96 if l[n-1] != goarch { 97 return false 98 } 99 if goos == "android" && l[n-2] == "linux" { 100 return true 101 } 102 return l[n-2] == goos 103 case n >= 1 && knownOS[l[n-1]]: 104 if allTags != nil { 105 allTags[l[n-1]] = true 106 } 107 if goos == "android" && l[n-1] == "linux" { 108 return true 109 } 110 return l[n-1] == goos 111 case n >= 1 && knownArch[l[n-1]]: 112 if allTags != nil { 113 allTags[l[n-1]] = true 114 } 115 return l[n-1] == goarch 116 default: 117 return true 118 } 119 } 120 121 type byName []os.FileInfo 122 123 func (x byName) Len() int { return len(x) } 124 func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 125 func (x byName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } 126 127 func loadPackage(p *Package) error { 128 dir, err := os.Open(p.Dir) 129 if err != nil { 130 return errors.Wrap(err, "unable open directory") 131 } 132 defer dir.Close() 133 134 dents, err := dir.Readdir(-1) 135 if err != nil { 136 return errors.Wrap(err, "unable read directory") 137 } 138 139 var Sfiles []string // files with ".S" (capital S) 140 var firstFile string 141 imported := make(map[string][]token.Position) 142 testImported := make(map[string][]token.Position) 143 xTestImported := make(map[string][]token.Position) 144 allTags := make(map[string]bool) 145 fset := token.NewFileSet() 146 147 // cmd/gb expects file names to be sorted ... this seems artificial 148 sort.Sort(byName(dents)) 149 for _, fi := range dents { 150 if fi.IsDir() { 151 continue 152 } 153 154 name := fi.Name() 155 path := filepath.Join(p.Dir, name) 156 match, data, err := p.matchFile(path, allTags) 157 if err != nil { 158 return err 159 } 160 ext := filepath.Ext(name) 161 if !match { 162 if ext == ".go" { 163 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) 164 } 165 continue 166 } 167 168 switch ext { 169 case ".c": 170 p.CFiles = append(p.CFiles, name) 171 case ".cc", ".cpp", ".cxx": 172 p.CXXFiles = append(p.CXXFiles, name) 173 case ".m": 174 p.MFiles = append(p.MFiles, name) 175 case ".h", ".hh", ".hpp", ".hxx": 176 p.HFiles = append(p.HFiles, name) 177 case ".s": 178 p.SFiles = append(p.SFiles, name) 179 case ".S": 180 Sfiles = append(Sfiles, name) 181 case ".swig": 182 p.SwigFiles = append(p.SwigFiles, name) 183 case ".swigcxx": 184 p.SwigCXXFiles = append(p.SwigCXXFiles, name) 185 case ".syso": 186 // binary objects to add to package archive 187 // Likely of the form foo_windows.syso, but 188 // the name was vetted above with goodOSArchFile. 189 p.SysoFiles = append(p.SysoFiles, name) 190 default: 191 pf, err := parser.ParseFile(fset, path, data, parser.ImportsOnly|parser.ParseComments) 192 if err != nil { 193 return err 194 } 195 196 pkg := pf.Name.Name 197 if pkg == "documentation" { 198 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) 199 continue 200 } 201 202 isTest := strings.HasSuffix(name, "_test.go") 203 isXTest := false 204 if isTest && strings.HasSuffix(pkg, "_test") { 205 isXTest = true 206 pkg = pkg[:len(pkg)-len("_test")] 207 } 208 209 if p.Name == "" { 210 p.Name = pkg 211 firstFile = name 212 } else if pkg != p.Name { 213 return &MultiplePackageError{ 214 Dir: p.Dir, 215 Packages: []string{p.Name, pkg}, 216 Files: []string{firstFile, name}, 217 } 218 } 219 // Record imports and information about cgo. 220 isCgo := false 221 for _, decl := range pf.Decls { 222 d, ok := decl.(*ast.GenDecl) 223 if !ok { 224 continue 225 } 226 for _, dspec := range d.Specs { 227 spec, ok := dspec.(*ast.ImportSpec) 228 if !ok { 229 continue 230 } 231 quoted := spec.Path.Value 232 path, err := strconv.Unquote(quoted) 233 if err != nil { 234 return errors.Errorf("%q: invalid quoted string: %q", path, quoted) 235 } 236 if isXTest { 237 xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos())) 238 } else if isTest { 239 testImported[path] = append(testImported[path], fset.Position(spec.Pos())) 240 } else { 241 imported[path] = append(imported[path], fset.Position(spec.Pos())) 242 } 243 if path == "C" { 244 if isTest { 245 return errors.Errorf("use of cgo in test %s not supported", path) 246 } 247 cg := spec.Doc 248 if cg == nil && len(d.Specs) == 1 { 249 cg = d.Doc 250 } 251 if cg != nil { 252 if err := saveCgo(p, path, cg); err != nil { 253 return err 254 } 255 } 256 isCgo = true 257 } 258 } 259 } 260 switch { 261 case isCgo: 262 allTags["cgo"] = true 263 if p.importer.(*Importer).CgoEnabled { 264 p.CgoFiles = append(p.CgoFiles, name) 265 } else { 266 p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) 267 } 268 case isXTest: 269 p.XTestGoFiles = append(p.XTestGoFiles, name) 270 case isTest: 271 p.TestGoFiles = append(p.TestGoFiles, name) 272 default: 273 p.GoFiles = append(p.GoFiles, name) 274 } 275 } 276 } 277 if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { 278 return &NoGoError{p.Dir} 279 } 280 281 for tag := range allTags { 282 p.AllTags = append(p.AllTags, tag) 283 } 284 sort.Strings(p.AllTags) 285 286 p.Imports, p.ImportPos = cleanImports(imported) 287 p.TestImports, p.TestImportPos = cleanImports(testImported) 288 p.XTestImports, p.XTestImportPos = cleanImports(xTestImported) 289 290 // add the .S files only if we are using cgo 291 // (which means gcc will compile them). 292 // The standard assemblers expect .s files. 293 if len(p.CgoFiles) > 0 { 294 p.SFiles = append(p.SFiles, Sfiles...) 295 sort.Strings(p.SFiles) 296 } 297 return nil 298 } 299 300 // saveCgo saves the information from the #cgo lines in the import "C" comment. 301 // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives 302 // that affect the way cgo's C code is built. 303 func saveCgo(di *Package, filename string, cg *ast.CommentGroup) error { 304 r := strings.NewReader(cg.Text()) 305 sc := bufio.NewScanner(r) 306 for sc.Scan() { 307 line := sc.Text() 308 309 // Line is 310 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff 311 // 312 line = strings.TrimSpace(line) 313 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { 314 continue 315 } 316 317 // Split at colon. 318 line = strings.TrimSpace(line[4:]) 319 i := strings.Index(line, ":") 320 if i < 0 { 321 return errors.Errorf("%s: invalid #cgo line: %s", filename, sc.Text()) 322 } 323 line, argstr := line[:i], line[i+1:] 324 325 // Parse GOOS/GOARCH stuff. 326 f := strings.Fields(line) 327 if len(f) < 1 { 328 return errors.Errorf("%s: invalid #cgo line: %s", filename, sc.Text()) 329 } 330 331 cond, verb := f[:len(f)-1], f[len(f)-1] 332 if len(cond) > 0 { 333 ok := false 334 for _, c := range cond { 335 if di.match(c, nil) { 336 ok = true 337 break 338 } 339 } 340 if !ok { 341 continue 342 } 343 } 344 345 args, err := splitQuoted(argstr) 346 if err != nil { 347 return errors.Wrapf(err, "%s: invalid #cgo line: %s", filename, sc.Text()) 348 } 349 for i, arg := range args { 350 arg, ok := expandSrcDir(arg, di.Dir) 351 if !ok { 352 return errors.Errorf("%s: malformed #cgo argument: %s", filename, arg) 353 } 354 args[i] = arg 355 } 356 357 switch verb { 358 case "CFLAGS": 359 di.CgoCFLAGS = append(di.CgoCFLAGS, args...) 360 case "CPPFLAGS": 361 di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) 362 case "CXXFLAGS": 363 di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) 364 case "LDFLAGS": 365 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) 366 case "pkg-config": 367 di.CgoPkgConfig = append(di.CgoPkgConfig, args...) 368 default: 369 return errors.Errorf("%s: invalid #cgo verb: %s", filename, sc.Text()) 370 } 371 } 372 return sc.Err() 373 } 374 375 func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { 376 if len(m) == 0 { 377 return nil, nil 378 } 379 all := make([]string, 0, len(m)) 380 for path := range m { 381 all = append(all, path) 382 } 383 sort.Strings(all) 384 return all, m 385 }