Question: How to simply graph a single package and it's dependencies?
I want to give it the package path or name of a command and I would like to see that command and all its direct and indirect dependencies but only within my module. I do not want to see any packages from my go.mod file or any stdlib packages. I also do not wish to see any starting nodes of other packages within my module that I did not specify unless they are a direct or indirect import of the package/command I gave it.
For example, if my command main package M imports module packages A, B, and C (and B also imports C) and also imports stdlib package S and vendor package V then I would like to see:
A
^
|
M --> B
\ /
v v
C
(S and V intentionally omitted)
I've tried various combos of import and all alone with package path wildcards with ... and subtracting packages with -, but I ALWAYS end up with either a single package node or extra package noise starting points that I don't want. Is there not a way to do this? It seems like it would be the number one use case, but I've just burned 30 minutes trying to figure this out to no avail. What am I missing?
You need to use the shared computation. From the root of your module, goda graph "shared(./cmd/example:all, ./...)".
-
./cmd/example:allgets the set of all direct and indirect dependencies of the command -
./...gets the set of packages in a given module -
sharedfinds the intersection of these two
Thanks, I will give this a try shortly.
This worked great, thanks for the quick reply.
Could I make a bold suggestion? This is a powerful tool and generates very nice diagrams, but using it is too complicated I think. I would suggest making this behavior the default, so that any package you give it from the command line does what this does... a simple trace of the packages used and how they connect. This is going to be the number one use case people are looking for IMO and also falls under the basic guideline of "make simple things easy, and hard things possible".
@nu11ptr I'm certainly open to adding a specific function or selector to do this behaviour. One of the problems with changing the default behaviour is that in principle it's a computation on sets of things, and changing ./... or x meaning would then somewhat make less clear what the selected packages are. e.g. ./cmd/x - ./util/..., could end up removing much more than ./util/....
So, maybe adding something like:
// mod selects all indirect or direct dependencies inside the module(s)
goda graph ./...:mod
// work selects all indirect or direct dependencies defined in the current workspace
goda graph ./...:work
I added https://github.com/loov/goda/commit/6c77438ef3ab56d92ff6bcfc39460defba76f49f for the time being. The workspace information isn't readily available, so I'll wait until someone needs it.
Hopefully this helps.
Selecting from multiple modules is still a bit cumbersome. I created https://github.com/loov/goda/issues/81 for that issue.
Sorry, meant to write back earlier. Personally I think the tool should be this simple:
goda graph <package>
And that does exactly what we discussed here. I feel fairly confident that is what 90%+ of users would want. Anything else then becomes an option. This allows simple use cases to be simple and complex ones to be possible. Obviously this is your program so do whatever you want, but as the author of several tools, it is very easy to not see our own complexity. Just my 2 cents.
I completely understand the complexity sentiment. One of the problems is that the input for the program isn't a package per se, but a computation of what packages to include and exclude.
For example, usually you do start out with something simple:
goda graph -std github.com/example/cmd/do:all
Then you remove some of the noisy packages:
goda graph -std github.com/example/cmd/do:+test:all - golang.org/x/sync/... - golang.org/x/tools/... - fmt - errors
If the default would be to select a transitive module based on the input package. Then it would need to be something like:
goda graph -std github.com/example/cmd/do:+test:all - golang.org/x/sync/errgroup:only - golang.org/x/tools/...:only - fmt:only - errors:only
Otherwise you can unintentionally end up removing much more than anticipated.
AFAIR, the first version did actually select the package and all the dependencies, but it became really difficult to use them as computational units.
So, I ended up switching to selecting only the specific package specified and letting the selectors do the rest of the work. In the end the decision didn't come easy, but it seemed to strike the best balance in being able to predict what the computation does vs. how easy it is to write it.
Hopefully the :mod selector makes the common case somewhat easier.
One of the problems is that the input for the program isn't a package per se, but a computation of what packages to include and exclude.
Yes, and that is the usability problem IMO. I'm suggesting you change that. You create two modes: simple mode (that does the basic thing most want) and complex mode (that does everything you want).
Simple mode:
goda graph <pkg>
Complex mode:
goda graph -advanced <endless amounts of complex operators to get exactly and precisely what one wants>
Obviously there are endless amounts of ways to "unlock" advanced mode... but just a quick and dirty example. It is always difficult as tool authors to realize our users simply aren't at the same level of understanding that we are. To design a tool as advanced as goda (and it is pretty impressive I think) one has to be "in the weeds", but most of your users will not be (with some small exceptions). Still, we write open source because it is fun and we want it to do what WE want it to do, and yet, we get joy out of giving our users something they want. This above I think does both.
So, I have seen a few scenarios where you want to use graph:
A. Small codebases, single package module, to visualize your dependencies to external packages. More for curiosity, rather than having actual actionable consequence. B. Medium codebases, maybe 5-20 modules, to visualize either your internal package structure or dependencies to external packages and exclude some common packages (e.g. golang.org/x/*) C. Large codebases, few hundred packages across several modules, to visualize their structure and remove noisy bits like common packages used by all of them.
For A. the default of "select within module", isn't quite right... the "select transitive dependencies excluding std" is.
For B. "select within module" seems appropriate, but so does "select package with dependencies excluding std".
For C. "select within module" seems fine, but "select within workspace" would be better. Either way, this usually is the exceptional case, but also the most likely case where the tool is the most useful. (Because it's the most likely place where you lose track of the dependencies).
Note, the ideal results for cut and tree would probably differ.
There are also exceptional cases of trying to find dependencies how cgo is used; or difference in packages based on the target os.
Regardless, I really do appreciate the feedback and suggestion.
After a few weeks I figured out a way that this would make sense... In other words introduce new behaviour when you don't have any arguments and automatically try to guess what the person wants to do.
So, the basic usage would be goda graph | dot -Tsvg and based on different factors it'll try to do the "right thing".
Here's my first idea of an heuristic:
- If it's a module with a single package, it will show
./...:all - If it's a module with a few packages, e.g. <5, it will show
./...:all - If it's a module with a few packages, e.g. <10, it will show
./...:mod + packages from the same org - If it's a module with a lot of packages, e.g. >10, it will show
./...:mod