@@ -4,10 +4,13 @@ package main
44
55import (
66 "bytes"
7- "io/ioutil"
7+ "go/parser"
8+ "go/token"
89 "log"
910 "os"
1011 "path/filepath"
12+ "regexp"
13+ "sort"
1114 "strings"
1215
1316 "golang.org/x/mod/modfile"
@@ -38,7 +41,7 @@ func findModuleRoot(dir string) (root string) {
3841}
3942
4043func loadModFile (filename string ) * modfile.File {
41- data , err := ioutil .ReadFile (filename )
44+ data , err := os .ReadFile (filename )
4245 if err != nil {
4346 panic (err )
4447 }
@@ -85,24 +88,30 @@ func stubModulesTxt() {
8588 }
8689
8790 modFile := loadModFile (filepath .Join (modRoot , "go.mod" ))
88-
8991 vdir := filepath .Join (modRoot , "vendor" )
9092
9193 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 )
9496
9597 generated := make (map [module.Version ]bool )
9698 var buf bytes.Buffer
9799 for _ , r := range modFile .Require {
98- // TODO: support replace lines
99100 generated [r .Mod ] = true
100101 line := moduleLine (r .Mod , module.Version {})
101102 buf .WriteString (line )
102-
103103 buf .WriteString ("## explicit\n " )
104104
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+ }
106115 }
107116
108117 // Record unused and wildcard replacements at the end of the modules.txt file:
@@ -128,8 +137,74 @@ func stubModulesTxt() {
128137 log .Fatalf ("go mod vendor: %v" , err )
129138 }
130139
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 {
132141 log .Fatalf ("go mod vendor: %v" , err )
133142 }
134143 }
135144}
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