]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/workcmd/use.go
be90989dddcf744ce21e0f22e5f9c6c53109ec6e
[gostls13.git] / src / cmd / go / internal / workcmd / use.go
1 // Copyright 2021 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 // go work use
6
7 package workcmd
8
9 import (
10         "cmd/go/internal/base"
11         "cmd/go/internal/fsys"
12         "cmd/go/internal/modload"
13         "cmd/go/internal/str"
14         "context"
15         "fmt"
16         "io/fs"
17         "os"
18         "path/filepath"
19 )
20
21 var cmdUse = &base.Command{
22         UsageLine: "go work use [-r] moddirs",
23         Short:     "add modules to workspace file",
24         Long: `Use provides a command-line interface for adding
25 directories, optionally recursively, to a go.work file.
26
27 A use directive will be added to the go.work file for each argument
28 directory listed on the command line go.work file, if it exists on disk,
29 or removed from the go.work file if it does not exist on disk.
30
31 The -r flag searches recursively for modules in the argument
32 directories, and the use command operates as if each of the directories
33 were specified as arguments: namely, use directives will be added for
34 directories that exist, and removed for directories that do not exist.
35
36 See the workspaces reference at https://go.dev/ref/mod#workspaces
37 for more information.
38 `,
39 }
40
41 var useR = cmdUse.Flag.Bool("r", false, "")
42
43 func init() {
44         cmdUse.Run = runUse // break init cycle
45
46         base.AddChdirFlag(&cmdUse.Flag)
47         base.AddModCommonFlags(&cmdUse.Flag)
48 }
49
50 func runUse(ctx context.Context, cmd *base.Command, args []string) {
51         modload.ForceUseModules = true
52
53         var gowork string
54         modload.InitWorkfile()
55         gowork = modload.WorkFilePath()
56
57         if gowork == "" {
58                 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
59         }
60         workFile, err := modload.ReadWorkFile(gowork)
61         if err != nil {
62                 base.Fatalf("go: %v", err)
63         }
64         workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute.
65
66         haveDirs := make(map[string][]string) // absolute → original(s)
67         for _, use := range workFile.Use {
68                 var abs string
69                 if filepath.IsAbs(use.Path) {
70                         abs = filepath.Clean(use.Path)
71                 } else {
72                         abs = filepath.Join(workDir, use.Path)
73                 }
74                 haveDirs[abs] = append(haveDirs[abs], use.Path)
75         }
76
77         // keepDirs maps each absolute path to keep to the literal string to use for
78         // that path (either an absolute or a relative path), or the empty string if
79         // all entries for the absolute path should be removed.
80         keepDirs := make(map[string]string)
81
82         // lookDir updates the entry in keepDirs for the directory dir,
83         // which is either absolute or relative to the current working directory
84         // (not necessarily the directory containing the workfile).
85         lookDir := func(dir string) {
86                 absDir, dir := pathRel(workDir, dir)
87
88                 fi, err := fsys.Stat(filepath.Join(absDir, "go.mod"))
89                 if err != nil {
90                         if os.IsNotExist(err) {
91                                 keepDirs[absDir] = ""
92                         } else {
93                                 base.Errorf("go: %v", err)
94                         }
95                         return
96                 }
97
98                 if !fi.Mode().IsRegular() {
99                         base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod"))
100                 }
101
102                 if dup := keepDirs[absDir]; dup != "" && dup != dir {
103                         base.Errorf(`go: already added "%s" as "%s"`, dir, dup)
104                 }
105                 keepDirs[absDir] = dir
106         }
107
108         if len(args) == 0 {
109                 base.Fatalf("go: 'go work use' requires one or more directory arguments")
110         }
111         for _, useDir := range args {
112                 absArg, _ := pathRel(workDir, useDir)
113
114                 info, err := fsys.Stat(absArg)
115                 if err != nil {
116                         // Errors raised from os.Stat are formatted to be more user-friendly.
117                         if os.IsNotExist(err) {
118                                 base.Errorf("go: directory %v does not exist", absArg)
119                         } else {
120                                 base.Errorf("go: %v", err)
121                         }
122                         continue
123                 } else if !info.IsDir() {
124                         base.Errorf("go: %s is not a directory", absArg)
125                         continue
126                 }
127
128                 if !*useR {
129                         lookDir(useDir)
130                         continue
131                 }
132
133                 // Add or remove entries for any subdirectories that still exist.
134                 fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error {
135                         if err != nil {
136                                 return err
137                         }
138
139                         if !info.IsDir() {
140                                 if info.Mode()&fs.ModeSymlink != 0 {
141                                         if target, err := fsys.Stat(path); err == nil && target.IsDir() {
142                                                 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
143                                         }
144                                 }
145                                 return nil
146                         }
147                         lookDir(path)
148                         return nil
149                 })
150
151                 // Remove entries for subdirectories that no longer exist.
152                 // Because they don't exist, they will be skipped by Walk.
153                 for absDir := range haveDirs {
154                         if str.HasFilePathPrefix(absDir, absArg) {
155                                 if _, ok := keepDirs[absDir]; !ok {
156                                         keepDirs[absDir] = "" // Mark for deletion.
157                                 }
158                         }
159                 }
160         }
161
162         base.ExitIfErrors()
163
164         for absDir, keepDir := range keepDirs {
165                 nKept := 0
166                 for _, dir := range haveDirs[absDir] {
167                         if dir == keepDir { // (note that dir is always non-empty)
168                                 nKept++
169                         } else {
170                                 workFile.DropUse(dir)
171                         }
172                 }
173                 if keepDir != "" && nKept != 1 {
174                         // If we kept more than one copy, delete them all.
175                         // We'll recreate a unique copy with AddUse.
176                         if nKept > 1 {
177                                 workFile.DropUse(keepDir)
178                         }
179                         workFile.AddUse(keepDir, "")
180                 }
181         }
182         modload.UpdateWorkFile(workFile)
183         modload.WriteWorkFile(gowork, workFile)
184 }
185
186 // pathRel returns the absolute and canonical forms of dir for use in a
187 // go.work file located in directory workDir.
188 //
189 // If dir is relative, it is intepreted relative to base.Cwd()
190 // and its canonical form is relative to workDir if possible.
191 // If dir is absolute or cannot be made relative to workDir,
192 // its canonical form is absolute.
193 //
194 // Canonical absolute paths are clean.
195 // Canonical relative paths are clean and slash-separated.
196 func pathRel(workDir, dir string) (abs, canonical string) {
197         if filepath.IsAbs(dir) {
198                 abs = filepath.Clean(dir)
199                 return abs, abs
200         }
201
202         abs = filepath.Join(base.Cwd(), dir)
203         rel, err := filepath.Rel(workDir, abs)
204         if err != nil {
205                 // The path can't be made relative to the go.work file,
206                 // so it must be kept absolute instead.
207                 return abs, abs
208         }
209
210         // Normalize relative paths to use slashes, so that checked-in go.work
211         // files with relative paths within the repo are platform-independent.
212         return abs, modload.ToDirectoryPath(rel)
213 }