@@ -4,10 +4,13 @@ package main
4
4
5
5
import (
6
6
"bytes"
7
- "io/ioutil"
7
+ "go/parser"
8
+ "go/token"
8
9
"log"
9
10
"os"
10
11
"path/filepath"
12
+ "regexp"
13
+ "sort"
11
14
"strings"
12
15
13
16
"golang.org/x/mod/modfile"
@@ -38,7 +41,7 @@ func findModuleRoot(dir string) (root string) {
38
41
}
39
42
40
43
func loadModFile (filename string ) * modfile.File {
41
- data , err := ioutil .ReadFile (filename )
44
+ data , err := os .ReadFile (filename )
42
45
if err != nil {
43
46
panic (err )
44
47
}
@@ -85,24 +88,30 @@ func stubModulesTxt() {
85
88
}
86
89
87
90
modFile := loadModFile (filepath .Join (modRoot , "go.mod" ))
88
-
89
91
vdir := filepath .Join (modRoot , "vendor" )
90
92
91
93
if gv := modFile .Go ; gv != nil && semver .Compare ("v" + gv .Version , "v1.14" ) >= 0 {
92
- // If the Go version is at least 1.14, generate a dummy modules.txt using only the information
93
- // in the go.mod file
94
+ // Find imports from all Go files in the project
95
+ usedPackages := findPackagesInSourceCode ( modRoot )
94
96
95
97
generated := make (map [module.Version ]bool )
96
98
var buf bytes.Buffer
97
99
for _ , r := range modFile .Require {
98
- // TODO: support replace lines
99
100
generated [r .Mod ] = true
100
101
line := moduleLine (r .Mod , module.Version {})
101
102
buf .WriteString (line )
102
-
103
103
buf .WriteString ("## explicit\n " )
104
104
105
- buf .WriteString (r .Mod .Path + "\n " )
105
+ // List package paths that are used in the source code
106
+ packagesForModule := findPackagesForModule (r .Mod .Path , usedPackages )
107
+ if len (packagesForModule ) > 0 {
108
+ for _ , pkg := range packagesForModule {
109
+ buf .WriteString (pkg + "\n " )
110
+ }
111
+ } else {
112
+ // If we can't find any packages then just list the module path itself
113
+ buf .WriteString (r .Mod .Path + "\n " )
114
+ }
106
115
}
107
116
108
117
// Record unused and wildcard replacements at the end of the modules.txt file:
@@ -128,8 +137,74 @@ func stubModulesTxt() {
128
137
log .Fatalf ("go mod vendor: %v" , err )
129
138
}
130
139
131
- if err := ioutil .WriteFile (filepath .Join (vdir , "modules.txt" ), buf .Bytes (), 0666 ); err != nil {
140
+ if err := os .WriteFile (filepath .Join (vdir , "modules.txt" ), buf .Bytes (), 0666 ); err != nil {
132
141
log .Fatalf ("go mod vendor: %v" , err )
133
142
}
134
143
}
135
144
}
145
+
146
+ // findPackagesInSourceCode scans all Go files in the directory tree and extracts import paths
147
+ func findPackagesInSourceCode (root string ) map [string ]bool {
148
+ packages := make (map [string ]bool )
149
+
150
+ err := filepath .Walk (root , func (path string , info os.FileInfo , err error ) error {
151
+ if err != nil {
152
+ return err
153
+ }
154
+
155
+ // Skip vendor directory and hidden directories
156
+ if info .IsDir () && (info .Name () == "vendor" || strings .HasPrefix (info .Name (), "." )) {
157
+ return filepath .SkipDir
158
+ }
159
+
160
+ // Only process Go files
161
+ if ! info .IsDir () && strings .HasSuffix (path , ".go" ) {
162
+ fset := token .NewFileSet ()
163
+ file , err := parser .ParseFile (fset , path , nil , parser .ImportsOnly )
164
+ if err != nil {
165
+ return err
166
+ }
167
+
168
+ // Extract import paths from the AST
169
+ for _ , imp := range file .Imports {
170
+ pkgPath := strings .Trim (imp .Path .Value , "\" " )
171
+ packages [pkgPath ] = true
172
+ }
173
+ }
174
+ return nil
175
+ })
176
+
177
+ if err != nil {
178
+ log .Printf ("Warning: error walking source directory: %v" , err )
179
+ }
180
+
181
+ return packages
182
+ }
183
+
184
+ // Compile the regular expression once
185
+ var majorVersionSuffixRegex = regexp .MustCompile (`^/v[1-9][0-9]*(/|$)` )
186
+
187
+ // findPackagesForModule returns the submodules of a given module that are actually used in the source code
188
+ func findPackagesForModule (modulePath string , usedPackages map [string ]bool ) []string {
189
+ var packages []string
190
+
191
+ for pkg := range usedPackages {
192
+ // Check if this package belongs to the module
193
+ if strings .HasPrefix (pkg , modulePath ) {
194
+ // Extract the part after modulePath
195
+ suffix := pkg [len (modulePath ):]
196
+
197
+ // If `suffix` begins with a major version suffix then we do not have the right module
198
+ // path. For example, if the module path is `example.com/mymodule` and the package path
199
+ // is `example.com/mymodule/v2/submodule` then we should not consider it a match - it
200
+ // is really a match for the module `example.com/mymodule/v2`.
201
+ if ! majorVersionSuffixRegex .MatchString (suffix ) {
202
+ packages = append (packages , pkg )
203
+ }
204
+ }
205
+ }
206
+
207
+ // Sort packages for consistent output
208
+ sort .Strings (packages )
209
+ return packages
210
+ }
0 commit comments