github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/autogo/shadow.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // autogo creates a shadow copy of the go packages at origin in a destination. 18 // 19 // In other words this program will walk the directory tree at origin 20 // and for each: 21 // * directory - create a directory with the same name in destination 22 // * go-related-file - link the file into the matching destination directory 23 // 24 // The effect is similar to: 25 // rsync -zarv --include="*/" --include="*.sh" --exclude="*" "$from" "$to" 26 // TODO(fejta): investigate just using rsync? 27 // 28 // The intended use case of this program is with the autogo_generate in //autogo:def.bzl. This rule will clone your primary workspace into an autogo workspace, and then run gazelle to generate rules for go packages. 29 // 30 // Usage: 31 // autogo -- <ORIGIN_DIR> <DEST_DIR> 32 // 33 package main 34 35 import ( 36 "fmt" 37 "log" 38 "os" 39 "path/filepath" 40 "strings" 41 ) 42 43 // main calls shadowClone and exits non-zero on failure 44 func main() { 45 if n := len(os.Args); n != 3 { 46 log.Printf("Expected 2 args, not %d", n) 47 log.Fatalf("Usage: %s <ORIGIN> <DEST>", filepath.Base(os.Args[0])) 48 } 49 if err := shadowClone(os.Args[1], os.Args[2]); err != nil { 50 log.Fatalf("Failed to clone %s to %s: %v", os.Args[1], os.Args[2], err) 51 } 52 } 53 54 // shadowClone walks origin to clone the directory structure and link files at the destination. 55 func shadowClone(origin, dest string) error { 56 v := visitor{ 57 origin: origin, 58 dest: dest, 59 } 60 return filepath.Walk(v.origin, v.visit) 61 } 62 63 // action does something with the current file (mkdir or link) 64 type action func(origin string, info os.FileInfo, dest string) error 65 66 // visitor stores the origin we're cloning from and destination we clone to. 67 type visitor struct { 68 // origin is the path we read to determine what to clone 69 origin string 70 // dest is where we clone 71 dest string 72 } 73 74 // visit chooses and then performs the right action for the current path 75 func (v visitor) visit(path string, info os.FileInfo, verr error) error { 76 act, dest, err := v.choose(path, info, verr) 77 if err != nil { 78 return err 79 } 80 if act == nil { 81 return nil 82 } 83 return act(path, info, dest) 84 } 85 86 // choose looks at the current path and returns the appropriate action: 87 // - testdata directory => skip it and everything under it 88 // - directory => create the directory 89 // - go file (.go, .s) => create it at dest 90 // - other files => do nothing 91 // TODO(fejta): consider including .proto in the mix 92 func (v visitor) choose(path string, info os.FileInfo, verr error) (action, string, error) { 93 if verr != nil { 94 return nil, "", fmt.Errorf("failed to walk to %s: %v", path, verr) 95 } 96 97 // If origin is /foo/bar and we receive /foo/bar/something/special 98 // Change this to something/special 99 r, err := filepath.Rel(v.origin, path) 100 // First ensure the path is a child of origin 101 if err != nil { 102 return nil, "", fmt.Errorf("%q is not relative to %q: %v", path, v.origin, err) 103 } 104 if r == ".." || strings.HasPrefix(r, "../") { 105 return nil, "", fmt.Errorf("%q is not a child of %q", path, v.origin) 106 } 107 108 // TODO(fejta): handle symlink loops better 109 // Many repos (dep is one) have testdata folders which are 110 // either symlink loops back to some other part of the repo 111 // or else intentionally do not compile. 112 // 113 // The gazelle tool is not robust to packages containing files that fail to compile. 114 // 115 // For now just ignore these testdata folders (usually in vendored packages). This makes bazel build work at the cost of breaking bazel test 116 if info.IsDir() && strings.HasSuffix(path, "/testdata") { 117 log.Printf("Skipping %s...", path) 118 return nil, "", filepath.SkipDir 119 } 120 121 d := filepath.Join(v.dest, r) 122 // Create dirs 123 if info.IsDir() { 124 return mkdir, d, nil 125 } 126 127 if strings.Contains(path, "/testdata/") { 128 return nil, "", fmt.Errorf("%s is within a testdata dir, which should not happen", path) 129 } 130 131 // Ignore files irrelevant to go 132 if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".s") { 133 // Assume .go and .s are the only relevant files to a build 134 // TODO(fejta): consider adding BUILD and BUILD.bazel files, or at least the go rules within them 135 return nil, "", nil 136 } 137 138 // Link in golang files 139 return link, d, nil 140 } 141 142 // mkdir creates directories for dest 143 func mkdir(_ string, info os.FileInfo, dest string) error { 144 if err := os.MkdirAll(dest, info.Mode()); err != nil { 145 return fmt.Errorf("failed to create %q: %v", dest, err) 146 } 147 return nil 148 } 149 150 // link clones source at dest 151 func link(source string, _ os.FileInfo, dest string) error { 152 // First try a hard link 153 if err := os.Link(source, dest); err == nil { 154 return nil 155 } 156 157 // If that fails require a symlink 158 return os.Symlink(source, dest) 159 }