1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// stdlib builds the standard library in the appropriate mode into a new goroot. 16package main 17 18import ( 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23) 24 25type replicateMode int 26 27const ( 28 copyMode replicateMode = iota 29 hardlinkMode 30 softlinkMode 31) 32 33type replicateOption func(*replicateConfig) 34type replicateConfig struct { 35 removeFirst bool 36 fileMode replicateMode 37 dirMode replicateMode 38 paths []string 39} 40 41func replicatePaths(paths ...string) replicateOption { 42 return func(config *replicateConfig) { 43 config.paths = append(config.paths, paths...) 44 } 45} 46 47// replicatePrepare is the common preparation steps for a replication entry 48func replicatePrepare(dst string, config *replicateConfig) error { 49 dir := filepath.Dir(dst) 50 if err := os.MkdirAll(dir, 0755); err != nil { 51 return fmt.Errorf("Failed to make %s: %v", dir, err) 52 } 53 if config.removeFirst { 54 _ = os.Remove(dst) 55 } 56 return nil 57} 58 59// replicateFile is called internally by replicate to map a single file from src into dst. 60func replicateFile(src, dst string, config *replicateConfig) error { 61 if err := replicatePrepare(dst, config); err != nil { 62 return err 63 } 64 switch config.fileMode { 65 case copyMode: 66 in, err := os.Open(src) 67 if err != nil { 68 return err 69 } 70 defer in.Close() 71 out, err := os.Create(dst) 72 if err != nil { 73 return err 74 } 75 _, err = io.Copy(out, in) 76 closeerr := out.Close() 77 if err != nil { 78 return err 79 } 80 if closeerr != nil { 81 return closeerr 82 } 83 s, err := os.Stat(src) 84 if err != nil { 85 return err 86 } 87 if err := os.Chmod(dst, s.Mode()); err != nil { 88 return err 89 } 90 return nil 91 case hardlinkMode: 92 return os.Link(src, dst) 93 case softlinkMode: 94 return os.Symlink(src, dst) 95 default: 96 return fmt.Errorf("Invalid replication mode %d", config.fileMode) 97 } 98} 99 100// replicateDir makes a tree of files visible in a new location. 101// It is allowed to take any efficient method of doing so. 102func replicateDir(src, dst string, config *replicateConfig) error { 103 if err := replicatePrepare(dst, config); err != nil { 104 return err 105 } 106 switch config.dirMode { 107 case copyMode: 108 return filepath.Walk(src, func(path string, f os.FileInfo, err error) error { 109 if f.IsDir() { 110 return nil 111 } 112 relative, err := filepath.Rel(src, path) 113 if err != nil { 114 return err 115 } 116 return replicateFile(path, filepath.Join(dst, relative), config) 117 }) 118 case hardlinkMode: 119 return os.Link(src, dst) 120 case softlinkMode: 121 return os.Symlink(src, dst) 122 default: 123 return fmt.Errorf("Invalid replication mode %d", config.fileMode) 124 } 125} 126 127// replicateTree is called for each single src dst pair. 128func replicateTree(src, dst string, config *replicateConfig) error { 129 if err := os.RemoveAll(dst); err != nil { 130 return fmt.Errorf("Failed to remove file at destination %s: %v", dst, err) 131 } 132 if l, err := filepath.EvalSymlinks(src); err != nil { 133 return err 134 } else { 135 src = l 136 } 137 if s, err := os.Stat(src); err != nil { 138 return err 139 } else if s.IsDir() { 140 return replicateDir(src, dst, config) 141 } 142 return replicateFile(src, dst, config) 143} 144 145// replicate makes a tree of files visible in a new location. 146// You control how it does so using options, by default it presumes the entire tree 147// of files rooted at src must be visible at dst, and that it should do so by copying. 148// src is allowed to be a file, in which case just the one file is copied. 149func replicate(src, dst string, options ...replicateOption) error { 150 config := replicateConfig{ 151 removeFirst: true, 152 } 153 for _, option := range options { 154 option(&config) 155 } 156 if len(config.paths) == 0 { 157 return replicateTree(src, dst, &config) 158 } 159 for _, base := range config.paths { 160 from := filepath.Join(src, base) 161 to := filepath.Join(dst, base) 162 if err := replicateTree(from, to, &config); err != nil { 163 return err 164 } 165 } 166 return nil 167} 168