complex-lowering: Better handling of PAREN_EXPR [PR68855]
[official-gcc.git] / libgo / go / cmd / go / internal / modfetch / codehost / codehost.go
blob4a0e2241e50ac828a92c5add57d174faa70ba6a9
1 // Copyright 2018 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.
5 // Package codehost defines the interface implemented by a code hosting source,
6 // along with support code for use by implementations.
7 package codehost
9 import (
10 "bytes"
11 "crypto/sha256"
12 "fmt"
13 exec "internal/execabs"
14 "io"
15 "io/fs"
16 "os"
17 "path/filepath"
18 "strings"
19 "sync"
20 "time"
22 "cmd/go/internal/cfg"
23 "cmd/go/internal/lockedfile"
24 "cmd/go/internal/str"
27 // Downloaded size limits.
28 const (
29 MaxGoMod = 16 << 20 // maximum size of go.mod file
30 MaxLICENSE = 16 << 20 // maximum size of LICENSE file
31 MaxZipFile = 500 << 20 // maximum size of downloaded zip file
34 // A Repo represents a code hosting source.
35 // Typical implementations include local version control repositories,
36 // remote version control servers, and code hosting sites.
37 // A Repo must be safe for simultaneous use by multiple goroutines.
38 type Repo interface {
39 // List lists all tags with the given prefix.
40 Tags(prefix string) (tags []string, err error)
42 // Stat returns information about the revision rev.
43 // A revision can be any identifier known to the underlying service:
44 // commit hash, branch, tag, and so on.
45 Stat(rev string) (*RevInfo, error)
47 // Latest returns the latest revision on the default branch,
48 // whatever that means in the underlying implementation.
49 Latest() (*RevInfo, error)
51 // ReadFile reads the given file in the file tree corresponding to revision rev.
52 // It should refuse to read more than maxSize bytes.
54 // If the requested file does not exist it should return an error for which
55 // os.IsNotExist(err) returns true.
56 ReadFile(rev, file string, maxSize int64) (data []byte, err error)
58 // ReadZip downloads a zip file for the subdir subdirectory
59 // of the given revision to a new file in a given temporary directory.
60 // It should refuse to read more than maxSize bytes.
61 // It returns a ReadCloser for a streamed copy of the zip file.
62 // All files in the zip file are expected to be
63 // nested in a single top-level directory, whose name is not specified.
64 ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)
66 // RecentTag returns the most recent tag on rev or one of its predecessors
67 // with the given prefix. allowed may be used to filter out unwanted versions.
68 RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error)
70 // DescendsFrom reports whether rev or any of its ancestors has the given tag.
72 // DescendsFrom must return true for any tag returned by RecentTag for the
73 // same revision.
74 DescendsFrom(rev, tag string) (bool, error)
77 // A Rev describes a single revision in a source code repository.
78 type RevInfo struct {
79 Name string // complete ID in underlying repository
80 Short string // shortened ID, for use in pseudo-version
81 Version string // version used in lookup
82 Time time.Time // commit time
83 Tags []string // known tags for commit
86 // A FileRev describes the result of reading a file at a given revision.
87 type FileRev struct {
88 Rev string // requested revision
89 Data []byte // file data
90 Err error // error if any; os.IsNotExist(Err)==true if rev exists but file does not exist in that rev
93 // UnknownRevisionError is an error equivalent to fs.ErrNotExist, but for a
94 // revision rather than a file.
95 type UnknownRevisionError struct {
96 Rev string
99 func (e *UnknownRevisionError) Error() string {
100 return "unknown revision " + e.Rev
102 func (UnknownRevisionError) Is(err error) bool {
103 return err == fs.ErrNotExist
106 // ErrNoCommits is an error equivalent to fs.ErrNotExist indicating that a given
107 // repository or module contains no commits.
108 var ErrNoCommits error = noCommitsError{}
110 type noCommitsError struct{}
112 func (noCommitsError) Error() string {
113 return "no commits"
115 func (noCommitsError) Is(err error) bool {
116 return err == fs.ErrNotExist
119 // AllHex reports whether the revision rev is entirely lower-case hexadecimal digits.
120 func AllHex(rev string) bool {
121 for i := 0; i < len(rev); i++ {
122 c := rev[i]
123 if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' {
124 continue
126 return false
128 return true
131 // ShortenSHA1 shortens a SHA1 hash (40 hex digits) to the canonical length
132 // used in pseudo-versions (12 hex digits).
133 func ShortenSHA1(rev string) string {
134 if AllHex(rev) && len(rev) == 40 {
135 return rev[:12]
137 return rev
140 // WorkDir returns the name of the cached work directory to use for the
141 // given repository type and name.
142 func WorkDir(typ, name string) (dir, lockfile string, err error) {
143 if cfg.GOMODCACHE == "" {
144 return "", "", fmt.Errorf("neither GOPATH nor GOMODCACHE are set")
147 // We name the work directory for the SHA256 hash of the type and name.
148 // We intentionally avoid the actual name both because of possible
149 // conflicts with valid file system paths and because we want to ensure
150 // that one checkout is never nested inside another. That nesting has
151 // led to security problems in the past.
152 if strings.Contains(typ, ":") {
153 return "", "", fmt.Errorf("codehost.WorkDir: type cannot contain colon")
155 key := typ + ":" + name
156 dir = filepath.Join(cfg.GOMODCACHE, "cache/vcs", fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
158 if cfg.BuildX {
159 fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", filepath.Dir(dir), typ, name)
161 if err := os.MkdirAll(filepath.Dir(dir), 0777); err != nil {
162 return "", "", err
165 lockfile = dir + ".lock"
166 if cfg.BuildX {
167 fmt.Fprintf(os.Stderr, "# lock %s", lockfile)
170 unlock, err := lockedfile.MutexAt(lockfile).Lock()
171 if err != nil {
172 return "", "", fmt.Errorf("codehost.WorkDir: can't find or create lock file: %v", err)
174 defer unlock()
176 data, err := os.ReadFile(dir + ".info")
177 info, err2 := os.Stat(dir)
178 if err == nil && err2 == nil && info.IsDir() {
179 // Info file and directory both already exist: reuse.
180 have := strings.TrimSuffix(string(data), "\n")
181 if have != key {
182 return "", "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key)
184 if cfg.BuildX {
185 fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name)
187 return dir, lockfile, nil
190 // Info file or directory missing. Start from scratch.
191 if cfg.BuildX {
192 fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name)
194 os.RemoveAll(dir)
195 if err := os.MkdirAll(dir, 0777); err != nil {
196 return "", "", err
198 if err := os.WriteFile(dir+".info", []byte(key), 0666); err != nil {
199 os.RemoveAll(dir)
200 return "", "", err
202 return dir, lockfile, nil
205 type RunError struct {
206 Cmd string
207 Err error
208 Stderr []byte
209 HelpText string
212 func (e *RunError) Error() string {
213 text := e.Cmd + ": " + e.Err.Error()
214 stderr := bytes.TrimRight(e.Stderr, "\n")
215 if len(stderr) > 0 {
216 text += ":\n\t" + strings.ReplaceAll(string(stderr), "\n", "\n\t")
218 if len(e.HelpText) > 0 {
219 text += "\n" + e.HelpText
221 return text
224 var dirLock sync.Map
226 // Run runs the command line in the given directory
227 // (an empty dir means the current directory).
228 // It returns the standard output and, for a non-zero exit,
229 // a *RunError indicating the command, exit status, and standard error.
230 // Standard error is unavailable for commands that exit successfully.
231 func Run(dir string, cmdline ...any) ([]byte, error) {
232 return RunWithStdin(dir, nil, cmdline...)
235 // bashQuoter escapes characters that have special meaning in double-quoted strings in the bash shell.
236 // See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html.
237 var bashQuoter = strings.NewReplacer(`"`, `\"`, `$`, `\$`, "`", "\\`", `\`, `\\`)
239 func RunWithStdin(dir string, stdin io.Reader, cmdline ...any) ([]byte, error) {
240 if dir != "" {
241 muIface, ok := dirLock.Load(dir)
242 if !ok {
243 muIface, _ = dirLock.LoadOrStore(dir, new(sync.Mutex))
245 mu := muIface.(*sync.Mutex)
246 mu.Lock()
247 defer mu.Unlock()
250 cmd := str.StringList(cmdline...)
251 if os.Getenv("TESTGOVCS") == "panic" {
252 panic(fmt.Sprintf("use of vcs: %v", cmd))
254 if cfg.BuildX {
255 text := new(strings.Builder)
256 if dir != "" {
257 text.WriteString("cd ")
258 text.WriteString(dir)
259 text.WriteString("; ")
261 for i, arg := range cmd {
262 if i > 0 {
263 text.WriteByte(' ')
265 switch {
266 case strings.ContainsAny(arg, "'"):
267 // Quote args that could be mistaken for quoted args.
268 text.WriteByte('"')
269 text.WriteString(bashQuoter.Replace(arg))
270 text.WriteByte('"')
271 case strings.ContainsAny(arg, "$`\\*?[\"\t\n\v\f\r \u0085\u00a0"):
272 // Quote args that contain special characters, glob patterns, or spaces.
273 text.WriteByte('\'')
274 text.WriteString(arg)
275 text.WriteByte('\'')
276 default:
277 text.WriteString(arg)
280 fmt.Fprintf(os.Stderr, "%s\n", text)
281 start := time.Now()
282 defer func() {
283 fmt.Fprintf(os.Stderr, "%.3fs # %s\n", time.Since(start).Seconds(), text)
286 // TODO: Impose limits on command output size.
287 // TODO: Set environment to get English error messages.
288 var stderr bytes.Buffer
289 var stdout bytes.Buffer
290 c := exec.Command(cmd[0], cmd[1:]...)
291 c.Dir = dir
292 c.Stdin = stdin
293 c.Stderr = &stderr
294 c.Stdout = &stdout
295 err := c.Run()
296 if err != nil {
297 err = &RunError{Cmd: strings.Join(cmd, " ") + " in " + dir, Stderr: stderr.Bytes(), Err: err}
299 return stdout.Bytes(), err