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  }