• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

11.33
/base/loading.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# Base.require is the implementation for the `import` statement
4
const require_lock = ReentrantLock()
5

6
# Cross-platform case-sensitive path canonicalization
7

8
if Sys.isunix() && !Sys.isapple()
9
    # assume case-sensitive filesystems, don't have to do anything
10
    isfile_casesensitive(path) = isaccessiblefile(path)
154✔
11
elseif Sys.iswindows()
12
    # GetLongPathName Win32 function returns the case-preserved filename on NTFS.
13
    function isfile_casesensitive(path)
×
14
        isaccessiblefile(path) || return false  # Fail fast
×
15
        basename(Filesystem.longpath(path)) == basename(path)
×
16
    end
17
elseif Sys.isapple()
18
    # HFS+ filesystem is case-preserving. The getattrlist API returns
19
    # a case-preserved filename. In the rare event that HFS+ is operating
20
    # in case-sensitive mode, this will still work but will be redundant.
21

22
    # Constants from <sys/attr.h>
23
    const ATRATTR_BIT_MAP_COUNT = 5
24
    const ATTR_CMN_NAME = 1
25
    const BITMAPCOUNT = 1
26
    const COMMONATTR = 5
27
    const FSOPT_NOFOLLOW = 1  # Don't follow symbolic links
28

29
    const attr_list = zeros(UInt8, 24)
30
    attr_list[BITMAPCOUNT] = ATRATTR_BIT_MAP_COUNT
31
    attr_list[COMMONATTR] = ATTR_CMN_NAME
32

33
    # This essentially corresponds to the following C code:
34
    # attrlist attr_list;
35
    # memset(&attr_list, 0, sizeof(attr_list));
36
    # attr_list.bitmapcount = ATTR_BIT_MAP_COUNT;
37
    # attr_list.commonattr = ATTR_CMN_NAME;
38
    # struct Buffer {
39
    #    u_int32_t total_length;
40
    #    u_int32_t filename_offset;
41
    #    u_int32_t filename_length;
42
    #    char filename[max_filename_length];
43
    # };
44
    # Buffer buf;
45
    # getattrpath(path, &attr_list, &buf, sizeof(buf), FSOPT_NOFOLLOW);
46
    function isfile_casesensitive(path)
×
47
        isaccessiblefile(path) || return false
×
48
        path_basename = String(basename(path))
×
49
        local casepreserved_basename
×
50
        header_size = 12
×
51
        buf = Vector{UInt8}(undef, length(path_basename) + header_size + 1)
×
52
        while true
×
53
            ret = ccall(:getattrlist, Cint,
×
54
                        (Cstring, Ptr{Cvoid}, Ptr{Cvoid}, Csize_t, Culong),
55
                        path, attr_list, buf, sizeof(buf), FSOPT_NOFOLLOW)
56
            systemerror(:getattrlist, ret ≠ 0)
×
57
            filename_length = GC.@preserve buf unsafe_load(
×
58
              convert(Ptr{UInt32}, pointer(buf) + 8))
59
            if (filename_length + header_size) > length(buf)
×
60
                resize!(buf, filename_length + header_size)
×
61
                continue
×
62
            end
63
            casepreserved_basename =
×
64
              view(buf, (header_size+1):(header_size+filename_length-1))
65
            break
×
66
        end
×
67
        # Hack to compensate for inability to create a string from a subarray with no allocations.
68
        codeunits(path_basename) == casepreserved_basename && return true
×
69

70
        # If there is no match, it's possible that the file does exist but HFS+
71
        # performed unicode normalization. See  https://developer.apple.com/library/mac/qa/qa1235/_index.html.
72
        isascii(path_basename) && return false
×
73
        codeunits(Unicode.normalize(path_basename, :NFD)) == casepreserved_basename
×
74
    end
75
else
76
    # Generic fallback that performs a slow directory listing.
77
    function isfile_casesensitive(path)
×
78
        isaccessiblefile(path) || return false
×
79
        dir, filename = splitdir(path)
×
80
        any(readdir(dir) .== filename)
×
81
    end
82
end
83

84
# Check if the file is accessible. If stat fails return `false`
85

86
function isaccessibledir(dir)
×
87
    return try
×
88
        isdir(dir)
×
89
    catch err
90
        err isa IOError || rethrow()
×
91
        false
×
92
    end
93
end
94

UNCOV
95
function isaccessiblefile(file)
×
UNCOV
96
    return try
×
UNCOV
97
        isfile(file)
×
98
    catch err
99
        err isa IOError || rethrow()
×
100
        false
×
101
    end
102
end
103

104
function isaccessiblepath(path)
×
105
    return try
×
106
        ispath(path)
×
107
    catch err
108
        err isa IOError || rethrow()
×
109
        false
×
110
    end
111
end
112

113
## SHA1 ##
114

115
struct SHA1
116
    bytes::NTuple{20, UInt8}
6✔
117
end
118
function SHA1(bytes::Vector{UInt8})
119
    length(bytes) == 20 ||
62✔
120
        throw(ArgumentError("wrong number of bytes for SHA1 hash: $(length(bytes))"))
121
    return SHA1(ntuple(i->bytes[i], Val(20)))
124✔
122
end
123
SHA1(s::AbstractString) = SHA1(hex2bytes(s))
6✔
124
parse(::Type{SHA1}, s::AbstractString) = SHA1(s)
×
125
function tryparse(::Type{SHA1}, s::AbstractString)
×
126
    try
×
127
        return parse(SHA1, s)
×
128
    catch e
129
        if isa(e, ArgumentError)
×
130
            return nothing
×
131
        end
132
        rethrow(e)
×
133
    end
134
end
135

136
string(hash::SHA1) = bytes2hex(hash.bytes)
77✔
137
print(io::IO, hash::SHA1) = bytes2hex(io, hash.bytes)
97✔
138
show(io::IO, hash::SHA1) = print(io, "SHA1(\"", hash, "\")")
×
139

140
isless(a::SHA1, b::SHA1) = isless(a.bytes, b.bytes)
×
141
hash(a::SHA1, h::UInt) = hash((SHA1, a.bytes), h)
58✔
142
==(a::SHA1, b::SHA1) = a.bytes == b.bytes
88✔
143

144
# fake uuid5 function (for self-assigned UUIDs)
145
# TODO: delete and use real uuid5 once it's in stdlib
146

147
function uuid5(namespace::UUID, key::String)
148
    u::UInt128 = 0
×
149
    h = hash(namespace)
1✔
150
    for _ = 1:sizeof(u)÷sizeof(h)
2✔
151
        u <<= sizeof(h) << 3
2✔
152
        u |= (h = hash(key, h))
2✔
153
    end
3✔
154
    u &= 0xffffffffffff0fff3fffffffffffffff
1✔
155
    u |= 0x00000000000050008000000000000000
1✔
156
    return UUID(u)
1✔
157
end
158

159
const ns_dummy_uuid = UUID("fe0723d6-3a44-4c41-8065-ee0f42c8ceab")
160

161
function dummy_uuid(project_file::String)
×
162
    @lock require_lock begin
×
163
    cache = LOADING_CACHE[]
×
164
    if cache !== nothing
×
165
        uuid = get(cache.dummy_uuid, project_file, nothing)
×
166
        uuid === nothing || return uuid
×
167
    end
168
    project_path = try
×
169
        realpath(project_file)
×
170
    catch ex
171
        ex isa IOError || rethrow()
×
172
        project_file
×
173
    end
174
    uuid = uuid5(ns_dummy_uuid, project_path)
×
175
    if cache !== nothing
×
176
        cache.dummy_uuid[project_file] = uuid
×
177
    end
178
    return uuid
×
179
    end
180
end
181

182
## package path slugs: turning UUID + SHA1 into a pair of 4-byte "slugs" ##
183

184
const slug_chars = String(['A':'Z'; 'a':'z'; '0':'9'])
185

186
function slug(x::UInt32, p::Int)
UNCOV
187
    y::UInt32 = x
×
UNCOV
188
    sprint(sizehint=p) do io
×
189
        n = length(slug_chars)
190
        for i = 1:p
191
            y, d = divrem(y, n)
192
            write(io, slug_chars[1+d])
193
        end
194
    end
195
end
196

197
function package_slug(uuid::UUID, p::Int=5)
UNCOV
198
    crc = _crc32c(uuid)
×
UNCOV
199
    return slug(crc, p)
×
200
end
201

202
function version_slug(uuid::UUID, sha1::SHA1, p::Int=5)
×
203
    crc = _crc32c(uuid)
229✔
204
    crc = _crc32c(sha1.bytes, crc)
×
205
    return slug(crc, p)
×
206
end
207

208
mutable struct CachedTOMLDict
209
    path::String
41✔
210
    inode::UInt64
211
    mtime::Float64
212
    size::Int64
213
    hash::UInt32
214
    d::Dict{String, Any}
215
end
216

217
function CachedTOMLDict(p::TOML.Parser, path::String)
41✔
218
    s = stat(path)
41✔
219
    content = read(path)
41✔
220
    crc32 = _crc32c(content)
41✔
221
    TOML.reinit!(p, String(content); filepath=path)
82✔
222
    d = TOML.parse(p)
41✔
223
    return CachedTOMLDict(
41✔
224
        path,
225
        s.inode,
226
        s.mtime,
227
        s.size,
228
        crc32,
229
        d,
230
   )
231
end
232

233
function get_updated_dict(p::TOML.Parser, f::CachedTOMLDict)
44✔
234
    s = stat(f.path)
44✔
235
    # note, this might miss very rapid in-place updates, such that mtime is
236
    # identical but that is solvable by not doing in-place updates, and not
237
    # rapidly changing these files
238
    if s.inode != f.inode || s.mtime != f.mtime || f.size != s.size
88✔
239
        content = read(f.path)
×
240
        new_hash = _crc32c(content)
×
241
        if new_hash != f.hash
×
242
            f.inode = s.inode
×
243
            f.mtime = s.mtime
×
244
            f.size = s.size
×
245
            f.hash = new_hash
×
246
            TOML.reinit!(p, String(content); filepath=f.path)
×
247
            return f.d = TOML.parse(p)
×
248
        end
249
    end
250
    return f.d
44✔
251
end
252

253
struct LoadingCache
254
    load_path::Vector{String}
255
    dummy_uuid::Dict{String, UUID}
256
    env_project_file::Dict{String, Union{Bool, String}}
257
    project_file_manifest_path::Dict{String, Union{Nothing, String}}
258
    require_parsed::Set{String}
259
    identified_where::Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, String}}}
260
    identified::Dict{String, Union{Nothing, Tuple{PkgId, String}}}
261
    located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{String, String}, Nothing}}
262
end
263
const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing)
264
LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict())
×
265

266

267
struct TOMLCache{Dates}
268
    p::TOML.Parser{Dates}
269
    d::Dict{String, CachedTOMLDict}
270
end
271
TOMLCache(p::TOML.Parser) = TOMLCache(p, Dict{String, CachedTOMLDict}())
×
272
TOMLCache(p::TOML.Parser, d::Dict{String, Dict{String, Any}}) = TOMLCache(p, convert(Dict{String, CachedTOMLDict}, d))
×
273

274
const TOML_CACHE = TOMLCache(TOML.Parser{nothing}())
275

276
parsed_toml(project_file::AbstractString) = parsed_toml(project_file, TOML_CACHE, require_lock)
2✔
277
function parsed_toml(project_file::AbstractString, toml_cache::TOMLCache, toml_lock::ReentrantLock)
278
    lock(toml_lock) do
87✔
279
        cache = LOADING_CACHE[]
85✔
280
        dd = if !haskey(toml_cache.d, project_file)
170✔
281
            d = CachedTOMLDict(toml_cache.p, project_file)
41✔
282
            toml_cache.d[project_file] = d
41✔
283
            d.d
41✔
284
        else
285
            d = toml_cache.d[project_file]
44✔
286
            # We are in a require call and have already parsed this TOML file
287
            # assume that it is unchanged to avoid hitting disk
288
            if cache !== nothing && project_file in cache.require_parsed
44✔
289
                d.d
×
290
            else
291
                get_updated_dict(toml_cache.p, d)
129✔
292
            end
293
        end
294
        if cache !== nothing
85✔
295
            push!(cache.require_parsed, project_file)
×
296
        end
297
        return dd
85✔
298
    end
299
end
300

301
## package identification: determine unique identity of package to be loaded ##
302

303
# Used by Pkg but not used in loading itself
304
function find_package(arg) # ::Union{Nothing,String}
305
    pkgenv = identify_package_env(arg)
×
306
    pkgenv === nothing && return nothing
×
307
    pkg, env = pkgenv
×
308
    return locate_package(pkg, env)
×
309
end
310

311
# is there a better/faster ground truth?
312
function is_stdlib(pkgid::PkgId)
×
313
    pkgid.name in readdir(Sys.STDLIB) || return false
×
314
    stdlib_root = joinpath(Sys.STDLIB, pkgid.name)
×
315
    project_file = locate_project_file(stdlib_root)
×
316
    if project_file isa String
×
317
        d = parsed_toml(project_file)
×
318
        uuid = get(d, "uuid", nothing)
×
319
        if uuid !== nothing
×
320
            return UUID(uuid) == pkgid.uuid
×
321
        end
322
    end
323
    return false
×
324
end
325

326
"""
327
    Base.identify_package_env(name::String)::Union{Tuple{PkgId, String}, Nothing}
328
    Base.identify_package_env(where::Union{Module,PkgId}, name::String)::Union{Tuple{PkgId, Union{String, Nothing}}, Nothing}
329

330
Same as [`Base.identify_package`](@ref) except that the path to the environment where the package is identified
331
is also returned, except when the identity is not identified.
332
"""
333
identify_package_env(where::Module, name::String) = identify_package_env(PkgId(where), name)
×
334
function identify_package_env(where::PkgId, name::String)
×
335
    cache = LOADING_CACHE[]
×
336
    if cache !== nothing
×
337
        pkg_env = get(cache.identified_where, (where, name), missing)
×
338
        pkg_env === missing || return pkg_env
×
339
    end
340
    pkg_env = nothing
×
341
    if where.name === name
×
342
        return (where, nothing)
×
343
    elseif where.uuid === nothing
×
344
        pkg_env = identify_package_env(name) # ignore `where`
×
345
    else
346
        for env in load_path()
×
347
            pkgid = manifest_deps_get(env, where, name)
×
348
            pkgid === nothing && continue # not found--keep looking
×
349
            if pkgid.uuid !== nothing
×
350
                pkg_env = pkgid, env # found in explicit environment--use it
×
351
            end
352
            break # found in implicit environment--return "not found"
×
353
        end
×
354
        if pkg_env === nothing && is_stdlib(where)
×
355
            # if not found it could be that manifests are from a different julia version/commit
356
            # where stdlib dependencies have changed, so look up deps based on the stdlib Project.toml
357
            # as a fallback
358
            pkg_env = identify_stdlib_project_dep(where, name)
×
359
        end
360
    end
361
    if cache !== nothing
×
362
        cache.identified_where[(where, name)] = pkg_env
×
363
    end
364
    return pkg_env
×
365
end
366
function identify_package_env(name::String)
×
367
    cache = LOADING_CACHE[]
×
368
    if cache !== nothing
×
369
        pkg_env = get(cache.identified, name, missing)
×
370
        pkg_env === missing || return pkg_env
×
371
    end
372
    pkg_env = nothing
×
373
    for env in load_path()
×
374
        pkg = project_deps_get(env, name)
×
375
        if pkg !== nothing
×
376
            pkg_env = pkg, env # found--return it
×
377
            break
×
378
        end
379
    end
×
380
    if cache !== nothing
×
381
        cache.identified[name] = pkg_env
×
382
    end
383
    return pkg_env
×
384
end
385

386
function identify_stdlib_project_dep(stdlib::PkgId, depname::String)
×
387
    @debug """
×
388
    Stdlib $(repr("text/plain", stdlib)) is trying to load `$depname`
389
    which is not listed as a dep in the load path manifests, so resorting to search
390
    in the stdlib Project.tomls for true deps"""
391
    stdlib_projfile = locate_project_file(joinpath(Sys.STDLIB, stdlib.name))
×
392
    stdlib_projfile === nothing && return nothing
×
393
    found = explicit_project_deps_get(stdlib_projfile, depname)
×
394
    if found !== nothing
×
395
        @debug "$(repr("text/plain", stdlib)) indeed depends on $depname in project $stdlib_projfile"
×
396
        pkgid = PkgId(found, depname)
×
397
        return pkgid, stdlib_projfile
×
398
    end
399
    return nothing
×
400
end
401

UNCOV
402
_nothing_or_first(x) = x === nothing ? nothing : first(x)
×
403

404
"""
405
    Base.identify_package(name::String)::Union{PkgId, Nothing}
406
    Base.identify_package(where::Union{Module,PkgId}, name::String)::Union{PkgId, Nothing}
407

408
Identify the package by its name from the current environment stack, returning
409
its `PkgId`, or `nothing` if it cannot be found.
410

411
If only the `name` argument is provided, it searches each environment in the
412
stack and its named direct dependencies.
413

414
The `where` argument provides the context from where to search for the
415
package: in this case it first checks if the name matches the context itself,
416
otherwise it searches all recursive dependencies (from the resolved manifest of
417
each environment) until it locates the context `where`, and from there
418
identifies the dependency with the corresponding name.
419

420
```julia-repl
421
julia> Base.identify_package("Pkg") # Pkg is a dependency of the default environment
422
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
423

424
julia> using LinearAlgebra
425

426
julia> Base.identify_package(LinearAlgebra, "Pkg") # Pkg is not a dependency of LinearAlgebra
427
```
428
"""
429
identify_package(where::Module, name::String) = _nothing_or_first(identify_package_env(where, name))
×
430
identify_package(where::PkgId, name::String)  = _nothing_or_first(identify_package_env(where, name))
×
431
identify_package(name::String)                = _nothing_or_first(identify_package_env(name))
×
432

UNCOV
433
function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,Tuple{String,String}}
×
434
    cache = LOADING_CACHE[]
19✔
UNCOV
435
    if cache !== nothing
×
436
        pathenv = get(cache.located, (pkg, stopenv), missing)
×
437
        pathenv === missing || return pathenv
×
438
    end
439
    path = nothing
×
440
    env′ = nothing
×
UNCOV
441
    if pkg.uuid === nothing
×
442
        for env in load_path()
×
443
            # look for the toplevel pkg `pkg.name` in this entry
444
            found = project_deps_get(env, pkg.name)
×
445
            if found !== nothing
×
446
                @assert found.name == pkg.name
×
447
                if found.uuid === nothing
×
448
                    # pkg.name is present in this directory or project file,
449
                    # return the path the entry point for the code, if it could be found
450
                    # otherwise, signal failure
451
                    path = implicit_manifest_uuid_path(env, pkg)
×
452
                    env′ = env
×
453
                    @goto done
×
454
                end
455
            end
456
            if !(loading_extension || precompiling_extension)
×
457
                stopenv == env && @goto done
×
458
            end
459
        end
×
460
    else
UNCOV
461
        for env in load_path()
×
UNCOV
462
            path = manifest_uuid_path(env, pkg)
×
463
            # missing is used as a sentinel to stop looking further down in envs
UNCOV
464
            if path === missing
×
465
                path = nothing
×
466
                @goto done
×
467
            end
UNCOV
468
            if path !== nothing
×
469
                env′ = env
×
UNCOV
470
                @goto done
×
471
            end
UNCOV
472
            if !(loading_extension || precompiling_extension)
×
473
                stopenv == env && break
×
474
            end
UNCOV
475
        end
×
476
        # Allow loading of stdlibs if the name/uuid are given
477
        # e.g. if they have been explicitly added to the project/manifest
478
        mbypath = manifest_uuid_path(Sys.STDLIB, pkg)
×
479
        if mbypath isa String
×
480
            path = mbypath
×
481
            env′ = Sys.STDLIB
×
482
            @goto done
×
483
        end
484
    end
485
    @label done
UNCOV
486
    if path !== nothing && !isfile_casesensitive(path)
×
487
        path = nothing
×
488
    end
UNCOV
489
    if cache !== nothing
×
490
        cache.located[(pkg, stopenv)] = path === nothing ? nothing : (path, something(env′))
×
491
    end
UNCOV
492
    path === nothing && return nothing
×
UNCOV
493
    return path, something(env′)
×
494
end
495

496
"""
497
    Base.locate_package(pkg::PkgId)::Union{String, Nothing}
498

499
The path to the entry-point file for the package corresponding to the identifier
500
`pkg`, or `nothing` if not found. See also [`identify_package`](@ref).
501

502
```julia-repl
503
julia> pkg = Base.identify_package("Pkg")
504
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
505

506
julia> Base.locate_package(pkg)
507
"/path/to/julia/stdlib/v$(VERSION.major).$(VERSION.minor)/Pkg/src/Pkg.jl"
508
```
509
"""
UNCOV
510
function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String}
×
511
    _nothing_or_first(locate_package_env(pkg, stopenv))
79✔
512
end
513

514
"""
515
    pathof(m::Module)
516

517
Return the path of the `m.jl` file that was used to `import` module `m`,
518
or `nothing` if `m` was not imported from a package.
519

520
Use [`dirname`](@ref) to get the directory part and [`basename`](@ref)
521
to get the file name part of the path.
522

523
See also [`pkgdir`](@ref).
524
"""
525
function pathof(m::Module)
×
526
    @lock require_lock begin
×
527
    pkgid = PkgId(m)
×
528
    origin = get(pkgorigins, pkgid, nothing)
×
529
    origin === nothing && return nothing
×
530
    path = origin.path
×
531
    path === nothing && return nothing
×
532
    return fixup_stdlib_path(path)
×
533
    end
534
end
535

536
"""
537
    pkgdir(m::Module[, paths::String...])
538

539
Return the root directory of the package that declared module `m`,
540
or `nothing` if `m` was not declared in a package. Optionally further
541
path component strings can be provided to construct a path within the
542
package root.
543

544
To get the root directory of the package that implements the current module
545
the form `pkgdir(@__MODULE__)` can be used.
546

547
If an extension module is given, the root of the parent package is returned.
548

549
```julia-repl
550
julia> pkgdir(Foo)
551
"/path/to/Foo.jl"
552

553
julia> pkgdir(Foo, "src", "file.jl")
554
"/path/to/Foo.jl/src/file.jl"
555
```
556

557
See also [`pathof`](@ref).
558

559
!!! compat "Julia 1.7"
560
    The optional argument `paths` requires at least Julia 1.7.
561
"""
562
function pkgdir(m::Module, paths::String...)
×
563
    rootmodule = moduleroot(m)
×
564
    path = pathof(rootmodule)
×
565
    path === nothing && return nothing
×
566
    original = path
×
567
    path, base = splitdir(dirname(path))
×
568
    if base == "src"
×
569
        # package source in `../src/Foo.jl`
570
    elseif base == "ext"
×
571
        # extension source in `../ext/FooExt.jl`
572
    elseif basename(path) == "ext"
×
573
        # extension source in `../ext/FooExt/FooExt.jl`
574
        path = dirname(path)
×
575
    else
