github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/cmd/checkpath/checkpath.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 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 package checkpath 16 17 import ( 18 "fmt" 19 "os" 20 "os/exec" 21 "path" 22 "path/filepath" 23 "regexp" 24 "runtime" 25 "strings" 26 "syscall" 27 28 "github.com/pkg/errors" 29 "github.com/termie/go-shutil" 30 ) 31 32 func VerifyProject(wd string, info bool) error { 33 gopath := os.Getenv("GOPATH") 34 if gopath == "" { 35 return fmt.Errorf("GOPATH environment variable must be set") 36 } 37 38 goroot := os.Getenv("GOROOT") 39 if goroot == "" { 40 // GOROOT is not set as environment variable: check value of GOROOT provided by compiled code 41 compiledGoRoot := runtime.GOROOT() 42 if goRootPathInfo, err := os.Stat(compiledGoRoot); err != nil || !goRootPathInfo.IsDir() { 43 // compiled GOROOT does not exist: get GOROOT from "go env GOROOT" 44 goEnvRoot := goEnvRoot() 45 46 if info { 47 fmt.Printf("GOROOT environment variable is not set and the value provided in the compiled code does not exist locally.\n") 48 } 49 50 if goEnvRoot != "" { 51 shellCfgFiles := getShellCfgFiles() 52 if info { 53 fmt.Printf("'go env' reports that GOROOT is %v. Suggested fix:\n\texport GOROOT=%v\n", goEnvRoot, goEnvRoot) 54 for _, currCfgFile := range shellCfgFiles { 55 fmt.Printf("\techo \"export GOROOT=%v\" >> %q \n", goEnvRoot, currCfgFile) 56 } 57 } else { 58 for _, currCfgFile := range shellCfgFiles { 59 fmt.Printf("Adding \"export GOROOT=%v\" to %v...\n", goEnvRoot, currCfgFile) 60 if err := appendToFile(currCfgFile, fmt.Sprintf("export GOROOT=%v\n", goEnvRoot)); err != nil { 61 fmt.Printf("Failed to add \"export GOROOT=%v\" in %v\n", goEnvRoot, currCfgFile) 62 } 63 } 64 } 65 } else { 66 fmt.Printf("Unable to determine GOROOT using 'go env GOROOT'. Ensure that Go was installed properly with source files.\n") 67 } 68 } 69 } 70 71 srcPath, err := gitRepoRootPath(wd) 72 if err != nil { 73 return fmt.Errorf("Directory %q must be in a git project", wd) 74 } 75 76 pkgPath, gitRemoteURL, err := gitRemoteOriginPkgPath(wd) 77 if err != nil { 78 return fmt.Errorf("Unable to determine URL of git remote for %v: verify that the project was checked out from a git remote and that the remote URL is set", wd) 79 } else if pkgPath == "" { 80 return fmt.Errorf("Unable to determine the expected package path from git remote URL of %v", gitRemoteURL) 81 } 82 83 dstPath := path.Join(gopath, "src", pkgPath) 84 if srcPath == dstPath { 85 fmt.Printf("Project appears to be in the correct location\n") 86 return nil 87 } 88 dstPathParentDir := path.Dir(dstPath) 89 90 fmt.Printf("Project path %q differs from expected path %q\n", srcPath, dstPath) 91 if _, err := os.Stat(dstPath); !os.IsNotExist(err) { 92 if !info { 93 return fmt.Errorf("Destination path %q already exists", dstPath) 94 } 95 fmt.Printf("Expected destination path %q already exists.\nIf this project is known to be the correct one, remove or rename the file or directory at the destination path and run this command again.\n", dstPath) 96 return nil 97 } 98 99 var fixOperation func(srcPath, dstPath string) error 100 var fixOpMessage string 101 if pathsOnSameDevice(srcPath, gopath) { 102 if info { 103 fmt.Printf("Project and GOPATH are on same device. Suggested fix:\n\tmkdir -p %q && mv %q %q\n", dstPathParentDir, srcPath, dstPath) 104 } 105 fixOpMessage = fmt.Sprintf("Moving %q to %q...\n", wd, dstPath) 106 fixOperation = os.Rename 107 } else { 108 if info { 109 fmt.Printf("Project and GOPATH are on different devices. Suggested fix:\n\tmkdir -p %q && cp -r %q %q\n", dstPathParentDir, srcPath, dstPath) 110 } 111 fixOpMessage = fmt.Sprintf("Copying %q to %q...\n", wd, dstPath) 112 fixOperation = func(srcPath, dstPath string) error { 113 return shutil.CopyTree(srcPath, dstPath, nil) 114 } 115 } 116 117 if info { 118 fmt.Printf("\n%v found issues. Run the following godel command to implement suggested fixes:\n\t%v\n", cmd, cmd) 119 } else { 120 if err := os.MkdirAll(dstPathParentDir, 0755); err != nil { 121 return fmt.Errorf("Failed to create path to %q: %v", dstPathParentDir, err) 122 } 123 fmt.Printf(fixOpMessage) 124 if err := fixOperation(srcPath, dstPath); err != nil { 125 return err 126 } 127 } 128 return nil 129 } 130 131 func VerifyGoEnv(wd string) { 132 goroot := os.Getenv("GOROOT") 133 if goroot == "" { 134 // GOROOT is not set as environment variable: check value of GOROOT provided by compiled code 135 compiledGoRoot := runtime.GOROOT() 136 if info, err := os.Stat(compiledGoRoot); err != nil || !info.IsDir() { 137 // compiled GOROOT does not exist: get GOROOT from "go env GOROOT" command and display warning 138 goEnvRoot := goEnvRoot() 139 140 title := "GOROOT environment variable is empty" 141 content := fmt.Sprintf("GOROOT is required to build Go code. The GOROOT environment variable was not set and the value of GOROOT in the compiled code does not exist locally.\n") 142 content += fmt.Sprintf("Run the godel command '%v --info' for more information.", cmd) 143 144 if goEnvRoot != "" { 145 content += fmt.Sprintf("\nFalling back to value provided by 'go env GOROOT': %v", goEnvRoot) 146 if err := os.Setenv("GOROOT", goEnvRoot); err != nil { 147 fmt.Printf("Failed to set GOROOT environment variable: %v", err) 148 return 149 } 150 } 151 printWarning(title, content) 152 } 153 } 154 155 gopath := os.Getenv("GOPATH") 156 if gopath == "" { 157 title := "GOPATH environment variable is empty" 158 content := fmt.Sprintf("GOPATH is required to build Go code.\nSee https://golang.org/doc/code.html#GOPATH for more information.") 159 printWarning(title, content) 160 return 161 } 162 163 goSrcPath := path.Join(gopath, "src") 164 if !isSubdir(goSrcPath, wd) { 165 title := "project directory is not a subdirectory of $GOPATH/src" 166 content := fmt.Sprintf("%q is not a subdirectory of the GOPATH/src directory (%q): the project location is likely incorrect.\n", wd, goSrcPath) 167 content += fmt.Sprintf("Run the godel command '%v --info' for more information.", cmd) 168 printWarning(title, content) 169 } 170 } 171 172 func goEnvRoot() string { 173 if output, err := exec.Command("go", "env", "GOROOT").CombinedOutput(); err == nil { 174 return strings.TrimSpace(string(output)) 175 } 176 return "" 177 } 178 179 func getShellCfgFiles() []string { 180 var files []string 181 for _, currCfg := range []string{".bash_profile", ".zshrc"} { 182 if currFile := checkForCfgFile(currCfg); currFile != "" { 183 files = append(files, currFile) 184 } 185 } 186 return files 187 } 188 189 func appendToFile(filename, content string) (rErr error) { 190 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0666) 191 if err != nil { 192 return err 193 } 194 defer func() { 195 if err := f.Close(); err != nil && rErr == nil { 196 rErr = errors.Wrapf(err, "failed to close file %s in defer", filename) 197 } 198 }() 199 if _, err = f.WriteString(content); err != nil { 200 return err 201 } 202 return nil 203 } 204 205 func checkForCfgFile(name string) string { 206 cfgFile := path.Join(os.Getenv("HOME"), name) 207 if _, err := os.Stat(cfgFile); err == nil { 208 return cfgFile 209 } 210 return "" 211 } 212 213 func printWarning(title, content string) { 214 warning := fmt.Sprintf("| WARNING: %v |", title) 215 fmt.Println(strings.Repeat("-", len(warning))) 216 fmt.Println(warning) 217 fmt.Println(strings.Repeat("-", len(warning))) 218 fmt.Println(content) 219 fmt.Println() 220 } 221 222 func pathsOnSameDevice(p1, p2 string) bool { 223 id1, ok := getDeviceID(p1) 224 if !ok { 225 return false 226 } 227 id2, ok := getDeviceID(p2) 228 if !ok { 229 return false 230 } 231 return id1 == id2 232 } 233 234 func getDeviceID(p string) (interface{}, bool) { 235 fi, err := os.Stat(p) 236 if err != nil { 237 return 0, false 238 } 239 s := fi.Sys() 240 switch s := s.(type) { 241 case *syscall.Stat_t: 242 return s.Dev, true 243 } 244 return 0, false 245 } 246 247 func isSubdir(base, dst string) bool { 248 relPath, err := filepath.Rel(base, dst) 249 return err == nil && !strings.HasPrefix(relPath, "..") 250 } 251 252 func gitRepoRootPath(dir string) (string, error) { 253 cmd := exec.Command("git", "rev-parse", "--show-toplevel") 254 cmd.Dir = dir 255 output, err := cmd.CombinedOutput() 256 if err != nil { 257 return "", fmt.Errorf("%v failed: could not determine root of git repository for directory %v", cmd.Args, dir) 258 } 259 return strings.TrimSpace(string(output)), nil 260 } 261 262 func gitRemoteOriginPkgPath(dir string) (string, string, error) { 263 cmd := exec.Command("git", "ls-remote", "--get-url") 264 cmd.Dir = dir 265 bytes, err := cmd.CombinedOutput() 266 if err != nil { 267 return "", "", fmt.Errorf("%v failed: git remote URL is not set", cmd.Args) 268 } 269 270 pkgPath := "" 271 url := strings.TrimSpace(string(bytes)) 272 if protocolSeparator := regexp.MustCompile("https?://").FindStringIndex(url); protocolSeparator != nil { 273 if protocolSeparator[0] == 0 { 274 pkgPath = url[protocolSeparator[1]:] 275 } 276 } else if atSign := strings.Index(url, "@"); atSign != -1 { 277 // assume SSH format of "user@host:org/repo" 278 pkgPath = url[atSign+1:] 279 pkgPath = strings.Replace(pkgPath, ":", "/", 1) 280 } 281 282 // trim ".git" suffix if present 283 if strings.HasSuffix(pkgPath, ".git") { 284 pkgPath = pkgPath[:len(pkgPath)-len(".git")] 285 } 286 287 return pkgPath, url, nil 288 }