diff options
Diffstat (limited to 'go/tools/builders/replicate.go')
-rw-r--r-- | go/tools/builders/replicate.go | 167 |
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 +} |