576
        error("Unexpected path structure for module source: $original")
×
577
    end
578
    return joinpath(path, paths...)
×
579
end
580

581
function get_pkgversion_from_path(path)
×
582
    project_file = locate_project_file(path)
×
583
    if project_file isa String
×
584
        d = parsed_toml(project_file)
×
585
        v = get(d, "version", nothing)
×
586
        if v !== nothing
×
587
            return VersionNumber(v::String)
×
588
        end
589
    end
590
    return nothing
×
591
end
592

593
"""
594
    pkgversion(m::Module)
595

596
Return the version of the package that imported module `m`,
597
or `nothing` if `m` was not imported from a package, or imported
598
from a package without a version field set.
599

600
The version is read from the package's Project.toml during package
601
load.
602

603
To get the version of the package that imported the current module
604
the form `pkgversion(@__MODULE__)` can be used.
605

606
!!! compat "Julia 1.9"
607
    This function was introduced in Julia 1.9.
608
"""
609
function pkgversion(m::Module)
×
610
    path = pkgdir(m)
×
611
    path === nothing && return nothing
×
612
    @lock require_lock begin
×
613
        v = get_pkgversion_from_path(path)
×
614
        pkgorigin = get(pkgorigins, PkgId(moduleroot(m)), nothing)
×
615
        # Cache the version
616
        if pkgorigin !== nothing && pkgorigin.version === nothing
×
617
            pkgorigin.version = v
×
618
        end
619
        return v
×
620
    end
621
end
622

623
## generic project & manifest API ##
624

625
const project_names = ("JuliaProject.toml", "Project.toml")
626
const manifest_names = (
627
    "JuliaManifest-v$(VERSION.major).$(VERSION.minor).toml",
628
    "Manifest-v$(VERSION.major).$(VERSION.minor).toml",
629
    "JuliaManifest.toml",
630
    "Manifest.toml",
631
)
632
const preferences_names = ("JuliaLocalPreferences.toml", "LocalPreferences.toml")
633

634
function locate_project_file(env::String)
635
    for proj in project_names
77✔
636
        project_file = joinpath(env, proj)
154✔
637
        if isfile_casesensitive(project_file)
154✔
638
            return project_file
77✔
639
        end
640
    end
77✔
UNCOV
641
    return true
×
642
end
643

644
# classify the LOAD_PATH entry to be one of:
645
#  - `false`: nonexistent / nothing to see here
646
#  - `true`: `env` is an implicit environment
647
#  - `path`: the path of an explicit project file
UNCOV
648
function env_project_file(env::String)::Union{Bool,String}
×
UNCOV
649
    @lock require_lock begin
×
UNCOV
650
    cache = LOADING_CACHE[]
×
UNCOV
651
    if cache !== nothing
×
652
        project_file = get(cache.env_project_file, env, nothing)
×
653
        project_file === nothing || return project_file
×
654
    end
UNCOV
655
    if isdir(env)
×
UNCOV
656
        project_file = locate_project_file(env)
×
UNCOV
657
    elseif basename(env) in project_names && isfile_casesensitive(env)
×
658
        project_file = env
659
    else
660
        project_file = false
661
    end
UNCOV
662
    if cache !== nothing
×
663
        cache.env_project_file[env] = project_file
×
664
    end
UNCOV
665
    return project_file
×
666
    end
667
end
668

UNCOV
669
function base_project(project_file)
×
UNCOV
670
    base_dir = abspath(joinpath(dirname(project_file), ".."))
×
UNCOV
671
    base_project_file = env_project_file(base_dir)
×
UNCOV
672
    base_project_file isa String || return nothing
×
673
    d = parsed_toml(base_project_file)
×
674
    workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
×
675
    if workspace === nothing
×
676
        return nothing
×
677
    end
678
    projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
×
679
    projects === nothing && return nothing
×
680
    if projects isa Vector && basename(dirname(project_file)) in projects
×
681
        return base_project_file
×
682
    end
683
    return nothing
×
684
end
685

686
function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}
687
    project_file = env_project_file(env)
×
688
    if project_file isa String
×
689
        pkg_uuid = explicit_project_deps_get(project_file, name)
×
690
        pkg_uuid === nothing || return PkgId(pkg_uuid, name)
×
691
    elseif project_file
×
692
        return implicit_project_deps_get(env, name)
×
693
    end
694
    return nothing
×
695
end
696

697
function package_get(project_file, where::PkgId, name::String)
698
    proj = project_file_name_uuid(project_file, where.name)
×
699
    if proj == where
×
700
        # if `where` matches the project, use [deps] section as manifest, and stop searching
701
        pkg_uuid = explicit_project_deps_get(project_file, name)
×
702
        return PkgId(pkg_uuid, name)
×
703
    end
704
    return nothing
×
705
end
706

707
function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothing,PkgId}
×
708
    uuid = where.uuid
×
709
    @assert uuid !== nothing
×
710
    project_file = env_project_file(env)
×
711
    if project_file isa String
×
712
        pkg = package_get(project_file, where, name)
×
713
        pkg === nothing || return pkg
×
714
        d = parsed_toml(project_file)
×
715
        exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
×
716
        if exts !== nothing
×
717
            proj = project_file_name_uuid(project_file, where.name)
×
718
            # Check if `where` is an extension of the project
719
            if where.name in keys(exts) && where.uuid == uuid5(proj.uuid::UUID, where.name)
×
720
                # Extensions can load weak deps...
721
                weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing}
×
722
                if weakdeps !== nothing
×
723
                    wuuid = get(weakdeps, name, nothing)::Union{String, Nothing}
×
724
                    if wuuid !== nothing
×
725
                        return PkgId(UUID(wuuid), name)
×
726
                    end
727
                end
728
                # ... and they can load same deps as the project itself
729
                mby_uuid = explicit_project_deps_get(project_file, name)
×
730
                mby_uuid === nothing || return PkgId(mby_uuid, name)
×
731
            end
732
        end
733
        # look for manifest file and `where` stanza
734
        return explicit_manifest_deps_get(project_file, where, name)
×
735
    elseif project_file
×
736
        # if env names a directory, search it
737
        return implicit_manifest_deps_get(env, where, name)
×
738
    end
739
    return nothing
×
740
end
741

UNCOV
742
function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missing}
×
UNCOV
743
    project_file = env_project_file(env)
×
UNCOV
744
    if project_file isa String
×
745
        proj = project_file_name_uuid(project_file, pkg.name)
×
746
        if proj == pkg
×
747
            # if `pkg` matches the project, return the project itself
748
            return project_file_path(project_file, pkg.name)
×
749
        end
750
        mby_ext = project_file_ext_path(project_file, pkg)
×
751
        mby_ext === nothing || return mby_ext
×
752
        # look for manifest file and `where` stanza
753
        return explicit_manifest_uuid_path(project_file, pkg)
×
UNCOV
754
    elseif project_file
×
755
        # if env names a directory, search it
UNCOV
756
        proj = implicit_manifest_uuid_path(env, pkg)
×
UNCOV
757
        proj === nothing || return proj
×
758
        # if not found
759
        triggers = get(EXT_PRIMED, pkg, nothing)
×
760
        if triggers !== nothing
×
761
            parentid = triggers[1]
×
762
            _, parent_project_file = entry_point_and_project_file(env, parentid.name)
×
763
            if parent_project_file !== nothing
×
764
                parentproj = project_file_name_uuid(parent_project_file, parentid.name)
×
765
                if parentproj == parentid
×
766
                    mby_ext = project_file_ext_path(parent_project_file, pkg)
×
767
                    mby_ext === nothing || return mby_ext
×
768
                end
769
            end
770
        end
771
    end
UNCOV
772
    return nothing
×
773
end
774

775

776
function find_ext_path(project_path::String, extname::String)
×
777
    extfiledir = joinpath(project_path, "ext", extname, extname * ".jl")
×
778
    isfile(extfiledir) && return extfiledir
×
779
    return joinpath(project_path, "ext", extname * ".jl")
×
780
end
781

782
function project_file_ext_path(project_file::String, ext::PkgId)
×
783
    d = parsed_toml(project_file)
×
784
    p = dirname(project_file)
×
785
    exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
×
786
    if exts !== nothing
×
787
        if ext.name in keys(exts) && ext.uuid == uuid5(UUID(d["uuid"]::String), ext.name)
×
788
            return find_ext_path(p, ext.name)
×
789
        end
790
    end
791
    return nothing
×
792
end
793

794
# find project file's top-level UUID entry (or nothing)
UNCOV
795
function project_file_name_uuid(project_file::String, name::String)::PkgId
×
UNCOV
796
    d = parsed_toml(project_file)
×
UNCOV
797
    uuid′ = get(d, "uuid", nothing)::Union{String, Nothing}
×
UNCOV
798
    uuid = uuid′ === nothing ? dummy_uuid(project_file) : UUID(uuid′)
×
UNCOV
799
    name = get(d, "name", name)::String
×
UNCOV
800
    return PkgId(uuid, name)
×
801
end
802

803
function project_file_path(project_file::String, name::String)
×
804
    d = parsed_toml(project_file)
×
805
    entryfile = get(d, "path", nothing)::Union{String, Nothing}
×
806
    # "path" entry in project file is soft deprecated
807
    if entryfile === nothing
×
808
        entryfile = get(d, "entryfile", nothing)::Union{String, Nothing}
×
809
    end
810
    return entry_path(dirname(project_file), name, entryfile)
×
811
end
812

813
function workspace_manifest(project_file)
×
814
    base = base_project(project_file)
×
815
    if base !== nothing
×
816
        return project_file_manifest_path(base)
×
817
    end
818
    return nothing
×
819
end
820

821
# find project file's corresponding manifest file
822
function project_file_manifest_path(project_file::String)::Union{Nothing,String}
×
823
    @lock require_lock begin
×
824
    cache = LOADING_CACHE[]
×
825
    if cache !== nothing
×
826
        manifest_path = get(cache.project_file_manifest_path, project_file, missing)
×
827
        manifest_path === missing || return manifest_path
×
828
    end
829
    dir = abspath(dirname(project_file))
×
830
    d = parsed_toml(project_file)
×
831
    base_manifest = workspace_manifest(project_file)
×
832
    if base_manifest !== nothing
×
833
        return base_manifest
×
834
    end
835
    explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing}
×
836
    manifest_path = nothing
837
    if explicit_manifest !== nothing
×
838
        manifest_file = normpath(joinpath(dir, explicit_manifest))
×
839
        if isfile_casesensitive(manifest_file)
×
840
            manifest_path = manifest_file
841
        end
842
    end
843
    if manifest_path === nothing
×
844
        for mfst in manifest_names
×
845
            manifest_file = joinpath(dir, mfst)
×
846
            if isfile_casesensitive(manifest_file)
×
847
                manifest_path = manifest_file
848
                break
×
849
            end
850
        end
×
851
    end
852
    if cache !== nothing
×
853
        cache.project_file_manifest_path[project_file] = manifest_path
×
854
    end
855
    return manifest_path
×
856
    end
857
end
858

859
# given a directory (implicit env from LOAD_PATH) and a name,
860
# check if it is an implicit package
861
function entry_point_and_project_file_inside(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
UNCOV
862
    path = normpath(joinpath(dir, "src", "$name.jl"))
×
UNCOV
863
    isfile_casesensitive(path) || return nothing, nothing
×
UNCOV
864
    for proj in project_names
×
UNCOV
865
        project_file = normpath(joinpath(dir, proj))
×
UNCOV
866
        isfile_casesensitive(project_file) || continue
×
UNCOV
867
        return path, project_file
×
UNCOV
868
    end
×
869
    return path, nothing
×
870
end
871

872
# given a project directory (implicit env from LOAD_PATH) and a name,
873
# find an entry point for `name`, and see if it has an associated project file
UNCOV
874
function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
×
UNCOV
875
    dir_name = joinpath(dir, name)
×
UNCOV
876
    path, project_file = entry_point_and_project_file_inside(dir_name, name)
×
UNCOV
877
    path === nothing || return path, project_file
×
878
    dir_jl = dir_name * ".jl"
×
879
    path, project_file = entry_point_and_project_file_inside(dir_jl, name)
×
880
    path === nothing || return path, project_file
×
881
    # check for less likely case with a bare file and no src directory last to minimize stat calls
882
    path = normpath(joinpath(dir, "$name.jl"))
×
883
    isfile_casesensitive(path) && return path, nothing
×
884
    return nothing, nothing
×
885
end
886

887
# Find the project file for the extension `ext` in the implicit env `dir``
888
function implicit_env_project_file_extension(dir::String, ext::PkgId)
889
    for pkg in readdir(dir; join=true)
×
890
        project_file = env_project_file(pkg)
×
891
        project_file isa String || continue
×
892
        path = project_file_ext_path(project_file, ext)
×
893
        if path !== nothing
×
894
            return path, project_file
×
895
        end
896
    end
×
897
    return nothing, nothing
×
898
end
899

900
# given a path, name, and possibly an entryfile, return the entry point
901
function entry_path(path::String, name::String, entryfile::Union{Nothing,String})::String
×
902
    isfile_casesensitive(path) && return normpath(path)
×
903
    entrypoint = entryfile === nothing ? joinpath("src", "$name.jl") : entryfile
×
904
    return normpath(joinpath(path, entrypoint))
×
905
end
906

907
## explicit project & manifest API ##
908

909
# find project file root or deps `name => uuid` mapping
910
# `ext` is the name of the extension if `name` is loaded from one
911
# return `nothing` if `name` is not found
912
function explicit_project_deps_get(project_file::String, name::String, ext::Union{String,Nothing}=nothing)::Union{Nothing,UUID}
×
913
    d = parsed_toml(project_file)
×
914
    if get(d, "name", nothing)::Union{String, Nothing} === name
×
915
        root_uuid = dummy_uuid(project_file)
×
916
        uuid = get(d, "uuid", nothing)::Union{String, Nothing}
×
917
        return uuid === nothing ? root_uuid : UUID(uuid)
×
918
    end
919
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
×
920
    if deps !== nothing
×
921
        uuid = get(deps, name, nothing)::Union{String, Nothing}
×
922
        uuid === nothing || return UUID(uuid)
×
923
    end
924
    if ext !== nothing
×
925
        extensions = get(d, "extensions", nothing)
×
926
        extensions === nothing && return nothing
×
927
        ext_data = get(extensions, ext, nothing)
×
928
        ext_data === nothing && return nothing
×
929
        if (ext_data isa String && name == ext_data) || (ext_data isa Vector{String} && name in ext_data)
×
930
            weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing}
×
931
            weakdeps === nothing && return nothing
×
932
            wuuid = get(weakdeps, name, nothing)::Union{String, Nothing}
×
933
            wuuid === nothing && return nothing
×
934
            return UUID(wuuid)
×
935
        end
936
    end
937
    return nothing
×
938
end
939

940
function is_v1_format_manifest(raw_manifest::Dict{String})
×
941
    if haskey(raw_manifest, "manifest_format")
×
942
        mf = raw_manifest["manifest_format"]
×
943
        if mf isa Dict{String} && haskey(mf, "uuid")
×
944
            # the off-chance where an old format manifest has a dep called "manifest_format"
945
            return true
×
946
        end
947
        return false
×
948
    else
949
        return true
×
950
    end
951
end
952

953
# returns a deps list for both old and new manifest formats
954
function get_deps(raw_manifest::Dict)
955
    if is_v1_format_manifest(raw_manifest)
1✔
956
        return raw_manifest
×
957
    else
958
        # if the manifest has no deps, there won't be a `deps` field
959
        return get(Dict{String, Any}, raw_manifest, "deps")::Dict{String, Any}
1✔
960
    end
961
end
962

963
# find `where` stanza and return the PkgId for `name`
964
# return `nothing` if it did not find `where` (indicating caller should continue searching)
965
function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId}
×
966
    manifest_file = project_file_manifest_path(project_file)
×
967
    manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
×
968
    d = get_deps(parsed_toml(manifest_file))
×
969
    found_where = false
×
970
    found_name = false
×
971
    for (dep_name, entries) in d
×
972
        entries::Vector{Any}
×
973
        for entry in entries
×
974
            entry = entry::Dict{String, Any}
×
975
            uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
×
976
            uuid === nothing && continue
×
977
            # deps is either a list of names (deps = ["DepA", "DepB"]) or
978
            # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
979
            deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
×
980
            if UUID(uuid) === where.uuid
×
981
                found_where = true
×
982
                if deps isa Vector{String}
×
983
                    found_name = name in deps
×
984
                    found_name && @goto done
×
985
                elseif deps isa Dict{String, Any}
×
986
                    deps = deps::Dict{String, Any}
×
987
                    for (dep, uuid) in deps
×
988
                        uuid::String
×
989
                        if dep === name
×
990
                            return PkgId(UUID(uuid), name)
×
991
                        end
992
                    end
×
993
                end
994
            else # Check for extensions
995
                extensions = get(entry, "extensions", nothing)
×
996
                if extensions !== nothing
×
997
                    if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name)
×
998
                        found_where = true
×
999
                        if name == dep_name
×
1000
                            return PkgId(UUID(uuid), name)
×
1001
                        end
1002
                        exts = extensions[where.name]::Union{String, Vector{String}}
×
1003
                        weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
×
1004
                        if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts)
×
1005
                            for deps′ in [weakdeps, deps]
×
1006
                                    if deps′ !== nothing
×
1007
                                        if deps′ isa Vector{String}
×
1008
                                            found_name = name in deps′
×
1009
                                            found_name && @goto done
×
1010
                                        elseif deps′ isa Dict{String, Any}
×
1011
                                            deps′ = deps′::Dict{String, Any}
×
1012
                                            for (dep, uuid) in deps′
×
1013
                                                uuid::String
×
1014
                                                if dep === name
×
1015
                                                    return PkgId(UUID(uuid), name)
×
1016
                                                end
1017
                                            end
×
1018
                                        end
1019
                                    end
1020
                                end
×
1021
                            end
1022
                        # `name` is not an ext, do standard lookup as if this was the parent
1023
                        return identify_package(PkgId(UUID(uuid), dep_name), name)
×
1024
                    end
1025
                end
1026
            end
1027
        end
×
1028
    end
×
1029
    @label done
1030
    found_where || return nothing
×
1031
    found_name || return PkgId(name)
×
1032
    # Only reach here if deps was not a dict which mean we have a unique name for the dep
1033
    name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
×
1034
    if name_deps === nothing || length(name_deps) != 1
×
1035
        error("expected a single entry for $(repr(name)) in $(repr(project_file))")
×
1036
    end
1037
    entry = first(name_deps::Vector{Any})::Dict{String, Any}
×
1038
    uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
×
1039
    uuid === nothing && return nothing
×
1040
    return PkgId(UUID(uuid), name)
×
1041
end
1042

1043
# find `uuid` stanza, return the corresponding path
1044
function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{Nothing,String,Missing}
×
1045
    manifest_file = project_file_manifest_path(project_file)
×
1046
    manifest_file === nothing && return nothing # no manifest, skip env
×
1047

1048
    d = get_deps(parsed_toml(manifest_file))
×
1049
    entries = get(d, pkg.name, nothing)::Union{Nothing, Vector{Any}}
×
1050
    if entries !== nothing
×
1051
        for entry in entries
×
1052
            entry = entry::Dict{String, Any}
×
1053
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
×
1054
            uuid === nothing && continue
×
1055
            if UUID(uuid) === pkg.uuid
×
1056
                return explicit_manifest_entry_path(manifest_file, pkg, entry)
×
1057
            end
1058
        end
×
1059
    end
1060
    # Extensions
1061
    for (name, entries) in d
×
1062
        entries = entries::Vector{Any}
×
1063
        for entry in entries
×
1064
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
×
1065
            extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
×
1066
            if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid
×
1067
                parent_path = locate_package(PkgId(UUID(uuid), name))
×
1068
                if parent_path === nothing
×
1069
                    error("failed to find source of parent package: \"$name\"")
×
1070
                end
1071
                p = normpath(dirname(parent_path), "..")
×
1072
                return find_ext_path(p, pkg.name)
×
1073
            end
1074
        end
×
1075
    end
×
1076
    return nothing
×
1077
end
1078

1079
function explicit_manifest_entry_path(manifest_file::String, pkg::PkgId, entry::Dict{String,Any})
×
1080
    path = get(entry, "path", nothing)::Union{Nothing, String}
×
1081
    entryfile = get(entry, "entryfile", nothing)::Union{Nothing, String}
×
1082
    if path !== nothing
×
1083
        path = entry_path(normpath(abspath(dirname(manifest_file), path)), pkg.name, entryfile)
×
1084
        return path
×
1085
    end
1086
    hash = get(entry, "git-tree-sha1", nothing)::Union{Nothing, String}
×
1087
    if hash === nothing
×
1088
        mbypath = manifest_uuid_path(Sys.STDLIB, pkg)
×
1089
        if mbypath isa String && isfile(mbypath)
×
1090
            return mbypath
×
1091
        end
1092
        return nothing
×
1093
    end
1094
    hash = SHA1(hash)
×
1095
    # Keep the 4 since it used to be the default
1096
    uuid = pkg.uuid::UUID # checked within `explicit_manifest_uuid_path`
×
1097
    for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4))
×
1098
        for depot in DEPOT_PATH
×
1099
            path = joinpath(depot, "packages", pkg.name, slug)
×
1100
            ispath(path) && return entry_path(abspath(path), pkg.name, entryfile)
×
1101
        end
×
1102
    end
×
1103
    # no depot contains the package, return missing to stop looking
1104
    return missing
×
1105
end
1106

1107
## implicit project & manifest API ##
1108

1109
# look for an entry point for `name` from a top-level package (no environment)
1110
# otherwise return `nothing` to indicate the caller should keep searching
1111
function implicit_project_deps_get(dir::String, name::String)::Union{Nothing,PkgId}
1112
    path, project_file = entry_point_and_project_file(dir, name)
×
1113
    if project_file === nothing
×
1114
        path === nothing && return nothing
×
1115
        return PkgId(name)
×
1116
    end
1117
    proj = project_file_name_uuid(project_file, name)
×
1118
    proj.name == name || return nothing
×
1119
    return proj
×
1120
end
1121

1122
# look for an entry-point for `name`, check that UUID matches
1123
# if there's a project file, look up `name` in its deps and return that
1124
# otherwise return `nothing` to indicate the caller should keep searching
1125
function implicit_manifest_deps_get(dir::String, where::PkgId, name::String)::Union{Nothing,PkgId}
×
1126
    @assert where.uuid !== nothing
×
1127
    project_file = entry_point_and_project_file(dir, where.name)[2]
×
1128
    if project_file === nothing
×
1129
        # `where` could be an extension
1130
        project_file = implicit_env_project_file_extension(dir, where)[2]
×
1131
        project_file === nothing && return nothing
×
1132
    end
1133
    proj = project_file_name_uuid(project_file, where.name)
×
1134
    ext = nothing
×
1135
    if proj !== where
×
1136
        # `where` could be an extension in `proj`
1137
        d = parsed_toml(project_file)
×
1138
        exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
×
1139
        if exts !== nothing && where.name in keys(exts)
