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
}