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