Not really rust specific, but how do you manage developing complex and potentially conflicting systems with components reused amongst them. I'm taking products not libraries.
For example, I have v1, and it's great. Now we want v2...great, big blank piece of paper....except some stuff from v1 should be pulled across. But v2 wants to apply lessons learned from v1 and potentially do things differently, better, but in an incompatible way with v2. Some other crates however, can be reused seamlessly between v1 and v2. I explicitly don't want my team to have to manage more versions of a thing then they have to.
Typical ways I've done this before over the years (excluding pre-git):
v1 branch and v2 branch (and v3 branch ...)
No, just no. Merge conflicts abound and the promise of "sharing code between them" falls apart really quickly.
[each coarse grained graph of components is a released and versioned product]
In the workspace of 100 crates, 10 of them are really a single coherent subsystem, so let's release that subsystem as a single crate pushed to the private repo.
Very easy to get "noisy" graphs which require "leafs" (i.e. common crates) so they need to be released. Pretty soon you end up with multiple graphs that each have different versions of common components.
single source tree with nested folders
- common/db
- v1/v1SpecificCrate
- v2/v2SpecificCrate
Cargo.toml
This works beautifully - everything is kept up to date, refactoring across components is a dream, everything fails fast. Wonderful. Except that common crate now differs between v1 and v2...oh. So deep breath, create v1CommonCrate
and v2CommonCrate
and then read 3 chapters of Clean Code and 4 chapters of Code Complete to renew your soul.
And then the next divergence....
copy and paste v1 and v2
I mean....if v1 is essentially dead then maybe.... I'm not going to justify it in any way though.
submodules and subtrees
This is really a tactic to achieve variations of the above, but it very quickly becomes painful as you realise you've fallen into a trap of still needing a release process without the formality of such a process.
So what do you all do?
I think the choice of "which causes the least pain with the most safety" is really context dependent. If you have very little cross over then it doesn't really matter. If you have lots of divergence in common code then it's pain everywhere.
I think I'm settling on a single source tree and avoiding divergence in common modules. If divergence must happen then I think separate crates ruthlessly cut down to only the divergence (so common/NastyCrateCommon
, v1/NastyCrate
, and v2\NastyCrate
where both the v1 and v2 crates just delegate what they can back to the common crate).
Feature gates help, as does a strict separation between my-crate-api
and my-crate-lib
.
So, what do you all do?