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 }