func runp(jirix *jiri.X, cmd *cmdline.Command, args []string) error { hasUntrackedSet := profilescmdline.IsFlagSet(cmd.ParsedFlags, "has-untracked") hasUncommitedSet := profilescmdline.IsFlagSet(cmd.ParsedFlags, "has-uncommitted") hasGerritSet := profilescmdline.IsFlagSet(cmd.ParsedFlags, "has-gerrit-message") if runpFlags.interactive { runpFlags.collateOutput = false } var keysRE, branchRE *regexp.Regexp var err error if profilescmdline.IsFlagSet(cmd.ParsedFlags, "projects") { re := "" for _, pre := range strings.Split(runpFlags.projectKeys, ",") { re += pre + "|" } re = strings.TrimRight(re, "|") keysRE, err = regexp.Compile(re) if err != nil { return fmt.Errorf("failed to compile projects regexp: %q: %v", runpFlags.projectKeys, err) } } if profilescmdline.IsFlagSet(cmd.ParsedFlags, "has-branch") { branchRE, err = regexp.Compile(runpFlags.hasBranch) if err != nil { return fmt.Errorf("failed to compile has-branch regexp: %q: %v", runpFlags.hasBranch, err) } } for _, f := range []string{"show-key-prefix", "show-name-prefix"} { if profilescmdline.IsFlagSet(cmd.ParsedFlags, f) { if runpFlags.interactive && profilescmdline.IsFlagSet(cmd.ParsedFlags, "interactive") { fmt.Fprintf(jirix.Stderr(), "WARNING: interactive mode being disabled because %s was set\n", f) } runpFlags.interactive = false runpFlags.collateOutput = true break } } git := gitutil.New(jirix.NewSeq()) homeBranch, err := git.CurrentBranchName() if err != nil { // jiri was run from outside of a project. Let's assume we'll // use all projects if none have been specified via the projects flag. if keysRE == nil { keysRE = regexp.MustCompile(".*") } } dirty := false if hasUntrackedSet || hasUncommitedSet { dirty = true } states, err := project.GetProjectStates(jirix, dirty) if err != nil { return err } mapInputs := map[project.ProjectKey]*mapInput{} var keys project.ProjectKeys for key, state := range states { if keysRE != nil { if !keysRE.MatchString(string(key)) { continue } } else { if state.CurrentBranch != homeBranch { continue } } if branchRE != nil { found := false for _, br := range state.Branches { if branchRE.MatchString(br.Name) { found = true break } } if !found { continue } } if hasUntrackedSet && (state.HasUntracked != runpFlags.hasUntracked) { continue } if hasUncommitedSet && (state.HasUncommitted != runpFlags.hasUncommitted) { continue } if hasGerritSet { hasMsg := false for _, br := range state.Branches { if (state.CurrentBranch == br.Name) && br.HasGerritMessage { hasMsg = true break } } if hasMsg != runpFlags.hasGerritMessage { continue } } mapInputs[key] = &mapInput{ ProjectState: state, jirix: jirix, key: key, } keys = append(keys, key) } total := len(mapInputs) index := 1 for _, mi := range mapInputs { mi.index = index mi.total = total index++ } if runpFlags.verbose { fmt.Fprintf(jirix.Stdout(), "Project Names: %s\n", strings.Join(stateNames(mapInputs), " ")) fmt.Fprintf(jirix.Stdout(), "Project Keys: %s\n", strings.Join(stateKeys(mapInputs), " ")) } reader, err := profilesreader.NewReader(jirix, runpFlags.ProfilesMode, runpFlags.DBFilename) runner := &runner{ reader: reader, args: args, } mr := simplemr.MR{} if runpFlags.interactive { // Run one mapper at a time. mr.NumMappers = 1 sort.Sort(keys) } in, out := make(chan *simplemr.Record, len(mapInputs)), make(chan *simplemr.Record, len(mapInputs)) sigch := make(chan os.Signal) signal.Notify(sigch, os.Interrupt) go func() { <-sigch; mr.Cancel() }() go mr.Run(in, out, runner, runner) for _, key := range keys { in <- &simplemr.Record{string(key), []interface{}{mapInputs[key]}} } close(in) <-out return mr.Error() }
func (r *runner) Map(mr *simplemr.MR, key string, val interface{}) error { mi := val.(*mapInput) output := &mapOutput{ key: key, mi: mi} jirix := mi.jirix path := os.Getenv("SHELL") if path == "" { path = "sh" } var wg sync.WaitGroup cmd := exec.Command(path, "-c", strings.Join(r.args, " ")) cmd.Env = envvar.MapToSlice(jirix.Env()) cmd.Dir = mi.ProjectState.Project.Path cmd.Stdin = mi.jirix.Stdin() var stdoutCloser, stderrCloser io.Closer if runpFlags.interactive { cmd.Stdout = jirix.Stdout() cmd.Stderr = jirix.Stderr() } else { var stdout io.Writer stderr := r.serializedWriter(jirix.Stderr()) var cleanup func() if runpFlags.collateOutput { // Write standard output to a file, stderr // is not collated. f, err := ioutil.TempFile("", mi.ProjectState.Project.Name+"-") if err != nil { return err } stdout = f output.outputFilename = f.Name() cleanup = func() { os.Remove(output.outputFilename) } // The child process will have exited by the // time this method returns so it's safe to close the file // here. defer f.Close() } else { stdout = r.serializedWriter(jirix.Stdout()) cleanup = func() {} } if !runpFlags.showNamePrefix && !runpFlags.showKeyPrefix { // write directly to stdout, stderr if there's no prefix cmd.Stdout = stdout cmd.Stderr = stderr } else { stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { cleanup() return err } stderrReader, stderrWriter, err := os.Pipe() if err != nil { cleanup() stdoutReader.Close() stdoutWriter.Close() return err } cmd.Stdout = stdoutWriter cmd.Stderr = stderrWriter // Record the write end of the pipe so that it can be closed // after the child has exited, this ensures that all goroutines // will finish. stdoutCloser = stdoutWriter stderrCloser = stderrWriter prefix := key if runpFlags.showNamePrefix { prefix = mi.ProjectState.Project.Name } wg.Add(2) go func() { copyWithPrefix(prefix, stdout, stdoutReader); wg.Done() }() go func() { copyWithPrefix(prefix, stderr, stderrReader); wg.Done() }() } } if err := cmd.Start(); err != nil { mi.result = err } done := make(chan error) go func() { done <- cmd.Wait() }() select { case output.err = <-done: if output.err != nil && runpFlags.exitOnError { mr.Cancel() } case <-mr.CancelCh(): output.err = cmd.Process.Kill() } for _, closer := range []io.Closer{stdoutCloser, stderrCloser} { if closer != nil { closer.Close() } } wg.Wait() mr.MapOut(key, output) return nil }