| package main | |
| import ( | |
| "fmt" | |
| "io/fs" | |
| "log" | |
| "os" | |
| "path/filepath" | |
| "sync" | |
| "github.com/aspect-build/bazel-lib/tools/common" | |
| ) | |
| type pathSet map[string]bool | |
| var srcPaths = pathSet{} | |
| var hardlink = false | |
| var verbose = false | |
| type walker struct { | |
| queue chan<- common.CopyOpts | |
| } | |
| func (w *walker) copyDir(src string, dst string) error { | |
| // filepath.WalkDir walks the file tree rooted at root, calling fn for each file or directory in | |
| // the tree, including root. See https://pkg.go.dev/path/filepath#WalkDir for more info. | |
| return filepath.WalkDir(src, func(p string, dirEntry fs.DirEntry, err error) error { | |
| if err != nil { | |
| return err | |
| } | |
| r, err := common.FileRel(src, p) | |
| if err != nil { | |
| return err | |
| } | |
| d := filepath.Join(dst, r) | |
| if dirEntry.IsDir() { | |
| srcPaths[src] = true | |
| return os.MkdirAll(d, os.ModePerm) | |
| } | |
| info, err := dirEntry.Info() | |
| if err != nil { | |
| return err | |
| } | |
| if info.Mode()&os.ModeSymlink == os.ModeSymlink { | |
| // symlink to directories are intentionally never followed by filepath.Walk to avoid infinite recursion | |
| linkPath, err := common.Realpath(p) | |
| if err != nil { | |
| return err | |
| } | |
| if srcPaths[linkPath] { | |
| // recursive symlink; silently ignore | |
| return nil | |
| } | |
| stat, err := os.Stat(linkPath) | |
| if err != nil { | |
| return fmt.Errorf("failed to stat file %s pointed to by symlink %s: %w", linkPath, p, err) | |
| } | |
| if stat.IsDir() { | |
| // symlink points to a directory | |
| return w.copyDir(linkPath, d) | |
| } else { | |
| // symlink points to a regular file | |
| w.queue <- common.NewCopyOpts(linkPath, d, stat, hardlink, verbose) | |
| return nil | |
| } | |
| } | |
| // a regular file | |
| w.queue <- common.NewCopyOpts(p, d, info, hardlink, verbose) | |
| return nil | |
| }) | |
| } | |
| func main() { | |
| args := os.Args[1:] | |
| if len(args) < 2 { | |
| fmt.Println("Usage: copy_directory src dst [--hardlink] [--verbose]") | |
| os.Exit(1) | |
| } | |
| src := args[0] | |
| dst := args[1] | |
| if len(args) > 2 { | |
| for _, a := range os.Args[2:] { | |
| if a == "--hardlink" { | |
| hardlink = true | |
| } else if a == "--verbose" { | |
| verbose = true | |
| } | |
| } | |
| } | |
| queue := make(chan common.CopyOpts, 100) | |
| var wg sync.WaitGroup | |
| const numWorkers = 10 | |
| wg.Add(numWorkers) | |
| for i := 0; i < numWorkers; i++ { | |
| go common.NewCopyWorker(queue).Run(&wg) | |
| } | |
| walker := &walker{queue} | |
| if err := walker.copyDir(src, dst); err != nil { | |
| log.Fatal(err) | |
| } | |
| close(queue) | |
| wg.Wait() | |
| } |