cargo icon indicating copy to clipboard operation
cargo copied to clipboard

Dependency analysis

Open txus opened this issue 10 years ago • 14 comments

Thanks to Cargo, it is possible to establish which modules depend on which other modules (export can remember which imports happened in that same file), collect the information, and then expose it somehow.

A way to expose it would be to graph it out, a bit like:

https://github.com/hilverd/lein-ns-dep-graph

Do you reckon this belongs into Cargo, or as a separate gem that would hook into import/export? (how would it hook then, maybe Cargo should accept import/export hooks?)

txus avatar Oct 10 '14 20:10 txus

I think this probably doesn't belong to Cargo, but to a package manager. I think Cargo's import is a replacement for Ruby's require, and there's still the need for something to replace RubyGems. I have some ideas for a package manager, a few of them expressed in this tool: https://github.com/soveran/pac

But that tool is very limited as the use case is very uncommon (even though it's a real use case, and in that scenario the tool works great). The idea I like is the use of a hashing algorithm for identifying the file, instead of relying on versions. That idea comes from the fact that versions are generated by humans, and usually contain errors, while a shasum of a file is a good guarantee for making sure only the intended file can be loaded. But as I said, that tool is very limited in scope.

I like the idea of having a package manager that allows the addition of plugins, and one of those plugins could be a dependency graph generator.

soveran avatar Oct 13 '14 07:10 soveran

Being the owner of an app using 200 gems in my Gemfile, I feel that package management is a much bigger problem for Ruby than most Rubyists are recognizing.

Rather than replacing Rubygems full-stop, how about making a Cargo-compliant package manager which can interoperate with RubyGems and Bundler? RubyGems could be used purely as a delivery mechanism, i.e. treat it as a source from which to download code, equivalent to fetching from Git or elsewhere. The package manager should be able to load "fully Cargo-ized" gems using a dependency graph (both the gem AND all of it's dependencies would need to be Cargo-ized), and at the same time load gems from Bundler into the global scope (a.k.a. the garbage dump!)

The Cargo-specific package manager would not have to care of things like version conflicts or from where it received gems (Rubygems, Github, Git, path) because it can load n different versions of the same gem. (Ideally you'd want to minimize n, but a proof-of-concept implementation wouldn't require it.)

The biggest problem for adoption would be how non-Cargo gems can become Cargo-ized. If I have to beg each of the owners of the 200 gems I use to replace require with import/export in their repo, clearly that's not going to happen. If however there were an easy way to "wrap" existing gems with a Cargo wrapper and clear migration path, I can see this actually being adopted.

Worth noting the same migration has happened in the Javascript community with the introduction of:

CC @bbozo

johnnyshields avatar Jul 12 '15 11:07 johnnyshields

@johnnyshields i'm very interested in the possibility you're sketching out. i would think one could write a fairly simple wrapper that overrides require and reroutes it to cargo calls.

mooreniemi avatar Jan 05 '16 02:01 mooreniemi

@mooreniemi glad you found this.

require loads variables into the global scope. import pulls whatever is being exported into the local scope of the file which is importing.

If require behaved like import, you'd have to require each lib you need in each file that needs whatever is being required. For example, doing something like this: https://github.com/radar/by_star/blob/master/lib/by_star.rb wouldn't work. So it's not as simple as wrapping require; it requires a structural change where you move from a "global scope dumping-ground" to "declaring your imports when you need them".

The best way forward IMHO is to be able to use Cargo in parallel with require / Bundler / etc and gradually migrate code over as it becomes Cargo-ized.

johnnyshields avatar Jan 05 '16 03:01 johnnyshields

ah yes of course, i see what you mean.

what if the global dump require does can be penned in, and from there, exports done? for the basic use case where someone doesnt want to know about import/export, then you can just export all to the global namespace as default (a pass thru). then as someone wants to exclude, they can at least use the "except" or "as" functionality to begin with

meanwhile someone willing to learn how to fully utilize the system can import per file as normal

mooreniemi avatar Jan 06 '16 02:01 mooreniemi

Any attempt to override require is asking for trouble. In the best possible scenario you may end up with several global dumps, but then you have the problem of how to share variables/constants between the dumps (== lots of code changes.) I don't see what problem this solves, and anyway it's not true isolation at the file-level like import / export provides.

It is perfectly fine to have some libs use require (global dump) and others use import / export (isolation) in the same project. This approach would allow a lib-by-lib migration to the new structure.

johnnyshields avatar Jan 06 '16 02:01 johnnyshields

It's also worth noting that you can make libs dual Cargo and non-Cargo use at the same time. If you add export(Foo) if defined?(export) at the bottom of the file, you can still do require 'my_lib/foo' the file which will dump Foo (and any other constants) to the global dump or MyVar = import('my_lib/foo') in which case you get an isolated Module(<anonymous>)::Foo

johnnyshields avatar Jan 06 '16 02:01 johnnyshields

idk about asking for trouble, but it is constraining by strict dependency. that is a major tradeoff. i agree it makes more sense to be interoperable where possible.

as for what problem it solves, i only see it as a way to first change how people do module loading as a incremental step towards the agreed goal of per-file import/export. it's a way of giving a useful feature (removing from namespace, aliasing) by wrapping require. making require an implementation detail of cargo, essentially.

in terms of what is actionable for a parallel usage, what do you see, other than simply using it in a project as you described? (in other words, what do you see the aim as the convo now, other than to further sketch out the implementation you wish to demonstrate?)

mooreniemi avatar Jan 06 '16 03:01 mooreniemi

I think the aim is to get as many gems / projects using cargo as possible. To do this, we'd need provide a clear upgrade path that:

  • doesn't change the behavior of existing code
  • introduces new methods (import / export) which can be layered onto existing code
  • works with and extends existing tools like Rubygems, Bundler etc.

johnnyshields avatar Jan 06 '16 03:01 johnnyshields

Hello @johnnyshields and @mooreniemi, I'm all for trying out these ideas. Over the years, a lot of people have told me about their need for something like this, so it looks like a legitimate use case.

soveran avatar Jan 06 '16 07:01 soveran

I have a proof of concept for loading cargo-compatible gems, I'll share the code soon :-)

soveran avatar Jan 06 '16 09:01 soveran

@soveran that's great to hear, really looking forward to seeing it.

johnnyshields avatar Jan 06 '16 09:01 johnnyshields

@johnnyshields I just pushed the proof of concept to https://github.com/soveran/packer

soveran avatar Jan 06 '16 11:01 soveran

Wow great start, like Cargo it's only a few lines of code yet very powerful. Let's move the discussion there.

johnnyshields avatar Jan 06 '16 12:01 johnnyshields