.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / internal / lsp / fake / sandbox.go
1 // Copyright 2020 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 fake
6
7 import (
8         "context"
9         "fmt"
10         "io/ioutil"
11         "os"
12         "path/filepath"
13         "strings"
14
15         "golang.org/x/tools/internal/gocommand"
16         "golang.org/x/tools/internal/testenv"
17         "golang.org/x/tools/txtar"
18         errors "golang.org/x/xerrors"
19 )
20
21 // Sandbox holds a collection of temporary resources to use for working with Go
22 // code in tests.
23 type Sandbox struct {
24         gopath  string
25         rootdir string
26         goproxy string
27         Workdir *Workdir
28 }
29
30 // SandboxConfig controls the behavior of a test sandbox. The zero value
31 // defines a reasonable default.
32 type SandboxConfig struct {
33         // RootDir sets the base directory to use when creating temporary
34         // directories. If not specified, defaults to a new temporary directory.
35         RootDir string
36         // Files holds a txtar-encoded archive of files to populate the initial state
37         // of the working directory.
38         //
39         // For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with
40         // the sandbox's resolved working directory before writing files.
41         Files string
42         // InGoPath specifies that the working directory should be within the
43         // temporary GOPATH.
44         InGoPath bool
45         // Workdir configures the working directory of the Sandbox. It behaves as
46         // follows:
47         //  - if set to an absolute path, use that path as the working directory.
48         //  - if set to a relative path, create and use that path relative to the
49         //    sandbox.
50         //  - if unset, default to a the 'work' subdirectory of the sandbox.
51         //
52         // This option is incompatible with InGoPath or Files.
53         Workdir string
54
55         // ProxyFiles holds a txtar-encoded archive of files to populate a file-based
56         // Go proxy.
57         ProxyFiles string
58         // GOPROXY is the explicit GOPROXY value that should be used for the sandbox.
59         //
60         // This option is incompatible with ProxyFiles.
61         GOPROXY string
62 }
63
64 // NewSandbox creates a collection of named temporary resources, with a
65 // working directory populated by the txtar-encoded content in srctxt, and a
66 // file-based module proxy populated with the txtar-encoded content in
67 // proxytxt.
68 //
69 // If rootDir is non-empty, it will be used as the root of temporary
70 // directories created for the sandbox. Otherwise, a new temporary directory
71 // will be used as root.
72 func NewSandbox(config *SandboxConfig) (_ *Sandbox, err error) {
73         if config == nil {
74                 config = new(SandboxConfig)
75         }
76         if err := validateConfig(*config); err != nil {
77                 return nil, fmt.Errorf("invalid SandboxConfig: %v", err)
78         }
79
80         sb := &Sandbox{}
81         defer func() {
82                 // Clean up if we fail at any point in this constructor.
83                 if err != nil {
84                         sb.Close()
85                 }
86         }()
87
88         rootDir := config.RootDir
89         if rootDir == "" {
90                 rootDir, err = ioutil.TempDir(config.RootDir, "gopls-sandbox-")
91                 if err != nil {
92                         return nil, fmt.Errorf("creating temporary workdir: %v", err)
93                 }
94         }
95         sb.rootdir = rootDir
96         sb.gopath = filepath.Join(sb.rootdir, "gopath")
97         if err := os.Mkdir(sb.gopath, 0755); err != nil {
98                 return nil, err
99         }
100         if config.GOPROXY != "" {
101                 sb.goproxy = config.GOPROXY
102         } else {
103                 proxydir := filepath.Join(sb.rootdir, "proxy")
104                 if err := os.Mkdir(proxydir, 0755); err != nil {
105                         return nil, err
106                 }
107                 sb.goproxy, err = WriteProxy(proxydir, config.ProxyFiles)
108                 if err != nil {
109                         return nil, err
110                 }
111         }
112         // Short-circuit writing the workdir if we're given an absolute path, since
113         // this is used for running in an existing directory.
114         // TODO(findleyr): refactor this to be less of a workaround.
115         if filepath.IsAbs(config.Workdir) {
116                 sb.Workdir = NewWorkdir(config.Workdir)
117                 return sb, nil
118         }
119         var workdir string
120         if config.Workdir == "" {
121                 if config.InGoPath {
122                         // Set the working directory as $GOPATH/src.
123                         workdir = filepath.Join(sb.gopath, "src")
124                 } else if workdir == "" {
125                         workdir = filepath.Join(sb.rootdir, "work")
126                 }
127         } else {
128                 // relative path
129                 workdir = filepath.Join(sb.rootdir, config.Workdir)
130         }
131         if err := os.MkdirAll(workdir, 0755); err != nil {
132                 return nil, err
133         }
134         sb.Workdir = NewWorkdir(workdir)
135         if err := sb.Workdir.writeInitialFiles(config.Files); err != nil {
136                 return nil, err
137         }
138         return sb, nil
139 }
140
141 // Tempdir creates a new temp directory with the given txtar-encoded files. It
142 // is the responsibility of the caller to call os.RemoveAll on the returned
143 // file path when it is no longer needed.
144 func Tempdir(txt string) (string, error) {
145         dir, err := ioutil.TempDir("", "gopls-tempdir-")
146         if err != nil {
147                 return "", err
148         }
149         files := unpackTxt(txt)
150         for name, data := range files {
151                 if err := WriteFileData(name, data, RelativeTo(dir)); err != nil {
152                         return "", errors.Errorf("writing to tempdir: %w", err)
153                 }
154         }
155         return dir, nil
156 }
157
158 func unpackTxt(txt string) map[string][]byte {
159         dataMap := make(map[string][]byte)
160         archive := txtar.Parse([]byte(txt))
161         for _, f := range archive.Files {
162                 dataMap[f.Name] = f.Data
163         }
164         return dataMap
165 }
166
167 func validateConfig(config SandboxConfig) error {
168         if filepath.IsAbs(config.Workdir) && (config.Files != "" || config.InGoPath) {
169                 return errors.New("absolute Workdir cannot be set in conjunction with Files or InGoPath")
170         }
171         if config.Workdir != "" && config.InGoPath {
172                 return errors.New("Workdir cannot be set in conjunction with InGoPath")
173         }
174         if config.GOPROXY != "" && config.ProxyFiles != "" {
175                 return errors.New("GOPROXY cannot be set in conjunction with ProxyFiles")
176         }
177         return nil
178 }
179
180 // splitModuleVersionPath extracts module information from files stored in the
181 // directory structure modulePath@version/suffix.
182 // For example:
183 //  splitModuleVersionPath("mod.com@v1.2.3/package") = ("mod.com", "v1.2.3", "package")
184 func splitModuleVersionPath(path string) (modulePath, version, suffix string) {
185         parts := strings.Split(path, "/")
186         var modulePathParts []string
187         for i, p := range parts {
188                 if strings.Contains(p, "@") {
189                         mv := strings.SplitN(p, "@", 2)
190                         modulePathParts = append(modulePathParts, mv[0])
191                         return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/")
192                 }
193                 modulePathParts = append(modulePathParts, p)
194         }
195         // Default behavior: this is just a module path.
196         return path, "", ""
197 }
198
199 func (sb *Sandbox) RootDir() string {
200         return sb.rootdir
201 }
202
203 // GOPATH returns the value of the Sandbox GOPATH.
204 func (sb *Sandbox) GOPATH() string {
205         return sb.gopath
206 }
207
208 // GoEnv returns the default environment variables that can be used for
209 // invoking Go commands in the sandbox.
210 func (sb *Sandbox) GoEnv() map[string]string {
211         vars := map[string]string{
212                 "GOPATH":           sb.GOPATH(),
213                 "GOPROXY":          sb.goproxy,
214                 "GO111MODULE":      "",
215                 "GOSUMDB":          "off",
216                 "GOPACKAGESDRIVER": "off",
217         }
218         if testenv.Go1Point() >= 5 {
219                 vars["GOMODCACHE"] = ""
220         }
221         return vars
222 }
223
224 // RunGoCommand executes a go command in the sandbox.
225 func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args []string) error {
226         var vars []string
227         for k, v := range sb.GoEnv() {
228                 vars = append(vars, fmt.Sprintf("%s=%s", k, v))
229         }
230         inv := gocommand.Invocation{
231                 Verb: verb,
232                 Args: args,
233                 Env:  vars,
234         }
235         // Use the provided directory for the working directory, if available.
236         // sb.Workdir may be nil if we exited the constructor with errors (we call
237         // Close to clean up any partial state from the constructor, which calls
238         // RunGoCommand).
239         if dir != "" {
240                 inv.WorkingDir = sb.Workdir.AbsPath(dir)
241         } else if sb.Workdir != nil {
242                 inv.WorkingDir = string(sb.Workdir.RelativeTo)
243         }
244         gocmdRunner := &gocommand.Runner{}
245         stdout, stderr, _, err := gocmdRunner.RunRaw(ctx, inv)
246         if err != nil {
247                 return errors.Errorf("go command failed (stdout: %s) (stderr: %s): %v", stdout.String(), stderr.String(), err)
248         }
249         // Since running a go command may result in changes to workspace files,
250         // check if we need to send any any "watched" file events.
251         if sb.Workdir != nil {
252                 if err := sb.Workdir.CheckForFileChanges(ctx); err != nil {
253                         return errors.Errorf("checking for file changes: %w", err)
254                 }
255         }
256         return nil
257 }
258
259 // Close removes all state associated with the sandbox.
260 func (sb *Sandbox) Close() error {
261         var goCleanErr error
262         if sb.gopath != "" {
263                 goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"})
264         }
265         err := os.RemoveAll(sb.rootdir)
266         if err != nil || goCleanErr != nil {
267                 return fmt.Errorf("error(s) cleaning sandbox: cleaning modcache: %v; removing files: %v", goCleanErr, err)
268         }
269         return nil
270 }