github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/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 "io" 20 "os" 21 "os/exec" 22 "path" 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, apply bool, stdout io.Writer) error { 33 gopath := os.Getenv("GOPATH") 34 if gopath == "" { 35 return errors.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 fmt.Fprintln(stdout, "GOROOT environment variable is not set and the value provided in the compiled code does not exist locally.") 47 48 if goEnvRoot != "" { 49 shellCfgFiles := getShellCfgFiles() 50 if !apply { 51 fmt.Fprintf(stdout, "'go env' reports that GOROOT is %s. Suggested fix:\n", goEnvRoot) 52 fmt.Fprintf(stdout, " export GOROOT=%s\n", goEnvRoot) 53 for _, currCfgFile := range shellCfgFiles { 54 fmt.Fprintf(stdout, " echo \"export GOROOT=%s\" >> %q \n", goEnvRoot, currCfgFile) 55 } 56 } else { 57 for _, currCfgFile := range shellCfgFiles { 58 fmt.Fprintf(stdout, "Adding \"export GOROOT=%s\" to %s...\n", goEnvRoot, currCfgFile) 59 if err := appendToFile(currCfgFile, fmt.Sprintf("export GOROOT=%v\n", goEnvRoot)); err != nil { 60 fmt.Fprintf(stdout, "Failed to add \"export GOROOT=%s\" in %s\n", goEnvRoot, currCfgFile) 61 } 62 } 63 } 64 } else { 65 fmt.Fprintln(stdout, "Unable to determine GOROOT using 'go env GOROOT'. Ensure that Go was installed properly with source files.") 66 } 67 } 68 } 69 70 srcPath, err := gitRepoRootPath(wd) 71 if err != nil { 72 return errors.Errorf("Directory %q must be in a git project", wd) 73 } 74 75 pkgPath, gitRemoteURL, err := gitRemoteOriginPkgPath(wd) 76 if err != nil { 77 return errors.Errorf("Unable to determine URL of git remote for %s: verify that the project was checked out from a git remote and that the remote URL is set", wd) 78 } else if pkgPath == "" { 79 return errors.Errorf("Unable to determine the expected package path from git remote URL of %s", gitRemoteURL) 80 } 81 82 dstPath := path.Join(gopath, "src", pkgPath) 83 if srcPath == dstPath { 84 fmt.Fprintln(stdout, "Project appears to be in the correct location") 85 return nil 86 } 87 dstPathParentDir := path.Dir(dstPath) 88 89 fmt.Fprintf(stdout, "Project path %q differs from expected path %q\n", srcPath, dstPath) 90 if _, err := os.Stat(dstPath); !os.IsNotExist(err) { 91 fmt.Fprintf(stdout, "Expected destination path %q already exists.\n", dstPath) 92 fmt.Fprintln(stdout, "If 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.") 93 return nil 94 } 95 96 var fixOperation func(srcPath, dstPath string) error 97 var fixOpMessage string 98 if pathsOnSameDevice(srcPath, gopath) { 99 if !apply { 100 fmt.Fprintln(stdout, "Project and GOPATH are on same device. Suggested fix:") 101 fmt.Fprintf(stdout, " mkdir -p %q && mv %q %q\n", dstPathParentDir, srcPath, dstPath) 102 } 103 fixOpMessage = fmt.Sprintf("Moving %q to %q...", wd, dstPath) 104 fixOperation = os.Rename 105 } else { 106 if !apply { 107 fmt.Fprintln(stdout, "Project and GOPATH are on different devices. Suggested fix:") 108 fmt.Fprintf(stdout, " mkdir -p %q && cp -r %q %q\n", dstPathParentDir, srcPath, dstPath) 109 } 110 fixOpMessage = fmt.Sprintf("Copying %q to %q...", wd, dstPath) 111 fixOperation = func(srcPath, dstPath string) error { 112 return shutil.CopyTree(srcPath, dstPath, nil) 113 } 114 } 115 116 if !apply { 117 fmt.Fprintf(stdout, "%s found issues. Run the following godel command to implement suggested fixes:\n", cmd) 118 fmt.Fprintf(stdout, " %s --%s\n", cmd, applyFlag) 119 } else { 120 if err := os.MkdirAll(dstPathParentDir, 0755); err != nil { 121 return errors.Errorf("Failed to create path to %q: %v", dstPathParentDir, err) 122 } 123 fmt.Fprintln(stdout, fixOpMessage) 124 if err := fixOperation(srcPath, dstPath); err != nil { 125 return err 126 } 127 } 128 return nil 129 } 130 131 func goEnvRoot() string { 132 if output, err := exec.Command("go", "env", "GOROOT").CombinedOutput(); err == nil { 133 return strings.TrimSpace(string(output)) 134 } 135 return "" 136 } 137 138 func getShellCfgFiles() []string { 139 var files []string 140 for _, currCfg := range []string{".bash_profile", ".zshrc"} { 141 if currFile := checkForCfgFile(currCfg); currFile != "" { 142 files = append(files, currFile) 143 } 144 } 145 return files 146 } 147 148 func appendToFile(filename, content string) (rErr error) { 149 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0666) 150 if err != nil { 151 return err 152 } 153 defer func() { 154 if err := f.Close(); err != nil && rErr == nil { 155 rErr = errors.Wrapf(err, "failed to close file %s in defer", filename) 156 } 157 }() 158 if _, err = f.WriteString(content); err != nil { 159 return err 160 } 161 return nil 162 } 163 164 func checkForCfgFile(name string) string { 165 cfgFile := path.Join(os.Getenv("HOME"), name) 166 if _, err := os.Stat(cfgFile); err == nil { 167 return cfgFile 168 } 169 return "" 170 } 171 172 func pathsOnSameDevice(p1, p2 string) bool { 173 id1, ok := getDeviceID(p1) 174 if !ok { 175 return false 176 } 177 id2, ok := getDeviceID(p2) 178 if !ok { 179 return false 180 } 181 return id1 == id2 182 } 183 184 func getDeviceID(p string) (interface{}, bool) { 185 fi, err := os.Stat(p) 186 if err != nil { 187 return 0, false 188 } 189 s := fi.Sys() 190 switch s := s.(type) { 191 case *syscall.Stat_t: 192 return s.Dev, true 193 } 194 return 0, false 195 } 196 197 func gitRepoRootPath(dir string) (string, error) { 198 cmd := exec.Command("git", "rev-parse", "--show-toplevel") 199 cmd.Dir = dir 200 output, err := cmd.CombinedOutput() 201 if err != nil { 202 return "", fmt.Errorf("%v failed: could not determine root of git repository for directory %v", cmd.Args, dir) 203 } 204 return strings.TrimSpace(string(output)), nil 205 } 206 207 func gitRemoteOriginPkgPath(dir string) (string, string, error) { 208 cmd := exec.Command("git", "ls-remote", "--get-url") 209 cmd.Dir = dir 210 bytes, err := cmd.CombinedOutput() 211 if err != nil { 212 return "", "", fmt.Errorf("%v failed: git remote URL is not set", cmd.Args) 213 } 214 215 pkgPath := "" 216 url := strings.TrimSpace(string(bytes)) 217 if protocolSeparator := regexp.MustCompile("https?://").FindStringIndex(url); protocolSeparator != nil { 218 if protocolSeparator[0] == 0 { 219 pkgPath = url[protocolSeparator[1]:] 220 } 221 } else if atSign := strings.Index(url, "@"); atSign != -1 { 222 // assume SSH format of "user@host:org/repo" 223 pkgPath = url[atSign+1:] 224 pkgPath = strings.Replace(pkgPath, ":", "/", 1) 225 } 226 227 // trim ".git" suffix if present 228 if strings.HasSuffix(pkgPath, ".git") { 229 pkgPath = pkgPath[:len(pkgPath)-len(".git")] 230 } 231 232 return pkgPath, url, nil 233 }