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