Skip to content

Commit 794fff5

Browse files
authored
Merge pull request #65 from disberd/fix_v12
2 parents bbe82ac + 0052567 commit 794fff5

File tree

15 files changed

+236
-155
lines changed

15 files changed

+236
-155
lines changed

.github/workflows/CI.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,29 @@ concurrency:
1818
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
1919
jobs:
2020
test:
21-
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
21+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }}
2222
runs-on: ${{ matrix.os }}
2323
strategy:
2424
fail-fast: false
2525
matrix:
2626
version:
2727
- '1'
28-
- '1.9'
28+
- 'lts'
2929
- 'pre'
3030
os:
3131
- ubuntu-latest
3232
- macOS-latest
3333
- windows-latest
34-
arch:
35-
- x64
3634
steps:
3735
- uses: actions/checkout@v4
3836
- uses: julia-actions/setup-julia@v2
3937
with:
4038
version: ${{ matrix.version }}
41-
arch: ${{ matrix.arch }}
42-
- uses: julia-actions/cache@v1
39+
- uses: julia-actions/cache@v2
4340
- uses: julia-actions/julia-buildpkg@v1
4441
- uses: julia-actions/julia-runtest@v1
4542
- uses: julia-actions/julia-processcoverage@v1
46-
- uses: codecov/codecov-action@v4
43+
- uses: codecov/codecov-action@v5
4744
with:
48-
files: lcov.info
49-
token: ${{ secrets.CODECOV_TOKEN }}
45+
token: ${{ secrets.CODECOV_TOKEN }}
46+
slug: disberd/PlutoDevMacros.jl

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ docs/build/
55

66
pluto_test*.jl
77
test*.jl
8-
!test/**/*.jl
8+
!test/**/*.jl
9+
10+
# Coverage
11+
coverage/

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1111
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
1212

1313
[compat]
14-
JuliaInterpreter = "0.9"
14+
JuliaInterpreter = "0.9, 0.10"
1515
Logging = "1"
1616
MacroTools = "0.5"
1717
Pkg = "1"
1818
TOML = "1"
19-
julia = "1.9"
19+
julia = "1.10"

src/frompackage/consts.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const IS_DEV = first(fullname(@__MODULE__)) === :Main
2-
const TEMP_MODULE_NAME = Symbol(:_FrompPackage_TempModule_, IS_DEV ? "DEV_" : "")
2+
const TEMP_MODULE_NAME = Symbol(:_FromPackage_TempModule_, IS_DEV ? "DEV_" : "")
33
const STDLIBS_DATA = Dict{String,Base.UUID}()
44
for (uuid, (name, _)) in Pkg.Types.stdlibs()
55
STDLIBS_DATA[name] = uuid
66
end
77
const PREV_CONTROLLER_NAME = Symbol(:_Previous_Controller_, IS_DEV ? "DEV_" : "")
88

9-
const CURRENT_FROMPACKAGE_CONTROLLER = Ref{FromPackageController}()
9+
const CURRENT_FROMPACKAGE_CONTROLLER = Ref{FromPackageController}()
10+
11+
const LOADED_TIMES = Dict{Symbol, Int}()

src/frompackage/helpers.jl

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ end
5353

