refactor: improve copy programs Signed-off-by: Thulio Ferraz Assis <3149049+f0rmiga@users.noreply.github.com>
diff --git a/tools/common/copy.go b/tools/common/copy.go index 3eab443..6b1dfc8 100644 --- a/tools/common/copy.go +++ b/tools/common/copy.go
@@ -4,13 +4,47 @@ "fmt" "io" "io/fs" - "log" "os" "sync" ) +func Copy(src string, dst string, info fs.FileInfo, link bool, verbose bool, wg *sync.WaitGroup) error { + defer wg.Done() + if !info.Mode().IsRegular() { + return fmt.Errorf("failed to copy %q: not a regular file", src) + } + if link { + // hardlink this file + if verbose { + fmt.Printf("hardlink %v => %v\n", src, dst) + } + if err := os.Link(src, dst); err != nil { + // fallback to copy + if verbose { + fmt.Printf("hardlink failed: %v\n", err) + fmt.Printf("copy (fallback) %v => %v\n", src, dst) + } + err = copyFile(src, dst) + if err != nil { + return fmt.Errorf("failed to copy %q: %w", src, err) + } + } + } else { + // copy this file + if verbose { + fmt.Printf("copy %v => %v\n", src, dst) + } + err := copyFile(src, dst) + if err != nil { + return fmt.Errorf("failed to copy %q: %w", src, err) + } + } + + return nil +} + // From https://opensource.com/article/18/6/copying-files-go -func CopyFile(src string, dst string) error { +func copyFile(src string, dst string) error { source, err := os.Open(src) if err != nil { return err @@ -22,42 +56,10 @@ return err } defer destination.Close() - _, err = io.Copy(destination, source) - return err -} -func Copy(src string, dst string, info fs.FileInfo, link bool, verbose bool, wg *sync.WaitGroup) { - if wg != nil { - defer wg.Done() + if _, err := io.Copy(destination, source); err != nil { + return err } - if !info.Mode().IsRegular() { - log.Fatalf("%s is not a regular file", src) - } - if link { - // hardlink this file - if verbose { - fmt.Printf("hardlink %v => %v\n", src, dst) - } - err := os.Link(src, dst) - if err != nil { - // fallback to copy - if verbose { - fmt.Printf("hardlink failed: %v\n", err) - fmt.Printf("copy (fallback) %v => %v\n", src, dst) - } - err = CopyFile(src, dst) - if err != nil { - log.Fatal(err) - } - } - } else { - // copy this file - if verbose { - fmt.Printf("copy %v => %v\n", src, dst) - } - err := CopyFile(src, dst) - if err != nil { - log.Fatal(err) - } - } + + return nil }
diff --git a/tools/copy_directory/main.go b/tools/copy_directory/main.go index 6bac13e..2e8dafb 100644 --- a/tools/copy_directory/main.go +++ b/tools/copy_directory/main.go
@@ -1,25 +1,23 @@ package main import ( + "flag" "fmt" "io/fs" - "log" "os" "path" "path/filepath" "sync" + "sync/atomic" "github.com/aspect-build/bazel-lib/tools/common" ) -type pathSet map[string]bool - -var srcPaths = pathSet{} +var srcPaths = make(map[string]struct{}) var copyWaitGroup sync.WaitGroup -var hardlink = false -var verbose = false +var hasErrors atomic.Bool -func copyDir(src string, dst string) error { +func copyDir(src string, dst string, hardlink bool, verbose bool, errors chan<- error) 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 { @@ -27,16 +25,23 @@ return err } + // Gracefully stop the walking if an error has been reported. + if hasErrors.Load() { + return nil + } + + copySrc := p + r, err := filepath.Rel(src, p) if err != nil { return err } - d := filepath.Join(dst, r) + copyDst := filepath.Join(dst, r) if dirEntry.IsDir() { - srcPaths[src] = true - return os.MkdirAll(d, os.ModePerm) + srcPaths[src] = struct{}{} + return os.MkdirAll(copyDst, os.ModePerm) } info, err := dirEntry.Info() @@ -53,7 +58,7 @@ if !path.IsAbs(linkPath) { linkPath = path.Join(path.Dir(p), linkPath) } - if srcPaths[linkPath] { + if _, isRecursive := srcPaths[linkPath]; isRecursive { // recursive symlink; silently ignore return nil } @@ -63,52 +68,62 @@ } if stat.IsDir() { // symlink points to a directory - return copyDir(linkPath, d) + return copyDir(linkPath, copyDst, hardlink, verbose, errors) } else { // symlink points to a regular file - copyWaitGroup.Add(1) - go common.Copy(linkPath, d, stat, hardlink, verbose, ©WaitGroup) - return nil + copySrc = linkPath } } // a regular file copyWaitGroup.Add(1) - go common.Copy(p, d, info, hardlink, verbose, ©WaitGroup) + go func() { + if err := common.Copy(copySrc, copyDst, info, hardlink, verbose, ©WaitGroup); err != nil { + errors <- err + } + }() return nil }) } func main() { args := os.Args[1:] - - if len(args) == 1 { - if args[0] == "--version" || args[0] == "-v" { - fmt.Printf("copy_directory %s\n", common.Version()) - return - } + if len(args) == 1 && (args[0] == "--version" || args[0] == "-v") { + fmt.Printf("copy_directory %s\n", common.Version()) + return } - if len(args) < 2 { + var hardlink bool + var verbose bool + + flag.BoolVar(&hardlink, "hardlink", false, "use hardlinks instead of copying files") + flag.BoolVar(&verbose, "verbose", false, "print verbose output") + flag.Parse() + + if flag.NArg() < 2 { fmt.Println("Usage: copy_directory src dst [--hardlink] [--verbose]") os.Exit(1) } - src := args[0] - dst := args[1] + src := flag.Arg(0) + dst := flag.Arg(1) - if len(args) > 2 { - for _, a := range os.Args[2:] { - if a == "--hardlink" { - hardlink = true - } else if a == "--verbose" { - verbose = true - } + errors := make(chan error, 100) + + go func() { + if err := copyDir(src, dst, hardlink, verbose, errors); err != nil { + errors <- err } + copyWaitGroup.Wait() + close(errors) + }() + + for err := range errors { + hasErrors.Store(true) + fmt.Fprintln(os.Stderr, err) } - if err := copyDir(src, dst); err != nil { - log.Fatal(err) + if hasErrors.Load() { + os.Exit(1) } - copyWaitGroup.Wait() }
diff --git a/tools/copy_to_directory/main.go b/tools/copy_to_directory/main.go index 7d1e15d..731e2eb 100644 --- a/tools/copy_to_directory/main.go +++ b/tools/copy_to_directory/main.go
@@ -3,8 +3,8 @@ import ( "encoding/json" "fmt" + "io" "io/fs" - "io/ioutil" "log" "os" "path" @@ -59,7 +59,7 @@ } defer f.Close() - byteValue, err := ioutil.ReadAll(f) + byteValue, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf("failed to read config file: %w", err) } @@ -262,10 +262,7 @@ return err } if rootPathMatch != "" { - outputPath = outputPath[len(rootPathMatch):] - if strings.HasPrefix(outputPath, "/") { - outputPath = outputPath[1:] - } + outputPath = strings.TrimPrefix(filepath.Clean(outputPath[len(rootPathMatch):]), string(filepath.Separator)) } // apply include_srcs_patterns