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  }