×
1140
            if where.uuid !== uuid5(proj.uuid, where.name)
×
1141
                return nothing
×
1142
            end
1143
            ext = where.name
×
1144
        else
1145
            return nothing
×
1146
        end
1147
    end
1148
    # this is the correct project, so stop searching here
1149
    pkg_uuid = explicit_project_deps_get(project_file, name, ext)
×
1150
    return PkgId(pkg_uuid, name)
×
1151
end
1152

1153
# look for an entry-point for `pkg` and return its path if UUID matches
1154
function implicit_manifest_uuid_path(dir::String, pkg::PkgId)::Union{Nothing,String}
UNCOV
1155
    path, project_file = entry_point_and_project_file(dir, pkg.name)
×
UNCOV
1156
    if project_file === nothing
×
1157
        pkg.uuid === nothing || return nothing
×
1158
        return path
×
1159
    end
UNCOV
1160
    proj = project_file_name_uuid(project_file, pkg.name)
×
UNCOV
1161
    proj == pkg || return nothing
×
UNCOV
1162
    return path
×
1163
end
1164

1165
## other code loading functionality ##
1166

1167
function find_source_file(path::AbstractString)
×
1168
    (isabspath(path) || isfile(path)) && return path
×
1169
    base_path = joinpath(Sys.BINDIR, DATAROOTDIR, "julia", "base", path)
×
1170
    return isfile(base_path) ? normpath(base_path) : nothing
×
1171
end
1172

UNCOV
1173
function cache_file_entry(pkg::PkgId)
×
UNCOV
1174
    uuid = pkg.uuid
×
UNCOV
1175
    return joinpath(
×
1176
        "compiled",
1177
        "v$(VERSION.major).$(VERSION.minor)",
1178
        uuid === nothing ? ""       : pkg.name),
1179
        uuid === nothing ? pkg.name : package_slug(uuid)
1180
end
1181

UNCOV
1182
function find_all_in_cache_path(pkg::PkgId, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH)
×
UNCOV
1183
    paths = String[]
×
UNCOV
1184
    entrypath, entryfile = cache_file_entry(pkg)
×
UNCOV
1185
    for path in DEPOT_PATH
×
UNCOV
1186
        path = joinpath(path, entrypath)
×
UNCOV
1187
        isdir(path) || continue
×
UNCOV
1188
        for file in readdir(path, sort = false) # no sort given we sort later
×
UNCOV
1189
            if !((pkg.uuid === nothing && file == entryfile * ".ji") ||
×
1190
                 (pkg.uuid !== nothing && startswith(file, entryfile * "_") &&
1191
                  endswith(file, ".ji")))
UNCOV
1192
                 continue
×
1193
            end
UNCOV
1194
            filepath = joinpath(path, file)
×
UNCOV
1195
            isfile_casesensitive(filepath) && push!(paths, filepath)
×
UNCOV
1196
        end
×
UNCOV
1197
    end
×
UNCOV
1198
    if length(paths) > 1
×
UNCOV
1199
        function sort_by(path)
×
1200
            # when using pkgimages, consider those cache files first
UNCOV
1201
            pkgimage = if JLOptions().use_pkgimages != 0
×
UNCOV
1202
                io = open(path, "r")
×
UNCOV
1203
                try
×
UNCOV
1204
                    if iszero(isvalid_cache_header(io))
×
1205
                        false
×
1206
                    else
UNCOV
1207
                        _, _, _, _, _, _, _, flags = parse_cache_header(io, path)
×
UNCOV
1208
                        CacheFlags(flags).use_pkgimages
×
1209
                    end
1210
                finally
UNCOV
1211
                    close(io)
×
1212
                end
1213
            else
UNCOV
1214
                false
×
1215
            end
UNCOV
1216
            (; pkgimage, mtime=mtime(path))
×
1217
        end
1218
        function sort_lt(a, b)
×
UNCOV
1219
            if a.pkgimage != b.pkgimage
×
1220
                return a.pkgimage < b.pkgimage
×
1221
            end
UNCOV
1222
            return a.mtime < b.mtime
×
1223
        end
1224

1225
        # allocating the sort vector is less expensive than using sort!(.. by=sort_by),
1226
        # which would call the relatively slow mtime multiple times per path
UNCOV
1227
        p = sortperm(sort_by.(paths), lt=sort_lt, rev=true)
×
UNCOV
1228
        return paths[p]
×
1229
    else
1230
        return paths
×
1231
    end
1232
end
1233

UNCOV
1234
ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Libc.Libdl.dlext)
×
1235
cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Libc.Libdl.dlext)"), ".ji")
×
1236

1237

1238
# use an Int counter so that nested @time_imports calls all remain open
1239
const TIMING_IMPORTS = Threads.Atomic{Int}(0)
1240

1241
# loads a precompile cache file, ignoring stale_cachefile tests
1242
# assuming all depmods are already loaded and everything is valid
1243
# these return either the array of modules loaded from the path / content given
1244
# or an Exception that describes why it couldn't be loaded
1245
# and it reconnects the Base.Docs.META
1246
function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}, ignore_native::Union{Nothing,Bool}=nothing; register::Bool=true)
60✔
1247
    if isnothing(ignore_native)
20✔
1248
        if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0
20✔
1249
            ignore_native = false
×
1250
        else
1251
            io = open(path, "r")
20✔
1252
            try
20✔
1253
                iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.")
20✔
1254
                _, (includes, _, _), _, _, _, _, _, _ = parse_cache_header(io, path)
20✔
1255
                ignore_native = pkg_tracked(includes)
20✔
1256
            finally
1257
                close(io)
20✔
1258
            end
1259
        end
1260
    end
1261
    assert_havelock(require_lock)
40✔
1262
    timing_imports = TIMING_IMPORTS[] > 0
20✔
1263
    try
20✔
1264
        if timing_imports
20✔
1265
            t_before = time_ns()
×
1266
            cumulative_compile_timing(true)
×
1267
            t_comp_before = cumulative_compile_time_ns()
×
1268
        end
1269

1270
        for i in eachindex(depmods)
20✔
1271
            dep = depmods[i]
294✔
1272
            dep isa Module && continue
294✔
1273
            _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128}
31✔
1274
            dep = something(maybe_loaded_precompile(depkey, depbuild_id))
62✔
1275
            @assert PkgId(dep) == depkey && module_build_id(dep) === depbuild_id
31✔
1276
            depmods[i] = dep
31✔
1277
        end
568✔
1278

1279
        unlock(require_lock) # temporarily _unlock_ during these operations
20✔
1280
        sv = try
20✔
1281
            if ocachepath !== nothing
20✔
1282
                @debug "Loading object cache file $ocachepath for $(repr("text/plain", pkg))"
20✔
1283
                ccall(:jl_restore_package_image_from_file, Ref{SimpleVector}, (Cstring, Any, Cint, Cstring, Cint),
40✔
1284
                    ocachepath, depmods, #=completeinfo=#false, pkg.name, ignore_native)
1285
            else
1286
                @debug "Loading cache file $path for $(repr("text/plain", pkg))"
×
1287
                ccall(:jl_restore_incremental, Ref{SimpleVector}, (Cstring, Any, Cint, Cstring),
20✔
1288
                    path, depmods, #=completeinfo=#false, pkg.name)
1289
            end
1290
        finally
1291
            lock(require_lock)
20✔
1292
        end
1293

1294
        edges = sv[3]::Vector{Any}
20✔
1295
        ext_edges = sv[4]::Union{Nothing,Vector{Any}}
20✔
1296
        extext_methods = sv[5]::Vector{Any}
20✔
1297
        internal_methods = sv[6]::Vector{Any}
20✔
1298
        StaticData.insert_backedges(edges, ext_edges, extext_methods, internal_methods)
40✔
1299

1300
        restored = register_restored_modules(sv, pkg, path)
20✔
1301

1302
        for M in restored
20✔
1303
            M = M::Module
40✔
1304
            if is_root_module(M) && PkgId(M) == pkg
60✔
1305
                register && register_root_module(M)
20✔
1306
                if timing_imports
20✔
1307
                    elapsed_time = time_ns() - t_before
×
1308
                    comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before
×
1309
                    print_time_imports_report(M, elapsed_time, comp_time, recomp_time)
×
1310
                end
1311
                return M
20✔
1312
            end
1313
        end
20✔
1314
        return ErrorException("Required dependency $(repr("text/plain", pkg)) failed to load from a cache file.")
×
1315

1316
    finally
1317
        timing_imports && cumulative_compile_timing(false)
20✔
1318
    end
1319
end
1320

1321
# printing functions for @time_imports
1322
# note that the time inputs are UInt64 on all platforms. Give default values here so that we don't have
1323
# confusing UInt64 types in generate_precompile.jl
1324
function print_time_imports_report(
×
1325
        mod::Module,
1326
        elapsed_time::UInt64=UInt64(1),
1327
        comp_time::UInt64=UInt64(1),
1328
        recomp_time::UInt64=UInt64(1)
1329
    )
1330
    print(lpad(round(elapsed_time / 1e6, digits=1), 9), " ms  ")
×
1331
    ext_parent = extension_parent_name(mod)
×
1332
    if ext_parent !== nothing
×
1333
        print(ext_parent::String, " → ")
×
1334
    end
1335
    print(string(mod))
×
1336
    if comp_time > 0
×
1337
        perc = Ryu.writefixed(Float64(100 * comp_time / (elapsed_time)), 2)
×
1338
        printstyled(" $perc% compilation time", color = Base.info_color())
×
1339
    end
1340
    if recomp_time > 0
×
1341
        perc = Float64(100 * recomp_time / comp_time)
×
1342
        perc_show = perc < 1 ? "<1" : Ryu.writefixed(perc, 0)
×
1343
        printstyled(" ($perc_show% recompilation)", color = Base.warn_color())
×
1344
    end
1345
    println()
×
1346
end
1347
function print_time_imports_report_init(
×
1348
        mod::Module, i::Int=1,
1349
        elapsed_time::UInt64=UInt64(1),
1350
        comp_time::UInt64=UInt64(1),
1351
        recomp_time::UInt64=UInt64(1)
1352
    )
1353
    connector = i > 1 ? "├" : "┌"
×
1354
    printstyled("               $connector ", color = :light_black)
×
1355
    print("$(round(elapsed_time / 1e6, digits=1)) ms $mod.__init__() ")
×
1356
    if comp_time > 0
×
1357
        perc = Ryu.writefixed(Float64(100 * (comp_time) / elapsed_time), 2)
×
1358
        printstyled("$perc% compilation time", color = Base.info_color())
×
1359
    end
1360
    if recomp_time > 0
×
1361
        perc = Float64(100 * recomp_time / comp_time)
×
1362
        printstyled(" ($(perc < 1 ? "<1" : Ryu.writefixed(perc, 0))% recompilation)", color = Base.warn_color())
×
1363
    end
1364
    println()
×
1365
end
1366

1367
# if M is an extension, return the string name of the parent. Otherwise return nothing
1368
function extension_parent_name(M::Module)
×
1369
    rootmodule = moduleroot(M)
×
1370
    src_path = pathof(rootmodule)
×
1371
    src_path === nothing && return nothing
×
1372
    pkgdir_parts = splitpath(src_path)
×
1373
    ext_pos = findlast(==("ext"), pkgdir_parts)
×
1374
    if ext_pos !== nothing && ext_pos >= length(pkgdir_parts) - 2
×
1375
        parent_package_root = joinpath(pkgdir_parts[1:ext_pos-1]...)
×
1376
        parent_package_project_file = locate_project_file(parent_package_root)
×
1377
        if parent_package_project_file isa String
×
1378
            d = parsed_toml(parent_package_project_file)
×
1379
            name = get(d, "name", nothing)
×
1380
            if name !== nothing
×
1381
                return name
×
1382
            end
1383
        end
1384
    end
1385
    return nothing
×
1386
end
1387

1388
function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
20✔
1389
    # This function is also used by PkgCacheInspector.jl
1390
    assert_havelock(require_lock)
40✔
1391
    restored = sv[1]::Vector{Any}
20✔
1392
    for M in restored
20✔
1393
        M = M::Module
40✔
1394
        if isdefinedglobal(M, Base.Docs.META)
40✔
1395
            push!(Base.Docs.modules, M)
21✔
1396
        end
1397
        if is_root_module(M)
60✔
1398
            push!(loaded_modules_order, M)
20✔
1399
            push!(get!(Vector{Module}, loaded_precompiles, pkg), M)
20✔
1400
        end
1401
    end
40✔
1402

1403
    # Register this cache path now - If Requires.jl is loaded, Revise may end
1404
    # up looking at the cache path during the init callback.
1405
    get!(PkgOrigin, pkgorigins, pkg).cachepath = path
20✔
1406

1407
    inits = sv[2]::Vector{Any}
20✔
1408
    if !isempty(inits)
20✔
1409
        unlock(require_lock) # temporarily _unlock_ during these callbacks
12✔
1410
        try
12✔
1411
            for (i, mod) in pairs(inits)
24✔
1412
                run_module_init(mod, i)
12✔
1413
            end
12✔
1414
        finally
1415
            lock(require_lock)
12✔
1416
        end
1417
    end
1418
    return restored
20✔
1419
end
1420

1421
function run_module_init(mod::Module, i::Int=1)
12✔
1422
    # `i` informs ordering for the `@time_imports` report formatting
1423
    if TIMING_IMPORTS[] == 0
12✔
1424
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
12✔
1425
    elseif isdefined(mod, :__init__)
×
1426
        elapsed_time = time_ns()
×
1427
        cumulative_compile_timing(true)
×
1428
        compile_elapsedtimes = cumulative_compile_time_ns()
×
1429

1430
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
×
1431

1432
        elapsed_time = time_ns() - elapsed_time
×
1433
        cumulative_compile_timing(false);
×
1434
        comp_time, recomp_time = cumulative_compile_time_ns() .- compile_elapsedtimes
×
1435

1436
        print_time_imports_report_init(mod, i, elapsed_time, comp_time, recomp_time)
×
1437
    end
1438
end
1439

1440
function run_package_callbacks(modkey::PkgId)
1✔
1441
    @assert modkey != precompilation_target
1✔
1442
    run_extension_callbacks(modkey)
1✔
1443
    assert_havelock(require_lock)
2✔
1444
    unlock(require_lock)
1✔
1445
    try
1✔
1446
        for callback in package_callbacks
1✔
1447
            invokelatest(callback, modkey)
×
1448
        end
×
1449
    catch
1450
        # Try to continue loading if a callback errors
1451
        errs = current_exceptions()
×
1452
        @error "Error during package callback" exception=errs
1✔
1453
    finally
1454
        lock(require_lock)
1✔
1455
    end
1456
    nothing
1✔
1457
end
1458

1459

1460
##############
1461
# Extensions #
1462
##############
1463

1464
mutable struct ExtensionId
1465
    const id::PkgId
×
1466
    const parentid::PkgId # just need the name, for printing
1467
    const n_total_triggers::Int
1468
    ntriggers::Int # how many more packages must be defined until this is loaded
1469
end
1470

1471
const EXT_PRIMED = Dict{PkgId,Vector{PkgId}}() # Extension -> Parent + Triggers (parent is always first)
1472
const EXT_DORMITORY = Dict{PkgId,Vector{ExtensionId}}() # Trigger -> Extensions that can be triggered by it
1473
const EXT_DORMITORY_FAILED = ExtensionId[]
1474

1475
function insert_extension_triggers(pkg::PkgId)
1476
    pkg.uuid === nothing && return
19✔
1477
    path_env_loc = locate_package_env(pkg)
19✔
1478
    path_env_loc === nothing && return
19✔
1479
    path, env_loc = path_env_loc
19✔
1480
    insert_extension_triggers(env_loc, pkg)
19✔
1481
end
1482

UNCOV
1483
function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing}
×
UNCOV
1484
    project_file = env_project_file(env)
×
UNCOV
1485
    if project_file isa String || project_file
×
1486
        implicit_project_file = project_file
×
UNCOV
1487
        if !(implicit_project_file isa String)
×
1488
            # if env names a directory, search it for an implicit project file (for stdlibs)
UNCOV
1489
            path, implicit_project_file = entry_point_and_project_file(env, pkg.name)
×
UNCOV
1490
            if !(implicit_project_file isa String)
×
1491
                return nothing
×
1492
            end
1493
        end
1494
        # Look in project for extensions to insert
UNCOV
1495
        proj_pkg = project_file_name_uuid(implicit_project_file, pkg.name)
×
UNCOV
1496
        if pkg == proj_pkg
×
UNCOV
1497
            d_proj = parsed_toml(implicit_project_file)
×
UNCOV
1498
            extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
×
UNCOV
1499
            extensions === nothing && return
×
1500
            weakdeps = get(Dict{String, Any}, d_proj, "weakdeps")::Dict{String,Any}
×
1501
            deps = get(Dict{String, Any}, d_proj, "deps")::Dict{String,Any}
×
1502
            total_deps = merge(weakdeps, deps)
×
1503
            return _insert_extension_triggers(pkg, extensions, total_deps)
×
1504
        end
1505

1506
        # Now look in manifest
1507
        project_file isa String || return nothing
×
1508
        manifest_file = project_file_manifest_path(project_file)
×
1509
        manifest_file === nothing && return
×
1510
        d = get_deps(parsed_toml(manifest_file))
×
1511
        for (dep_name, entries) in d
×
1512
            entries::Vector{Any}
×
1513
            for entry in entries
×
1514
                entry = entry::Dict{String, Any}
×
1515
                uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
×
1516
                uuid === nothing && continue
×
1517
                if UUID(uuid) == pkg.uuid
×
1518
                    extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
×
1519
                    extensions === nothing && return
×
1520
                    weakdeps = get(Dict{String, Any}, entry, "weakdeps")::Union{Vector{String}, Dict{String,Any}}
×
1521
                    deps = get(Dict{String, Any}, entry, "deps")::Union{Vector{String}, Dict{String,Any}}
×
1522

1523
                    function expand_deps_list(deps′::Vector{String})
×
1524
                        deps′_expanded = Dict{String, Any}()
×
1525
                        for (dep_name, entries) in d
×
1526
                            dep_name in deps′ || continue
×
1527
                            entries::Vector{Any}
×
1528
                            if length(entries) != 1
×
1529
                                error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
×
1530
                            end
1531
                            entry = first(entries)::Dict{String, Any}
×
1532
                            uuid = entry["uuid"]::String
×
1533
                            deps′_expanded[dep_name] = uuid
×
1534
                        end
×
1535
                        return deps′_expanded
×
1536
                    end
1537

1538
                    if weakdeps isa Vector{String}
×
1539
                        weakdeps = expand_deps_list(weakdeps)
×
1540
                    end
1541
                    if deps isa Vector{String}
×
1542
                        deps = expand_deps_list(deps)
×
1543
                    end
1544

1545
                    total_deps = merge(weakdeps, deps)
×
1546
                    return _insert_extension_triggers(pkg, extensions, total_deps)
×
1547
                end
1548
            end
×
1549
        end
×
1550
    end
1551
    return nothing
×
1552
end
1553

1554
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, totaldeps::Dict{String, Any})
×
1555
    for (ext, triggers) in extensions
×
1556
        triggers = triggers::Union{String, Vector{String}}
×
1557
        triggers isa String && (triggers = [triggers])
×
1558
        id = PkgId(uuid5(parent.uuid::UUID, ext), ext)
×
1559
        if haskey(EXT_PRIMED, id) || haskey(Base.loaded_modules, id)
×
1560
            continue  # extension is already primed or loaded, don't add it again
×
1561
        end
1562
        EXT_PRIMED[id] = trigger_ids = PkgId[parent]
×
1563
        gid = ExtensionId(id, parent, 1 + length(triggers), 1 + length(triggers))
×
1564
        trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent)
×
1565
        push!(trigger1, gid)
×
1566
        for trigger in triggers
×
1567
            # TODO: Better error message if this lookup fails?
1568
            uuid_trigger = UUID(totaldeps[trigger]::String)
×
1569
            trigger_id = PkgId(uuid_trigger, trigger)
×
1570
            push!(trigger_ids, trigger_id)
×
1571
            if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) || (trigger_id == precompilation_target)
×
1572
                trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
×
1573
                push!(trigger1, gid)
×
1574
            else
1575
                gid.ntriggers -= 1
×
1576
            end
1577
        end
×
1578
    end
×
1579
end
1580

1581
loading_extension::Bool = false
1582
loadable_extensions::Union{Nothing,Vector{PkgId}} = nothing
1583
precompiling_extension::Bool = false
1584
precompilation_target::Union{Nothing,PkgId} = nothing
1585
function run_extension_callbacks(extid::ExtensionId)
×
1586
    assert_havelock(require_lock)
×
1587
    succeeded = try
×
1588
        # Used by Distributed to now load extensions in the package callback
1589
        global loading_extension = true
×
1590
        _require_prelocked(extid.id)
×
1591
        @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded"
×
1592
        true
×
1593
    catch
1594
        # Try to continue loading if loading an extension errors
1595
        if JLOptions().incremental != 0
×
1596
            # during incremental precompilation, this should be fail-fast
1597
            rethrow()
×
1598
        else
1599
            errs = current_exceptions()
×
1600
            @error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name), \
×
1601
                use `Base.retry_load_extensions()` to retry." exception=errs
1602
        end
1603
        false
×
1604
    finally
1605
        global loading_extension = false
×
1606
    end
1607
    return succeeded
×
1608
end
1609

1610
function run_extension_callbacks(pkgid::PkgId)
1✔
1611
    assert_havelock(require_lock)
2✔
1612
    # take ownership of extids that depend on this pkgid
1613
    extids = pop!(EXT_DORMITORY, pkgid, nothing)
1✔
1614
    extids === nothing && return
1✔
1615
    extids_to_load = Vector{ExtensionId}()
1✔
1616
    for extid in extids
1✔
1617
        @assert extid.ntriggers > 0
1✔
1618
        extid.ntriggers -= 1
1✔
1619
        if extid.ntriggers == 0 && (loadable_extensions === nothing || extid.id in loadable_extensions)
1✔
1620
            push!(extids_to_load, extid)
×
1621
        end
1622
    end
1✔
1623
    # Load extensions with the fewest triggers first
1624
    sort!(extids_to_load, by=extid->extid.n_total_triggers)
1✔
1625
    for extid in extids_to_load
1✔
1626
        # actually load extid, now that all dependencies are met,
1627
        succeeded = run_extension_callbacks(extid)
×
1628
        succeeded || push!(EXT_DORMITORY_FAILED, extid)
×
1629
    end
×
1630

1631
    return
1✔
1632
end
1633

1634
"""
1635
    retry_load_extensions()
1636

1637
Loads all the (not yet loaded) extensions that have their extension-dependencies loaded.
1638
This is used in cases where the automatic loading of an extension failed
1639
due to some problem with the extension. Instead of restarting the Julia session,
1640
the extension can be fixed, and this function run.
1641
"""
1642
function retry_load_extensions()
×
1643
    @lock require_lock begin
×
1644
    # this copy is desired since run_extension_callbacks will release this lock
1645
    # so this can still mutate the list to drop successful ones
