1
2
3
4
5 package toolchain
6
7 import (
8 "context"
9 "fmt"
10 "os"
11 "path/filepath"
12 "sort"
13 "strings"
14
15 "cmd/go/internal/base"
16 "cmd/go/internal/cfg"
17 "cmd/go/internal/gover"
18 "cmd/go/internal/modfetch"
19 "cmd/go/internal/modload"
20 "cmd/internal/telemetry/counter"
21 )
22
23
24
25
26
27
28
29
30
31
32
33
34 type Switcher struct {
35 TooNew *gover.TooNewError
36 Errors []error
37 loaderstate *modload.State
38 }
39
40 func NewSwitcher(s *modload.State) *Switcher {
41 sw := new(Switcher)
42 sw.loaderstate = s
43 return sw
44 }
45
46
47
48 func (s *Switcher) Error(err error) {
49 s.Errors = append(s.Errors, err)
50 s.addTooNew(err)
51 }
52
53
54 func (s *Switcher) addTooNew(err error) {
55 switch err := err.(type) {
56 case interface{ Unwrap() []error }:
57 for _, e := range err.Unwrap() {
58 s.addTooNew(e)
59 }
60
61 case interface{ Unwrap() error }:
62 s.addTooNew(err.Unwrap())
63
64 case *gover.TooNewError:
65 if s.TooNew == nil ||
66 gover.Compare(err.GoVersion, s.TooNew.GoVersion) > 0 ||
67 gover.Compare(err.GoVersion, s.TooNew.GoVersion) == 0 && err.What < s.TooNew.What {
68 s.TooNew = err
69 }
70 }
71 }
72
73
74 func (s *Switcher) NeedSwitch() bool {
75 return s.TooNew != nil && (HasAuto() || HasPath())
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 func (s *Switcher) Switch(ctx context.Context) {
92 if !s.NeedSwitch() {
93 for _, err := range s.Errors {
94 base.Error(err)
95 }
96 return
97 }
98
99
100 tv, err := NewerToolchain(ctx, s.loaderstate.Fetcher(), s.TooNew.GoVersion)
101 if err != nil {
102 for _, err := range s.Errors {
103 base.Error(err)
104 }
105 base.Error(fmt.Errorf("switching to go >= %v: %w", s.TooNew.GoVersion, err))
106 return
107 }
108
109 fmt.Fprintf(os.Stderr, "go: %v requires go >= %v; switching to %v\n", s.TooNew.What, s.TooNew.GoVersion, tv)
110 counterSwitchExec.Inc()
111 Exec(s.loaderstate, tv)
112 panic("unreachable")
113 }
114
115 var counterSwitchExec = counter.New("go/toolchain/switch-exec")
116
117
118
119 func SwitchOrFatal(loaderstate *modload.State, ctx context.Context, err error) {
120 s := NewSwitcher(loaderstate)
121 s.Error(err)
122 s.Switch(ctx)
123 base.Exit()
124 }
125
126
127
128
129
130
131
132
133 func NewerToolchain(ctx context.Context, f *modfetch.Fetcher, version string) (string, error) {
134 fetch := func(ctx context.Context) ([]string, error) {
135 return autoToolchains(ctx, f)
136 }
137
138 if !HasAuto() {
139 fetch = pathToolchains
140 }
141 list, err := fetch(ctx)
142 if err != nil {
143 return "", err
144 }
145 return newerToolchain(version, list)
146 }
147
148
149 func autoToolchains(ctx context.Context, f *modfetch.Fetcher) ([]string, error) {
150 var versions *modfetch.Versions
151 err := modfetch.TryProxies(func(proxy string) error {
152 v, err := f.Lookup(ctx, proxy, "go").Versions(ctx, "")
153 if err != nil {
154 return err
155 }
156 versions = v
157 return nil
158 })
159 if err != nil {
160 return nil, err
161 }
162 return versions.List, nil
163 }
164
165
166 func pathToolchains(ctx context.Context) ([]string, error) {
167 have := make(map[string]bool)
168 var list []string
169 for _, dir := range pathDirs() {
170 if dir == "" || !filepath.IsAbs(dir) {
171
172 continue
173 }
174 entries, err := os.ReadDir(dir)
175 if err != nil {
176 continue
177 }
178 for _, de := range entries {
179 if de.IsDir() || !strings.HasPrefix(de.Name(), "go1.") {
180 continue
181 }
182 info, err := de.Info()
183 if err != nil {
184 continue
185 }
186 v, ok := pathVersion(dir, de, info)
187 if !ok || !strings.HasPrefix(v, "1.") || have[v] {
188 continue
189 }
190 have[v] = true
191 list = append(list, v)
192 }
193 }
194 sort.Slice(list, func(i, j int) bool {
195 return gover.Compare(list[i], list[j]) < 0
196 })
197 return list, nil
198 }
199
200
201
202 func newerToolchain(need string, list []string) (string, error) {
203
204
205
206
207
208
209
210
211
212
213 latest := ""
214 for i := len(list) - 1; i >= 0; i-- {
215 v := list[i]
216 if gover.Compare(v, need) < 0 {
217 break
218 }
219 if gover.Lang(latest) == gover.Lang(v) {
220 continue
221 }
222 newer := latest
223 latest = v
224 if newer != "" && !gover.IsPrerelease(newer) {
225
226
227 break
228 }
229 }
230 if latest == "" {
231 return "", fmt.Errorf("no releases found for go >= %v", need)
232 }
233 return "go" + latest, nil
234 }
235
236
237 func HasAuto() bool {
238 env := cfg.Getenv("GOTOOLCHAIN")
239 return env == "auto" || strings.HasSuffix(env, "+auto")
240 }
241
242
243 func HasPath() bool {
244 env := cfg.Getenv("GOTOOLCHAIN")
245 return env == "path" || strings.HasSuffix(env, "+path")
246 }
247
View as plain text