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