1646
    failed = copy(EXT_DORMITORY_FAILED)
×
1647
    empty!(EXT_DORMITORY_FAILED)
×
1648
    filter!(failed) do extid
×
1649
        return !run_extension_callbacks(extid)
×
1650
    end
1651
    prepend!(EXT_DORMITORY_FAILED, failed)
×
1652
    end
1653
    return
×
1654
end
1655

1656
"""
1657
    get_extension(parent::Module, extension::Symbol)
1658

1659
Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded.
1660
"""
1661
get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext)
×
1662
function get_extension(parentid::PkgId, ext::Symbol)
×
1663
    parentid.uuid === nothing && return nothing
×
1664
    extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext))
×
1665
    return maybe_root_module(extid)
×
1666
end
1667

1668
# End extensions
1669

1670

1671
struct CacheFlags
1672
    # OOICCDDP - see jl_cache_flags
1673
    use_pkgimages::Bool
40✔
1674
    debug_level::Int
1675
    check_bounds::Int
1676
    inline::Bool
1677
    opt_level::Int
1678
end
1679
function CacheFlags(f::UInt8)
1680
    use_pkgimages = Bool(f & 1)
80✔
1681
    debug_level = Int((f >> 1) & 3)
40✔
1682
    check_bounds = Int((f >> 3) & 3)
40✔
1683
    inline = Bool((f >> 5) & 1)
80✔
1684
    opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils
40✔
1685
    CacheFlags(use_pkgimages, debug_level, check_bounds, inline, opt_level)
×
1686
end
1687
CacheFlags(f::Int) = CacheFlags(UInt8(f))
×
1688
function CacheFlags(cf::CacheFlags=CacheFlags(ccall(:jl_cache_flags, UInt8, ()));
120✔
1689
            use_pkgimages::Union{Nothing,Bool}=nothing,
1690
            debug_level::Union{Nothing,Int}=nothing,
1691
            check_bounds::Union{Nothing,Int}=nothing,
1692
            inline::Union{Nothing,Bool}=nothing,
1693
            opt_level::Union{Nothing,Int}=nothing
1694
        )
1695
    return CacheFlags(
40✔
1696
        use_pkgimages === nothing ? cf.use_pkgimages : use_pkgimages,
1697
        debug_level === nothing ? cf.debug_level : debug_level,
1698
        check_bounds === nothing ? cf.check_bounds : check_bounds,
1699
        inline === nothing ? cf.inline : inline,
1700
        opt_level === nothing ? cf.opt_level : opt_level
1701
    )
1702
end
1703
# reflecting jloptions.c defaults
1704
const DefaultCacheFlags = CacheFlags(use_pkgimages=true, debug_level=isdebugbuild() ? 2 : 1, check_bounds=0, inline=true, opt_level=2)
1705

1706
function _cacheflag_to_uint8(cf::CacheFlags)::UInt8
1707
    f = UInt8(0)
×
UNCOV
1708
    f |= cf.use_pkgimages << 0
×
UNCOV
1709
    f |= cf.debug_level << 1
×
UNCOV
1710
    f |= cf.check_bounds << 3
×
UNCOV
1711
    f |= cf.inline << 5
×
UNCOV
1712
    f |= cf.opt_level << 6
×
UNCOV
1713
    return f
×
1714
end
1715

1716
function translate_cache_flags(cacheflags::CacheFlags, defaultflags::CacheFlags)
×
1717
    opts = String[]
×
1718
    cacheflags.use_pkgimages    != defaultflags.use_pkgimages   && push!(opts, cacheflags.use_pkgimages ? "--pkgimages=yes" : "--pkgimages=no")
×
1719
    cacheflags.debug_level      != defaultflags.debug_level     && push!(opts, "-g$(cacheflags.debug_level)")
×
1720
    cacheflags.check_bounds     != defaultflags.check_bounds    && push!(opts, ("--check-bounds=auto", "--check-bounds=yes", "--check-bounds=no")[cacheflags.check_bounds + 1])
×
1721
    cacheflags.inline           != defaultflags.inline          && push!(opts, cacheflags.inline ? "--inline=yes" : "--inline=no")
×
1722
    cacheflags.opt_level        != defaultflags.opt_level       && push!(opts, "-O$(cacheflags.opt_level)")
×
1723
    return opts
×
1724
end
1725

1726
function show(io::IO, cf::CacheFlags)
×
1727
    print(io, "CacheFlags(")
×
1728
    print(io, "; use_pkgimages=")
×
1729
    print(io, cf.use_pkgimages)
×
1730
    print(io, ", debug_level=")
×
1731
    print(io, cf.debug_level)
×
1732
    print(io, ", check_bounds=")
×
1733
    print(io, cf.check_bounds)
×
1734
    print(io, ", inline=")
×
1735
    print(io, cf.inline)
×
1736
    print(io, ", opt_level=")
×
1737
    print(io, cf.opt_level)
×
1738
    print(io, ")")
×
1739
end
1740

1741
struct ImageTarget
1742
    name::String
×
1743
    flags::Int32
1744
    ext_features::String
1745
    features_en::Vector{UInt8}
1746
    features_dis::Vector{UInt8}
1747
end
1748

1749
function parse_image_target(io::IO)
×
1750
    flags = read(io, Int32)
×
1751
    nfeature = read(io, Int32)
×
1752
    feature_en = read(io, 4*nfeature)
×
1753
    feature_dis = read(io, 4*nfeature)
×
1754
    name_len = read(io, Int32)
×
1755
    name = String(read(io, name_len))
×
1756
    ext_features_len = read(io, Int32)
×
1757
    ext_features = String(read(io, ext_features_len))
×
1758
    ImageTarget(name, flags, ext_features, feature_en, feature_dis)
×
1759
end
1760

1761
function parse_image_targets(targets::Vector{UInt8})
×
1762
    io = IOBuffer(targets)
×
1763
    ntargets = read(io, Int32)
×
1764
    targets = Vector{ImageTarget}(undef, ntargets)
×
1765
    for i in 1:ntargets
×
1766
        targets[i] = parse_image_target(io)
×
1767
    end
×
1768
    return targets
×
1769
end
1770

1771
function current_image_targets()
1772
    targets = @ccall jl_reflect_clone_targets()::Vector{UInt8}
×
1773
    return parse_image_targets(targets)
×
1774
end
1775

1776
struct FeatureName
1777
    name::Cstring
1778
    bit::UInt32 # bit index into a `uint32_t` array;
1779
    llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support
1780
end
1781

1782
function feature_names()
×
1783
    fnames = Ref{Ptr{FeatureName}}()
×
1784
    nf = Ref{Csize_t}()
×
1785
    @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid
×
1786
    if fnames[] == C_NULL
×
1787
        @assert nf[] == 0
×
1788
        return Vector{FeatureName}(undef, 0)
×
1789
    end
1790
    Base.unsafe_wrap(Array, fnames[], nf[], own=false)
×
1791
end
1792

1793
function test_feature(features::Vector{UInt8}, feat::FeatureName)
×
1794
    bitidx = feat.bit
×
1795
    u8idx = div(bitidx, 8) + 1
×
1796
    bit = bitidx % 8
×
1797
    return (features[u8idx] & (1 << bit)) != 0
×
1798
end
1799

1800
function show(io::IO, it::ImageTarget)
×
1801
    print(io, it.name)
×
1802
    if !isempty(it.ext_features)
×
1803
        print(io, ",", it.ext_features)
×
1804
    end
1805
    print(io, "; flags=", it.flags)
×
1806
    print(io, "; features_en=(")
×
1807
    first = true
×
1808
    for feat in feature_names()
×
1809
        if test_feature(it.features_en, feat)
×
1810
            name = Base.unsafe_string(feat.name)
×
1811
            if first
×
1812
                first = false
×
1813
                print(io, name)
×
1814
            else
1815
                print(io, ", ", name)
×
1816
            end
1817
        end
1818
    end
×
1819
    print(io, ")")
×
1820
    # Is feature_dis useful?
1821
end
1822

1823
# should sync with the types of arguments of `stale_cachefile`
1824
const StaleCacheKey = Tuple{PkgId, UInt128, String, String}
1825

1826
function compilecache_path(pkg::PkgId;
71✔
1827
        ignore_loaded::Bool=false,
1828
        stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
1829
        cachepath_cache::Dict{PkgId, Vector{String}}=Dict{PkgId, Vector{String}}(),
1830
        cachepaths::Vector{String}=get!(() -> find_all_in_cache_path(pkg), cachepath_cache, pkg),
1831
        sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
1832
        flags::CacheFlags=CacheFlags())
1833
    path = nothing
1834
    isnothing(sourcepath) && error("Cannot locate source for $(repr("text/plain", pkg))")
1835
    for path_to_try in cachepaths
1836
        staledeps = stale_cachefile(sourcepath, path_to_try; ignore_loaded, requested_flags=flags)
1837
        if staledeps === true
1838
            continue
1839
        end
1840
        staledeps, _, _ = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1841
        # finish checking staledeps module graph
1842
        for dep in staledeps
1843
            dep isa Module && continue
1844
            modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
1845
            modpaths = get!(() -> find_all_in_cache_path(modkey), cachepath_cache, modkey)
1846
            for modpath_to_try in modpaths::Vector{String}
1847
                stale_cache_key = (modkey, modbuild_id, modpath, modpath_to_try)::StaleCacheKey
1848
                if get!(() -> stale_cachefile(stale_cache_key...; ignore_loaded, requested_flags=flags) === true,
1849
                        stale_cache, stale_cache_key)
1850
                    continue
1851
                end
1852
                @goto check_next_dep
1853
            end
1854
            @goto check_next_path
1855
            @label check_next_dep
1856
        end
1857
        try
1858
            # update timestamp of precompilation file so that it is the first to be tried by code loading
1859
            touch(path_to_try)
1860
        catch ex
1861
            # file might be read-only and then we fail to update timestamp, which is fine
1862
            ex isa IOError || rethrow()
1863
        end
1864
        path = path_to_try
1865
        break
1866
        @label check_next_path
1867
    end
1868
    return path
1869
end
1870

1871
"""
1872
    Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
1873

1874
Returns whether a given PkgId within the active project is precompiled.
1875

1876
By default this check observes the same approach that code loading takes
1877
with respect to when different versions of dependencies are currently loaded
1878
to that which is expected. To ignore loaded modules and answer as if in a
1879
fresh julia session specify `ignore_loaded=true`.
1880

1881
!!! compat "Julia 1.10"
1882
    This function requires at least Julia 1.10.
1883
"""
1884
function isprecompiled(pkg::PkgId;
104✔
1885
        ignore_loaded::Bool=false,
1886
        stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
1887
        cachepath_cache::Dict{PkgId, Vector{String}}=Dict{PkgId, Vector{String}}(),
1888
        cachepaths::Vector{String}=get!(() -> find_all_in_cache_path(pkg), cachepath_cache, pkg),
1889
        sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
1890
        flags::CacheFlags=CacheFlags())
1891
    path = compilecache_path(pkg; ignore_loaded, stale_cache, cachepath_cache, cachepaths, sourcepath, flags)
71✔
1892
    return !isnothing(path)
104✔
1893
end
1894

1895
"""
1896
    Base.isrelocatable(pkg::PkgId)
1897

1898
Returns whether a given PkgId within the active project is precompiled and the
1899
associated cache is relocatable.
1900

1901
!!! compat "Julia 1.11"
1902
    This function requires at least Julia 1.11.
1903
"""
1904
function isrelocatable(pkg::PkgId)
×
1905
    path = compilecache_path(pkg)
×
1906
    isnothing(path) && return false
×
1907
    io = open(path, "r")
×
1908
    try
×
1909
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
1910
        _, (includes, includes_srcfiles, _), _... = _parse_cache_header(io, path)
×
1911
        for inc in includes
×
1912
            !startswith(inc.filename, "@depot") && return false
×
1913
            if inc ∉ includes_srcfiles
×
1914
                # its an include_dependency
1915
                track_content = inc.mtime == -1.0
×
1916
                track_content || return false
×
1917
            end
1918
        end
×
1919
    finally
1920
        close(io)
×
1921
    end
1922
    return true
×
1923
end
1924

1925
# search for a precompile cache file to load, after some various checks
1926
function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128)
×
1927
    assert_havelock(require_lock)
×
1928
    loaded = start_loading(modkey, build_id, false)
×
1929
    if loaded === nothing
×
1930
        try
×
1931
            modpath = locate_package(modkey)
×
1932
            isnothing(modpath) && error("Cannot locate source for $(repr("text/plain", modkey))")
×
1933
            modpath = String(modpath)::String
×
1934
            set_pkgorigin_version_path(modkey, modpath)
×
1935
            loaded = _require_search_from_serialized(modkey, modpath, build_id, true)
×
1936
        finally
1937
            end_loading(modkey, loaded)
×
1938
        end
1939
        if loaded isa Module
×
1940
            insert_extension_triggers(modkey)
×
1941
            run_package_callbacks(modkey)
×
1942
        end
1943
    end
1944
    if loaded isa Module && PkgId(loaded) == modkey && module_build_id(loaded) === build_id
×
1945
        return loaded
×
1946
    end
1947
    return ErrorException("Required dependency $modkey failed to load from a cache file.")
×
1948
end
1949

1950
# returns whether the package is tracked in coverage or malloc tracking based on
1951
# JLOptions and includes
UNCOV
1952
function pkg_tracked(includes)
×
UNCOV
1953
    if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0
×
1954
        return false
×
UNCOV
1955
    elseif JLOptions().code_coverage == 1 || JLOptions().malloc_log == 1 # user
×
1956
        # Just say true. Pkgimages aren't in Base
1957
        return true
×
UNCOV
1958
    elseif JLOptions().code_coverage == 2 || JLOptions().malloc_log == 2 # all
×
UNCOV
1959
        return true
×
1960
    elseif JLOptions().code_coverage == 3 || JLOptions().malloc_log == 3 # tracked path
×
1961
        if JLOptions().tracked_path == C_NULL
×
1962
            return false
×
1963
        else
1964
            tracked_path = unsafe_string(JLOptions().tracked_path)
×
1965
            if isempty(tracked_path)
×
1966
                return false
×
1967
            else
1968
                return any(includes) do inc
×
1969
                    startswith(inc.filename, tracked_path)
×
1970
                end
1971
            end
1972
        end
1973
    end
1974
end
1975

1976
# loads a precompile cache file, ignoring stale_cachefile tests
1977
# load all dependent modules first
1978
function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String})
×
1979
    assert_havelock(require_lock)
×
1980
    local depmodnames
1981
    io = open(path, "r")
×
1982
    ignore_native = false
×
1983
    try
×
1984
        iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.")
×
1985
        _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path)
×
1986

1987
        ignore_native = pkg_tracked(includes)
×
1988

1989
        pkgimage = !isempty(clone_targets)
×
1990
        if pkgimage
×
1991
            ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided")
×
1992
            isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.")
×
1993
            ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile")
×
1994
            # TODO: Check for valid clone_targets?
1995
            isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.")
×
1996
        else
1997
            @assert ocachepath === nothing
×
1998
        end
1999
        isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.")
×
2000
    finally
2001
        close(io)
×
2002
    end
2003
    ndeps = length(depmodnames)
×
2004
    depmods = Vector{Any}(undef, ndeps)
×
2005
    for i in 1:ndeps
×
2006
        modkey, build_id = depmodnames[i]
×
2007
        dep = _tryrequire_from_serialized(modkey, build_id)
×
2008
        if !isa(dep, Module)
×
2009
            return dep
×
2010
        end
2011
        depmods[i] = dep
×
2012
    end
×
2013
    # then load the file
2014
    loaded = _include_from_serialized(pkg, path, ocachepath, depmods, ignore_native; register = true)
×
2015
    return loaded
×
2016
end
2017

2018
# returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it or it was stale
2019
# returns the set of modules restored if the cache load succeeded
2020
@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128, stalecheck::Bool; reasons=nothing, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH)
2✔
2021
    assert_havelock(require_lock)
2✔
2022
    paths = find_all_in_cache_path(pkg, DEPOT_PATH)
1✔
2023
    newdeps = PkgId[]
1✔
2024
    try_build_ids = UInt128[build_id]
1✔
2025
    if build_id == UInt128(0)
1✔
2026
        let loaded = get(loaded_precompiles, pkg, nothing)
1✔
2027
            if loaded !== nothing
1✔
2028
                for mod in loaded # try these in reverse original load order to see if one is already valid
×
2029
                    pushfirst!(try_build_ids, module_build_id(mod))
×
2030
                end
×
2031
            end
2032
        end
2033
    end
2034
    for build_id in try_build_ids
1✔
2035
        for path_to_try in paths::Vector{String}
1✔
2036
            staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck)
4✔
2037
            if staledeps === true
2✔
2038
                continue
1✔
2039
            end
2040
            staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
1✔
2041
            startedloading = length(staledeps) + 1
1✔
2042
            try # any exit from here (goto, break, continue, return) will end_loading
1✔
2043
                # finish checking staledeps module graph, while acquiring all start_loading locks
2044
                # so that concurrent require calls won't make any different decisions that might conflict with the decisions here
2045
                # note that start_loading will drop the loading lock if necessary
2046
                let i = 0
1✔
2047
                    # start_loading here has a deadlock problem if we try to load `A,B,C` and `B,A,D` at the same time:
2048
                    # it will claim A,B have a cycle, but really they just have an ambiguous order and need to be batch-acquired rather than singly
2049
                    # solve that by making sure we can start_loading everything before allocating each of those and doing all the stale checks
2050
                    while i < length(staledeps)
36✔
2051
                        i += 1
35✔
2052
                        dep = staledeps[i]
35✔
2053
                        dep isa Module && continue
35✔
2054
                        _, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
19✔
2055
                        dep = canstart_loading(modkey, modbuild_id, stalecheck)
19✔
2056
                        if dep isa Module
19✔
2057
                            if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
×
2058
                                staledeps[i] = dep
×
2059
                                continue
×
2060
                            else
2061
                                @debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
×
2062
                                @goto check_next_path
×
2063
                            end
2064
                            continue
×
2065
                        elseif dep === nothing
19✔
2066
                            continue
19✔
2067
                        end
2068
                        wait(dep) # releases require_lock, so requires restarting this loop
×
2069
                        i = 0
×
2070
                    end
35✔
2071
                end
2072
                for i in reverse(eachindex(staledeps))
2✔
2073
                    dep = staledeps[i]
35✔
2074
                    dep isa Module && continue
35✔
2075
                    modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
19✔
2076
                    # inline a call to start_loading here
2077
                    @assert canstart_loading(modkey, modbuild_id, stalecheck) === nothing
19✔
2078
                    package_locks[modkey] = (current_task(), Threads.Condition(require_lock), modbuild_id)
19✔
2079
                    startedloading = i
19✔
2080
                    modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
19✔
2081
                    for modpath_to_try in modpaths
19✔
2082
                        modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
74✔
2083
                        if modstaledeps === true
37✔
2084
                            continue
18✔
2085
                        end
2086
                        modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
19✔
2087
                        staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
19✔
2088
                        @goto check_next_dep
19✔
2089
                    end
18✔
2090
                    @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
×
2091
                    @goto check_next_path
×
2092
                    @label check_next_dep
2093
                end
69✔
2094
                M = maybe_loaded_precompile(pkg, newbuild_id)
1✔
2095
                if isa(M, Module)
1✔
2096
                    stalecheck && register_root_module(M)
×
2097
                    return M
×
2098
                end
2099
                if stalecheck
1✔
2100
                    try
×
2101
                        touch(path_to_try) # update timestamp of precompilation file
×
2102
                    catch ex # file might be read-only and then we fail to update timestamp, which is fine
2103
                        ex isa IOError || rethrow()
×
2104
                    end
2105
                end
2106
                # finish loading module graph into staledeps
2107
                # n.b. this runs __init__ methods too early, so it is very unwise to have those, as they may see inconsistent loading state, causing them to fail unpredictably here
2108
                for i in eachindex(staledeps)
1✔
2109
                    dep = staledeps[i]
35✔
2110
                    dep isa Module && continue
35✔
2111
                    modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
19✔
2112
                    set_pkgorigin_version_path(modkey, modpath)
19✔
2113
                    dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
38✔
2114
                    if !isa(dep, Module)
19✔
2115
                        @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
×
2116
                        @goto check_next_path
×
2117
                    else
2118
                        startedloading = i + 1
19✔
2119
                        end_loading(modkey, dep)
19✔
2120
                        staledeps[i] = dep
19✔
2121
                        push!(newdeps, modkey)
19✔
2122
                    end
2123
                end
69✔
2124
                restored = maybe_loaded_precompile(pkg, newbuild_id)
1✔
2125
                if !isa(restored, Module)
1✔
2126
                    restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
2✔
2127
                end
2128
                isa(restored, Module) && return restored
1✔
2129
                @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
×
2130
                @label check_next_path
×
2131
            finally
2132
                # cancel all start_loading locks that were taken but not fulfilled before failing
2133
                for i in startedloading:length(staledeps)
1✔
2134
                    dep = staledeps[i]
4✔
2135
                    dep isa Module && continue
4✔
2136
                    if dep isa Tuple{String, PkgId, UInt128}
×
2137
                        _, modkey, _ = dep
×
2138
                    else
2139
                        _, modkey, _ = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
×
2140
                    end
2141
                    end_loading(modkey, nothing)
×
2142
                end
7✔
2143
                for modkey in newdeps
1✔
2144
                    insert_extension_triggers(modkey)
38✔
2145
                    stalecheck && run_package_callbacks(modkey)
19✔
2146
                end
19✔
2147
            end
2148
        end
1✔
2149
    end
×
2150
    return nothing
×
2151
end
2152

2153
# to synchronize multiple tasks trying to import/using something
2154
const package_locks = Dict{PkgId,Tuple{Task,Threads.Condition,UInt128}}()
2155

2156
debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
2157
                               # This only triggers if you have multiple tasks trying to load the same package at the same time,
2158
                               # so it is unlikely to make a performance difference normally.
2159

UNCOV
2160
function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
×
UNCOV
2161
    assert_havelock(require_lock)
×
UNCOV
2162
    require_lock.reentrancy_cnt == 1 || throw(ConcurrencyViolationError("recursive call to start_loading"))
×
UNCOV
2163
    loading = get(package_locks, modkey, nothing)
×
UNCOV
2164
    if loading === nothing
×
UNCOV
2165
        loaded = stalecheck ? maybe_root_module(modkey) : nothing
×
UNCOV
2166
        loaded isa Module && return loaded
×
UNCOV
2167
        if build_id != UInt128(0)
×
UNCOV
2168
            loaded = maybe_loaded_precompile(modkey, build_id)
×
UNCOV
2169
            loaded isa Module && return loaded
×
2170
        end
UNCOV
2171
        return nothing
×
2172
    end
2173
    if !stalecheck && build_id != UInt128(0) && loading[3] != build_id
×
2174
        # don't block using an existing specific loaded module on needing a different concurrently loaded one
2175
        loaded = maybe_loaded_precompile(modkey, build_id)
×
2176
        loaded isa Module && return loaded
