func run()

in cmd/eachmodule/main.go [59:223]


func run() (err error) {
	flag.Parse()
	cmds := flag.Args()
	if len(cmds) == 0 {
		log.Fatalf("no command specified")
	}

	var boots repotools.Boots

	repoRoot, err := repotools.FindRepoRoot(rootPath)
	if err != nil {
		return fmt.Errorf("failed to get repository root path, %w", err)
	}

	if pathRelRoot {
		rootPath = filepath.Join(repoRoot, rootPath)

	} else {
		if len(rootPath) == 0 {
			rootPath, err = repotools.FindRepoRoot(rootPath)
			if err != nil {
				return fmt.Errorf("failed to get repository root path, %w", err)
			}

		} else if !filepath.IsAbs(rootPath) {
			rootPath, err = repotools.JoinWorkingDirectory(rootPath)
			if err != nil {
				return fmt.Errorf("failed to get relative path, %w", err)
			}
		}

	}

	// Skip built in paths relative from the repo root.
	for _, skip := range getSkipDirs() {
		boots.SkipDirs = append(boots.SkipDirs, filepath.Join(repoRoot, skip))
	}

	// Skip additional paths relative to the root path.
	for _, skip := range strings.Split(skipPaths, string(os.PathListSeparator)) {
		skip = strings.TrimSpace(skip)
		if len(skip) == 0 {
			continue
		}
		boots.SkipDirs = append(boots.SkipDirs, filepath.Join(rootPath, skip))
	}

	if err := filepath.Walk(rootPath, boots.Walk); err != nil {
		return fmt.Errorf("failed to walk directory, %w", err)
	}

	// Set up channel on which to send signal notifications.
	// We must use a buffered channel or risk missing the signal
	// if we're not ready to receive when the signal is sent.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	ctx, cancelFn := context.WithCancel(context.Background())
	defer cancelFn()

	// Block until a signal is received.
	go func() {
		<-c
		cancelFn()
	}()

	// Logging command status
	var failed bool
	var resWG sync.WaitGroup
	resWG.Add(1)
	results := make(chan WorkLog)
	go func() {
		defer resWG.Done()
		for result := range results {
			relPath, err := filepath.Rel(repoRoot, result.Path)
			if err != nil {
				log.Println("failed to get path relative to repo root",
					repoRoot, result.Path, err)
				relPath = result.Path
			}

			var output string
			if result.Output != nil {
				b, err := ioutil.ReadAll(result.Output)
				if err != nil {
					log.Printf("%s: %s => failed to read result output, %v",
						relPath, result.Cmd, err)
				}
				output = string(b)
			}

			if result.Err != nil {
				log.Printf("%s: %s => error: %v\n%s",
					relPath, result.Cmd, result.Err, output)
				failed = true
			} else {
				log.Printf("%s: %s =>\n%s",
					relPath, result.Cmd, output)
			}

			//  Terminate early as soon as any command fails.
			if failFast && result.Err != nil {
				cancelFn()
			}
		}
	}()

	// Work consumer
	var jobWG sync.WaitGroup
	jobWG.Add(atOnce)
	jobs := make(chan Work)
	for i := 0; i < atOnce; i++ {
		go func() {
			defer jobWG.Done()
			var streamOut io.Writer
			if atOnce == 1 {
				streamOut = os.Stdout
			}
			CommandWorker(ctx, jobs, results, streamOut)
		}()
	}

	// Special case to skip root path when path if they don't contain go files.
	if skipEmptyRootPaths {
		matches, err := filepath.Glob(filepath.Join(rootPath, "*.go"))
		if err != nil || len(matches) == 0 {
			skipRootPath = true
		}
	}

	modulePaths := boots.Modules()
	if skipRootPath {
		modulePaths = removePath(rootPath, modulePaths)

	} else if !hasPath(rootPath, modulePaths) {
		modulePaths = append([]string{rootPath}, modulePaths...)
	}

	// Work producer
Loop:
	for _, modPath := range modulePaths {
		for _, cmd := range cmds {
			select {
			case <-ctx.Done():
				break Loop
			case jobs <- Work{
				Path: modPath,
				Cmd:  cmd,
			}:
			}
		}
	}
	close(jobs)

	jobWG.Wait()
	close(results)

	resWG.Wait()

	if failed {
		return fmt.Errorf("a command failed")
	}

	return nil
}