summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/modfetch/toolchain.go
blob: 0d7cfcfe7d10734caa8c4616cf7492b13b21a855 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 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"
	"sort"
	"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)
		}
	}

	// Always include our own version.
	// This means that the development branch of Go 1.21 (say) will allow 'go get go@1.21'
	// even though there are no Go 1.21 releases yet.
	// Once there is a release, 1.21 will be treated as a query matching the latest available release.
	// Before then, 1.21 will be treated as a query that resolves to this entry we are adding (1.21).
	if v := gover.Local(); !have[v] {
		list = append(list, goPrefix+v)
	}

	if r.path == "go" {
		sort.Slice(list, func(i, j int) bool {
			return gover.Compare(list[i], list[j]) < 0
		})
	} else {
		sort.Slice(list, func(i, j int) bool {
			return gover.Compare(gover.FromToolchain(list[i]), gover.FromToolchain(list[j])) < 0
		})
	}
	versions.List = list
	return versions, nil
}

func (r *toolchainRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) {
	// Convert rev to DL version and stat that to make sure it exists.
	// In theory the go@ versions should be like 1.21.0
	// and the toolchain@ versions should be like go1.21.0
	// but people will type the wrong one, and so we accept
	// both and silently correct it to the standard form.
	prefix := ""
	v := rev
	v = strings.TrimPrefix(v, "go")
	if r.path == "toolchain" {
		prefix = "go"
	}

	if !gover.IsValid(v) {
		return nil, fmt.Errorf("invalid %s version %s", r.path, rev)
	}

	// 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(v, gover.Local()) <= 0 {
		return &RevInfo{Version: prefix + v}, nil
	}

	// Similarly, if we're asking about *exactly* the current toolchain,
	// we don't need to access the network to know that it exists.
	if r.path == "toolchain" && v == gover.Local() {
		return &RevInfo{Version: prefix + v}, nil
	}

	if gover.IsLang(v) {
		// We can only use a language (development) version if the current toolchain
		// implements that version, and the two checks above have ruled that out.
		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
}