// 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 (b *Builder) Build() ([]*BuildError, error) { b.Cancel() cmd, err := b.compilerCmd() if err != nil { return nil, err } var buf bytes.Buffer cmd.Stdout = os.Stdout cmd.Stderr = io.MultiWriter(&buf, os.Stderr) err = cmd.Run() var errs []*BuildError if err != nil { exitErr, ok := err.(*exec.ExitError) if !ok { log.Panic(err) } if es := exitStatus(exitErr.ProcessState); es != 1 && es != 2 { // gc returns 1 when it can't find a package, 2 when there are compilation errors log.Panic(err) } r := bufio.NewReader(bytes.NewReader(buf.Bytes())) var pkg string for { eline, err := r.ReadString('\n') if err != nil { if err == io.EOF { break } log.Panic(err) } var be *BuildError switch { case strings.HasPrefix(eline, "package "): // package level error, like cyclic or non-allowed // (e.g. internal) imports. We need to create an error // now, since this line will usually be followed by // lines starting with \t pkg = strings.TrimSpace(eline[len("package"):]) be = &BuildError{ Error: strings.TrimSpace(eline), } case strings.HasPrefix(eline, "#"): // Package name before file level errors pkg = strings.TrimSpace(eline[1:]) case strings.HasPrefix(eline, "\t"): // Info related to the previous error. Let it // crash if we don't have a previous error, just // in case there are any circumstances where a line // starting with \t means something else in the future. // This way the problem will be easier to catch. be := errs[len(errs)-1] be.Error += fmt.Sprintf(" (%s)", strings.TrimSpace(eline)) default: parts := strings.SplitN(eline, ":", 3) if len(parts) == 3 { // file level error => filename:line:error filename := filepath.Clean(filepath.Join(b.Dir, parts[0])) line, err := strconv.Atoi(parts[1]) if err != nil { // Not a line number, show error message be = &BuildError{ Error: strings.TrimSpace(eline), } break } be = &BuildError{ Filename: filename, Line: line, Error: strings.TrimSpace(parts[2]), } } else { // Unknown error, just show the error message be = &BuildError{ Error: strings.TrimSpace(eline), } } } if be != nil { be.Package = pkg errs = append(errs, be) } } } if c := len(errs); c > 0 { return errs, fmt.Errorf("%d errors", c) } return nil, nil }