Add Infiltrator.jl to Base?

I know that folks often ask to add things to Base here on Discourse, and that usually the correct answer is “no”, but I think there’s a very good reason to add Infiltrator.jl to Base Julia. Let me explain.

Infiltrator.jl is a very useful and performant tool for debugging. However, it has one main drawback. If you want to debug code in a package, you have to add Infiltrator as a project dependency. So whenever I want to use @infiltrate, I have to do ] add Infiltrator and then add using Infiltrator to my package code. And then when I’m done debugging, I have to remember to undo those by deleting using Infiltrator and running ] rm Infiltrator.

If Infiltrator.jl was in Base, then we wouldn’t need to do those things. We would always have a good debugging tool close at hand. Also, Infiltrator.jl is only 511 source lines of code and has no dependencies other than base + stdlib Julia, so it would not be a very large addition to the Julia code base.

5 Likes

I share this concern, but it’s not too hard to write Main.@infiltrate instead of just @infiltrate.

9 Likes

Ohhh, does that work? I was wondering if there was a better way to do this. I guess I should have asked on Discourse instead of Stack Overflow, which I did 22 days ago:

https://stackoverflow.com/questions/71208181/use-infiltrator-jl-on-package-code-without-adding-infitrator-to-project-toml

Hmm, I tried using Main.@infiltrate in my code and I’m getting the error below. Any advice?

julia> using Infiltrator

julia> using PackageA
[ Info: Precompiling PackageA [b7d5e62f-924e-4906-b2b5-430ba4531d91]
ERROR: LoadError: UndefVarError: @infiltrate not defined
Stacktrace:
  [1] #macroexpand#51
    @ ./expr.jl:115 [inlined]
  [2] macroexpand
    @ ./expr.jl:114 [inlined]
  [3] docm(source::LineNumberNode, mod::Module, meta::Any, ex::Any, define::Bool) (repeats 2 times)
    @ Base.Docs ./docs/Docs.jl:537
  [4] (::DocStringExtensions.var"#32#33"{typeof(DocStringExtensions.template_hook)})(::LineNumberNode, ::Vararg{Any})
    @ DocStringExtensions ~/.julia/packages/DocStringExtensions/iscC8/src/templates.jl:11
  [5] var"@doc"(::LineNumberNode, ::Module, ::String, ::Vararg{Any})
    @ Core ./boot.jl:517
  [6] include(mod::Module, _path::String)
    @ Base ./Base.jl:418
  [7] include(x::String)
    @ PackageA ~/projects/PackageA/src/PackageA.jl:1
  [8] top-level scope
    @ ~/projects/PackageA/src/PackageA.jl:12
  [9] include
    @ ./Base.jl:418 [inlined]
 [10] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt64}}, source::Nothing)
    @ Base ./loading.jl:1318
 [11] top-level scope
    @ none:1
 [12] eval
    @ ./boot.jl:373 [inlined]
 [13] eval(x::Expr)
    @ Base.MainInclude ./client.jl:453
 [14] top-level scope
    @ none:1

That shouldn’t be fatal, it just means your package wasn’t fully precompiled

Did you try adding Infiltrator in your default Julia environment? I think that makes it implicitly available in other environments as part of the environment stack in LOAD_PATH?

Then you should be able to do using Infiltrator within your package — it’ll emit some warnings about undeclared dependencies but otherwise work.

That said, I do kinda wish Infiltrator or something like it was part of the REPL stdlib. If there was a macro @REPL to spawn a REPL inside an arbitrary scope that would be pretty great.

21 Likes

Just wrote some (minimal) documentation for this:
https://juliadebug.github.io/Infiltrator.jl/stable/API/#Infiltration-and-exfiltration

There’s also a precompile-safe function form now, which you can use like this:

if isdefined(Main, :Infiltrator)
  Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__)
end
8 Likes

An older thread has additional details / tips that might be helpful for keeping Infiltrator out of your dependencies:

2 Likes

I’ve been waiting here 30 minutes for the debugger to reach my breakpoint just so I can experiment with a few local variables there. I tried @infiltrate first, but it errored since not in my package dependencies (even with Infiltrate in my default environment).

Has there been any movement in the last year to get the Infiltrator functionality into Base?

Did you know that you can write Main.@infiltrate in your package code and it will work if you have Infiltrator loaded in your session? You can edit it in with Revise once the deved package is loaded, otherwise it might complain at load, not sure

3 Likes

That was mentioned in the first reply: Add Infiltrator.jl to Base? - #2 by pdeffebach

Main.@infiltrate wasn’t working when I tried it earlier, but it is working now.
Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) did work earlier, but it’s not a very convenient solution.

That would be a nice convenience, but in the meantime why not just add it as a dependency to your project? It’s a little annoying to have to remember to remove it later when you’re done debugging, but if you don’t add a compat bound for it adding Aqua to your tests will catch it for you.

3 Likes

If you need to quickly grab some variables internal to the function you can use the Ref trick that is discussed in this video. Extremely useful, I use it literally all the time. Between that and Infiltrator I actually never use Debugger when developing the code packages I use for my PhD research.

Basically it works like this. Make a “box” that can hold anything.
Define this in Main.

xx = Ref{Any}()

Then you have a big deep function that the debugger would take forever to reach. You can do

function some_very_deep_function()
# code
# code 
# ...
Main.xx[] = some var

end

After the function finishes you can then play around with that variable in Main.

xx[] # de-ref, do whatever you want, inspect, etc.
3 Likes

If I want to see some local variables in a function for debugging I just declare them as global in that function…

1 Like

try this: ALL_MODULES_EXCEPT_MAIN