...

Source file src/pkg/cmd/go/internal/modfetch/repo.go

     1	// Copyright 2018 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package modfetch
     6	
     7	import (
     8		"errors"
     9		"fmt"
    10		"io"
    11		"os"
    12		"sort"
    13		"strconv"
    14		"time"
    15	
    16		"cmd/go/internal/cfg"
    17		"cmd/go/internal/get"
    18		"cmd/go/internal/modfetch/codehost"
    19		"cmd/go/internal/par"
    20		"cmd/go/internal/semver"
    21		"cmd/go/internal/str"
    22		web "cmd/go/internal/web"
    23	)
    24	
    25	const traceRepo = false // trace all repo actions, for debugging
    26	
    27	// A Repo represents a repository storing all versions of a single module.
    28	// It must be safe for simultaneous use by multiple goroutines.
    29	type Repo interface {
    30		// ModulePath returns the module path.
    31		ModulePath() string
    32	
    33		// Versions lists all known versions with the given prefix.
    34		// Pseudo-versions are not included.
    35		// Versions should be returned sorted in semver order
    36		// (implementations can use SortVersions).
    37		Versions(prefix string) (tags []string, err error)
    38	
    39		// Stat returns information about the revision rev.
    40		// A revision can be any identifier known to the underlying service:
    41		// commit hash, branch, tag, and so on.
    42		Stat(rev string) (*RevInfo, error)
    43	
    44		// Latest returns the latest revision on the default branch,
    45		// whatever that means in the underlying source code repository.
    46		// It is only used when there are no tagged versions.
    47		Latest() (*RevInfo, error)
    48	
    49		// GoMod returns the go.mod file for the given version.
    50		GoMod(version string) (data []byte, err error)
    51	
    52		// Zip writes a zip file for the given version to dst.
    53		Zip(dst io.Writer, version string) error
    54	}
    55	
    56	// A Rev describes a single revision in a module repository.
    57	type RevInfo struct {
    58		Version string    // version string
    59		Time    time.Time // commit time
    60	
    61		// These fields are used for Stat of arbitrary rev,
    62		// but they are not recorded when talking about module versions.
    63		Name  string `json:"-"` // complete ID in underlying repository
    64		Short string `json:"-"` // shortened ID, for use in pseudo-version
    65	}
    66	
    67	// Re: module paths, import paths, repository roots, and lookups
    68	//
    69	// A module is a collection of Go packages stored in a file tree
    70	// with a go.mod file at the root of the tree.
    71	// The go.mod defines the module path, which is the import path
    72	// corresponding to the root of the file tree.
    73	// The import path of a directory within that file tree is the module path
    74	// joined with the name of the subdirectory relative to the root.
    75	//
    76	// For example, the module with path rsc.io/qr corresponds to the
    77	// file tree in the repository https://github.com/rsc/qr.
    78	// That file tree has a go.mod that says "module rsc.io/qr".
    79	// The package in the root directory has import path "rsc.io/qr".
    80	// The package in the gf256 subdirectory has import path "rsc.io/qr/gf256".
    81	// In this example, "rsc.io/qr" is both a module path and an import path.
    82	// But "rsc.io/qr/gf256" is only an import path, not a module path:
    83	// it names an importable package, but not a module.
    84	//
    85	// As a special case to incorporate code written before modules were
    86	// introduced, if a path p resolves using the pre-module "go get" lookup
    87	// to the root of a source code repository without a go.mod file,
    88	// that repository is treated as if it had a go.mod in its root directory
    89	// declaring module path p. (The go.mod is further considered to
    90	// contain requirements corresponding to any legacy version
    91	// tracking format such as Gopkg.lock, vendor/vendor.conf, and so on.)
    92	//
    93	// The presentation so far ignores the fact that a source code repository
    94	// has many different versions of a file tree, and those versions may
    95	// differ in whether a particular go.mod exists and what it contains.
    96	// In fact there is a well-defined mapping only from a module path, version
    97	// pair - often written path@version - to a particular file tree.
    98	// For example rsc.io/qr@v0.1.0 depends on the "implicit go.mod at root of
    99	// repository" rule, while rsc.io/qr@v0.2.0 has an explicit go.mod.
   100	// Because the "go get" import paths rsc.io/qr and github.com/rsc/qr
   101	// both redirect to the Git repository https://github.com/rsc/qr,
   102	// github.com/rsc/qr@v0.1.0 is the same file tree as rsc.io/qr@v0.1.0
   103	// but a different module (a different name). In contrast, since v0.2.0
   104	// of that repository has an explicit go.mod that declares path rsc.io/qr,
   105	// github.com/rsc/qr@v0.2.0 is an invalid module path, version pair.
   106	// Before modules, import comments would have had the same effect.
   107	//
   108	// The set of import paths associated with a given module path is
   109	// clearly not fixed: at the least, new directories with new import paths
   110	// can always be added. But another potential operation is to split a
   111	// subtree out of a module into its own module. If done carefully,
   112	// this operation can be done while preserving compatibility for clients.
   113	// For example, suppose that we want to split rsc.io/qr/gf256 into its
   114	// own module, so that there would be two modules rsc.io/qr and rsc.io/qr/gf256.
   115	// Then we can simultaneously issue rsc.io/qr v0.3.0 (dropping the gf256 subdirectory)
   116	// and rsc.io/qr/gf256 v0.1.0, including in their respective go.mod
   117	// cyclic requirements pointing at each other: rsc.io/qr v0.3.0 requires
   118	// rsc.io/qr/gf256 v0.1.0 and vice versa. Then a build can be
   119	// using an older rsc.io/qr module that includes the gf256 package, but if
   120	// it adds a requirement on either the newer rsc.io/qr or the newer
   121	// rsc.io/qr/gf256 module, it will automatically add the requirement
   122	// on the complementary half, ensuring both that rsc.io/qr/gf256 is
   123	// available for importing by the build and also that it is only defined
   124	// by a single module. The gf256 package could move back into the
   125	// original by another simultaneous release of rsc.io/qr v0.4.0 including
   126	// the gf256 subdirectory and an rsc.io/qr/gf256 v0.2.0 with no code
   127	// in its root directory, along with a new requirement cycle.
   128	// The ability to shift module boundaries in this way is expected to be
   129	// important in large-scale program refactorings, similar to the ones
   130	// described in https://talks.golang.org/2016/refactor.article.
   131	//
   132	// The possibility of shifting module boundaries reemphasizes
   133	// that you must know both the module path and its version
   134	// to determine the set of packages provided directly by that module.
   135	//
   136	// On top of all this, it is possible for a single code repository
   137	// to contain multiple modules, either in branches or subdirectories,
   138	// as a limited kind of monorepo. For example rsc.io/qr/v2,
   139	// the v2.x.x continuation of rsc.io/qr, is expected to be found
   140	// in v2-tagged commits in https://github.com/rsc/qr, either
   141	// in the root or in a v2 subdirectory, disambiguated by go.mod.
   142	// Again the precise file tree corresponding to a module
   143	// depends on which version we are considering.
   144	//
   145	// It is also possible for the underlying repository to change over time,
   146	// without changing the module path. If I copy the github repo over
   147	// to https://bitbucket.org/rsc/qr and update https://rsc.io/qr?go-get=1,
   148	// then clients of all versions should start fetching from bitbucket
   149	// instead of github. That is, in contrast to the exact file tree,
   150	// the location of the source code repository associated with a module path
   151	// does not depend on the module version. (This is by design, as the whole
   152	// point of these redirects is to allow package authors to establish a stable
   153	// name that can be updated as code moves from one service to another.)
   154	//
   155	// All of this is important background for the lookup APIs defined in this
   156	// file.
   157	//
   158	// The Lookup function takes a module path and returns a Repo representing
   159	// that module path. Lookup can do only a little with the path alone.
   160	// It can check that the path is well-formed (see semver.CheckPath)
   161	// and it can check that the path can be resolved to a target repository.
   162	// To avoid version control access except when absolutely necessary,
   163	// Lookup does not attempt to connect to the repository itself.
   164	//
   165	// The ImportRepoRev function is a variant of Import which is limited
   166	// to code in a source code repository at a particular revision identifier
   167	// (usually a commit hash or source code repository tag, not necessarily
   168	// a module version).
   169	// ImportRepoRev is used when converting legacy dependency requirements
   170	// from older systems into go.mod files. Those older systems worked
   171	// at either package or repository granularity, and most of the time they
   172	// recorded commit hashes, not tagged versions.
   173	
   174	var lookupCache par.Cache
   175	
   176	type lookupCacheKey struct {
   177		proxy, path string
   178	}
   179	
   180	// Lookup returns the module with the given module path,
   181	// fetched through the given proxy.
   182	//
   183	// The distinguished proxy "direct" indicates that the path should be fetched
   184	// from its origin, and "noproxy" indicates that the patch should be fetched
   185	// directly only if GONOPROXY matches the given path.
   186	//
   187	// For the distinguished proxy "off", Lookup always returns a non-nil error.
   188	//
   189	// A successful return does not guarantee that the module
   190	// has any defined versions.
   191	func Lookup(proxy, path string) (Repo, error) {
   192		if traceRepo {
   193			defer logCall("Lookup(%q, %q)", proxy, path)()
   194		}
   195	
   196		type cached struct {
   197			r   Repo
   198			err error
   199		}
   200		c := lookupCache.Do(lookupCacheKey{proxy, path}, func() interface{} {
   201			r, err := lookup(proxy, path)
   202			if err == nil {
   203				if traceRepo {
   204					r = newLoggingRepo(r)
   205				}
   206				r = newCachingRepo(r)
   207			}
   208			return cached{r, err}
   209		}).(cached)
   210	
   211		return c.r, c.err
   212	}
   213	
   214	// lookup returns the module with the given module path.
   215	func lookup(proxy, path string) (r Repo, err error) {
   216		if cfg.BuildMod == "vendor" {
   217			return nil, errModVendor
   218		}
   219	
   220		if str.GlobsMatchPath(cfg.GONOPROXY, path) {
   221			switch proxy {
   222			case "noproxy", "direct":
   223				return lookupDirect(path)
   224			default:
   225				return nil, errNoproxy
   226			}
   227		}
   228	
   229		switch proxy {
   230		case "off":
   231			return nil, errProxyOff
   232		case "direct":
   233			return lookupDirect(path)
   234		case "noproxy":
   235			return nil, errUseProxy
   236		default:
   237			return newProxyRepo(proxy, path)
   238		}
   239	}
   240	
   241	var (
   242		errModVendor       = errors.New("module lookup disabled by -mod=vendor")
   243		errProxyOff        = notExistError("module lookup disabled by GOPROXY=off")
   244		errNoproxy   error = notExistError("disabled by GOPRIVATE/GONOPROXY")
   245		errUseProxy  error = notExistError("path does not match GOPRIVATE/GONOPROXY")
   246	)
   247	
   248	func lookupDirect(path string) (Repo, error) {
   249		security := web.SecureOnly
   250		if get.Insecure {
   251			security = web.Insecure
   252		}
   253		rr, err := get.RepoRootForImportPath(path, get.PreferMod, security)
   254		if err != nil {
   255			// We don't know where to find code for a module with this path.
   256			return nil, notExistError(err.Error())
   257		}
   258	
   259		if rr.VCS == "mod" {
   260			// Fetch module from proxy with base URL rr.Repo.
   261			return newProxyRepo(rr.Repo, path)
   262		}
   263	
   264		code, err := lookupCodeRepo(rr)
   265		if err != nil {
   266			return nil, err
   267		}
   268		return newCodeRepo(code, rr.Root, path)
   269	}
   270	
   271	func lookupCodeRepo(rr *get.RepoRoot) (codehost.Repo, error) {
   272		code, err := codehost.NewRepo(rr.VCS, rr.Repo)
   273		if err != nil {
   274			if _, ok := err.(*codehost.VCSError); ok {
   275				return nil, err
   276			}
   277			return nil, fmt.Errorf("lookup %s: %v", rr.Root, err)
   278		}
   279		return code, nil
   280	}
   281	
   282	// ImportRepoRev returns the module and version to use to access
   283	// the given import path loaded from the source code repository that
   284	// the original "go get" would have used, at the specific repository revision
   285	// (typically a commit hash, but possibly also a source control tag).
   286	func ImportRepoRev(path, rev string) (Repo, *RevInfo, error) {
   287		if cfg.BuildMod == "vendor" || cfg.BuildMod == "readonly" {
   288			return nil, nil, fmt.Errorf("repo version lookup disabled by -mod=%s", cfg.BuildMod)
   289		}
   290	
   291		// Note: Because we are converting a code reference from a legacy
   292		// version control system, we ignore meta tags about modules
   293		// and use only direct source control entries (get.IgnoreMod).
   294		security := web.SecureOnly
   295		if get.Insecure {
   296			security = web.Insecure
   297		}
   298		rr, err := get.RepoRootForImportPath(path, get.IgnoreMod, security)
   299		if err != nil {
   300			return nil, nil, err
   301		}
   302	
   303		code, err := lookupCodeRepo(rr)
   304		if err != nil {
   305			return nil, nil, err
   306		}
   307	
   308		revInfo, err := code.Stat(rev)
   309		if err != nil {
   310			return nil, nil, err
   311		}
   312	
   313		// TODO: Look in repo to find path, check for go.mod files.
   314		// For now we're just assuming rr.Root is the module path,
   315		// which is true in the absence of go.mod files.
   316	
   317		repo, err := newCodeRepo(code, rr.Root, rr.Root)
   318		if err != nil {
   319			return nil, nil, err
   320		}
   321	
   322		info, err := repo.(*codeRepo).convert(revInfo, rev)
   323		if err != nil {
   324			return nil, nil, err
   325		}
   326		return repo, info, nil
   327	}
   328	
   329	func SortVersions(list []string) {
   330		sort.Slice(list, func(i, j int) bool {
   331			cmp := semver.Compare(list[i], list[j])
   332			if cmp != 0 {
   333				return cmp < 0
   334			}
   335			return list[i] < list[j]
   336		})
   337	}
   338	
   339	// A loggingRepo is a wrapper around an underlying Repo
   340	// that prints a log message at the start and end of each call.
   341	// It can be inserted when debugging.
   342	type loggingRepo struct {
   343		r Repo
   344	}
   345	
   346	func newLoggingRepo(r Repo) *loggingRepo {
   347		return &loggingRepo{r}
   348	}
   349	
   350	// logCall prints a log message using format and args and then
   351	// also returns a function that will print the same message again,
   352	// along with the elapsed time.
   353	// Typical usage is:
   354	//
   355	//	defer logCall("hello %s", arg)()
   356	//
   357	// Note the final ().
   358	func logCall(format string, args ...interface{}) func() {
   359		start := time.Now()
   360		fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...))
   361		return func() {
   362			fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...))
   363		}
   364	}
   365	
   366	func (l *loggingRepo) ModulePath() string {
   367		return l.r.ModulePath()
   368	}
   369	
   370	func (l *loggingRepo) Versions(prefix string) (tags []string, err error) {
   371		defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)()
   372		return l.r.Versions(prefix)
   373	}
   374	
   375	func (l *loggingRepo) Stat(rev string) (*RevInfo, error) {
   376		defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)()
   377		return l.r.Stat(rev)
   378	}
   379	
   380	func (l *loggingRepo) Latest() (*RevInfo, error) {
   381		defer logCall("Repo[%s]: Latest()", l.r.ModulePath())()
   382		return l.r.Latest()
   383	}
   384	
   385	func (l *loggingRepo) GoMod(version string) ([]byte, error) {
   386		defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)()
   387		return l.r.GoMod(version)
   388	}
   389	
   390	func (l *loggingRepo) Zip(dst io.Writer, version string) error {
   391		dstName := "_"
   392		if dst, ok := dst.(interface{ Name() string }); ok {
   393			dstName = strconv.Quote(dst.Name())
   394		}
   395		defer logCall("Repo[%s]: Zip(%s, %q)", l.r.ModulePath(), dstName, version)()
   396		return l.r.Zip(dst, version)
   397	}
   398	
   399	// A notExistError is like os.ErrNotExist, but with a custom message
   400	type notExistError string
   401	
   402	func (e notExistError) Error() string {
   403		return string(e)
   404	}
   405	func (notExistError) Is(target error) bool {
   406		return target == os.ErrNotExist
   407	}
   408	

View as plain text