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