func devCommand(args *command.Args, opts *devOptions) error { if !opts.Verbose { log.SetLevel(log.LInfo) } dir := opts.Dir if dir == "" { dir = "." } path, err := filepath.Abs(dir) if err != nil { return err } configPath := findConfig(dir, opts.Config) if configPath == "" { name := opts.Config if name == "" { name = fmt.Sprintf("(tried %s)", strings.Join(autoConfigNames(), ", ")) } log.Panicf("can't find configuration file %s in %s", name, dir) } log.Infof("Using config file %s", configPath) p := NewProject(path, configPath) p.port = opts.Port p.tags = opts.Tags p.goFlags = opts.GoFlags p.noDebug = opts.NoDebug p.noCache = opts.NoCache p.profile = opts.Profile go p.Build() log.Infof("Starting Gondola development server on port %d (press Control+C to exit)", p.port) if !opts.NoBrowser { time.AfterFunc(time.Second, func() { host := "localhost" if sshConn := os.Getenv("SSH_CONNECTION"); sshConn != "" { parts := strings.Split(sshConn, " ") // e.g. SSH_CONNECTION="10.211.55.2 56989 10.211.55.8 22" if len(parts) == 4 { if net.ParseIP(parts[2]) != nil { host = parts[2] } } } url := fmt.Sprintf("http://%s:%d", host, p.App.Config().Port) if err := browser.Open(url); err != nil { log.Errorf("error opening browser: open %s manually (error was %s)", url, err) } }) } p.Listen() return nil }
func (p *Project) startLocked() error { p.port = randomFreePort() cmd := p.ProjectCmd() log.Infof("Starting %s (%s)", p.Name(), cmdString(cmd)) p.cmd = cmd p.out.Reset() p.runError = nil p.exitCode = 0 err := cmd.Start() go func() { werr := cmd.Wait() if cmd == p.cmd { // Othewise the process was intentionally killed if s := cmd.ProcessState; s != nil { exitCode := exitStatus(s) p.Lock() defer p.Unlock() p.runError = werr p.exitCode = exitCode log.Warningf("%s exited with code %d", p.Name(), exitCode) } } }() time.AfterFunc(100*time.Millisecond, p.projectStarted) return err }
func rmGenCommand(args *command.Args) error { dir := "." if len(args.Args()) > 0 { dir = args.Args()[0] } re := regexp.MustCompile("(?i).+\\.gen\\..+") return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if info != nil && !info.IsDir() && re.MatchString(path) { log.Infof("Removing %s", path) if err := os.Remove(path); err != nil { return err } dir := filepath.Dir(path) if infos, err := ioutil.ReadDir(dir); err == nil && len(infos) == 0 { log.Infof("Removing empty dir %s", dir) if err := os.Remove(dir); err != nil { return err } } } return nil }) }
// Build builds the project. If the project was already building, the build // is restarted. func (p *Project) Build() { builder := &Builder{ Dir: p.dir, GoFlags: p.goFlags, Tags: p.tags, } var restarted bool p.Lock() if p.builder != nil { p.builder.Cancel() restarted = true } p.builder = builder p.StopMonitoring() p.Unlock() if err := p.Stop(); err != nil { log.Panic(err) } p.errors = nil if !restarted { log.Infof("Building %s (%s)", p.Name(), builder.BuildCommandString()) } var err error p.errors, err = builder.Build() p.Lock() defer p.Unlock() if p.builder != builder { // Canceled by another build return } p.builder = nil p.built = time.Now().UTC() if err != nil { log.Errorf("%d errors building %s", len(p.errors), p.Name()) p.reloadClients() } else { if err := p.startLocked(); err != nil { log.Panic(err) } } if err := p.StartMonitoring(); err != nil { log.Errorf("Error monitoring files for project %s: %s. Development server must be manually restarted.", p.Name(), err) } // Build dependencies, to speed up future builds go func() { builder.GoInstallDeps() }() }
func (p *Project) StartMonitoring() error { watcher, err := newFSWatcher() if err != nil { return err } var files []string pkgs, err := p.Packages() if err != nil && len(pkgs) == 0 { // Monitor just the files in the project directory infos, err2 := ioutil.ReadDir(p.dir) if err2 != nil { // Return the original error, since it will show // why the the packages failed to import return err } for _, entry := range infos { if !entry.IsDir() { files = append(files, filepath.Join(p.dir, entry.Name())) } } } watcher.IsValidFile = func(path string) bool { return path == p.configPath || isSource(path) } var timer *time.Timer var mu sync.Mutex onChanged := func(path string) { if path == p.configPath { log.Infof("Config file %s changed, restarting...", p.configPath) if err := p.Stop(); err != nil { log.Errorf("Error stopping %s: %s", p.Name(), err) } if err := p.Start(); err != nil { log.Panicf("Error starting %s: %s", p.Name(), err) } } else { // Merge multiple events arriving in // a small time window mu.Lock() if timer == nil { timer = time.AfterFunc(10*time.Millisecond, func() { mu.Lock() timer = nil p.Build() mu.Unlock() }) } mu.Unlock() } } watcher.Added = onChanged watcher.Removed = onChanged watcher.Changed = onChanged if len(files) > 0 { // Packages could not be imported and we're // using files as a fallback. for _, f := range files { if err := watcher.Add(f); err != nil { return err } } } else { if err := watcher.AddPackages(pkgs); err != nil { return err } } if err := watcher.Add(p.configPath); err != nil { return err } p.watcher = watcher return nil }