github.com/octohelm/cuemod@v0.9.4/pkg/cuemod/mod_resolver.go (about) 1 package cuemod 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "path/filepath" 9 "sort" 10 11 "github.com/go-courier/logr" 12 "github.com/octohelm/cuemod/pkg/cuemod/modfile" 13 "github.com/octohelm/cuemod/pkg/cuemod/stdlib" 14 "github.com/octohelm/cuemod/pkg/modutil" 15 "github.com/pkg/errors" 16 "golang.org/x/mod/module" 17 "golang.org/x/mod/semver" 18 ) 19 20 func newModResolver() *modResolver { 21 return &modResolver{ 22 replace: map[modfile.VersionedPathIdentity]replaceTargetWithMod{}, 23 mods: map[module.Version]*Mod{}, 24 repoVersions: map[string]modfile.ModVersion{}, 25 } 26 } 27 28 type modResolver struct { 29 root *Mod 30 31 replace map[modfile.VersionedPathIdentity]replaceTargetWithMod 32 // { [<module>@<version>]: *Mod } 33 mods map[module.Version]*Mod 34 // { [<repo>]:latest-version } 35 repoVersions map[string]modfile.ModVersion 36 } 37 38 const ModSumFilename = "cue.mod/module.sum" 39 40 func (r *modResolver) ModuleSum() []byte { 41 buf := bytes.NewBuffer(nil) 42 43 moduleVersions := make([]module.Version, 0) 44 45 for moduleVersion, m := range r.mods { 46 if m.Root { 47 moduleVersions = append(moduleVersions, moduleVersion) 48 } 49 } 50 51 sort.Slice(moduleVersions, func(i, j int) bool { 52 return moduleVersions[i].Path < moduleVersions[j].Path 53 }) 54 55 for _, n := range moduleVersions { 56 m := r.mods[n] 57 58 if m.Version != "" { 59 _, _ = fmt.Fprintf(buf, "%s %s %s\n", m.Module, m.Version, m.Sum) 60 } 61 } 62 63 return buf.Bytes() 64 } 65 66 type replaceTargetWithMod struct { 67 modfile.ReplaceTarget 68 mod *Mod 69 } 70 71 func (r *modResolver) ResolveImportPath(ctx context.Context, root *Mod, importPath string, version string) (p *Path, e error) { 72 // self import '<root.module>/dir/to/sub' 73 if isSubDirFor(importPath, root.Module) { 74 return PathFor(root, importPath), nil 75 } 76 77 if matched, replaceTarget, ok := r.LookupReplace(importPath); ok { 78 // xxx => ../xxx 79 // only works for root 80 if replaceTarget.IsLocalReplace() { 81 mod := &Mod{Dir: filepath.Join(root.Dir, replaceTarget.Path)} 82 83 mod.Version = "v0.0.0" 84 if _, err := mod.LoadInfo(ctx); err != nil { 85 return nil, err 86 } 87 88 if mod.Module == "" { 89 mod.Module = filepath.Join(root.Module, replaceTarget.Path) 90 } 91 92 r.Collect(ctx, mod) 93 return PathFor(mod, importPath), nil 94 } 95 96 // a[@latest] => b@latest 97 // must strict version 98 replacedImportPath := replaceImportPath(replaceTarget.Path, matched.Path, importPath) 99 100 // when version never upgrade for upgrade 101 if replaceTarget.Exactly() { 102 ctx = WithOpts(ctx, OptUpgrade(false)) 103 } 104 105 mod, err := r.Get(ctx, replacedImportPath, replaceTarget.ModVersion) 106 if err != nil { 107 return nil, err 108 } 109 110 return PathFor(mod, replacedImportPath).WithReplace(matched.Path, replaceTarget), nil 111 } 112 113 mod, err := r.Get(ctx, importPath, modfile.ModVersion{Version: version}) 114 if err != nil { 115 return nil, err 116 } 117 118 return PathFor(mod, importPath), nil 119 } 120 121 func (r *modResolver) LookupReplace(importPath string) (matched modfile.VersionedPathIdentity, replace modfile.ReplaceTarget, exists bool) { 122 for _, path := range paths(importPath) { 123 p := modfile.VersionedPathIdentity{Path: path} 124 if rp, ok := r.replace[p]; ok { 125 return p, rp.ReplaceTarget, true 126 } 127 } 128 return modfile.VersionedPathIdentity{}, modfile.ReplaceTarget{}, false 129 } 130 131 func (r *modResolver) Collect(ctx context.Context, mod *Mod) { 132 if r.root == nil { 133 r.root = mod 134 } 135 136 moduleVersion := module.Version{Path: mod.Module, Version: mod.Version} 137 138 if mod.Repo == "" { 139 mod.Repo = mod.Module 140 } 141 142 r.mods[moduleVersion] = mod 143 144 // cached moduel@tag too 145 if mod.VcsRef != "" { 146 r.mods[module.Version{Path: mod.Module, Version: mod.VcsRef}] = mod 147 } 148 149 r.SetRepoVersion(mod.Repo, mod.ModVersion) 150 151 for repo, req := range mod.Require { 152 r.SetRepoVersion(repo, req.ModVersion) 153 } 154 155 for k, replaceTarget := range mod.Replace { 156 // only work for root mod 157 if replaceTarget.IsLocalReplace() && mod != r.root { 158 return 159 } 160 161 // never modify replaced 162 if _, ok := r.replace[k]; !ok { 163 r.replace[k] = replaceTargetWithMod{mod: mod, ReplaceTarget: replaceTarget} 164 } 165 } 166 } 167 168 func (r *modResolver) SetRepoVersion(module string, version modfile.ModVersion) { 169 if mv, ok := r.repoVersions[module]; ok { 170 if mv.Version == "" { 171 mv.Version = version.Version 172 } else if versionGreaterThan(version.Version, mv.Version) { 173 mv.Version = version.Version 174 } 175 176 // sync tag version 177 if version.VcsRef != "" { 178 mv.VcsRef = version.VcsRef 179 } 180 181 r.repoVersions[module] = mv 182 } else { 183 r.repoVersions[module] = version 184 } 185 } 186 187 func (r *modResolver) RepoVersion(repo string) modfile.ModVersion { 188 if v, ok := r.repoVersions[repo]; ok { 189 return v 190 } 191 return modfile.ModVersion{} 192 } 193 194 func (r *modResolver) Get(ctx context.Context, pkgImportPath string, modVersion modfile.ModVersion) (*Mod, error) { 195 repo, err := r.repoRoot(ctx, pkgImportPath) 196 if err != nil { 197 return nil, err 198 } 199 return r.get(ctx, repo, modVersion, pkgImportPath) 200 } 201 202 func (r *modResolver) get(ctx context.Context, repo string, requestedVersion modfile.ModVersion, importPath string) (*Mod, error) { 203 // fix /v2 204 if p, m, ok := module.SplitPathVersion(repo); ok { 205 if requestedVersion.VcsRef == "" { 206 requestedVersion.VcsRef = m 207 } 208 } else { 209 repo = p 210 } 211 212 if requestedVersion.VcsRef == "" && requestedVersion.Version != "" && !semver.IsValid(requestedVersion.Version) { 213 requestedVersion.VcsRef = requestedVersion.Version 214 requestedVersion.Version = "" 215 } 216 217 if requestedVersion.VcsRef == "" { 218 requestedVersion.VcsRef = "latest" 219 } 220 221 version := requestedVersion.Version 222 223 forUpgrade := OptsFromContext(ctx).Upgrade 224 225 if forUpgrade || version == "" { 226 if requestedVersion.VcsRef != "" { 227 version = requestedVersion.VcsRef 228 } 229 } 230 231 if !forUpgrade { 232 if mv, ok := r.repoVersions[repo]; ok { 233 if mv.Version != "" { 234 version = mv.Version 235 } 236 } 237 } 238 239 var root *Mod 240 241 if mod, ok := r.mods[module.Version{Path: repo, Version: version}]; ok && mod.Resolved() { 242 // resolved 243 root = mod 244 } else { 245 m, err := r.resolveMod(ctx, repo, version) 246 if err != nil { 247 return nil, err 248 } 249 250 if requestedVersion.VcsRef != "latest" { 251 m.VcsRef = requestedVersion.VcsRef 252 } 253 254 root = m 255 // fetched repo always root 256 root.Root = true 257 258 if _, err := root.LoadInfo(ctx); err != nil { 259 return nil, err 260 } 261 262 r.Collect(ctx, root) 263 } 264 265 if root != nil { 266 // sub dir may a mod. 267 importPaths := paths(importPath) 268 269 for _, m := range importPaths { 270 if m == root.Module { 271 break 272 } 273 274 if mod, ok := r.mods[module.Version{Path: m, Version: version}]; ok && mod.Resolved() { 275 return mod, nil 276 } else { 277 subPath, _ := subDir(root.Module, m) 278 279 sub := Mod{} 280 sub.Sum = root.Sum 281 sub.Repo = root.Repo 282 sub.SubPath = subPath 283 284 sub.Module = m 285 sub.ModVersion = root.ModVersion 286 287 sub.Dir = filepath.Join(root.Dir, subPath) 288 289 ok, err := sub.LoadInfo(ctx) 290 if err != nil { 291 // if subPath contains go.mod, it will be empty 292 if os.IsNotExist(errors.Unwrap(err)) { 293 return r.get(ctx, sub.Module, requestedVersion, importPath) 294 } 295 return nil, err 296 } 297 298 if ok { 299 r.Collect(ctx, &sub) 300 return &sub, nil 301 } 302 } 303 } 304 } 305 306 return root, nil 307 } 308 309 func (r *modResolver) repoRoot(ctx context.Context, importPath string) (string, error) { 310 importPaths := paths(importPath) 311 312 for _, p := range importPaths { 313 if _, ok := r.repoVersions[p]; ok { 314 return p, nil 315 } 316 } 317 318 logr.FromContext(ctx).Debug(fmt.Sprintf("resolve %s", importPath)) 319 320 var root string 321 322 if stdrepo, ok := stdlib.RepoRootForImportPath(importPath); ok { 323 root = stdrepo 324 } else { 325 re, err := modutil.RepoRootForImportPath(importPath) 326 if err != nil { 327 return "", errors.Wrapf(err, "resolve `%s` failed", importPath) 328 } 329 root = re 330 } 331 332 r.SetRepoVersion(root, modfile.ModVersion{}) 333 334 return root, nil 335 } 336 337 func (r *modResolver) resolveMod(ctx context.Context, pkg string, version string) (*Mod, error) { 338 cuemModRoot := "" 339 if r.root != nil { 340 cuemModRoot = r.root.Dir 341 } 342 343 stdm, err := stdlib.Mount(ctx, pkg, cuemModRoot) 344 if err != nil { 345 return nil, err 346 } 347 348 if stdm != nil { 349 return stdm, nil 350 } 351 352 info := modutil.Get(ctx, pkg, version, OptsFromContext(ctx).Verbose) 353 if info == nil { 354 return nil, fmt.Errorf("can't found %s@%s", pkg, version) 355 } 356 357 if info.Error != "" { 358 return nil, errors.New(info.Error) 359 } 360 361 if info.Dir == "" { 362 logr.FromContext(ctx).Debug(fmt.Sprintf("get %s@%s", pkg, version)) 363 modutil.Download(ctx, info) 364 if info.Error != "" { 365 return nil, errors.New(info.Error) 366 } 367 } 368 369 mod := &Mod{} 370 371 mod.Module = info.Path 372 mod.Version = info.Version 373 mod.Repo = info.Path 374 mod.Sum = info.Sum 375 mod.Dir = info.Dir 376 377 return mod, nil 378 }