| // 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 |
| } |