github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modfetch/toolchain.go (about) 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 5 package modfetch 6 7 import ( 8 "context" 9 "fmt" 10 "io" 11 "sort" 12 "strings" 13 14 "github.com/octohelm/cuemod/internal/cmd/go/internals/gover" 15 "github.com/octohelm/cuemod/internal/cmd/go/internals/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. 30 type toolchainRepo struct { 31 path string // either "go" or "toolchain" 32 repo Repo // underlying DL repo 33 } 34 35 func (r *toolchainRepo) ModulePath() string { 36 return r.path 37 } 38 39 func (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 go@1.21' 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 85 func (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 136 func (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 150 func (r *toolchainRepo) GoMod(ctx context.Context, version string) (data []byte, err error) { 151 return []byte("module " + r.path + "\n"), nil 152 } 153 154 func (r *toolchainRepo) Zip(ctx context.Context, dst io.Writer, version string) error { 155 return fmt.Errorf("invalid use of toolchainRepo: Zip") 156 } 157 158 func (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". 163 func 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". 168 func 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 }