github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modload/vendor.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modload 6 7 import ( 8 "errors" 9 "fmt" 10 "io/fs" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 16 "github.com/octohelm/cuemod/internal/cmd/go/internals/base" 17 "github.com/octohelm/cuemod/internal/cmd/go/internals/gover" 18 19 "golang.org/x/mod/modfile" 20 "golang.org/x/mod/module" 21 "golang.org/x/mod/semver" 22 ) 23 24 var ( 25 vendorOnce sync.Once 26 vendorList []module.Version // modules that contribute packages to the build, in order of appearance 27 vendorReplaced []module.Version // all replaced modules; may or may not also contribute packages 28 vendorVersion map[string]string // module path → selected version (if known) 29 vendorPkgModule map[string]module.Version // package → containing module 30 vendorMeta map[module.Version]vendorMetadata 31 ) 32 33 type vendorMetadata struct { 34 Explicit bool 35 Replacement module.Version 36 GoVersion string 37 } 38 39 // readVendorList reads the list of vendored modules from vendor/modules.txt. 40 func readVendorList(vendorDir string) { 41 vendorOnce.Do(func() { 42 vendorList = nil 43 vendorPkgModule = make(map[string]module.Version) 44 vendorVersion = make(map[string]string) 45 vendorMeta = make(map[module.Version]vendorMetadata) 46 vendorFile := filepath.Join(vendorDir, "modules.txt") 47 data, err := os.ReadFile(vendorFile) 48 if err != nil { 49 if !errors.Is(err, fs.ErrNotExist) { 50 base.Fatalf("go: %s", err) 51 } 52 return 53 } 54 55 var mod module.Version 56 for _, line := range strings.Split(string(data), "\n") { 57 if strings.HasPrefix(line, "# ") { 58 f := strings.Fields(line) 59 60 if len(f) < 3 { 61 continue 62 } 63 if semver.IsValid(f[2]) { 64 // A module, but we don't yet know whether it is in the build list or 65 // only included to indicate a replacement. 66 mod = module.Version{Path: f[1], Version: f[2]} 67 f = f[3:] 68 } else if f[2] == "=>" { 69 // A wildcard replacement found in the main module's go.mod file. 70 mod = module.Version{Path: f[1]} 71 f = f[2:] 72 } else { 73 // Not a version or a wildcard replacement. 74 // We don't know how to interpret this module line, so ignore it. 75 mod = module.Version{} 76 continue 77 } 78 79 if len(f) >= 2 && f[0] == "=>" { 80 meta := vendorMeta[mod] 81 if len(f) == 2 { 82 // File replacement. 83 meta.Replacement = module.Version{Path: f[1]} 84 vendorReplaced = append(vendorReplaced, mod) 85 } else if len(f) == 3 && semver.IsValid(f[2]) { 86 // Path and version replacement. 87 meta.Replacement = module.Version{Path: f[1], Version: f[2]} 88 vendorReplaced = append(vendorReplaced, mod) 89 } else { 90 // We don't understand this replacement. Ignore it. 91 } 92 vendorMeta[mod] = meta 93 } 94 continue 95 } 96 97 // Not a module line. Must be a package within a module or a metadata 98 // directive, either of which requires a preceding module line. 99 if mod.Path == "" { 100 continue 101 } 102 103 if annotations, ok := strings.CutPrefix(line, "## "); ok { 104 // Metadata. Take the union of annotations across multiple lines, if present. 105 meta := vendorMeta[mod] 106 for _, entry := range strings.Split(annotations, ";") { 107 entry = strings.TrimSpace(entry) 108 if entry == "explicit" { 109 meta.Explicit = true 110 } 111 if goVersion, ok := strings.CutPrefix(entry, "go "); ok { 112 meta.GoVersion = goVersion 113 rawGoVersion.Store(mod, meta.GoVersion) 114 if gover.Compare(goVersion, gover.Local()) > 0 { 115 base.Fatal(&gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion}) 116 } 117 } 118 // All other tokens are reserved for future use. 119 } 120 vendorMeta[mod] = meta 121 continue 122 } 123 124 if f := strings.Fields(line); len(f) == 1 && module.CheckImportPath(f[0]) == nil { 125 // A package within the current module. 126 vendorPkgModule[f[0]] = mod 127 128 // Since this module provides a package for the build, we know that it 129 // is in the build list and is the selected version of its path. 130 // If this information is new, record it. 131 if v, ok := vendorVersion[mod.Path]; !ok || gover.ModCompare(mod.Path, v, mod.Version) < 0 { 132 vendorList = append(vendorList, mod) 133 vendorVersion[mod.Path] = mod.Version 134 } 135 } 136 } 137 }) 138 } 139 140 // checkVendorConsistency verifies that the vendor/modules.txt file matches (if 141 // go 1.14) or at least does not contradict (go 1.13 or earlier) the 142 // requirements and replacements listed in the main module's go.mod file. 143 func checkVendorConsistency(indexes []*modFileIndex, modFiles []*modfile.File, modRoots []string) { 144 // readVendorList only needs the main module to get the directory 145 // the vendor directory is in. 146 readVendorList(VendorDir()) 147 148 if len(modFiles) < 1 { 149 // We should never get here if there are zero modfiles. Either 150 // we're in single module mode and there's a single module, or 151 // we're in workspace mode, and we fail earlier reporting that 152 // "no modules were found in the current workspace". 153 panic("checkVendorConsistency called with zero modfiles") 154 } 155 156 pre114 := false 157 if !inWorkspaceMode() { // workspace mode was added after Go 1.14 158 if len(indexes) != 1 { 159 panic(fmt.Errorf("not in workspace mode but number of indexes is %v, not 1", len(indexes))) 160 } 161 index := indexes[0] 162 if gover.Compare(index.goVersion, "1.14") < 0 { 163 // Go versions before 1.14 did not include enough information in 164 // vendor/modules.txt to check for consistency. 165 // If we know that we're on an earlier version, relax the consistency check. 166 pre114 = true 167 } 168 } 169 170 vendErrors := new(strings.Builder) 171 vendErrorf := func(mod module.Version, format string, args ...any) { 172 detail := fmt.Sprintf(format, args...) 173 if mod.Version == "" { 174 fmt.Fprintf(vendErrors, "\n\t%s: %s", mod.Path, detail) 175 } else { 176 fmt.Fprintf(vendErrors, "\n\t%s@%s: %s", mod.Path, mod.Version, detail) 177 } 178 } 179 180 // Iterate over the Require directives in their original (not indexed) order 181 // so that the errors match the original file. 182 for _, modFile := range modFiles { 183 for _, r := range modFile.Require { 184 if !vendorMeta[r.Mod].Explicit { 185 if pre114 { 186 // Before 1.14, modules.txt did not indicate whether modules were listed 187 // explicitly in the main module's go.mod file. 188 // However, we can at least detect a version mismatch if packages were 189 // vendored from a non-matching version. 190 if vv, ok := vendorVersion[r.Mod.Path]; ok && vv != r.Mod.Version { 191 vendErrorf(r.Mod, fmt.Sprintf("is explicitly required in go.mod, but vendor/modules.txt indicates %s@%s", r.Mod.Path, vv)) 192 } 193 } else { 194 vendErrorf(r.Mod, "is explicitly required in go.mod, but not marked as explicit in vendor/modules.txt") 195 } 196 } 197 } 198 } 199 200 describe := func(m module.Version) string { 201 if m.Version == "" { 202 return m.Path 203 } 204 return m.Path + "@" + m.Version 205 } 206 207 // We need to verify *all* replacements that occur in modfile: even if they 208 // don't directly apply to any module in the vendor list, the replacement 209 // go.mod file can affect the selected versions of other (transitive) 210 // dependencies 211 seenrep := make(map[module.Version]bool) 212 checkReplace := func(replaces []*modfile.Replace) { 213 for _, r := range replaces { 214 if seenrep[r.Old] { 215 continue // Don't print the same error more than once 216 } 217 seenrep[r.Old] = true 218 rNew, modRoot, replacementSource := replacementFrom(r.Old) 219 rNewCanonical := canonicalizeReplacePath(rNew, modRoot) 220 vr := vendorMeta[r.Old].Replacement 221 if vr == (module.Version{}) { 222 if rNewCanonical == (module.Version{}) { 223 // r.Old is not actually replaced. It might be a main module. 224 // Don't return an error. 225 } else if pre114 && (r.Old.Version == "" || vendorVersion[r.Old.Path] != r.Old.Version) { 226 // Before 1.14, modules.txt omitted wildcard replacements and 227 // replacements for modules that did not have any packages to vendor. 228 } else { 229 vendErrorf(r.Old, "is replaced in %s, but not marked as replaced in vendor/modules.txt", base.ShortPath(replacementSource)) 230 } 231 } else if vr != rNewCanonical { 232 vendErrorf(r.Old, "is replaced by %s in %s, but marked as replaced by %s in vendor/modules.txt", describe(rNew), base.ShortPath(replacementSource), describe(vr)) 233 } 234 } 235 } 236 for _, modFile := range modFiles { 237 checkReplace(modFile.Replace) 238 } 239 if MainModules.workFile != nil { 240 checkReplace(MainModules.workFile.Replace) 241 } 242 243 for _, mod := range vendorList { 244 meta := vendorMeta[mod] 245 if meta.Explicit { 246 // in workspace mode, check that it's required by at least one of the main modules 247 var foundRequire bool 248 for _, index := range indexes { 249 if _, inGoMod := index.require[mod]; inGoMod { 250 foundRequire = true 251 } 252 } 253 if !foundRequire { 254 article := "" 255 if inWorkspaceMode() { 256 article = "a " 257 } 258 vendErrorf(mod, "is marked as explicit in vendor/modules.txt, but not explicitly required in %vgo.mod", article) 259 } 260 261 } 262 } 263 264 for _, mod := range vendorReplaced { 265 r := Replacement(mod) 266 replacementSource := "go.mod" 267 if inWorkspaceMode() { 268 replacementSource = "the workspace" 269 } 270 if r == (module.Version{}) { 271 vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in %s", replacementSource) 272 continue 273 } 274 // If both replacements exist, we've already reported that they're different above. 275 } 276 277 if vendErrors.Len() > 0 { 278 subcmd := "mod" 279 if inWorkspaceMode() { 280 subcmd = "work" 281 } 282 base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo %s vendor", filepath.Dir(VendorDir()), vendErrors, subcmd) 283 } 284 }