aboutsummaryrefslogtreecommitdiff
path: root/go/tools/builders/replicate.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/tools/builders/replicate.go')
-rw-r--r--go/tools/builders/replicate.go167
1 files changed, 167 insertions, 0 deletions
diff --git a/go/tools/builders/replicate.go b/go/tools/builders/replicate.go
new file mode 100644
index 00000000..117f882c
--- /dev/null
+++ b/go/tools/builders/replicate.go
@@ -0,0 +1,167 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// stdlib builds the standard library in the appropriate mode into a new goroot.
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+type replicateMode int
+
+const (
+ copyMode replicateMode = iota
+ hardlinkMode
+ softlinkMode
+)
+
+type replicateOption func(*replicateConfig)
+type replicateConfig struct {
+ removeFirst bool
+ fileMode replicateMode
+ dirMode replicateMode
+ paths []string
+}
+
+func replicatePaths(paths ...string) replicateOption {
+ return func(config *replicateConfig) {
+ config.paths = append(config.paths, paths...)
+ }
+}
+
+// replicatePrepare is the common preparation steps for a replication entry
+func replicatePrepare(dst string, config *replicateConfig) error {
+ dir := filepath.Dir(dst)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("Failed to make %s: %v", dir, err)
+ }
+ if config.removeFirst {
+ _ = os.Remove(dst)
+ }
+ return nil
+}
+
+// replicateFile is called internally by replicate to map a single file from src into dst.
+func replicateFile(src, dst string, config *replicateConfig) error {
+ if err := replicatePrepare(dst, config); err != nil {
+ return err
+ }
+ switch config.fileMode {
+ case copyMode:
+ in, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ out, err := os.Create(dst)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(out, in)
+ closeerr := out.Close()
+ if err != nil {
+ return err
+ }
+ if closeerr != nil {
+ return closeerr
+ }
+ s, err := os.Stat(src)
+ if err != nil {
+ return err
+ }
+ if err := os.Chmod(dst, s.Mode()); err != nil {
+ return err
+ }
+ return nil
+ case hardlinkMode:
+ return os.Link(src, dst)
+ case softlinkMode:
+ return os.Symlink(src, dst)
+ default:
+ return fmt.Errorf("Invalid replication mode %d", config.fileMode)
+ }
+}
+
+// replicateDir makes a tree of files visible in a new location.
+// It is allowed to take any efficient method of doing so.
+func replicateDir(src, dst string, config *replicateConfig) error {
+ if err := replicatePrepare(dst, config); err != nil {
+ return err
+ }
+ switch config.dirMode {
+ case copyMode:
+ return filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
+ if f.IsDir() {
+ return nil
+ }
+ relative, err := filepath.Rel(src, path)
+ if err != nil {
+ return err
+ }
+ return replicateFile(path, filepath.Join(dst, relative), config)
+ })
+ case hardlinkMode:
+ return os.Link(src, dst)
+ case softlinkMode:
+ return os.Symlink(src, dst)
+ default:
+ return fmt.Errorf("Invalid replication mode %d", config.fileMode)
+ }
+}
+
+// replicateTree is called for each single src dst pair.
+func replicateTree(src, dst string, config *replicateConfig) error {
+ if err := os.RemoveAll(dst); err != nil {
+ return fmt.Errorf("Failed to remove file at destination %s: %v", dst, err)
+ }
+ if l, err := filepath.EvalSymlinks(src); err != nil {
+ return err
+ } else {
+ src = l
+ }
+ if s, err := os.Stat(src); err != nil {
+ return err
+ } else if s.IsDir() {
+ return replicateDir(src, dst, config)
+ }
+ return replicateFile(src, dst, config)
+}
+
+// replicate makes a tree of files visible in a new location.
+// You control how it does so using options, by default it presumes the entire tree
+// of files rooted at src must be visible at dst, and that it should do so by copying.
+// src is allowed to be a file, in which case just the one file is copied.
+func replicate(src, dst string, options ...replicateOption) error {
+ config := replicateConfig{
+ removeFirst: true,
+ }
+ for _, option := range options {
+ option(&config)
+ }
+ if len(config.paths) == 0 {
+ return replicateTree(src, dst, &config)
+ }
+ for _, base := range config.paths {
+ from := filepath.Join(src, base)
+ to := filepath.Join(dst, base)
+ if err := replicateTree(from, to, &config); err != nil {
+ return err
+ }
+ }
+ return nil
+}