×
2177
    end
2178
    # load already in progress for this module on the task
2179
    task, cond = loading
×
2180
    deps = String[modkey.name]
×
2181
    pkgid = modkey
×
2182
    assert_havelock(cond.lock)
×
2183
    if debug_loading_deadlocks && current_task() !== task
×
2184
        waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
×
2185
        for each in package_locks
×
2186
            cond2 = each[2][2]
×
2187
            assert_havelock(cond2.lock)
×
2188
            for waiting in cond2.waitq
×
2189
                push!(waiters, waiting => (each[2][1] => each[1]))
×
2190
            end
×
2191
        end
×
2192
        while true
×
2193
            running = get(waiters, task, nothing)
×
2194
            running === nothing && break
×
2195
            task, pkgid = running
×
2196
            push!(deps, pkgid.name)
×
2197
            task === current_task() && break
×
2198
        end
×
2199
    end
2200
    if current_task() === task
×
2201
        others = String[modkey.name] # repeat this to emphasize the cycle here
×
2202
        for each in package_locks # list the rest of the packages being loaded too
×
2203
            if each[2][1] === task
×
2204
                other = each[1].name
×
2205
                other == modkey.name || other == pkgid.name || push!(others, other)
×
2206
            end
2207
        end
×
2208
        msg = sprint(deps, others) do io, deps, others
×
2209
            print(io, "deadlock detected in loading ")
2210
            join(io, deps, " -> ")
2211
            print(io, " -> ")
2212
            join(io, others, " && ")
2213
        end
2214
        throw(ConcurrencyViolationError(msg))
×
2215
    end
2216
    return cond
×
2217
end
2218

UNCOV
2219
function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
×
2220
    # handle recursive and concurrent calls to require
2221
    while true
×
UNCOV
2222
        loaded = canstart_loading(modkey, build_id, stalecheck)
×
UNCOV
2223
        if loaded === nothing
×
UNCOV
2224
            package_locks[modkey] = (current_task(), Threads.Condition(require_lock), build_id)
×
UNCOV
2225
            return nothing
×
2226
        elseif loaded isa Module
×
2227
            return loaded
×
2228
        end
2229
        loaded = wait(loaded)
×
2230
        loaded isa Module && return loaded
×
2231
    end
×
2232
end
2233

UNCOV
2234
function end_loading(modkey::PkgId, @nospecialize loaded)
×
UNCOV
2235
    assert_havelock(require_lock)
×
UNCOV
2236
    loading = pop!(package_locks, modkey)
×
UNCOV
2237
    notify(loading[2], loaded, all=true)
×
2238
    nothing
×
2239
end
2240

2241
# to notify downstream consumers that a module was successfully loaded
2242
# Callbacks take the form (mod::Base.PkgId) -> nothing.
2243
# WARNING: This is an experimental feature and might change later, without deprecation.
2244
const package_callbacks = Any[]
2245
# to notify downstream consumers that a file has been included into a particular module
2246
# Callbacks take the form (mod::Module, filename::String) -> nothing
2247
# WARNING: This is an experimental feature and might change later, without deprecation.
2248
const include_callbacks = Any[]
2249

2250
# used to optionally track dependencies when requiring a module:
2251
const _concrete_dependencies = Pair{PkgId,UInt128}[] # these dependency versions are "set in stone", because they are explicitly loaded, and the process should try to avoid invalidating them
2252
const _require_dependencies = Any[] # a list of (mod, abspath, fsize, hash, mtime) tuples that are the file dependencies of the module currently being precompiled
2253
const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies
2254

UNCOV
2255
function _include_dependency(mod::Module, _path::AbstractString; track_content::Bool=true,
×
2256
                             path_may_be_dir::Bool=false)
UNCOV
2257
    _include_dependency!(_require_dependencies, _track_dependencies[], mod, _path, track_content, path_may_be_dir)
×
2258
end
2259

UNCOV
2260
function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool,
×
2261
                              mod::Module, _path::AbstractString,
2262
                              track_content::Bool, path_may_be_dir::Bool)
UNCOV
2263
    prev = source_path(nothing)
×
UNCOV
2264
    if prev === nothing
×
UNCOV
2265
        path = abspath(_path)
×
2266
    else
2267
        path = normpath(joinpath(dirname(prev), _path))
×
2268
    end
UNCOV
2269
    if !track_dependencies[]
×
UNCOV
2270
        if !path_may_be_dir && !isfile(path)
×
2271
            throw(SystemError("opening file $(repr(path))", Libc.ENOENT))
×
UNCOV
2272
        elseif path_may_be_dir && !Filesystem.isreadable(path)
×
2273
            throw(SystemError("opening file or folder $(repr(path))", Libc.ENOENT))
×
2274
        end
2275
    else
2276
        @lock require_lock begin
×
2277
            if track_content
×
2278
                hash = isdir(path) ? _crc32c(join(readdir(path))) : open(_crc32c, path, "r")
×
2279
                # use mtime=-1.0 here so that fsize==0 && mtime==0.0 corresponds to a missing include_dependency
2280
                push!(dep_list, (mod, path, filesize(path), hash, -1.0))
×
2281
            else
2282
                push!(dep_list, (mod, path, UInt64(0), UInt32(0), mtime(path)))
×
2283
            end
2284
        end
2285
    end
UNCOV
2286
    return path, prev
×
2287
end
2288

2289
"""
2290
    include_dependency(path::AbstractString; track_content::Bool=true)
2291

2292
In a module, declare that the file, directory, or symbolic link specified by `path`
2293
(relative or absolute) is a dependency for precompilation; that is, if `track_content=true`
2294
the module will need to be recompiled if the content of `path` changes
2295
(if `path` is a directory the content equals `join(readdir(path))`).
2296
If `track_content=false` recompilation is triggered when the modification time `mtime` of `path` changes.
2297

2298
This is only needed if your module depends on a path that is not used via [`include`](@ref). It has
2299
no effect outside of compilation.
2300

2301
!!! compat "Julia 1.11"
2302
    Keyword argument `track_content` requires at least Julia 1.11.
2303
    An error is now thrown if `path` is not readable.
2304
"""
2305
function include_dependency(path::AbstractString; track_content::Bool=true)
×
2306
    _include_dependency(Main, path, track_content=track_content, path_may_be_dir=true)
×
2307
    return nothing
×
2308
end
2309

2310
# we throw PrecompilableError when a module doesn't want to be precompiled
2311
import Core: PrecompilableError
2312
function show(io::IO, ex::PrecompilableError)
×
2313
    print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.")
×
2314
end
2315
precompilableerror(ex::PrecompilableError) = true
×
2316
precompilableerror(ex::WrappedException) = precompilableerror(ex.error)
×
2317
precompilableerror(@nospecialize ex) = false
×
2318

2319
# Call __precompile__(false) at the top of a tile prevent it from being precompiled (false)
2320
"""
2321
    __precompile__(isprecompilable::Bool)
2322

2323
Specify whether the file calling this function is precompilable, defaulting to `true`.
2324
If a module or file is *not* safely precompilable, it should call `__precompile__(false)` in
2325
order to throw an error if Julia attempts to precompile it.
2326
"""
2327
@noinline function __precompile__(isprecompilable::Bool=true)
×
2328
    if !isprecompilable && generating_output()
×
2329
        throw(PrecompilableError())
×
2330
    end
2331
    nothing
×
2332
end
2333

2334
# require always works in Main scope and loads files from node 1
2335
# XXX: (this is deprecated, but still used by Distributed)
2336
const toplevel_load = Ref(true)
2337

2338
const _require_world_age = Ref{UInt}(typemax(UInt))
2339

2340
"""
2341
    require(into::Module, module::Symbol)
2342

2343
This function is part of the implementation of [`using`](@ref) / [`import`](@ref), if a module is not
2344
already defined in `Main`. It can also be called directly to force reloading a module,
2345
regardless of whether it has been loaded before (for example, when interactively developing
2346
libraries).
2347

2348
Loads a source file, in the context of the `Main` module, on every active node, searching
2349
standard locations for files. `require` is considered a top-level operation, so it sets the
2350
current `include` path but does not use it to search for files (see help for [`include`](@ref)).
2351
This function is typically used to load library code, and is implicitly called by `using` to
2352
load packages.
2353

2354
When searching for files, `require` first looks for package code in the global array
2355
[`LOAD_PATH`](@ref). `require` is case-sensitive on all platforms, including those with
2356
case-insensitive filesystems like macOS and Windows.
2357

2358
For more details regarding code loading, see the manual sections on [modules](@ref modules) and
2359
[parallel computing](@ref code-availability).
2360
"""
2361
function require(into::Module, mod::Symbol)
×
2362
    world = _require_world_age[]
×
2363
    if world == typemax(UInt)
×
2364
        world = get_world_counter()
×
2365
    end
2366
    return invoke_in_world(world, __require, into, mod)
×
2367
end
2368

2369
function check_for_hint(into, mod)
×
2370
    return begin
×
2371
        if isdefined(into, mod) && getfield(into, mod) isa Module
×
2372
            true, "."
×
2373
        elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
×
2374
            true, ".."
×
2375
        else
2376
            false, ""
×
2377
        end
2378
    end
2379
end
2380

2381
function __require(into::Module, mod::Symbol)
×
2382
    if into === __toplevel__ && generating_output(#=incremental=#true)
×
2383
        error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
×
2384
         is not allowed during package precompilation.")
2385
    end
2386
    @lock require_lock begin
×
2387
    LOADING_CACHE[] = LoadingCache()
×
2388
    try
×
2389
        uuidkey_env = identify_package_env(into, String(mod))
×
2390
        # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env")
2391
        if uuidkey_env === nothing
×
2392
            where = PkgId(into)
×
2393
            if where.uuid === nothing
×
2394
                hint, dots = invokelatest(check_for_hint, into, mod)
×
2395
                hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
×
2396
                install_message = if mod != :Pkg
×
2397
                    start_sentence = hint ? "Otherwise, run" : "Run"
×
2398
                    "\n- $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package."
×
2399
                else  # for some reason Pkg itself isn't availability so do not tell them to use Pkg to install it.
2400
                    ""
×
2401
                end
2402

2403
                throw(ArgumentError("Package $mod not found in current path$hint_message.$install_message"))
×
2404
            else
2405
                manifest_warnings = collect_manifest_warnings()
×
2406
                throw(ArgumentError("""
×
2407
                Package $(where.name) does not have $mod in its dependencies:
2408
                $manifest_warnings- You may have a partially installed environment. Try `Pkg.instantiate()`
2409
                  to ensure all packages in the environment are installed.
2410
                - Or, if you have $(where.name) checked out for development and have
2411
                  added $mod as a dependency but haven't updated your primary
2412
                  environment's manifest file, try `Pkg.resolve()`.
2413
                - Otherwise you may need to report an issue with $(where.name)"""))
2414
            end
2415
        end
2416
        uuidkey, env = uuidkey_env
×
2417
        if _track_dependencies[]
×
2418
            path = binpack(uuidkey)
×
2419
            push!(_require_dependencies, (into, path, UInt64(0), UInt32(0), 0.0))
×
2420
        end
2421
        return _require_prelocked(uuidkey, env)
×
2422
    finally
2423
        LOADING_CACHE[] = nothing
×
2424
    end
2425
    end
2426
end
2427

2428
function find_unsuitable_manifests_versions()
×
2429
    unsuitable_manifests = String[]
×
2430
    dev_manifests = String[]
×
2431
    for env in load_path()
×
2432
        project_file = env_project_file(env)
×
2433
        project_file isa String || continue # no project file
×
2434
        manifest_file = project_file_manifest_path(project_file)
×
2435
        manifest_file isa String || continue # no manifest file
×
2436
        m = parsed_toml(manifest_file)
×
2437
        man_julia_version = get(m, "julia_version", nothing)
×
2438
        man_julia_version isa String || @goto mark
×
2439
        man_julia_version = VersionNumber(man_julia_version)
×
2440
        thispatch(man_julia_version) != thispatch(VERSION) && @goto mark
×
2441
        isempty(man_julia_version.prerelease) != isempty(VERSION.prerelease) && @goto mark
×
2442
        isempty(man_julia_version.prerelease) && continue
×
2443
        man_julia_version.prerelease[1] != VERSION.prerelease[1] && @goto mark
×
2444
        if VERSION.prerelease[1] == "DEV"
×
2445
            # manifests don't store the 2nd part of prerelease, so cannot check further
2446
            # so treat them specially in the warning
2447
            push!(dev_manifests, manifest_file)
×
2448
        end
2449
        continue
×
2450
        @label mark
×
2451
        push!(unsuitable_manifests, string(manifest_file, " (v", man_julia_version, ")"))
×
2452
    end
×
2453
    return unsuitable_manifests, dev_manifests
×
2454
end
2455

2456
function collect_manifest_warnings()
×
2457
    unsuitable_manifests, dev_manifests = find_unsuitable_manifests_versions()
×
2458
    msg = ""
×
2459
    if !isempty(unsuitable_manifests)
×
2460
        msg *= """
×
2461
        - Note that the following manifests in the load path were resolved with a different
2462
          julia version, which may be the cause of the error. Try to re-resolve them in the
2463
          current version, or consider deleting them if that fails:
2464
            $(join(unsuitable_manifests, "\n    "))
2465
        """
2466
    end
2467
    if !isempty(dev_manifests)
×
2468
        msg *= """
×
2469
        - Note that the following manifests in the load path were resolved with a potentially
2470
          different DEV version of the current version, which may be the cause of the error.
2471
          Try to re-resolve them in the current version, or consider deleting them if that fails:
2472
            $(join(dev_manifests, "\n    "))
2473
        """
2474
    end
2475
    return msg
×
2476
end
2477

2478
function require(uuidkey::PkgId)
2479
    world = _require_world_age[]
1✔
2480
    if world == typemax(UInt)
1✔
2481
        world = get_world_counter()
×
2482
    end
2483
    return invoke_in_world(world, __require, uuidkey)
1✔
2484
end
2485
__require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
×
2486
function _require_prelocked(uuidkey::PkgId, env=nothing)
×
2487
    assert_havelock(require_lock)
×
2488
    m = start_loading(uuidkey, UInt128(0), true)
×
2489
    if m === nothing
×
2490
        last = toplevel_load[]
×
2491
        try
×
2492
            toplevel_load[] = false
×
2493
            m = __require_prelocked(uuidkey, env)
×
2494
            if m === nothing
×
2495
                error("package `$(uuidkey.name)` did not define the expected \
×
2496
                      module `$(uuidkey.name)`, check for typos in package module name")
2497
            end
2498
        finally
2499
            toplevel_load[] = last
×
2500
            end_loading(uuidkey, m)
×
2501
        end
2502
        insert_extension_triggers(uuidkey)
×
2503
        # After successfully loading, notify downstream consumers
2504
        run_package_callbacks(uuidkey)
×
2505
    end
2506
    return m
×
2507
end
2508

2509
mutable struct PkgOrigin
UNCOV
2510
    path::Union{String,Nothing}
×
2511
    cachepath::Union{String,Nothing}
2512
    version::Union{VersionNumber,Nothing}
2513
end
UNCOV
2514
PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
×
2515
const pkgorigins = Dict{PkgId,PkgOrigin}()
2516

2517
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
2518
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
2519
const loaded_modules_order = Vector{Module}()
2520

2521
root_module_key(m::Module) = PkgId(m)
30✔
2522

UNCOV
2523
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
×
UNCOV
2524
    @lock require_lock begin
×
UNCOV
2525
    mods = get(loaded_precompiles, key, nothing)
×
UNCOV
2526
    mods === nothing && return
×
UNCOV
2527
    for mod in mods
×
UNCOV
2528
        module_build_id(mod) == buildid && return mod
×
2529
    end
×
2530
    end
2531
end
2532

2533
function module_build_id(m::Module)
2534
    hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
31✔
2535
    return (UInt128(hi) << 64) | lo
31✔
2536
end
2537

2538
@constprop :none function register_root_module(m::Module)
×
2539
    # n.b. This is called from C after creating a new module in `Base.__toplevel__`,
2540
    # instead of adding them to the binding table there.
2541
    @lock require_lock begin
×
2542
    key = PkgId(m, String(nameof(m)))
×
2543
    if haskey(loaded_modules, key)
×
2544
        oldm = loaded_modules[key]
×
2545
        if oldm !== m
×
2546
            if generating_output(#=incremental=#true)
×
2547
                error("Replacing module `$(key.name)`")
×
2548
            else
2549
                @warn "Replacing module `$(key.name)`"
×
2550
            end
2551
        end
2552
    end
2553
    maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
×
2554
    loaded_modules[key] = m
×
2555
    end
2556
    nothing
×
2557
end
2558

2559
register_root_module(Core)
2560
register_root_module(Base)
2561
register_root_module(Main)
2562

2563
# This is used as the current module when loading top-level modules.
2564
# It has the special behavior that modules evaluated in it get added
2565
# to the loaded_modules table instead of getting bindings.
2566
baremodule __toplevel__
2567
using Base
2568
end
2569

2570
# get a top-level Module from the given key
2571
# this is similar to `require`, but worse in almost every possible way
2572
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
×
2573
function root_module(where::Module, name::Symbol)
×
2574
    key = identify_package(where, String(name))
×
2575
    key isa PkgId || throw(KeyError(name))
×
2576
    return root_module(key)
×
2577
end
2578
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
×
UNCOV
2579
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
×
2580

2581
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
×
2582

2583
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2584
# reload(m) = (unreference_module(m); require(m))
2585
function unreference_module(key::PkgId)
×
2586
    @lock require_lock begin
×
2587
    if haskey(loaded_modules, key)
×
2588
        m = pop!(loaded_modules, key)
×
2589
        # need to ensure all modules are GC rooted; will still be referenced
2590
        # in loaded_modules_order
2591
    end
2592
    end
2593
end
2594

2595
# whoever takes the package_locks[pkg] must call this function immediately
UNCOV
2596
function set_pkgorigin_version_path(pkg::PkgId, path::String)
×
UNCOV
2597
    assert_havelock(require_lock)
×
UNCOV
2598
    pkgorigin = get!(PkgOrigin, pkgorigins, pkg)
×
2599
    # Pkg needs access to the version of packages in the sysimage.
UNCOV
2600
    if generating_output(#=incremental=#false)
×
2601
        pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), ".."))
×
2602
    end
UNCOV
2603
    pkgorigin.path = path
×
2604
    nothing
×
2605
end
2606

2607
# Unused
2608
const PKG_PRECOMPILE_HOOK = Ref{Function}()
2609
disable_parallel_precompile::Bool = false
2610

2611
# Returns `nothing` or the new(ish) module
2612
function __require_prelocked(pkg::PkgId, env)
×
2613
    assert_havelock(require_lock)
×
2614

2615
    # perform the search operation to select the module file require intends to load
2616
    path = locate_package(pkg, env)
×
2617
    if path === nothing
×
2618
        throw(ArgumentError("""
×
2619
            Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2620
             - Run `Pkg.instantiate()` to install all recorded dependencies.
2621
            """))
2622
    end
2623
    set_pkgorigin_version_path(pkg, path)
×
2624

2625
    parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop
×
2626
    reasons = Dict{String,Int}()
×
2627
    # attempt to load the module file via the precompile cache locations
2628
    if JLOptions().use_compiled_modules != 0
×
2629
        @label load_from_cache
2630
        loaded = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons)
×
2631
        if loaded isa Module
×
2632
            return loaded
×
2633
        end
2634
    end
2635

2636
    if JLOptions().use_compiled_modules == 3
×
2637
        error("Precompiled image $pkg not available with flags $(CacheFlags())")
×
2638
    end
2639

2640
    # if the module being required was supposed to have a particular version
2641
    # but it was not handled by the precompile loader, complain
2642
    for (concrete_pkg, concrete_build_id) in _concrete_dependencies
×
2643
        if pkg == concrete_pkg
×
2644
            @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
×
2645
                 This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2646
            if JLOptions().incremental != 0
×
2647
                # during incremental precompilation, this should be fail-fast
2648
                throw(PrecompilableError())
×
2649
            end
2650
        end
2651
    end
×
2652

2653
    if JLOptions().use_compiled_modules == 1
×
2654
        if !generating_output(#=incremental=#false)
×
2655
            project = active_project()
×
2656
            if !generating_output() && !parallel_precompile_attempted && !disable_parallel_precompile && @isdefined(Precompilation) && project !== nothing &&
×
2657
                    isfile(project) && project_file_manifest_path(project) !== nothing
2658
                parallel_precompile_attempted = true
×
2659
                unlock(require_lock)
×
2660
                try
×
2661
                    Precompilation.precompilepkgs([pkg.name]; _from_loading=true, ignore_loaded=false)
×
2662
                finally
2663
                    lock(require_lock)
×
2664
                end
2665
                @goto load_from_cache
×
2666
            end
2667
            # spawn off a new incremental pre-compile task for recursive `require` calls
2668
            loaded = maybe_cachefile_lock(pkg, path) do
×
2669
                # double-check the search now that we have lock
2670
                m = _require_search_from_serialized(pkg, path, UInt128(0), true)
2671
                m isa Module && return m
2672
                triggers = get(EXT_PRIMED, pkg, nothing)
2673
                loadable_exts = nothing
2674
                if triggers !== nothing # extension
2675
                    loadable_exts = PkgId[]
2676
                    for (ext′, triggers′) in EXT_PRIMED
2677
                        if triggers′ ⊊ triggers
2678
                            push!(loadable_exts, ext′)
2679
                        end
2680
                    end
2681
                end
2682
                return compilecache(pkg, path; reasons, loadable_exts)
2683
            end
2684
            loaded isa Module && return loaded
×
2685
            if isnothing(loaded) # maybe_cachefile_lock returns nothing if it had to wait for another process
×
2686
                @goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search
×
2687
            elseif isa(loaded, Exception)
×
2688
                if precompilableerror(loaded)
×
2689
                    verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
×
2690
                    @logmsg verbosity "Skipping precompilation due to precompilable error. Importing $(repr("text/plain", pkg))." exception=loaded
×
2691
                else
2692
                    @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
×
2693
                end
2694
                # fall-through to loading the file locally if not incremental
2695
            else
2696
                cachefile, ocachefile = loaded::Tuple{String, Union{Nothing, String}}
×
2697
                loaded = _tryrequire_from_serialized(pkg, cachefile, ocachefile)
×
2698
                if !isa(loaded, Module)
×
2699
                    @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
×
2700
                else
2701
                    return loaded
×
2702
                end
2703
            end
2704
            if JLOptions().incremental != 0
×
2705
                # during incremental precompilation, this should be fail-fast
2706
                throw(PrecompilableError())
×
2707
            end
2708
        end
2709
    end
2710

2711
    # just load the file normally via include
2712
    # for unknown dependencies
2713
    uuid = pkg.uuid
×
2714
    uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
×
2715
    old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
×
2716
    if uuid !== old_uuid
×
2717
        ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
×
2718
    end
2719
    unlock(require_lock)
×
2720
    try
×
2721
        include(__toplevel__, path)
×
2722
        loaded = maybe_root_module(pkg)
×
2723
    finally
2724
        lock(require_lock)
×
2725
        if uuid !== old_uuid
×
2726
            ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
×
2727
        end
2728
    end
2729
    return loaded
×
2730
end
2731

2732
# load a serialized file directly, including dependencies (without checking staleness except for immediate conflicts)
2733
# this does not call start_loading / end_loading, so can lead to some odd behaviors
2734
function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}, sourcepath::String)
×
2735
    @lock require_lock begin
