github.com/octohelm/cuemod@v0.9.4/pkg/cuemod/context.go (about) 1 package cuemod 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "strings" 8 9 cueerrors "cuelang.org/go/cue/errors" 10 cueload "cuelang.org/go/cue/load" 11 12 "cuelang.org/go/cue/build" 13 "github.com/octohelm/cuemod/pkg/cuemod/modfile" 14 "github.com/pkg/errors" 15 ) 16 17 type cuemodKey struct{} 18 19 func FromContext(c context.Context) *Context { 20 return c.Value(cuemodKey{}).(*Context) 21 } 22 23 func InjectContext(c context.Context, cc *Context) context.Context { 24 return context.WithValue(c, cuemodKey{}, cc) 25 } 26 27 func ContextFor(root string) *Context { 28 vm := &Context{} 29 30 if !filepath.IsAbs(root) { 31 cwd, _ := os.Getwd() 32 root = filepath.Join(cwd, root) 33 } 34 35 rootMod := &Mod{} 36 rootMod.Dir = root 37 38 vm.resolver = newModResolver() 39 40 ctx := context.Background() 41 42 if _, err := rootMod.LoadInfo(ctx); err != nil { 43 panic(err) 44 } 45 46 vm.Mod = rootMod 47 vm.resolver.Collect(ctx, rootMod) 48 49 return vm 50 } 51 52 type Context struct { 53 Mod *Mod 54 resolver *modResolver 55 } 56 57 func (r *Context) Cleanup() error { 58 if err := os.RemoveAll(filepath.Join(r.CueModRoot(), "gen")); err != nil { 59 return err 60 } 61 if err := os.RemoveAll(filepath.Join(r.CueModRoot(), "pkg")); err != nil { 62 return err 63 } 64 return nil 65 } 66 67 func (r *Context) CompletePath(p string) string { 68 if filepath.IsAbs(p) { 69 if filepath.Ext(p) == ".cue" { 70 return p 71 } 72 if !strings.HasPrefix(p, r.Mod.Dir+"/") { 73 return p 74 } 75 p, _ = filepath.Rel(r.Mod.Dir, p) 76 if strings.HasPrefix(p, r.Mod.Repo+"/") || p == r.Mod.Repo { 77 return p 78 } 79 return filepath.Join(r.Mod.Repo, p) 80 } 81 if filepath.Ext(p) == ".cue" { 82 return filepath.Join(r.Mod.Dir, p) 83 } 84 if strings.HasPrefix(p, r.Mod.Repo+"/") || p == r.Mod.Repo { 85 return p 86 } 87 return filepath.Join(r.Mod.Repo, p) 88 } 89 90 func (r *Context) ListCue(fromPath string) ([]string, error) { 91 files := make([]string, 0) 92 93 walkSubDir := strings.HasSuffix(fromPath, "/...") 94 95 if walkSubDir { 96 fromPath = fromPath[0 : len(fromPath)-4] 97 } 98 99 start := filepath.Join(r.Mod.Dir, fromPath) 100 101 err := filepath.Walk(start, func(path string, info os.FileInfo, err error) error { 102 if path == start { 103 return nil 104 } 105 106 // skip cue.mod 107 if isSubDirFor(path, r.CueModRoot()) { 108 return filepath.SkipDir 109 } 110 111 if info.IsDir() { 112 // skip sub dir which is cuemod root 113 if _, err := os.Stat(filepath.Join(path, modfile.ModFilename)); err == nil { 114 return filepath.SkipDir 115 } 116 117 if walkSubDir { 118 return nil 119 } 120 return filepath.SkipDir 121 } 122 123 if filepath.Ext(path) == ".cue" { 124 files = append(files, path) 125 } 126 127 return nil 128 }) 129 130 if err != nil { 131 return nil, err 132 } 133 134 return files, nil 135 } 136 137 func (r *Context) Get(ctx context.Context, i string) error { 138 if i[0] == '.' { 139 return r.autoImport(ctx, i) 140 } 141 // get import path should be always upgrade 142 return r.download(WithOpts(ctx, OptUpgrade(true)), i) 143 } 144 145 func (r *Context) CueModRoot() string { 146 return filepath.Join(r.Mod.Dir, "cue.mod") 147 } 148 149 func (r *Context) setRequireFromImportPath(ctx context.Context, p *Path, indirect bool) error { 150 modVersion := p.ModVersion 151 152 if mv := r.resolver.RepoVersion(p.Repo); mv.Version != "" { 153 modVersion = mv 154 } 155 156 if err := p.SymlinkOrImport(ctx, r.Mod.Dir); err != nil { 157 return err 158 } 159 160 // only root mod could be in require 161 if p.Root { 162 r.Mod.SetRequire(p.ImportPathRoot(), modVersion, indirect) 163 } 164 165 return r.syncFiles() 166 } 167 168 var gomodStub = []byte(` 169 module github.com/octohelm/cuemod/stub 170 171 go 1.21 172 `) 173 174 func (r *Context) syncFiles() error { 175 if err := writeFile(filepath.Join(r.Mod.Dir, modfile.ModFilename), r.Mod.ModFile.Bytes()); err != nil { 176 return err 177 } 178 if err := writeFile(filepath.Join(r.Mod.Dir, "cue.mod/gen/go.mod"), gomodStub); err != nil { 179 return err 180 } 181 if err := writeFile(filepath.Join(r.Mod.Dir, "cue.mod/pkg/go.mod"), gomodStub); err != nil { 182 return err 183 } 184 if err := writeFile(filepath.Join(r.Mod.Dir, ModSumFilename), r.resolver.ModuleSum()); err != nil { 185 return nil 186 } 187 return writeFile(filepath.Join(r.CueModRoot(), ".gitignore"), []byte(strings.TrimSpace(` 188 gen/ 189 pkg/ 190 module.sum 191 `))) 192 } 193 194 func (r *Context) BuildConfig(ctx context.Context, options ...OptionFunc) *cueload.Config { 195 return BuildConfig(append([]OptionFunc{ 196 OptRoot(r.Mod.Dir), 197 OptImportFunc(func(importPath string, importedAt string) (resolvedDir string, err error) { 198 return r.Resolve(ctx, importPath, importedAt) 199 }), 200 }, options...)...) 201 } 202 203 func (r *Context) Build(ctx context.Context, files []string, options ...OptionFunc) *build.Instance { 204 return BuildInstances(r.BuildConfig(ctx, options...), files)[0] 205 } 206 207 func (r *Context) Resolve(ctx context.Context, importPath string, importedAt string) (string, error) { 208 resolvedImportPath, err := r.resolver.ResolveImportPath(ctx, r.Mod, importPath, "") 209 if err != nil { 210 return "", errors.Wrapf(err, "resolve import `%s` failed", importPath) 211 } 212 213 indirect := isSubDirFor(importedAt, r.CueModRoot()) && !isSubDirFor(importedAt, filepath.Join(r.CueModRoot(), "usr", r.Mod.Module)) 214 215 if err := r.setRequireFromImportPath(ctx, resolvedImportPath, indirect); err != nil { 216 return "", err 217 } 218 219 dir := resolvedImportPath.ResolvedImportPath() 220 return dir, nil 221 } 222 223 func (r *Context) autoImport(ctx context.Context, fromPath string) error { 224 cueFiles, err := r.ListCue(fromPath) 225 if err != nil { 226 return err 227 } 228 229 for i := range cueFiles { 230 if inst := r.Build(ctx, []string{cueFiles[i]}); inst.Err != nil { 231 cueerrors.Print(os.Stdout, err, nil) 232 return inst.Err 233 } 234 } 235 236 return err 237 } 238 239 func (r *Context) download(ctx context.Context, importPath string) error { 240 importPathAndVersion := strings.Split(importPath, "@") 241 242 importPath, version := importPathAndVersion[0], "" 243 if len(importPathAndVersion) > 1 { 244 version = importPathAndVersion[1] 245 } 246 247 if lang := OptsFromContext(ctx).Import; lang != "" { 248 p := modfile.VersionedPathIdentity{Path: importPath} 249 250 if v, ok := r.Mod.Replace[p]; ok { 251 v.Import = lang 252 r.Mod.Replace[p] = v 253 } else { 254 r.Mod.Replace[p] = modfile.ReplaceTarget{ 255 VersionedPathIdentity: p, 256 Import: lang, 257 } 258 } 259 } 260 261 p, err := r.resolver.ResolveImportPath(ctx, r.Mod, importPath, version) 262 if err != nil { 263 return err 264 } 265 266 return r.setRequireFromImportPath(ctx, p, true) 267 }