// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package modload import ( "context" "errors" "fmt" "os" "path/filepath" "sort" "sync" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/lockedfile" "cmd/go/internal/modfetch" "cmd/go/internal/mvs" "cmd/go/internal/par" "golang.org/x/mod/modfile" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) // mvsReqs implements mvs.Reqs for module semantic versions, // with any exclusions or replacements applied internally. type mvsReqs struct { buildList []module.Version cache par.Cache versions sync.Map } // Reqs returns the current module requirement graph. // Future calls to SetBuildList do not affect the operation // of the returned Reqs. func Reqs() mvs.Reqs { r := &mvsReqs{ buildList: buildList, } return r } func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { type cached struct { list []module.Version err error } c := r.cache.Do(mod, func() interface{} { list, err := r.required(mod) if err != nil { return cached{nil, err} } for i, mv := range list { if index != nil { for index.exclude[mv] { mv1, err := r.next(mv) if err != nil { return cached{nil, err} } if mv1.Version == "none" { return cached{nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)} } mv = mv1 } } list[i] = mv } return cached{list, nil} }).(cached) return c.list, c.err } func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version { list := make([]module.Version, 0, len(f.Require)) for _, r := range f.Require { list = append(list, r.Mod) } return list } // required returns a unique copy of the requirements of mod. func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) { if mod == Target { if modFile != nil && modFile.Go != nil { r.versions.LoadOrStore(mod, modFile.Go.Version) } return append([]module.Version(nil), r.buildList[1:]...), nil } if cfg.BuildMod == "vendor" { // For every module other than the target, // return the full list of modules from modules.txt. readVendorList() return append([]module.Version(nil), vendorList...), nil } origPath := mod.Path if repl := Replacement(mod); repl.Path != "" { if repl.Version == "" { // TODO: need to slip the new version into the tags list etc. dir := repl.Path if !filepath.IsAbs(dir) { dir = filepath.Join(ModRoot(), dir) } gomod := filepath.Join(dir, "go.mod") data, err := lockedfile.Read(gomod) if err != nil { return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err) } f, err := modfile.ParseLax(gomod, data, nil) if err != nil { return nil, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err) } if f.Go != nil { r.versions.LoadOrStore(mod, f.Go.Version) } return r.modFileToList(f), nil } mod = repl } if mod.Version == "none" { return nil, nil } if !semver.IsValid(mod.Version) { // Disallow the broader queries supported by fetch.Lookup. base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", mod.Path, mod.Version) } data, err := modfetch.GoMod(mod.Path, mod.Version) if err != nil { return nil, err } f, err := modfile.ParseLax("go.mod", data, nil) if err != nil { return nil, module.VersionError(mod, fmt.Errorf("parsing go.mod: %v", err)) } if f.Module == nil { return nil, module.VersionError(mod, errors.New("parsing go.mod: missing module line")) } if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path { return nil, module.VersionError(mod, fmt.Errorf(`parsing go.mod: module declares its path as: %s but was required as: %s`, mpath, origPath)) } if f.Go != nil { r.versions.LoadOrStore(mod, f.Go.Version) } return r.modFileToList(f), nil } // Max returns the maximum of v1 and v2 according to semver.Compare. // // As a special case, the version "" is considered higher than all other // versions. The main module (also known as the target) has no version and must // be chosen over other versions of the same module in the module dependency // graph. func (*mvsReqs) Max(v1, v2 string) string { if v1 != "" && semver.Compare(v1, v2) == -1 { return v2 } return v1 } // Upgrade is a no-op, here to implement mvs.Reqs. // The upgrade logic for go get -u is in ../modget/get.go. func (*mvsReqs) Upgrade(m module.Version) (module.Version, error) { return m, nil } func versions(path string) ([]string, error) { // Note: modfetch.Lookup and repo.Versions are cached, // so there's no need for us to add extra caching here. var versions []string err := modfetch.TryProxies(func(proxy string) error { repo, err := modfetch.Lookup(proxy, path) if err == nil { versions, err = repo.Versions("") } return err }) return versions, err } // Previous returns the tagged version of m.Path immediately prior to // m.Version, or version "none" if no prior version is tagged. func (*mvsReqs) Previous(m module.Version) (module.Version, error) { list, err := versions(m.Path) if err != nil { return module.Version{}, err } i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 }) if i > 0 { return module.Version{Path: m.Path, Version: list[i-1]}, nil } return module.Version{Path: m.Path, Version: "none"}, nil } // next returns the next version of m.Path after m.Version. // It is only used by the exclusion processing in the Required method, // not called directly by MVS. func (*mvsReqs) next(m module.Version) (module.Version, error) { list, err := versions(m.Path) if err != nil { return module.Version{}, err } i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 }) if i < len(list) { return module.Version{Path: m.Path, Version: list[i]}, nil } return module.Version{Path: m.Path, Version: "none"}, nil } // fetch downloads the given module (or its replacement) // and returns its location. // // The isLocal return value reports whether the replacement, // if any, is local to the filesystem. func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) { if mod == Target { return ModRoot(), true, nil } if r := Replacement(mod); r.Path != "" { if r.Version == "" { dir = r.Path if !filepath.IsAbs(dir) { dir = filepath.Join(ModRoot(), dir) } // Ensure that the replacement directory actually exists: // dirInModule does not report errors for missing modules, // so if we don't report the error now, later failures will be // very mysterious. if _, err := os.Stat(dir); err != nil { if os.IsNotExist(err) { // Semantically the module version itself “exists” — we just don't // have its source code. Remove the equivalence to os.ErrNotExist, // and make the message more concise while we're at it. err = fmt.Errorf("replacement directory %s does not exist", r.Path) } else { err = fmt.Errorf("replacement directory %s: %w", r.Path, err) } return dir, true, module.VersionError(mod, err) } return dir, true, nil } mod = r } dir, err = modfetch.Download(ctx, mod) return dir, false, err }