github.com/acwpower/studygo@v0.0.0-20220210033106-9f3f6e60fdcc/pkg/mod/golang.org/x/tools@v0.1.1/refactor/rename/mvpkg.go (about) 1 // Copyright 2015 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 // This file contains the implementation of the 'gomvpkg' command 6 // whose main function is in golang.org/x/tools/cmd/gomvpkg. 7 8 package rename 9 10 // TODO(matloob): 11 // - think about what happens if the package is moving across version control systems. 12 // - dot imports are not supported. Make sure it's clearly documented. 13 14 import ( 15 "bytes" 16 "fmt" 17 "go/ast" 18 "go/build" 19 "go/format" 20 "go/token" 21 exec "golang.org/x/sys/execabs" 22 "log" 23 "os" 24 "path" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "strconv" 29 "strings" 30 "text/template" 31 32 "golang.org/x/tools/go/buildutil" 33 "golang.org/x/tools/go/loader" 34 "golang.org/x/tools/refactor/importgraph" 35 ) 36 37 // Move, given a package path and a destination package path, will try 38 // to move the given package to the new path. The Move function will 39 // first check for any conflicts preventing the move, such as a 40 // package already existing at the destination package path. If the 41 // move can proceed, it builds an import graph to find all imports of 42 // the packages whose paths need to be renamed. This includes uses of 43 // the subpackages of the package to be moved as those packages will 44 // also need to be moved. It then renames all imports to point to the 45 // new paths, and then moves the packages to their new paths. 46 func Move(ctxt *build.Context, from, to, moveTmpl string) error { 47 srcDir, err := srcDir(ctxt, from) 48 if err != nil { 49 return err 50 } 51 52 // This should be the only place in the program that constructs 53 // file paths. 54 fromDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(from)) 55 toDir := buildutil.JoinPath(ctxt, srcDir, filepath.FromSlash(to)) 56 toParent := filepath.Dir(toDir) 57 if !buildutil.IsDir(ctxt, toParent) { 58 return fmt.Errorf("parent directory does not exist for path %s", toDir) 59 } 60 61 // Build the import graph and figure out which packages to update. 62 _, rev, errors := importgraph.Build(ctxt) 63 if len(errors) > 0 { 64 // With a large GOPATH tree, errors are inevitable. 65 // Report them but proceed. 66 fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") 67 for path, err := range errors { 68 fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) 69 } 70 } 71 72 // Determine the affected packages---the set of packages whose import 73 // statements need updating. 74 affectedPackages := map[string]bool{from: true} 75 destinations := make(map[string]string) // maps old import path to new import path 76 for pkg := range subpackages(ctxt, srcDir, from) { 77 for r := range rev[pkg] { 78 affectedPackages[r] = true 79 } 80 destinations[pkg] = strings.Replace(pkg, from, to, 1) 81 } 82 83 // Load all the affected packages. 84 iprog, err := loadProgram(ctxt, affectedPackages) 85 if err != nil { 86 return err 87 } 88 89 // Prepare the move command, if one was supplied. 90 var cmd string 91 if moveTmpl != "" { 92 if cmd, err = moveCmd(moveTmpl, fromDir, toDir); err != nil { 93 return err 94 } 95 } 96 97 m := mover{ 98 ctxt: ctxt, 99 rev: rev, 100 iprog: iprog, 101 from: from, 102 to: to, 103 fromDir: fromDir, 104 toDir: toDir, 105 affectedPackages: affectedPackages, 106 destinations: destinations, 107 cmd: cmd, 108 } 109 110 if err := m.checkValid(); err != nil { 111 return err 112 } 113 114 m.move() 115 116 return nil 117 } 118 119 // srcDir returns the absolute path of the srcdir containing pkg. 120 func srcDir(ctxt *build.Context, pkg string) (string, error) { 121 for _, srcDir := range ctxt.SrcDirs() { 122 path := buildutil.JoinPath(ctxt, srcDir, pkg) 123 if buildutil.IsDir(ctxt, path) { 124 return srcDir, nil 125 } 126 } 127 return "", fmt.Errorf("src dir not found for package: %s", pkg) 128 } 129 130 // subpackages returns the set of packages in the given srcDir whose 131 // import path equals to root, or has "root/" as the prefix. 132 func subpackages(ctxt *build.Context, srcDir string, root string) map[string]bool { 133 var subs = make(map[string]bool) 134 buildutil.ForEachPackage(ctxt, func(pkg string, err error) { 135 if err != nil { 136 log.Fatalf("unexpected error in ForEachPackage: %v", err) 137 } 138 139 // Only process the package root, or a sub-package of it. 140 if !(strings.HasPrefix(pkg, root) && 141 (len(pkg) == len(root) || pkg[len(root)] == '/')) { 142 return 143 } 144 145 p, err := ctxt.Import(pkg, "", build.FindOnly) 146 if err != nil { 147 log.Fatalf("unexpected: package %s can not be located by build context: %s", pkg, err) 148 } 149 if p.SrcRoot == "" { 150 log.Fatalf("unexpected: could not determine srcDir for package %s: %s", pkg, err) 151 } 152 if p.SrcRoot != srcDir { 153 return 154 } 155 156 subs[pkg] = true 157 }) 158 return subs 159 } 160 161 type mover struct { 162 // iprog contains all packages whose contents need to be updated 163 // with new package names or import paths. 164 iprog *loader.Program 165 ctxt *build.Context 166 // rev is the reverse import graph. 167 rev importgraph.Graph 168 // from and to are the source and destination import 169 // paths. fromDir and toDir are the source and destination 170 // absolute paths that package source files will be moved between. 171 from, to, fromDir, toDir string 172 // affectedPackages is the set of all packages whose contents need 173 // to be updated to reflect new package names or import paths. 174 affectedPackages map[string]bool 175 // destinations maps each subpackage to be moved to its 176 // destination path. 177 destinations map[string]string 178 // cmd, if not empty, will be executed to move fromDir to toDir. 179 cmd string 180 } 181 182 func (m *mover) checkValid() error { 183 const prefix = "invalid move destination" 184 185 match, err := regexp.MatchString("^[_\\pL][_\\pL\\p{Nd}]*$", path.Base(m.to)) 186 if err != nil { 187 panic("regexp.MatchString failed") 188 } 189 if !match { 190 return fmt.Errorf("%s: %s; gomvpkg does not support move destinations "+ 191 "whose base names are not valid go identifiers", prefix, m.to) 192 } 193 194 if buildutil.FileExists(m.ctxt, m.toDir) { 195 return fmt.Errorf("%s: %s conflicts with file %s", prefix, m.to, m.toDir) 196 } 197 if buildutil.IsDir(m.ctxt, m.toDir) { 198 return fmt.Errorf("%s: %s conflicts with directory %s", prefix, m.to, m.toDir) 199 } 200 201 for _, toSubPkg := range m.destinations { 202 if _, err := m.ctxt.Import(toSubPkg, "", build.FindOnly); err == nil { 203 return fmt.Errorf("%s: %s; package or subpackage %s already exists", 204 prefix, m.to, toSubPkg) 205 } 206 } 207 208 return nil 209 } 210 211 // moveCmd produces the version control move command used to move fromDir to toDir by 212 // executing the given template. 213 func moveCmd(moveTmpl, fromDir, toDir string) (string, error) { 214 tmpl, err := template.New("movecmd").Parse(moveTmpl) 215 if err != nil { 216 return "", err 217 } 218 219 var buf bytes.Buffer 220 err = tmpl.Execute(&buf, struct { 221 Src string 222 Dst string 223 }{fromDir, toDir}) 224 return buf.String(), err 225 } 226 227 func (m *mover) move() error { 228 filesToUpdate := make(map[*ast.File]bool) 229 230 // Change the moved package's "package" declaration to its new base name. 231 pkg, ok := m.iprog.Imported[m.from] 232 if !ok { 233 log.Fatalf("unexpected: package %s is not in import map", m.from) 234 } 235 newName := filepath.Base(m.to) 236 for _, f := range pkg.Files { 237 // Update all import comments. 238 for _, cg := range f.Comments { 239 c := cg.List[0] 240 if c.Slash >= f.Name.End() && 241 sameLine(m.iprog.Fset, c.Slash, f.Name.End()) && 242 (f.Decls == nil || c.Slash < f.Decls[0].Pos()) { 243 if strings.HasPrefix(c.Text, `// import "`) { 244 c.Text = `// import "` + m.to + `"` 245 break 246 } 247 if strings.HasPrefix(c.Text, `/* import "`) { 248 c.Text = `/* import "` + m.to + `" */` 249 break 250 } 251 } 252 } 253 f.Name.Name = newName // change package decl 254 filesToUpdate[f] = true 255 } 256 257 // Look through the external test packages (m.iprog.Created contains the external test packages). 258 for _, info := range m.iprog.Created { 259 // Change the "package" declaration of the external test package. 260 if info.Pkg.Path() == m.from+"_test" { 261 for _, f := range info.Files { 262 f.Name.Name = newName + "_test" // change package decl 263 filesToUpdate[f] = true 264 } 265 } 266 267 // Mark all the loaded external test packages, which import the "from" package, 268 // as affected packages and update the imports. 269 for _, imp := range info.Pkg.Imports() { 270 if imp.Path() == m.from { 271 m.affectedPackages[info.Pkg.Path()] = true 272 m.iprog.Imported[info.Pkg.Path()] = info 273 if err := importName(m.iprog, info, m.from, path.Base(m.from), newName); err != nil { 274 return err 275 } 276 } 277 } 278 } 279 280 // Update imports of that package to use the new import name. 281 // None of the subpackages will change their name---only the from package 282 // itself will. 283 for p := range m.rev[m.from] { 284 if err := importName(m.iprog, m.iprog.Imported[p], m.from, path.Base(m.from), newName); err != nil { 285 return err 286 } 287 } 288 289 // Update import paths for all imports by affected packages. 290 for ap := range m.affectedPackages { 291 info, ok := m.iprog.Imported[ap] 292 if !ok { 293 log.Fatalf("unexpected: package %s is not in import map", ap) 294 } 295 for _, f := range info.Files { 296 for _, imp := range f.Imports { 297 importPath, _ := strconv.Unquote(imp.Path.Value) 298 if newPath, ok := m.destinations[importPath]; ok { 299 imp.Path.Value = strconv.Quote(newPath) 300 301 oldName := path.Base(importPath) 302 if imp.Name != nil { 303 oldName = imp.Name.Name 304 } 305 306 newName := path.Base(newPath) 307 if imp.Name == nil && oldName != newName { 308 imp.Name = ast.NewIdent(oldName) 309 } else if imp.Name == nil || imp.Name.Name == newName { 310 imp.Name = nil 311 } 312 filesToUpdate[f] = true 313 } 314 } 315 } 316 } 317 318 for f := range filesToUpdate { 319 var buf bytes.Buffer 320 if err := format.Node(&buf, m.iprog.Fset, f); err != nil { 321 log.Printf("failed to pretty-print syntax tree: %v", err) 322 continue 323 } 324 tokenFile := m.iprog.Fset.File(f.Pos()) 325 writeFile(tokenFile.Name(), buf.Bytes()) 326 } 327 328 // Move the directories. 329 // If either the fromDir or toDir are contained under version control it is 330 // the user's responsibility to provide a custom move command that updates 331 // version control to reflect the move. 332 // TODO(matloob): If the parent directory of toDir does not exist, create it. 333 // For now, it's required that it does exist. 334 335 if m.cmd != "" { 336 // TODO(matloob): Verify that the windows and plan9 cases are correct. 337 var cmd *exec.Cmd 338 switch runtime.GOOS { 339 case "windows": 340 cmd = exec.Command("cmd", "/c", m.cmd) 341 case "plan9": 342 cmd = exec.Command("rc", "-c", m.cmd) 343 default: 344 cmd = exec.Command("sh", "-c", m.cmd) 345 } 346 cmd.Stderr = os.Stderr 347 cmd.Stdout = os.Stdout 348 if err := cmd.Run(); err != nil { 349 return fmt.Errorf("version control system's move command failed: %v", err) 350 } 351 352 return nil 353 } 354 355 return moveDirectory(m.fromDir, m.toDir) 356 } 357 358 // sameLine reports whether two positions in the same file are on the same line. 359 func sameLine(fset *token.FileSet, x, y token.Pos) bool { 360 return fset.Position(x).Line == fset.Position(y).Line 361 } 362 363 var moveDirectory = func(from, to string) error { 364 return os.Rename(from, to) 365 }