github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/project/run.go (about) 1 package project 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "os/exec" 11 "runtime" 12 "strings" 13 14 "golang.org/x/sync/errgroup" 15 16 "github.com/mistwind/reviewdog" 17 "github.com/mistwind/reviewdog/diff" 18 "github.com/mistwind/reviewdog/filter" 19 "github.com/mistwind/reviewdog/parser" 20 ) 21 22 // RunAndParse runs commands and parse results. Returns map of tool name to check results. 23 func RunAndParse(ctx context.Context, conf *Config, runners map[string]bool, defaultLevel string, teeMode bool) (*reviewdog.ResultMap, error) { 24 var results reviewdog.ResultMap 25 // environment variables for each commands 26 envs := filteredEnviron() 27 cmdBuilder := newCmdBuilder(envs, teeMode) 28 var usedRunners []string 29 var g errgroup.Group 30 semaphoreNum := runtime.NumCPU() 31 if teeMode { 32 semaphoreNum = 1 33 } 34 semaphore := make(chan int, semaphoreNum) 35 for key, runner := range conf.Runner { 36 runner := runner 37 runnerName := getRunnerName(key, runner) 38 if len(runners) != 0 && !runners[runnerName] { 39 continue // Skip this runner. 40 } 41 usedRunners = append(usedRunners, runnerName) 42 semaphore <- 1 43 log.Printf("reviewdog: [start]\trunner=%s", runnerName) 44 fname := runner.Format 45 if fname == "" && len(runner.Errorformat) == 0 { 46 fname = runnerName 47 } 48 opt := &parser.Option{FormatName: fname, Errorformat: runner.Errorformat} 49 log.Printf("reviewdog: parse opts = %v\n", *opt) 50 p, err := parser.New(opt) 51 if err != nil { 52 return nil, err 53 } 54 cmd, stdout, stderr, err := cmdBuilder.build(ctx, runner.Cmd) 55 if err != nil { 56 return nil, err 57 } 58 if err := cmd.Start(); err != nil { 59 return nil, fmt.Errorf("fail to start command: %w", err) 60 } 61 g.Go(func() error { 62 defer func() { <-semaphore }() 63 diagnostics, err := p.Parse(io.MultiReader(stdout, stderr)) 64 if err != nil { 65 return err 66 } 67 log.Printf("reviewdog: parse ok\n") 68 log.Printf("reviewdog: diagnostic len = %d\n", len(diagnostics)) 69 70 for _, d := range diagnostics { 71 log.Printf("reviewdog: diagnostic = %#v\n", d) 72 } 73 74 level := runner.Level 75 if level == "" { 76 level = defaultLevel 77 } 78 cmdErr := cmd.Wait() 79 if e, ok := cmdErr.(*exec.ExitError); ok { 80 fmt.Printf("exit error = %s, processtate = %s\n", string(e.Stderr), e.ProcessState) 81 } else { 82 fmt.Printf("cmdErr is not a exitError\n") 83 } 84 85 results.Store(runnerName, &reviewdog.Result{ 86 Name: runnerName, 87 Level: level, 88 Diagnostics: diagnostics, 89 CmdErr: cmdErr, 90 }) 91 msg := fmt.Sprintf("reviewdog: [finish]\trunner=%s", runnerName) 92 if cmdErr != nil { 93 msg += fmt.Sprintf("\terror=%v", cmdErr) 94 } 95 log.Println(msg) 96 return nil 97 }) 98 } 99 if err := g.Wait(); err != nil { 100 return nil, fmt.Errorf("fail to run reviewdog: %w", err) 101 } 102 if err := checkUnknownRunner(runners, usedRunners); err != nil { 103 return nil, err 104 } 105 return &results, nil 106 } 107 108 // Run runs reviewdog tasks based on Config. 109 func Run(ctx context.Context, conf *Config, runners map[string]bool, c reviewdog.CommentService, d reviewdog.DiffService, teeMode bool, filterMode filter.Mode, failOnError bool) error { 110 results, err := RunAndParse(ctx, conf, runners, "", teeMode) // Level is not used. 111 if err != nil { 112 return err 113 } 114 if results.Len() == 0 { 115 return nil 116 } 117 118 b, err := d.Diff(ctx) 119 if err != nil { 120 fmt.Printf("diff error: %s\n", err) 121 return err 122 } 123 fmt.Printf("diff ok\n") 124 125 filediffs, err := diff.ParseMultiFile(bytes.NewReader(b)) 126 if err != nil { 127 return err 128 } 129 var g errgroup.Group 130 results.Range(func(toolname string, result *reviewdog.Result) { 131 ds := result.Diagnostics 132 g.Go(func() error { 133 if err := result.CheckUnexpectedFailure(); err != nil { 134 return err 135 } 136 return reviewdog.RunFromResult(ctx, c, ds, filediffs, d.Strip(), toolname, filterMode, failOnError) 137 }) 138 }) 139 return g.Wait() 140 } 141 142 var secretEnvs = [...]string{ 143 "REVIEWDOG_GITHUB_API_TOKEN", 144 "REVIEWDOG_GITLAB_API_TOKEN", 145 "REVIEWDOG_TOKEN", 146 } 147 148 func filteredEnviron() []string { 149 for _, name := range secretEnvs { 150 defer func(name, value string) { 151 if value != "" { 152 os.Setenv(name, value) 153 } 154 }(name, os.Getenv(name)) 155 os.Unsetenv(name) 156 } 157 return os.Environ() 158 } 159 160 func checkUnknownRunner(specifiedRunners map[string]bool, usedRunners []string) error { 161 if len(specifiedRunners) == 0 { 162 return nil 163 } 164 for _, r := range usedRunners { 165 delete(specifiedRunners, r) 166 } 167 var rs []string 168 for r := range specifiedRunners { 169 rs = append(rs, r) 170 } 171 if len(specifiedRunners) != 0 { 172 return fmt.Errorf("runner not found: [%s]", strings.Join(rs, ",")) 173 } 174 return nil 175 } 176 177 func getRunnerName(key string, runner *Runner) string { 178 if runner.Name != "" { 179 return runner.Name 180 } 181 return key 182 }