github.com/octohelm/cuekit@v0.0.0-20240424021256-e7df8d743066/pkg/mod/modregistry/resolver.go (about) 1 package modregistry 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/octohelm/cuekit/pkg/mod/modfile" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "regexp" 11 "strings" 12 "sync" 13 14 "github.com/go-courier/logr" 15 "github.com/pkg/errors" 16 17 "github.com/octohelm/cuekit/internal/gomod" 18 "github.com/octohelm/cuekit/pkg/mod/module" 19 ) 20 21 type resolver struct { 22 CacheDir string 23 Root *module.Module 24 25 resolved sync.Map 26 } 27 28 func (r *resolver) ResolveLocal(ctx context.Context, path string) (module.SourceLoc, error) { 29 dir := filepath.Join(r.Root.SourceRoot(), path) 30 31 info, err := os.Stat(dir) 32 if err != nil || !info.IsDir() { 33 return module.SourceLoc{}, errors.Errorf("replace dir must exists dir %s", dir) 34 } 35 36 return module.SourceLocOfOSDir(dir), nil 37 } 38 39 func (r *resolver) Fetch(ctx context.Context, m module.Version) (module.SourceLoc, error) { 40 return r.Resolve(ctx, m.Path(), m.Version()) 41 } 42 43 var reVersionSuffixed = regexp.MustCompile("(.+)/v[\\d+]+$") 44 45 func trimVersionedSuffix(p string) string { 46 if reVersionSuffixed.MatchString(p) { 47 return reVersionSuffixed.FindAllStringSubmatch(p, 1)[0][1] 48 } 49 return p 50 } 51 52 func (r *resolver) ModuleVersions(ctx context.Context, mpath string) ([]string, error) { 53 path := module.BasePath(mpath) 54 55 repoRoot, err := gomod.RepoRootForImportPath(path) 56 if err != nil { 57 return nil, nil 58 } 59 60 if repoRoot != trimVersionedSuffix(path) { 61 return nil, nil 62 } 63 64 versions, _ := gomod.ListVersion(ctx, path) 65 if len(versions) > 0 { 66 validVersions := make([]string, 0, len(versions)) 67 68 for _, version := range versions { 69 v, err := module.NewVersion(path, version) 70 if err == nil { 71 r.Root.Overwrites.AddDep(v.Path(), &modfile.DepOverwrite{ 72 Path: path, 73 Version: v.Version(), 74 }) 75 validVersions = append(validVersions, v.Version()) 76 } else { 77 panic(err) 78 } 79 } 80 81 return validVersions, nil 82 83 } 84 return versions, nil 85 } 86 87 func (r *resolver) Resolve(ctx context.Context, mpath string, version string) (module.SourceLoc, error) { 88 do, _ := r.resolved.LoadOrStore(fmt.Sprintf("%s@%s", mpath, version), sync.OnceValue(func() any { 89 info, err := r.gomodDownload(ctx, mpath, version) 90 if err != nil { 91 return err 92 } 93 loc, err := r.convertToCueMod(ctx, mpath, info) 94 if err != nil { 95 return err 96 } 97 return loc 98 })) 99 switch x := do.(func() any)().(type) { 100 case error: 101 return module.SourceLoc{}, x 102 default: 103 return x.(module.SourceLoc), nil 104 } 105 } 106 107 func (r *resolver) gomodDownload(ctx context.Context, mpath string, version string) (*gomod.Module, error) { 108 pkg := module.BasePath(mpath) 109 info := gomod.Get(ctx, pkg, version, true) 110 if info == nil { 111 return nil, fmt.Errorf("can't found %s@%s", pkg, version) 112 } 113 if info.Error != "" { 114 return nil, errors.Wrap(errors.New(info.Error), "gomod download failed") 115 } 116 117 if info.Dir == "" { 118 logr.FromContext(ctx).Debug(fmt.Sprintf("get %s@%s", pkg, version)) 119 gomod.Download(ctx, info) 120 if info.Error != "" { 121 return nil, errors.New(info.Error) 122 } 123 } 124 return info, nil 125 } 126 127 func (r *resolver) convertToCueMod(ctx context.Context, mpath string, info *gomod.Module) (module.SourceLoc, error) { 128 dist := filepath.Join(r.CacheDir, fmt.Sprintf("%s@%s", module.BasePath(mpath), info.Version)) 129 130 if err := os.RemoveAll(dist); err != nil { 131 return module.SourceLoc{}, errors.Wrapf(err, "clean dest failed: %s", dist) 132 } 133 134 if err := fs.WalkDir(module.OSDirFS(info.Dir), ".", func(path string, d fs.DirEntry, err error) error { 135 if d.IsDir() { 136 if strings.Contains(path, "cue.mod") { 137 return fs.SkipDir 138 } 139 return nil 140 } 141 142 // only cp cue files 143 if !strings.HasSuffix(path, ".cue") { 144 return nil 145 } 146 147 return copyFile(filepath.Join(info.Dir, path), filepath.Join(dist, path)) 148 }); err != nil { 149 return module.SourceLoc{}, err 150 } 151 152 mod := &module.Module{ 153 SourceLoc: module.SourceLocOfOSDir(info.Dir), 154 } 155 156 // could empty 157 _ = mod.Load(false) 158 159 mod.Module = mpath 160 mod.Overwrites.Path = info.Path 161 mod.Overwrites.Version = info.Version 162 163 // switch to dist 164 mod.SourceLoc = module.SourceLocOfOSDir(dist) 165 if err := mod.Save(); err != nil { 166 return module.SourceLoc{}, err 167 } 168 169 return mod.SourceLoc, nil 170 }