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