github.com/mattn/gom@v0.0.0-20190726063113-0ebf2b5d812d/install.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "path" 10 "path/filepath" 11 "regexp" 12 "strings" 13 ) 14 15 type vcsCmd struct { 16 checkout []string 17 update []string 18 revision []string 19 revisionMask string 20 } 21 22 var ( 23 hg = &vcsCmd{ 24 []string{"hg", "update"}, 25 []string{"hg", "pull"}, 26 []string{"hg", "id", "-i"}, 27 "^(.+)$", 28 } 29 git = &vcsCmd{ 30 []string{"git", "checkout", "-q"}, 31 []string{"git", "fetch"}, 32 []string{"git", "rev-parse", "HEAD"}, 33 "^(.+)$", 34 } 35 bzr = &vcsCmd{ 36 []string{"bzr", "revert", "-r"}, 37 []string{"bzr", "pull"}, 38 []string{"bzr", "log", "-r-1", "--line"}, 39 "^([0-9]+)", 40 } 41 ) 42 43 func (vcs *vcsCmd) Checkout(p, destination string) error { 44 args := append(vcs.checkout, destination) 45 return vcsExec(p, args...) 46 } 47 48 func (vcs *vcsCmd) Update(p string) error { 49 return vcsExec(p, vcs.update...) 50 } 51 52 func (vcs *vcsCmd) Revision(dir string) (string, error) { 53 args := append(vcs.revision) 54 cmd := exec.Command(args[0], args[1:]...) 55 cmd.Dir = dir 56 cmd.Stderr = os.Stderr 57 b, err := cmd.Output() 58 if err != nil { 59 return "", err 60 } 61 rev := strings.TrimSpace(string(b)) 62 if vcs.revisionMask != "" { 63 return regexp.MustCompile(vcs.revisionMask).FindString(rev), nil 64 } 65 return rev, nil 66 } 67 68 func (vcs *vcsCmd) Sync(p, destination string) error { 69 err := vcs.Checkout(p, destination) 70 if err != nil { 71 err = vcs.Update(p) 72 if err != nil { 73 return err 74 } 75 err = vcs.Checkout(p, destination) 76 } 77 return err 78 } 79 80 func vcsExec(dir string, args ...string) error { 81 cmd := exec.Command(args[0], args[1:]...) 82 cmd.Dir = dir 83 cmd.Stdout = os.Stdout 84 cmd.Stderr = os.Stderr 85 return cmd.Run() 86 } 87 88 func list(dir string) ([]string, error) { 89 cmd := exec.Command("go", "list", "./...") 90 cmd.Dir = dir 91 var stdout bytes.Buffer 92 cmd.Stdout = &stdout 93 err := cmd.Run() 94 if err != nil { 95 return nil, err 96 } 97 98 return strings.Split(stdout.String(), "\n"), nil 99 } 100 101 func has(c interface{}, key string) bool { 102 if m, ok := c.(map[string]interface{}); ok { 103 _, ok := m[key] 104 return ok 105 } else if a, ok := c.([]string); ok { 106 for _, s := range a { 107 if ok && s == key { 108 return true 109 } 110 } 111 } 112 return false 113 } 114 115 func (gom *Gom) Update() error { 116 cmdArgs := []string{"go", "get", "-u"} 117 if insecure, ok := gom.options["insecure"].(string); ok { 118 if insecure == "true" { 119 cmdArgs = append(cmdArgs, "-insecure") 120 } 121 } 122 recursive := "/..." 123 if recursiveFlag, ok := gom.options["recursive"].(string); ok { 124 if recursiveFlag == "false" { 125 recursive = "" 126 } 127 } 128 cmdArgs = append(cmdArgs, gom.name+recursive) 129 130 fmt.Printf("updating %s\n", gom.name) 131 return run(cmdArgs, Green) 132 } 133 134 func (gom *Gom) Clone(args []string) error { 135 vendor, err := filepath.Abs(vendorFolder) 136 if err != nil { 137 return err 138 } 139 if command, ok := gom.options["command"].(string); ok { 140 target, ok := gom.options["target"].(string) 141 if !ok { 142 target = gom.name 143 } 144 145 srcdir := filepath.Join(vendor, "src", target) 146 if err := os.MkdirAll(srcdir, 0755); err != nil { 147 return err 148 } 149 150 customCmd := strings.Split(command, " ") 151 customCmd = append(customCmd, srcdir) 152 153 fmt.Printf("fetching %s (%v)\n", gom.name, customCmd) 154 err = run(customCmd, Blue) 155 if err != nil { 156 return err 157 } 158 } else if private, ok := gom.options["private"].(string); ok { 159 if private == "true" { 160 target, ok := gom.options["target"].(string) 161 if !ok { 162 target = gom.name 163 } 164 srcdir := filepath.Join(vendor, "src", target) 165 if _, err := os.Stat(srcdir); err != nil { 166 if err := os.MkdirAll(srcdir, 0755); err != nil { 167 return err 168 } 169 if err := gom.clonePrivate(srcdir); err != nil { 170 return err 171 } 172 } else { 173 if err := gom.pullPrivate(srcdir); err != nil { 174 return err 175 } 176 } 177 } 178 } 179 180 if skipdep, ok := gom.options["skipdep"].(string); ok { 181 if skipdep == "true" { 182 return nil 183 } 184 } 185 cmdArgs := []string{"go", "get", "-d"} 186 if insecure, ok := gom.options["insecure"].(string); ok { 187 if insecure == "true" { 188 cmdArgs = append(cmdArgs, "-insecure") 189 } 190 } 191 recursive := "/..." 192 if recursiveFlag, ok := gom.options["recursive"].(string); ok { 193 if recursiveFlag == "false" { 194 recursive = "" 195 } 196 } 197 cmdArgs = append(cmdArgs, args...) 198 cmdArgs = append(cmdArgs, gom.name+recursive) 199 200 fmt.Printf("downloading %s\n", gom.name) 201 return run(cmdArgs, Blue) 202 } 203 204 func (gom *Gom) pullPrivate(srcdir string) (err error) { 205 cwd, err := os.Getwd() 206 if err != nil { 207 return err 208 } 209 if err := os.Chdir(srcdir); err != nil { 210 return err 211 } 212 defer os.Chdir(cwd) 213 214 fmt.Printf("fetching private repo %s\n", gom.name) 215 216 branch := "master" 217 if has(gom.options, "branch") { 218 branch = gom.options["branch"].(string) 219 } 220 221 var vcs *vcsCmd 222 if isDir(filepath.Join(srcdir, ".git")) { 223 vcs = git 224 } else if isDir(filepath.Join(srcdir, ".hg")) { 225 vcs = hg 226 } else if isDir(filepath.Join(srcdir, ".bzr")) { 227 vcs = bzr 228 } 229 if vcs != nil { 230 err = vcs.Sync(srcdir, branch) 231 if err != nil { 232 return 233 } 234 } 235 236 pullCmd := "git pull origin " + branch 237 pullArgs := strings.Split(pullCmd, " ") 238 err = run(pullArgs, Blue) 239 if err != nil { 240 return 241 } 242 243 return 244 } 245 246 func (gom *Gom) clonePrivate(srcdir string) (err error) { 247 name := strings.Split(gom.name, "/") 248 if len(name) < 3 { 249 return errors.New("the format of private repo address is wrong") 250 } 251 nameTail := strings.Join(name[2:], "/") 252 privateUrl := fmt.Sprintf("git@%s:%s/%s", name[0], name[1], nameTail) 253 254 fmt.Printf("fetching private repo %s\n", gom.name) 255 cloneCmd := []string{"git", "clone", privateUrl, srcdir} 256 err = run(cloneCmd, Blue) 257 if err != nil { 258 return 259 } 260 261 return 262 } 263 264 func (gom *Gom) Checkout() error { 265 commit_or_branch_or_tag := "" 266 if has(gom.options, "branch") { 267 commit_or_branch_or_tag, _ = gom.options["branch"].(string) 268 } 269 if has(gom.options, "tag") { 270 commit_or_branch_or_tag, _ = gom.options["tag"].(string) 271 } 272 if has(gom.options, "commit") { 273 commit_or_branch_or_tag, _ = gom.options["commit"].(string) 274 } 275 if commit_or_branch_or_tag == "" { 276 return nil 277 } 278 vendor, err := filepath.Abs(vendorFolder) 279 if err != nil { 280 return err 281 } 282 p := filepath.Join(vendor, "src") 283 target, ok := gom.options["target"].(string) 284 if !ok { 285 target = gom.name 286 } 287 for _, elem := range strings.Split(target, "/") { 288 var vcs *vcsCmd 289 p = filepath.Join(p, elem) 290 if isDir(filepath.Join(p, ".git")) { 291 vcs = git 292 } else if isDir(filepath.Join(p, ".hg")) { 293 vcs = hg 294 } else if isDir(filepath.Join(p, ".bzr")) { 295 vcs = bzr 296 } 297 if vcs != nil { 298 p = filepath.Join(vendor, "src", target) 299 return vcs.Sync(p, commit_or_branch_or_tag) 300 } 301 } 302 fmt.Printf("Warning: don't know how to checkout for %v\n", gom.name) 303 return errors.New("gom currently support git/hg/bzr for specifying tag/branch/commit") 304 } 305 306 func hasGoSource(p string) bool { 307 dir, err := os.Open(p) 308 if err != nil { 309 return false 310 } 311 defer dir.Close() 312 fis, err := dir.Readdir(-1) 313 if err != nil { 314 return false 315 } 316 for _, fi := range fis { 317 if fi.IsDir() { 318 continue 319 } 320 name := fi.Name() 321 if strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") { 322 return true 323 } 324 } 325 return false 326 } 327 328 func (gom *Gom) Build(args []string) (err error) { 329 return gom.build(args, true) 330 } 331 332 func (gom *Gom) build(args []string, move bool) (err error) { 333 var vendor string 334 vendor, err = filepath.Abs(vendorFolder) 335 if err != nil { 336 return err 337 } 338 339 if move && !isVendoringSupported { 340 err := moveSrcToVendorSrc(vendor) 341 if err != nil { 342 return err 343 } 344 defer func() { 345 err = moveSrcToVendor(vendor) 346 }() 347 } 348 349 installCmd := []string{"go", "get"} 350 hasPkg := false 351 for _, arg := range args { 352 if !strings.HasPrefix(arg, "-") { 353 arg = path.Join(arg, "...") 354 hasPkg = true 355 } 356 installCmd = append(installCmd, arg) 357 } 358 359 target, ok := gom.options["target"].(string) 360 if !ok { 361 target = gom.name 362 } 363 p := filepath.Join(vendor, "src", target) 364 365 if hasPkg { 366 return vcsExec(p, installCmd...) 367 } 368 369 pkgs, err := list(p) 370 if err != nil { 371 return err 372 } 373 374 for _, pkg := range pkgs { 375 if isIgnorePackage(pkg) { 376 continue 377 } 378 p = filepath.Join(vendor, "src", pkg) 379 if !hasGoSource(p) { 380 continue 381 } 382 err := vcsExec(p, installCmd...) 383 if err != nil { 384 return err 385 } 386 } 387 return nil 388 } 389 390 func isFile(p string) bool { 391 if fi, err := os.Stat(filepath.Join(p)); err == nil && !fi.IsDir() { 392 return true 393 } 394 return false 395 } 396 397 func isDir(p string) bool { 398 if fi, err := os.Stat(filepath.Join(p)); err == nil && fi.IsDir() { 399 return true 400 } 401 return false 402 } 403 404 func isIgnorePackage(pkg string) bool { 405 if pkg == "" { 406 return true 407 } 408 paths := strings.Split(pkg, "/") 409 for _, path := range paths { 410 if path == "examples" { 411 return true 412 } 413 if strings.HasPrefix(path, "_") { 414 return true 415 } 416 } 417 return false 418 } 419 420 func moveSrcToVendorSrc(vendor string) error { 421 vendorSrc := filepath.Join(vendor, "src") 422 dirs, err := readdirnames(vendor) 423 if err != nil { 424 return err 425 } 426 err = os.MkdirAll(vendorSrc, 0755) 427 if err != nil { 428 return err 429 } 430 for _, dir := range dirs { 431 if dir == "bin" || dir == "pkg" || dir == "src" { 432 continue 433 } 434 err = os.Rename(filepath.Join(vendor, dir), filepath.Join(vendorSrc, dir)) 435 if err != nil { 436 return err 437 } 438 } 439 return nil 440 } 441 442 func moveSrcToVendor(vendor string) error { 443 vendorSrc := filepath.Join(vendor, "src") 444 dirs, err := readdirnames(vendorSrc) 445 if err != nil { 446 return err 447 } 448 for _, dir := range dirs { 449 err = os.Rename(filepath.Join(vendorSrc, dir), filepath.Join(vendor, dir)) 450 if err != nil { 451 return err 452 } 453 } 454 err = os.Remove(vendorSrc) 455 if err != nil { 456 return err 457 } 458 return nil 459 } 460 461 func readdirnames(dirname string) ([]string, error) { 462 f, err := os.Open(dirname) 463 if err != nil { 464 return nil, err 465 } 466 list, err := f.Readdirnames(-1) 467 f.Close() 468 if err != nil { 469 return nil, err 470 } 471 return list, nil 472 } 473 474 func parseInstallFlags(args []string) (opts map[string]string, retargs []string) { 475 opts = make(map[string]string) 476 re := regexp.MustCompile(`^--([a-z][a-z_]*)(=\S*)?`) 477 for _, arg := range args { 478 ss := re.FindAllStringSubmatch(arg, -1) 479 if len(ss) > 0 { 480 opts[ss[0][1]] = opts[ss[0][2]] 481 } else { 482 retargs = append(retargs, arg) 483 } 484 } 485 return 486 } 487 488 func hasSaveOpts(opts map[string]string) bool { 489 if _, ok := opts["save"]; ok { 490 return true 491 } 492 if _, ok := opts["save-dev"]; ok { 493 return true 494 } 495 return false 496 } 497 498 func install(args []string) error { 499 var opts map[string]string 500 opts, args = parseInstallFlags(args) 501 allGoms, err := parseGomfile("Gomfile") 502 if err != nil { 503 return err 504 } 505 if hasSaveOpts(opts) { 506 found := false 507 for _, arg := range args { 508 for _, gom := range allGoms { 509 if gom.name == arg { 510 found = true 511 break 512 } 513 } 514 if !found { 515 options := map[string]interface{}{} 516 if _, ok := opts["save-dev"]; ok { 517 options["envs"] = []string{"development"} 518 } 519 allGoms = append(allGoms, Gom{name: arg, options: options}) 520 } 521 } 522 err = writeGomfile("Gomfile", allGoms) 523 if err != nil { 524 return err 525 } 526 } 527 vendor, err := filepath.Abs(vendorFolder) 528 if err != nil { 529 return err 530 } 531 _, err = os.Stat(vendor) 532 if err != nil { 533 err = os.MkdirAll(vendor, 0755) 534 if err != nil { 535 return err 536 } 537 } 538 err = os.Setenv("GOPATH", vendor) 539 if err != nil { 540 return err 541 } 542 err = os.Setenv("GOBIN", filepath.Join(vendor, "bin")) 543 if err != nil { 544 return err 545 } 546 547 // 1. Filter goms to install 548 goms := make([]Gom, 0) 549 for _, gom := range allGoms { 550 if group, ok := gom.options["group"]; ok { 551 if !matchEnv(group) { 552 continue 553 } 554 } 555 if goos, ok := gom.options["goos"]; ok { 556 if !matchOS(goos) { 557 continue 558 } 559 } 560 goms = append(goms, gom) 561 } 562 563 err = moveSrcToVendorSrc(vendor) 564 if err != nil { 565 return err 566 } 567 568 // 2. Clone the repositories 569 for _, gom := range goms { 570 err = gom.Clone(args) 571 if err != nil { 572 return err 573 } 574 } 575 576 // 3. Checkout the commit/branch/tag if needed 577 for _, gom := range goms { 578 err = gom.Checkout() 579 if err != nil { 580 return err 581 } 582 } 583 584 // 4. Build and install 585 for _, gom := range goms { 586 if skipdep, ok := gom.options["skipdep"].(string); ok { 587 if skipdep == "true" { 588 continue 589 } 590 } 591 err = gom.build(args, false) 592 if err != nil { 593 return err 594 } 595 } 596 597 err = moveSrcToVendor(vendor) 598 if err != nil { 599 return err 600 } 601 602 return nil 603 } 604 605 func update() error { 606 goms, err := parseGomfile("Gomfile") 607 if err != nil { 608 return err 609 } 610 vendor, err := filepath.Abs(vendorFolder) 611 if err != nil { 612 return err 613 } 614 err = os.Setenv("GOPATH", vendor) 615 if err != nil { 616 return err 617 } 618 err = os.Setenv("GOBIN", filepath.Join(vendor, "bin")) 619 if err != nil { 620 return err 621 } 622 623 err = moveSrcToVendorSrc(vendor) 624 if err != nil { 625 return err 626 } 627 628 for _, gom := range goms { 629 err = gom.Update() 630 if err != nil { 631 return err 632 } 633 vcs, _, p := vcsScan(vendorSrc(vendor), gom.name) 634 if vcs != nil { 635 rev, err := vcs.Revision(p) 636 if err == nil && rev != "" { 637 gom.options["commit"] = rev 638 } 639 } 640 } 641 642 err = moveSrcToVendor(vendor) 643 if err != nil { 644 return err 645 } 646 647 return writeGomfile("Gomfile", goms) 648 }