×
2736
    set_pkgorigin_version_path(uuidkey, sourcepath)
×
2737
    newm = _tryrequire_from_serialized(uuidkey, path, ocachepath)
×
2738
    newm isa Module || throw(newm)
×
2739
    insert_extension_triggers(uuidkey)
×
2740
    # After successfully loading, notify downstream consumers
2741
    run_package_callbacks(uuidkey)
×
2742
    return newm
×
2743
    end
2744
end
2745

2746
# load a serialized file directly from append_bundled_depot_path for uuidkey without stalechecks
2747
"""
2748
    require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=nothing)
2749

2750
!!! warning "May load duplicate copies of stdlib packages."
2751

2752
    This requires that all stdlib packages loaded are compatible with having concurrent
2753
    copies of themselves loaded into memory. It also places additional restrictions on
2754
    the kinds of type-piracy that are allowed in stdlibs, since type-piracy can cause the
2755
    dispatch table to become visibly "torn" across multiple different packages.
2756

2757
    The specific requirements are:
2758

2759
      The import side (caller of `require_stdlib`) must not leak any stdlib types, esp.
2760
      to any context that may have a conflicting copy of the stdlib(s) (or vice-versa).
2761
         - e.g., if an output is forwarded to user code, it must contain only Base types.
2762
         - e.g., if an output contains types from the stdlib, it must be consumed "internally"
2763
                 before reaching user code.
2764

2765
      The imported code (loaded stdlibs) must be very careful about type piracy:
2766
         - It must not access any global state that may differ between stdlib copies in
2767
           type-pirated methods.
2768
         - It must not return any stdlib types from any type-pirated public methods (since
2769
           a loaded duplicate would overwrite the Base method again, returning different
2770
           types that don't correspond to the user-accessible copy of the stdlib).
2771
         - It must not pass / discriminate stdlib types in type-pirated methods, except
2772
           indirectly via methods defined in Base and implemented (w/o type-piracy) in
2773
           all copies of the stdlib over their respective types.
2774

2775
      The idea behind the above restrictions is that any type-pirated methods in the stdlib
2776
      must return a result that is simultaneously correct for all of the stdlib's loaded
2777
      copies, including accounting for global state differences and split type identities.
2778

2779
      Furthermore, any imported code must not leak any stdlib types to globals and containers
2780
      (e.g. Vectors and mutable structs) in upstream Modules, since this will also lead to
2781
      type-confusion when the type is later pulled out in user / stdlib code.
2782

2783
    For examples of issues like the above, see:
2784
      [1] https://github.com/JuliaLang/Pkg.jl/issues/4017#issuecomment-2377589989
2785
      [2] https://github.com/JuliaLang/StyledStrings.jl/issues/91#issuecomment-2379602914
2786
"""
2787
function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}=nothing)
1✔
2788
    if generating_output(#=incremental=#true)
2✔
2789
        # Otherwise this would lead to awkward dependency issues by loading a package that isn't in the Project/Manifest
2790
        error("This interactive function requires a stdlib to be loaded, and package code should instead use it directly from that stdlib.")
×
2791
    end
2792
    @lock require_lock begin
1✔
2793
    # the PkgId of the ext, or package if not an ext
2794
    this_uuidkey = ext isa String ? PkgId(uuid5(package_uuidkey.uuid, ext), ext) : package_uuidkey
1✔
2795
    env = Sys.STDLIB
1✔
2796
    newm = start_loading(this_uuidkey, UInt128(0), true)
1✔
2797
    newm === nothing || return newm
1✔
2798
    try
1✔
2799
        # first since this is a stdlib, try to look there directly first
2800
        if ext === nothing
1✔
2801
            sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl")
1✔
2802
        else
2803
            sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext)
2804
        end
2805
        depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
1✔
2806
        set_pkgorigin_version_path(this_uuidkey, sourcepath)
1✔
2807
        newm = _require_search_from_serialized(this_uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path)
1✔
2808
    finally
2809
        end_loading(this_uuidkey, newm)
2✔
2810
    end
2811
    if newm isa Module
1✔
2812
        # After successfully loading, notify downstream consumers
2813
        insert_extension_triggers(env, this_uuidkey)
1✔
2814
        run_package_callbacks(this_uuidkey)
1✔
2815
    else
2816
        # if the user deleted their bundled depot, next try to load it completely normally
2817
        newm = _require_prelocked(this_uuidkey)
×
2818
    end
2819
    return newm
1✔
2820
    end
2821
end
2822

2823
# relative-path load
2824

2825
"""
2826
    include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")
2827

2828
Like [`include`](@ref), except reads code from the given string rather than from a file.
2829

2830
The optional first argument `mapexpr` can be used to transform the included code before
2831
it is evaluated: for each parsed expression `expr` in `code`, the `include_string` function
2832
actually evaluates `mapexpr(expr)`.  If it is omitted, `mapexpr` defaults to [`identity`](@ref).
2833

2834
!!! compat "Julia 1.5"
2835
    Julia 1.5 is required for passing the `mapexpr` argument.
2836
"""
UNCOV
2837
function include_string(mapexpr::Function, mod::Module, code::AbstractString,
×
2838
                        filename::AbstractString="string")
UNCOV
2839
    loc = LineNumberNode(1, Symbol(filename))
×
UNCOV
2840
    try
×
UNCOV
2841
        ast = Meta.parseall(code, filename=filename)
×
UNCOV
2842
        @assert Meta.isexpr(ast, :toplevel)
×
UNCOV
2843
        result = nothing
×
UNCOV
2844
        line_and_ex = Expr(:toplevel, loc, nothing)
×
UNCOV
2845
        for ex in ast.args
×
UNCOV
2846
            if ex isa LineNumberNode
×
UNCOV
2847
                loc = ex
×
UNCOV
2848
                line_and_ex.args[1] = ex
×
UNCOV
2849
                continue
×
2850
            end
2851
            ex = mapexpr(ex)
×
2852
            # Wrap things to be eval'd in a :toplevel expr to carry line
2853
            # information as part of the expr.
2854
            line_and_ex.args[2] = ex
×
2855
            result = Core.eval(mod, line_and_ex)
×
UNCOV
2856
        end
×
UNCOV
2857
        return result
×
2858
    catch exc
2859
        # TODO: Now that stacktraces are more reliable we should remove
2860
        # LoadError and expose the real error type directly.
2861
        rethrow(LoadError(filename, loc.line, exc))
×
2862
    end
2863
end
2864

2865
include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
×
2866
    include_string(identity, m, txt, fname)
2867

2868
function source_path(default::Union{AbstractString,Nothing}="")
UNCOV
2869
    s = current_task().storage
×
UNCOV
2870
    if s !== nothing
×
UNCOV
2871
        s = s::IdDict{Any,Any}
×
UNCOV
2872
        if haskey(s, :SOURCE_PATH)
×
2873
            return s[:SOURCE_PATH]::Union{Nothing,String}
×
2874
        end
2875
    end
UNCOV
2876
    return default
×
2877
end
2878

2879
function source_dir()
×
2880
    p = source_path(nothing)
×
2881
    return p === nothing ? pwd() : dirname(p)
×
2882
end
2883

2884
"""
2885
    Base.include([mapexpr::Function,] m::Module, path::AbstractString)
2886

2887
Evaluate the contents of the input source file in the global scope of module `m`.
2888
Every module (except those defined with [`baremodule`](@ref)) has its own
2889
definition of `include` omitting the `m` argument, which evaluates the file in that module.
2890
Returns the result of the last evaluated expression of the input file. During including,
2891
a task-local include path is set to the directory containing the file. Nested calls to
2892
`include` will search relative to that path. This function is typically used to load source
2893
interactively, or to combine files in packages that are broken into multiple source files.
2894

2895
The optional first argument `mapexpr` can be used to transform the included code before
2896
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
2897
actually evaluates `mapexpr(expr)`.  If it is omitted, `mapexpr` defaults to [`identity`](@ref).
2898

2899
!!! compat "Julia 1.5"
2900
    Julia 1.5 is required for passing the `mapexpr` argument.
2901
"""
2902
Base.include # defined in Base.jl
2903

2904
# Full include() implementation which is used after bootstrap
UNCOV
2905
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
×
2906
    @noinline # Workaround for module availability in _simplify_include_frames
×
UNCOV
2907
    path, prev = _include_dependency(mod, _path)
×
UNCOV
2908
    for callback in include_callbacks # to preserve order, must come before eval in include_string
×
2909
        invokelatest(callback, mod, path)
×
2910
    end
×
UNCOV
2911
    code = read(path, String)
×
UNCOV
2912
    tls = task_local_storage()
×
UNCOV
2913
    tls[:SOURCE_PATH] = path
×
UNCOV
2914
    try
×
UNCOV
2915
        return include_string(mapexpr, mod, code, path)
×
2916
    finally
UNCOV
2917
        if prev === nothing
×
UNCOV
2918
            delete!(tls, :SOURCE_PATH)
×
2919
        else
2920
            tls[:SOURCE_PATH] = prev
×
2921
        end
2922
    end
2923
end
2924

2925
"""
2926
    evalfile(path::AbstractString, args::Vector{String}=String[])
2927

2928
Load the file into an anonymous module using [`include`](@ref), evaluate all expressions,
2929
and return the value of the last expression.
2930
The optional `args` argument can be used to set the input arguments of the script (i.e. the global `ARGS` variable).
2931
Note that definitions (e.g. methods, globals) are evaluated in the anonymous module and do not affect the current module.
2932

2933
# Examples
2934

2935
```jldoctest
2936
julia> write("testfile.jl", \"\"\"
2937
           @show ARGS
2938
           1 + 1
2939
       \"\"\");
2940

2941
julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]);
2942
ARGS = ["ARG1", "ARG2"]
2943

2944
julia> x
2945
2
2946

2947
julia> rm("testfile.jl")
2948
```
2949
"""
2950
function evalfile(path::AbstractString, args::Vector{String}=String[])
×
2951
    m = Module(:__anon__)
×
2952
    return Core.eval(m,
×
2953
        Expr(:toplevel,
2954
             :(const ARGS = $args),
2955
             :(const include = $(Base.IncludeInto(m))),
2956
             :(const eval = $(Core.EvalInto(m))),
2957
             :(include($path))))
2958
end
2959
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
×
2960

2961
function load_path_setup_code(load_path::Bool=true)
2962
    code = """
×
2963
    append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH))))
2964
    append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH))))
2965
    """
2966
    if load_path
×
2967
        load_path = map(abspath, Base.load_path())
×
2968
        path_sep = Sys.iswindows() ? ';' : ':'
×
2969
        any(path -> path_sep in path, load_path) &&
×
2970
            error("LOAD_PATH entries cannot contain $(repr(path_sep))")
2971
        code *= """
×
2972
        append!(empty!(Base.LOAD_PATH), $(repr(load_path)))
2973
        ENV["JULIA_LOAD_PATH"] = $(repr(join(load_path, Sys.iswindows() ? ';' : ':')))
2974
        Base.set_active_project(nothing)
2975
        """
2976
    end
2977
    return code
×
2978
end
2979

2980
# Const global for GC root
2981
const newly_inferred = CodeInstance[]
2982

2983
# this is called in the external process that generates precompiled package files
2984
function include_package_for_output(pkg::PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String},
×
2985
                                    concrete_deps::typeof(_concrete_dependencies), source::Union{Nothing,String})
2986

2987
    append!(empty!(Base.DEPOT_PATH), depot_path)
×
2988
    append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
×
2989
    append!(empty!(Base.LOAD_PATH), load_path)
×
2990
    ENV["JULIA_LOAD_PATH"] = join(load_path, Sys.iswindows() ? ';' : ':')
×
2991
    set_active_project(nothing)
×
2992
    Base._track_dependencies[] = true
×
2993
    get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
×
2994
    append!(empty!(Base._concrete_dependencies), concrete_deps)
×
2995
    uuid_tuple = pkg.uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, pkg.uuid)
×
2996

2997
    ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Base.__toplevel__, uuid_tuple)
×
2998
    if source !== nothing
×
2999
        task_local_storage()[:SOURCE_PATH] = source
×
3000
    end
3001

3002
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3003
    try
×
3004
        Base.include(Base.__toplevel__, input)
×
3005
    catch ex
3006
        precompilableerror(ex) || rethrow()
×
3007
        @debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
×
3008
        exit(125) # we define status = 125 means PrecompileableError
×
3009
    finally
3010
        ccall(:jl_set_newly_inferred, Cvoid, (Any,), nothing)
×
3011
    end
3012
    # check that the package defined the expected module so we can give a nice error message if not
3013
    Base.check_package_module_loaded(pkg)
×
3014

3015
    # Re-populate the runtime's newly-inferred array, which will be included
3016
    # in the output. We removed it above to avoid including any code we may
3017
    # have compiled for error handling and validation.
3018
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3019
end
3020

3021
function check_package_module_loaded(pkg::PkgId)
×
3022
    if !haskey(Base.loaded_modules, pkg)
×
3023
        # match compilecache error type for non-125 errors
3024
        error("$(repr("text/plain", pkg)) did not define the expected module `$(pkg.name)`, \
×
3025
            check for typos in package module name")
3026
    end
3027
    return nothing
×
3028
end
3029

3030
# protects against PkgId and UUID being imported and losing Base prefix
3031
_pkg_str(_pkg::PkgId) = (_pkg.uuid === nothing) ? "Base.PkgId($(repr(_pkg.name)))" : "Base.PkgId(Base.UUID(\"$(_pkg.uuid)\"), $(repr(_pkg.name)))"
×
3032
_pkg_str(_pkg::Vector) = sprint(show, eltype(_pkg); context = :module=>nothing) * "[" * join(map(_pkg_str, _pkg), ",") * "]"
×
3033
_pkg_str(_pkg::Pair{PkgId}) = _pkg_str(_pkg.first) * " => " * repr(_pkg.second)
×
3034
_pkg_str(_pkg::Nothing) = "nothing"
×
3035

3036
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
3037
function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String},
×
3038
                           concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3039
                           internal_stderr::IO = stderr, internal_stdout::IO = stdout, loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3040
    @nospecialize internal_stderr internal_stdout
×
3041
    rm(output, force=true)   # Remove file if it exists
×
3042
    output_o === nothing || rm(output_o, force=true)
×
3043
    depot_path = String[abspath(x) for x in DEPOT_PATH]
×
3044
    dl_load_path = String[abspath(x) for x in DL_LOAD_PATH]
×
3045
    load_path = String[abspath(x) for x in Base.load_path()]
×
3046
    # if pkg is a stdlib, append its parent Project.toml to the load path
3047
    triggers = get(EXT_PRIMED, pkg, nothing)
×
3048
    if triggers !== nothing
×
3049
        parentid = triggers[1]
×
3050
        for env in load_path
×
3051
            project_file = env_project_file(env)
×
3052
            if project_file === true
×
3053
                _, parent_project_file = entry_point_and_project_file(env, parentid.name)
×
3054
                if parent_project_file !== nothing
×
3055
                    parentproj = project_file_name_uuid(parent_project_file, parentid.name)
×
3056
                    if parentproj == parentid
×
3057
                        push!(load_path, parent_project_file)
×
3058
                    end
3059
                end
3060
            end
3061
        end
×
3062
    end
3063
    path_sep = Sys.iswindows() ? ';' : ':'
×
3064
    any(path -> path_sep in path, load_path) &&
×
3065
        error("LOAD_PATH entries cannot contain $(repr(path_sep))")
3066

3067
    if output_o === nothing
×
3068
        # remove options that make no difference given the other cache options
3069
        cacheflags = CacheFlags(cacheflags, opt_level=0)
×
3070
    end
3071
    opts = translate_cache_flags(cacheflags, CacheFlags()) # julia_cmd is generated for the running system, and must be fixed if running for precompile instead
×
3072
    if output_o !== nothing
×
3073
        @debug "Generating object cache file for $(repr("text/plain", pkg))"
×
3074
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
×
3075
        push!(opts, "--output-o", output_o)
×
3076
    else
3077
        @debug "Generating cache file for $(repr("text/plain", pkg))"
×
3078
        cpu_target = nothing
×
3079
    end
3080
    push!(opts, "--output-ji", output)
×
3081
    if isassigned(PRECOMPILE_TRACE_COMPILE)
×
3082
        push!(opts, "--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])")
×
3083
        push!(opts, "--trace-compile-timing")
×
3084
    end
3085

3086
    io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd)
×
3087
                               $(flags)
3088
                               $(opts)
3089
                               --output-incremental=yes
3090
                               --startup-file=no --history-file=no --warn-overwrite=yes
3091
                               $(have_color === nothing ? "--color=auto" : have_color ? "--color=yes" : "--color=no")
