A package management tool
gx is a packaging tool built around the distributed, content addressed filesystem IPFS. It aims to be flexible, powerful and simple.
gx is Alpha Quality. While not perfect, gx is reliable enough to manage dependencies in go-ipfs and is ready for use by developers of all skill levels.
gx was originally designed to handle dependencies in Go projects in a distributed fashion, and pulls ideas from other beloved package managers (like npm).
gx was designed with the following major goals in mind:
Users are encouraged to have a running IPFS daemon of at least version 0.4.2 on their machines. If not present, gx will use the public gateway. If you wish to publish a package, a local running daemon is a hard requirement. If your IPFS repo is in a non-standard location, remember to set $IPFS_PATH. Alternatively, you can explicitly set $IPFS_API to $IPFS_API_IPADDR:$PORT.
$ (cd ~ && GO111MODULE=on go get github.com/whyrusleeping/gx)
This will download, build, and install a binary to $GOPATH/bin. To modify gx,
just change the source in that directory, and run go build.
Creating and publishing new generic package:
$ gx init
$ gx publish
This will output a 'package-hash' unique to the content of the package at the time of publishing. If someone downloads the package and republishes it, the exact same hash will be produced.
It should be noted that gx is meant to work with existing package.json files. If you are adding a package to gx that already has a package.json file in its root, gx will try and work with it. Any shared fields will have the same types, and any fields unique to gx will kept separate.
E.g. A single package.json file could be used to serve both gx and another packaging tool, such as npm. Since gx is Alpha Quality there may be some exceptions to the above statements, if you notice one, please file an issue.
If you've cloned down a gx package, simply run gx install or gx i to
install it (and its dependencies).
To add a dependency of another package to your package, simply import it by its hash:
$ gx import QmaDFJvcHAnxpnMwcEh6VStYN4v4PB4S16j4pAuC2KSHVr
This downloads the package specified by the hash into the vendor directory in your
workspace. It also adds an entry referencing the package to the local package.json.
Gx has a few nice tools to view and analyze dependencies. First off, the simple:
$ gx deps
go-log QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52 1.2.0
go-libp2p-peer QmWXjJo15p4pzT7cayEwZi2sWgJqLnGDof6ZGMh9xBgU1p 2.0.4
go-libp2p-peerstore QmYkwVGkwoPbMVQEbf6LonZg4SsCxGP3H7PBEtdNCNRyxD 1.2.5
go-testutil QmYpVUnnedgGrp6cX2pBii5HRQgcSr778FiKVe7o7nF5Z3 1.0.2
go-ipfs-util QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1 1.0.0
This just lists out the immediate dependencies of this package. To see
dependencies of dependencies, use the -r option: (and optionally the -s
option to sort them)
…
That's pretty useful, I now know the full set of packages my package depends on.
But what's difficult now is being able to tell what is imported where. To
address that, gx has a --tree option:
…
Now you can see the entire tree of dependencies for this project. Although,
for larger projects, this will get messy. If you're just interested in the
dependency tree of a single package, you can use the --highlight option
to filter the trees printing:
…
This tree is a subset of the previous one, filtered to only show leaves that end in the selected package.
The gx deps command also has two other smaller subcommands, dupes and
stats. gx deps dupes will print out packages that are imported multiple
times with the same name, but different hashes. This is useful to see if
different versions of the same package have been imported in different places
in the dependency tree. Allowing the user to more easily address the
discrepancy. gx deps stats will output the total number of packages imported
(total and unique) as well as the average depth of imports in the tree. This
gives you a rough idea of the complexity of your package.
I firmly believe that packages are better when:
This means restructuring your code in such a way that flattens (and perhaps widens as a consequence) the tree. For example, in Go, this often times means making an interface its own package, and implementations into their own separate packages. The benefits here are that flatter trees are far easier to update. For every package deep a dependency is, you have to update, test, commit, review and merge another package. That's a lot of work, and also a lot of extra room for problems to sneak in.
This should be fairly common sense, but striving to import packages only where they are actually needed helps to improve code quality. Imagine having a helper function in one package, simply because it's convenient to have it there, that depends on a bunch of other imports from elsewhere in the tree. Sure it's nice, and doesn't actually increase the 'total' number of packages you depend on. But now you've created an extra batch of work for you to do any time any of these are updated, and you also now force anyone who wants to import the package with your helper function to also import all those other dependencies.
Adhering to the above two rules should (I'm very open to discussion on this) improve overall code quality, and make your codebase far easier to navigate and work on.
Updating packages in gx is simple:
$ gx update mypkg QmbH7fpAV1FgMp6J7GZXUV6rj6Lck5tDix9JJGBSjFPgUd
This looks into your package.json for a dependency named mypkg and replaces
its hash reference with the one given.
Alternatively, you can just specify the hash you want to update to:
$ gx update QmbH7fpAV1FgMp6J7GZXUV6rj6Lck5tDix9JJGBSjFPgUd
Doing it this way will pull down the package, check its name, and then update that dependency.
Note that by default, this will not touch your code at all, so any references
to that hash you have in your code will need to be updated. If you have a
language tool (e.g. gx-go) installed, and it has a post-update hook,
references to the given package should be updated correctly. If not, you may
have to run sed over the package to update everything. The bright side of that
is that you are very unlikely to have those hashes sitting around for any other
reason so a global find-replace should be just fine.
Gx by default will not let you publish a package twice if you haven't updated
its version. To get around this, you can pass the -f flag. Though this is not
recommended, it's still perfectly possible to do.
To update the version easily, use the gx version subcommand. You can either set the version manually:
$ gx version 5.11.4
Or just do a 'version bump':
$ gx version patch
updated version to: 5.11.5
$ gx version minor
updated version to: 5.12.0
$ gx version major
updated version to: 6.0.0
Most of the time, your process will look something like:
$ gx version minor
updated version to: 6.1.0
$ gx publish
package whys-awesome-package published with hash: QmaoaEi6uNMuuXKeYcXM3gGUEQLzbDWGcFUdd3y49crtZK
$ git commit -a -m "gx publish 6.1.0"
[master 5c4d36c] gx publish 6.1.0
2 files changed, 3 insertions(+), 2 deletions(-)
The release subcommand can be used to automate the above process. gx release <version>
will do a version update (using the same inputs as the normal
version command), run a gx publish, and then execute whatever you have set
in your package.json as your releaseCmd. To get the above git commit flow,
you can set it to: git commit -a -m \"gx publish $VERSION\" and gx will
replace $VERSION with the newly changed version before executing the git
commit.
You can use a .gxignore file to make gx ignore certain files during a publish.
This has the same behaviour as a .gitignore.
Gx also respects a .gitignore file if present, and will not publish any file
excluded by it.
gx supports named packages via user configured repositories. A repository is simply an ipfs object whose links name package hashes. You can add a repository as either an ipns or ipfs path.
Add a new repo
$ gx repo add myrepo /ipns/QmPupmUqXHBxikXxuptYECKaq8tpGNDSetx1Ed44irmew3
List configured repos
$ gx repo list
myrepo /ipns/QmPupmUqXHBxikXxuptYECKaq8tpGNDSetx1Ed44irmew3
List packages in a given repo
$ gx repo list myrepo
events QmeJjwRaGJfx7j6LkPLjyPfzcD2UHHkKehDPkmizqSpcHT
smalltree QmRgTZA6jGi49ipQxorkmC75d3pLe69N6MZBKfQaN6grGY
stump QmebiJS1saSNEPAfr9AWoExvpfGoEK4QCtdLKCK4z6Qw7U
Import a package from a repo:
$ gx repo import events
gx supports a wide array of use cases by having sane defaults that are extensible based on the scenario the user is in. To this end, gx has hooks that get called during certain operations.
These hooks are language specific, and gx will attempt to make calls to a
helper binary matching your language to execute the hooks. For example, when
writing go, gx calls gx-go hook <hookname> <args> for any given hook.
Currently available hooks are:
post-importpost-initpre-publishgx publish before the package is bundled up and added to ipfs.post-publishgx publish after the package has been added to ipfs.post-updategx update after a dependency has been updated.post-installinstall-pathGx by default will install packages 'globally' in the global install location for your given project type. Global gx packages are shared across all packages that depend on them.
No open issues yet, or sync has not completed.