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
5package modfetch
6
7import (
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.
30type toolchainRepo struct {
31	path string // either "go" or "toolchain"
32	repo Repo   // underlying DL repo
33}
34
35func (r *toolchainRepo) ModulePath() string {
36	return r.path
37}
38
39func (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 [email protected]'
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
85func (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
136func (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
150func (r *toolchainRepo) GoMod(ctx context.Context, version string) (data []byte, err error) {
151	return []byte("module " + r.path + "\n"), nil
152}
153
154func (r *toolchainRepo) Zip(ctx context.Context, dst io.Writer, version string) error {
155	return fmt.Errorf("invalid use of toolchainRepo: Zip")
156}
157
158func (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".
163func 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".
168func 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