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  }