Skip to content

Commit 3b49ecb

Browse files
committed
rules_go improvement to externalize the nogo fix
1 parent 80626f5 commit 3b49ecb

17 files changed

+368
-236
lines changed

go/.DS_Store

-8 KB
Binary file not shown.

go/private/actions/compilepkg.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def emit_compilepkg(
9090
fail("nogo must be specified if and only if out_nogo_validation is specified")
9191
if bool(nogo) != bool(out_nogo_fix):
9292
fail("nogo must be specified if and only if out_nogo_fix is specified")
93+
9394
if cover and go.coverdata:
9495
archives = archives + [go.coverdata]
9596

go/private/rules/binary.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,14 @@ def _go_binary_impl(ctx):
148148
executable = executable,
149149
)
150150
validation_output = archive.data._validation_output
151+
nogo_fix_output = archive.data._out_nogo_fix
151152

152153
providers = [
153154
archive,
154155
OutputGroupInfo(
155156
cgo_exports = archive.cgo_exports,
156157
compilation_outputs = [archive.data.file],
158+
nogo_fix = [nogo_fix_output] if nogo_fix_output else [],
157159
_validation = [validation_output] if validation_output else [],
158160
),
159161
]

go/private/rules/library.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def _go_library_impl(ctx):
6666
OutputGroupInfo(
6767
cgo_exports = archive.cgo_exports,
6868
compilation_outputs = [archive.data.file],
69-
out_nogo_fix = [nogo_fix_output] if nogo_fix_output else [],
69+
nogo_fix = [nogo_fix_output] if nogo_fix_output else [],
7070
_validation = [validation_output] if validation_output else [],
7171
),
7272
]

go/private/rules/test.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,19 @@ def _go_test_impl(ctx):
6868
)
6969

7070
validation_outputs = []
71+
nogo_fix_outputs = []
7172

7273
# Compile the library to test with internal white box tests
7374
internal_library = go.new_library(go, testfilter = "exclude")
7475
internal_source = go.library_to_source(go, ctx.attr, internal_library, ctx.coverage_instrumented())
7576
internal_archive = go.archive(go, internal_source)
7677
if internal_archive.data._validation_output:
7778
validation_outputs.append(internal_archive.data._validation_output)
79+
if internal_archive.data._out_nogo_fix:
80+
# Note we will not include external_archive.data._out_nogo_fix since
81+
# they correspond to the packages external to the current workspace.
82+
nogo_fix_outputs.append(internal_archive.data._out_nogo_fix)
83+
7884
go_srcs = [src for src in internal_source.srcs if src.extension == "go"]
7985

8086
# Compile the library with the external black box tests
@@ -199,6 +205,7 @@ def _go_test_impl(ctx):
199205
),
200206
OutputGroupInfo(
201207
compilation_outputs = [internal_archive.data.file],
208+
nogo_fix = nogo_fix_outputs,
202209
_validation = validation_outputs,
203210
),
204211
coverage_common.instrumented_files_info(

go/private/sdk.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def _go_download_sdk_impl(ctx):
9191
)
9292

9393
data = ctx.read("versions.json")
94+
ctx.delete("versions.json")
9495
sdks_by_version = _parse_versions_json(data)
9596

9697
if not version:

go/tools/builders/BUILD.bazel

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ go_test(
3131
],
3232
)
3333

