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