github.com/rogpeppe/godep@v0.0.0-20140525002653-983ff9241cea/save.go (about) 1 package main 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 13 "github.com/kr/fs" 14 ) 15 16 var cmdSave = &Command{ 17 Usage: "save [-r] [-copy=false] [packages]", 18 Short: "list and copy dependencies into Godeps", 19 Long: ` 20 Save writes a list of the dependencies of the named packages along 21 with the exact source control revision of each dependency, and copies 22 their source code into a subdirectory. 23 24 The dependency list is a JSON document with the following structure: 25 26 type Godeps struct { 27 ImportPath string 28 GoVersion string // Abridged output of 'go version'. 29 Packages []string // Arguments to godep save, if any. 30 Deps []struct { 31 ImportPath string 32 Comment string // Tag or description of commit. 33 Rev string // VCS-specific commit ID. 34 } 35 } 36 37 Any dependencies already present in the list will be left unchanged. 38 To update a dependency to a newer revision, use 'godep update'. 39 40 If -r is given, import statements will be rewritten to refer 41 directly to the copied source code. 42 43 If -copy=false is given, the list alone is written to file Godeps. 44 45 Otherwise, the list is written to Godeps/Godeps.json, and source 46 code for all dependencies is copied into Godeps/_workspace. 47 48 For more about specifying packages, see 'go help packages'. 49 `, 50 Run: runSave, 51 } 52 53 var ( 54 saveCopy = true 55 saveR = false 56 ) 57 58 func init() { 59 cmdSave.Flag.BoolVar(&saveCopy, "copy", true, "copy source code") 60 cmdSave.Flag.BoolVar(&saveR, "r", false, "rewrite import paths") 61 } 62 63 func runSave(cmd *Command, args []string) { 64 err := save(args) 65 if err != nil { 66 log.Fatalln(err) 67 } 68 } 69 70 func save(pkgs []string) error { 71 dot, err := LoadPackages(".") 72 if err != nil { 73 return err 74 } 75 ver, err := goVersion() 76 if err != nil { 77 return err 78 } 79 manifest := "Godeps" 80 if saveCopy { 81 manifest = filepath.Join("Godeps", "Godeps.json") 82 } 83 var gold Godeps 84 err = ReadGodeps(manifest, &gold) 85 if err != nil && !os.IsNotExist(err) { 86 return err 87 } 88 gnew := &Godeps{ 89 ImportPath: dot[0].ImportPath, 90 GoVersion: ver, 91 } 92 if len(pkgs) > 0 { 93 gnew.Packages = pkgs 94 } else { 95 pkgs = []string{"."} 96 } 97 a, err := LoadPackages(pkgs...) 98 if err != nil { 99 return err 100 } 101 err = gnew.Load(a) 102 if err != nil { 103 return err 104 } 105 if a := badSandboxVCS(gnew.Deps); a != nil && !saveCopy { 106 log.Println("Unsupported sandbox VCS:", strings.Join(a, ", ")) 107 log.Printf("Instead, run: godep save -copy %s", strings.Join(pkgs, " ")) 108 return errors.New("error") 109 } 110 if gnew.Deps == nil { 111 gnew.Deps = make([]Dependency, 0) // produce json [], not null 112 } 113 err = carryVersions(&gold, gnew) 114 if err != nil { 115 return err 116 } 117 if saveCopy { 118 os.Remove("Godeps") // remove regular file if present; ignore error 119 path := filepath.Join("Godeps", "Readme") 120 err = writeFile(path, strings.TrimSpace(Readme)+"\n") 121 if err != nil { 122 log.Println(err) 123 } 124 } 125 f, err := os.Create(manifest) 126 if err != nil { 127 return err 128 } 129 _, err = gnew.WriteTo(f) 130 if err != nil { 131 return err 132 } 133 err = f.Close() 134 if err != nil { 135 return err 136 } 137 if saveCopy { 138 // We use a name starting with "_" so the go tool 139 // ignores this directory when traversing packages 140 // starting at the project's root. For example, 141 // godep go list ./... 142 workspace := filepath.Join("Godeps", "_workspace") 143 srcdir := filepath.Join(workspace, "src") 144 rem := subDeps(gold.Deps, gnew.Deps) 145 add := subDeps(gnew.Deps, gold.Deps) 146 err = removeSrc(srcdir, rem) 147 if err != nil { 148 return err 149 } 150 err = copySrc(srcdir, add) 151 if err != nil { 152 return err 153 } 154 writeVCSIgnore(workspace) 155 } 156 var rewritePaths []string 157 if saveR { 158 for _, dep := range gnew.Deps { 159 rewritePaths = append(rewritePaths, dep.ImportPath) 160 } 161 } 162 return rewrite(a, dot[0].ImportPath, rewritePaths) 163 } 164 165 type revError struct { 166 ImportPath string 167 HaveRev string 168 WantRev string 169 } 170 171 func (v *revError) Error() string { 172 return v.ImportPath + ": revision is " + v.HaveRev + ", want " + v.WantRev 173 } 174 175 // carryVersions copies Rev and Comment from a to b for 176 // each dependency with an identical ImportPath. For any 177 // dependency in b that appears to be from the same repo 178 // as one in a (for example, a parent or child directory), 179 // the Rev must already match - otherwise it is an error. 180 func carryVersions(a, b *Godeps) error { 181 for i := range b.Deps { 182 err := carryVersion(a, &b.Deps[i]) 183 if err != nil { 184 return err 185 } 186 } 187 return nil 188 } 189 190 func carryVersion(a *Godeps, db *Dependency) error { 191 // First see if this exact package is already in the list. 192 for _, da := range a.Deps { 193 if db.ImportPath == da.ImportPath { 194 db.Rev = da.Rev 195 db.Comment = da.Comment 196 return nil 197 } 198 } 199 // No exact match, check for child or sibling package. 200 // We can't handle mismatched versions for packages in 201 // the same repo, so report that as an error. 202 for _, da := range a.Deps { 203 switch { 204 case strings.HasPrefix(db.ImportPath, da.ImportPath+"/"): 205 if da.Rev != db.Rev { 206 return &revError{db.ImportPath, db.Rev, da.Rev} 207 } 208 case strings.HasPrefix(da.ImportPath, db.root+"/"): 209 if da.Rev != db.Rev { 210 return &revError{db.ImportPath, db.Rev, da.Rev} 211 } 212 } 213 } 214 // No related package in the list, must be a new repo. 215 return nil 216 } 217 218 // subDeps returns a - b, using ImportPath for equality. 219 func subDeps(a, b []Dependency) (diff []Dependency) { 220 Diff: 221 for _, da := range a { 222 for _, db := range b { 223 if da.ImportPath == db.ImportPath { 224 continue Diff 225 } 226 } 227 diff = append(diff, da) 228 } 229 return diff 230 } 231 232 // badSandboxVCS returns a list of VCSes that don't work 233 // with the `godep go` sandbox code. 234 func badSandboxVCS(deps []Dependency) (a []string) { 235 for _, d := range deps { 236 if d.vcs.CreateCmd == "" { 237 a = append(a, d.vcs.vcs.Name) 238 } 239 } 240 sort.Strings(a) 241 return uniq(a) 242 } 243 244 func removeSrc(srcdir string, deps []Dependency) error { 245 for _, dep := range deps { 246 path := filepath.FromSlash(dep.ImportPath) 247 err := os.RemoveAll(filepath.Join(srcdir, path)) 248 if err != nil { 249 return err 250 } 251 } 252 return nil 253 } 254 255 func copySrc(dir string, deps []Dependency) error { 256 ok := true 257 for _, dep := range deps { 258 srcdir := filepath.Join(dep.ws, "src") 259 rel, err := filepath.Rel(srcdir, dep.dir) 260 if err != nil { // this should never happen 261 return err 262 } 263 dstpkgroot := filepath.Join(dir, rel) 264 err = os.RemoveAll(dstpkgroot) 265 if err != nil { 266 log.Println(err) 267 ok = false 268 } 269 w := fs.Walk(dep.dir) 270 for w.Step() { 271 err = copyPkgFile(dir, srcdir, w) 272 if err != nil { 273 log.Println(err) 274 ok = false 275 } 276 } 277 } 278 if !ok { 279 return errors.New("error copying source code") 280 } 281 return nil 282 } 283 284 func copyPkgFile(dstroot, srcroot string, w *fs.Walker) error { 285 if w.Err() != nil { 286 return w.Err() 287 } 288 if c := w.Stat().Name()[0]; c == '.' || c == '_' { 289 // Skip directories using a rule similar to how 290 // the go tool enumerates packages. 291 // See $GOROOT/src/cmd/go/main.go:/matchPackagesInFs 292 w.SkipDir() 293 } 294 if w.Stat().IsDir() { 295 return nil 296 } 297 rel, err := filepath.Rel(srcroot, w.Path()) 298 if err != nil { // this should never happen 299 return err 300 } 301 return copyFile(filepath.Join(dstroot, rel), w.Path()) 302 } 303 304 // copyFile copies a regular file from src to dst. 305 // dst is opened with os.Create. 306 func copyFile(dst, src string) error { 307 err := os.MkdirAll(filepath.Dir(dst), 0777) 308 if err != nil { 309 return err 310 } 311 312 linkDst, err := os.Readlink(src) 313 if err == nil { 314 return os.Symlink(linkDst, dst) 315 } 316 317 r, err := os.Open(src) 318 if err != nil { 319 return err 320 } 321 defer r.Close() 322 323 w, err := os.Create(dst) 324 if err != nil { 325 return err 326 } 327 328 _, err = io.Copy(w, r) 329 err1 := w.Close() 330 if err == nil { 331 err = err1 332 } 333 334 return err 335 } 336 337 // Func writeVCSIgnore writes "ignore" files inside dir for known VCSs, 338 // so that dir/pkg and dir/bin don't accidentally get committed. 339 // It logs any errors it encounters. 340 func writeVCSIgnore(dir string) { 341 // Currently git is the only VCS for which we know how to do this. 342 // Mercurial and Bazaar have similar mechasims, but they apparently 343 // require writing files outside of dir. 344 const ignore = "/pkg\n/bin\n" 345 name := filepath.Join(dir, ".gitignore") 346 err := writeFile(name, ignore) 347 if err != nil { 348 log.Println(err) 349 } 350 } 351 352 // writeFile is like ioutil.WriteFile but it creates 353 // intermediate directories with os.MkdirAll. 354 func writeFile(name, body string) error { 355 err := os.MkdirAll(filepath.Dir(name), 0777) 356 if err != nil { 357 return err 358 } 359 return ioutil.WriteFile(name, []byte(body), 0666) 360 } 361 362 const Readme = ` 363 This directory tree is generated automatically by godep. 364 365 Please do not edit. 366 367 See https://github.com/tools/godep for more information. 368 `