|
8 | 8 | "log" |
9 | 9 | "os" |
10 | 10 | "path/filepath" |
| 11 | + "regexp" |
11 | 12 | "strings" |
12 | 13 |
|
13 | 14 | "golang.org/x/mod/modfile" |
@@ -85,24 +86,30 @@ func stubModulesTxt() { |
85 | 86 | } |
86 | 87 |
|
87 | 88 | modFile := loadModFile(filepath.Join(modRoot, "go.mod")) |
88 | | - |
89 | 89 | vdir := filepath.Join(modRoot, "vendor") |
90 | 90 |
|
91 | 91 | 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 |
| 92 | + // Find imports from all Go files in the project |
| 93 | + usedPackages := findPackagesInSourceCode(modRoot) |
94 | 94 |
|
95 | 95 | generated := make(map[module.Version]bool) |
96 | 96 | var buf bytes.Buffer |
97 | 97 | for _, r := range modFile.Require { |
98 | | - // TODO: support replace lines |
99 | 98 | generated[r.Mod] = true |
100 | 99 | line := moduleLine(r.Mod, module.Version{}) |
101 | 100 | buf.WriteString(line) |
102 | | - |
103 | 101 | buf.WriteString("## explicit\n") |
104 | 102 |
|
105 | | - buf.WriteString(r.Mod.Path + "\n") |
| 103 | + // List submodules that are actually used in the source code |
| 104 | + submodulesForModule := findSubmodulesForModule(r.Mod.Path, usedPackages) |
| 105 | + if len(submodulesForModule) > 0 { |
| 106 | + for _, submod := range submodulesForModule { |
| 107 | + buf.WriteString(submod + "\n") |
| 108 | + } |
| 109 | + } else { |
| 110 | + // If no specific submodules are used, just list the module path itself |
| 111 | + buf.WriteString(r.Mod.Path + "\n") |
| 112 | + } |
106 | 113 | } |
107 | 114 |
|
108 | 115 | // Record unused and wildcard replacements at the end of the modules.txt file: |
@@ -133,3 +140,110 @@ func stubModulesTxt() { |
133 | 140 | } |
134 | 141 | } |
135 | 142 | } |
| 143 | + |
| 144 | +// findPackagesInSourceCode scans all Go files in the directory tree and extracts import paths |
| 145 | +func findPackagesInSourceCode(root string) map[string]bool { |
| 146 | + packages := make(map[string]bool) |
| 147 | + |
| 148 | + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { |
| 149 | + if err != nil { |
| 150 | + return err |
| 151 | + } |
| 152 | + |
| 153 | + // Skip vendor directory and hidden directories |
| 154 | + if info.IsDir() && (info.Name() == "vendor" || strings.HasPrefix(info.Name(), ".")) { |
| 155 | + return filepath.SkipDir |
| 156 | + } |
| 157 | + |
| 158 | + // Only process Go files |
| 159 | + if !info.IsDir() && strings.HasSuffix(path, ".go") { |
| 160 | + content, err := os.ReadFile(path) |
| 161 | + if err != nil { |
| 162 | + return err |
| 163 | + } |
| 164 | + |
| 165 | + // Simple parsing to find import statements |
| 166 | + // This is a basic implementation - a more robust solution would use go/parser |
| 167 | + lines := strings.Split(string(content), "\n") |
| 168 | + inImport := false |
| 169 | + for _, line := range lines { |
| 170 | + line = strings.TrimSpace(line) |
| 171 | + |
| 172 | + if strings.HasPrefix(line, "import (") { |
| 173 | + inImport = true |
| 174 | + continue |
| 175 | + } |
| 176 | + |
| 177 | + if inImport && strings.HasPrefix(line, ")") { |
| 178 | + inImport = false |
| 179 | + continue |
| 180 | + } |
| 181 | + |
| 182 | + if strings.HasPrefix(line, "import ") || inImport { |
| 183 | + // Extract package path from import line |
| 184 | + pkgPath := extractPackagePath(line) |
| 185 | + if pkgPath != "" { |
| 186 | + packages[pkgPath] = true |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + return nil |
| 192 | + }) |
| 193 | + |
| 194 | + if err != nil { |
| 195 | + log.Printf("Warning: error walking source directory: %v", err) |
| 196 | + } |
| 197 | + |
| 198 | + return packages |
| 199 | +} |
| 200 | + |
| 201 | +// extractPackagePath parses an import line to extract the package path |
| 202 | +func extractPackagePath(line string) string { |
| 203 | + line = strings.TrimSpace(line) |
| 204 | + |
| 205 | + // Skip if not an import line |
| 206 | + if !(strings.HasPrefix(line, "import ") || strings.Contains(line, "\"") || strings.Contains(line, "`")) { |
| 207 | + return "" |
| 208 | + } |
| 209 | + |
| 210 | + // Handle quoted imports |
| 211 | + if strings.Contains(line, "\"") { |
| 212 | + start := strings.Index(line, "\"") |
| 213 | + end := strings.LastIndex(line, "\"") |
| 214 | + if start >= 0 && end > start { |
| 215 | + return line[start+1 : end] |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + // Handle backtick imports |
| 220 | + if strings.Contains(line, "`") { |
| 221 | + start := strings.Index(line, "`") |
| 222 | + end := strings.LastIndex(line, "`") |
| 223 | + if start >= 0 && end > start { |
| 224 | + return line[start+1 : end] |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + return "" |
| 229 | +} |
| 230 | + |
| 231 | +// findSubmodulesForModule returns the submodules of a given module that are actually used in the source code |
| 232 | +func findSubmodulesForModule(modulePath string, usedPackages map[string]bool) []string { |
| 233 | + var submodules []string |
| 234 | + |
| 235 | + for pkg := range usedPackages { |
| 236 | + // Check if this package belongs to the module |
| 237 | + if strings.HasPrefix(pkg, modulePath) { |
| 238 | + // Extract the part after modulePath |
| 239 | + suffix := pkg[len(modulePath):] |
| 240 | + matched, _ := regexp.MatchString(`^/v[1-9][0-9]*(/|$)`, suffix) |
| 241 | + |
| 242 | + if !matched { |
| 243 | + submodules = append(submodules, pkg) |
| 244 | + } |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + return submodules |
| 249 | +} |
0 commit comments