34+
go_test(
35+
name = "nogo_change_test",
36+
size = "small",
37+
srcs = [
38+
"nogo_change.go",
39+
"nogo_change_serialization.go",
40+
"nogo_change_serialization_test.go",
41+
"nogo_change_test.go",
42+
],
43+
deps = [
44+
"@org_golang_x_tools//go/analysis",
45+
],
46+
)
47+
3448
go_test(
3549
name = "stdliblist_test",
3650
size = "small",
@@ -99,7 +113,6 @@ go_source(
99113
"flags.go",
100114
"nogo_change.go",
101115
"nogo_change_serialization.go",
102-
"nogo_edit.go",
103116
"nogo_main.go",
104117
"nogo_typeparams_go117.go",
105118
"nogo_typeparams_go118.go",

go/tools/builders/builder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ func main() {
6262
log.SetPrefix(verb + ": ")
6363

6464
if err := action(rest); err != nil {
65-
log.Fatalf("\n$$$$$$$$$$$$$$$$$$$$$$$$ fatal: %+v", err)
65+
log.Fatal(err)
6666
}
6767
}

go/tools/builders/nogo.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ func nogo(args []string) error {
4040
fs.StringVar(&nogoPath, "nogo", "", "The nogo binary")
4141
fs.StringVar(&outFactsPath, "out_facts", "", "The file to emit serialized nogo facts to")
4242
fs.StringVar(&outLogPath, "out_log", "", "The file to emit nogo logs into")
43-
44-
fs.StringVar(&nogoFixPath, "fixpath", "", "The fix path")
43+
fs.StringVar(&nogoFixPath, "fixpath", "", "The path of the file that stores the nogo fixes")
4544

4645
if err := fs.Parse(args); err != nil {
4746
return err
@@ -90,6 +89,7 @@ func nogo(args []string) error {
9089
}
9190

9291
func runNogo(workDir string, nogoPath string, srcs, ignores []string, facts []archive, packagePath, importcfgPath, outFactsPath string, outLogPath string, nogoFixPath string) error {
92+
9393
if len(srcs) == 0 {
9494
// emit_compilepkg expects a nogo facts file, even if it's empty.
9595
// We also need to write the validation output log.
@@ -101,14 +101,15 @@ func runNogo(workDir string, nogoPath string, srcs, ignores []string, facts []ar
101101
if err != nil {
102102
return fmt.Errorf("error writing empty nogo log file: %v", err)
103103
}
104+
err = os.WriteFile(nogoFixPath, nil, 0o666)
105+
if err != nil {
106+
return fmt.Errorf("error writing empty nogo fix file: %v", err)
107+
}
104108
return nil
105109
}
106110
args := []string{nogoPath}
107111
args = append(args, "-p", packagePath)
108112
args = append(args, "-fixpath", nogoFixPath)
109-
110-
111-
// args = append(args, "-json")
112113
args = append(args, "-importcfg", importcfgPath)
113114
for _, fact := range facts {
114115
args = append(args, "-fact", fmt.Sprintf("%s=%s", fact.importPath, fact.file))

go/tools/builders/nogo_change.go

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,88 @@
11
package main
22

3+
34
import (
45
"fmt"
56
"go/token"
6-
"strings"
7+
"os"
8+
"path/filepath"
79

810
"golang.org/x/tools/go/analysis"
911
)
1012

13+
// DiagnosticEntry represents a diagnostic entry with the corresponding analyzer.
14+
type DiagnosticEntry struct {
15+
analysis.Diagnostic
16+
*analysis.Analyzer
17+
}
18+
19+
// An Edit describes the replacement of a portion of a text file.
20+
type Edit struct {
21+
New string `json:"new"` // the replacement
22+
Start int `json:"start"` // starting byte offset of the region to replace
23+
End int `json:"end"` // ending byte offset of the region to replace
24+
}
25+
26+
1127
// Change represents a set of edits to be applied to a set of files.
1228
type Change struct {
13-
AnalysisName string `json:"analysis_name"`
14-
FileToEdits map[string][]Edit `json:"file_to_edits"`
29+
AnalyzerToFileToEdits map[string]map[string][]Edit `json:"analyzer_file_to_edits"`
1530
}
1631

32+
33+
1734
// NewChange creates a new Change object.
1835
func NewChange() *Change {
1936
return &Change{
20-
FileToEdits: make(map[string][]Edit),
37+
AnalyzerToFileToEdits: make(map[string]map[string][]Edit),
2138
}
2239
}
2340

24-
// SetAnalysisName sets the name of the analysis that produced the change.
25-
func (c *Change) SetAnalysisName(name string) {
26-
c.AnalysisName = name
27-
}
28-
29-
// AddEdit adds an edit to the change.
30-
func (c *Change) AddEdit(file string, edit Edit) {
31-
c.FileToEdits[file] = append(c.FileToEdits[file], edit)
32-
}
33-
34-
// BuildFromDiagnostics builds a Change from a set of diagnostics.
41+
// NewChangeFromDiagnostics builds a Change from a set of diagnostics.
3542
// Unlike Diagnostic, Change is independent of the FileSet given it uses perf-file offsets instead of token.Pos.
3643
// This allows Change to be used in contexts where the FileSet is not available, e.g., it remains applicable after it is saved to disk and loaded back.
3744
// See https://github.com/golang/tools/blob/master/go/analysis/diagnostic.go for details.
38-
func (c *Change) BuildFromDiagnostics(diagnostics []analysis.Diagnostic, fileSet *token.FileSet) error {
39-
for _, diag := range diagnostics {
40-
for _, sf := range diag.SuggestedFixes {
45+
func NewChangeFromDiagnostics(entries []DiagnosticEntry, fileSet *token.FileSet) (*Change, error) {
46+
c := NewChange()
47+
cwd, err := os.Getwd() // workspace root
48+
if err != nil {
49+
return c, fmt.Errorf("Error getting current working directory: (%v)", err)
50+
}
51+
for _, entry := range entries {
52+
analyzer := entry.Analyzer.Name
53+
for _, sf := range entry.Diagnostic.SuggestedFixes {
4154
for _, edit := range sf.TextEdits {
4255
file := fileSet.File(edit.Pos)
4356

4457
if file == nil {
45-
return fmt.Errorf("invalid fix: missing file info for pos (%v)", edit.Pos)
58+
return c, fmt.Errorf("invalid fix: missing file info for pos (%v)", edit.Pos)
4659
}
4760
if edit.Pos > edit.End {
48-
return fmt.Errorf("invalid fix: pos (%v) > end (%v)", edit.Pos, edit.End)
61+
return c, fmt.Errorf("invalid fix: pos (%v) > end (%v)", edit.Pos, edit.End)
4962
}
5063
if eof := token.Pos(file.Base() + file.Size()); edit.End > eof {
51-
return fmt.Errorf("invalid fix: end (%v) past end of file (%v)", edit.End, eof)
64+
return c, fmt.Errorf("invalid fix: end (%v) past end of file (%v)", edit.End, eof)
5265
}
5366
edit := Edit{Start: file.Offset(edit.Pos), End: file.Offset(edit.End), New: string(edit.NewText)}
54-
fileRelativePath := file.Name()
55-
c.AddEdit(fileRelativePath, edit)
67+
fileRelativePath, err := filepath.Rel(cwd, file.Name())
68+
if err != nil {
69+
fileRelativePath = file.Name() // fallback logic
70+
}
71+
c.AddEdit(analyzer, fileRelativePath, edit)
5672
}
5773
}
5874
}
59-
return nil
75+
return c, nil
6076
}
6177

62-
// MergeChanges merges multiple changes into a single change.
63-
func MergeChanges(changes []Change) Change {
64-
mergedChange := NewChange() // Create a new Change object for the result
65-
analysisNames := []string{} // no deduplication needed
66-
67-
for _, change := range changes {
68-
if change.AnalysisName != "" {
69-
analysisNames = append(analysisNames, change.AnalysisName)
70-
}
71-
for file, edits := range change.FileToEdits {
72-
// If the file already exists in the merged change, append the edits
73-
if existingEdits, found := mergedChange.FileToEdits[file]; found {
74-
// checking the overlapping of edits happens in edit.go during the ApplyEdits function.
75-
// so we don't need to check it here.
76-
mergedChange.FileToEdits[file] = append(existingEdits, edits...)
77-
} else {
78-
// Otherwise, just set the file and edits
79-
mergedChange.FileToEdits[file] = edits
80-
}
81-
}
78+
// AddEdit adds an edit to the change.
79+
func (c *Change) AddEdit(analyzer string, file string, edit Edit) {
80+
// Check if the analyzer exists in the map
81+
if _, ok := c.AnalyzerToFileToEdits[analyzer]; !ok {
82+
// Initialize the map for the analyzer if it doesn't exist
83+
c.AnalyzerToFileToEdits[analyzer] = make(map[string][]Edit)
8284
}
83-
mergedChange.AnalysisName = strings.Join(analysisNames, ",")
84-
return *mergedChange
85+
86+
// Append the edit to the list of edits for the specific file under the analyzer
87+
c.AnalyzerToFileToEdits[analyzer][file] = append(c.AnalyzerToFileToEdits[analyzer][file], edit)
8588
}

go/tools/builders/nogo_change_serialization.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@ import (
44
"encoding/json"
55
"fmt"
66
"io/ioutil"
7-
// "log"
7+
"os"
88
)
99

1010
// SaveToFile saves the Change struct to a JSON file.
1111
func SaveToFile(filename string, change Change) error {
1212
// Serialize Change to JSON
1313
jsonData, err := json.MarshalIndent(change, "", " ")
1414
if err != nil {
15-
return fmt.Errorf("error serializing to JSON: %v", err)
15+
// needs to write to the bazel-declared output anyway.
16+
errWrite := os.WriteFile(filename, []byte(""), 0644)
17+
if errWrite != nil {
18+
return fmt.Errorf("error serializing to JSON: %v and error writing to the file: %v", err, errWrite)
19+
} else {
20+
return fmt.Errorf("error serializing to JSON: %v", err)
21+
}
1622
}
17-
// log.Fatalf("!!!!: %v", change)
23+
1824
// Write the JSON data to the file
19-
err = ioutil.WriteFile(filename, jsonData, 0644)
25+
err = os.WriteFile(filename, jsonData, 0644)
2026
if err != nil {
2127
return fmt.Errorf("error writing to file: %v", err)
2228
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"reflect"
6+
"testing"
7+
)
8+
9+
func TestSaveLoad(t *testing.T) {
10+
// Create a temporary file
11+
file, err := os.CreateTemp("", "tmp_file")
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
defer os.Remove(file.Name())
16+
17+
// Initialize a Change struct with some edits and analyzers
18+
change := *NewChange()
19+
change.AddEdit("AnalyzerA", "file1.txt", Edit{New: "replacement1", Start: 0, End: 5})
20+
change.AddEdit("AnalyzerA", "file1.txt", Edit{New: "replacement2", Start: 10, End: 15})
21+
change.AddEdit("AnalyzerB", "file2.txt", Edit{New: "new text", Start: 20, End: 25})
22+
23+
// Test saving to file
24+
err = SaveToFile(file.Name(), change)
25+
if err != nil {
26+
t.Fatalf("Failed to save Change struct to file: %v", err)
27+
}
28+
29+
// Test loading from file
30+
loadedChange, err := LoadFromFile(file.Name())
31+
if err != nil {
32+
t.Fatalf("Failed to load Change struct from file: %v", err)
33+
}
34+
35+
// Compare original and loaded Change structs
36+
if !reflect.DeepEqual(change, loadedChange) {
37+
t.Fatalf("Loaded Change struct does not match original.\nOriginal: %+v\nLoaded: %+v", change, loadedChange)
38+
}
39+
}

0 commit comments

Comments
 (0)