Source file src/cmd/go/internal/modfetch/toolchain.go

     1  // Copyright 2023 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  	"context"
     9  	"fmt"
    10  	"io"
    11  	"sort"
    12  	"strings"
    13  
    14  	"cmd/go/internal/gover"
    15  	"cmd/go/internal/modfetch/codehost"
    16  )
    17  
    18  // A toolchainRepo is a synthesized repository reporting Go toolchain versions.
    19  // It has path "go" or "toolchain". The "go" repo reports versions like "1.2".
    20  // The "toolchain" repo reports versions like "go1.2".
    21  //
    22  // Note that the repo ONLY reports versions. It does not actually support
    23  // downloading of the actual toolchains. Instead, that is done using
    24  // the regular repo code with "golang.org/toolchain".
    25  // The naming conflict is unfortunate: "golang.org/toolchain"
    26  // should perhaps have been "go.dev/dl", but it's too late.
    27  //
    28  // For clarity, this file refers to golang.org/toolchain as the "DL" repo,
    29  // the one you can actually download.
    30  type toolchainRepo struct {
    31  	path string // either "go" or "toolchain"
    32  	repo Repo   // underlying DL repo
    33  }
    34  
    35  func (r *toolchainRepo) ModulePath() string {
    36  	return r.path
    37  }
    38  
    39  func (r *toolchainRepo) Versions(ctx context.Context, prefix string) (*Versions, error) {
    40  	// Read DL repo list and convert to "go" or "toolchain" version list.
    41  	versions, err := r.repo.Versions(ctx, "")
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	versions.Origin = nil
    46  	var list []string
    47  	have := make(map[string]bool)
    48  	goPrefix := ""
    49  	if r.path == "toolchain" {
    50  		goPrefix = "go"
    51  	}
    52  	for _, v := range versions.List {
    53  		v, ok := dlToGo(v)
    54  		if !ok {
    55  			continue
    56  		}
    57  		if !have[v] {
    58  			have[v] = true
    59  			list = append(list, goPrefix+v)
    60  		}
    61  	}
    62  
    63  	// Always include our own version.
    64  	// This means that the development branch of Go 1.21 (say) will allow 'go get go@1.21'
    65  	// even though there are no Go 1.21 releases yet.
    66  	// Once there is a release, 1.21 will be treated as a query matching the latest available release.
    67  	// Before then, 1.21 will be treated as a query that resolves to this entry we are adding (1.21).
    68  	if v := gover.Local(); !have[v] {
    69  		list = append(list, goPrefix+v)
    70  	}
    71  
    72  	if r.path == "go" {
    73  		sort.Slice(list, func(i, j int) bool {
    74  			return gover.Compare(list[i], list[j]) < 0
    75  		})
    76  	} else {
    77  		sort.Slice(list, func(i, j int) bool {
    78  			return gover.Compare(gover.FromToolchain(list[i]), gover.FromToolchain(list[j])) < 0
    79  		})
    80  	}
    81  	versions.List = list
    82  	return versions, nil
    83  }
    84  
    85  func (r *toolchainRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
    86  	// Convert rev to DL version and stat that to make sure it exists.
    87  	// In theory the go@ versions should be like 1.21.0
    88  	// and the toolchain@ versions should be like go1.21.0
    89  	// but people will type the wrong one, and so we accept
    90  	// both and silently correct it to the standard form.
    91  	prefix := ""
    92  	v := rev
    93  	v = strings.TrimPrefix(v, "go")
    94  	if r.path == "toolchain" {
    95  		prefix = "go"
    96  	}
    97  
    98  	if !gover.IsValid(v) {
    99  		return nil, fmt.Errorf("invalid %s version %s", r.path, rev)
   100  	}
   101  
   102  	// If we're asking about "go" (not "toolchain"), pretend to have
   103  	// all earlier Go versions available without network access:
   104  	// we will provide those ourselves, at least in GOTOOLCHAIN=auto mode.
   105  	if r.path == "go" && gover.Compare(v, gover.Local()) <= 0 {
   106  		return &RevInfo{Version: prefix + v}, nil
   107  	}
   108  
   109  	// Similarly, if we're asking about *exactly* the current toolchain,
   110  	// we don't need to access the network to know that it exists.
   111  	if r.path == "toolchain" && v == gover.Local() {
   112  		return &RevInfo{Version: prefix + v}, nil
   113  	}
   114  
   115  	if gover.IsLang(v) {
   116  		// We can only use a language (development) version if the current toolchain
   117  		// implements that version, and the two checks above have ruled that out.
   118  		return nil, fmt.Errorf("go language version %s is not a toolchain version", rev)
   119  	}
   120  
   121  	// Check that the underlying toolchain exists.
   122  	// We always ask about linux-amd64 because that one
   123  	// has always existed and is likely to always exist in the future.
   124  	// This avoids different behavior validating go versions on different
   125  	// architectures. The eventual download uses the right GOOS-GOARCH.
   126  	info, err := r.repo.Stat(ctx, goToDL(v, "linux", "amd64"))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Return the info using the canonicalized rev
   132  	// (toolchain 1.2 => toolchain go1.2).
   133  	return &RevInfo{Version: prefix + v, Time: info.Time}, nil
   134  }
   135  
   136  func (r *toolchainRepo) Latest(ctx context.Context) (*RevInfo, error) {
   137  	versions, err := r.Versions(ctx, "")
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	var max string
   142  	for _, v := range versions.List {
   143  		if max == "" || gover.ModCompare(r.path, v, max) > 0 {
   144  			max = v
   145  		}
   146  	}
   147  	return r.Stat(ctx, max)
   148  }
   149  
   150  func (r *toolchainRepo) GoMod(ctx context.Context, version string) (data []byte, err error) {
   151  	return []byte("module " + r.path + "\n"), nil
   152  }
   153  
   154  func (r *toolchainRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
   155  	return fmt.Errorf("invalid use of toolchainRepo: Zip")
   156  }
   157  
   158  func (r *toolchainRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error {
   159  	return fmt.Errorf("invalid use of toolchainRepo: CheckReuse")
   160  }
   161  
   162  // goToDL converts a Go version like "1.2" to a DL module version like "v0.0.1-go1.2.linux-amd64".
   163  func goToDL(v, goos, goarch string) string {
   164  	return "v0.0.1-go" + v + ".linux-amd64"
   165  }
   166  
   167  // dlToGo converts a DL module version like "v0.0.1-go1.2.linux-amd64" to a Go version like "1.2".
   168  func dlToGo(v string) (string, bool) {
   169  	// v0.0.1-go1.19.7.windows-amd64
   170  	// cut v0.0.1-
   171  	_, v, ok := strings.Cut(v, "-")
   172  	if !ok {
   173  		return "", false
   174  	}
   175  	// cut .windows-amd64
   176  	i := strings.LastIndex(v, ".")
   177  	if i < 0 || !strings.Contains(v[i+1:], "-") {
   178  		return "", false
   179  	}
   180  	return strings.TrimPrefix(v[:i], "go"), true
   181  }
   182  

View as plain text