| // Copyright 2018 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 modfetch |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| pathpkg "path" |
| "sort" |
| "strings" |
| "time" |
| |
| "cmd/go/internal/cfg" |
| "cmd/go/internal/modfetch/bitbucket" |
| "cmd/go/internal/modfetch/codehost" |
| "cmd/go/internal/modfetch/github" |
| "cmd/go/internal/modfetch/googlesource" |
| "cmd/go/internal/module" |
| "cmd/go/internal/par" |
| "cmd/go/internal/semver" |
| ) |
| |
| const traceRepo = false // trace all repo actions, for debugging |
| |
| // A Repo represents a repository storing all versions of a single module. |
| // It must be safe for simultaneous use by multiple goroutines. |
| type Repo interface { |
| // ModulePath returns the module path. |
| ModulePath() string |
| |
| // Versions lists all known versions with the given prefix. |
| // Pseudo-versions are not included. |
| // Versions should be returned sorted in semver order |
| // (implementations can use SortVersions). |
| Versions(prefix string) (tags []string, err error) |
| |
| // Stat returns information about the revision rev. |
| // A revision can be any identifier known to the underlying service: |
| // commit hash, branch, tag, and so on. |
| Stat(rev string) (*RevInfo, error) |
| |
| // Latest returns the latest revision on the default branch, |
| // whatever that means in the underlying source code repository. |
| // It is only used when there are no tagged versions. |
| Latest() (*RevInfo, error) |
| |
| // GoMod returns the go.mod file for the given version. |
| GoMod(version string) (data []byte, err error) |
| |
| // Zip downloads a zip file for the given version |
| // to a new file in a given temporary directory. |
| // It returns the name of the new file. |
| // The caller should remove the file when finished with it. |
| Zip(version, tmpdir string) (tmpfile string, err error) |
| } |
| |
| // A Rev describes a single revision in a module repository. |
| type RevInfo struct { |
| Version string // version string |
| Name string // complete ID in underlying repository |
| Short string // shortened ID, for use in pseudo-version |
| Time time.Time // commit time |
| } |
| |
| var lookupCache par.Cache |
| |
| // Lookup returns the module with the given module path. |
| func Lookup(path string) (Repo, error) { |
| if traceRepo { |
| defer logCall("Lookup(%q)", path)() |
| } |
| |
| type cached struct { |
| r Repo |
| err error |
| } |
| c := lookupCache.Do(path, func() interface{} { |
| r, err := lookup(path) |
| if err == nil { |
| if traceRepo { |
| r = newLoggingRepo(r) |
| } |
| r = newCachingRepo(r) |
| } |
| return cached{r, err} |
| }).(cached) |
| |
| return c.r, c.err |
| } |
| |
| // lookup returns the module with the given module path. |
| func lookup(path string) (r Repo, err error) { |
| if cfg.BuildGetmode != "" { |
| return nil, fmt.Errorf("module lookup disabled by -getmode=%s", cfg.BuildGetmode) |
| } |
| if proxyURL != "" { |
| return lookupProxy(path) |
| } |
| if code, err := lookupCodeHost(path, false); err != errNotHosted { |
| if err != nil { |
| return nil, err |
| } |
| return newCodeRepo(code, path) |
| } |
| return lookupCustomDomain(path) |
| } |
| |
| func Import(path string, allowed func(module.Version) bool) (Repo, *RevInfo, error) { |
| if traceRepo { |
| defer logCall("Import(%q, ...)", path)() |
| } |
| try := func(path string) (Repo, *RevInfo, error) { |
| r, err := Lookup(path) |
| if err != nil { |
| return nil, nil, err |
| } |
| info, err := Query(path, "latest", allowed) |
| if err != nil { |
| return nil, nil, err |
| } |
| _, err = r.GoMod(info.Version) |
| if err != nil { |
| return nil, nil, err |
| } |
| return r, info, nil |
| } |
| |
| var firstErr error |
| for { |
| r, info, err := try(path) |
| if err == nil { |
| return r, info, nil |
| } |
| if firstErr == nil { |
| firstErr = err |
| } |
| p := pathpkg.Dir(path) |
| if p == "." { |
| break |
| } |
| path = p |
| } |
| return nil, nil, firstErr |
| } |
| |
| var errNotHosted = errors.New("not hosted") |
| |
| var isTest bool |
| |
| func lookupCodeHost(path string, customDomain bool) (codehost.Repo, error) { |
| switch { |
| case strings.HasPrefix(path, "github.com/"): |
| return github.Lookup(path) |
| case strings.HasPrefix(path, "bitbucket.org/"): |
| return bitbucket.Lookup(path) |
| case customDomain && strings.HasSuffix(path[:strings.Index(path, "/")+1], ".googlesource.com/") || |
| isTest && strings.HasPrefix(path, "go.googlesource.com/scratch"): |
| return googlesource.Lookup(path) |
| case strings.HasPrefix(path, "gopkg.in/"): |
| return gopkginLookup(path) |
| } |
| return nil, errNotHosted |
| } |
| |
| func SortVersions(list []string) { |
| sort.Slice(list, func(i, j int) bool { |
| cmp := semver.Compare(list[i], list[j]) |
| if cmp != 0 { |
| return cmp < 0 |
| } |
| return list[i] < list[j] |
| }) |
| } |
| |
| // A loggingRepo is a wrapper around an underlying Repo |
| // that prints a log message at the start and end of each call. |
| // It can be inserted when debugging. |
| type loggingRepo struct { |
| r Repo |
| } |
| |
| func newLoggingRepo(r Repo) *loggingRepo { |
| return &loggingRepo{r} |
| } |
| |
| // logCall prints a log message using format and args and then |
| // also returns a function that will print the same message again, |
| // along with the elapsed time. |
| // Typical usage is: |
| // |
| // defer logCall("hello %s", arg)() |
| // |
| // Note the final (). |
| func logCall(format string, args ...interface{}) func() { |
| start := time.Now() |
| fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...)) |
| return func() { |
| fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...)) |
| } |
| } |
| |
| func (l *loggingRepo) ModulePath() string { |
| return l.r.ModulePath() |
| } |
| |
| func (l *loggingRepo) Versions(prefix string) (tags []string, err error) { |
| defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)() |
| return l.r.Versions(prefix) |
| } |
| |
| func (l *loggingRepo) Stat(rev string) (*RevInfo, error) { |
| defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)() |
| return l.r.Stat(rev) |
| } |
| |
| func (l *loggingRepo) Latest() (*RevInfo, error) { |
| defer logCall("Repo[%s]: Latest()", l.r.ModulePath())() |
| return l.r.Latest() |
| } |
| |
| func (l *loggingRepo) GoMod(version string) ([]byte, error) { |
| defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)() |
| return l.r.GoMod(version) |
| } |
| |
| func (l *loggingRepo) Zip(version, tmpdir string) (string, error) { |
| defer logCall("Repo[%s]: Zip(%q, %q)", l.r.ModulePath(), version, tmpdir)() |
| return l.r.Zip(version, tmpdir) |
| } |