github.com/whyrusleeping/gx@v0.14.3/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "sort" 13 "strings" 14 "text/tabwriter" 15 16 "github.com/blang/semver" 17 cli "github.com/urfave/cli" 18 gx "github.com/whyrusleeping/gx/gxutil" 19 filter "github.com/whyrusleeping/json-filter" 20 progmeter "github.com/whyrusleeping/progmeter" 21 log "github.com/whyrusleeping/stump" 22 ) 23 24 var ( 25 cwd string 26 pm *gx.PM 27 ) 28 29 const PkgFileName = gx.PkgFileName 30 31 func LoadPackageFile(path string) (*gx.Package, error) { 32 if path == PkgFileName { 33 root, err := gx.GetPackageRoot() 34 if err != nil { 35 return nil, err 36 } 37 38 path = filepath.Join(root, PkgFileName) 39 } 40 41 var pkg gx.Package 42 err := gx.LoadPackageFile(&pkg, path) 43 if err != nil { 44 return nil, err 45 } 46 47 if pkg.GxVersion == "" { 48 pkg.GxVersion = gx.GxVersion 49 } 50 51 if pkg.SubtoolRequired { 52 found, err := gx.IsSubtoolInstalled(pkg.Language) 53 if err != nil { 54 return nil, err 55 } 56 if !found { 57 return nil, fmt.Errorf("package requires a subtool (gx-%s) and none was found", pkg.Language) 58 } 59 } 60 61 dephashes := make(map[string]string) 62 for _, dep := range pkg.Dependencies { 63 if pkgname := dephashes[dep.Hash]; pkgname != "" { 64 return nil, fmt.Errorf("have two packages with a hash of %s (%s, %s)", dep.Hash, dep.Name, pkgname) 65 } 66 dephashes[dep.Hash] = dep.Name 67 } 68 69 return &pkg, nil 70 } 71 72 func main() { 73 cfg, err := gx.LoadConfig() 74 if err != nil { 75 log.Fatal(err) 76 } 77 78 pm, err = gx.NewPM(cfg) 79 if err != nil { 80 log.Fatal(err) 81 } 82 83 app := cli.NewApp() 84 app.Author = "whyrusleeping" 85 app.Version = gx.GxVersion 86 app.Flags = []cli.Flag{ 87 cli.BoolFlag{ 88 Name: "verbose", 89 Usage: "print verbose logging information", 90 }, 91 } 92 app.Before = func(c *cli.Context) error { 93 log.Verbose = c.Bool("verbose") 94 95 gcwd, err := os.Getwd() 96 if err != nil { 97 return err 98 } 99 cwd = gcwd 100 101 return nil 102 } 103 104 app.Usage = "gx is a packaging tool that uses ipfs" 105 106 app.Commands = []cli.Command{ 107 CleanCommand, 108 DepsCommand, 109 GetCommand, 110 ImportCommand, 111 DiffCommand, 112 InitCommand, 113 InstallCommand, 114 LockInstallCommand, 115 PublishCommand, 116 ReleaseCommand, 117 RepoCommand, 118 UpdateCommand, 119 VersionCommand, 120 ViewCommand, 121 SetCommand, 122 TestCommand, 123 } 124 125 if err := app.Run(os.Args); err != nil { 126 log.Fatal(err) 127 } 128 } 129 130 func checkLastPubVer() string { 131 out, err := ioutil.ReadFile(filepath.Join(cwd, ".gx", "lastpubver")) 132 if err != nil { 133 return "" 134 } 135 136 parts := bytes.Split(out, []byte{':'}) 137 return string(parts[0]) 138 } 139 140 var PublishCommand = cli.Command{ 141 Name: "publish", 142 Usage: "publish a package", 143 Description: `publish a package into ipfs using a locally running daemon. 144 145 'publish' bundles up all files associated with the package (respecting 146 .gitignore and .gxignore files), adds them to ipfs, and writes out the 147 resulting package hash. 148 149 By default, you cannot publish a package without updating the version 150 number. This is a soft requirement and can be skipped by specifying the 151 -f or --force flag. 152 `, 153 Flags: []cli.Flag{ 154 cli.BoolFlag{ 155 Name: "force,f", 156 Usage: "allow publishing without bumping version", 157 }, 158 }, 159 Action: func(c *cli.Context) error { 160 if gx.UsingGateway { 161 log.Log("gx cannot publish using public gateways.") 162 log.Log("please run an ipfs node and try again.") 163 return nil 164 } 165 166 pkg, err := LoadPackageFile(PkgFileName) 167 if err != nil { 168 return err 169 } 170 171 if !c.Bool("force") { 172 if pkg.Version == checkLastPubVer() { 173 log.Fatal("please update your packages version before publishing. (use -f to skip)") 174 } 175 } 176 177 _, err = doPublish(pkg) 178 return err 179 }, 180 } 181 182 func doPublish(pkg *gx.Package) (string, error) { 183 if !pm.ShellOnline() { 184 return "", fmt.Errorf("ipfs daemon isn't running") 185 } 186 187 err := gx.TryRunHook("pre-publish", pkg.Language, pkg.SubtoolRequired) 188 if err != nil { 189 return "", err 190 } 191 192 hash, err := pm.PublishPackage(cwd, &pkg.PackageBase) 193 if err != nil { 194 return hash, fmt.Errorf("publishing: %s", err) 195 } 196 log.Log("package %s published with hash: %s", pkg.Name, hash) 197 198 // write out version hash 199 err = writeLastPub(pkg.Version, hash) 200 if err != nil { 201 return hash, err 202 } 203 204 err = gx.TryRunHook("post-publish", pkg.Language, pkg.SubtoolRequired, hash) 205 return hash, err 206 } 207 208 func writeLastPub(vers string, hash string) error { 209 err := os.MkdirAll(".gx", 0755) 210 if err != nil { 211 return err 212 } 213 214 fi, err := os.Create(".gx/lastpubver") 215 if err != nil { 216 return fmt.Errorf("failed to create version file: %s", err) 217 } 218 219 defer fi.Close() 220 221 log.VLog("writing published version to .gx/lastpubver") 222 _, err = fmt.Fprintf(fi, "%s: %s\n", vers, hash) 223 if err != nil { 224 return fmt.Errorf("failed to write version file: %s", err) 225 } 226 227 return nil 228 } 229 230 var ImportCommand = cli.Command{ 231 Name: "import", 232 Usage: "import a package as a dependency", 233 Description: `Download packages and add them as a dependency in package.json. 234 235 EXAMPLE 236 > gx import QmUAQaWbKxGCUTuoQVvvicbQNZ9APF5pDGWyAZSe93AtKH 237 > gx import github.com/libp2p/go-libp2p 238 239 In the last example, Gx will check the ".gx/lastpubver" 240 file in the repository to find which hash to import. 241 `, 242 Flags: []cli.Flag{ 243 cli.BoolTFlag{ 244 Name: "global", 245 Usage: "download imported package to global store", 246 }, 247 cli.BoolFlag{ 248 Name: "local", 249 Usage: "install packages locally (equal to --global=false)", 250 }, 251 }, 252 Action: func(c *cli.Context) error { 253 if len(c.Args()) == 0 { 254 return fmt.Errorf("import requires a package reference") 255 } 256 257 global := c.BoolT("global") 258 if c.Bool("local") { 259 global = false 260 } 261 262 pm.SetGlobal(global) 263 264 pkg, err := LoadPackageFile(PkgFileName) 265 if err != nil { 266 return err 267 } 268 269 depname := c.Args().First() 270 cdep := pkg.FindDep(depname) 271 if cdep != nil { 272 return fmt.Errorf("package %s already imported as %s", cdep.Hash, cdep.Name) 273 } 274 275 dephash, err := pm.ResolveDepName(depname) 276 if err != nil { 277 return err 278 } 279 280 ipath, err := gx.InstallPath(pkg.Language, "", global) 281 if err != nil { 282 return err 283 } 284 285 npkg, err := pm.InstallPackage(dephash, ipath) 286 if err != nil { 287 return fmt.Errorf("(install): %s", err) 288 } 289 290 pm.ProgMeter.Stop() 291 292 if pkg.FindDep(npkg.Name) != nil { 293 s := fmt.Sprintf("package with name %s already imported, continue?", npkg.Name) 294 if !yesNoPrompt(s, false) { 295 return nil 296 } 297 log.Log("continuing, please note some things may not work as expected") 298 } 299 300 ndep := &gx.Dependency{ 301 Author: npkg.Author, 302 Hash: dephash, 303 Name: npkg.Name, 304 Version: npkg.Version, 305 } 306 307 pkg.Dependencies = append(pkg.Dependencies, ndep) 308 err = gx.SavePackageFile(pkg, PkgFileName) 309 if err != nil { 310 return fmt.Errorf("writing pkgfile: %s", err) 311 } 312 313 err = gx.TryRunHook("post-import", npkg.Language, npkg.SubtoolRequired, dephash) 314 if err != nil { 315 return fmt.Errorf("running post-import: %s", err) 316 } 317 318 return nil 319 }, 320 } 321 322 var InstallCommand = cli.Command{ 323 Name: "install", 324 Usage: "install this package", 325 Aliases: []string{"i"}, 326 Flags: []cli.Flag{ 327 cli.BoolTFlag{ 328 Name: "global", 329 Usage: "install package in global namespace", 330 }, 331 cli.BoolFlag{ 332 Name: "local", 333 Usage: "install packages locally (equal to --global=false)", 334 }, 335 cli.BoolFlag{ 336 Name: "save", 337 Usage: "write installed packages as deps in package.json", 338 }, 339 cli.BoolFlag{ 340 Name: "nofancy", 341 Usage: "write minimal output", 342 }, 343 }, 344 Action: func(c *cli.Context) error { 345 pkg, err := LoadPackageFile(PkgFileName) 346 if err != nil { 347 return err 348 } 349 350 save := c.Bool("save") 351 352 global := c.BoolT("global") 353 if c.Bool("local") { 354 global = false 355 } 356 357 pm.SetGlobal(global) 358 359 pm.ProgMeter = progmeter.NewProgMeter(c.Bool("nofancy")) 360 361 if len(c.Args()) == 0 { 362 cwd, err := os.Getwd() 363 if err != nil { 364 return err 365 } 366 367 err = gx.TryRunHook("req-check", pkg.Language, pkg.SubtoolRequired, cwd) 368 if err != nil { 369 return err 370 } 371 372 ipath, err := gx.InstallPath(pkg.Language, cwd, global) 373 if err != nil { 374 return err 375 } 376 377 err = pm.InstallDeps(pkg, ipath) 378 if err != nil { 379 return fmt.Errorf("install deps: %s", err) 380 } 381 return nil 382 } 383 384 ipath, err := gx.InstallPath(pkg.Language, "", global) 385 if err != nil { 386 return err 387 } 388 389 for _, p := range c.Args() { 390 phash, err := pm.ResolveDepName(p) 391 if err != nil { 392 return fmt.Errorf("resolving package '%s': %s", p, err) 393 } 394 395 if p != phash { 396 log.VLog("%s resolved to %s", p, phash) 397 } 398 399 ndep, err := pm.ImportPackage(ipath, p) 400 if err != nil { 401 return fmt.Errorf("importing package '%s': %s", p, err) 402 } 403 404 if save { 405 foundPackage := false 406 for _, d := range pkg.Dependencies { 407 if d.Hash == ndep.Hash { 408 foundPackage = true 409 } 410 } 411 if !foundPackage { 412 pkg.Dependencies = append(pkg.Dependencies, ndep) 413 } 414 } 415 } 416 417 if save { 418 err := gx.SavePackageFile(pkg, PkgFileName) 419 if err != nil { 420 return err 421 } 422 } 423 424 return nil 425 }, 426 } 427 428 var GetCommand = cli.Command{ 429 Name: "get", 430 Usage: "download a package", 431 Flags: []cli.Flag{ 432 cli.StringFlag{ 433 Name: "o", 434 Usage: "specify output dir name", 435 }, 436 }, 437 Action: func(c *cli.Context) error { 438 if !c.Args().Present() { 439 return fmt.Errorf("no package specified") 440 } 441 442 pkg, err := pm.ResolveDepName(c.Args().First()) 443 if err != nil { 444 return err 445 } 446 447 out := c.String("o") 448 if out == "" { 449 out = filepath.Join(cwd, pkg) 450 } 451 452 log.Log("writing package to:", out) 453 _, err = pm.GetPackageTo(pkg, out) 454 if err != nil { 455 return fmt.Errorf("fetching package: %s", err) 456 } 457 return nil 458 }, 459 } 460 461 var InitCommand = cli.Command{ 462 Name: "init", 463 Usage: "initialize a package in the current working directory", 464 Flags: []cli.Flag{ 465 cli.StringFlag{ 466 Name: "lang", 467 Usage: "specify primary language of new package", 468 }, 469 }, 470 Action: func(c *cli.Context) error { 471 var pkgname string 472 if len(c.Args()) > 0 { 473 pkgname = c.Args().First() 474 } else { 475 pkgname = filepath.Base(cwd) 476 } 477 478 lang := c.String("lang") 479 if !c.IsSet("lang") { 480 lang = promptUser("what language will the project be in?") 481 } 482 483 log.Log("initializing package %s...", pkgname) 484 err := pm.InitPkg(cwd, pkgname, lang, func(p *gx.Package) { 485 p.Bugs.Url = promptUser("where should users go to report issues?") 486 }) 487 488 if err != nil { 489 return fmt.Errorf("init error: %s", err) 490 } 491 492 return nil 493 }, 494 } 495 496 var UpdateCommand = cli.Command{ 497 Name: "update", 498 Usage: "update a package dependency", 499 ArgsUsage: "[oldref] [newref]", 500 Description: `Update a package to a specified ref. 501 502 EXAMPLE: 503 Update 'myPkg' to a given version (referencing it by package name): 504 505 $ gx update myPkg QmPZ6gM12JxshKzwSyrhbEmyrsi7UaMrnoQZL6mdrzSfh1 506 507 or reference it by hash: 508 509 $ export OLDHASH=QmdTTcAwxWhHLruoZtowxuqua1e5GVkYzxziiYPDn4vWJb 510 $ export NEWHASH=QmPZ6gM12JxshKzwSyrhbEmyrsi7UaMrnoQZL6mdrzSfh1 511 $ gx update $OLDHASH $NEWHASH 512 `, 513 Flags: []cli.Flag{ 514 cli.BoolTFlag{ 515 Name: "global", 516 Usage: "install new package in global namespace", 517 }, 518 cli.BoolFlag{ 519 Name: "local", 520 Usage: "install packages locally (equal to --global=false)", 521 }, 522 cli.BoolFlag{ 523 Name: "with-deps", 524 Usage: "experimental feature to recursively update child deps too", 525 }, 526 }, 527 Action: func(c *cli.Context) error { 528 pkg, err := LoadPackageFile(PkgFileName) 529 if err != nil { 530 log.Fatal("error: ", err) 531 } 532 533 var existing, target string 534 switch len(c.Args()) { 535 case 0: 536 log.Fatal("update requires two arguments, current and target") 537 case 1: 538 target = c.Args()[0] 539 case 2: 540 existing = c.Args()[0] 541 target = c.Args()[1] 542 default: 543 log.Log("ignoring extra arguments: %s", c.Args()[2:]) 544 } 545 546 trgthash, err := pm.ResolveDepName(target) 547 if err != nil { 548 return err 549 } 550 551 global := c.BoolT("global") 552 if c.Bool("local") { 553 global = false 554 } 555 556 ipath, err := gx.InstallPath(pkg.Language, cwd, global) 557 if err != nil { 558 return err 559 } 560 561 npkg, err := pm.InstallPackage(trgthash, ipath) 562 if err != nil { 563 log.Fatal("(installpackage) : ", err) 564 } 565 566 if existing == "" { 567 existing = npkg.Name 568 } 569 570 var oldhash string 571 olddep := pkg.FindDep(existing) 572 if olddep == nil { 573 log.Fatal("unknown package: ", existing) 574 } 575 oldhash = olddep.Hash 576 577 log.Log("updating %s to version %s (%s)", olddep.Name, npkg.Version, trgthash) 578 579 if npkg.Name != olddep.Name { 580 prompt := fmt.Sprintf(`Target package has a different name than new package: 581 old: %s (%s) 582 new: %s (%s) 583 continue?`, olddep.Name, olddep.Hash, npkg.Name, trgthash) 584 if !yesNoPrompt(prompt, false) { 585 log.Fatal("refusing to update package with different names") 586 } 587 } 588 589 log.VLog("running pre update hook...") 590 err = gx.TryRunHook("pre-update", pkg.Language, pkg.SubtoolRequired, existing) 591 if err != nil { 592 return err 593 } 594 595 if c.Bool("with-deps") { 596 err := RecursiveDepUpdate(pkg, oldhash, trgthash) 597 if err != nil { 598 return err 599 } 600 } else { 601 log.VLog("checking for potential package naming collisions...") 602 err = updateCollisionCheck(pkg, olddep, trgthash, nil, make(map[string]struct{})) 603 if err != nil { 604 log.Fatal("update sanity check: ", err) 605 } 606 log.VLog(" - no collisions found for updated package") 607 } 608 609 olddep.Hash = trgthash 610 olddep.Version = npkg.Version 611 612 err = gx.SavePackageFile(pkg, PkgFileName) 613 if err != nil { 614 return fmt.Errorf("writing package file: %s", err) 615 } 616 617 log.VLog("running post update hook...") 618 err = gx.TryRunHook("post-update", pkg.Language, pkg.SubtoolRequired, oldhash, trgthash) 619 if err != nil { 620 return err 621 } 622 623 log.VLog("update complete!") 624 625 return nil 626 }, 627 } 628 629 func updateCollisionCheck(ipkg *gx.Package, idep *gx.Dependency, trgt string, chain []string, skip map[string]struct{}) error { 630 return ipkg.ForEachDep(func(dep *gx.Dependency, pkg *gx.Package) error { 631 if _, ok := skip[dep.Hash]; ok { 632 return nil 633 } 634 635 if dep == idep { 636 return nil 637 } 638 skip[dep.Hash] = struct{}{} 639 640 if (dep.Name == idep.Name && dep.Hash != trgt) || (dep.Hash == idep.Hash && dep.Name != idep.Name) { 641 log.Log("dep %s also imports %s (%s)", strings.Join(chain, "/"), dep.Name, dep.Hash) 642 return nil 643 } 644 645 return updateCollisionCheck(pkg, idep, trgt, append(chain, dep.Name), skip) 646 }) 647 } 648 649 var VersionCommand = cli.Command{ 650 Name: "version", 651 Usage: "view or modify this package's version", 652 Description: `view or modify this package's version 653 654 run without any arguments, will print the current semver of this package. 655 656 if an argument is given, it will be parsed as a semver; if that succeeds, 657 the version will be set to that exactly. If the argument is not a semver, 658 it should be one of three things: "major", "minor", or "patch". Passing 659 any of those three will bump the corresponding segment of the semver up 660 by one. 661 662 EXAMPLE: 663 664 > gx version 665 0.4.0 666 667 > gx version patch 668 updated version to 0.4.1 669 670 > gx version major 671 updated version to 1.0.0 672 673 > gx version 2.5.7 674 updated version to 2.5.7 675 `, 676 Action: func(c *cli.Context) (outerr error) { 677 pkg, err := LoadPackageFile(PkgFileName) 678 if err != nil { 679 return err 680 } 681 if !c.Args().Present() { 682 fmt.Println(pkg.Version) 683 return 684 } 685 686 return updateVersion(pkg, c.Args().First()) 687 }, 688 } 689 690 func updateVersion(pkg *gx.Package, nver string) (outerr error) { 691 if nver == "" { 692 return fmt.Errorf("must specify version with non-zero length") 693 } 694 695 defer func() { 696 err := gx.SavePackageFile(pkg, PkgFileName) 697 if err != nil { 698 outerr = err 699 } 700 }() 701 702 // if argument is a semver, set version to it 703 _, err := semver.Make(nver) 704 if err == nil { 705 pkg.Version = nver 706 return nil 707 } 708 709 v, err := semver.Make(pkg.Version) 710 if err != nil { 711 return err 712 } 713 switch nver { 714 case "major": 715 v.Major++ 716 v.Minor = 0 717 v.Patch = 0 718 v.Pre = nil // reset prerelase info 719 case "minor": 720 v.Minor++ 721 v.Patch = 0 722 v.Pre = nil 723 case "patch": 724 v.Patch++ 725 v.Pre = nil 726 default: 727 if nver[0] == 'v' { 728 nver = nver[1:] 729 } 730 newver, err := semver.Make(nver) 731 if err != nil { 732 log.Error(err) 733 return 734 } 735 v = newver 736 } 737 log.Log("updated version to: %s", v) 738 739 pkg.Version = v.String() 740 741 return nil 742 } 743 744 var ViewCommand = cli.Command{ 745 Name: "view", 746 Usage: "view package information", 747 Description: `view can be used to print out information in the package.json 748 of this package, or a dependency specified either by name or hash. 749 750 EXAMPLE: 751 > gx view language 752 go 753 754 > gx view . 755 { 756 "language": "go", 757 "name": "gx", 758 "version": "0.2.0 759 } 760 761 > gx view go-libp2p gx.dvcsimport 762 "github.com/ipfs/go-libp2p" 763 764 > gx view '.gxDependencies[0].name' 765 go-multihash 766 767 > gx view '.gxDependencies[.name=go-multiaddr].hash' 768 QmWLfU4tstw2aNcTykDm44xbSTCYJ9pUJwfhQCKGwckcHx 769 `, 770 Action: func(c *cli.Context) error { 771 if !c.Args().Present() { 772 log.Fatal("must specify at least a query") 773 } 774 775 var cfg map[string]interface{} 776 if len(c.Args()) == 2 { 777 pkg, err := LoadPackageFile(gx.PkgFileName) 778 if err != nil { 779 return err 780 } 781 782 ref := c.Args()[0] 783 dep := pkg.FindDep(ref) 784 if dep == nil { 785 return fmt.Errorf("no dep referenced by %s", ref) 786 } 787 err = gx.LoadPackage(&cfg, pkg.Language, dep.Hash) 788 if err != nil { 789 return err 790 } 791 } else { 792 root, err := gx.GetPackageRoot() 793 if err != nil { 794 return err 795 } 796 797 err = gx.LoadPackageFile(&cfg, filepath.Join(root, PkgFileName)) 798 if err != nil { 799 return err 800 } 801 } 802 803 queryStr := c.Args()[len(c.Args())-1] 804 val, err := filter.Get(cfg, queryStr) 805 if err != nil { 806 return err 807 } 808 809 jsonPrint(val) 810 return nil 811 }, 812 } 813 814 var depDotCommand = cli.Command{ 815 Name: "dot", 816 Usage: "generate dot graph of the package tree", 817 Action: func(c *cli.Context) error { 818 pkg, err := LoadPackageFile(PkgFileName) 819 if err != nil { 820 return err 821 } 822 dt, err := genDepsTree(pm, pkg) 823 if err != nil { 824 log.Fatal(err) 825 } 826 827 toProcess := make([]*depTreeNode, 0, 32) 828 rank := make(map[string]int) 829 830 name := func(d *gx.Dependency) string { 831 hash := d.Hash 832 if len(hash) > 6 { 833 hash = hash[0:6] 834 } 835 return fmt.Sprintf("\"%s@%s\"", d.Name, hash) 836 } 837 838 bfs := func(dt *depTreeNode, depth int) { 839 toProcess = append(toProcess, dt.children...) 840 rank[name(dt.this)] = depth 841 } 842 depth := 0 843 bfs(dt, depth) 844 for len(toProcess) > 0 { 845 depth++ 846 current := toProcess 847 toProcess = make([]*depTreeNode, 0, 32) 848 for _, dt := range current { 849 bfs(dt, depth) 850 } 851 } 852 853 fmt.Println("digraph G {") 854 fmt.Println("rankdir = TB;") 855 fmt.Println("subgraph {") 856 857 toPrint := make([]*depTreeNode, 0, 32) 858 printed := make(map[string]struct{}) 859 printNode := func(dt *depTreeNode) { 860 if _, ok := printed[dt.this.Hash]; ok { 861 return 862 } 863 printed[dt.this.Hash] = struct{}{} 864 toPrint = append(toPrint, dt.children...) 865 for _, c := range dt.children { 866 fmt.Printf("%s -> %s\n", name(dt.this), name(c.this)) 867 } 868 } 869 870 printNode(dt) 871 for len(toPrint) > 0 { 872 current := toPrint 873 toPrint = make([]*depTreeNode, 0, 32) 874 for _, dt := range current { 875 printNode(dt) 876 } 877 } 878 reverseRank := make([][]string, depth+1) 879 for n, r := range rank { 880 reverseRank[r] = append(reverseRank[r], n) 881 } 882 for _, r := range reverseRank { 883 fmt.Printf("{rank = same;") 884 for _, n := range r { 885 fmt.Printf(" %s;", n) 886 } 887 fmt.Printf("}\n") 888 } 889 fmt.Println("}") 890 fmt.Println("}") 891 return nil 892 893 }, 894 } 895 896 var depCheckCommand = cli.Command{ 897 Name: "check", 898 Usage: "perform comprehensive sanity check and note any packaging issues", 899 Action: func(c *cli.Context) error { 900 pkg, err := LoadPackageFile(PkgFileName) 901 if err != nil { 902 return err 903 } 904 success, err := check(pkg) 905 if err != nil { 906 return err 907 } 908 if success { 909 os.Exit(0) 910 } else { 911 os.Exit(1) 912 } 913 return nil 914 }, 915 } 916 917 var LockInstallCommand = cli.Command{ 918 Name: "lock-install", 919 Usage: "Install deps from lockfile into vendor", 920 Flags: []cli.Flag{ 921 cli.BoolFlag{ 922 Name: "nofancy", 923 Usage: "write minimal output", 924 }, 925 }, 926 Action: func(c *cli.Context) error { 927 cwd, err := os.Getwd() 928 if err != nil { 929 return err 930 } 931 932 var lck gx.LockFile 933 if err := gx.LoadLockFile(&lck, filepath.Join(cwd, gx.LckFileName)); err != nil { 934 return err 935 } 936 937 pm.ProgMeter = progmeter.NewProgMeter(c.Bool("nofancy")) 938 939 if err := pm.InstallLock(lck.Lock, cwd); err != nil { 940 return fmt.Errorf("install deps: %s", err) 941 } 942 943 return nil 944 }, 945 } 946 947 var CleanCommand = cli.Command{ 948 Name: "clean", 949 Usage: "cleanup unused packages in vendor directory", 950 Description: `deletes any package in the 'vendor/gx' directory 951 that is not a dependency of this package. 952 953 use '--dry-run' to print packages that would be deleted without actually 954 removing them. 955 `, 956 Flags: []cli.Flag{ 957 cli.BoolFlag{ 958 Name: "dry-run", 959 Usage: "print out things to be removed without removing them", 960 }, 961 }, 962 Action: func(c *cli.Context) error { 963 pkg, err := LoadPackageFile(PkgFileName) 964 if err != nil { 965 return err 966 } 967 968 dry := c.Bool("dry-run") 969 970 good, err := pm.EnumerateDependencies(pkg) 971 if err != nil { 972 return err 973 } 974 975 ipath, err := gx.InstallPath(pkg.Language, cwd, false) 976 if err != nil { 977 return err 978 } 979 980 vdir := filepath.Join(ipath, "gx", "ipfs") 981 dirinfos, err := ioutil.ReadDir(vdir) 982 if err != nil { 983 if os.IsNotExist(err) { 984 return nil 985 } 986 return err 987 } 988 989 for _, di := range dirinfos { 990 if !strings.HasPrefix(di.Name(), "Qm") { 991 continue 992 } 993 _, keep := good[di.Name()] 994 if !keep { 995 fmt.Println(di.Name()) 996 if !dry { 997 err := os.RemoveAll(filepath.Join(vdir, di.Name())) 998 if err != nil { 999 return err 1000 } 1001 } 1002 } 1003 } 1004 1005 return nil 1006 }, 1007 } 1008 1009 var DepsCommand = cli.Command{ 1010 Name: "deps", 1011 Usage: "print out package dependencies", 1012 Description: `prints out dependencies for this package 1013 1014 Run with no flags, will print out name, hash, and version for each 1015 package that is a direct dependency of this package. 1016 1017 The '-r' option will recursively print out all dependencies directly 1018 and indirectly required by this package. 1019 1020 The '--tree' option will do the same as '-r', but will add indents 1021 to show which packages are dependent on which other. 1022 `, 1023 Flags: []cli.Flag{ 1024 cli.BoolFlag{ 1025 Name: "r", 1026 Usage: "print deps recursively", 1027 }, 1028 cli.BoolFlag{ 1029 Name: "q", 1030 Usage: "only print hashes", 1031 }, 1032 cli.BoolFlag{ 1033 Name: "tree", 1034 Usage: "print deps as a tree", 1035 }, 1036 cli.BoolTFlag{ 1037 Name: "s,sort", 1038 Usage: "sort output by package name", 1039 }, 1040 cli.StringFlag{ 1041 Name: "highlight", 1042 Usage: "for tree printing, prune branches unrelated to arg", 1043 }, 1044 cli.BoolFlag{ 1045 Name: "collapse", 1046 Usage: "for tree printing, prune branches already printed", 1047 }, 1048 }, 1049 Subcommands: []cli.Command{ 1050 depBundleCommand, 1051 depFindCommand, 1052 depStatsCommand, 1053 depDupesCommand, 1054 depCheckCommand, 1055 depDotCommand, 1056 }, 1057 Action: func(c *cli.Context) error { 1058 rec := c.Bool("r") 1059 quiet := c.Bool("q") 1060 1061 pkg, err := LoadPackageFile(PkgFileName) 1062 if err != nil { 1063 return err 1064 } 1065 1066 if c.Bool("tree") { 1067 dt, err := genDepsTree(pm, pkg) 1068 if err != nil { 1069 log.Fatal(err) 1070 } 1071 1072 dt.printFiltered(c.String("highlight"), quiet, c.Bool("collapse")) 1073 return nil 1074 } 1075 1076 var deps []string 1077 if rec { 1078 depmap, err := pm.EnumerateDependencies(pkg) 1079 if err != nil { 1080 return err 1081 } 1082 1083 for k := range depmap { 1084 deps = append(deps, k) 1085 } 1086 } else { 1087 for _, d := range pkg.Dependencies { 1088 deps = append(deps, d.Hash) 1089 } 1090 } 1091 1092 sort.Strings(deps) 1093 1094 buf := new(bytes.Buffer) 1095 w := tabwriter.NewWriter(buf, 12, 4, 1, ' ', 0) 1096 for _, d := range deps { 1097 if !quiet { 1098 var dpkg gx.Package 1099 err := gx.LoadPackage(&dpkg, pkg.Language, d) 1100 if err != nil { 1101 if os.IsNotExist(err) { 1102 return fmt.Errorf("package %s not found", d) 1103 } 1104 return err 1105 } 1106 1107 fmt.Fprintf(w, "%s\t%s\t%s\n", dpkg.Name, d, dpkg.Version) 1108 } else { 1109 fmt.Fprintln(w, d) 1110 } 1111 } 1112 w.Flush() 1113 1114 if c.Bool("sort") { 1115 lines := strings.Split(buf.String(), "\n") 1116 lines = lines[:len(lines)-1] // remove trailing newline 1117 sort.Strings(lines) 1118 for _, l := range lines { 1119 fmt.Println(l) 1120 } 1121 } else { 1122 io.Copy(os.Stdout, buf) 1123 } 1124 1125 return nil 1126 }, 1127 } 1128 1129 var depFindCommand = cli.Command{ 1130 Name: "find", 1131 Usage: "print hash of a given dependency", 1132 Action: func(c *cli.Context) error { 1133 1134 if len(c.Args()) != 1 { 1135 return fmt.Errorf("must be passed exactly one argument") 1136 } 1137 1138 pkg, err := LoadPackageFile(PkgFileName) 1139 if err != nil { 1140 return err 1141 } 1142 1143 dep := c.Args()[0] 1144 1145 for _, d := range pkg.Dependencies { 1146 if d.Name == dep { 1147 fmt.Println(d.Hash) 1148 return nil 1149 } 1150 } 1151 log.Fatal("no dependency named '%s' found", dep) 1152 1153 return nil 1154 }, 1155 } 1156 1157 var depDupesCommand = cli.Command{ 1158 Name: "dupes", 1159 Usage: "print out packages with same names, but different hashes", 1160 Action: func(c *cli.Context) error { 1161 root, err := LoadPackageFile(PkgFileName) 1162 if err != nil { 1163 return err 1164 } 1165 pkgs := []*gx.Package{root} 1166 1167 // name -> hash -> importer 1168 set := make(map[string]map[string][]string) 1169 1170 for len(pkgs) > 0 { 1171 for _, d := range pkgs[0].Dependencies { 1172 if set[d.Name] == nil { 1173 set[d.Name] = map[string][]string{} 1174 } 1175 _, load := set[d.Name][d.Hash] 1176 set[d.Name][d.Hash] = append(set[d.Name][d.Hash], pkgs[0].Name) 1177 1178 if load { 1179 continue 1180 } 1181 1182 var depkg gx.Package 1183 err := gx.LoadPackage(&depkg, pkgs[0].Language, d.Hash) 1184 if err != nil { 1185 if os.IsNotExist(err) { 1186 return fmt.Errorf("package %s (%s) not found", d.Name, d.Hash) 1187 } 1188 return err 1189 } 1190 1191 pkgs = append(pkgs, &depkg) 1192 } 1193 pkgs = pkgs[1:] 1194 } 1195 1196 for name, hashes := range set { 1197 if len(hashes) > 1 { 1198 mostImporters := make([]string, 0, len(hashes)) 1199 for hash := range hashes { 1200 mostImporters = append(mostImporters, hash) 1201 } 1202 sort.Slice(mostImporters, func(i, j int) bool { 1203 return len(hashes[mostImporters[i]]) >= len(hashes[mostImporters[j]]) 1204 }) 1205 fmt.Printf("package %s imported with multiple hashes (mostly as %s):\n", name, mostImporters[0]) 1206 for _, hash := range mostImporters[1:] { 1207 //only print a few first since it's likely where the problem is 1208 n := len(hashes[hash]) 1209 if n > 3 { 1210 n = 3 1211 } 1212 fmt.Printf("\tas %s by %s\n", hash, strings.Join(hashes[hash][:n], ", ")) 1213 } 1214 1215 } 1216 } 1217 1218 return nil 1219 }, 1220 } 1221 1222 var depStatsCommand = cli.Command{ 1223 Name: "stats", 1224 Usage: "print out statistics about this packages dependency tree", 1225 Action: func(c *cli.Context) error { 1226 pkg, err := LoadPackageFile(PkgFileName) 1227 if err != nil { 1228 return err 1229 } 1230 1231 ds, err := gx.GetDepStats(pkg) 1232 if err != nil { 1233 return err 1234 } 1235 1236 fmt.Printf("Total Import Count: %d\n", ds.TotalCount) 1237 fmt.Printf("Unique Import Count: %d\n", ds.TotalUnique) 1238 fmt.Printf("Average Import Depth: %.2f\n", ds.AverageDepth) 1239 1240 return nil 1241 }, 1242 } 1243 1244 var depBundleCommand = cli.Command{ 1245 Name: "bundle", 1246 Usage: "print hash of object containing all dependencies for this package", 1247 Action: func(c *cli.Context) error { 1248 pkg, err := LoadPackageFile(PkgFileName) 1249 if err != nil { 1250 return err 1251 } 1252 1253 obj, err := depBundleForPkg(pkg) 1254 if err != nil { 1255 return err 1256 } 1257 1258 fmt.Println(obj) 1259 return nil 1260 }, 1261 } 1262 1263 func depBundleForPkg(pkg *gx.Package) (string, error) { 1264 return depBundleForPkgRec(pkg, make(map[string]bool)) 1265 } 1266 1267 func depBundleForPkgRec(pkg *gx.Package, done map[string]bool) (string, error) { 1268 obj, err := pm.Shell().NewObject("unixfs-dir") 1269 if err != nil { 1270 return "", err 1271 } 1272 1273 for _, dep := range pkg.Dependencies { 1274 if done[dep.Hash] { 1275 continue 1276 } 1277 1278 log.Log("processing dep: ", dep.Name) 1279 nobj, err := pm.Shell().PatchLink(obj, dep.Name+"-"+dep.Hash, dep.Hash, false) 1280 if err != nil { 1281 return "", err 1282 } 1283 1284 var cpkg gx.Package 1285 err = gx.LoadPackage(&cpkg, pkg.Language, dep.Hash) 1286 if err != nil { 1287 return "", err 1288 } 1289 1290 child, err := depBundleForPkgRec(&cpkg, done) 1291 if err != nil { 1292 return "", err 1293 } 1294 1295 nobj, err = pm.Shell().PatchLink(nobj, dep.Name+"-"+dep.Hash+"-deps", child, false) 1296 if err != nil { 1297 return "", err 1298 } 1299 1300 obj = nobj 1301 1302 done[dep.Hash] = true 1303 } 1304 1305 return obj, nil 1306 } 1307 1308 var DiffCommand = cli.Command{ 1309 Name: "diff", 1310 Usage: "gx diff <old> <new>", 1311 Description: "gx diff prints the changes between two given packages", 1312 Action: func(c *cli.Context) error { 1313 if len(c.Args()) != 2 { 1314 return fmt.Errorf("gx diff takes two arguments") 1315 } 1316 a := c.Args()[0] 1317 b := c.Args()[1] 1318 1319 diff, err := DiffPackages(a, b) 1320 if err != nil { 1321 return err 1322 } 1323 1324 diff.Print(true) 1325 diff.Cleanup() 1326 return nil 1327 }, 1328 } 1329 1330 var SetCommand = cli.Command{ 1331 Name: "set", 1332 Usage: "set package information", 1333 Description: `set can be used to change package information. 1334 EXAMPLE: 1335 > gx set license MIT 1336 `, 1337 Flags: []cli.Flag{ 1338 cli.BoolFlag{ 1339 Name: "in-json", 1340 Usage: "Interpret input as json", 1341 }, 1342 }, 1343 Action: func(c *cli.Context) error { 1344 if c.NArg() < 2 { 1345 log.Fatal("must specify query and value") 1346 } 1347 1348 var cfg map[string]interface{} 1349 err := gx.LoadPackageFile(&cfg, PkgFileName) 1350 if err != nil { 1351 return err 1352 } 1353 1354 queryStr := c.Args().Get(0) 1355 valueStr := c.Args().Get(1) 1356 var value interface{} = valueStr 1357 if c.Bool("in-json") { 1358 err = json.Unmarshal([]byte(valueStr), &value) 1359 if err != nil { 1360 return err 1361 } 1362 } 1363 1364 err = filter.Set(cfg, queryStr, value) 1365 if err != nil { 1366 return err 1367 } 1368 1369 return gx.SavePackageFile(cfg, PkgFileName) 1370 }, 1371 } 1372 1373 var ReleaseCommand = cli.Command{ 1374 Name: "release", 1375 Usage: "perform a release of a package", 1376 Description: `release updates the package version, publishes the package, and runs a configured release script.`, 1377 Action: func(c *cli.Context) error { 1378 if c.NArg() < 1 { 1379 log.Fatal("must specify release severity (major, minor, patch)") 1380 } 1381 1382 if !pm.ShellOnline() { 1383 return fmt.Errorf("ipfs daemon isn't running") 1384 } 1385 1386 pkg, err := LoadPackageFile(PkgFileName) 1387 if err != nil { 1388 return err 1389 } 1390 1391 err = updateVersion(pkg, c.Args().First()) 1392 if err != nil { 1393 return err 1394 } 1395 1396 fmt.Printf("publishing package...\r") 1397 hash, err := doPublish(pkg) 1398 if err != nil { 1399 return err 1400 } 1401 1402 return runRelease(pkg, hash) 1403 }, 1404 } 1405 1406 var TestCommand = cli.Command{ 1407 Name: "test", 1408 Usage: "run package tests", 1409 Description: `Runs a pre-test setup hook, the test command itself, and then a post-test cleanup hook.`, 1410 SkipFlagParsing: true, 1411 Action: func(c *cli.Context) error { 1412 pkg, err := LoadPackageFile(PkgFileName) 1413 if err != nil { 1414 return err 1415 } 1416 1417 err = gx.TryRunHook("pre-test", pkg.Language, pkg.SubtoolRequired) 1418 if err != nil { 1419 return err 1420 } 1421 1422 var testErr error 1423 if pkg.Test != "" { 1424 testErr = fmt.Errorf("don't support running custom test script yet, bug whyrusleeping") 1425 } else { 1426 testErr = gx.TryRunHook("test", pkg.Language, pkg.SubtoolRequired, c.Args()...) 1427 } 1428 1429 err = gx.TryRunHook("post-test", pkg.Language, pkg.SubtoolRequired) 1430 if err != nil { 1431 return err 1432 } 1433 1434 return testErr 1435 }, 1436 } 1437 1438 func runRelease(pkg *gx.Package, hash string) error { 1439 if pkg.ReleaseCmd == "" { 1440 return nil 1441 } 1442 1443 cmd := exec.Command("sh", "-c", pkg.ReleaseCmd) 1444 cmd.Env = append( 1445 os.Environ(), 1446 "VERSION="+pkg.Version, // deprecated. 1447 "GX_VERSION="+pkg.Version, 1448 "GX_NAME="+pkg.Name, 1449 "GX_LANGUAGE="+pkg.Language, 1450 "GX_LICENSE="+pkg.License, 1451 "GX_AUTHOR="+pkg.Author, 1452 "GX_HASH="+hash, 1453 ) 1454 cmd.Stderr = os.Stderr 1455 cmd.Stdout = os.Stdout 1456 cmd.Stdin = os.Stdin 1457 1458 return cmd.Run() 1459 }