5454
function _popup_style()
5555
#! format: off
56-
"""
56+
return """
5757
fromparent-container {
5858
height: 20px;
5959
position: fixed;
@@ -171,9 +171,19 @@ function extract_target_path(ex, caller_module::Module; calling_file, notebook_l
171171
return path
172172
end
173173

174+
# Mostly like fullname, but for modules we generated we always have the path from Main
175+
function _fullname(m::Module)
176+
ref = fullname(m) |> collect
177+
rootname = first(ref)
178+
rootname === :Main && return ref # This is already fine
179+
temp_mod = get_temp_module()
180+
return invokelatest(isdefined, temp_mod, rootname) ? prepend!(ref, fullname(temp_mod)) : ref
181+
end
182+
174183
function beautify_package_path(p::FromPackageController)
175184
@nospecialize
176185
modpath..., name = fullname(get_temp_module(p))
186+
isempty(modpath) && return "" # If the module was loaded as roootmodule, on 1.12 it also becomes it's own parent so we don't have modpath
177187
modpath = map(enumerate(modpath)) do (i, s)
178188
Base.isgensym(s) || return String(s)
179189
return "var\"$s\""
@@ -267,7 +277,7 @@ end
267277

268278
function populate_manifest_deps!(p::FromPackageController)
269279
@nospecialize
270-
(;manifest_deps) = p
280+
(; manifest_deps) = p
271281
d = TOML.parsefile(get_manifest_file(p))
272282
for (name, data) in d["deps"]
273283
# We use `only` here because I believe the entry will always contain a single dict wrapped in an array. If we encounter a case where this is not true the only will throw instead of silently taking just the first
@@ -285,13 +295,15 @@ function get_manifest_file(p::FromPackageController)
285295
proj_file = project.file
286296
envdir = dirname(abspath(proj_file))
287297
manifest_file = if mode in (:instantiate, :resolve)
288-
context_kwargs = options.verbose ? (;) : (; io = devnull)
289-
c = Context(;env = EnvCache(proj_file), context_kwargs...)
298+
context_kwargs = options.verbose ? (;) : (; io=devnull)
299+
c = Context(; env=EnvCache(proj_file), context_kwargs...)
290300
resolve = mode === :resolve
291301
if resolve
302+
options.verbose && @info "Resolving Manifest as explicitly requested"
292303
Pkg.resolve(c)
293304
else
294-
Pkg.instantiate(c; update_registry = false, allow_build = false, allow_autoprecomp = false)
305+
options.verbose && @info "Instantiating Manifest as explicitly requested"
306+
Pkg.instantiate(c; update_registry=false, allow_build=false, allow_autoprecomp=false)
295307
end
296308
joinpath(envdir, "Manifest.toml")
297309
else
@@ -338,8 +350,8 @@ function extract_nested_module(starting_module::Module, nested_path; first_dot_s
338350
m = if name === :.
339351
first_dot_skipped ? parentmodule(m) : m
340352
else
341-
@assert isdefined(m, name) "The module `$name` could not be found inside parent module `$(nameof(m))`"
342-
getproperty(m, name)::Module
353+
@assert invokelatest(isdefined, m, name) "The module `$name` could not be found inside parent module `$(nameof(m))`"
354+
invokelatest(getproperty, m, name)::Module
343355
end
344356
first_dot_skipped = true
345357
end
@@ -351,20 +363,24 @@ unique_module_name(m::Module) = Symbol(Base.PkgId(m))
351363
unique_module_name(uuid::Base.UUID, name::AbstractString) = Symbol(Base.PkgId(uuid, name))
352364

353365
function get_temp_module()
354-
if isdefined(Main, TEMP_MODULE_NAME)
355-
getproperty(Main, TEMP_MODULE_NAME)::Module
356-
else
357-
Core.eval(Main, :(module $TEMP_MODULE_NAME
358-
module _LoadedModules_ end
359-
module _DirectDeps_ end
360-
end))::Module
366+
invokelatest() do
367+
if isdefined(Main, TEMP_MODULE_NAME)
368+
getproperty(Main, TEMP_MODULE_NAME)::Module
369+
else
370+
Core.eval(Main, :(module $TEMP_MODULE_NAME
371+
module _LoadedModules_ end
372+
module _DirectDeps_ end
373+
end))::Module
374+
end
361375
end
362376
end
363377
get_temp_module(s::Symbol) = get_temp_module([s])
364378
function get_temp_module(names::Vector{Symbol})
365-
temp = get_temp_module()
366-
out = extract_nested_module(temp, names)::Module
367-
return out
379+
invokelatest(names) do names
380+
temp = get_temp_module()
381+
out = extract_nested_module(temp, names)::Module
382+
return out
383+
end
368384
end
369385
function get_temp_module(::FromPackageController{name}) where {name}
370386
@nospecialize
@@ -374,38 +390,44 @@ end
374390
get_loaded_modules_mod() = get_temp_module(:_LoadedModules_)::Module
375391
get_direct_deps_mod() = get_temp_module(:_DirectDeps_)::Module
376392

377-
function populate_loaded_modules(; verbose=false)
378-
loaded_modules = get_loaded_modules_mod()
379-
@lock Base.require_lock begin
380-
for (id, m) in Base.loaded_modules
381-
name = Symbol(id)
382-
isdefined(loaded_modules, name) && continue
383-
Core.eval(loaded_modules, :(const $name = $m))
393+
function populate_loaded_modules(p::Union{FromPackageController, Nothing} = nothing; verbose=false)
394+
invokelatest() do
395+
loaded_modules = get_loaded_modules_mod()
396+
@lock Base.require_lock begin
397+
for (id, m) in Base.loaded_modules
398+
# We check and eventually skip the target package from the loaded modules
399+
if p isa FromPackageController
400+
p.project.name == id.name && p.project.uuid == id.uuid && continue
401+
end
402+
name = Symbol(id)
403+
isdefined(loaded_modules, name) && continue
404+
Core.eval(loaded_modules, :(const $name = $m))
405+
end
384406
end
385-
end
386-
callbacks = Base.package_callbacks
387-
if mirror_package_callback callbacks
388-
for i in reverse(eachindex(callbacks))
389-
# This part is only useful when developing this package itself
390-
f = callbacks[i]
391-
nameof(f) === :mirror_package_callback || continue
392-
owner = parentmodule(f)
393-
nameof(owner) === nameof(@__MODULE__) || continue
394-
isdefined(owner, :IS_DEV) && owner.IS_DEV || continue
395-
# We delete this as it's a previous version of the mirror_package_callback function
396-
verbose && @warn "Deleting previous version of package_callback function"
397-
deleteat!(callbacks, i)
407+
callbacks = Base.package_callbacks
408+
if mirror_package_callback callbacks
409+
for i in reverse(eachindex(callbacks))
410+
# This part is only useful when developing this package itself
411+
f = callbacks[i]
412+
nameof(f) === :mirror_package_callback || continue
413+
owner = parentmodule(f)
414+
nameof(owner) === nameof(@__MODULE__) || continue
415+
isdefined(owner, :IS_DEV) && owner.IS_DEV || continue
416+
# We delete this as it's a previous version of the mirror_package_callback function
417+
verbose && @warn "Deleting previous version of package_callback function"
418+
deleteat!(callbacks, i)
419+
end
420+
# Add the package callback if not already present
421+
push!(callbacks, mirror_package_callback)
398422
end
399-
# Add the package callback if not already present
400-
push!(callbacks, mirror_package_callback)
401423
end
402424
end
403425

404426
# This function will extract a module from the _LoadedModules_ module which will be populated when each package is loaded in julia
405427
function get_dep_from_loaded_modules(key::Symbol)
406428
loaded_modules = get_loaded_modules_mod()
407429
isdefined(loaded_modules, key) || error("The module $key can not be found in the loaded modules.")
408-
m = getproperty(loaded_modules, key)::Module
430+
m = invokelatest(getproperty, loaded_modules, key)::Module
409431
return m
410432
end
411433
# This is internally calls the previous function, allowing to control which packages can be loaded (by default only direct dependencies and stdlibs are allowed)
@@ -443,10 +465,10 @@ end
443465

444466
# Basically Base.names but ignores names that are not defined in the module and allows to restrict to only exported names (since 1.11 added also public names as out of names). It also defaults `all` and `imported` to true (to be more precise, to the opposite of `only_exported`)
445467
function _names(m::Module; only_exported=false, all=!only_exported, imported=!only_exported, kwargs...)
446-
mod_names = names(m; all, imported, kwargs...)
468+
mod_names = invokelatest(names, m; all, imported, kwargs...)
447469
filter!(mod_names) do nm
448-
isdefined(m, nm) || return false
449-
only_exported && return Base.isexported(m, nm)
470+
invokelatest(isdefined, m, nm) || return false
471+
only_exported && return invokelatest(Base.isexported, m, nm)
450472
return true
451473
end
452474
end

src/frompackage/imports_helpers.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ function process_modpath!(mwn::ModuleWithNames, p::FromPackageController{name};
111111
if root_name in (:ParentModule, :<)
112112
@assert !isnothing(p.target_module) "You can't import from the Parent Module when the calling file is not a file `included` in the target package."
113113
m = p.target_module
114-
prepend!(path, fullname(m))
114+
prepend!(path, _fullname(m))
115115
elseif root_name in (:PackageModule, :^, name)
116116
m = get_temp_module(p)
117-
prepend!(path, fullname(m))
117+
prepend!(path, _fullname(m))
118118
elseif root_name === :>
119119
# Deps import
120120
@assert !is_catchall(mwn) "You can't use the catch-all expression when importing from dependencies"
@@ -128,13 +128,13 @@ function process_modpath!(mwn::ModuleWithNames, p::FromPackageController{name};
128128
imported = mwn.imported
129129
@assert isempty(imported) "You can't use the catchall import statement `import *` with explicitly imported names"
130130
m = @something p.target_module get_temp_module(p)
131-
prepend!(path, fullname(m))
131+
prepend!(path, _fullname(m))
132132
push!(mwn.imported, ImportAs(:*))
133133
elseif root_name === :.
134134
@assert inner || !isnothing(p.target_module) "You can't use relative imports when the calling file is not a file `included` in the target package."
135135
starting_module = @something p.target_module get_temp_module(p)
136136
m = extract_nested_module(starting_module, path; first_dot_skipped=true)
137-
modname.original = fullname(m) |> collect
137+
modname.original = _fullname(m) |> collect
138138
else
139139
error("The provided import statement is not a valid input for the @frompackage macro.\nIf you want to import from a dependency of the target package, prepend `>.` in front of the package name, e.g. `using >.BenchmarkTools`.")
140140
end
@@ -156,7 +156,7 @@ end
156156
# This function will include all the names of the module as explicit imports in the import statement. It will modify the provided mwn in place and unless usings are excluded, it will also add all the using statements being parsed while evaluating the target module
157157
function catchall_import_expression!(mwn::ModuleWithNames, p::FromPackageController, m::Module; exclude_usings::Bool)
158158
@nospecialize
159-
mwn.imported = filterednames(p, m) .|> ImportAs
159+
mwn.imported = invokelatest(filterednames, p, m) .|> ImportAs
160160
ex = reconstruct_import_statement(mwn; head=:import)
161161
# If we exclude using, we simply return the expression
162162
exclude_usings && return ex
@@ -188,7 +188,8 @@ function complete_imported_names!(mwn::ModuleWithNames, p::FromPackageController
188188
# Here we do not modify the list of explicitily imported names, as it's better to get an error if you explicitly import something that was already defined in the notebook
189189
return reconstruct_import_statement(mwn; head=:import)
190190
end
191-
m = extract_nested_module(Main, mwn.modname.original)
191+
nested_path = mwn.modname.original
192+
m = extract_nested_module(Main, nested_path)
192193
if catchall
193194
# We extract all the names, potentially include usings encountered
194195
return catchall_import_expression!(mwn, p, m; exclude_usings)

src/frompackage/input_parsing.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ end
3636

3737
# This function will parse the input expression and eventually
3838
function process_input_expr(p::FromPackageController, ex)
39-
# Eventually remove `@exclude_using`
40-
exclude_usings = should_exclude_using_names!(ex)
41-
return process_import_statement(p, ex; exclude_usings, inner=false)
39+
try
40+
# Eventually remove `@exclude_using`
41+
exclude_usings = should_exclude_using_names!(ex)
42+
return process_import_statement(p, ex; exclude_usings, inner=false)
43+
catch
44+
@error "Error processing input expression" ex
45+
rethrow()
46+
end
4247
end
4348

4449
function excluded_names(p::FromPackageController)

src/frompackage/loading.jl

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
function maybe_call_init(m::Module)
2-
# Check if it exists
3-
isdefined(m, :__init__) || return nothing
4-
# Check if it's owned by this module
5-
which(m, :__init__) === m || return nothing
6-
f = getproperty(m, :__init__)
7-
# Verify that is a function
8-
f isa Function || return nothing
9-
Core.eval(m, :(__init__()))
2+
invokelatest() do
3+
# Check if it exists
4+
isdefined(m, :__init__) || return nothing
5+
# Check if it's owned by this module
6+
which(m, :__init__) === m || return nothing
7+
f = getproperty(m, :__init__)
8+
# Verify that is a function
9+
f isa Function || return nothing
10+
Core.eval(m, :(__init__()))
11+
end
1012
return nothing
1113
end
1214

@@ -47,7 +49,7 @@ end
4749

4850
## Misc ##
4951
# Returns the name (as Symbol) of the variable where the controller will be stored within the generated module
50-
variable_name(p::FromPackageController) = (@nospecialize; :_frompackage_controller_)
52+
variable_name(p::FromPackageController) = (@nospecialize; return :_frompackage_controller_)
5153

5254
# This is a callback to add any new loaded package to the Main._FromPackage_TempModule_._LoadedModules_ module
5355
function mirror_package_callback(modkey::Base.PkgId)
@@ -81,8 +83,10 @@ function try_load_extensions!(p::FromPackageController)
8183
options.verbose && @info "Loading code of extension $name for package $package_name"
8284
entry_path = find_ext_path(p.project, name)
8385
# Set the module to the package module parent, which is a temp module in the Pluto workspace
84-
p.current_module = get_temp_module(p) |> parentmodule
86+
p.current_module = get_temp_module()
8587
try
88+
# We have to recreate the module of the extension, as ExprSplitter will not recreate it if it's already there and it will still have inside a reference to an old version of the package of interest. Not sure if this was a non-found bug before or it's only a problem with either julia 1.12 or JuliaIntepreterv0.10
89+
Core.eval(p.current_module, :(module $(name |> Symbol) end))
8690
# Load the extension module inside the package module
8791
process_include_expr!(p, entry_path)
8892
push!(p.loaded_extensions, name)
@@ -130,9 +134,12 @@ function load_module!(p::FromPackageController{name}; reset=true) where {name}
130134
maybe_call_init(get_temp_module(p))
131135
# We populate the loaded modules
132136
(; verbose) = p.options
133-
populate_loaded_modules(;verbose)
137+
populate_loaded_modules(p ;verbose)
134138
# Try loading extensions
135139
try_load_extensions!(p)
140+
# We increment the number of loads
141+
LOADED_TIMES[name] = get!(LOADED_TIMES, name, 0) + 1
142+
p.nloads = LOADED_TIMES[name]
136143
return p
137144
end
138145

@@ -183,6 +190,7 @@ function register_target_as_root(p::FromPackageController)
183190
@lock Base.require_lock begin
184191
# Set the uuid of this module with the C API. This is required to get the correct UUID just from the module within `register_root_module`
185192
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), m, uuid)
193+
VERSION > v"1.11.99" && ccall(:jl_set_module_parent, Cvoid, (Any, Any), m, m) # We need to also set the module as parent of itself in 1.12
186194
# Register this module as root
187195
logger = verbose ? Logging.current_logger() : Logging.NullLogger()
188196
Logging.with_logger(logger) do

0 commit comments

Comments
 (0)