From f373f67048e2566af7fb5eaa68c3bec11607010a Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Fri, 23 Apr 2021 17:31:18 -0700 Subject: Improve nogo action cache-ability. Presently, the standard library facts are not serialized in a deterministic order. This means that they have the possibility to change on each iteration, requiring a large scale re-analysis of all downstream actions, which includes all packages. Improve cache-ability of nogo actions by improving the determinism of the both facts and findings. Internally, default facts should be serialized as a sorted list for this reason already. PiperOrigin-RevId: 370188259 --- tools/nogo/findings.go | 28 ++++++++++++++++++++++++++++ tools/nogo/nogo.go | 33 +++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 4 deletions(-) (limited to 'tools') diff --git a/tools/nogo/findings.go b/tools/nogo/findings.go index 5bd850269..a00cfe813 100644 --- a/tools/nogo/findings.go +++ b/tools/nogo/findings.go @@ -19,6 +19,7 @@ import ( "fmt" "go/token" "io/ioutil" + "sort" ) // Finding is a single finding. @@ -44,6 +45,33 @@ func WriteFindingsToFile(findings []Finding, filename string) error { // WriteFindingsToBytes serializes findings as bytes. func WriteFindingsToBytes(findings []Finding) ([]byte, error) { + // N.B. Sort all the findings in order to maximize cacheability. + sort.Slice(findings, func(i, j int) bool { + switch { + case findings[i].Position.Filename < findings[j].Position.Filename: + return true + case findings[i].Position.Filename > findings[j].Position.Filename: + return false + case findings[i].Position.Line < findings[j].Position.Line: + return true + case findings[i].Position.Line > findings[j].Position.Line: + return false + case findings[i].Position.Column < findings[j].Position.Column: + return true + case findings[i].Position.Column > findings[j].Position.Column: + return false + case findings[i].Category < findings[j].Category: + return true + case findings[i].Category > findings[j].Category: + return false + case findings[i].Message < findings[j].Message: + return true + case findings[i].Message > findings[j].Message: + return false + default: + return false + } + }) return json.Marshal(findings) } diff --git a/tools/nogo/nogo.go b/tools/nogo/nogo.go index 779d4d6d8..c1b88e89f 100644 --- a/tools/nogo/nogo.go +++ b/tools/nogo/nogo.go @@ -34,6 +34,7 @@ import ( "path" "path/filepath" "reflect" + "sort" "strings" "golang.org/x/tools/go/analysis" @@ -75,6 +76,12 @@ type loader func(string) ([]byte, error) // saver is a fact-saver function. type saver func([]byte) error +// stdlibFact is used for serialiation. +type stdlibFact struct { + Package string + Facts []byte +} + // factLoader returns a function that loads facts. // // This resolves all standard library facts and imported package facts up @@ -90,10 +97,17 @@ func (c *PackageConfig) factLoader() (loader, error) { if err != nil { return nil, fmt.Errorf("error loading stdlib facts from %q: %w", c.StdlibFacts, err) } - var stdlibFacts map[string][]byte - if err := json.Unmarshal(data, &stdlibFacts); err != nil { + var ( + stdlibFactsSorted []stdlibFact + stdlibFacts = make(map[string][]byte) + ) + // See below re: sorted serialization. + if err := json.Unmarshal(data, &stdlibFactsSorted); err != nil { return nil, fmt.Errorf("error loading stdlib facts: %w", err) } + for _, stdlibFact := range stdlibFactsSorted { + stdlibFacts[stdlibFact.Package] = stdlibFact.Facts + } for pkg, data := range stdlibFacts { allFacts[pkg] = data } @@ -327,8 +341,19 @@ func CheckStdlib(config *StdlibConfig, analyzers []*analysis.Analyzer) (allFindi return nil, nil, fmt.Errorf("no stdlib facts found: misconfiguration?") } - // Write out all findings. - factData, err := json.Marshal(stdlibFacts) + // Write out all findings. Note that the standard library facts + // must be serialized in a sorted order to ensure cacheability. + stdlibFactsSorted := make([]stdlibFact, 0, len(stdlibFacts)) + for pkg, facts := range stdlibFacts { + stdlibFactsSorted = append(stdlibFactsSorted, stdlibFact{ + Package: pkg, + Facts: facts, + }) + } + sort.Slice(stdlibFactsSorted, func(i, j int) bool { + return stdlibFactsSorted[i].Package < stdlibFactsSorted[j].Package + }) + factData, err := json.Marshal(stdlibFactsSorted) if err != nil { return nil, nil, fmt.Errorf("error saving stdlib facts: %w", err) } -- cgit v1.2.3