I developed quite a few small packages with self-contained functionality, but inevitably during development I need complex dependencies (eg. Plots.jl) for functionality not required by the package itself, but required for e.g. illustrating usage, debugging, prototyping etc.
Unfortunately (and this is not a comment agains Plots) packages such as Plots have complex dependencies that (for me) can occassionally break in interesting, non-trivial ways. I also like to keep my source packages clean so the corresponding CI/CD/Container images are equally sparse.
Which brings me to my question, what is your workflow for this kind of scenario? Do you keep the complex dependencies in your base/main environment (but this can clash), or do you keep it all in the package with optional flags?
Or is there a middle way by stacking environments?
Aside from REPL, and especially for plots, notebooks are very convenient. There, all extra deps are managed automatically: Pluto adds packages automatically when you do using SomePackage.
For one-off debugging et cetera, a temp environment that devs my package:
(@v1.10) pkg> activate --temp
Activating new project at `/var/folders/57/1z4jgn5133lbbbrnnt4vf_zw0000gn/T/jl_IoTzyY`
(jl_IoTzyY) pkg> add Plots
Resolving package versions...
[...]
(jl_IoTzyY) pkg> dev path/to/MyPackage
julia> # do stuff with Plots and MyPackage
For more persistent needs, the same, but rather than a temp environment, I create a regular env in a subfolder like path/to/MyPackage/scripts. If you make sure to use the relative path, dev .., this solution is portable and allows you to copy the project around, push to github, et cetera, without breaking the scripts environment.
Or, as @aplavin said, a Pluto.jl notebook somewhere like path/to/MyPackage/notebooks, where the first cell looks something like
begin
import Pkg
Pkg.dev(path="..")
Pkg.add(["Plots"])
end
For exactly this use case I use shared / stacked environments, with ShareAdd.jl package (disclosure: I’m the author) to simplify their usage and management.