Skip to content

Commit e9d25ca

Browse files
authored
Add Base.isrelocatable(pkg) (#53906)
This PR adds a utility function `isrelocatable(pkg)` that can be used to check if `pkg` is already precompiled and if the associated cachefile is relocatable. The reason to implicitly perform the `isprecompiled` check is that the exact same computation needs to be done to find the right `.ji`. A `pkg` is said to be relocatable if 1. all `include()` paths are relocatable (they start with `@depot`), 2. all `include_dependency()` paths are relocatable (they start with `@depot` and `track_content=true` was used to include them).
1 parent a3f710e commit e9d25ca

File tree

2 files changed

+79
-17
lines changed

2 files changed

+79
-17
lines changed

base/loading.jl

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,25 +1678,13 @@ end
16781678
# should sync with the types of arguments of `stale_cachefile`
16791679
const StaleCacheKey = Tuple{Base.PkgId, UInt128, String, String}
16801680

1681-
"""
1682-
Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
1683-
1684-
Returns whether a given PkgId within the active project is precompiled.
1685-
1686-
By default this check observes the same approach that code loading takes
1687-
with respect to when different versions of dependencies are currently loaded
1688-
to that which is expected. To ignore loaded modules and answer as if in a
1689-
fresh julia session specify `ignore_loaded=true`.
1690-
1691-
!!! compat "Julia 1.10"
1692-
This function requires at least Julia 1.10.
1693-
"""
1694-
function isprecompiled(pkg::PkgId;
1681+
function compilecache_path(pkg::PkgId;
16951682
ignore_loaded::Bool=false,
16961683
stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
16971684
cachepaths::Vector{String}=Base.find_all_in_cache_path(pkg),
16981685
sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
16991686
flags::CacheFlags=CacheFlags())
1687+
path = nothing
17001688
isnothing(sourcepath) && error("Cannot locate source for $(repr("text/plain", pkg))")
17011689
for path_to_try in cachepaths
17021690
staledeps = stale_cachefile(sourcepath, path_to_try, ignore_loaded = true, requested_flags=flags)
@@ -1728,10 +1716,64 @@ function isprecompiled(pkg::PkgId;
17281716
# file might be read-only and then we fail to update timestamp, which is fine
17291717
ex isa IOError || rethrow()
17301718
end
1731-
return true
1719+
path = path_to_try
1720+
break
17321721
@label check_next_path
17331722
end
1734-
return false
1723+
return path
1724+
end
1725+
1726+
"""
1727+
Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
1728+
1729+
Returns whether a given PkgId within the active project is precompiled.
1730+
1731+
By default this check observes the same approach that code loading takes
1732+
with respect to when different versions of dependencies are currently loaded
1733+
to that which is expected. To ignore loaded modules and answer as if in a
1734+
fresh julia session specify `ignore_loaded=true`.
1735+
1736+
!!! compat "Julia 1.10"
1737+
This function requires at least Julia 1.10.
1738+
"""
1739+
function isprecompiled(pkg::PkgId;
1740+
ignore_loaded::Bool=false,
1741+
stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
1742+
cachepaths::Vector{String}=Base.find_all_in_cache_path(pkg),
1743+
sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
1744+
flags::CacheFlags=CacheFlags())
1745+
path = compilecache_path(pkg; ignore_loaded, stale_cache, cachepaths, sourcepath, flags)
1746+
return !isnothing(path)
1747+
end
1748+
1749+
"""
1750+
Base.isrelocatable(pkg::PkgId)
1751+
1752+
Returns whether a given PkgId within the active project is precompiled and the
1753+
associated cache is relocatable.
1754+
1755+
!!! compat "Julia 1.11"
1756+
This function requires at least Julia 1.11.
1757+
"""
1758+
function isrelocatable(pkg::PkgId)
1759+
path = compilecache_path(pkg)
1760+
isnothing(path) && return false
1761+
io = open(path, "r")
1762+
try
1763+
iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile."))
1764+
_, (includes, includes_srcfiles, _), _... = _parse_cache_header(io, path)
1765+
for inc in includes
1766+
!startswith(inc.filename, "@depot") && return false
1767+
if inc includes_srcfiles
1768+
# its an include_dependency
1769+
track_content = inc.mtime == -1.0
1770+
track_content || return false
1771+
end
1772+
end
1773+
finally
1774+
close(io)
1775+
end
1776+
return true
17351777
end
17361778

17371779
# search for a precompile cache file to load, after some various checks
@@ -3064,7 +3106,7 @@ function resolve_depot(inc::AbstractString)
30643106
end
30653107

30663108

3067-
function parse_cache_header(f::IO, cachefile::AbstractString)
3109+
function _parse_cache_header(f::IO, cachefile::AbstractString)
30683110
flags = read(f, UInt8)
30693111
modules = Vector{Pair{PkgId, UInt64}}()
30703112
while true
@@ -3148,6 +3190,13 @@ function parse_cache_header(f::IO, cachefile::AbstractString)
31483190

31493191
srcfiles = srctext_files(f, srctextpos, includes)
31503192

3193+
return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
3194+
end
3195+
3196+
function parse_cache_header(f::IO, cachefile::AbstractString)
3197+
modules, (includes, srcfiles, requires), required_modules,
3198+
srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile)
3199+
31513200
includes_srcfiles = CacheHeaderIncludes[]
31523201
includes_depfiles = CacheHeaderIncludes[]
31533202
for (i, inc) in enumerate(includes)

test/relocatedepot.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ if !test_relocated_depot
7878
cachefiles = Base.find_all_in_cache_path(pkg)
7979
rm.(cachefiles, force=true)
8080
@test Base.isprecompiled(pkg) == false
81+
@test Base.isrelocatable(pkg) == false # because not precompiled
8182
Base.require(pkg)
8283
@test Base.isprecompiled(pkg, ignore_loaded=true) == true
84+
@test Base.isrelocatable(pkg) == true
8385
end
8486
end
8587

@@ -93,10 +95,12 @@ if !test_relocated_depot
9395
rm.(cachefiles, force=true)
9496
rm(joinpath(@__DIR__, pkgname, "src", "foodir"), force=true, recursive=true)
9597
@test Base.isprecompiled(pkg) == false
98+
@test Base.isrelocatable(pkg) == false # because not precompiled
9699
touch(joinpath(@__DIR__, pkgname, "src", "foo.txt"))
97100
mkdir(joinpath(@__DIR__, pkgname, "src", "foodir"))
98101
Base.require(pkg)
99102
@test Base.isprecompiled(pkg, ignore_loaded=true) == true
103+
@test Base.isrelocatable(pkg) == false # because tracked by mtime
100104
end
101105
end
102106

@@ -110,10 +114,12 @@ if !test_relocated_depot
110114
rm.(cachefiles, force=true)
111115
rm(joinpath(@__DIR__, pkgname, "src", "bardir"), force=true, recursive=true)
112116
@test Base.isprecompiled(pkg) == false
117+
@test Base.isrelocatable(pkg) == false # because not precompiled
113118
touch(joinpath(@__DIR__, pkgname, "src", "bar.txt"))
114119
mkdir(joinpath(@__DIR__, pkgname, "src", "bardir"))
115120
Base.require(pkg)
116121
@test Base.isprecompiled(pkg, ignore_loaded=true) == true
122+
@test Base.isrelocatable(pkg) == true
117123
end
118124
end
119125

@@ -202,6 +208,7 @@ else
202208
# stdlib should be already precompiled
203209
pkg = Base.identify_package("DelimitedFiles")
204210
@test Base.isprecompiled(pkg) == true
211+
@test Base.isrelocatable(pkg) == true
205212
end
206213
end
207214

@@ -213,6 +220,7 @@ else
213220
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file
214221
pkg = Base.identify_package(pkgname)
215222
@test Base.isprecompiled(pkg) == true
223+
@test Base.isrelocatable(pkg) == true
216224
end
217225
end
218226

@@ -224,10 +232,13 @@ else
224232
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file
225233
pkg = Base.identify_package(pkgname)
226234
@test Base.isprecompiled(pkg) == false # moving depot changes mtime of include_dependency
235+
@test Base.isrelocatable(pkg) == false # because not precompiled
227236
Base.require(pkg)
228237
@test Base.isprecompiled(pkg) == true
238+
@test Base.isrelocatable(pkg) == false # because tracked by mtime
229239
touch(joinpath(@__DIR__, "relocatedepot", "RelocationTestPkg2", "src", "foodir", "foofoo"))
230240
@test Base.isprecompiled(pkg) == false
241+
@test Base.isrelocatable(pkg) == false # because tracked by mtime
231242
end
232243
end
233244

@@ -239,8 +250,10 @@ else
239250
push!(DEPOT_PATH, joinpath(@__DIR__, "relocatedepot", "julia")) # contains cache file
240251
pkg = Base.identify_package(pkgname)
241252
@test Base.isprecompiled(pkg) == true
253+
@test Base.isrelocatable(pkg) == true
242254
touch(joinpath(@__DIR__, "relocatedepot", "RelocationTestPkg3", "src", "bardir", "barbar"))
243255
@test Base.isprecompiled(pkg) == false
256+
@test Base.isrelocatable(pkg) == false # because not precompiled
244257
end
245258
end
246259

0 commit comments

Comments
 (0)