github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/replicate.go (about) 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. 16 package main 17 18 import ( 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 ) 24 25 type replicateMode int 26 27 const ( 28 copyMode replicateMode = iota 29 hardlinkMode 30 softlinkMode 31 ) 32 33 type replicateOption func(*replicateConfig) 34 type replicateConfig struct { 35 removeFirst bool 36 fileMode replicateMode 37 dirMode replicateMode 38 paths []string 39 } 40 41 func 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 48 func 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. 60 func 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. 102 func 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. 128 func 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. 149 func 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 }