Suppose that I am developing a package P. I want package to have a published package W as a weak dependency, but if the weak dependency is activated then I want another published package C to be automatically loaded.
I can make this happen by making C a strong dependency of P but that seems overkill.
What is the recommended route to make this happen?
Yes, double weak dependencies is what you have to resort to.
Another case is ONNXRunTime.jl. This has support for a CUDA execution provider through a package extension. It would make a lot of sense if you could activate it with just
import ONNXRunTime, CUDA
but sadly you have to do
import ONNXRunTime, CUDA, cuDNN
where cuDNN really is just an implementation detail which leaks into the API.
P actually can arrange for loading P and W to automatically load C, and Requires.jl does just that: the README’s demo section shows P=Reqs automatically loading C=Colors (within Reqs, not on the same global scope) after W=JSON had been loaded. However, that comes with a number of problems that most have moved away from.
If the packages don’t have anything to do with each other, then you only save a using C line at the cost of any environment’s ability to only install and precompile P and W. If this automatic loading isn’t a small part of a granular ecosystem, 1 re-exporting package could load P+W+C more simply than P+W and without that cost.
When a package C outside the trigger packages P and W’s dependency trees is automatically loaded, unbounded chain loading is possible. Say P has some deeply indirect dependency D, and C automatically loads E if D was, then E automatically loads F if P was, then F automatically loads G if W was, etc. Obviously many packages wouldn’t automatically load others, but the probability of encountering another one only rises as the environment explodes.
More of an implementation problem, but we had to know to install C to be available, and W didn’t have a reasonable spot in Project.toml to help out. [weakdeps] didn’t exist yet.
Requires was mostly used for defining methods that mixed a set of trigger packages, and in the cases where a package C would be loaded, it’d usually just hold those methods in order to precompile them (controlled type piracy, no package loading of its own to risk chains). v1.9’s extensions just cleaned up and enforced the best practice.
Note that if extensions were able to load their own strong dependencies, it would also enable unbounded chain loading across dependency trees. Resulting load cycles were a primary reason for the prohibition (Issue #48533), but I’m uncertain if that’s still a problem; given how extensions are specified in a trigger package’s Project.toml, the minimal example in PR #48513 {C → [A,B], A:Bext → C} would be detected as easily as a package-only cycle {C → A, A → C}. I don’t think that extension dependencies have been entirely ruled out, but reining in that problem is still up for debate. For example, strong dependencies of only extensions in a given environment could be excluded as extension triggers, but that incorrectly stops an extension depending on a full trigger package set from loading the proper extension. Many people would be happy if a more reasonable idea gets proven and implemented.