Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some enhacements to is_loaded_directly #1880

Merged
merged 6 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
function is_loaded_directly()
try
@static if VERSION < v"1.11.0-"
@debug "is_loaded_directly: VERSION < 1.11.0-"
# Check if were loaded from another package
# if VERSION < 1.7.*, only the "other" package will have the
# _tryrequire_from_serialized in the backtrace.
Expand All @@ -12,22 +13,51 @@
# or one with four arguments (hence five as the function name is the first argument)
# 'using Package' serialized will have a version with less arguments
bt = Base.process_backtrace(Base.backtrace())
@debug "is_loaded_directly: full backtrace:\n$(sprint(show, "text/plain", bt))"
Base.filter!(sf -> sf[1].func === :_tryrequire_from_serialized, bt)
return length(bt) == 0 ||
(length(bt) == 1 && length(only(bt)[1].linfo.specTypes.parameters) < 4)
length_bt = length(bt)
@debug "is_loaded_directly: `_tryrequire_from_serialized` appears $(length_bt) times in backtrace"
length_bt == 0 && return true
length_bt != 1 && return false
params = only(bt)[1].linfo.specTypes.parameters
@debug "is_loaded_directly: `_tryrequire_from_serialized` gets called with parameters of type $(params)"
return length(params) < 4
else
@debug "is_loaded_directly: VERSION >= 1.11.0-"
# Starting with julia 1.11, the package loading was completely revamped.
# The only difference in the callstack is the line number of the call to _include_from_serialized
# inside of the _require_search_from_serialized function.
# To make it a bit more robust, we check the difference between the line number of the beginning
# of _require_search_from_serialized and the call to _include_from_serialized.
# For `using OtherPackage`, the difference is 61, while for `using Package`, the difference is 75 or 78
# (on all 1.11 pre-releases up to 1.11.0-rc1 and 1.12.0-DEV.896, which are the newest at the time of writing this).
# for 1.12.0-DEV.1322 the differences are 72 and 88
bt = Base.process_backtrace(Base.backtrace())
@debug "is_loaded_directly: full backtrace:\n$(sprint(show, "text/plain", bt))"
Base.filter!(sf -> contains(string(sf[1].func), "_require_search_from_serialized"), bt)
length_bt = length(bt)
@debug "is_loaded_directly: `_require_search_from_serialized` appears $(length_bt) times in backtrace; expected 1"
bt_entry = only(bt)[1]
return bt_entry.line - bt_entry.linfo.def.line >= 73
line_call = bt_entry.line
line_funcbegin = bt_entry.linfo.def.line
line_difference = line_call - line_funcbegin
@debug "is_loaded_directly: `_require_search_from_serialized` called at line $line_call, function begins at line $line_funcbegin, difference $(line_difference)"
# difference for `using Package` / `using OtherPackage`
# 1.11.0-alpha1: 75 / 61
# 1.11.0-alpha2: 75 / 61
# 1.11.0-beta1: 75 / 61
# 1.11.0-beta2: 75 / 61
# 1.11.0-rc1: 75 / 61
# 1.11.0-rc2: 78 / 61
# 1.11.0-rc3: 78 / 61
# 1.11.0-rc4: 77 / 61
# 1.11.0: 77 / 61
# 1.11.1: 77 / 61
# 1.12.0-DEV.896: 78 / 61 # ignored
# 1.12.0-DEV.1322: 88 / 72 # ignored
# 1.12.0-DEV.1506: 106 / 93
@static if v"1.11.0-" < VERSION < v"1.12.0-DEV.1506"

Check warning on line 56 in src/utils.jl

View check run for this annotation

Codecov / codecov/patch

src/utils.jl#L56

Added line #L56 was not covered by tests
return line_difference >= 73
else # v"1.12.0-DEV.1506" <= VERSION
return line_difference >= 100
end
end
catch e
@debug "Error while checking if loaded directly" exception=(e, Base.catch_backtrace())
Expand Down
1 change: 1 addition & 0 deletions test/utils/Banners/ModA/src/ModA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module ModA
import AbstractAlgebra: should_show_banner

function __init__()
@debug "__init__ of ModA"
if should_show_banner()
println("Banner of ModA")
end
Expand Down
1 change: 1 addition & 0 deletions test/utils/Banners/ModB/src/ModB.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import AbstractAlgebra: should_show_banner
using ModA

function __init__()
@debug "__init__ of ModB"
if should_show_banner()
println("Banner of ModB")
end
Expand Down
32 changes: 16 additions & 16 deletions test/utils/Banners/banners.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using Pkg
using Pkg, Test

@testset "Banners" begin

function run_repl_code(code::String, proj::String)
bin = Base.julia_cmd()
opts = ["--project=$proj", "-i", "-e", "$code; exit();"]
opts = ["--startup-file=no", "--project=$proj", "-i", "-e", "$code; exit();"]
cmd = Cmd(`$bin $opts`, ignorestatus=true)
outs = IOBuffer()
errs = IOBuffer()
proc = run(pipeline(`$cmd`, stderr=errs, stdout=outs))
result = String(take!(outs))
proc = run(pipeline(addenv(`$cmd`, "JULIA_DEBUG" => "AbstractAlgebra,ModA,ModB"), stderr=errs, stdout=outs))
out = String(take!(outs))
err = String(take!(errs))
return result, err, proc.exitcode
return out, err, proc.exitcode
end

# Set up a separate temporary project for some modules that depend on each
Expand All @@ -33,43 +33,43 @@ using Pkg
Pkg.develop(path=raw"$modcdir");
Pkg.precompile();
"""
out,err,exitcode = run_repl_code(code, td)
out, err, exitcode = run_repl_code(code, td)
res = @test exitcode == 0
if res isa Test.Fail
println("out\n$out")
println("err\n$err")
println("OUT:\n$out")
println("ERR:\n$err")
end

# Banner of ModA shows
out, err = run_repl_code("using ModA;", td)
res = @test strip(out) == "Banner of ModA"
if res isa Test.Fail
println("out\n$out")
println("err\n$err")
println("OUT:\n$out")
println("ERR:\n$err")
end

# Banner of ModB shows, but ModA is supressed
out, err = run_repl_code("using ModB;", td)
res = @test strip(out) == "Banner of ModB"
if res isa Test.Fail
println("out\n$out")
println("err\n$err")
println("OUT:\n$out")
println("ERR:\n$err")
end

# Banner of ModB shows, but ModA is supressed, even if ModA is specifically
# used after ModB
out, err = run_repl_code("using ModB; using ModA;", td)
res = @test strip(out) == "Banner of ModB"
if res isa Test.Fail
println("out\n$out")
println("err\n$err")
println("OUT:\n$out")
println("ERR:\n$err")
end

# Banner does not show when our module is a dependency
out, err = run_repl_code("using ModC;", td)
res = @test strip(out) == ""
if res isa Test.Fail
println("out\n$out")
println("err\n$err")
println("OUT:\n$out")
println("ERR:\n$err")
end
end
Loading