3092
                               -`,
3093
                              "OPENBLAS_NUM_THREADS" => 1,
3094
                              "JULIA_NUM_THREADS" => 1),
3095
                       stderr = internal_stderr, stdout = internal_stdout),
3096
              "w", stdout)
3097
    # write data over stdin to avoid the (unlikely) case of exceeding max command line size
3098
    write(io.in, """
×
3099
        empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
3100
        Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg))))
3101
        Base.loadable_extensions = $(_pkg_str(loadable_exts))
3102
        Base.precompiling_extension = $(loading_extension)
3103
        Base.precompilation_target = $(_pkg_str(pkg))
3104
        Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
3105
            $(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing))))
3106
        """)
3107
    close(io.in)
×
3108
    return io
×
3109
end
3110

3111
const precompilation_stack = Vector{PkgId}()
3112
# Helpful for debugging when precompilation is unexpectedly nested.
3113
# Enable with `JULIA_DEBUG=nested_precomp`. Note that it expected to be nested in classical code-load precompilation
3114
# TODO: Add detection if extension precompilation is nested and error / return early?
3115
function track_nested_precomp(pkgs::Vector{PkgId})
×
3116
    append!(precompilation_stack, pkgs)
×
3117
    if length(precompilation_stack) > 1
×
3118
        list() = join(map(p->p.name, precompilation_stack), " > ")
×
3119
        @debug "Nested precompilation: $(list())" _group=:nested_precomp
×
3120
    end
3121
end
3122

3123
function compilecache_dir(pkg::PkgId)
×
3124
    entrypath, entryfile = cache_file_entry(pkg)
×
3125
    return joinpath(DEPOT_PATH[1], entrypath)
×
3126
end
3127

3128
function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String
×
3129
    entrypath, entryfile = cache_file_entry(pkg)
×
3130
    cachepath = joinpath(DEPOT_PATH[1], entrypath)
×
3131
    isdir(cachepath) || mkpath(cachepath)
×
3132
    if pkg.uuid === nothing
×
3133
        abspath(cachepath, entryfile) * ".ji"
×
3134
    else
3135
        crc = _crc32c(project)
×
3136
        crc = _crc32c(unsafe_string(JLOptions().image_file), crc)
×
3137
        crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc)
×
3138
        crc = _crc32c(_cacheflag_to_uint8(flags), crc)
×
3139

3140
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
×
3141
        if cpu_target === nothing
×
3142
            cpu_target = unsafe_string(JLOptions().cpu_target)
×
3143
        end
3144
        crc = _crc32c(cpu_target, crc)
×
3145

3146
        crc = _crc32c(prefs_hash, crc)
×
3147
        project_precompile_slug = slug(crc, 5)
×
3148
        abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji"))
×
3149
    end
3150
end
3151

3152
"""
3153
    Base.compilecache(module::PkgId)
3154

3155
Creates a precompiled cache file for a module and all of its dependencies.
3156
This can be used to reduce package load times. Cache files are stored in
3157
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
3158
for important notes.
3159
"""
3160
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
×
3161
    @nospecialize internal_stderr internal_stdout
×
3162
    path = locate_package(pkg)
×
3163
    path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation"))
×
3164
    return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, loadable_exts)
×
3165
end
3166

3167
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
3168

3169
function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout,
×
3170
                      keep_loaded_modules::Bool = true; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3171
                      reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3172

3173
    @nospecialize internal_stderr internal_stdout
×
3174
    # decide where to put the resulting cache file
3175
    cachepath = compilecache_dir(pkg)
×
3176

3177
    # build up the list of modules that we want the precompile process to preserve
3178
    if keep_loaded_modules
×
3179
        concrete_deps = copy(_concrete_dependencies)
×
3180
        for (pkgreq, modreq) in loaded_modules
×
3181
            if !(pkgreq === Main || pkgreq === Core || pkgreq === Base)
×
3182
                push!(concrete_deps, pkgreq => module_build_id(modreq))
×
3183
            end
3184
        end
×
3185
    else
3186
        concrete_deps = empty(_concrete_dependencies)
×
3187
    end
3188
    # run the expression and cache the result
3189
    verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
×
3190
    @logmsg verbosity "Precompiling $(repr("text/plain", pkg)) $(list_reasons(reasons))"
×
3191

3192
    # create a temporary file in `cachepath` directory, write the cache in it,
3193
    # write the checksum, _and then_ atomically move the file to `cachefile`.
3194
    mkpath(cachepath)
×
3195
    cache_objects = JLOptions().use_pkgimages == 1
×
3196
    tmppath, tmpio = mktemp(cachepath)
×
3197

3198
    if cache_objects
×
3199
        tmppath_o, tmpio_o = mktemp(cachepath)
×
3200
        tmppath_so, tmpio_so = mktemp(cachepath)
×
3201
    else
3202
        tmppath_o = nothing
×
3203
    end
3204
    local p
×
3205
    try
×
3206
        close(tmpio)
×
3207
        if cache_objects
×
3208
            close(tmpio_o)
×
3209
            close(tmpio_so)
×
3210
        end
3211
        p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, loadable_exts)
×
3212

3213
        if success(p)
×
3214
            if cache_objects
×
3215
                # Run linker over tmppath_o
3216
                Linking.link_image(tmppath_o, tmppath_so)
×
3217
            end
3218

3219
            # Read preferences hash back from .ji file (we can't precompute because
3220
            # we don't actually know what the list of compile-time preferences are without compiling)
3221
            prefs_hash = preferences_hash(tmppath)
×
3222
            cachefile = compilecache_path(pkg, prefs_hash; flags=cacheflags)
×
3223
            ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing
×
3224

3225
            # append checksum for so to the end of the .ji file:
3226
            crc_so = UInt32(0)
×
3227
            if cache_objects
×
3228
                crc_so = open(_crc32c, tmppath_so, "r")
×
3229
            end
3230

3231
            # append extra crc to the end of the .ji file:
3232
            open(tmppath, "r+") do f
×
3233
                if iszero(isvalid_cache_header(f))
×
3234
                    error("Incompatible header for $(repr("text/plain", pkg)) in new cache file $(repr(tmppath)).")
×
3235
                end
3236
                seekend(f)
×
3237
                write(f, crc_so)
×
3238
                seekstart(f)
×
3239
                write(f, _crc32c(f))
×
3240
            end
3241

3242
            # inherit permission from the source file (and make them writable)
3243
            chmod(tmppath, filemode(path) & 0o777 | 0o200)
×
3244

3245
            # prune the directory with cache files
3246
            if pkg.uuid !== nothing
×
3247
                entrypath, entryfile = cache_file_entry(pkg)
×
3248
                cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath))
×
3249
                if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[]
×
3250
                    idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2]
×
3251
                    evicted_cachefile = joinpath(cachepath, cachefiles[idx])
×
3252
                    @debug "Evicting file from cache" evicted_cachefile
×
3253
                    rm(evicted_cachefile; force=true)
×
3254
                    try
×
3255
                        rm(ocachefile_from_cachefile(evicted_cachefile); force=true)
×
3256
                        @static if Sys.isapple()
×
3257
                            rm(ocachefile_from_cachefile(evicted_cachefile) * ".dSYM"; force=true, recursive=true)
×
3258
                        end
3259
                    catch e
3260
                        e isa IOError || rethrow()
×
3261
                    end
3262
                end
3263
            end
3264

3265
            if cache_objects
×
3266
                ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile)
×
3267
                if ocachefile_new != ocachefile
×
3268
                    cachefile = cachefile_from_ocachefile(ocachefile_new)
×
3269
                    ocachefile = ocachefile_new
×
3270
                end
3271
                @static if Sys.isapple()
×
3272
                    run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull())
×
3273
                end
3274
            end
3275
            # this is atomic according to POSIX (not Win32):
3276
            # but force=true means it will fall back to non atomic
3277
            # move if the initial rename fails.
3278
            mv(tmppath, cachefile; force=true)
×
3279
            return cachefile, ocachefile
×
3280
        end
3281
    finally
3282
        rm(tmppath, force=true)
×
3283
        if cache_objects
×
3284
            rm(tmppath_o::String, force=true)
×
3285
            rm(tmppath_so, force=true)
×
3286
        end
3287
    end
3288
    if p.exitcode == 125
×
3289
        return PrecompilableError()
×
3290
    else
3291
        error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)).")
×
3292
    end
3293
end
3294

3295
function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0)
×
3296
    try
×
3297
        mv(tmppath_so, ocachefile; force=true)
×
3298
    catch e
3299
        e isa IOError || rethrow()
×
3300
        # If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup
3301
        # on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded
3302
        if !isfile(ocachefile) && e.code != Base.UV_EACCES
×
3303
            rethrow()
×
3304
        end
3305
        # Windows prevents renaming a file that is in use so if there is a Julia session started
3306
        # with a package image loaded, we cannot rename that file.
3307
        # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that
3308
        # that cache file does not exist.
3309
        ocachename, ocacheext = splitext(ocachefile_orig)
×
3310
        ocachefile_unique = ocachename * "_$num" * ocacheext
×
3311
        ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1)
×
3312
    end
3313
    return ocachefile
×
3314
end
3315

3316
function object_build_id(obj)
×
3317
    mod = ccall(:jl_object_top_module, Any, (Any,), obj)
×
3318
    if mod === nothing
×
3319
        return nothing
×
3320
    end
3321
    return module_build_id(mod::Module)
×
3322
end
3323

UNCOV
3324
function isvalid_cache_header(f::IOStream)
×
UNCOV
3325
    pkgimage = Ref{UInt8}()
×
UNCOV
3326
    checksum = ccall(:jl_read_verify_header, UInt64, (Ptr{Cvoid}, Ptr{UInt8}, Ptr{Int64}, Ptr{Int64}), f.ios, pkgimage, Ref{Int64}(), Ref{Int64}()) # returns checksum id or zero
×
3327

UNCOV
3328
    if !iszero(checksum) && pkgimage[] != 0
×
3329
        @debug "Cache header was for pkgimage"
×
3330
        return UInt64(0) # We somehow read the header for a pkgimage and not a ji
×
3331
    end
UNCOV
3332
    return checksum
×
3333
end
UNCOV
3334
isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32))
×
3335

UNCOV
3336
function isvalid_pkgimage_crc(f::IOStream, ocachefile::String)
×
UNCOV
3337
    seekstart(f) # TODO necessary
×
UNCOV
3338
    seek(f, filesize(f) - 8)
×
UNCOV
3339
    expected_crc_so = read(f, UInt32)
×
UNCOV
3340
    crc_so = open(_crc32c, ocachefile, "r")
×
UNCOV
3341
    expected_crc_so == crc_so
×
3342
end
3343

3344
mutable struct CacheHeaderIncludes
UNCOV
3345
    const id::PkgId
×
3346
    filename::String
3347
    const fsize::UInt64
3348
    const hash::UInt32
3349
    const mtime::Float64
3350
    const modpath::Vector{String}   # seemingly not needed in Base, but used by Revise
3351
end
3352

3353
function CacheHeaderIncludes(dep_tuple::Tuple{Module, String, Int64, UInt32, Float64})
×
3354
    return CacheHeaderIncludes(PkgId(dep_tuple[1]), dep_tuple[2:end]..., String[])
×
3355
end
3356

3357
function replace_depot_path(path::AbstractString, depots::Vector{String}=normalize_depots_for_relocation())
×
3358
    for depot in depots
×
3359
        if startswith(path, string(depot, Filesystem.pathsep())) || path == depot
×
3360
            path = replace(path, depot => "@depot"; count=1)
×
3361
            break
×
3362
        end
3363
    end
×
3364
    return path
×
3365
end
3366

3367
function normalize_depots_for_relocation()
×
3368
    depots = String[]
×
3369
    sizehint!(depots, length(DEPOT_PATH))
×
3370
    for d in DEPOT_PATH
×
3371
        isdir(d) || continue
×
3372
        if isdirpath(d)
×
3373
            d = dirname(d)
×
3374
        end
3375
        push!(depots, abspath(d))
×
3376
    end
×
3377
    return depots
×
3378
end
3379

3380
function restore_depot_path(path::AbstractString, depot::AbstractString)
UNCOV
3381
    replace(path, r"^@depot" => depot; count=1)
×
3382
end
3383

UNCOV
3384
function resolve_depot(inc::AbstractString)
×
UNCOV
3385
    startswith(inc, string("@depot", Filesystem.pathsep())) || return :not_relocatable
×
UNCOV
3386
    for depot in DEPOT_PATH
×
UNCOV
3387
        ispath(restore_depot_path(inc, depot)) && return depot
×
UNCOV
3388
    end
×
UNCOV
3389
    return :no_depot_found
×
3390
end
3391

UNCOV
3392
function read_module_list(f::IO, has_buildid_hi::Bool)
×
UNCOV
3393
    modules = Vector{Pair{PkgId, UInt128}}()
×
3394
    while true
×
UNCOV
3395
        n = read(f, Int32)
×
UNCOV
3396
        n == 0 && break
×
UNCOV
3397
        sym = String(read(f, n)) # module name
×
UNCOV
3398
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
×
UNCOV
3399
        build_id_hi = UInt128(has_buildid_hi ? read(f, UInt64) : UInt64(0)) << 64
×
UNCOV
3400
        build_id = (build_id_hi | read(f, UInt64)) # build id (checksum + time - not a UUID)
×
UNCOV
3401
        push!(modules, PkgId(uuid, sym) => build_id)
×
UNCOV
3402
    end
×
UNCOV
3403
    return modules
×
3404
end
3405

UNCOV
3406
function _parse_cache_header(f::IO, cachefile::AbstractString)
×
UNCOV
3407
    flags = read(f, UInt8)
×
UNCOV
3408
    modules = read_module_list(f, false)
×
UNCOV
3409
    totbytes = Int64(read(f, UInt64)) # total bytes for file dependencies + preferences
×
3410
    # read the list of requirements
3411
    # and split the list into include and requires statements
UNCOV
3412
    includes = CacheHeaderIncludes[]
×
UNCOV
3413
    requires = Pair{PkgId, PkgId}[]
×
UNCOV
3414
    while true
×
UNCOV
3415
        n2 = read(f, Int32)
×
UNCOV
3416
        totbytes -= 4
×
UNCOV
3417
        if n2 == 0
×
UNCOV
3418
            break
×
3419
        end
UNCOV
3420
        depname = String(read(f, n2))
×
UNCOV
3421
        totbytes -= n2
×
UNCOV
3422
        fsize = read(f, UInt64)
×
UNCOV
3423
        totbytes -= 8
×
UNCOV
3424
        hash = read(f, UInt32)
×
UNCOV
3425
        totbytes -= 4
×
UNCOV
3426
        mtime = read(f, Float64)
×
UNCOV
3427
        totbytes -= 8
×
UNCOV
3428
        n1 = read(f, Int32)
×
UNCOV
3429
        totbytes -= 4
×
3430
        # map ids to keys
UNCOV
3431
        modkey = (n1 == 0) ? PkgId("") : modules[n1].first
×
UNCOV
3432
        modpath = String[]
×
UNCOV
3433
        if n1 != 0
×
3434
            # determine the complete module path
UNCOV
3435
            while true
×
UNCOV
3436
                n1 = read(f, Int32)
×
UNCOV
3437
                totbytes -= 4
×
UNCOV
3438
                if n1 == 0
×
UNCOV
3439
                    break
×
3440
                end
3441
                push!(modpath, String(read(f, n1)))
×
3442
                totbytes -= n1
×
3443
            end
×
3444
        end
UNCOV
3445
        if depname[1] == '\0'
×
UNCOV
3446
            push!(requires, modkey => binunpack(depname))
×
3447
        else
UNCOV
3448
            push!(includes, CacheHeaderIncludes(modkey, depname, fsize, hash, mtime, modpath))
×
3449
        end
UNCOV
3450
    end
×
UNCOV
3451
    prefs = String[]
×
UNCOV
3452
    while true
×
UNCOV
3453
        n2 = read(f, Int32)
×
UNCOV
3454
        totbytes -= 4
×
UNCOV
3455
        if n2 == 0
×
UNCOV
3456
            break
×
3457
        end
3458
        push!(prefs, String(read(f, n2)))
×
3459
        totbytes -= n2
×
3460
    end
×
UNCOV
3461
    prefs_hash = read(f, UInt64)
×
UNCOV
3462
    totbytes -= 8
×
UNCOV
3463
    srctextpos = read(f, Int64)
×
UNCOV
3464
    totbytes -= 8
×
UNCOV
3465
    @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))"
×
3466
    # read the list of modules that are required to be present during loading
UNCOV
3467
    required_modules = read_module_list(f, true)
×
UNCOV
3468
    l = read(f, Int32)
×
UNCOV
3469
    clone_targets = read(f, l)
×
3470

UNCOV
3471
    srcfiles = srctext_files(f, srctextpos, includes)
×
3472

UNCOV
3473
    return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
×
3474
end
3475

UNCOV
3476
function parse_cache_header(f::IO, cachefile::AbstractString)
×
UNCOV
3477
    modules, (includes, srcfiles, requires), required_modules,
×
3478
        srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile)
3479

UNCOV
3480
    includes_srcfiles = CacheHeaderIncludes[]
×
UNCOV
3481
    includes_depfiles = CacheHeaderIncludes[]
×
UNCOV
3482
    for inc in includes
×
UNCOV
3483
        if inc.filename ∈ srcfiles
×
UNCOV
3484
            push!(includes_srcfiles, inc)
×
3485
        else
3486
            push!(includes_depfiles, inc)
×
3487
        end
UNCOV
3488
    end
×
3489

3490

3491
    # The @depot resolution logic for include() files:
3492
    # 1. If the cache is not relocatable because of an absolute path,
3493
    #    we ignore that path for the depot search.
3494
    #    Recompilation will be triggered by stale_cachefile() if that absolute path does not exist.
3495
    # 2. If we can't find a depot for a relocatable path,
3496
    #    we still replace it with the depot we found from other files.
3497
    #    Recompilation will be triggered by stale_cachefile() because the resolved path does not exist.
3498
    # 3. We require that relocatable paths all resolve to the same depot.
3499
    # 4. We explicitly check that all relocatable paths resolve to the same depot. This has two reasons:
3500
    #    - We want to scan all source files in order to provide logs for 1. and 2. above.
3501
    #    - It is possible that a depot might be missing source files.
3502
    #      Assume that we have two depots on DEPOT_PATH, depot_complete and depot_incomplete.
3503
    #      If DEPOT_PATH=["depot_complete","depot_incomplete"] then no recompilation shall happen,
3504
    #      because depot_complete will be picked.
3505
    #      If DEPOT_PATH=["depot_incomplete","depot_complete"] we trigger recompilation and
3506
    #      hopefully a meaningful error about missing files is thrown.
3507
    #      If we were to just select the first depot we find, then whether recompilation happens would
3508
    #      depend on whether the first relocatable file resolves to depot_complete or depot_incomplete.
3509
    srcdepot = nothing
×
3510
    any_not_relocatable = false
×
3511
    any_no_depot_found = false
×
3512
    multiple_depots_found = false
×
UNCOV
3513
    for src in srcfiles
×
UNCOV
3514
        depot = resolve_depot(src)
×
UNCOV
3515
        if depot === :not_relocatable
×
3516
            any_not_relocatable = true
×
UNCOV
3517
        elseif depot === :no_depot_found
×
3518
            any_no_depot_found = true
×
3519
        elseif isnothing(srcdepot)
×
3520
            srcdepot = depot
×
3521
        elseif depot != srcdepot
×
3522
            multiple_depots_found = true
×
3523
        end
UNCOV
3524
    end
×
UNCOV
3525
    if any_no_depot_found
×
UNCOV
3526
        @debug("Unable to resolve @depot tag for at least one include() file from cache file $cachefile", srcfiles, _group=:relocatable)
×
3527
    end
UNCOV
3528
    if any_not_relocatable
×
3529
        @debug("At least one include() file from $cachefile is not relocatable", srcfiles, _group=:relocatable)
×
3530
    end
UNCOV
3531
    if multiple_depots_found
×
3532
        @debug("Some include() files from $cachefile are distributed over multiple depots", srcfiles, _group=:relocatable)
×
UNCOV
3533
    elseif !isnothing(srcdepot)
×
3534
        for inc in includes_srcfiles
×
3535
            inc.filename = restore_depot_path(inc.filename, srcdepot)
×
3536
        end
×
3537
    end
3538

3539
    # unlike include() files, we allow each relocatable include_dependency() file to resolve
3540
    # to a separate depot, #52161
UNCOV
3541
    for inc in includes_depfiles
×
3542
        depot = resolve_depot(inc.filename)
×
3543
        if depot === :no_depot_found
×
3544
            @debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", _group=:relocatable)
×
3545
        elseif depot === :not_relocatable
×
3546
            @debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", _group=:relocatable)
×
3547
        else
3548
            inc.filename = restore_depot_path(inc.filename, depot)
×
3549
        end
3550
    end
×
3551

UNCOV
3552
    return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
×
3553
end
3554

3555
function parse_cache_header(cachefile::String)
×
3556
    io = open(cachefile, "r")
×
3557
    try
×
3558
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3559
        ret = parse_cache_header(io, cachefile)
×
3560
        return ret
×
3561
    finally
3562
        close(io)
×
3563
    end
3564
end
3565

3566
preferences_hash(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[6]
×
3567
function preferences_hash(cachefile::String)
×
3568
    io = open(cachefile, "r")
×
3569
    try
×
3570
        if iszero(isvalid_cache_header(io))
×
3571
            throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3572
        end
3573
        return preferences_hash(io, cachefile)
×
3574
    finally
3575
        close(io)
×
3576
    end
3577
end
3578

3579
function cache_dependencies(f::IO, cachefile::AbstractString)
×
3580
    _, (includes, _, _), modules, _... = parse_cache_header(f, cachefile)
×
3581
    return modules, map(chi -> chi.filename, includes)  # return just filename
×
3582
end
3583

3584
function cache_dependencies(cachefile::String)
×
3585
    io = open(cachefile, "r")
×
3586
    try
×
3587
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3588
        return cache_dependencies(io, cachefile)
×
3589
    finally
3590
        close(io)
×
3591
    end
3592
end
3593

3594
function read_dependency_src(io::IO, cachefile::AbstractString, filename::AbstractString)
×
3595
    _, (includes, _, _), _, srctextpos, _, _, _, _ = parse_cache_header(io, cachefile)
×
3596
    srctextpos == 0 && error("no source-text stored in cache file")
×
3597
    seek(io, srctextpos)
×
3598
    return _read_dependency_src(io, filename, includes)
×
3599
end
3600

3601
function _read_dependency_src(io::IO, filename::AbstractString, includes::Vector{CacheHeaderIncludes}=CacheHeaderIncludes[])
×
3602
    while !eof(io)
×
3603
        filenamelen = read(io, Int32)
×
3604
        filenamelen == 0 && break
×
3605
        depotfn = String(read(io, filenamelen))
×
3606
        len = read(io, UInt64)
×
3607
        fn = if !startswith(depotfn, string("@depot", Filesystem.pathsep()))
×
3608
            depotfn
×
3609
        else
3610
            basefn = restore_depot_path(depotfn, "")
×
3611
            idx = findfirst(includes) do inc
×
3612
                endswith(inc.filename, basefn)
×
3613
            end
3614
            isnothing(idx) ? depotfn : includes[idx].filename
×
3615
        end
3616
        if fn == filename
×
3617
            return String(read(io, len))
×
3618
        end
3619
        seek(io, position(io) + len)
×
3620
    end
×
3621
    error(filename, " is not stored in the source-text cache")
×
3622
end
3623

3624
function read_dependency_src(cachefile::String, filename::AbstractString)
×
3625
    io = open(cachefile, "r")
×
3626
    try
×
3627
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3628
        return read_dependency_src(io, cachefile, filename)
×
3629
    finally
3630
        close(io)
×
3631
    end
3632
end
3633

UNCOV
3634
function srctext_files(f::IO, srctextpos::Int64, includes::Vector{CacheHeaderIncludes})
×
UNCOV
3635
    files = Set{String}()
×
UNCOV
3636
    srctextpos == 0 && return files
×
UNCOV
3637
    seek(f, srctextpos)
×
UNCOV
3638
    while !eof(f)
×
UNCOV
3639
        filenamelen = read(f, Int32)
×
UNCOV
3640
        filenamelen == 0 && break
×
UNCOV
3641
        filename = String(read(f, filenamelen))
×
UNCOV
3642
        len = read(f, UInt64)
×
UNCOV
3643
        push!(files, filename)
×
UNCOV
3644
        seek(f, position(f) + len)
×
UNCOV
3645
    end
×
UNCOV
3646
    return files
×
3647
end
3648

3649
# Test to see if this UUID is mentioned in this `Project.toml`; either as
3650
# the top-level UUID (e.g. that of the project itself), as a dependency,
3651
# or as an extra/weakdep for Preferences.
3652
function get_uuid_name(project::Dict{String, Any}, uuid::UUID)
×
3653
    uuid_p = get(project, "uuid", nothing)::Union{Nothing, String}
×
3654
    name = get(project, "name", nothing)::Union{Nothing, String}
×
3655
    if name !== nothing && uuid_p !== nothing && UUID(uuid_p) == uuid
×
3656
        return name
×
3657
    end
3658
    deps = get(project, "deps", nothing)::Union{Nothing, Dict{String, Any}}
×
3659
    if deps !== nothing
×
3660
        for (k, v) in deps
×
3661
            if uuid == UUID(v::String)
×
3662
                return k
×
3663
            end
3664
        end
×
3665
    end
3666
    for subkey in ("deps", "extras", "weakdeps")
×
3667
        subsection = get(project, subkey, nothing)::Union{Nothing, Dict{String, Any}}
×
3668
        if subsection !== nothing
×
3669
            for (k, v) in subsection
×
3670
                if uuid == UUID(v::String)
×
3671
                    return k
×
3672
                end
3673
            end
×
3674
        end
3675
    end
×
3676
    return nothing
×
3677
end
3678

3679
function get_uuid_name(project_toml::String, uuid::UUID)
×
3680
    project = parsed_toml(project_toml)
×
3681
    return get_uuid_name(project, uuid)
×
3682
end
3683

3684
# If we've asked for a specific UUID, this function will extract the prefs
3685
# for that particular UUID.  Otherwise, it returns all preferences.
3686
function filter_preferences(prefs::Dict{String, Any}, pkg_name)
3687
    if pkg_name === nothing
×
3688
        return prefs
×
3689
    else
3690
        return get(Dict{String, Any}, prefs, pkg_name)::Dict{String, Any}
×
3691
    end
3692
end
3693

3694
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing})
×
3695
    # We'll return a list of dicts to be merged
3696
    dicts = Dict{String, Any}[]
×
3697

3698
    project = parsed_toml(project_toml)
×
3699
    pkg_name = nothing
×
3700
    if uuid !== nothing
×
3701
        # If we've been given a UUID, map that to the name of the package as
3702
        # recorded in the preferences section.  If we can't find that mapping,
3703
        # exit out, as it means there's no way preferences can be set for that
3704
        # UUID, as we only allow actual dependencies to have preferences set.
3705
        pkg_name = get_uuid_name(project, uuid)
×
3706
        if pkg_name === nothing
×
3707
            return dicts
×
3708
        end
3709
    end
3710

3711
    # Look first inside of `Project.toml` to see we have preferences embedded within there
3712
    proj_preferences = get(Dict{String, Any}, project, "preferences")::Dict{String, Any}
×
3713
    push!(dicts, filter_preferences(proj_preferences, pkg_name))
×
3714

3715
    # Next, look for `(Julia)LocalPreferences.toml` files next to this `Project.toml`
3716
    project_dir = dirname(project_toml)
×
3717
    for name in preferences_names
×
3718
        toml_path = joinpath(project_dir, name)
×
3719
        if isfile(toml_path)
×
3720
            prefs = parsed_toml(toml_path)
×
3721
            push!(dicts, filter_preferences(prefs, pkg_name))
×
3722

3723
            # If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
3724
            break
×
3725
        end
3726
    end
×
3727

3728
    return dicts
×
3729
end
3730

3731
"""
3732
    recursive_prefs_merge(base::Dict, overrides::Dict...)
3733

3734
Helper function to merge preference dicts recursively, honoring overrides in nested
3735
dictionaries properly.
3736
"""
3737
function recursive_prefs_merge(base::Dict{String, Any}, overrides::Dict{String, Any}...)
×
3738
    new_base = Base._typeddict(base, overrides...)
×
3739

3740
    for override in overrides
×
3741
        # Clear entries are keys that should be deleted from any previous setting.
3742
        override_clear = get(override, "__clear__", nothing)
×
3743
        if override_clear isa Vector{String}
×
3744
            for k in override_clear
×
3745
                delete!(new_base, k)
×
3746
            end
×
3747
        end
3748

3749
        for (k, override_k) in override
×
3750
            # Note that if `base` has a mapping that is _not_ a `Dict`, and `override`
3751
            new_base_k = get(new_base, k, nothing)
×
3752
            if new_base_k isa Dict{String, Any} && override_k isa Dict{String, Any}
×
3753
                new_base[k] = recursive_prefs_merge(new_base_k, override_k)
×
3754
            else
3755
                new_base[k] = override_k
×
3756
            end
3757
        end
×
3758
    end
×
3759
    return new_base
×
3760
end
3761

UNCOV
3762
function get_projects_workspace_to_root(project_file)
×
UNCOV
3763
    projects = String[project_file]
×
UNCOV
3764
    while true
×
UNCOV
3765
        project_file = base_project(project_file)
×
UNCOV
3766
        if project_file === nothing
×
UNCOV
3767
            return projects
×
3768
        end
3769
        push!(projects, project_file)
×
3770
    end
×
3771
end
3772

UNCOV
3773
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
×
UNCOV
3774
    merged_prefs = Dict{String,Any}()
×
UNCOV
3775
    loadpath = load_path()
×
UNCOV
3776
    projects_to_merge_prefs = String[]
×
UNCOV
3777
    append!(projects_to_merge_prefs, Iterators.drop(loadpath, 1))
×
UNCOV
3778
    if length(loadpath) >= 1
×
UNCOV
3779
        prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath)))
×
3780
    end
3781

UNCOV
3782
    for env in reverse(projects_to_merge_prefs)
×
UNCOV
3783
        project_toml = env_project_file(env)
×
UNCOV
3784
        if !isa(project_toml, String)
×
UNCOV
3785
            continue
×
3786
        end
3787

3788
        # Collect all dictionaries from the current point in the load path, then merge them in
3789
        dicts = collect_preferences(project_toml, uuid)
×
3790
        merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
×
UNCOV
3791
    end
×
UNCOV
3792
    return merged_prefs
×
3793
end
3794

UNCOV
3795
function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{String})
×
3796
    # Start from a predictable hash point to ensure that the same preferences always
3797
    # hash to the same value, modulo changes in how Dictionaries are hashed.
3798
    h = UInt(0)
×
3799
    uuid === nothing && return UInt64(h)
×
3800

3801
    # Load the preferences
UNCOV
3802
    prefs = get_preferences(uuid)
×
3803

3804
    # Walk through each name that's called out as a compile-time preference
UNCOV
3805
    for name in prefs_list
×
3806
        prefs_value = get(prefs, name, nothing)
×
3807
        if prefs_value !== nothing
×
3808
            h = hash(prefs_value, h)::UInt
×
3809
        end
3810
    end
×
3811
    # We always return a `UInt64` so that our serialization format is stable
UNCOV
3812
    return UInt64(h)
×
3813
end
3814

3815
get_preferences_hash(m::Module, prefs_list::Vector{String}) = get_preferences_hash(PkgId(m).uuid, prefs_list)
×
3816

3817
# This is how we keep track of who is using what preferences at compile-time
3818
const COMPILETIME_PREFERENCES = Dict{UUID,Set{String}}()
3819

3820
# In `Preferences.jl`, if someone calls `load_preference(@__MODULE__, key)` while we're precompiling,
3821
# we mark that usage as a usage at compile-time and call this method, so that at the end of `.ji` generation,
3822
# we can record the list of compile-time preferences and embed that into the `.ji` header
3823
function record_compiletime_preference(uuid::UUID, key::String)
×
3824
    pref = get!(Set{String}, COMPILETIME_PREFERENCES, uuid)
×
3825
    push!(pref, key)
×
3826
    return nothing
×
3827
end
3828
get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIME_PREFERENCES, uuid))
×
3829
get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid)
×
3830
get_compiletime_preferences(::Nothing) = String[]
×
3831

3832
function check_clone_targets(clone_targets)
UNCOV
3833
    rejection_reason = ccall(:jl_check_pkgimage_clones, Any, (Ptr{Cchar},), clone_targets)
×
UNCOV
3834
    if rejection_reason !== nothing
×
3835
        return rejection_reason
×
3836
    end
3837
end
3838

3839
# Set by FileWatching.__init__()
3840
global mkpidlock_hook::Any
3841
global trymkpidlock_hook::Any
3842
global parse_pidfile_hook::Any
3843

3844
# The preferences hash is only known after precompilation so just assume no preferences.
3845
# Also ignore the active project, which means that if all other conditions are equal,
3846
# the same package cannot be precompiled from different projects and/or different preferences at the same time.
3847
compilecache_pidfile_path(pkg::PkgId; flags::CacheFlags=CacheFlags()) = compilecache_path(pkg, UInt64(0); project="", flags) * ".pidfile"
×
3848

3849
const compilecache_pidlock_stale_age = 10
3850

3851
# Allows processes to wait if another process is precompiling a given source already.
3852
# The lock file mtime will be updated when held at most every `stale_age/2` seconds, with expected
3853
# variance of 10 seconds or more being infrequent but not unusual.
3854
# After `stale_age` seconds beyond the mtime of the lock file, the lock file is deleted and
3855
# precompilation will proceed if the locking process no longer exists or after `stale_age * 5`
3856
# seconds if the process does still exist.
3857
# If the lock is held by another host, it will conservatively wait `stale_age * 5`
3858
# seconds since processes cannot be checked remotely
3859
function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=compilecache_pidlock_stale_age)
×
3860
    if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook)
3861
        pidfile = compilecache_pidfile_path(pkg)
3862
        cachefile = @invokelatest trymkpidlock_hook(f, pidfile; stale_age)
3863
        if cachefile === false
3864
            pid, hostname, age = @invokelatest parse_pidfile_hook(pidfile)
3865
            verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
3866
            if isempty(hostname) || hostname == gethostname()
3867
                @logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile"
3868
            else
3869
                @logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile"
3870
            end
3871
            # wait until the lock is available, but don't actually acquire it
3872
            # returning nothing indicates a process waited for another
3873
            return @invokelatest mkpidlock_hook(Returns(nothing), pidfile; stale_age)
3874
        end
3875
        return cachefile
3876
    else
3877
        # for packages loaded before FileWatching.__init__()
3878
        f()
3879
    end
3880
end
3881

3882
function record_reason(reasons::Dict{String,Int}, reason::String)
×
3883
    reasons[reason] = get(reasons, reason, 0) + 1
×
3884
end
3885
record_reason(::Nothing, ::String) = nothing
×
3886
function list_reasons(reasons::Dict{String,Int})
×
3887
    isempty(reasons) && return ""
×
3888
    return "(cache misses: $(join(("$k ($v)" for (k,v) in reasons), ", ")))"
×
3889
end
3890
list_reasons(::Nothing) = ""
×
3891

3892
function any_includes_stale(includes::Vector{CacheHeaderIncludes}, cachefile::String, reasons::Union{Dict{String,Int},Nothing}=nothing)
×
3893
    for chi in includes
×
3894
        f, fsize_req, hash_req, ftime_req = chi.filename, chi.fsize, chi.hash, chi.mtime
×
3895
        if startswith(f, string("@depot", Filesystem.pathsep()))
×
3896
            @debug("Rejecting stale cache file $cachefile because its depot could not be resolved")
×
3897
            record_reason(reasons, "nonresolveable depot")
×
3898
            return true
×
3899
        end
3900
        if !ispath(f)
×
3901
            _f = fixup_stdlib_path(f)
×
3902
            if _f != f && isfile(_f) && startswith(_f, Sys.STDLIB)
×
3903
                continue
×
3904
            end
3905
            @debug "Rejecting stale cache file $cachefile because file $f does not exist"
×
3906
            record_reason(reasons, "missing sourcefile")
×
3907
            return true
×
3908
        end
3909
        if ftime_req >= 0.0
×
3910
            # this is an include_dependency for which we only recorded the mtime
3911
            ftime = mtime(f)
×
3912
            is_stale = ( ftime != ftime_req ) &&
×
3913
                       ( ftime != floor(ftime_req) ) &&           # Issue #13606, PR #13613: compensate for Docker images rounding mtimes
3914
                       ( ftime != ceil(ftime_req) ) &&            # PR: #47433 Compensate for CirceCI's truncating of timestamps in its caching
3915
                       ( ftime != trunc(ftime_req, digits=6) ) && # Issue #20837, PR #20840: compensate for GlusterFS truncating mtimes to microseconds
3916
                       ( ftime != 1.0 )  &&                       # PR #43090: provide compatibility with Nix mtime.
3917
                       !( 0 < (ftime_req - ftime) < 1e-6 )        # PR #45552: Compensate for Windows tar giving mtimes that may be incorrect by up to one microsecond
3918
            if is_stale
×
3919
                @debug "Rejecting stale cache file $cachefile because mtime of include_dependency $f has changed (mtime $ftime, before $ftime_req)"
×
3920
                record_reason(reasons, "include_dependency mtime change")
×
3921
                return true
×
3922
            end
3923
        else
3924
            fstat = stat(f)
×
3925
            fsize = filesize(fstat)
×
3926
            if fsize != fsize_req
×
3927
                @debug "Rejecting stale cache file $cachefile because file size of $f has changed (file size $fsize, before $fsize_req)"
×
3928
                record_reason(reasons, "include_dependency fsize change")
×
3929
                return true
×
3930
            end
3931
            hash = isdir(fstat) ? _crc32c(join(readdir(f))) : open(_crc32c, f, "r")
×
3932
            if hash != hash_req
×
3933
                @debug "Rejecting stale cache file $cachefile because hash of $f has changed (hash $hash, before $hash_req)"
×
3934
                record_reason(reasons, "include_dependency fhash change")
×
3935
                return true
×
3936
            end
3937
        end
3938
    end
×
3939
    return false
×
3940
end
3941

3942
# returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey
3943
# otherwise returns the list of dependencies to also check
3944
@constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false, requested_flags::CacheFlags=CacheFlags(), reasons=nothing)
×
3945
    return stale_cachefile(PkgId(""), UInt128(0), modpath, cachefile; ignore_loaded, requested_flags, reasons)
×
3946
end
3947
@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String;
78✔
3948
                                          ignore_loaded::Bool=false, requested_flags::CacheFlags=CacheFlags(),
3949
                                          reasons::Union{Dict{String,Int},Nothing}=nothing, stalecheck::Bool=true)
3950
    # n.b.: this function does nearly all of the file validation, not just those checks related to stale, so the name is potentially unclear
UNCOV
3951
    io = try
×
UNCOV
3952
        open(cachefile, "r")
×
3953
    catch ex
3954
        ex isa IOError || ex isa SystemError || rethrow()
×
3955
        @debug "Rejecting cache file $cachefile for $modkey because it could not be opened" isfile(cachefile)
×
3956
        return true
×
3957
    end
UNCOV
3958
    try
×
UNCOV
3959
        checksum = isvalid_cache_header(io)
×
UNCOV
3960
        if iszero(checksum)
×
3961
            @debug "Rejecting cache file $cachefile due to it containing an incompatible cache header"
×
3962
            record_reason(reasons, "incompatible header")
×
3963
            return true # incompatible cache file
×
3964
        end
UNCOV
3965
        modules, (includes, _, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, actual_flags = parse_cache_header(io, cachefile)
×
UNCOV
3966
        if isempty(modules)
×
3967
            return true # ignore empty file
×
3968
        end
UNCOV
3969
        if @ccall(jl_match_cache_flags(_cacheflag_to_uint8(requested_flags)::UInt8, actual_flags::UInt8)::UInt8) == 0
×
3970
            @debug """
×
3971
            Rejecting cache file $cachefile for $modkey since the flags are mismatched
3972
              requested flags: $(requested_flags) [$(_cacheflag_to_uint8(requested_flags))]
3973
              cache file:      $(CacheFlags(actual_flags)) [$actual_flags]
3974
            """
3975
            record_reason(reasons, "mismatched flags")
×
3976
            return true
×
3977
        end
UNCOV
3978
        pkgimage = !isempty(clone_targets)
×
UNCOV
3979
        if pkgimage
×
UNCOV
3980
            ocachefile = ocachefile_from_cachefile(cachefile)
×
UNCOV
3981
            if JLOptions().use_pkgimages == 0
×
3982
                # presence of clone_targets means native code cache
3983
                @debug "Rejecting cache file $cachefile for $modkey since it would require usage of pkgimage"
×
3984
                record_reason(reasons, "requires pkgimages")
×
3985
                return true
×
3986
            end
UNCOV
3987
            rejection_reasons = check_clone_targets(clone_targets)
×
UNCOV
3988
            if !isnothing(rejection_reasons)
×
3989
                @debug("Rejecting cache file $cachefile for $modkey:",
×
3990
                    Reasons=rejection_reasons,
3991
                    var"Image Targets"=parse_image_targets(clone_targets),
3992
                    var"Current Targets"=current_image_targets())
3993
                record_reason(reasons, "target mismatch")
×
3994
                return true
×
3995
            end
UNCOV
3996
            if !isfile(ocachefile)
×
3997
                @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found"
×
3998
                record_reason(reasons, "missing ocachefile")
×
3999
                return true
×
4000
            end
4001
        else
4002
            ocachefile = nothing
×
4003
        end
UNCOV
4004
        id = first(modules)
×
UNCOV
4005
        if id.first != modkey && modkey != PkgId("")
×
4006
            @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead"
×
4007
            record_reason(reasons, "for different pkgid")
×
4008
            return true
×
4009
        end
UNCOV
4010
        id_build = id.second
×
UNCOV
4011
        id_build = (UInt128(checksum) << 64) | (id_build % UInt64)
×
UNCOV
4012
        if build_id != UInt128(0)
×
UNCOV
4013
            if id_build != build_id
×
4014
                @debug "Ignoring cache file $cachefile for $modkey ($(UUID(id_build))) since it does not provide desired build_id ($((UUID(build_id))))"
×
4015
                record_reason(reasons, "for different buildid")
×
4016
                return true
×
4017
            end
4018
        end
UNCOV
4019
        id = id.first
×
UNCOV
4020
        modules = Dict{PkgId, UInt64}(modules)
×
4021

4022
        # Check if transitive dependencies can be fulfilled
UNCOV
4023
        ndeps = length(required_modules)
×
UNCOV
4024
        depmods = Vector{Any}(undef, ndeps)
×
UNCOV
4025
        for i in 1:ndeps
×
UNCOV
4026
            req_key, req_build_id = required_modules[i]
×
4027
            # Check if module is already loaded
UNCOV
4028
            M = stalecheck ? nothing : maybe_loaded_precompile(req_key, req_build_id)
×
UNCOV
4029
            if M !== nothing
×
UNCOV
4030
                @assert PkgId(M) == req_key && module_build_id(M) === req_build_id
×
UNCOV
4031
                depmods[i] = M
×
UNCOV
4032
                continue
×
4033
            end
UNCOV
4034
            M = maybe_root_module(req_key)
×
UNCOV
4035
            if M isa Module
×
4036
                if PkgId(M) == req_key && module_build_id(M) === req_build_id
×
4037
                    depmods[i] = M
×
4038
                    continue
×
4039
                elseif M == Core
×
4040
                    @debug "Rejecting cache file $cachefile because it was made with a different julia version"
×
4041
                    record_reason(reasons, "wrong julia version")
×
4042
                    return true # Won't be able to fulfill dependency
×
4043
                elseif ignore_loaded || !stalecheck
×
4044
                    # Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages
4045
                else
4046
                    @debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible."
×
4047
                    record_reason(reasons, "wrong dep version loaded")
×
4048
                    return true # Won't be able to fulfill dependency
×
4049
                end
4050
            end
UNCOV
4051
            path = locate_package(req_key) # TODO: add env and/or skip this when stalecheck is false
×
UNCOV
4052
            if path === nothing
×
4053
                @debug "Rejecting cache file $cachefile because dependency $req_key not found."
×
4054
                record_reason(reasons, "dep missing source")
×
4055
                return true # Won't be able to fulfill dependency
×
4056
            end
UNCOV
4057
            depmods[i] = (path, req_key, req_build_id)
×
UNCOV
4058
        end
×
4059

4060
        # check if this file is going to provide one of our concrete dependencies
4061
        # or if it provides a version that conflicts with our concrete dependencies
4062
        # or neither
UNCOV
4063
        if stalecheck
×
4064
            for (req_key, req_build_id) in _concrete_dependencies
×
4065
                build_id = get(modules, req_key, UInt64(0))
×
4066
                if build_id !== UInt64(0)
×
4067
                    build_id |= UInt128(checksum) << 64
×
4068
                    if build_id === req_build_id
×
4069
                        stalecheck = false
×
4070
                        break
×
4071
                    end
4072
                    @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))"
×
4073
                    record_reason(reasons, "wrong dep buildid")
×
4074
                    return true # cachefile doesn't provide the required version of the dependency
×
4075
                end
4076
            end
×
4077
        end
4078

4079
        # now check if this file's content hash has changed relative to its source files
UNCOV
4080
        if stalecheck
×
4081
            if !samefile(includes[1].filename, modpath)
×
4082
                # In certain cases the path rewritten by `fixup_stdlib_path` may
4083
                # point to an unreadable directory, make sure we can `stat` the
4084
                # file before comparing it with `modpath`.
4085
                stdlib_path = fixup_stdlib_path(includes[1].filename)
×
4086
                if !(isreadable(stdlib_path) && samefile(stdlib_path, modpath))
×
4087
                    !samefile(fixup_stdlib_path(includes[1].filename), modpath)
×
4088
                    @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath"
×
4089
                    record_reason(reasons, "wrong source")
×
4090
                    return true # cache file was compiled from a different path
×
4091
                end
4092
            end
4093
            for (modkey, req_modkey) in requires
×
4094
                # verify that `require(modkey, name(req_modkey))` ==> `req_modkey`
4095
                pkg = identify_package(modkey, req_modkey.name)
×
4096
                if pkg != req_modkey
×
4097
                    @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed, expected $modkey => $(repr("text/plain", pkg))"
×
4098
                    record_reason(reasons, "dep uuid changed")
×
4099
                    return true
×
4100
                end
4101
            end
×
4102
            if any_includes_stale(includes, cachefile, reasons)
×
4103
                return true
×
4104
            end
4105
        end
4106

UNCOV
4107
        if !isvalid_file_crc(io)
×
4108
            @debug "Rejecting cache file $cachefile because it has an invalid checksum"
×
4109
            record_reason(reasons, "invalid checksum")
×
4110
            return true
×
4111
        end
4112

UNCOV
4113
        if pkgimage
×
UNCOV
4114
            if !isvalid_pkgimage_crc(io, ocachefile::String)
×
4115
                @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum"
×
4116
                record_reason(reasons, "ocachefile invalid checksum")
×
4117
                return true
×
4118
            end
4119
        end
4120

UNCOV
4121
        curr_prefs_hash = get_preferences_hash(id.uuid, prefs)
×
UNCOV
4122
        if prefs_hash != curr_prefs_hash
×
4123
            @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))"
×
4124
            record_reason(reasons, "preferences hash mismatch")
×
4125
            return true
×
4126
        end
4127

UNCOV
4128
        return depmods, ocachefile, id_build # fresh cachefile
×
4129
    finally
UNCOV
4130
        close(io)
×
4131
    end
4132
end
4133

4134
"""
4135
    @__FILE__ -> String
4136

4137
Expand to a string with the path to the file containing the
4138
macrocall, or an empty string if evaluated by `julia -e <expr>`.
4139
Return `nothing` if the macro was missing parser source information.
4140
Alternatively see [`PROGRAM_FILE`](@ref).
4141
"""
4142
macro __FILE__()
4143
    __source__.file === nothing && return nothing
4144
    return String(__source__.file::Symbol)
4145
end
4146

4147
"""
4148
    @__DIR__ -> String
4149

4150
Macro to obtain the absolute path of the current directory as a string.
4151

4152
If in a script, returns the directory of the script containing the `@__DIR__` macrocall. If run from a
4153
REPL or if evaluated by `julia -e <expr>`, returns the current working directory.
4154

4155
# Examples
4156

4157
The example illustrates the difference in the behaviors of `@__DIR__` and `pwd()`, by creating
4158
a simple script in a different directory than the current working one and executing both commands:
4159

4160
```julia-repl
4161
julia> cd("/home/JuliaUser") # working directory
4162

