In most build systems and development environments, there tends to be exactly one dependency tree per project - each module depends on a constant set of other modules to do its job, with the only leniency being different backwards-compatible versions of the same module. I'm currently going through several circles of hell trying to update such a dependency tree, where many non-backwards-compatible elements depend on a library that has been deprecated due to security vulnerabilities.
What I would be interested to know is if there are viable build systems and languages that allow multiple alternate modules to be selected automatically - the idea is that if you have multiple dependencies that can all solve the same problem, but not necessarily the same way or within the same amount of generality, the build system can choose the best dependency from that list and inform the compilation or runtime of which dependency was used, allowing it to choose the implementation that uses that dependency.
In this way, dependencies first in the preference list can be jettisoned in favor of later ones, either per platform to choose the one that works best on each, per compilation to create a sort of A/B test for different binaries, or permanently when a security vulnerability is discovered. This would also help the dependency tree to be less rigid, as alternatives would be available if the dev team ever wanted to cut off a dependency for other reasons (such as expense).
Does something like this exist, or are there better ways to solve the problem? I imagine that the biggest limiter is language support - anything with a set of static import statements at the top of the file, where you can't modify it with macros or check import errors at runtime, is not going to support this, so Java, Python, and modern JavaScript are right out. There's also the issue of having to write alternate implementations for each dependency anyway, which likely not enough software houses want to do. If a solution like this does exist, what kinds of limitations does it face?
EDIT: To highlight more directly why this might be useful:
- Permanent removal of dependencies due to security or expense. The tradeoff here is that the change occurs earlier than when the need arises - it might not ever actually be needed, but you have more time to do it and are more likely to do it right.
- Optimal dependency satisfaction among multiple libraries. For instance, if you have three separate dependencies, one of which uses dependency alternates (A, B, C), one of which uses (A, B, D), and one of which uses (B, C, E), the build system can just load B to satisfy all of them. Such a benefit would be more pronounced if you're writing a software library used by multiple external clients.
- A/B testing. Each dependency in an alternate set has different performance and usability characteristics, and it would be easy to release unique combinations to different users.
- OS build differentiation. A certain dependency might perform better on Windows x64 than a different dependency with the same purpose on Linux ARM64, even though a version for both does exist. (I am aware that most build systems let you accomplish this using different OS versions of an intermediate library).