github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/build/build.go (about) 1 package build 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/build" 7 "go/parser" 8 "go/scanner" 9 "go/token" 10 "go/types" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "runtime" 18 "sort" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/fsnotify/fsnotify" 24 "github.com/goplusjs/gopherjs/compiler" 25 "github.com/goplusjs/gopherjs/compiler/gopherjspkg" 26 "github.com/goplusjs/gopherjs/compiler/natives" 27 "github.com/goplusjs/gopherjs/internal/goversion" 28 "github.com/neelance/sourcemap" 29 "github.com/shurcooL/httpfs/vfsutil" 30 "github.com/visualfc/fastmod" 31 "golang.org/x/tools/go/buildutil" 32 ) 33 34 type ImportCError struct { 35 pkgPath string 36 } 37 38 func (e *ImportCError) Error() string { 39 return e.pkgPath + `: importing "C" is not supported by GopherJS` 40 } 41 42 // NewBuildContext creates a build context for building Go packages 43 // with GopherJS compiler. 44 // 45 // Core GopherJS packages (i.e., "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync") 46 // are loaded from gopherjspkg.FS virtual filesystem rather than GOPATH. 47 func NewBuildContext(installSuffix string, buildTags []string) *build.Context { 48 gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs") 49 return &build.Context{ 50 GOROOT: build.Default.GOROOT, 51 GOPATH: build.Default.GOPATH, 52 GOOS: build.Default.GOOS, 53 GOARCH: "js", 54 InstallSuffix: installSuffix, 55 Compiler: "gc", 56 BuildTags: append(buildTags, 57 "netgo", // See https://godoc.org/net#hdr-Name_Resolution. 58 "purego", // See https://golang.org/issues/23172. 59 ), 60 ReleaseTags: goversion.ReleaseTags(), 61 CgoEnabled: true, // detect `import "C"` to throw proper error 62 63 IsDir: func(path string) bool { 64 if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) { 65 path = filepath.ToSlash(path[len(gopherjsRoot):]) 66 if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil { 67 return fi.IsDir() 68 } 69 } 70 fi, err := os.Stat(path) 71 return err == nil && fi.IsDir() 72 }, 73 ReadDir: func(path string) ([]os.FileInfo, error) { 74 if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) { 75 path = filepath.ToSlash(path[len(gopherjsRoot):]) 76 if fis, err := vfsutil.ReadDir(gopherjspkg.FS, path); err == nil { 77 return fis, nil 78 } 79 } 80 return ioutil.ReadDir(path) 81 }, 82 OpenFile: func(path string) (io.ReadCloser, error) { 83 if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) { 84 path = filepath.ToSlash(path[len(gopherjsRoot):]) 85 if f, err := gopherjspkg.FS.Open(path); err == nil { 86 return f, nil 87 } 88 } 89 return os.Open(path) 90 }, 91 } 92 } 93 94 // statFile returns an os.FileInfo describing the named file. 95 // For files in "$GOROOT/src/github.com/gopherjs/gopherjs" directory, 96 // gopherjspkg.FS is consulted first. 97 func statFile(path string) (os.FileInfo, error) { 98 gopherjsRoot := filepath.Join(build.Default.GOROOT, "src", "github.com", "gopherjs", "gopherjs") 99 if strings.HasPrefix(path, gopherjsRoot+string(filepath.Separator)) { 100 path = filepath.ToSlash(path[len(gopherjsRoot):]) 101 if fi, err := vfsutil.Stat(gopherjspkg.FS, path); err == nil { 102 return fi, nil 103 } 104 } 105 return os.Stat(path) 106 } 107 108 // Import returns details about the Go package named by the import path. If the 109 // path is a local import path naming a package that can be imported using 110 // a standard import path, the returned package will set p.ImportPath to 111 // that path. 112 // 113 // In the directory containing the package, .go and .inc.js files are 114 // considered part of the package except for: 115 // 116 // - .go files in package documentation 117 // - files starting with _ or . (likely editor temporary files) 118 // - files with build constraints not satisfied by the context 119 // 120 // If an error occurs, Import returns a non-nil error and a nil 121 // *PackageData. 122 func Import(path string, mode build.ImportMode, installSuffix string, buildTags []string) (*PackageData, error) { 123 wd, err := os.Getwd() 124 if err != nil { 125 // Getwd may fail if we're in GOARCH=js mode. That's okay, handle 126 // it by falling back to empty working directory. It just means 127 // Import will not be able to resolve relative import paths. 128 wd = "" 129 } 130 bctx := NewBuildContext(installSuffix, buildTags) 131 return importWithSrcDir(*bctx, path, wd, mode, installSuffix, nil) 132 } 133 134 func importWithSrcDir(bctx build.Context, path string, srcDir string, mode build.ImportMode, installSuffix string, mod *fastmod.Package) (*PackageData, error) { 135 // bctx is passed by value, so it can be modified here. 136 var isVirtual bool 137 if path == "github.com/goplusjs/gopherjs/js" { 138 path = "github.com/gopherjs/gopherjs/js" 139 } else if path == "github.com/goplusjs/gopherjs/nosync" { 140 path = "github.com/gopherjs/gopherjs/nosync" 141 } 142 switch path { 143 case "syscall": 144 // syscall needs to use a typical GOARCH like amd64 to pick up definitions for _Socklen, BpfInsn, IFNAMSIZ, Timeval, BpfStat, SYS_FCNTL, Flock_t, etc. 145 bctx.GOARCH = runtime.GOARCH 146 bctx.InstallSuffix = "js" 147 if installSuffix != "" { 148 bctx.InstallSuffix += "_" + installSuffix 149 } 150 case "syscall/js": 151 // There are no buildable files in this package, but we need to use files in the virtual directory. 152 mode |= build.FindOnly 153 case "math/big": 154 // Use pure Go version of math/big; we don't want non-Go assembly versions. 155 bctx.BuildTags = append(bctx.BuildTags, "math_big_pure_go") 156 case "crypto/x509", "os/user": 157 // These stdlib packages have cgo and non-cgo versions (via build tags); we want the latter. 158 bctx.CgoEnabled = false 159 case "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync": 160 // These packages are already embedded via gopherjspkg.FS virtual filesystem (which can be 161 // safely vendored). Don't try to use vendor directory to resolve them. 162 mode |= build.IgnoreVendor 163 isVirtual = true 164 _, name := filepath.Split(path) 165 var gofiles []string 166 if name == "js" { 167 gofiles = []string{"js.go"} 168 } else { 169 gofiles = []string{"map.go", "mutex.go", "once.go", "pool.go"} 170 } 171 pkg := &build.Package{ 172 Dir: filepath.Join(bctx.GOROOT, "src", path), 173 Name: name, 174 ImportPath: path, 175 GoFiles: gofiles, 176 } 177 return &PackageData{Package: pkg, IsVirtual: isVirtual}, nil 178 } 179 var pkg *build.Package 180 var err error 181 if mod != nil && mod.IsValid() && !mod.IsStd() { 182 if _, dir, typ := mod.Lookup(path); typ != fastmod.PkgTypeNil { 183 srcDir = dir 184 pkg, err = bctx.ImportDir(srcDir, mode) 185 if err == nil { 186 pkg.ImportPath = path 187 } 188 } 189 } 190 if pkg == nil { 191 if filepath.IsAbs(path) { 192 pkg, err = bctx.ImportDir(path, mode) 193 } else { 194 pkg, err = bctx.Import(path, srcDir, mode) 195 } 196 } 197 if err != nil { 198 return nil, err 199 } 200 201 switch path { 202 case "os": 203 pkg.GoFiles = excludeExecutable(pkg.GoFiles) // Need to exclude executable implementation files, because some of them contain package scope variables that perform (indirectly) syscalls on init. 204 pkg.GoFiles = exclude(pkg.GoFiles, "dirent_js.go") 205 case "runtime": 206 pkg.GoFiles = []string{"typekind.go", "error.go"} 207 case "runtime/internal/sys": 208 pkg.GoFiles = []string{fmt.Sprintf("zgoos_%s.go", bctx.GOOS), "zversion.go", "stubs.go", "zgoarch_386.go", "arch_386.go", "arch.go"} 209 case "runtime/pprof": 210 pkg.GoFiles = nil 211 case "internal/poll": 212 pkg.GoFiles = exclude(pkg.GoFiles, "fd_poll_runtime.go") 213 case "crypto/rand": 214 pkg.GoFiles = []string{"rand.go", "util.go"} 215 pkg.TestGoFiles = exclude(pkg.TestGoFiles, "rand_linux_test.go") // Don't want linux-specific tests (since linux-specific package files are excluded too). 216 case "crypto/x509": 217 var files []string 218 for _, v := range pkg.GoFiles { 219 if strings.HasPrefix(v, "root_") { 220 continue 221 } 222 files = append(files, v) 223 } 224 pkg.GoFiles = files 225 case "syscall": 226 pkg.GoFiles = exclude(pkg.GoFiles, "ptrace_ios.go") 227 } 228 229 if len(pkg.CgoFiles) > 0 { 230 return nil, &ImportCError{path} 231 } 232 233 if pkg.IsCommand() { 234 pkg.PkgObj = filepath.Join(pkg.BinDir, filepath.Base(pkg.ImportPath)+".js") 235 } 236 237 if _, err := os.Stat(pkg.PkgObj); os.IsNotExist(err) && strings.HasPrefix(pkg.PkgObj, build.Default.GOROOT) { 238 // fall back to GOPATH 239 firstGopathWorkspace := filepath.SplitList(build.Default.GOPATH)[0] // TODO: Need to check inside all GOPATH workspaces. 240 gopathPkgObj := filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(build.Default.GOROOT):]) 241 if _, err := os.Stat(gopathPkgObj); err == nil { 242 pkg.PkgObj = gopathPkgObj 243 } 244 } 245 246 jsFiles, err := jsFilesFromDir(&bctx, pkg.Dir) 247 if err != nil { 248 return nil, err 249 } 250 251 return &PackageData{Package: pkg, JSFiles: jsFiles, IsVirtual: isVirtual}, nil 252 } 253 254 // excludeExecutable excludes all executable implementation .go files. 255 // They have "executable_" prefix. 256 func excludeExecutable(goFiles []string) []string { 257 var s []string 258 for _, f := range goFiles { 259 if strings.HasPrefix(f, "executable_") { 260 continue 261 } 262 s = append(s, f) 263 } 264 return s 265 } 266 267 // exclude returns files, excluding specified files. 268 func exclude(files []string, exclude ...string) []string { 269 var s []string 270 Outer: 271 for _, f := range files { 272 for _, e := range exclude { 273 if f == e { 274 continue Outer 275 } 276 } 277 s = append(s, f) 278 } 279 return s 280 } 281 282 // ImportDir is like Import but processes the Go package found in the named 283 // directory. 284 func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTags []string) (*PackageData, error) { 285 bctx := NewBuildContext(installSuffix, buildTags) 286 pkg, err := bctx.ImportDir(dir, mode) 287 if err != nil { 288 return nil, err 289 } 290 291 jsFiles, err := jsFilesFromDir(bctx, pkg.Dir) 292 if err != nil { 293 return nil, err 294 } 295 296 return &PackageData{Package: pkg, JSFiles: jsFiles}, nil 297 } 298 299 // parseAndAugment parses and returns all .go files of given pkg. 300 // Standard Go library packages are augmented with files in compiler/natives folder. 301 // If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests. 302 // If isTest is true and pkg.ImportPath has _test suffix, package is built for running external tests. 303 // 304 // The native packages are augmented by the contents of natives.FS in the following way. 305 // The file names do not matter except the usual `_test` suffix. The files for 306 // native overrides get added to the package (even if they have the same name 307 // as an existing file from the standard library). For all identifiers that exist 308 // in the original AND the overrides, the original identifier in the AST gets 309 // replaced by `_`. New identifiers that don't exist in original package get added. 310 func parseAndAugment(bctx *build.Context, pkg *build.Package, isTest bool, fileSet *token.FileSet) ([]*ast.File, error) { 311 var files []*ast.File 312 replacedDeclNames := make(map[string]bool) 313 funcName := func(d *ast.FuncDecl) string { 314 if d.Recv == nil || len(d.Recv.List) == 0 { 315 return d.Name.Name 316 } 317 recv := d.Recv.List[0].Type 318 if star, ok := recv.(*ast.StarExpr); ok { 319 recv = star.X 320 } 321 return recv.(*ast.Ident).Name + "." + d.Name.Name 322 } 323 isXTest := strings.HasSuffix(pkg.ImportPath, "_test") 324 importPath := pkg.ImportPath 325 if isXTest { 326 importPath = importPath[:len(importPath)-5] 327 } 328 329 nativesContext := &build.Context{ 330 GOROOT: "/", 331 GOOS: build.Default.GOOS, 332 GOARCH: "js", 333 Compiler: "gc", 334 ReleaseTags: goversion.ReleaseTags(), 335 JoinPath: path.Join, 336 SplitPathList: func(list string) []string { 337 if list == "" { 338 return nil 339 } 340 return strings.Split(list, "/") 341 }, 342 IsAbsPath: path.IsAbs, 343 IsDir: func(name string) bool { 344 dir, err := natives.FS.Open(name) 345 if err != nil { 346 return false 347 } 348 defer dir.Close() 349 info, err := dir.Stat() 350 if err != nil { 351 return false 352 } 353 return info.IsDir() 354 }, 355 HasSubdir: func(root, name string) (rel string, ok bool) { 356 panic("not implemented") 357 }, 358 ReadDir: func(name string) (fi []os.FileInfo, err error) { 359 dir, err := natives.FS.Open(name) 360 if err != nil { 361 return nil, err 362 } 363 defer dir.Close() 364 return dir.Readdir(0) 365 }, 366 OpenFile: func(name string) (r io.ReadCloser, err error) { 367 return natives.FS.Open(name) 368 }, 369 } 370 371 if importPath == "syscall" { 372 nativesContext.BuildTags = []string{runtime.GOARCH} 373 } 374 375 if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil { 376 names := nativesPkg.GoFiles 377 if isTest { 378 names = append(names, nativesPkg.TestGoFiles...) 379 } 380 if isXTest { 381 names = nativesPkg.XTestGoFiles 382 } 383 for _, name := range names { 384 fullPath := path.Join(nativesPkg.Dir, name) 385 r, err := nativesContext.OpenFile(fullPath) 386 if err != nil { 387 panic(err) 388 } 389 file, err := parser.ParseFile(fileSet, fullPath, r, parser.ParseComments) 390 if err != nil { 391 panic(err) 392 } 393 r.Close() 394 for _, decl := range file.Decls { 395 switch d := decl.(type) { 396 case *ast.FuncDecl: 397 replacedDeclNames[funcName(d)] = true 398 case *ast.GenDecl: 399 switch d.Tok { 400 case token.TYPE: 401 for _, spec := range d.Specs { 402 replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = true 403 } 404 case token.VAR, token.CONST: 405 for _, spec := range d.Specs { 406 for _, name := range spec.(*ast.ValueSpec).Names { 407 replacedDeclNames[name.Name] = true 408 } 409 } 410 } 411 } 412 } 413 files = append(files, file) 414 } 415 } 416 delete(replacedDeclNames, "init") 417 418 var errList compiler.ErrorList 419 for _, name := range pkg.GoFiles { 420 if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`. 421 name = filepath.Join(pkg.Dir, name) 422 } 423 r, err := buildutil.OpenFile(bctx, name) 424 if err != nil { 425 return nil, err 426 } 427 file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments) 428 r.Close() 429 if err != nil { 430 if list, isList := err.(scanner.ErrorList); isList { 431 if len(list) > 10 { 432 list = append(list[:10], &scanner.Error{Pos: list[9].Pos, Msg: "too many errors"}) 433 } 434 for _, entry := range list { 435 errList = append(errList, entry) 436 } 437 continue 438 } 439 errList = append(errList, err) 440 continue 441 } 442 443 switch pkg.ImportPath { 444 case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "testing", "time": 445 for _, spec := range file.Imports { 446 path, _ := strconv.Unquote(spec.Path.Value) 447 if path == "sync" { 448 if spec.Name == nil { 449 spec.Name = ast.NewIdent("sync") 450 } 451 spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"` 452 } 453 } 454 } 455 456 for _, decl := range file.Decls { 457 switch d := decl.(type) { 458 case *ast.FuncDecl: 459 if replacedDeclNames[funcName(d)] { 460 d.Name = ast.NewIdent("_") 461 } 462 case *ast.GenDecl: 463 switch d.Tok { 464 case token.TYPE: 465 for _, spec := range d.Specs { 466 s := spec.(*ast.TypeSpec) 467 if replacedDeclNames[s.Name.Name] { 468 s.Name = ast.NewIdent("_") 469 } 470 } 471 case token.VAR, token.CONST: 472 for _, spec := range d.Specs { 473 s := spec.(*ast.ValueSpec) 474 for i, name := range s.Names { 475 if replacedDeclNames[name.Name] { 476 s.Names[i] = ast.NewIdent("_") 477 } 478 } 479 } 480 } 481 } 482 } 483 files = append(files, file) 484 } 485 if errList != nil { 486 return nil, errList 487 } 488 return files, nil 489 } 490 491 type Options struct { 492 GOROOT string 493 GOPATH string 494 Verbose bool 495 Quiet bool 496 Watch bool 497 CreateMapFile bool 498 MapToLocalDisk bool 499 Minify bool 500 Color bool 501 BuildTags []string 502 Rebuild bool 503 } 504 505 func (o *Options) PrintError(format string, a ...interface{}) { 506 if o.Color { 507 format = "\x1B[31m" + format + "\x1B[39m" 508 } 509 fmt.Fprintf(os.Stderr, format, a...) 510 } 511 512 func (o *Options) PrintSuccess(format string, a ...interface{}) { 513 if o.Color { 514 format = "\x1B[32m" + format + "\x1B[39m" 515 } 516 fmt.Fprintf(os.Stderr, format, a...) 517 } 518 519 type PackageData struct { 520 *build.Package 521 JSFiles []string 522 IsTest bool // IsTest is true if the package is being built for running tests. 523 SrcModTime time.Time 524 UpToDate bool 525 IsVirtual bool // If true, the package does not have a corresponding physical directory on disk. 526 } 527 528 type linkname struct { 529 pos token.Pos 530 local string 531 traget string 532 } 533 534 type Session struct { 535 options *Options 536 bctx *build.Context 537 mod *fastmod.Package 538 Archives map[string]*compiler.Archive 539 Packages map[string]*PackageData 540 Types map[string]*types.Package 541 Watcher *fsnotify.Watcher 542 compilePkg string 543 compileLinkNames map[string][]compiler.LinkName 544 } 545 546 func (s *Session) checkMod(pkg *PackageData) (err error) { 547 s.mod.Clear() 548 if pkg != nil && !pkg.Goroot { 549 err := s.mod.LoadModule(pkg.Dir) 550 if err != nil { 551 return err 552 } 553 } 554 return nil 555 } 556 557 func NewSession(options *Options) *Session { 558 if options.GOROOT == "" { 559 options.GOROOT = build.Default.GOROOT 560 } 561 if options.GOPATH == "" { 562 options.GOPATH = build.Default.GOPATH 563 } 564 options.Verbose = options.Verbose || options.Watch 565 566 s := &Session{ 567 options: options, 568 Archives: make(map[string]*compiler.Archive), 569 Packages: make(map[string]*PackageData), 570 compileLinkNames: make(map[string][]compiler.LinkName), 571 } 572 s.bctx = NewBuildContext(s.InstallSuffix(), s.options.BuildTags) 573 s.mod = fastmod.NewPackage(s.bctx) 574 s.Types = make(map[string]*types.Package) 575 if options.Watch { 576 if out, err := exec.Command("ulimit", "-n").Output(); err == nil { 577 if n, err := strconv.Atoi(strings.TrimSpace(string(out))); err == nil && n < 1024 { 578 fmt.Printf("Warning: The maximum number of open file descriptors is very low (%d). Change it with 'ulimit -n 8192'.\n", n) 579 } 580 } 581 582 var err error 583 s.Watcher, err = fsnotify.NewWatcher() 584 if err != nil { 585 panic(err) 586 } 587 } 588 return s 589 } 590 591 // BuildContext returns the session's build context. 592 func (s *Session) BuildContext() *build.Context { return s.bctx } 593 594 func (s *Session) InstallSuffix() string { 595 if s.options.Minify { 596 return "min" 597 } 598 return "" 599 } 600 601 func (s *Session) BuildDir(packagePath string, importPath string, pkgObj string) error { 602 if s.Watcher != nil { 603 s.Watcher.Add(packagePath) 604 } 605 buildPkg, err := s.bctx.ImportDir(packagePath, 0) 606 if err != nil { 607 return err 608 } 609 pkg := &PackageData{Package: buildPkg} 610 jsFiles, err := jsFilesFromDir(s.bctx, pkg.Dir) 611 if err != nil { 612 return err 613 } 614 pkg.JSFiles = jsFiles 615 archive, err := s.buildPackage(pkg) 616 if err != nil { 617 return err 618 } 619 if pkgObj == "" { 620 pkgObj = filepath.Base(packagePath) + ".js" 621 } 622 if pkg.IsCommand() && !pkg.UpToDate { 623 if err := s.WriteCommandPackage(archive, pkgObj); err != nil { 624 return err 625 } 626 } 627 return nil 628 } 629 630 func (s *Session) BuildFiles(filenames []string, pkgObj string, packagePath string) error { 631 pkg := &PackageData{ 632 Package: &build.Package{ 633 Name: "main", 634 ImportPath: "main", 635 Dir: packagePath, 636 }, 637 } 638 err := s.checkMod(pkg) 639 if err != nil { 640 return err 641 } 642 643 for _, file := range filenames { 644 if strings.HasSuffix(file, ".inc.js") { 645 pkg.JSFiles = append(pkg.JSFiles, file) 646 continue 647 } 648 pkg.GoFiles = append(pkg.GoFiles, file) 649 } 650 651 archive, err := s.buildPackage(pkg) 652 if err != nil { 653 return err 654 } 655 if s.Types["main"].Name() != "main" { 656 return fmt.Errorf("cannot build/run non-main package") 657 } 658 return s.WriteCommandPackage(archive, pkgObj) 659 } 660 661 func (s *Session) BuildImportPath(path string) (*compiler.Archive, error) { 662 _, archive, err := s.BuildImportPathWithPackage(path, nil) 663 return archive, err 664 } 665 666 func (s *Session) BuildImportPathWithPackage(path string, pkgData *PackageData) (*PackageData, *compiler.Archive, error) { 667 var srcDir string 668 var mod *fastmod.Package 669 if pkgData != nil { 670 srcDir = pkgData.Dir 671 if !pkgData.Goroot { 672 mod = s.mod 673 } 674 } 675 pkg, err := importWithSrcDir(*s.bctx, path, srcDir, 0, s.InstallSuffix(), mod) 676 if s.Watcher != nil && pkg != nil { // add watch even on error 677 s.Watcher.Add(pkg.Dir) 678 } 679 if err != nil { 680 return nil, nil, err 681 } 682 683 archive, err := s.buildPackage(pkg) 684 if err != nil { 685 return nil, nil, err 686 } 687 688 return pkg, archive, nil 689 } 690 691 func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) { 692 err := s.checkMod(pkg) 693 if err != nil { 694 return nil, err 695 } 696 return s.buildPackage(pkg) 697 } 698 699 func (s *Session) checkLinkNames(importPath string, fileSet *token.FileSet, files []*ast.File) (linknames []compiler.LinkName, err error) { 700 if importPath == "internal/bytealg" || importPath == "runtime" { 701 return 702 } 703 for _, f := range files { 704 for _, group := range f.Comments { 705 for _, c := range group.List { 706 // method //go:linkname _Println fmt.Println 707 // method //go:linkname _WriteString bytes.(*Buffer).WriteString 708 if strings.HasPrefix(c.Text, "//go:linkname ") { 709 f := strings.Fields(c.Text) 710 var target string 711 if len(f) == 3 { 712 target = f[2] 713 } 714 var targetName, targetRecv, targetMethod, targetImportPath string 715 pos := strings.Index(target, ".") 716 if pos > 0 { 717 // pkg.func 718 // pkg.recv.method 719 // pkg.(*recv).method 720 targetImportPath = target[:pos] 721 fnname := target[pos+1:] 722 pos = strings.Index(fnname, ".") 723 if pos > 0 { 724 if fnname[0] == '(' { 725 if fnname[1] != '*' || fnname[pos-1] != ')' { 726 return nil, fmt.Errorf("relocation target %v not defined", target) 727 } 728 targetRecv = fnname[1 : pos-1] 729 targetMethod = fnname[pos+1:] 730 targetName = "__linkname__" + targetRecv[1:] + "_" + targetMethod 731 target = "(*" + targetImportPath + "." + targetRecv[1:] + ")." + targetMethod 732 } else { 733 targetRecv = fnname[:pos] 734 targetMethod = fnname[pos+1:] 735 targetName = "__linkname__" + targetRecv + "_" + targetMethod 736 target = "(" + targetImportPath + "." + targetRecv + ")." + targetMethod 737 } 738 } else { 739 targetName = "__linkname__" + fnname 740 } 741 } else { 742 targetName = target 743 } 744 linknames = append(linknames, compiler.LinkName{Local: f[1], Target: target, TargetName: targetName, TargetRecv: targetRecv, TargetMethod: targetMethod, TargetImportPath: targetImportPath}) 745 } 746 } 747 } 748 } 749 if len(linknames) == 0 { 750 return 751 } 752 var linkImports []string 753 for _, link := range linknames { 754 if link.TargetImportPath != "" { 755 var found bool 756 for _, v := range linkImports { 757 if v == link.TargetImportPath { 758 found = true 759 } 760 } 761 if !found { 762 linkImports = append(linkImports, link.TargetImportPath) 763 } 764 } 765 } 766 if len(linkImports) == 0 { 767 return 768 } 769 sort.Strings(linkImports) 770 771 for _, im := range linkImports { 772 if s.compilePkg == im { 773 s.compileLinkNames[im] = linknames 774 continue 775 } 776 ar, ok := s.Archives[im] 777 if !ok { 778 ar, err = s.BuildImportPath(im) 779 if err != nil { 780 return 781 } 782 } 783 compiler.UpdateLinkNames(ar, linknames) 784 if pkg, ok := s.Packages[im]; ok { 785 s.writeArchive(ar, pkg) 786 } 787 } 788 return 789 } 790 791 func (s *Session) buildPackage(pkg *PackageData) (*compiler.Archive, error) { 792 if archive, ok := s.Archives[pkg.ImportPath]; ok { 793 return archive, nil 794 } 795 796 if pkg.PkgObj != "" { 797 var fileInfo os.FileInfo 798 gopherjsBinary, err := os.Executable() 799 if err == nil { 800 fileInfo, err = os.Stat(gopherjsBinary) 801 if err == nil { 802 pkg.SrcModTime = fileInfo.ModTime() 803 } 804 } 805 if err != nil { 806 os.Stderr.WriteString("Could not get GopherJS binary's modification timestamp. Please report issue.\n") 807 pkg.SrcModTime = time.Now() 808 } 809 810 for _, importedPkgPath := range pkg.Imports { 811 // Ignore all imports that aren't mentioned in import specs of pkg. 812 // For example, this ignores imports such as runtime/internal/sys and runtime/internal/atomic. 813 ignored := true 814 for _, pos := range pkg.ImportPos[importedPkgPath] { 815 importFile := filepath.Base(pos.Filename) 816 for _, file := range pkg.GoFiles { 817 if importFile == file { 818 ignored = false 819 break 820 } 821 } 822 if !ignored { 823 break 824 } 825 } 826 827 if importedPkgPath == "unsafe" || ignored { 828 continue 829 } 830 importedPkg, _, err := s.BuildImportPathWithPackage(importedPkgPath, pkg) 831 if err != nil { 832 return nil, err 833 } 834 impModTime := importedPkg.SrcModTime 835 if impModTime.After(pkg.SrcModTime) { 836 pkg.SrcModTime = impModTime 837 } 838 } 839 840 for _, name := range append(pkg.GoFiles, pkg.JSFiles...) { 841 fileInfo, err := statFile(filepath.Join(pkg.Dir, name)) 842 if err != nil { 843 return nil, err 844 } 845 if fileInfo.ModTime().After(pkg.SrcModTime) { 846 pkg.SrcModTime = fileInfo.ModTime() 847 } 848 } 849 850 pkgObjFileInfo, err := os.Stat(pkg.PkgObj) 851 if !s.options.Rebuild && err == nil && !pkg.SrcModTime.After(pkgObjFileInfo.ModTime()) { 852 // package object is up to date, load from disk if library 853 pkg.UpToDate = true 854 if pkg.IsCommand() { 855 return nil, nil 856 } 857 858 objFile, err := os.Open(pkg.PkgObj) 859 if err != nil { 860 return nil, err 861 } 862 defer objFile.Close() 863 864 archive, err := compiler.ReadArchive(pkg.PkgObj, pkg.ImportPath, objFile, s.Types) 865 if err != nil { 866 return nil, err 867 } 868 869 if archive.Version == goversion.Version { 870 s.Archives[pkg.ImportPath] = archive 871 return archive, nil 872 } 873 } 874 } 875 876 fileSet := token.NewFileSet() 877 files, err := parseAndAugment(s.bctx, pkg.Package, pkg.IsTest, fileSet) 878 if err != nil { 879 return nil, err 880 } 881 882 localImportPathCache := make(map[string]*compiler.Archive) 883 importContext := &compiler.ImportContext{ 884 Packages: s.Types, 885 Import: func(path string) (*compiler.Archive, error) { 886 if archive, ok := localImportPathCache[path]; ok { 887 return archive, nil 888 } 889 _, archive, err := s.BuildImportPathWithPackage(path, pkg) 890 if err != nil { 891 return nil, err 892 } 893 localImportPathCache[path] = archive 894 return archive, nil 895 }, 896 } 897 embedfile, err := s.checkEmbed(pkg, fileSet, files) 898 if err != nil { 899 return nil, fmt.Errorf("check embed error: %v", err) 900 } 901 if embedfile != nil { 902 files = append(files, embedfile) 903 } 904 905 linknames, err := s.checkLinkNames(pkg.ImportPath, fileSet, files) 906 if err != nil { 907 return nil, err 908 } 909 s.compilePkg = pkg.ImportPath 910 s.compileLinkNames[pkg.ImportPath] = nil 911 archive, err := compiler.Compile(pkg.ImportPath, files, fileSet, importContext, linknames, s.options.Minify) 912 if err != nil { 913 return nil, err 914 } 915 if links := s.compileLinkNames[pkg.ImportPath]; links != nil { 916 compiler.UpdateLinkNames(archive, links) 917 } 918 919 for _, jsFile := range pkg.JSFiles { 920 code, err := ioutil.ReadFile(filepath.Join(pkg.Dir, jsFile)) 921 if err != nil { 922 return nil, err 923 } 924 archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...) 925 archive.IncJSCode = append(archive.IncJSCode, code...) 926 archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...) 927 } 928 929 if s.options.Verbose { 930 fmt.Println(pkg.Dir) 931 } 932 s.Archives[pkg.ImportPath] = archive 933 s.Packages[pkg.ImportPath] = pkg 934 935 return s.writeArchive(archive, pkg) 936 } 937 938 func (s *Session) writeArchive(archive *compiler.Archive, pkg *PackageData) (*compiler.Archive, error) { 939 if pkg.PkgObj == "" || pkg.IsCommand() { 940 return archive, nil 941 } 942 if err := s.writeLibraryPackage(archive, pkg.PkgObj); err != nil { 943 if strings.HasPrefix(pkg.PkgObj, s.options.GOROOT) { 944 // fall back to first GOPATH workspace 945 firstGopathWorkspace := filepath.SplitList(s.options.GOPATH)[0] 946 if err := s.writeLibraryPackage(archive, filepath.Join(firstGopathWorkspace, pkg.PkgObj[len(s.options.GOROOT):])); err != nil { 947 return nil, err 948 } 949 return archive, nil 950 } 951 return nil, err 952 } 953 return archive, nil 954 } 955 956 func (s *Session) writeLibraryPackage(archive *compiler.Archive, pkgObj string) error { 957 if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { 958 return err 959 } 960 961 objFile, err := os.Create(pkgObj) 962 if err != nil { 963 return err 964 } 965 defer objFile.Close() 966 967 archive.Version = goversion.Version 968 return compiler.WriteArchive(archive, objFile) 969 } 970 971 func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string) error { 972 if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { 973 return err 974 } 975 codeFile, err := os.Create(pkgObj) 976 if err != nil { 977 return err 978 } 979 defer codeFile.Close() 980 981 sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile} 982 if s.options.CreateMapFile { 983 m := &sourcemap.Map{File: filepath.Base(pkgObj)} 984 mapFile, err := os.Create(pkgObj + ".map") 985 if err != nil { 986 return err 987 } 988 989 defer func() { 990 m.WriteTo(mapFile) 991 mapFile.Close() 992 fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj)) 993 }() 994 995 sourceMapFilter.MappingCallback = NewMappingCallback(m, s.options.GOROOT, s.options.GOPATH, s.options.MapToLocalDisk) 996 } 997 998 deps, err := compiler.ImportDependencies(archive, func(path string) (*compiler.Archive, error) { 999 if archive, ok := s.Archives[path]; ok { 1000 return archive, nil 1001 } 1002 _, archive, err := s.BuildImportPathWithPackage(path, nil) 1003 return archive, err 1004 }) 1005 if err != nil { 1006 return err 1007 } 1008 return compiler.WriteProgramCode(deps, sourceMapFilter) 1009 } 1010 1011 func NewMappingCallback(m *sourcemap.Map, goroot, gopath string, localMap bool) func(generatedLine, generatedColumn int, originalPos token.Position) { 1012 return func(generatedLine, generatedColumn int, originalPos token.Position) { 1013 if !originalPos.IsValid() { 1014 m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn}) 1015 return 1016 } 1017 1018 file := originalPos.Filename 1019 1020 switch hasGopathPrefix, prefixLen := hasGopathPrefix(file, gopath); { 1021 case localMap: 1022 // no-op: keep file as-is 1023 case hasGopathPrefix: 1024 file = filepath.ToSlash(file[prefixLen+4:]) 1025 case strings.HasPrefix(file, goroot): 1026 file = filepath.ToSlash(file[len(goroot)+4:]) 1027 default: 1028 file = filepath.Base(file) 1029 } 1030 1031 m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn, OriginalFile: file, OriginalLine: originalPos.Line, OriginalColumn: originalPos.Column}) 1032 } 1033 } 1034 1035 func jsFilesFromDir(bctx *build.Context, dir string) ([]string, error) { 1036 files, err := buildutil.ReadDir(bctx, dir) 1037 if err != nil { 1038 return nil, err 1039 } 1040 var jsFiles []string 1041 for _, file := range files { 1042 if strings.HasSuffix(file.Name(), ".inc.js") && file.Name()[0] != '_' && file.Name()[0] != '.' { 1043 jsFiles = append(jsFiles, file.Name()) 1044 } 1045 } 1046 return jsFiles, nil 1047 } 1048 1049 // hasGopathPrefix returns true and the length of the matched GOPATH workspace, 1050 // iff file has a prefix that matches one of the GOPATH workspaces. 1051 func hasGopathPrefix(file, gopath string) (hasGopathPrefix bool, prefixLen int) { 1052 gopathWorkspaces := filepath.SplitList(gopath) 1053 for _, gopathWorkspace := range gopathWorkspaces { 1054 gopathWorkspace = filepath.Clean(gopathWorkspace) 1055 if strings.HasPrefix(file, gopathWorkspace) { 1056 return true, len(gopathWorkspace) 1057 } 1058 } 1059 return false, 0 1060 } 1061 1062 func (s *Session) WaitForChange() { 1063 s.options.PrintSuccess("watching for changes...\n") 1064 for { 1065 select { 1066 case ev := <-s.Watcher.Events: 1067 if ev.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove|fsnotify.Rename) == 0 || filepath.Base(ev.Name)[0] == '.' { 1068 continue 1069 } 1070 if !strings.HasSuffix(ev.Name, ".go") && !strings.HasSuffix(ev.Name, ".inc.js") { 1071 continue 1072 } 1073 s.options.PrintSuccess("change detected: %s\n", ev.Name) 1074 case err := <-s.Watcher.Errors: 1075 s.options.PrintError("watcher error: %s\n", err.Error()) 1076 } 1077 break 1078 } 1079 1080 go func() { 1081 for range s.Watcher.Events { 1082 // consume, else Close() may deadlock 1083 } 1084 }() 1085 s.Watcher.Close() 1086 }