4163
julia> # create script at /home/JuliaUser/Projects
4164
       open("/home/JuliaUser/Projects/test.jl","w") do io
4165
           print(io, \"\"\"
4166
               println("@__DIR__ = ", @__DIR__)
4167
               println("pwd() = ", pwd())
4168
           \"\"\")
4169
       end
4170

4171
julia> # outputs script directory and current working directory
4172
       include("/home/JuliaUser/Projects/test.jl")
4173
@__DIR__ = /home/JuliaUser/Projects
4174
pwd() = /home/JuliaUser
4175
```
4176
"""
4177
macro __DIR__()
4178
    __source__.file === nothing && return nothing
4179
    _dirname = dirname(String(__source__.file::Symbol))
4180
    return isempty(_dirname) ? pwd() : abspath(_dirname)
4181
end
4182

4183
function prepare_compiler_stub_image!()
×
4184
    ccall(:jl_add_to_module_init_list, Cvoid, (Any,), Compiler)
×
4185
    register_root_module(Compiler)
×
4186
    filter!(mod->mod !== Compiler, loaded_modules_order)
×
4187
end
4188

4189
function expand_compiler_path(tup)
×
4190
    (tup[1], joinpath(Sys.BINDIR, DATAROOTDIR, tup[2]), tup[3:end]...)
×
4191
end
4192
compiler_chi(tup::Tuple) = CacheHeaderIncludes(expand_compiler_path(tup))
×
4193

4194
"""
4195
    precompile(f, argtypes::Tuple{Vararg{Any}})
4196

4197
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
4198
"""
4199
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
4200
    precompile(Tuple{Core.Typeof(f), argtypes...})
×
4201
end
4202

4203
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
4204
function precompile(@nospecialize(argt::Type))
×
4205
    ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
×
4206
    if !ret && ENABLE_PRECOMPILE_WARNINGS[]
×
4207
        @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
×
4208
    end
4209
    return ret
×
4210
end
4211

4212
# Variants that work for `invoke`d calls for which the signature may not be sufficient
4213
precompile(mi::MethodInstance, world::UInt=get_world_counter()) =
×
4214
    (ccall(:jl_compile_method_instance, Cvoid, (Any, Ptr{Cvoid}, UInt), mi, C_NULL, world); return true)
×
4215

4216
"""
4217
    precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
4218

4219
Precompile a specific method for the given argument types. This may be used to precompile
4220
a different method than the one that would ordinarily be chosen by dispatch, thus
4221
mimicking `invoke`.
4222
"""
4223
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
×
4224
    precompile(Tuple{Core.Typeof(f), argtypes...}, m)
×
4225
end
4226

4227
function precompile(@nospecialize(argt::Type), m::Method)
×
4228
    atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
×
4229
    mi = Base.Compiler.specialize_method(m, atype, sparams)
×
4230
    return precompile(mi)
×
4231
end
4232

4233
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) || @assert false
4234
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) || @assert false
4235
precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), Cmd, CacheFlags, IO, IO)) || @assert false
4236
precompile(create_expr_cache, (PkgId, String, String, Nothing, typeof(_concrete_dependencies), Cmd, CacheFlags, IO, IO)) || @assert false
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc