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