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  }