Skip to content

Commit 6449beb

Browse files
🌱 parse go.mod with modfile.Parse
Signed-off-by: Alexandre Mahdhaoui <[email protected]>
1 parent d09c61e commit 6449beb

File tree

6 files changed

+65
-66
lines changed

6 files changed

+65
-66
lines changed
File renamed without changes.

Makefile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ $(GOLANGCI_LINT): # Build golangci-lint from tools folder.
9494

9595
GO_MOD_CHECK_DIR := $(abspath ./hack/tools/cmd/gomodcheck)
9696
GO_MOD_CHECK := $(abspath $(TOOLS_BIN_DIR)/gomodcheck)
97-
GO_MOD_CHECK_IGNORE := $(abspath ./hack/.gomodcheck.yaml)
97+
GO_MOD_CHECK_IGNORE := $(abspath .gomodcheck.yaml)
9898
.PHONY: $(GO_MOD_CHECK)
9999
$(GO_MOD_CHECK): # Build gomodcheck
100100
go build -C $(GO_MOD_CHECK_DIR) -o $(GO_MOD_CHECK)
@@ -149,5 +149,3 @@ APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)
149149
.PHONY: apidiff
150150
verify-apidiff: $(GO_APIDIFF) ## Check for API differences
151151
$(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible
152-
153-

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ require (
3030
sigs.k8s.io/yaml v1.3.0
3131
)
3232

33+
require golang.org/x/mod v0.15.0
34+
3335
require (
3436
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
3537
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6R
156156
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
157157
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
158158
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
159+
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
160+
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
159161
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
160162
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
161163
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

hack/tools/.keep

Whitespace-only changes.

hack/tools/cmd/gomodcheck/main.go

Lines changed: 60 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"fmt"
66
"os"
77
"os/exec"
8-
"regexp"
98
"strings"
109

1110
"go.uber.org/zap"
11+
"golang.org/x/mod/modfile"
1212
"sigs.k8s.io/yaml"
1313
)
1414

@@ -17,8 +17,8 @@ const (
1717
)
1818

1919
type config struct {
20-
UpstreamRefs []string `yaml:"upstreamRefs"`
21-
ExcludedModules []string `yaml:"excludedModules"`
20+
UpstreamRefs []string `json:"upstreamRefs"`
21+
ExcludedModules []string `json:"excludedModules"`
2222
}
2323

2424
type upstream struct {
@@ -45,12 +45,12 @@ func main() {
4545
// --- 1. parse config
4646
b, err := os.ReadFile(os.Args[1])
4747
if err != nil {
48-
logger.Fatal(err.Error())
48+
fatal(err)
4949
}
5050

5151
cfg := new(config)
5252
if err := yaml.Unmarshal(b, cfg); err != nil {
53-
logger.Fatal(err.Error())
53+
fatal(err)
5454
}
5555

5656
excludedMods := make(map[string]any)
@@ -59,15 +59,15 @@ func main() {
5959
}
6060

6161
// --- 2. project mods
62-
deps, err := parseModFile()
62+
projectModules, err := modulesFromGoModFile()
6363
if err != nil {
64-
logger.Fatal(err.Error())
64+
fatal(err)
6565
}
6666

67-
// --- 3. upstream mods (holding upstream refs)
68-
upstreamModGraph, err := getUpstreamModGraph(cfg.UpstreamRefs)
67+
// --- 3. upstream mods
68+
upstreamModules, err := modulesFromUpstreamModGraph(cfg.UpstreamRefs)
6969
if err != nil {
70-
logger.Fatal(err.Error())
70+
fatal(err)
7171
}
7272

7373
oosMods := make([]oosMod, 0)
@@ -78,13 +78,13 @@ func main() {
7878
// then for each upstream module,
7979
// if project module version doesn't match upstream version,
8080
// then we add the version and the ref to the list of out of sync modules.
81-
for mod, version := range deps {
81+
for mod, version := range projectModules {
8282
if _, ok := excludedMods[mod]; ok {
8383
logger.Infof("skipped excluded module: %s", mod)
8484
continue
8585
}
8686

87-
if versionToRef, ok := upstreamModGraph[mod]; ok {
87+
if versionToRef, ok := upstreamModules[mod]; ok {
8888
upstreams := make([]upstream, 0)
8989

9090
for upstreamVersion, upstreamRef := range versionToRef {
@@ -107,97 +107,94 @@ func main() {
107107
}
108108

109109
if len(oosMods) == 0 {
110-
fmt.Println("Success! 🎉")
110+
fmt.Println("🎉 Success!")
111111
os.Exit(0)
112112
}
113113

114114
b, err = json.MarshalIndent(map[string]any{"outOfSyncModules": oosMods}, "", " ")
115115
if err != nil {
116-
panic(err)
116+
fatal(err)
117117
}
118118

119119
fmt.Println(string(b))
120120
os.Exit(1)
121121
}
122122

123-
var (
124-
cleanMods = regexp.MustCompile(`\t| *//.*`)
125-
modDelimStart = regexp.MustCompile(`^require.*`)
126-
modDelimEnd = ")"
127-
)
128-
129-
func parseModFile() (map[string]string, error) {
123+
func modulesFromGoModFile() (map[string]string, error) {
130124
b, err := os.ReadFile(modFile)
131125
if err != nil {
132126
return nil, err
133127
}
134128

135-
in := string(cleanMods.ReplaceAll(b, []byte("")))
136-
out := make(map[string]string)
137-
138-
start := false
139-
for _, s := range strings.Split(in, "\n") {
140-
switch {
141-
case modDelimStart.MatchString(s) && !start:
142-
start = true
143-
case s == modDelimEnd:
144-
return out, nil
145-
case start:
146-
kv := strings.SplitN(s, " ", 2)
147-
if len(kv) < 2 {
148-
return nil, fmt.Errorf("unexpected format for module: %q", s)
149-
}
129+
f, err := modfile.Parse(modFile, b, nil)
130+
if err != nil {
131+
return nil, err
132+
}
150133

151-
out[kv[0]] = kv[1]
152-
}
134+
out := make(map[string]string)
135+
for _, mod := range f.Require {
136+
out[mod.Mod.Path] = mod.Mod.Version
153137
}
154138

155139
return out, nil
156140
}
157141

158-
func getUpstreamModGraph(upstreamRefs []string) (map[string]map[string]string, error) {
142+
func modulesFromUpstreamModGraph(upstreamRefList []string) (map[string]map[string]string, error) {
159143
b, err := exec.Command("go", "mod", "graph").Output()
160144
if err != nil {
161145
return nil, err
162146
}
163147

164148
graph := string(b)
165-
o1Refs := make(map[string]bool)
166-
for _, upstreamRef := range upstreamRefs {
167-
o1Refs[upstreamRef] = false
149+
150+
// upstreamRefs is a set of user specified upstream modules.
151+
// The set has 2 functions:
152+
// 1. Check if `go mod graph` modules are one of the user specified upstream modules.
153+
// 2. Mark if a user specified upstream module was found in the module graph.
154+
// If a user specified upstream module is not found, gomodcheck will exit with an error.
155+
upstreamRefs := make(map[string]bool)
156+
for _, ref := range upstreamRefList {
157+
upstreamRefs[ref] = false
168158
}
169159

170160
modToVersionToUpstreamRef := make(map[string]map[string]string)
171-
172161
for _, line := range strings.Split(graph, "\n") {
173-
upstreamRef := strings.SplitN(line, "@", 2)[0]
174-
if _, ok := o1Refs[upstreamRef]; ok {
175-
o1Refs[upstreamRef] = true
176-
kv := strings.SplitN(strings.SplitN(line, " ", 2)[1], "@", 2)
177-
name := kv[0]
178-
version := kv[1]
179-
180-
if m, ok := modToVersionToUpstreamRef[kv[0]]; ok {
181-
m[version] = upstreamRef
182-
} else {
183-
versionToRef := map[string]string{version: upstreamRef}
184-
modToVersionToUpstreamRef[name] = versionToRef
185-
}
162+
ref := strings.SplitN(line, "@", 2)[0]
163+
164+
if _, ok := upstreamRefs[ref]; !ok {
165+
continue
186166
}
167+
168+
upstreamRefs[ref] = true // mark the ref as found
169+
170+
kv := strings.SplitN(strings.SplitN(line, " ", 2)[1], "@", 2)
171+
name := kv[0]
172+
version := kv[1]
173+
174+
if _, ok := modToVersionToUpstreamRef[name]; !ok {
175+
modToVersionToUpstreamRef[name] = make(map[string]string)
176+
}
177+
178+
modToVersionToUpstreamRef[name][version] = ref
187179
}
188180

189-
notFound := ""
190-
for ref, found := range o1Refs {
181+
notFoundErr := ""
182+
for ref, found := range upstreamRefs {
191183
if !found {
192-
notFound = fmt.Sprintf("%s%s, ", notFound, ref)
184+
notFoundErr = fmt.Sprintf("%s%s, ", notFoundErr, ref)
193185
}
194186
}
195187

196-
if notFound != "" {
197-
return nil, fmt.Errorf("cannot verify modules;"+
198-
"the following specified upstream module cannot be found in go.mod: [ %s ]",
199-
strings.TrimSuffix(notFound, ", "))
188+
if notFoundErr != "" {
189+
return nil, fmt.Errorf("cannot verify modules: "+
190+
"the following specified upstream module(s) cannot be found in go.mod: [ %s ]",
191+
strings.TrimSuffix(notFoundErr, ", "))
200192
}
201193

202194
return modToVersionToUpstreamRef, nil
203195
}
196+
197+
func fatal(err error) {
198+
fmt.Printf("❌ %s\n", err.Error())
199+
os.Exit(1)
200+
}

0 commit comments

Comments
 (0)