all: integrate changes made in cmd/go packages
This change integrates changes made to cmd/go packages into the
corresponding x/mod packages.
This is the second step of a bidirectional synchronization. The
previous step was CL 200138, which copied changes made in these
packages back into cmd/go. With this change, the cmd/go and x/mod
packages should be the same except for imports. After this change, we
can vendor x/mod into cmd, then remove the duplicate packages in
cmd/go.
Other important changes:
* Updated requirement on golang.org/x/crypto to latest and added
golang.org/x/xerrors. crypto is needed for ed25519, which was added
to std in go1.13. xerrors is needed for unwrapping, which was also
added in 1.13. The x versions of these are compatible with go1.12.
* Copied internal/lazyregexp from std, since it's used in sumdb.
Updates golang/go#34801
Change-Id: Iafdd0668970f9004e663040535c521241d11a6a8
Reviewed-on: https://go-review.googlesource.com/c/mod/+/200767
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/go.mod b/go.mod
index a5fc715..9d78a76 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,8 @@
module golang.org/x/mod
-require golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529
-
go 1.12
+
+require (
+ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
+ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
+)
diff --git a/go.sum b/go.sum
index 179d072..249ed71 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/lazyregexp/lazyre.go b/internal/lazyregexp/lazyre.go
new file mode 100644
index 0000000..2681af3
--- /dev/null
+++ b/internal/lazyregexp/lazyre.go
@@ -0,0 +1,78 @@
+// 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 lazyregexp is a thin wrapper over regexp, allowing the use of global
+// regexp variables without forcing them to be compiled at init.
+package lazyregexp
+
+import (
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+)
+
+// Regexp is a wrapper around regexp.Regexp, where the underlying regexp will be
+// compiled the first time it is needed.
+type Regexp struct {
+ str string
+ once sync.Once
+ rx *regexp.Regexp
+}
+
+func (r *Regexp) re() *regexp.Regexp {
+ r.once.Do(r.build)
+ return r.rx
+}
+
+func (r *Regexp) build() {
+ r.rx = regexp.MustCompile(r.str)
+ r.str = ""
+}
+
+func (r *Regexp) FindSubmatch(s []byte) [][]byte {
+ return r.re().FindSubmatch(s)
+}
+
+func (r *Regexp) FindStringSubmatch(s string) []string {
+ return r.re().FindStringSubmatch(s)
+}
+
+func (r *Regexp) FindStringSubmatchIndex(s string) []int {
+ return r.re().FindStringSubmatchIndex(s)
+}
+
+func (r *Regexp) ReplaceAllString(src, repl string) string {
+ return r.re().ReplaceAllString(src, repl)
+}
+
+func (r *Regexp) FindString(s string) string {
+ return r.re().FindString(s)
+}
+
+func (r *Regexp) FindAllString(s string, n int) []string {
+ return r.re().FindAllString(s, n)
+}
+
+func (r *Regexp) MatchString(s string) bool {
+ return r.re().MatchString(s)
+}
+
+func (r *Regexp) SubexpNames() []string {
+ return r.re().SubexpNames()
+}
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+// New creates a new lazy regexp, delaying the compiling work until it is first
+// needed. If the code is being run as part of tests, the regexp compiling will
+// happen immediately.
+func New(str string) *Regexp {
+ lr := &Regexp{str: str}
+ if inTest {
+ // In tests, always compile the regexps early.
+ lr.re()
+ }
+ return lr
+}
diff --git a/module/module.go b/module/module.go
index 3135e4b..abd8149 100644
--- a/module/module.go
+++ b/module/module.go
@@ -103,6 +103,7 @@
"unicode/utf8"
"golang.org/x/mod/semver"
+ errors "golang.org/x/xerrors"
)
// A Version (for clients, a module.Version) is defined by a module path and version pair.
@@ -111,9 +112,14 @@
// Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
Path string
- // Version is a module version.
- // By convention, the version string is in canonical semver form,
- // but enforcement is left up to individual APIs.
+ // Version is usually a semantic version in canonical form.
+ // There are three exceptions to this general rule.
+ // First, the top-level target of a build has no specific version
+ // and uses Version = "".
+ // Second, during MVS calculations the version "none" is used
+ // to represent the decision to take no version of a given module.
+ // Third, filesystem paths found in "replace" directives are
+ // represented by a path with an empty version.
Version string `json:",omitempty"`
}
@@ -122,6 +128,65 @@
return m.Path + "@" + m.Version
}
+// A ModuleError indicates an error specific to a module.
+type ModuleError struct {
+ Path string
+ Version string
+ Err error
+}
+
+// VersionError returns a ModuleError derived from a Version and error,
+// or err itself if it is already such an error.
+func VersionError(v Version, err error) error {
+ var mErr *ModuleError
+ if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version {
+ return err
+ }
+ return &ModuleError{
+ Path: v.Path,
+ Version: v.Version,
+ Err: err,
+ }
+}
+
+func (e *ModuleError) Error() string {
+ if v, ok := e.Err.(*InvalidVersionError); ok {
+ return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
+ }
+ if e.Version != "" {
+ return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
+ }
+ return fmt.Sprintf("module %s: %v", e.Path, e.Err)
+}
+
+func (e *ModuleError) Unwrap() error { return e.Err }
+
+// An InvalidVersionError indicates an error specific to a version, with the
+// module path unknown or specified externally.
+//
+// A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError
+// must not wrap a ModuleError.
+type InvalidVersionError struct {
+ Version string
+ Pseudo bool
+ Err error
+}
+
+// noun returns either "version" or "pseudo-version", depending on whether
+// e.Version is a pseudo-version.
+func (e *InvalidVersionError) noun() string {
+ if e.Pseudo {
+ return "pseudo-version"
+ }
+ return "version"
+}
+
+func (e *InvalidVersionError) Error() string {
+ return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
+}
+
+func (e *InvalidVersionError) Unwrap() error { return e.Err }
+
// Check checks that a given module path, version pair is valid.
// In addition to the path being a valid module path
// and the version being a valid semantic version,
@@ -133,17 +198,14 @@
return err
}
if !semver.IsValid(version) {
- return fmt.Errorf("malformed semantic version %v", version)
+ return &ModuleError{
+ Path: path,
+ Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
+ }
}
_, pathMajor, _ := SplitPathVersion(path)
- if !MatchPathMajor(version, pathMajor) {
- if pathMajor == "" {
- pathMajor = "v0 or v1"
- }
- if pathMajor[0] == '.' { // .v1
- pathMajor = pathMajor[1:]
- }
- return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor)
+ if err := CheckPathMajor(version, pathMajor); err != nil {
+ return &ModuleError{Path: path, Err: err}
}
return nil
}
@@ -161,7 +223,7 @@
// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
// This matches what "go get" has historically recognized in import paths.
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
-// care in the safe encoding (see note above).
+// care in the safe encoding (see "escaped paths" above).
func pathOK(r rune) bool {
if r < utf8.RuneSelf {
return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
@@ -176,7 +238,7 @@
// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
// If we expand the set of allowed characters here, we have to
// work harder at detecting potential case-folding and normalization collisions.
-// See note about "safe encoding" above.
+// See note about "escaped paths" above.
func fileNameOK(r rune) bool {
if r < utf8.RuneSelf {
// Entire set of ASCII punctuation, from which we remove characters:
@@ -276,8 +338,8 @@
if path == "" {
return fmt.Errorf("empty string")
}
- if strings.Contains(path, "..") {
- return fmt.Errorf("double dot")
+ if path[0] == '-' {
+ return fmt.Errorf("leading dash")
}
if strings.Contains(path, "//") {
return fmt.Errorf("double slash")
@@ -440,20 +502,62 @@
// MatchPathMajor reports whether the semantic version v
// matches the path major version pathMajor.
+//
+// MatchPathMajor returns true if and only if CheckPathMajor returns nil.
func MatchPathMajor(v, pathMajor string) bool {
+ return CheckPathMajor(v, pathMajor) == nil
+}
+
+// CheckPathMajor returns a non-nil error if the semantic version v
+// does not match the path major version pathMajor.
+func CheckPathMajor(v, pathMajor string) error {
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
}
if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
- return true
+ return nil
}
m := semver.Major(v)
if pathMajor == "" {
- return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible"
+ if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
+ return nil
+ }
+ pathMajor = "v0 or v1"
+ } else if pathMajor[0] == '/' || pathMajor[0] == '.' {
+ if m == pathMajor[1:] {
+ return nil
+ }
+ pathMajor = pathMajor[1:]
}
- return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:]
+ return &InvalidVersionError{
+ Version: v,
+ Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
+ }
+}
+
+// PathMajorPrefix returns the major-version tag prefix implied by pathMajor.
+// An empty PathMajorPrefix allows either v0 or v1.
+//
+// Note that MatchPathMajor may accept some versions that do not actually begin
+// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1'
+// pathMajor, even though that pathMajor implies 'v1' tagging.
+func PathMajorPrefix(pathMajor string) string {
+ if pathMajor == "" {
+ return ""
+ }
+ if pathMajor[0] != '/' && pathMajor[0] != '.' {
+ panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
+ }
+ if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
+ pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
+ }
+ m := pathMajor[1:]
+ if m != semver.Major(m) {
+ panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
+ }
+ return m
}
// CanonicalVersion returns the canonical form of the version string v.
@@ -511,7 +615,10 @@
// and not contain exclamation marks.
func EscapeVersion(v string) (escaped string, err error) {
if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
- return "", fmt.Errorf("disallowed version string %q", v)
+ return "", &InvalidVersionError{
+ Version: v,
+ Err: fmt.Errorf("disallowed version string"),
+ }
}
return escapeString(v)
}
diff --git a/module/module_test.go b/module/module_test.go
index 0759bb1..87f8ec8 100644
--- a/module/module_test.go
+++ b/module/module_test.go
@@ -79,8 +79,8 @@
{"/x.y/z", false, false, false},
{"x./z", false, false, false},
{".x/z", false, false, true},
- {"-x/z", false, true, true},
- {"x..y/z", false, false, false},
+ {"-x/z", false, false, false},
+ {"x..y/z", true, true, true},
{"x.y/z/../../w", false, false, false},
{"x.y//z", false, false, false},
{"x.y/z//w", false, false, false},
diff --git a/semver/semver.go b/semver/semver.go
index 122e612..2988e3c 100644
--- a/semver/semver.go
+++ b/semver/semver.go
@@ -107,7 +107,7 @@
}
// Compare returns an integer comparing two versions according to
-// according to semantic version precedence.
+// semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
diff --git a/sumdb/server.go b/sumdb/server.go
index acaf720..a4f8bee 100644
--- a/sumdb/server.go
+++ b/sumdb/server.go
@@ -9,9 +9,9 @@
"context"
"net/http"
"os"
- "regexp"
"strings"
+ "golang.org/x/mod/internal/lazyregexp"
"golang.org/x/mod/module"
"golang.org/x/mod/sumdb/tlog"
)
@@ -61,7 +61,7 @@
"/tile/",
}
-var modVerRE = regexp.MustCompile(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
+var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
diff --git a/sumdb/test.go b/sumdb/test.go
index 534ca3e..e4c166d 100644
--- a/sumdb/test.go
+++ b/sumdb/test.go
@@ -7,9 +7,9 @@
import (
"context"
"fmt"
- "strings"
"sync"
+ "golang.org/x/mod/module"
"golang.org/x/mod/sumdb/note"
"golang.org/x/mod/sumdb/tlog"
)
@@ -75,7 +75,8 @@
return list, nil
}
-func (s *TestServer) Lookup(ctx context.Context, key string) (int64, error) {
+func (s *TestServer) Lookup(ctx context.Context, m module.Version) (int64, error) {
+ key := m.String()
s.mu.Lock()
id, ok := s.lookup[key]
s.mu.Unlock()
@@ -84,12 +85,7 @@
}
// Look up module and compute go.sum lines.
- i := strings.Index(key, "@")
- if i < 0 {
- return 0, fmt.Errorf("invalid lookup key %q", key)
- }
- path, vers := key[:i], key[i+1:]
- data, err := s.gosum(path, vers)
+ data, err := s.gosum(m.Path, m.Version)
if err != nil {
return 0, err
}
diff --git a/sumdb/tlog/tlog.go b/sumdb/tlog/tlog.go
index a5b3d03..01d06c4 100644
--- a/sumdb/tlog/tlog.go
+++ b/sumdb/tlog/tlog.go
@@ -118,7 +118,7 @@
func StoredHashIndex(level int, n int64) int64 {
// Level L's n'th hash is written right after level L+1's 2n+1'th hash.
// Work our way down to the level 0 ordering.
- // We'll add back the orignal level count at the end.
+ // We'll add back the original level count at the end.
for l := level; l > 0; l-- {
n = 2*n + 1
}
@@ -152,7 +152,7 @@
n++
indexN = x
}
- // The hash we want was commited with record n,
+ // The hash we want was committed with record n,
// meaning it is one of (0, n), (1, n/2), (2, n/4), ...
level = int(index - indexN)
return level, n >> uint(level)