github.com/ajguerrer/rules_go@v0.20.3/go/tools/builders/generate_nogo_main.go (about)

     1  /* Copyright 2018 The Bazel Authors. All rights reserved.
     2  
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  // Generates the nogo binary to analyze Go source code at build time.
    17  
    18  package main
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"math"
    27  	"os"
    28  	"regexp"
    29  	"strconv"
    30  	"text/template"
    31  )
    32  
    33  const nogoMainTpl = `
    34  package main
    35  
    36  
    37  import (
    38  {{- if .NeedRegexp }}
    39  	"regexp"
    40  {{- end}}
    41  {{- range $import := .Imports}}
    42  	{{$import.Name}} "{{$import.Path}}"
    43  {{- end}}
    44  	"golang.org/x/tools/go/analysis"
    45  )
    46  
    47  var analyzers = []*analysis.Analyzer{
    48  {{- range $import := .Imports}}
    49  	{{$import.Name}}.Analyzer,
    50  {{- end}}
    51  }
    52  
    53  // configs maps analysis names to configurations.
    54  var configs = map[string]config{
    55  {{- range $name, $config := .Configs}}
    56  	{{printf "%q" $name}}: config{
    57  		{{- if $config.OnlyFiles}}
    58  		onlyFiles: []*regexp.Regexp{
    59  			{{- range $path, $comment := $config.OnlyFiles}}
    60  			{{- if $comment}}
    61  			// {{$comment}}
    62  			{{end -}}
    63  			{{printf "regexp.MustCompile(%q)" $path}},
    64  			{{- end}}
    65  		},
    66  		{{- end -}}
    67  		{{- if $config.ExcludeFiles}}
    68  		excludeFiles: []*regexp.Regexp{
    69  			{{- range $path, $comment := $config.ExcludeFiles}}
    70  			{{- if $comment}}
    71  			// {{$comment}}
    72  			{{end -}}
    73  			{{printf "regexp.MustCompile(%q)" $path}},
    74  			{{- end}}
    75  		},
    76  		{{- end}}
    77  	},
    78  {{- end}}
    79  }
    80  `
    81  
    82  func genNogoMain(args []string) error {
    83  	analyzerImportPaths := multiFlag{}
    84  	flags := flag.NewFlagSet("generate_nogo_main", flag.ExitOnError)
    85  	out := flags.String("output", "", "output file to write (defaults to stdout)")
    86  	flags.Var(&analyzerImportPaths, "analyzer_importpath", "import path of an analyzer library")
    87  	configFile := flags.String("config", "", "nogo config file")
    88  	if err := flags.Parse(args); err != nil {
    89  		return err
    90  	}
    91  	if *out == "" {
    92  		return errors.New("must provide output file")
    93  	}
    94  
    95  	outFile := os.Stdout
    96  	var cErr error
    97  	outFile, err := os.Create(*out)
    98  	if err != nil {
    99  		return fmt.Errorf("os.Create(%q): %v", *out, err)
   100  	}
   101  	defer func() {
   102  		if err := outFile.Close(); err != nil {
   103  			cErr = fmt.Errorf("error closing %s: %v", outFile.Name(), err)
   104  		}
   105  	}()
   106  
   107  	config, err := buildConfig(*configFile)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	type Import struct {
   113  		Path, Name string
   114  	}
   115  	// Create unique name for each imported analyzer.
   116  	suffix := 1
   117  	imports := make([]Import, 0, len(analyzerImportPaths))
   118  	for _, path := range analyzerImportPaths {
   119  		imports = append(imports, Import{
   120  			Path: path,
   121  			Name: "analyzer" + strconv.Itoa(suffix)})
   122  		if suffix == math.MaxInt32 {
   123  			return fmt.Errorf("cannot generate more than %d analyzers", suffix)
   124  		}
   125  		suffix++
   126  	}
   127  	data := struct {
   128  		Imports    []Import
   129  		Configs    Configs
   130  		NeedRegexp bool
   131  	}{
   132  		Imports: imports,
   133  		Configs: config,
   134  	}
   135  	for _, c := range config {
   136  		if len(c.OnlyFiles) > 0 || len(c.ExcludeFiles) > 0 {
   137  			data.NeedRegexp = true
   138  			break
   139  		}
   140  	}
   141  
   142  	tpl := template.Must(template.New("source").Parse(nogoMainTpl))
   143  	if err := tpl.Execute(outFile, data); err != nil {
   144  		return fmt.Errorf("template.Execute failed: %v", err)
   145  	}
   146  	return cErr
   147  }
   148  
   149  func buildConfig(path string) (Configs, error) {
   150  	if path == "" {
   151  		return Configs{}, nil
   152  	}
   153  	b, err := ioutil.ReadFile(path)
   154  	if err != nil {
   155  		return Configs{}, fmt.Errorf("failed to read config file: %v", err)
   156  	}
   157  	configs := make(Configs)
   158  	if err = json.Unmarshal(b, &configs); err != nil {
   159  		return Configs{}, fmt.Errorf("failed to unmarshal config file: %v", err)
   160  	}
   161  	for name, config := range configs {
   162  		for pattern := range config.OnlyFiles {
   163  			if _, err := regexp.Compile(pattern); err != nil {
   164  				return Configs{}, fmt.Errorf("invalid pattern for analysis %q: %v", name, err)
   165  			}
   166  		}
   167  		for pattern := range config.ExcludeFiles {
   168  			if _, err := regexp.Compile(pattern); err != nil {
   169  				return Configs{}, fmt.Errorf("invalid pattern for analysis %q: %v", name, err)
   170  			}
   171  		}
   172  		configs[name] = Config{
   173  			// Description is currently unused.
   174  			OnlyFiles:    config.OnlyFiles,
   175  			ExcludeFiles: config.ExcludeFiles,
   176  		}
   177  	}
   178  	return configs, nil
   179  }
   180  
   181  type Configs map[string]Config
   182  
   183  type Config struct {
   184  	Description  string
   185  	OnlyFiles    map[string]string `json:"only_files"`
   186  	ExcludeFiles map[string]string `json:"exclude_files"`
   187  }