Skip to content

Commit a749d1f

Browse files
authored
Path-switching for extensions (#909)
Fixes #858
1 parent f09cf39 commit a749d1f

File tree

2 files changed

+94
-45
lines changed

2 files changed

+94
-45
lines changed

src/pkgs.jl

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -416,55 +416,23 @@ function watch_manifest(mfile::String)
416416
@debug "Pkg" _group="manifest_update" manifest_file=mfile
417417
isfile(mfile) || return nothing
418418
pkgdirs = manifest_paths(mfile)
419+
pathreplacements = Pair{String,String}[]
419420
for (id, pkgdir) in pkgdirs
420421
if haskey(pkgdatas, id)
421422
pkgdata = pkgdatas[id]
422423
if pkgdir != basedir(pkgdata)
423424
## The package directory has changed
424425
@debug "Pkg" _group="pathswitch" oldpath=basedir(pkgdata) newpath=pkgdir
425-
# Stop all associated watching tasks
426-
for dir in unique_dirs(srcfiles(pkgdata))
427-
@debug "Pkg" _group="unwatch" dir=dir
428-
delete!(watched_files, joinpath(basedir(pkgdata), dir))
429-
# Note: if the file is revised, the task(s) will run one more time.
430-
# However, because we've removed the directory from the watch list this will be a no-op,
431-
# and then the tasks will be dropped.
432-
end
433-
# Revise code as needed
434-
files = String[]
435-
mustnotify = false
436-
for file in srcfiles(pkgdata)
437-
fi = try
438-
maybe_parse_from_cache!(pkgdata, file)
439-
catch err
440-
# https://github.com/JuliaLang/julia/issues/42404
441-
# Get the source-text from the package source instead
442-
fi = fileinfo(pkgdata, file)
443-
if isempty(fi.modexsigs) && (!isempty(fi.cachefile) || !isempty(fi.cacheexprs))
444-
filep = joinpath(basedir(pkgdata), file)
445-
src = read(filep, String)
446-
topmod = first(keys(fi.modexsigs))
447-
if parse_source!(fi.modexsigs, src, filep, topmod) === nothing
448-
@error "failed to parse source text for $filep"
449-
end
450-
add_modexs!(fi, fi.cacheexprs)
451-
empty!(fi.cacheexprs)
452-
fi.parsed[] = true
453-
end
454-
fi
455-
end
456-
maybe_extract_sigs!(fi)
457-
push!(revision_queue, (pkgdata, file))
458-
push!(files, file)
459-
mustnotify = true
460-
end
461-
mustnotify && notify(revision_event)
462-
# Update the directory
463-
pkgdata.info.basedir = pkgdir
464-
# Restart watching, if applicable
465-
if has_writable_paths(pkgdata)
466-
init_watching(pkgdata, files)
467-
end
426+
push!(pathreplacements, basedir(pkgdata)=>pkgdir)
427+
switch_basepath(pkgdata, pkgdir)
428+
end
429+
end
430+
end
431+
# Update the paths in the watchlist
432+
for (oldpath, newpath) in pathreplacements
433+
for (_, pkgdata) in pkgdatas
434+
if basedir(pkgdata) == oldpath
435+
switch_basepath(pkgdata, newpath)
468436
end
469437
end
470438
end
@@ -475,6 +443,53 @@ function watch_manifest(mfile::String)
475443
end
476444
end
477445

446+
function switch_basepath(pkgdata::PkgData, newpath::String)
447+
# Stop all associated watching tasks
448+
for dir in unique_dirs(srcfiles(pkgdata))
449+
@debug "Pkg" _group="unwatch" dir=dir
450+
delete!(watched_files, joinpath(basedir(pkgdata), dir))
451+
# Note: if the file is revised, the task(s) will run one more time.
452+
# However, because we've removed the directory from the watch list this will be a no-op,
453+
# and then the tasks will be dropped.
454+
end
455+
# Revise code as needed
456+
files = String[]
457+
mustnotify = false
458+
for file in srcfiles(pkgdata)
459+
fi = try
460+
maybe_parse_from_cache!(pkgdata, file)
461+
catch err
462+
# https://github.com/JuliaLang/julia/issues/42404
463+
# Get the source-text from the package source instead
464+
fi = fileinfo(pkgdata, file)
465+
if isempty(fi.modexsigs) && (!isempty(fi.cachefile) || !isempty(fi.cacheexprs))
466+
filep = joinpath(basedir(pkgdata), file)
467+
src = read(filep, String)
468+
topmod = first(keys(fi.modexsigs))
469+
if parse_source!(fi.modexsigs, src, filep, topmod) === nothing
470+
@error "failed to parse source text for $filep"
471+
end
472+
add_modexs!(fi, fi.cacheexprs)
473+
empty!(fi.cacheexprs)
474+
fi.parsed[] = true
475+
end
476+
fi
477+
end
478+
maybe_extract_sigs!(fi)
479+
push!(revision_queue, (pkgdata, file))
480+
push!(files, file)
481+
mustnotify = true
482+
end
483+
mustnotify && notify(revision_event)
484+
# Update the directory
485+
pkgdata.info.basedir = newpath
486+
# Restart watching, if applicable
487+
if has_writable_paths(pkgdata)
488+
init_watching(pkgdata, files)
489+
end
490+
return nothing
491+
end
492+
478493
function active_project_watcher()
479494
mfile = manifest_file()
480495
if !isnothing(mfile) && mfile watched_manifests

test/runtests.jl

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3096,23 +3096,46 @@ do_test("Utilities") && @testset "Utilities" begin
30963096
end
30973097

30983098
do_test("Switching free/dev") && @testset "Switching free/dev" begin
3099-
function make_a2d(path, val, mode="r"; generate=true)
3099+
function make_a2d(path, val, mode="r"; generate=true, uuid=nothing)
31003100
# Create a new "read-only package" (which mimics how Pkg works when you `add` a package)
3101+
# use generate=false and copy the Project.toml to use the same UUID
31013102
cd(path) do
31023103
pkgpath = normpath(joinpath(path, "A2D"))
31033104
srcpath = joinpath(pkgpath, "src")
31043105
if generate
31053106
Pkg.generate("A2D")
3107+
projpath = joinpath(pkgpath, "Project.toml")
3108+
open(projpath, "a") do io
3109+
write(io, """
3110+
3111+
[weakdeps]
3112+
Dummy = "$uuid"
3113+
3114+
[extensions]
3115+
A2DDummyExt = "Dummy"
3116+
""")
3117+
end
31063118
else
31073119
mkpath(srcpath)
31083120
end
31093121
filepath = joinpath(srcpath, "A2D.jl")
31103122
write(filepath, """
31113123
module A2D
31123124
f() = $val
3125+
function g end
31133126
end
31143127
""")
31153128
chmod(filepath, mode=="r" ? 0o100444 : 0o100644)
3129+
mkdir(joinpath(pkgpath, "ext"))
3130+
extpath = joinpath(pkgpath, "ext", "A2DDummyExt.jl")
3131+
write(extpath, """
3132+
module A2DDummyExt
3133+
using A2D
3134+
using Dummy
3135+
A2D.g() = $val
3136+
end
3137+
""")
3138+
chmod(extpath, mode=="r" ? 0o100444 : 0o100644)
31163139
return pkgpath
31173140
end
31183141
end
@@ -3126,12 +3149,21 @@ do_test("Switching free/dev") && @testset "Switching free/dev" begin
31263149
Base.ACTIVE_PROJECT[] = joinpath(depot, "environments", "v$(VERSION.major).$(VERSION.minor)", "Project.toml")
31273150
mkpath(dirname(Base.ACTIVE_PROJECT[]))
31283151
write(Base.ACTIVE_PROJECT[], "[deps]")
3129-
ropkgpath = make_a2d(depot, 1)
3152+
cd(depot) do
3153+
Pkg.generate("Dummy")
3154+
end
3155+
dummypath = joinpath(depot, "Dummy")
3156+
Pkg.develop(PackageSpec(path=dummypath))
3157+
dummyuuid = match(r"uuid = \"([0-9a-f].*)\"", read(joinpath(dummypath, "Project.toml"), String)).captures[1]
3158+
ropkgpath = make_a2d(depot, 1; uuid=dummyuuid)
31303159
Pkg.develop(PackageSpec(path=ropkgpath))
31313160
sleep(mtimedelay)
31323161
@eval using A2D
31333162
sleep(mtimedelay)
31343163
@test Base.invokelatest(A2D.f) == 1
3164+
@test_throws MethodError Base.invokelatest(A2D.g)
3165+
@eval using Dummy
3166+
@test Base.invokelatest(A2D.g) == 1
31353167
for dir in keys(Revise.watched_files)
31363168
@test !startswith(dir, ropkgpath)
31373169
end
@@ -3145,9 +3177,11 @@ do_test("Switching free/dev") && @testset "Switching free/dev" begin
31453177
Pkg.develop(PackageSpec(path=pkgdevpath))
31463178
@yry()
31473179
@test Base.invokelatest(A2D.f) == 2
3180+
@test Base.invokelatest(A2D.g) == 2
31483181
Pkg.develop(PackageSpec(path=ropkgpath))
31493182
@yry()
31503183
@test Base.invokelatest(A2D.f) == 1
3184+
@test Base.invokelatest(A2D.g) == 1
31513185
for dir in keys(Revise.watched_files)
31523186
@test !startswith(dir, ropkgpath)
31533187
end

0 commit comments

Comments
 (0)