github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/workcmd/use.go (about)

     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  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/base"
    11  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/fsys"
    12  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/modload"
    13  	"github.com/bir3/gocompiler/src/cmd/gocmd/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  }