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