// use a different version syntax and semantics (gover, this package)
// than most modules (semver).
func ModCompare(path string, x, y string) int {
- if IsToolchain(path) {
+ if path == "go" {
return Compare(x, y)
}
+ if path == "toolchain" {
+ return Compare(untoolchain(x), untoolchain(y))
+ }
return semver.Compare(x, y)
}
}
return semver.IsValid(vers)
}
+
+// untoolchain converts a toolchain name like "go1.2.3" to a Go version like "1.2.3".
+// It also converts "anything-go1.2.3" (for example, "gccgo-go1.2.3") to "1.2.3".
+func untoolchain(x string) string {
+ if strings.HasPrefix(x, "go1") {
+ return x[len("go"):]
+ }
+ if i := strings.Index(x, "-go1"); i >= 0 {
+ return x[i+len("-go"):]
+ }
+ return x
+}
{"go", "1.2", "1.3", -1},
{"go", "v1.2", "v1.3", 0}, // equal because invalid
{"go", "1.2", "1.2", 0},
- {"toolchain", "1.2", "1.3", -1},
- {"toolchain", "1.2", "1.2", 0},
+ {"toolchain", "go1.2", "go1.3", -1},
+ {"toolchain", "go1.2", "go1.2", 0},
+ {"toolchain", "1.2", "1.3", -1}, // accepted but non-standard
{"toolchain", "v1.2", "v1.3", 0}, // equal because invalid
{"rsc.io/quote", "v1.2", "v1.3", -1},
{"rsc.io/quote", "1.2", "1.3", 0}, // equal because invalid
"cmd/go/internal/base"
"cmd/go/internal/cfg"
+ "cmd/go/internal/gover"
"cmd/go/internal/lockedfile"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/par"
}
func CachePath(ctx context.Context, m module.Version, suffix string) (string, error) {
+ if gover.IsToolchain(m.Path) {
+ return "", ErrToolchain
+ }
dir, err := cacheDir(ctx, m.Path)
if err != nil {
return "", err
// along with the directory if the directory does not exist or if the directory
// is not completely populated.
func DownloadDir(ctx context.Context, m module.Version) (string, error) {
+ if gover.IsToolchain(m.Path) {
+ return "", ErrToolchain
+ }
if err := checkCacheDir(ctx); err != nil {
return "", err
}
}
func (r *cachingRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
+ if gover.IsToolchain(r.path) {
+ // Skip disk cache; the underlying golang.org/toolchain repo is cached instead.
+ return r.repo(ctx).Stat(ctx, rev)
+ }
info, err := r.statCache.Do(rev, func() (*RevInfo, error) {
file, info, err := readDiskStat(ctx, r.path, rev)
if err == nil {
}
func (r *cachingRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ if gover.IsToolchain(r.path) {
+ // Skip disk cache; the underlying golang.org/toolchain repo is cached instead.
+ return r.repo(ctx).Latest(ctx)
+ }
info, err := r.latestCache.Do(struct{}{}, func() (*RevInfo, error) {
info, err := r.repo(ctx).Latest(ctx)
}
func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) {
+ if gover.IsToolchain(r.path) {
+ // Skip disk cache; the underlying golang.org/toolchain repo is cached instead.
+ return r.repo(ctx).GoMod(ctx, version)
+ }
text, err := r.gomodCache.Do(version, func() ([]byte, error) {
file, text, err := readDiskGoMod(ctx, r.path, version)
if err == nil {
}
func (r *cachingRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
+ if gover.IsToolchain(r.path) {
+ return ErrToolchain
+ }
return r.repo(ctx).Zip(ctx, dst, version)
}
// If the read fails, the caller can use
// writeDiskStat(file, info) to write a new cache entry.
func readDiskStat(ctx context.Context, path, rev string) (file string, info *RevInfo, err error) {
+ if gover.IsToolchain(path) {
+ return "", nil, errNotCached
+ }
file, data, err := readDiskCache(ctx, path, rev, "info")
if err != nil {
// If the cache already contains a pseudo-version with the given hash, we
// just to find out about a commit we already know about
// (and have cached under its pseudo-version).
func readDiskStatByHash(ctx context.Context, path, rev string) (file string, info *RevInfo, err error) {
+ if gover.IsToolchain(path) {
+ return "", nil, errNotCached
+ }
if cfg.GOMODCACHE == "" {
// Do not download to current directory.
return "", nil, errNotCached
// If the read fails, the caller can use
// writeDiskGoMod(file, data) to write a new cache entry.
func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) {
+ if gover.IsToolchain(path) {
+ return "", nil, errNotCached
+ }
file, data, err = readDiskCache(ctx, path, rev, "mod")
// If the file has an old auto-conversion prefix, pretend it's not there.
// If the read fails, the caller can use
// writeDiskCache(file, data) to write a new cache entry.
func readDiskCache(ctx context.Context, path, rev, suffix string) (file string, data []byte, err error) {
+ if gover.IsToolchain(path) {
+ return "", nil, errNotCached
+ }
file, err = CachePath(ctx, module.Version{Path: path, Version: rev}, suffix)
if err != nil {
return "", nil, errNotCached
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
+ "cmd/go/internal/gover"
"cmd/go/internal/lockedfile"
"cmd/go/internal/par"
"cmd/go/internal/robustio"
var downloadCache par.ErrCache[module.Version, string] // version → directory
+var ErrToolchain = errors.New("internal error: invalid operation on toolchain module")
+
// Download downloads the specific module version to the
// local download cache and returns the name of the directory
// corresponding to the root of the module's file tree.
func Download(ctx context.Context, mod module.Version) (dir string, err error) {
+ if gover.IsToolchain(mod.Path) {
+ return "", ErrToolchain
+ }
if err := checkCacheDir(ctx); err != nil {
base.Fatalf("go: %v", err)
}
return nil, errLookupDisabled
}
+ switch path {
+ case "go", "toolchain":
+ return &toolchainRepo{path, Lookup(ctx, proxy, "golang.org/toolchain")}, nil
+ }
+
if module.MatchPrefixPatterns(cfg.GONOPROXY, path) {
switch proxy {
case "noproxy", "direct":
--- /dev/null
+// Copyright 2023 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 (
+ "context"
+ "fmt"
+ "io"
+ "strings"
+
+ "cmd/go/internal/gover"
+ "cmd/go/internal/modfetch/codehost"
+)
+
+// A toolchainRepo is a synthesized repository reporting Go toolchain versions.
+// It has path "go" or "toolchain". The "go" repo reports versions like "1.2".
+// The "toolchain" repo reports versions like "go1.2".
+//
+// Note that the repo ONLY reports versions. It does not actually support
+// downloading of the actual toolchains. Instead, that is done using
+// the regular repo code with "golang.org/toolchain".
+// The naming conflict is unfortunate: "golang.org/toolchain"
+// should perhaps have been "go.dev/dl", but it's too late.
+//
+// For clarity, this file refers to golang.org/toolchain as the "DL" repo,
+// the one you can actually download.
+type toolchainRepo struct {
+ path string // either "go" or "toolchain"
+ repo Repo // underlying DL repo
+}
+
+func (r *toolchainRepo) ModulePath() string {
+ return r.path
+}
+
+func (r *toolchainRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
+ // Read DL repo list and convert to "go" or "toolchain" version list.
+ versions, err := r.repo.Versions(ctx, "")
+ if err != nil {
+ return nil, err
+ }
+ versions.Origin = nil
+ var list []string
+ have := make(map[string]bool)
+ goPrefix := ""
+ if r.path == "toolchain" {
+ goPrefix = "go"
+ }
+ for _, v := range versions.List {
+ v, ok := dlToGo(v)
+ if !ok {
+ continue
+ }
+ if !have[v] {
+ have[v] = true
+ list = append(list, goPrefix+v)
+ }
+ }
+ versions.List = list
+ return versions, nil
+}
+
+func (r *toolchainRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
+ // If we're asking about "go" (not "toolchain"), pretend to have
+ // all earlier Go versions available without network access:
+ // we will provide those ourselves, at least in GOTOOLCHAIN=auto mode.
+ if r.path == "go" && gover.Compare(rev, gover.Local()) <= 0 {
+ return &RevInfo{Version: rev}, nil
+ }
+
+ // Convert rev to DL version and stat that to make sure it exists.
+ prefix := ""
+ v := rev
+ if r.path == "toolchain" {
+ prefix = "go"
+ v = strings.TrimPrefix(v, "go")
+ }
+ if gover.IsLang(v) {
+ return nil, fmt.Errorf("go language version %s is not a toolchain version", rev)
+ }
+
+ // Check that the underlying toolchain exists.
+ // We always ask about linux-amd64 because that one
+ // has always existed and is likely to always exist in the future.
+ // This avoids different behavior validating go versions on different
+ // architectures. The eventual download uses the right GOOS-GOARCH.
+ info, err := r.repo.Stat(ctx, goToDL(v, "linux", "amd64"))
+ if err != nil {
+ return nil, err
+ }
+
+ // Return the info using the canonicalized rev
+ // (toolchain 1.2 => toolchain go1.2).
+ return &RevInfo{Version: prefix + v, Time: info.Time}, nil
+}
+
+func (r *toolchainRepo) Latest(ctx context.Context) (*RevInfo, error) {
+ versions, err := r.Versions(ctx, "")
+ if err != nil {
+ return nil, err
+ }
+ var max string
+ for _, v := range versions.List {
+ if max == "" || gover.ModCompare(r.path, v, max) > 0 {
+ max = v
+ }
+ }
+ return r.Stat(ctx, max)
+}
+
+func (r *toolchainRepo) GoMod(ctx context.Context, version string) (data []byte, err error) {
+ return []byte("module " + r.path + "\n"), nil
+}
+
+func (r *toolchainRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
+ return fmt.Errorf("invalid use of toolchainRepo: Zip")
+}
+
+func (r *toolchainRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
+ return fmt.Errorf("invalid use of toolchainRepo: CheckReuse")
+}
+
+// goToDL converts a Go version like "1.2" to a DL module version like "v0.0.1-go1.2.linux-amd64".
+func goToDL(v, goos, goarch string) string {
+ return "v0.0.1-go" + v + ".linux-amd64"
+}
+
+// dlToGo converts a DL module version like "v0.0.1-go1.2.linux-amd64" to a Go version like "1.2".
+func dlToGo(v string) (string, bool) {
+ // v0.0.1-go1.19.7.windows-amd64
+ // cut v0.0.1-
+ _, v, ok := strings.Cut(v, "-")
+ if !ok {
+ return "", false
+ }
+ // cut .windows-amd64
+ i := strings.LastIndex(v, ".")
+ if i < 0 || !strings.Contains(v[i+1:], "-") {
+ return "", false
+ }
+ return strings.TrimPrefix(v[:i], "go"), true
+}
//
// The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) {
+ if m.Path == "go" || m.Path == "toolchain" {
+ return &modFileSummary{module: m}, nil
+ }
if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
panic("internal error: goModSummary called on a main module")
}
//
// rawGoModSummary cannot be used on the main module outside of workspace mode.
func rawGoModSummary(m module.Version) (*modFileSummary, error) {
+ if gover.IsToolchain(m.Path) {
+ return &modFileSummary{module: m}, nil
+ }
if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
// Calling rawGoModSummary implies that we are treating m as a module whose
// requirements aren't the roots of the module graph and can't be modified.
var _ versionRepo = modfetch.Repo(nil)
func lookupRepo(ctx context.Context, proxy, path string) (repo versionRepo, err error) {
- err = module.CheckPath(path)
+ if path != "go" && path != "toolchain" {
+ err = module.CheckPath(path)
+ }
if err == nil {
repo = modfetch.Lookup(ctx, proxy, path)
} else {
--- /dev/null
+[!net:golang.org] skip
+
+env GOPROXY=
+
+go list -m -versions go
+stdout 1.20.1 # among others
+stdout 1.19rc2
+! stdout go1.20.1 # no go prefixes
+! stdout go1.19rc2
+
+go list -m -versions toolchain
+stdout go1.20.1 # among others
+stdout go1.19rc2