Typically for development packages, including the defaults such as InteractiveUtils, we install them into the global environment, and are able to access them from the REPL, no matter what project we are in. Sometimes rather than executing code in the REPL, it’s easier to modify some file within a project temporarily, just to get some information. In this case, I would like to modify part of my package to have using InteractiveUtils: @code_warntype and then use @code_warntype. Currently, to do so, I have to add InteractiveUtils as a package dependency, but I would quite like to be able to specify that I want certain packages to have access to the global environment in the environment stack, e.g. like setting JULIA_DEBUG='mypackage' but instead JULIA_REPLENV='mypackage' or similar.
Is there currently a better/existing way to achieve this? Would it be possible to have some startup code or a small utility package like TestEnv to do this?
The Infiltrator documentation has a tip about this, that I find incredibly useful: the following snippet allows package code to access Infiltrator when it’s loaded in an interactive session (even though it’s not declared as a dependency of the package itself)
if isdefined(Main, :Infiltrator)
Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__)
end
I think the technique could be extended to many similar situations. For example, it should be possible to access @code_warntype from InteractiveUtils using something like this:
if isdefined(Main, :InteractiveUtils)
interactiveutils = getproperty(Main, :InteractiveUtils)
@eval $(interactiveutils).@code_warntype foo(x, y)
end
I think the technique could be extended to many similar situations. For example, it should be possible to access @code_warntype from InteractiveUtils using something like this:
if isdefined(Main, :InteractiveUtils)
interactiveutils = getproperty(Main, :InteractiveUtils)
@eval $(interactiveutils).@code_warntype foo(x, y)
end
If I try this, I get an error like the following at import time
ERROR: LoadError: ArgumentError: Package FooPackageDependency not found in current path.
- Run `import Pkg; Pkg.add("FooPackageDependency")` to install the FooPackageDependency package.
Stacktrace:
[1] macro expansion
@ ./loading.jl:1630 [inlined]
[2] macro expansion
@ ./lock.jl:267 [inlined]
[3] require(into::Module, mod::Symbol)
@ Base ./loading.jl:1611
[4] include(fname::String)
@ Base.MainInclude ./client.jl:478
[5] top-level scope
@ none:1
Where I’m importing Package which is where I’m pasting the snippet, which has FooPackageDependency in its dependencies, but I’m in an environment with Package in the dependencies and not FooPackageDependency.
I guess this is probably because I’m trying to use a macro which makes stuff a bit more difficult.
Can we make a macro for it?
And then put that macro in startup.jl?
I think maybe we could, at least for the calling a function from a module imported into Main case, but then I think we may have the same problem, the macro is defined in Main and we want to have it in MyPackage.
Perhaps if there was some way to hack at the MyPackage’s stack before `MyPackage is loaded? – But I don’t know whether this is even possible/makes sense/where to start. Also I guess this might trigger a bunch of recompilation.
Does it always work, or only in interactive contexts (where InteractiveUtils is automatically loaded)?
I’m not sure it’s necessary to put anything in Base, because Main seems to be accessible everywhere as well. For example, something like this seems to work (from my very limited testing):
# startup.jl
macro infiltrate_from_main()
file = String(__source__.file); @show file
line = __source__.line; @show line
quote
if isdefined(Main, :Infiltrator)
Main.infiltrate($__module__, Base.@locals, $file, $line)
end
end
end
# package code
module Foo
function foo()
Main.@infiltrate_from_main
end
end #module
Could it be because precompilation is not happy with such constructs?
It seems (again, from very limited testing) that when debugging, it’s a bit more reliable to first load the package to be debugged, unaltered. And only afterwards modify its sources to incorporate calls to Infiltrator/InteractiveUtils/whatever, relying on Revise to make things work.