-
-
Notifications
You must be signed in to change notification settings - Fork 684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow the checker to run vet #1673
Conversation
go/private/rules/checker.bzl
Outdated
@@ -33,12 +33,14 @@ def _go_checker_impl(ctx): | |||
go = go_context(ctx) | |||
checker_main = go.declare_file(go, "checker_main.go") | |||
checker_args = ctx.actions.args() | |||
checker_args.add(["-output", checker_main]) | |||
checker_args.add_all(["-output", checker_main]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be checker.add("-output", checker_main)
add_all
should now only be used for lists of options (this API changed a lot recently).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/private/rules/checker.bzl
Outdated
if ctx.file.config: | ||
checker_args.add(["-config", ctx.file.config]) | ||
checker_args.add_all(["-config", ctx.file.config.path]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing about add_all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/checker_main.go
Outdated
flags := flag.NewFlagSet("checker", flag.ContinueOnError) | ||
flags.Var(&archiveFiles, "archivefile", "Archive file of a direct dependency") | ||
flags.Var(&importMapIn, "importmap", "Import maps of a direct dependency") | ||
flags.Var(&filenames, "filename", "A source file being compiled") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: change "filename"
to "src"
for consistency with compile.go
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/checker_main.go
Outdated
continue // sanity check | ||
|
||
if enableVet { | ||
vcfgFile, err := makeVetCfg(*packageList, *stdLib, archiveFiles, importMapIn, filenames) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to extract this out into another analysis so that we can run vet in the same loop as the others? Was this because of the FileSet
/ line number thing?
If the analysis API doesn't support this, we should reconsider the design of the analysis API. I think there are a lot of tools we'll want to shell out to with a thin wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem is that analysis.Finding values are specific by token positions which we cannot accurately extract from vet's command-line output. Another problem is that the analysis.Package that each analysis is given is not enough to construct the vet.cfg file that vet needs to run.
The first problem I can hack around by leaving the token positions blank and adding special cases to the filtering and error-printing logic to account for these empty tokens. Fixing the second second problem is trickier. Even if I create the vet.cfg outside of the analysis-loop, the analysis API doesn't give me a way to hand this file path to a specific analysis.
Another route I explored was special-casing in the main analysis loop so that it runs in parallel with all the other analyses. However, this made the implementation of runAnalyses() and checkAnalysisResults() a lot messier, so I decided against it. This option might be worth revisiting if vet becomes a big bottleneck in the checker program.
go/tools/builders/checker_main.go
Outdated
defer os.Remove(vcfgFile) | ||
findings, err := runVet(*vetTool, vcfgFile) | ||
if err != nil { | ||
log.Printf("error running vet:\n%v\n", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this just a printed message instead of a hard failure?
I know we discussed this before, but I can't remember the rationale. It seems like this should never fail, and if it does, it should fail closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that only legitimate analysis findings should fail builds. Other internal errors (e.g. type-checking, loading) should merely emit a warning message. Developers shouldn't be blocked by errors they have no control over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I agree. What errors can occur here?
- Analysis errors - these are legitimate and should fail the build.
- Type checking, syntax errors - these are under the user's control. Reporting these errors and failing the build is redundant because the compiler should also encounter them and fail the build. However, there's no harm in reporting an failing, since compile.go only reports checker failures if the compiler succeeds.
- Bugs and spurious I/O errors - for example, if we forgot to add vet to the sandbox, or there was a network failure retrieving something from remote execution. These are outside the user's control, but failing silently (or printing a warning without failing) can mask legitimate problems. I think this is like swallowing exceptions in Java: it's usually better to fail loudly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of the third kind of error. The vet wrapper is a hack that might break if the contract between vet and and the go build program changes in an upcoming version of Go. I don't think we should fail users if our implementation fails to handle some of these edge cases. OTOH, failing hard will ensure that these bugs are fixed in a timely manner.
Since users have to opt-in to use vet and can easily disable it, the impact on developer productivity will probably be minimal. I'll make all vet errors hard errors then.
go/tools/builders/checker_main.go
Outdated
// runAnalyses runs all analyses, filters results, and writes findings to the | ||
// given channel. | ||
func runAnalyses(c chan result, fset *token.FileSet, archiveFiles, importMapIn, srcFiles []string, stdLib string) error { | ||
apkg, err := newAnalysisPackage(fset, archiveFiles, importMapIn, srcFiles, stdLib) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a TODO, but it looks like we could save some I/O by returning early here if there are no registered analyses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/vet.go
Outdated
// parseArchivesAndImportMap parses the -archivefile and -importmap arguments | ||
// created by the compile rule into the packageFiles and importMap maps used | ||
// by the vet and checker importers. | ||
func parseArchivesAndImportMap(archiveFiles, importMapIn []string) (packageFiles map[string]string, importMap map[string]string, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should probably be in checker_main.go, since its output is also used for the importer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/vet.go
Outdated
// makeVetCfg creates a vet.cfg file and returns its file path. It is the | ||
// caller's responsibility to remove this file when it is no longer needed. | ||
func makeVetCfg(packageList, stdLib string, archiveFiles, importMap, files []string) (string, error) { | ||
pf, im, err := parseArchivesAndImportMap(archiveFiles, importMap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseArchivesAndImportMap
does a fair amount of work, but it's called twice when vet is enabled. Can its output be passed into makeVetCfg
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/vet.go
Outdated
i := strings.Index(mapping, "=") | ||
if i < 0 { | ||
err = fmt.Errorf("invalid importmap %v: no = separator", mapping) | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, nil, err
or return nil, nil, fmt.Errorf...
when errors occur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/vet.go
Outdated
for _, a := range archiveFiles { | ||
kv := strings.Split(a, "=") | ||
if len(kv) != 2 { | ||
err = fmt.Errorf("invalid archivefile %v: no = separator", a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
go/tools/builders/checker_main.go
Outdated
defer os.Remove(vcfgFile) | ||
findings, err := runVet(*vetTool, vcfgFile) | ||
if err != nil { | ||
log.Printf("error running vet:\n%v\n", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I agree. What errors can occur here?
- Analysis errors - these are legitimate and should fail the build.
- Type checking, syntax errors - these are under the user's control. Reporting these errors and failing the build is redundant because the compiler should also encounter them and fail the build. However, there's no harm in reporting an failing, since compile.go only reports checker failures if the compiler succeeds.
- Bugs and spurious I/O errors - for example, if we forgot to add vet to the sandbox, or there was a network failure retrieving something from remote execution. These are outside the user's control, but failing silently (or printing a warning without failing) can mask legitimate problems. I think this is like swallowing exceptions in Java: it's usually better to fail loudly.
go/private/rules/checker.bzl
Outdated
if ctx.file.config: | ||
checker_args.add(["-config", ctx.file.config]) | ||
checker_args.add(["-config", ctx.file.config.path]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About Args.add
again: make sure to use checker_args.add("-output", checker_main)
instead of checker_args.add(["-output", checker_main])
. add
now takes a key and optionally a value. The key should be a string, and the value may be a string or File
.
Bazel can warn you about this if you test with the --incompatible_disallow_old_style_args_add
flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks for pointing this out.
log.Printf("error parsing arguments: %v", err) | ||
return nil | ||
} | ||
if enableVet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing the thread about wrapping vet as an Analysis
, I asked Alan about this, and he wants to keep findings they way they are. It's important to be able to programmatically consume findings alongside the other data produced by the API.
Vet as it stands doesn't fit that model (though we plan to do some refactoring so it will). I expect in the future we'll want to wrap external tools in general, so we may want another abstraction, but no change needed for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack.
flags := flag.NewFlagSet("checker", flag.ContinueOnError) | ||
flags.Var(&archiveFiles, "archivefile", "Archive file of a direct dependency") | ||
flags.Var(&importMapIn, "importmap", "Import maps of a direct dependency") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing the thread about importcfg files, Bazel will set GOROOT
to the target standard library root, which is what we need. That's what the code being checked will compile and link against.
I don't think the I/O overhead will be bad. The file will be freshly written by compile.go, so it will definitely be in the kernel cache. I'd be mildly surprised if that data ever actually hits the disk.
If you want to do this in another PR after master
is merged to feature/check
, that's fine, but please add a TODO here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack. I've added a TODO here which I will address after merging master
into feature/check
.
One meta-comment: GitHub has made an unfortunate design choice where one cannot reply to comment threads that are attached to commits that are no longer part of a PR (after an amend or rebase). So it's best to update PRs by just adding new commits and pushing without When the PR is merged, the whole diff will be squashed into a single commit that will be added to the branch. So don't worry about making the PR branch messy; it won't be reflected in the final history. |
Add a go_checker attribute that causes the generated checker binary to run 'go tool vet'. Just like in the official Go build toolchain, only a subset of vet checks are run. Any vet findings will fail compilation. To avoid breaking users, vet is disabled by default. While there: - Fix issues with the importer used to load analysis packages. - Silence type-checking errors generated by the checker binary. It will now fail silently if type-checking errors occur. - Refactor the checker binary and unit tests to improve readability.
Sorry about the latest forced push. I read your meta-comment too late and went along with my commit-amending workflow. I'll make sure to use separate commits in future PRs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
This is a big change, so let's merge it now instead of iterating further.
After merging master
to feature/check
and implementing the importcfg thing, do you have any other changes planned? If not, I'll go through and do a final pass, and we can merge to master and ship a release.
This will likely be after GopherCon though; I'll be mostly offline next week during the conference, and I'm taking a couple days off after that. Will be back on Sept 4.
The only extra change I have planned is a big renaming to brand this feature as "nogo". I also have the "bottom-up" and "top-down" checks on to-do list, but that can wait till after the launch. Let's see if I can get the importcfg change in by the end of this week. Otherwise, I don't mind waiting till after Sept 4. Thanks again for reviewing all of this code. |
Add a go_checker attribute that causes the generated checker
binary to run 'go tool vet'.
Just like in the official Go build toolchain, only a subset of
vet checks are run. Any vet findings will fail compilation. To avoid
breaking users, vet is disabled by default.
While there:
now fail silently if type-checking errors occur.