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

JuliaLang / julia / #38182

15 Aug 2025 03:55AM UTC coverage: 77.87% (-0.4%) from 78.28%
#38182

push

local

web-flow
🤖 [master] Bump the SparseArrays stdlib from 30201ab to bb5ecc0 (#59263)

Stdlib: SparseArrays
URL: https://github.com/JuliaSparse/SparseArrays.jl.git
Stdlib branch: main
Julia branch: master
Old commit: 30201ab
New commit: bb5ecc0
Julia version: 1.13.0-DEV
SparseArrays version: 1.13.0
Bump invoked by: @ViralBShah
Powered by:
[BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl)

Diff:
https://github.com/JuliaSparse/SparseArrays.jl/compare/30201abcb...bb5ecc091

```
$ git log --oneline 30201ab..bb5ecc0
bb5ecc0 fast quadratic form for dense matrix, sparse vectors (#640)
34ece87 Extend 3-arg `dot` to generic `HermOrSym` sparse matrices (#643)
095b685 Exclude unintended complex symmetric sparse matrices from 3-arg `dot` (#642)
8049287 Fix signature for 2-arg matrix-matrix `dot` (#641)
cff971d Make cond(::SparseMatrix, 1 / Inf) discoverable from 2-norm error (#629)
```

Co-authored-by: ViralBShah <744411+ViralBShah@users.noreply.github.com>

48274 of 61993 relevant lines covered (77.87%)

9571166.83 hits per line

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

74.0
/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)
×
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)
29,734✔
47
        isaccessiblefile(path) || return false
43,938✔
48
        path_basename = String(basename(path))
15,530✔
49
        local casepreserved_basename
50
        header_size = 12
15,530✔
51
        buf = Vector{UInt8}(undef, length(path_basename) + header_size + 1)
15,530✔
52
        while true
15,530✔
53
            ret = ccall(:getattrlist, Cint,
15,530✔
54
                        (Cstring, Ptr{Cvoid}, Ptr{Cvoid}, Csize_t, Culong),
55
                        path, attr_list, buf, sizeof(buf), FSOPT_NOFOLLOW)
56
            systemerror(:getattrlist, ret ≠ 0)
15,530✔
57
            filename_length = GC.@preserve buf unsafe_load(
15,530✔
58
              convert(Ptr{UInt32}, pointer(buf) + 8))
59
            if (filename_length + header_size) > length(buf)
15,530✔
60
                resize!(buf, filename_length + header_size)
×
61
                continue
×
62
            end
63
            casepreserved_basename =
15,530✔
64
              view(buf, (header_size+1):(header_size+filename_length-1))
65
            break
15,530✔
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
15,530✔
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)
22✔
87
    return try
22✔
88
        isdir(dir)
22✔
89
    catch err
90
        err isa IOError || rethrow()
1✔
91
        false
1✔
92
    end
93
end
94

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

104
function isaccessiblepath(path)
1✔
105
    return try
1✔
106
        ispath(path)
1✔
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}
241✔
117
end
118
function SHA1(bytes::Vector{UInt8})
×
119
    length(bytes) == 20 ||
245✔
120
        throw(ArgumentError("wrong number of bytes for SHA1 hash: $(length(bytes))"))
121
    return SHA1(ntuple(i->bytes[i], Val(20)))
490✔
122
end
123
SHA1(s::AbstractString) = SHA1(hex2bytes(s))
40✔
124
parse(::Type{SHA1}, s::AbstractString) = SHA1(s)
1✔
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)
190✔
137
print(io::IO, hash::SHA1) = bytes2hex(io, hash.bytes)
34✔
138
show(io::IO, hash::SHA1) = print(io, "SHA1(\"", hash, "\")")
1✔
139

140
isless(a::SHA1, b::SHA1) = isless(a.bytes, b.bytes)
×
141
hash(a::SHA1, h::UInt) = hash((SHA1, a.bytes), h)
×
142
==(a::SHA1, b::SHA1) = a.bytes == b.bytes
34✔
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
366✔
149
    h = hash(namespace)
366✔
150
    for _ = 1:sizeof(u)÷sizeof(h)
366✔
151
        u <<= sizeof(h) << 3
732✔
152
        u |= (h = hash(key, h))
732✔
153
    end
1,098✔
154
    u &= 0xffffffffffff0fff3fffffffffffffff
366✔
155
    u |= 0x00000000000050008000000000000000
366✔
156
    return UUID(u)
366✔
157
end
158

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

161
function dummy_uuid(project_file::String)
128✔
162
    @lock require_lock begin
128✔
163
    cache = LOADING_CACHE[]
128✔
164
    if cache !== nothing
128✔
165
        uuid = get(cache.dummy_uuid, project_file, nothing)
108✔
166
        uuid === nothing || return uuid
108✔
167
    end
168
    project_path = try
106✔
169
        realpath(project_file)
110✔
170
    catch ex
171
        ex isa IOError || rethrow()
4✔
172
        project_file
110✔
173
    end
174
    uuid = uuid5(ns_dummy_uuid, project_path)
212✔
175
    if cache !== nothing
106✔
176
        cache.dummy_uuid[project_file] = uuid
64✔
177
    end
178
    return uuid
106✔
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)
4✔
187
    sprint(sizehint=p) do io
1,542✔
188
        y = x
1,542✔
189
        n = length(slug_chars)
1,542✔
190
        for i = 1:p
3,080✔
191
            y, d = divrem(y, n)
13,878✔
192
            write(io, slug_chars[1+d])
7,710✔
193
        end
7,710✔
194
    end
195
end
196

197
function package_slug(uuid::UUID, p::Int=5)
1,437✔
198
    crc = _crc32c(uuid)
2,870✔
199
    return slug(crc, p)
1,434✔
200
end
201

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

208
mutable struct CachedTOMLDict
209
    path::String
1,170✔
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)
1,170✔
218
    s = stat(path)
1,170✔
219
    content = read(path)
1,170✔
220
    crc32 = _crc32c(content)
1,170✔
221
    TOML.reinit!(p, String(content); filepath=path)
2,339✔
222
    d = TOML.parse(p)
1,170✔
223
    return CachedTOMLDict(
1,170✔
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)
2,312✔
234
    s = stat(f.path)
2,312✔
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
4,624✔
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
2,312✔
251
end
252

253
struct LoadingCache
254
    load_path::Vector{String}
482✔
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())
482✔
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)
8,928✔
277
function parsed_toml(project_file::AbstractString, toml_cache::TOMLCache, toml_lock::ReentrantLock)
2✔
278
    lock(toml_lock) do
9,087✔
279
        cache = LOADING_CACHE[]
9,083✔
280
        dd = if !haskey(toml_cache.d, project_file)
17,999✔
281
            d = CachedTOMLDict(toml_cache.p, project_file)
1,170✔
282
            toml_cache.d[project_file] = d
1,170✔
283
            d.d
1,170✔
284
        else
285
            d = toml_cache.d[project_file]
7,913✔
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
13,560✔
289
                d.d
5,601✔
290
            else
291
                get_updated_dict(toml_cache.p, d)
16,996✔
292
            end
293
        end
294
        if cache !== nothing
9,083✔
295
            push!(cache.require_parsed, project_file)
6,888✔
296
        end
297
        return dd
9,083✔
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)
292✔
306
    pkgenv === nothing && return nothing
292✔
307
    pkg, env = pkgenv
292✔
308
    return locate_package(pkg, env)
292✔
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)
519✔
334
function identify_package_env(where::PkgId, name::String)
2,031✔
335
    cache = LOADING_CACHE[]
2,031✔
336
    if cache !== nothing
2,031✔
337
        pkg_env = get(cache.identified_where, (where, name), missing)
1,889✔
338
        pkg_env === missing || return pkg_env
1,889✔
339
    end
340
    pkg_env = nothing
1,941✔
341
    if where.name === name
1,941✔
342
        return (where, nothing)
×
343
    elseif where.uuid === nothing
1,941✔
344
        pkg_env = identify_package_env(name) # ignore `where`
535✔
345
    else
346
        for env in load_path()
1,406✔
347
            pkgid = manifest_deps_get(env, where, name)
1,626✔
348
            pkgid === nothing && continue # not found--keep looking
1,626✔
349
            if pkgid.uuid !== nothing
1,406✔
350
                pkg_env = pkgid, env # found in explicit environment--use it
1,406✔
351
            end
352
            break # found in implicit environment--return "not found"
1,406✔
353
        end
220✔
354
        if pkg_env === nothing && is_stdlib(where)
1,406✔
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
1,941✔
362
        cache.identified_where[(where, name)] = pkg_env
3,418✔
363
    end
364
    return pkg_env
1,941✔
365
end
366
function identify_package_env(name::String)
836✔
367
    cache = LOADING_CACHE[]
836✔
368
    if cache !== nothing
836✔
369
        pkg_env = get(cache.identified, name, missing)
484✔
370
        pkg_env === missing || return pkg_env
484✔
371
    end
372
    pkg_env = nothing
835✔
373
    for env in load_path()
835✔
374
        pkg = project_deps_get(env, name)
1,039✔
375
        if pkg !== nothing
940✔
376
            pkg_env = pkg, env # found--return it
835✔
377
            break
835✔
378
        end
379
    end
105✔
380
    if cache !== nothing
835✔
381
        cache.identified[name] = pkg_env
964✔
382
    end
383
    return pkg_env
835✔
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

402
_nothing_or_first(x) = x === nothing ? nothing : first(x)
58,524✔
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
```jldoctest
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))
76✔
430
identify_package(where::PkgId, name::String)  = _nothing_or_first(identify_package_env(where, name))
118,393✔
431
identify_package(name::String)                = _nothing_or_first(identify_package_env(name))
19,934✔
432

433
function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,Tuple{String,String}}
3,903✔
434
    cache = LOADING_CACHE[]
5,032✔
435
    if cache !== nothing
3,903✔
436
        pathenv = get(cache.located, (pkg, stopenv), missing)
5,067✔
437
        pathenv === missing || return pathenv
5,067✔
438
    end
439
    path = nothing
2,089✔
440
    env′ = nothing
2,089✔
441
    if pkg.uuid === nothing
2,089✔
442
        for env in load_path()
183✔
443
            # look for the toplevel pkg `pkg.name` in this entry
444
            found = project_deps_get(env, pkg.name)
187✔
445
            if found !== nothing
187✔
446
                @assert found.name == pkg.name
181✔
447
                if found.uuid === nothing
181✔
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)
181✔
452
                    env′ = env
181✔
453
                    @goto done
181✔
454
                end
455
            end
456
            if !(loading_extension || precompiling_extension)
12✔
457
                stopenv == env && @goto done
6✔
458
            end
459
        end
6✔
460
    else
461
        for env in load_path()
1,906✔
462
            path = manifest_uuid_path(env, pkg)
2,170✔
463
            # missing is used as a sentinel to stop looking further down in envs
464
            if path === missing
2,170✔
465
                path = nothing
×
466
                @goto done
×
467
            end
468
            if path !== nothing
2,170✔
469
                env′ = env
1,905✔
470
                @goto done
1,905✔
471
            end
472
            if !(loading_extension || precompiling_extension)
526✔
473
                stopenv == env && break
261✔
474
            end
475
        end
265✔
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)
1✔
479
        if mbypath isa String
1✔
480
            path = mbypath
×
481
            env′ = Sys.STDLIB
×
482
            @goto done
×
483
        end
484
    end
485
    @label done
486
    if path !== nothing && !isfile_casesensitive(path)
2,089✔
487
        path = nothing
×
488
    end
489
    if cache !== nothing
2,089✔
490
        cache.located[(pkg, stopenv)] = path === nothing ? nothing : (path, something(env′))
2,877✔
491
    end
492
    path === nothing && return nothing
2,089✔
493
    return path, something(env′)
2,086✔
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
"""
510
function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String}
2,185✔
511
    _nothing_or_first(locate_package_env(pkg, stopenv))
58,552✔
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)
45✔
526
    @lock require_lock begin
45✔
527
    pkgid = PkgId(m)
45✔
528
    origin = get(pkgorigins, pkgid, nothing)
90✔
529
    origin === nothing && return nothing
45✔
530
    path = origin.path
45✔
531
    path === nothing && return nothing
45✔
532
    return fixup_stdlib_path(path)
45✔
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...)
48✔
563
    rootmodule = moduleroot(m)
52✔
564
    path = pathof(rootmodule)
48✔
565
    path === nothing && return nothing
48✔
566
    original = path
47✔
567
    path, base = splitdir(dirname(path))
47✔
568
    if base == "src"
47✔
569
        # package source in `../src/Foo.jl`
570
    elseif base == "ext"
22✔
571
        # extension source in `../ext/FooExt.jl`
572
    elseif basename(path) == "ext"
4✔
573
        # extension source in `../ext/FooExt/FooExt.jl`
574
        path = dirname(path)
4✔
575
    else
576
        error("Unexpected path structure for module source: $original")
×
577
    end
578
    return joinpath(path, paths...)
47✔
579
end
580

581
function get_pkgversion_from_path(path)
22✔
582
    project_file = locate_project_file(path)
44✔
583
    if project_file isa String
22✔
584
        d = parsed_toml(project_file)
22✔
585
        v = get(d, "version", nothing)
44✔
586
        if v !== nothing
22✔
587
            return VersionNumber(v::String)
22✔
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)
22✔
610
    path = pkgdir(m)
22✔
611
    path === nothing && return nothing
22✔
612
    @lock require_lock begin
22✔
613
        v = get_pkgversion_from_path(path)
22✔
614
        pkgorigin = get(pkgorigins, PkgId(moduleroot(m)), nothing)
44✔
615
        # Cache the version
616
        if pkgorigin !== nothing && pkgorigin.version === nothing
22✔
617
            pkgorigin.version = v
36✔
618
        end
619
        return v
22✔
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)
2✔
635
    for proj in project_names
2,105✔
636
        project_file = joinpath(env, proj)
4,210✔
637
        if isfile_casesensitive(project_file)
4,210✔
638
            return project_file
29✔
639
        end
640
    end
6,255✔
641
    return true
2,076✔
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
648
function env_project_file(env::String)::Union{Bool,String}
8,812✔
649
    @lock require_lock begin
8,812✔
650
    cache = LOADING_CACHE[]
8,812✔
651
    if cache !== nothing
8,812✔
652
        project_file = get(cache.env_project_file, env, nothing)
12,805✔
653
        project_file === nothing || return project_file
12,805✔
654
    end
655
    if isdir(env)
2,797✔
656
        project_file = locate_project_file(env)
6,230✔
657
    elseif basename(env) in project_names && isfile_casesensitive(env)
1,436✔
658
        project_file = env
420✔
659
    else
660
        project_file = false
298✔
661
    end
662
    if cache !== nothing
2,797✔
663
        cache.env_project_file[env] = project_file
899✔
664
    end
665
    return project_file
2,797✔
666
    end
667
end
668

669
function base_project(project_file)
1,372✔
670
    base_dir = abspath(joinpath(dirname(project_file), ".."))
1,372✔
671
    base_project_file = env_project_file(base_dir)
1,372✔
672
    base_project_file isa String || return nothing
2,741✔
673
    d = parsed_toml(base_project_file)
3✔
674
    workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
3✔
675
    if workspace === nothing
3✔
676
        return nothing
3✔
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}
1✔
687
    project_file = env_project_file(env)
1,127✔
688
    if project_file isa String
1,127✔
689
        pkg_uuid = explicit_project_deps_get(project_file, name)
104✔
690
        pkg_uuid === nothing || return PkgId(pkg_uuid, name)
203✔
691
    elseif project_file
1,023✔
692
        return implicit_project_deps_get(env, name)
1,010✔
693
    end
694
    return nothing
18✔
695
end
696

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

707
function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothing,PkgId}
1,626✔
708
    uuid = where.uuid
1,626✔
709
    @assert uuid !== nothing
1,626✔
710
    project_file = env_project_file(env)
1,626✔
711
    if project_file isa String
1,626✔
712
        pkg = package_get(project_file, where, name)
158✔
713
        pkg === nothing || return pkg
158✔
714
        d = parsed_toml(project_file)
114✔
715
        exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
170✔
716
        if exts !== nothing
114✔
717
            proj = project_file_name_uuid(project_file, where.name)
56✔
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)
84✔
720
                # Extensions can load weak deps...
721
                weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing}
56✔
722
                if weakdeps !== nothing
28✔
723
                    wuuid = get(weakdeps, name, nothing)::Union{String, Nothing}
41✔
724
                    if wuuid !== nothing
28✔
725
                        return PkgId(UUID(wuuid), name)
13✔
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)
15✔
730
                mby_uuid === nothing || return PkgId(mby_uuid, name)
30✔
731
            end
732
        end
733
        # look for manifest file and `where` stanza
734
        return explicit_manifest_deps_get(project_file, where, name)
86✔
735
    elseif project_file
1,490✔
736
        # if env names a directory, search it
737
        return implicit_manifest_deps_get(env, where, name)
1,339✔
738
    end
739
    return nothing
151✔
740
end
741

742
function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missing}
2,171✔
743
    project_file = env_project_file(env)
2,171✔
744
    if project_file isa String
2,171✔
745
        proj = project_file_name_uuid(project_file, pkg.name)
333✔
746
        if proj == pkg
666✔
747
            # if `pkg` matches the project, return the project itself
748
            return project_file_path(project_file, pkg.name)
56✔
749
        end
750
        mby_ext = project_file_ext_path(project_file, pkg)
277✔
751
        mby_ext === nothing || return mby_ext
310✔
752
        # look for manifest file and `where` stanza
753
        return explicit_manifest_uuid_path(project_file, pkg)
244✔
754
    elseif project_file
1,838✔
755
        # if env names a directory, search it
756
        proj = implicit_manifest_uuid_path(env, pkg)
1,723✔
757
        proj === nothing || return proj
3,251✔
758
        # if not found
759
        triggers = get(EXT_PRIMED, pkg, nothing)
68✔
760
        if triggers !== nothing
65✔
761
            parentid = triggers[1]
3✔
762
            _, parent_project_file = entry_point_and_project_file(env, parentid.name)
3✔
763
            if parent_project_file !== nothing
3✔
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
772
    return nothing
245✔
773
end
774

775

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

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

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

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

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

821
# find project file's corresponding manifest file
822
function project_file_manifest_path(project_file::String)::Union{Nothing,String}
466✔
823
    @lock require_lock begin
466✔
824
    cache = LOADING_CACHE[]
466✔
825
    if cache !== nothing
466✔
826
        manifest_path = get(cache.project_file_manifest_path, project_file, missing)
535✔
827
        manifest_path === missing || return manifest_path
535✔
828
    end
829
    dir = abspath(dirname(project_file))
228✔
830
    d = parsed_toml(project_file)
228✔
831
    base_manifest = workspace_manifest(project_file)
228✔
832
    if base_manifest !== nothing
228✔
833
        return base_manifest
×
834
    end
835
    explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing}
228✔
836
    manifest_path = nothing
228✔
837
    if explicit_manifest !== nothing
228✔
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
228✔
844
        for mfst in manifest_names
228✔
845
            manifest_file = joinpath(dir, mfst)
912✔
846
            if isfile_casesensitive(manifest_file)
912✔
847
                manifest_path = manifest_file
226✔
848
                break
226✔
849
            end
850
        end
686✔
851
    end
852
    if cache !== nothing
228✔
853
        cache.project_file_manifest_path[project_file] = manifest_path
116✔
854
    end
855
    return manifest_path
228✔
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}}
2✔
862
    path = normpath(joinpath(dir, "src", "$name.jl"))
5,918✔
863
    isfile_casesensitive(path) || return nothing, nothing
7,326✔
864
    for proj in project_names
4,510✔
865
        project_file = normpath(joinpath(dir, proj))
9,020✔
866
        isfile_casesensitive(project_file) || continue
9,020✔
867
        return path, project_file
4,510✔
868
    end
4,510✔
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
874
function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
5,214✔
875
    dir_name = joinpath(dir, name)
5,214✔
876
    path, project_file = entry_point_and_project_file_inside(dir_name, name)
9,724✔
877
    path === nothing || return path, project_file
9,724✔
878
    dir_jl = dir_name * ".jl"
704✔
879
    path, project_file = entry_point_and_project_file_inside(dir_jl, name)
704✔
880
    path === nothing || return path, project_file
704✔
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"))
704✔
883
    isfile_casesensitive(path) && return path, nothing
704✔
884
    return nothing, nothing
223✔
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)
62✔
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
62✔
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
242✔
902
    isfile_casesensitive(path) && return normpath(path)
242✔
903
    entrypoint = entryfile === nothing ? joinpath("src", "$name.jl") : entryfile
242✔
904
    return normpath(joinpath(path, entrypoint))
242✔
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}
1,418✔
913
    d = parsed_toml(project_file)
1,559✔
914
    if get(d, "name", nothing)::Union{String, Nothing} === name
2,829✔
915
        root_uuid = dummy_uuid(project_file)
69✔
916
        uuid = get(d, "uuid", nothing)::Union{String, Nothing}
138✔
917
        return uuid === nothing ? root_uuid : UUID(uuid)
69✔
918
    end
919
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2,698✔
920
    if deps !== nothing
1,349✔
921
        uuid = get(deps, name, nothing)::Union{String, Nothing}
2,693✔
922
        uuid === nothing || return UUID(uuid)
2,693✔
923
    end
924
    if ext !== nothing
5✔
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
5✔
938
end
939

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

953
# returns a deps list for both old and new manifest formats
954
function get_deps(raw_manifest::Dict)
2✔
955
    if is_v1_format_manifest(raw_manifest)
449✔
956
        return raw_manifest
2✔
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}
447✔
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}
86✔
966
    manifest_file = project_file_manifest_path(project_file)
86✔
967
    manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
86✔
968
    d = get_deps(parsed_toml(manifest_file))
172✔
969
    found_where = false
86✔
970
    found_name = false
86✔
971
    for (dep_name, entries) in d
172✔
972
        entries::Vector{Any}
290✔
973
        for entry in entries
290✔
974
            entry = entry::Dict{String, Any}
290✔
975
            uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
580✔
976
            uuid === nothing && continue
290✔
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}
414✔
980
            if UUID(uuid) === where.uuid
290✔
981
                found_where = true
32✔
982
                if deps isa Vector{String}
32✔
983
                    found_name = name in deps
47✔
984
                    found_name && @goto done
32✔
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)
312✔
996
                if extensions !== nothing
258✔
997
                    if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name)
101✔
998
                        found_where = true
47✔
999
                        if name == dep_name
47✔
1000
                            return PkgId(UUID(uuid), name)
21✔
1001
                        end
1002
                        exts = extensions[where.name]::Union{String, Vector{String}}
26✔
1003
                        weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
52✔
1004
                        if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts)
40✔
1005
                            for deps′ in [weakdeps, deps]
26✔
1006
                                    if deps′ !== nothing
32✔
1007
                                        if deps′ isa Vector{String}
32✔
1008
                                            found_name = name in deps′
49✔
1009
                                            found_name && @goto done
32✔
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
6✔
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
211✔
1028
    end
415✔
1029
    @label done
1030
    found_where || return nothing
72✔
1031
    found_name || return PkgId(name)
58✔
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}}
116✔
1034
    if name_deps === nothing || length(name_deps) != 1
116✔
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}
58✔
1038
    uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
116✔
1039
    uuid === nothing && return nothing
58✔
1040
    return PkgId(UUID(uuid), name)
58✔
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}
244✔
1045
    manifest_file = project_file_manifest_path(project_file)
244✔
1046
    manifest_file === nothing && return nothing # no manifest, skip env
244✔
1047

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

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

1108
## implicit project & manifest API ##
1109

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

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

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

1166
## other code loading functionality ##
1167

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

1174
function cache_file_entry(pkg::PkgId)
3✔
1175
    uuid = pkg.uuid
1,785✔
1176
    return joinpath(
3,218✔
1177
        "compiled",
1178
        "v$(VERSION.major).$(VERSION.minor)",
1179
        uuid === nothing ? ""       : pkg.name),
1180
        uuid === nothing ? pkg.name : package_slug(uuid)
1181
end
1182

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

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

1235
ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Libc.Libdl.dlext)
1,371✔
1236
cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Libc.Libdl.dlext)"), ".ji")
×
1237

1238

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

1242
# loads a precompile cache file, ignoring stale_cachefile tests
1243
# assuming all depmods are already loaded and everything is valid
1244
# these return either the array of modules loaded from the path / content given
1245
# or an Exception that describes why it couldn't be loaded
1246
# and it reconnects the Base.Docs.META
1247
function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}; register::Bool=true)
2,402✔
1248
    assert_havelock(require_lock)
2,418✔
1249
    timing_imports = TIMING_IMPORTS[] > 0
1,209✔
1250
    try
1,209✔
1251
        if timing_imports
1,209✔
1252
            t_before = time_ns()
×
1253
            cumulative_compile_timing(true)
×
1254
            t_comp_before = cumulative_compile_time_ns()
×
1255
        end
1256

1257
        for i in eachindex(depmods)
2,418✔
1258
            dep = depmods[i]
17,839✔
1259
            dep isa Module && continue
17,839✔
1260
            _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128}
1,031✔
1261
            dep = something(maybe_loaded_precompile(depkey, depbuild_id))
2,062✔
1262
            @assert PkgId(dep) == depkey && module_build_id(dep) === depbuild_id
1,031✔
1263
            depmods[i] = dep
1,031✔
1264
        end
34,469✔
1265

1266
        ignore_native = false
1,209✔
1267
        unlock(require_lock) # temporarily _unlock_ during these operations
1,209✔
1268
        sv = try
1,209✔
1269
            if ocachepath !== nothing
1,209✔
1270
                @debug "Loading object cache file $ocachepath for $(repr("text/plain", pkg))"
1,203✔
1271
                ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint),
1,203✔
1272
                    ocachepath, depmods, #=completeinfo=#false, pkg.name, ignore_native)
1273
            else
1274
                @debug "Loading cache file $path for $(repr("text/plain", pkg))"
6✔
1275
                ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring),
1,209✔
1276
                    path, depmods, #=completeinfo=#false, pkg.name)
1277
            end
1278
        finally
1279
            lock(require_lock)
1,209✔
1280
        end
1281
        if isa(sv, Exception)
1,209✔
1282
            return sv
×
1283
        end
1284

1285
        sv = sv::SimpleVector
1,209✔
1286
        edges = sv[3]::Vector{Any}
1,209✔
1287
        ext_edges = sv[4]::Union{Nothing,Vector{Any}}
1,209✔
1288
        extext_methods = sv[5]::Vector{Any}
1,209✔
1289
        internal_methods = sv[6]::Vector{Any}
1,209✔
1290
        Compiler.@zone "CC: INSERT_BACKEDGES" begin
1291
            ReinferUtils.insert_backedges_typeinf(edges, ext_edges, extext_methods, internal_methods)
1,209✔
1292
        end
1293
        restored = register_restored_modules(sv, pkg, path)
1,209✔
1294

1295
        for M in restored
1,209✔
1296
            M = M::Module
1,646✔
1297
            if is_root_module(M) && PkgId(M) == pkg
2,773✔
1298
                register && register_root_module(M)
1,209✔
1299
                if timing_imports
1,209✔
1300
                    elapsed_time = time_ns() - t_before
×
1301
                    comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before
×
1302
                    print_time_imports_report(M, elapsed_time, comp_time, recomp_time)
×
1303
                end
1304
                return M
1,209✔
1305
            end
1306
        end
437✔
1307
        return ErrorException("Required dependency $(repr("text/plain", pkg)) failed to load from a cache file.")
×
1308

1309
    finally
1310
        timing_imports && cumulative_compile_timing(false)
1,209✔
1311
    end
1312
end
1313

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

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

1381
function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
1,209✔
1382
    # This function is also used by PkgCacheInspector.jl
1383
    assert_havelock(require_lock)
2,418✔
1384
    restored = sv[1]::Vector{Any}
1,209✔
1385
    for M in restored
1,209✔
1386
        M = M::Module
1,646✔
1387
        if isdefinedglobal(M, Base.Docs.META)
1,646✔
1388
            push!(Base.Docs.modules, M)
1,237✔
1389
        end
1390
        if is_root_module(M)
2,083✔
1391
            push!(loaded_modules_order, M)
1,209✔
1392
            push!(get!(Vector{Module}, loaded_precompiles, pkg), M)
1,209✔
1393
        end
1394
    end
1,646✔
1395

1396
    # Register this cache path now - If Requires.jl is loaded, Revise may end
1397
    # up looking at the cache path during the init callback.
1398
    get!(PkgOrigin, pkgorigins, pkg).cachepath = path
1,209✔
1399

1400
    inits = sv[2]::Vector{Any}
1,209✔
1401
    if !isempty(inits)
1,209✔
1402
        unlock(require_lock) # temporarily _unlock_ during these callbacks
331✔
1403
        try
331✔
1404
            for (i, mod) in pairs(inits)
662✔
1405
                run_module_init(mod, i)
332✔
1406
            end
333✔
1407
        finally
1408
            lock(require_lock)
331✔
1409
        end
1410
    end
1411
    return restored
1,209✔
1412
end
1413

1414
function run_module_init(mod::Module, i::Int=1)
332✔
1415
    # `i` informs ordering for the `@time_imports` report formatting
1416
    if TIMING_IMPORTS[] == 0
332✔
1417
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
332✔
1418
    elseif isdefined(mod, :__init__)
×
1419
        elapsed_time = time_ns()
×
1420
        cumulative_compile_timing(true)
×
1421
        compile_elapsedtimes = cumulative_compile_time_ns()
×
1422

1423
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
×
1424

1425
        elapsed_time = time_ns() - elapsed_time
×
1426
        cumulative_compile_timing(false);
×
1427
        comp_time, recomp_time = cumulative_compile_time_ns() .- compile_elapsedtimes
×
1428

1429
        print_time_imports_report_init(mod, i, elapsed_time, comp_time, recomp_time)
×
1430
    end
1431
end
1432

1433
function run_package_callbacks(modkey::PkgId)
1,233✔
1434
    run_extension_callbacks(modkey)
1,233✔
1435
    assert_havelock(require_lock)
2,466✔
1436
    unlock(require_lock)
1,233✔
1437
    try
1,233✔
1438
        for callback in package_callbacks
1,233✔
1439
            invokelatest(callback, modkey)
170✔
1440
        end
170✔
1441
    catch
1442
        # Try to continue loading if a callback errors
1443
        errs = current_exceptions()
×
1444
        @error "Error during package callback" exception=errs
1,233✔
1445
    finally
1446
        lock(require_lock)
1,234✔
1447
    end
1448
    nothing
1,233✔
1449
end
1450

1451

1452
##############
1453
# Extensions #
1454
##############
1455

1456
mutable struct ExtensionId
1457
    const id::PkgId
38✔
1458
    const parentid::PkgId # just need the name, for printing
1459
    const n_total_triggers::Int
1460
    ntriggers::Int # how many more packages must be defined until this is loaded
1461
end
1462

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

1467
function insert_extension_triggers(pkg::PkgId)
1468
    pkg.uuid === nothing && return
1,219✔
1469
    path_env_loc = locate_package_env(pkg)
1,129✔
1470
    path_env_loc === nothing && return
1,129✔
1471
    path, env_loc = path_env_loc
1,129✔
1472
    insert_extension_triggers(env_loc, pkg)
1,129✔
1473
end
1474

1475
function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing}
1,146✔
1476
    project_file = env_project_file(env)
1,146✔
1477
    if project_file isa String || project_file
2,169✔
1478
        implicit_project_file = project_file
1,146✔
1479
        if !(implicit_project_file isa String)
1,146✔
1480
            # if env names a directory, search it for an implicit project file (for stdlibs)
1481
            path, implicit_project_file = entry_point_and_project_file(env, pkg.name)
2,046✔
1482
            if !(implicit_project_file isa String)
1,023✔
1483
                return nothing
×
1484
            end
1485
        end
1486
        # Look in project for extensions to insert
1487
        proj_pkg = project_file_name_uuid(implicit_project_file, pkg.name)
1,146✔
1488
        if pkg == proj_pkg
2,292✔
1489
            d_proj = parsed_toml(implicit_project_file)
1,046✔
1490
            extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
1,057✔
1491
            extensions === nothing && return
1,046✔
1492
            weakdeps = get(Dict{String, Any}, d_proj, "weakdeps")::Dict{String,Any}
11✔
1493
            deps = get(Dict{String, Any}, d_proj, "deps")::Dict{String,Any}
11✔
1494
            total_deps = merge(weakdeps, deps)
11✔
1495
            return _insert_extension_triggers(pkg, extensions, total_deps)
11✔
1496
        end
1497

1498
        # Now look in manifest
1499
        project_file isa String || return nothing
100✔
1500
        manifest_file = project_file_manifest_path(project_file)
100✔
1501
        manifest_file === nothing && return
100✔
1502
        d = get_deps(parsed_toml(manifest_file))
200✔
1503
        for (dep_name, entries) in d
200✔
1504
            entries::Vector{Any}
326✔
1505
            for entry in entries
326✔
1506
                entry = entry::Dict{String, Any}
326✔
1507
                uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
326✔
1508
                uuid === nothing && continue
326✔
1509
                if UUID(uuid) == pkg.uuid
652✔
1510
                    extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
64✔
1511
                    extensions === nothing && return
64✔
1512
                    weakdeps = get(Dict{String, Any}, entry, "weakdeps")::Union{Vector{String}, Dict{String,Any}}
10✔
1513
                    deps = get(Dict{String, Any}, entry, "deps")::Union{Vector{String}, Dict{String,Any}}
10✔
1514

1515
                    function expand_deps_list(deps′::Vector{String})
27✔
1516
                        deps′_expanded = Dict{String, Any}()
17✔
1517
                        for (dep_name, entries) in d
34✔
1518
                            dep_name in deps′ || continue
171✔
1519
                            entries::Vector{Any}
22✔
1520
                            if length(entries) != 1
22✔
1521
                                error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
×
1522
                            end
1523
                            entry = first(entries)::Dict{String, Any}
22✔
1524
                            uuid = entry["uuid"]::String
22✔
1525
                            deps′_expanded[dep_name] = uuid
22✔
1526
                        end
151✔
1527
                        return deps′_expanded
17✔
1528
                    end
1529

1530
                    if weakdeps isa Vector{String}
10✔
1531
                        weakdeps = expand_deps_list(weakdeps)
10✔
1532
                    end
1533
                    if deps isa Vector{String}
10✔
1534
                        deps = expand_deps_list(deps)
7✔
1535
                    end
1536

1537
                    total_deps = merge(weakdeps, deps)
10✔
1538
                    return _insert_extension_triggers(pkg, extensions, total_deps)
10✔
1539
                end
1540
            end
262✔
1541
        end
488✔
1542
    end
1543
    return nothing
36✔
1544
end
1545

1546
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, totaldeps::Dict{String, Any})
21✔
1547
    for (ext, triggers) in extensions
42✔
1548
        triggers = triggers::Union{String, Vector{String}}
40✔
1549
        triggers isa String && (triggers = [triggers])
40✔
1550
        id = PkgId(uuid5(parent.uuid::UUID, ext), ext)
80✔
1551
        if haskey(EXT_PRIMED, id) || haskey(Base.loaded_modules, id)
80✔
1552
            continue  # extension is already primed or loaded, don't add it again
2✔
1553
        end
1554
        EXT_PRIMED[id] = trigger_ids = PkgId[parent]
38✔
1555
        gid = ExtensionId(id, parent, 1 + length(triggers), 1 + length(triggers))
38✔
1556
        trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent)
38✔
1557
        push!(trigger1, gid)
38✔
1558
        for trigger in triggers
38✔
1559
            # TODO: Better error message if this lookup fails?
1560
            uuid_trigger = UUID(totaldeps[trigger]::String)
49✔
1561
            trigger_id = PkgId(uuid_trigger, trigger)
49✔
1562
            push!(trigger_ids, trigger_id)
49✔
1563
            if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
64✔
1564
                trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
35✔
1565
                push!(trigger1, gid)
35✔
1566
            else
1567
                gid.ntriggers -= 1
14✔
1568
            end
1569
        end
49✔
1570
    end
40✔
1571
end
1572

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

1601
function run_extension_callbacks(pkgid::PkgId)
1,233✔
1602
    assert_havelock(require_lock)
2,466✔
1603
    # take ownership of extids that depend on this pkgid
1604
    extids = pop!(EXT_DORMITORY, pkgid, nothing)
1,233✔
1605
    extids === nothing && return
1,233✔
1606
    extids_to_load = Vector{ExtensionId}()
44✔
1607
    for extid in extids
44✔
1608
        @assert extid.ntriggers > 0
69✔
1609
        extid.ntriggers -= 1
69✔
1610
        if extid.ntriggers == 0 && (loadable_extensions === nothing || extid.id in loadable_extensions)
69✔
1611
            push!(extids_to_load, extid)
34✔
1612
        end
1613
    end
69✔
1614
    # Load extensions with the fewest triggers first
1615
    sort!(extids_to_load, by=extid->extid.n_total_triggers)
52✔
1616
    for extid in extids_to_load
44✔
1617
        # actually load extid, now that all dependencies are met,
1618
        succeeded = run_extension_callbacks(extid)
34✔
1619
        succeeded || push!(EXT_DORMITORY_FAILED, extid)
34✔
1620
    end
34✔
1621

1622
    return
44✔
1623
end
1624

1625
"""
1626
    retry_load_extensions()
1627

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

1647
"""
1648
    get_extension(parent::Module, extension::Symbol)
1649

1650
Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded.
1651
"""
1652
get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext)
43✔
1653
function get_extension(parentid::PkgId, ext::Symbol)
41✔
1654
    parentid.uuid === nothing && return nothing
41✔
1655
    extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext))
82✔
1656
    return maybe_root_module(extid)
41✔
1657
end
1658

1659
# End extensions
1660

1661

1662
struct CacheFlags
1663
    # OOICCDDP - see jl_cache_flags
1664
    use_pkgimages::Bool
6,887✔
1665
    debug_level::Int
1666
    check_bounds::Int
1667
    inline::Bool
1668
    opt_level::Int
1669
end
1670
function CacheFlags(f::UInt8)
2✔
1671
    use_pkgimages = Bool(f & 1)
9,247✔
1672
    debug_level = Int((f >> 1) & 3)
4,636✔
1673
    check_bounds = Int((f >> 3) & 3)
4,636✔
1674
    inline = Bool((f >> 5) & 1)
8,884✔
1675
    opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils
4,636✔
1676
    CacheFlags(use_pkgimages, debug_level, check_bounds, inline, opt_level)
4,621✔
1677
end
1678
CacheFlags(f::Int) = CacheFlags(UInt8(f))
×
1679
function CacheFlags(cf::CacheFlags=CacheFlags(ccall(:jl_cache_flags, UInt8, ()));
6,772✔
1680
            use_pkgimages::Union{Nothing,Bool}=nothing,
1681
            debug_level::Union{Nothing,Int}=nothing,
1682
            check_bounds::Union{Nothing,Int}=nothing,
1683
            inline::Union{Nothing,Bool}=nothing,
1684
            opt_level::Union{Nothing,Int}=nothing
1685
        )
1686
    return CacheFlags(
2,266✔
1687
        use_pkgimages === nothing ? cf.use_pkgimages : use_pkgimages,
1688
        debug_level === nothing ? cf.debug_level : debug_level,
1689
        check_bounds === nothing ? cf.check_bounds : check_bounds,
1690
        inline === nothing ? cf.inline : inline,
1691
        opt_level === nothing ? cf.opt_level : opt_level
1692
    )
1693
end
1694
# reflecting jloptions.c defaults
1695
const DefaultCacheFlags = CacheFlags(use_pkgimages=true, debug_level=isdebugbuild() ? 2 : 1, check_bounds=0, inline=true, opt_level=2)
1696

1697
function _cacheflag_to_uint8(cf::CacheFlags)::UInt8
1✔
1698
    f = UInt8(0)
2,097✔
1699
    f |= cf.use_pkgimages << 0
2,097✔
1700
    f |= cf.debug_level << 1
2,097✔
1701
    f |= cf.check_bounds << 3
2,097✔
1702
    f |= cf.inline << 5
2,097✔
1703
    f |= cf.opt_level << 6
2,097✔
1704
    return f
2,097✔
1705
end
1706

1707
function translate_cache_flags(cacheflags::CacheFlags, defaultflags::CacheFlags)
143✔
1708
    opts = String[]
143✔
1709
    cacheflags.use_pkgimages    != defaultflags.use_pkgimages   && push!(opts, cacheflags.use_pkgimages ? "--pkgimages=yes" : "--pkgimages=no")
143✔
1710
    cacheflags.debug_level      != defaultflags.debug_level     && push!(opts, "-g$(cacheflags.debug_level)")
143✔
1711
    cacheflags.check_bounds     != defaultflags.check_bounds    && push!(opts, ("--check-bounds=auto", "--check-bounds=yes", "--check-bounds=no")[cacheflags.check_bounds + 1])
143✔
1712
    cacheflags.inline           != defaultflags.inline          && push!(opts, cacheflags.inline ? "--inline=yes" : "--inline=no")
143✔
1713
    cacheflags.opt_level        != defaultflags.opt_level       && push!(opts, "-O$(cacheflags.opt_level)")
143✔
1714
    return opts
143✔
1715
end
1716

1717
function show(io::IO, cf::CacheFlags)
8✔
1718
    print(io, "CacheFlags(")
8✔
1719
    print(io, "; use_pkgimages=")
8✔
1720
    print(io, cf.use_pkgimages)
8✔
1721
    print(io, ", debug_level=")
8✔
1722
    print(io, cf.debug_level)
8✔
1723
    print(io, ", check_bounds=")
8✔
1724
    print(io, cf.check_bounds)
8✔
1725
    print(io, ", inline=")
8✔
1726
    print(io, cf.inline)
8✔
1727
    print(io, ", opt_level=")
8✔
1728
    print(io, cf.opt_level)
8✔
1729
    print(io, ")")
8✔
1730
end
1731

1732
function Base.parse(::Type{CacheFlags}, s::AbstractString)
1✔
1733
    e = Meta.parse(s)
1✔
1734
    if !(e isa Expr && e.head === :call && length(e.args) == 2 &&
2✔
1735
        e.args[1] === :CacheFlags &&
1736
        e.args[2] isa Expr && e.args[2].head == :parameters)
1737
        throw(ArgumentError("Malformed CacheFlags string"))
×
1738
    end
1739
    params = Dict{Symbol, Any}(p.args[1] => p.args[2] for p in e.args[2].args)
1✔
1740
    use_pkgimages = get(params, :use_pkgimages, nothing)
1✔
1741
    debug_level = get(params, :debug_level, nothing)
1✔
1742
    check_bounds = get(params, :check_bounds, nothing)
1✔
1743
    inline = get(params, :inline, nothing)
1✔
1744
    opt_level = get(params, :opt_level, nothing)
1✔
1745
    return CacheFlags(; use_pkgimages, debug_level, check_bounds, inline, opt_level)
1✔
1746
end
1747

1748
struct ImageTarget
1749
    name::String
1✔
1750
    flags::Int32
1751
    ext_features::String
1752
    features_en::Vector{UInt8}
1753
    features_dis::Vector{UInt8}
1754
end
1755

1756
function parse_image_target(io::IO)
1✔
1757
    flags = read(io, Int32)
1✔
1758
    nfeature = read(io, Int32)
1✔
1759
    feature_en = read(io, 4*nfeature)
1✔
1760
    feature_dis = read(io, 4*nfeature)
1✔
1761
    name_len = read(io, Int32)
1✔
1762
    name = String(read(io, name_len))
2✔
1763
    ext_features_len = read(io, Int32)
1✔
1764
    ext_features = String(read(io, ext_features_len))
1✔
1765
    ImageTarget(name, flags, ext_features, feature_en, feature_dis)
1✔
1766
end
1767

1768
function parse_image_targets(targets::Vector{UInt8})
1✔
1769
    io = IOBuffer(targets)
1✔
1770
    ntargets = read(io, Int32)
1✔
1771
    targets = Vector{ImageTarget}(undef, ntargets)
1✔
1772
    for i in 1:ntargets
2✔
1773
        targets[i] = parse_image_target(io)
1✔
1774
    end
1✔
1775
    return targets
1✔
1776
end
1777

1778
function current_image_targets()
1779
    targets = @ccall jl_reflect_clone_targets()::Vector{UInt8}
×
1780
    return parse_image_targets(targets)
×
1781
end
1782

1783
struct FeatureName
1784
    name::Cstring
1785
    bit::UInt32 # bit index into a `uint32_t` array;
1786
    llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support
1787
end
1788

1789
function feature_names()
×
1790
    fnames = Ref{Ptr{FeatureName}}()
×
1791
    nf = Ref{Csize_t}()
×
1792
    @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid
×
1793
    if fnames[] == C_NULL
×
1794
        @assert nf[] == 0
×
1795
        return Vector{FeatureName}(undef, 0)
×
1796
    end
1797
    Base.unsafe_wrap(Array, fnames[], nf[], own=false)
×
1798
end
1799

1800
function test_feature(features::Vector{UInt8}, feat::FeatureName)
×
1801
    bitidx = feat.bit
×
1802
    u8idx = div(bitidx, 8) + 1
×
1803
    bit = bitidx % 8
×
1804
    return (features[u8idx] & (1 << bit)) != 0
×
1805
end
1806

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

1830
# should sync with the types of arguments of `stale_cachefile`
1831
const StaleCacheKey = Tuple{PkgId, UInt128, String, String}
1832

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

1878
"""
1879
    Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
1880

1881
Returns whether a given PkgId within the active project is precompiled.
1882

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

1888
!!! compat "Julia 1.10"
1889
    This function requires at least Julia 1.10.
1890
"""
1891
function isprecompiled(pkg::PkgId;
110✔
1892
        ignore_loaded::Bool=false,
1893
        stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
1894
        cachepath_cache::Dict{PkgId, Vector{String}}=Dict{PkgId, Vector{String}}(),
1895
        cachepaths::Vector{String}=get!(() -> find_all_in_cache_path(pkg), cachepath_cache, pkg),
10✔
1896
        sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
1897
        flags::CacheFlags=CacheFlags())
1898
    path = compilecache_path(pkg; ignore_loaded, stale_cache, cachepath_cache, cachepaths, sourcepath, flags)
95✔
1899
    return !isnothing(path)
118✔
1900
end
1901

1902
"""
1903
    Base.isrelocatable(pkg::PkgId)
1904

1905
Returns whether a given PkgId within the active project is precompiled and the
1906
associated cache is relocatable.
1907

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

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

1957
# loads a precompile cache file, ignoring stale_cachefile tests
1958
# load all dependent modules first
1959
function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String})
12✔
1960
    assert_havelock(require_lock)
24✔
1961
    local depmodnames
1962
    io = open(path, "r")
12✔
1963
    try
12✔
1964
        iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.")
12✔
1965
        _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path)
12✔
1966

1967

1968
        pkgimage = !isempty(clone_targets)
12✔
1969
        if pkgimage
12✔
1970
            ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided")
9✔
1971
            isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.")
9✔
1972
            ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile")
9✔
1973
            # TODO: Check for valid clone_targets?
1974
            isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.")
9✔
1975
        else
1976
            @assert ocachepath === nothing
3✔
1977
        end
1978
        isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.")
12✔
1979
    finally
1980
        close(io)
12✔
1981
    end
1982
    ndeps = length(depmodnames)
12✔
1983
    depmods = Vector{Any}(undef, ndeps)
12✔
1984
    for i in 1:ndeps
24✔
1985
        modkey, build_id = depmodnames[i]
187✔
1986
        dep = _tryrequire_from_serialized(modkey, build_id)
187✔
1987
        if !isa(dep, Module)
187✔
1988
            return dep
×
1989
        end
1990
        depmods[i] = dep
187✔
1991
    end
362✔
1992
    # then load the file
1993
    loaded = _include_from_serialized(pkg, path, ocachepath, depmods; register = true)
12✔
1994
    return loaded
12✔
1995
end
1996

1997
# returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it or it was stale
1998
# returns the set of modules restored if the cache load succeeded
1999
@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)
734✔
2000
    assert_havelock(require_lock)
734✔
2001
    paths = find_all_in_cache_path(pkg, DEPOT_PATH)
367✔
2002
    newdeps = PkgId[]
367✔
2003
    try_build_ids = UInt128[build_id]
367✔
2004
    if build_id == UInt128(0)
367✔
2005
        let loaded = get(loaded_precompiles, pkg, nothing)
357✔
2006
            if loaded !== nothing
356✔
2007
                for mod in loaded # try these in reverse original load order to see if one is already valid
1✔
2008
                    pushfirst!(try_build_ids, module_build_id(mod))
2✔
2009
                end
1✔
2010
            end
2011
        end
2012
    end
2013
    for build_id in try_build_ids
367✔
2014
        for path_to_try in paths::Vector{String}
367✔
2015
            staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck)
632✔
2016
            if staledeps === true
316✔
2017
                continue
23✔
2018
            end
2019
            staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
293✔
2020
            startedloading = length(staledeps) + 1
293✔
2021
            try # any exit from here (goto, break, continue, return) will end_loading
293✔
2022
                # finish checking staledeps module graph, while acquiring all start_loading locks
2023
                # so that concurrent require calls won't make any different decisions that might conflict with the decisions here
2024
                # note that start_loading will drop the loading lock if necessary
2025
                let i = 0
293✔
2026
                    # start_loading here has a deadlock problem if we try to load `A,B,C` and `B,A,D` at the same time:
2027
                    # 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
2028
                    # solve that by making sure we can start_loading everything before allocating each of those and doing all the stale checks
2029
                    while i < length(staledeps)
5,140✔
2030
                        i += 1
4,847✔
2031
                        dep = staledeps[i]
4,847✔
2032
                        dep isa Module && continue
4,847✔
2033
                        _, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
893✔
2034
                        dep = canstart_loading(modkey, modbuild_id, stalecheck)
893✔
2035
                        if dep isa Module
893✔
2036
                            if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
×
2037
                                staledeps[i] = dep
×
2038
                                continue
×
2039
                            else
2040
                                @debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
×
2041
                                @goto check_next_path
×
2042
                            end
2043
                            continue
×
2044
                        elseif dep === nothing
893✔
2045
                            continue
893✔
2046
                        end
2047
                        wait(dep) # releases require_lock, so requires restarting this loop
×
2048
                        i = 0
×
2049
                    end
4,847✔
2050
                end
2051
                for i in reverse(eachindex(staledeps))
586✔
2052
                    dep = staledeps[i]
4,821✔
2053
                    dep isa Module && continue
4,821✔
2054
                    modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
893✔
2055
                    # inline a call to start_loading here
2056
                    @assert canstart_loading(modkey, modbuild_id, stalecheck) === nothing
893✔
2057
                    package_locks[modkey] = (current_task(), Threads.Condition(require_lock), modbuild_id)
893✔
2058
                    startedloading = i
893✔
2059
                    modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
893✔
2060
                    for modpath_to_try in modpaths
893✔
2061
                        modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
3,240✔
2062
                        if modstaledeps === true
1,620✔
2063
                            continue
729✔
2064
                        end
2065
                        modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
891✔
2066
                        staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
891✔
2067
                        @goto check_next_dep
891✔
2068
                    end
729✔
2069
                    @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
2✔
2070
                    @goto check_next_path
2✔
2071
                    @label check_next_dep
2072
                end
9,347✔
2073
                M = maybe_loaded_precompile(pkg, newbuild_id)
291✔
2074
                if isa(M, Module)
291✔
2075
                    stalecheck && register_root_module(M)
1✔
2076
                    return M
1✔
2077
                end
2078
                if stalecheck
290✔
2079
                    try
289✔
2080
                        touch(path_to_try) # update timestamp of precompilation file
289✔
2081
                    catch ex # file might be read-only and then we fail to update timestamp, which is fine
2082
                        ex isa IOError || rethrow()
×
2083
                    end
2084
                end
2085
                # finish loading module graph into staledeps
2086
                # 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
2087
                for i in eachindex(staledeps)
580✔
2088
                    dep = staledeps[i]
4,806✔
2089
                    dep isa Module && continue
4,806✔
2090
                    modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
891✔
2091
                    set_pkgorigin_version_path(modkey, modpath)
891✔
2092
                    dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
1,782✔
2093
                    if !isa(dep, Module)
891✔
2094
                        @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
×
2095
                        @goto check_next_path
×
2096
                    else
2097
                        startedloading = i + 1
891✔
2098
                        end_loading(modkey, dep)
891✔
2099
                        staledeps[i] = dep
891✔
2100
                        push!(newdeps, modkey)
891✔
2101
                    end
2102
                end
9,322✔
2103
                restored = maybe_loaded_precompile(pkg, newbuild_id)
290✔
2104
                if !isa(restored, Module)
290✔
2105
                    restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
577✔
2106
                end
2107
                isa(restored, Module) && return restored
290✔
2108
                @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
×
2109
                @label check_next_path
2✔
2110
            finally
2111
                # cancel all start_loading locks that were taken but not fulfilled before failing
2112
                for i in startedloading:length(staledeps)
301✔
2113
                    dep = staledeps[i]
24✔
2114
                    dep isa Module && continue
24✔
2115
                    if dep isa Tuple{String, PkgId, UInt128}
2✔
2116
                        _, modkey, _ = dep
2✔
2117
                    else
2118
                        _, modkey, _ = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
×
2119
                    end
2120
                    end_loading(modkey, nothing)
2✔
2121
                end
41✔
2122
                for modkey in newdeps
293✔
2123
                    insert_extension_triggers(modkey)
1,770✔
2124
                    stalecheck && run_package_callbacks(modkey)
891✔
2125
                end
891✔
2126
            end
2127
        end
25✔
2128
    end
76✔
2129
    return nothing
76✔
2130
end
2131

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

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

2139
function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
2,538✔
2140
    assert_havelock(require_lock)
5,076✔
2141
    require_lock.reentrancy_cnt == 1 || throw(ConcurrencyViolationError("recursive call to start_loading"))
2,538✔
2142
    loading = get(package_locks, modkey, nothing)
2,554✔
2143
    if loading === nothing
2,538✔
2144
        loaded = stalecheck ? maybe_root_module(modkey) : nothing
2,522✔
2145
        loaded isa Module && return loaded
2,522✔
2146
        if build_id != UInt128(0)
2,335✔
2147
            loaded = maybe_loaded_precompile(modkey, build_id)
2,014✔
2148
            loaded isa Module && return loaded
2,014✔
2149
        end
2150
        return nothing
2,146✔
2151
    end
2152
    if !stalecheck && build_id != UInt128(0) && loading[3] != build_id
16✔
2153
        # don't block using an existing specific loaded module on needing a different concurrently loaded one
2154
        loaded = maybe_loaded_precompile(modkey, build_id)
×
2155
        loaded isa Module && return loaded
×
2156
    end
2157
    # load already in progress for this module on the task
2158
    task, cond = loading
16✔
2159
    deps = String[modkey.name]
16✔
2160
    assert_havelock(cond.lock)
32✔
2161
    if debug_loading_deadlocks && current_task() !== task
16✔
2162
        waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
15✔
2163
        for each in package_locks
30✔
2164
            cond2 = each[2][2]
15✔
2165
            assert_havelock(cond2.lock)
30✔
2166
            for waiting in cond2.waitq
15✔
2167
                push!(waiters, waiting => (each[2][1] => each[1]))
15✔
2168
            end
15✔
2169
        end
15✔
2170
        while true
15✔
2171
            running = get(waiters, task, nothing)
15✔
2172
            running === nothing && break
15✔
2173
            task, pkgid = running
×
2174
            push!(deps, pkgid.name)
×
2175
            task === current_task() && break
×
2176
        end
×
2177
    end
2178
    if current_task() === task
16✔
2179
        push!(deps, modkey.name) # repeat this to emphasize the cycle here
1✔
2180
        others = Set{String}()
1✔
2181
        for each in package_locks # list the rest of the packages being loaded too
2✔
2182
            if each[2][1] === task
1✔
2183
                other = each[1].name
1✔
2184
                other == modkey.name || push!(others, other)
1✔
2185
            end
2186
        end
1✔
2187
        # remove duplicates from others already in deps
2188
        for dep in deps
1✔
2189
            delete!(others, dep)
2✔
2190
        end
2✔
2191
        msg = sprint(deps, others) do io, deps, others
1✔
2192
            print(io, "deadlock detected in loading ")
1✔
2193
            join(io, deps, " using ")
1✔
2194
            if !isempty(others)
1✔
2195
                print(io, " (while loading ")
×
2196
                join(io, others, " and ")
×
2197
                print(io, ")")
×
2198
            end
2199
        end
2200
        throw(ConcurrencyViolationError(msg))
1✔
2201
    end
2202
    return cond
15✔
2203
end
2204

2205
function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
724✔
2206
    # handle recursive and concurrent calls to require
2207
    while true
724✔
2208
        loaded = canstart_loading(modkey, build_id, stalecheck)
724✔
2209
        if loaded === nothing
723✔
2210
            package_locks[modkey] = (current_task(), Threads.Condition(require_lock), build_id)
332✔
2211
            return nothing
332✔
2212
        elseif loaded isa Module
391✔
2213
            return loaded
376✔
2214
        end
2215
        loaded = wait(loaded)
15✔
2216
        loaded isa Module && return loaded
15✔
2217
    end
×
2218
end
2219

2220
function end_loading(modkey::PkgId, @nospecialize loaded)
1,241✔
2221
    assert_havelock(require_lock)
2,482✔
2222
    loading = pop!(package_locks, modkey)
1,241✔
2223
    notify(loading[2], loaded, all=true)
1,241✔
2224
    nothing
1,241✔
2225
end
2226

2227
# to notify downstream consumers that a module was successfully loaded
2228
# Callbacks take the form (mod::Base.PkgId) -> nothing.
2229
# WARNING: This is an experimental feature and might change later, without deprecation.
2230
const package_callbacks = Any[]
2231
# to notify downstream consumers that a file has been included into a particular module
2232
# Callbacks take the form (mod::Module, filename::String) -> nothing
2233
# WARNING: This is an experimental feature and might change later, without deprecation.
2234
const include_callbacks = Any[]
2235

2236
# used to optionally track dependencies when requiring a module:
2237
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
2238
const _require_dependencies = Any[] # a list of (mod::Module, abspath::String, fsize::UInt64, hash::UInt32, mtime::Float64) tuples that are the file dependencies of the module currently being precompiled
2239
const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies
2240

2241
function _include_dependency(mod::Module, _path::AbstractString; track_content::Bool=true,
86✔
2242
                             path_may_be_dir::Bool=false)
2243
    _include_dependency!(_require_dependencies, _track_dependencies[], mod, _path, track_content, path_may_be_dir)
86✔
2244
end
2245

2246
function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool,
83✔
2247
                              mod::Module, _path::AbstractString,
2248
                              track_content::Bool, path_may_be_dir::Bool)
2249
    prev = source_path(nothing)
105✔
2250
    if prev === nothing
83✔
2251
        path = abspath(_path)
22✔
2252
    else
2253
        path = normpath(joinpath(dirname(prev), _path))
61✔
2254
    end
2255
    if !track_dependencies[]
83✔
2256
        if !path_may_be_dir && !isfile(path)
83✔
2257
            throw(SystemError("opening file $(repr(path))", Libc.ENOENT))
3✔
2258
        elseif path_may_be_dir && !Filesystem.isreadable(path)
80✔
2259
            throw(SystemError("opening file or folder $(repr(path))", Libc.ENOENT))
4✔
2260
        end
2261
    else
2262
        @lock require_lock begin
×
2263
            if track_content
×
2264
                hash = (isdir(path) ? _crc32c(join(readdir(path))) : open(_crc32c, path, "r"))::UInt32
×
2265
                # use mtime=-1.0 here so that fsize==0 && mtime==0.0 corresponds to a missing include_dependency
2266
                push!(dep_list, (mod, path, UInt64(filesize(path)), hash, -1.0))
×
2267
            else
2268
                push!(dep_list, (mod, path, UInt64(0), UInt32(0), mtime(path)))
×
2269
            end
2270
        end
2271
    end
2272
    return path, prev
76✔
2273
end
2274

2275
"""
2276
    include_dependency(path::AbstractString; track_content::Bool=true)
2277

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

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

2287
!!! compat "Julia 1.11"
2288
    Keyword argument `track_content` requires at least Julia 1.11.
2289
    An error is now thrown if `path` is not readable.
2290
"""
2291
function include_dependency(path::AbstractString; track_content::Bool=true)
10✔
2292
    _include_dependency(Main, path, track_content=track_content, path_may_be_dir=true)
10✔
2293
    return nothing
5✔
2294
end
2295

2296
# we throw PrecompilableError when a module doesn't want to be precompiled
2297
import Core: PrecompilableError
2298
function show(io::IO, ex::PrecompilableError)
×
2299
    print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.")
×
2300
end
2301
precompilableerror(ex::PrecompilableError) = true
×
2302
precompilableerror(ex::WrappedException) = precompilableerror(ex.error)
×
2303
precompilableerror(@nospecialize ex) = false
×
2304

2305
# Call __precompile__(false) at the top of a tile prevent it from being precompiled (false)
2306
"""
2307
    __precompile__(isprecompilable::Bool)
2308

2309
Specify whether the file calling this function is precompilable, defaulting to `true`.
2310
If a module or file is *not* safely precompilable, it should call `__precompile__(false)` in
2311
order to throw an error if Julia attempts to precompile it.
2312
"""
2313
@noinline function __precompile__(isprecompilable::Bool=true)
2✔
2314
    if !isprecompilable && generating_output()
2✔
2315
        throw(PrecompilableError())
×
2316
    end
2317
    nothing
2✔
2318
end
2319

2320
# require always works in Main scope and loads files from node 1
2321
# XXX: (this is deprecated, but still used by Distributed)
2322
const toplevel_load = Ref(true)
2323

2324
const _require_world_age = Ref{UInt}(typemax(UInt))
2325

2326
"""
2327
    require(into::Module, module::Symbol)
2328

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

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

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

2344
For more details regarding code loading, see the manual sections on [modules](@ref modules) and
2345
[parallel computing](@ref code-availability).
2346
"""
2347
function require(into::Module, mod::Symbol)
2348
    world = _require_world_age[]
481✔
2349
    if world == typemax(UInt)
479✔
2350
        world = get_world_counter()
481✔
2351
    end
2352
    return Compiler.@zone "LOAD_Require" invoke_in_world(world, __require, into, mod)
481✔
2353
end
2354

2355
function check_for_hint(into, mod)
6✔
2356
    return begin
6✔
2357
        if isdefined(into, mod) && getfield(into, mod) isa Module
6✔
2358
            true, "."
2✔
2359
        elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
4✔
2360
            true, ".."
1✔
2361
        else
2362
            false, ""
3✔
2363
        end
2364
    end
2365
end
2366

2367
function __require(into::Module, mod::Symbol)
484✔
2368
    if into === __toplevel__ && generating_output(#=incremental=#true)
483✔
2369
        error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
×
2370
         is not allowed during package precompilation.")
2371
    end
2372
    topmod = moduleroot(into)
605✔
2373
    if nameof(topmod) === mod
484✔
2374
        return topmod
3✔
2375
    end
2376
    @lock require_lock begin
480✔
2377
    LOADING_CACHE[] = LoadingCache()
481✔
2378
    try
481✔
2379
        uuidkey_env = identify_package_env(into, String(mod))
481✔
2380
        # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env")
2381
        if uuidkey_env === nothing
481✔
2382
            where = PkgId(into)
×
2383
            if where.uuid === nothing
×
2384
                hint, dots = invokelatest(check_for_hint, into, mod)
×
2385
                hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
×
2386
                install_message = if mod != :Pkg
×
2387
                    start_sentence = hint ? "Otherwise, run" : "Run"
×
2388
                    "\n- $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package."
×
2389
                else  # for some reason Pkg itself isn't availability so do not tell them to use Pkg to install it.
2390
                    ""
×
2391
                end
2392

2393
                throw(ArgumentError("Package $mod not found in current path$hint_message.$install_message"))
×
2394
            else
2395
                manifest_warnings = collect_manifest_warnings()
×
2396
                throw(ArgumentError("""
×
2397
                Cannot load (`using/import`) module $mod into module $into in package $(where.name)
2398
                because package $(where.name) does not have $mod in its dependencies:
2399
                $manifest_warnings- You may have a partially installed environment. Try `Pkg.instantiate()`
2400
                  to ensure all packages in the environment are installed.
2401
                - Or, if you have $(where.name) checked out for development and have
2402
                  added $mod as a dependency but haven't updated your primary
2403
                  environment's manifest file, try `Pkg.resolve()`.
2404
                - Otherwise you may need to report an issue with $(where.name)"""))
2405
            end
2406
        end
2407
        uuidkey, env = uuidkey_env
962✔
2408
        if _track_dependencies[]
481✔
2409
            path = binpack(uuidkey)
×
2410
            push!(_require_dependencies, (into, path, UInt64(0), UInt32(0), 0.0))
×
2411
        end
2412
        return _require_prelocked(uuidkey, env)
962✔
2413
    finally
2414
        LOADING_CACHE[] = nothing
481✔
2415
    end
2416
    end
2417
end
2418

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

2447
function collect_manifest_warnings()
×
2448
    unsuitable_manifests, dev_manifests = find_unsuitable_manifests_versions()
×
2449
    msg = ""
×
2450
    if !isempty(unsuitable_manifests)
×
2451
        msg *= """
×
2452
        - Note that the following manifests in the load path were resolved with a different
2453
          julia version, which may be the cause of the error. Try to re-resolve them in the
2454
          current version, or consider deleting them if that fails:
2455
            $(join(unsuitable_manifests, "\n    "))
2456
        """
2457
    end
2458
    if !isempty(dev_manifests)
×
2459
        msg *= """
×
2460
        - Note that the following manifests in the load path were resolved with a potentially
2461
          different DEV version of the current version, which may be the cause of the error.
2462
          Try to re-resolve them in the current version, or consider deleting them if that fails:
2463
            $(join(dev_manifests, "\n    "))
2464
        """
2465
    end
2466
    return msg
×
2467
end
2468

2469
function require(uuidkey::PkgId)
2470
    world = _require_world_age[]
27✔
2471
    if world == typemax(UInt)
27✔
2472
        world = get_world_counter()
4✔
2473
    end
2474
    return invoke_in_world(world, __require, uuidkey)
27✔
2475
end
2476
__require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
4✔
2477
function _require_prelocked(uuidkey::PkgId, env=nothing)
553✔
2478
    assert_havelock(require_lock)
1,076✔
2479
    m = start_loading(uuidkey, UInt128(0), true)
519✔
2480
    if m === nothing
518✔
2481
        last = toplevel_load[]
320✔
2482
        try
320✔
2483
            toplevel_load[] = false
320✔
2484
            m = __require_prelocked(uuidkey, env)
320✔
2485
            m isa Module || check_package_module_loaded_error(uuidkey)
317✔
2486
        finally
2487
            toplevel_load[] = last
320✔
2488
            end_loading(uuidkey, m)
320✔
2489
        end
2490
        insert_extension_triggers(uuidkey)
559✔
2491
        # After successfully loading, notify downstream consumers
2492
        run_package_callbacks(uuidkey)
317✔
2493
    end
2494
    return m
515✔
2495
end
2496

2497
mutable struct PkgOrigin
2498
    path::Union{String,Nothing}
1,238✔
2499
    cachepath::Union{String,Nothing}
2500
    version::Union{VersionNumber,Nothing}
2501
end
2502
PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
1,238✔
2503
const pkgorigins = Dict{PkgId,PkgOrigin}()
2504

2505
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
2506
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
2507
const loaded_modules_order = Vector{Module}()
2508

2509
root_module_key(m::Module) = PkgId(m)
67,666✔
2510

2511
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
4,933✔
2512
    @lock require_lock begin
4,933✔
2513
    mods = get(loaded_precompiles, key, nothing)
7,422✔
2514
    mods === nothing && return
4,933✔
2515
    for mod in mods
2,489✔
2516
        module_build_id(mod) == buildid && return mod
2,489✔
2517
    end
×
2518
    end
2519
end
2520

2521
function module_build_id(m::Module)
17✔
2522
    hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
26,983✔
2523
    return (UInt128(hi) << 64) | lo
26,983✔
2524
end
2525

2526
@constprop :none function register_root_module(m::Module)
1,231✔
2527
    # n.b. This is called from C after creating a new module in `Base.__toplevel__`,
2528
    # instead of adding them to the binding table there.
2529
    @lock require_lock begin
1,231✔
2530
    key = PkgId(m, String(nameof(m)))
2,372✔
2531
    if haskey(loaded_modules, key)
1,231✔
2532
        oldm = loaded_modules[key]
×
2533
        if oldm !== m
×
2534
            if generating_output(#=incremental=#true)
×
2535
                error("Replacing module `$(key.name)`")
×
2536
            else
2537
                @warn "Replacing module `$(key.name)`"
×
2538
            end
2539
        end
2540
    end
2541
    maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
1,231✔
2542
    loaded_modules[key] = m
1,231✔
2543
    end
2544
    nothing
1,231✔
2545
end
2546

2547
register_root_module(Core)
2548
register_root_module(Base)
2549
register_root_module(Main)
2550

2551
# This is used as the current module when loading top-level modules.
2552
# It has the special behavior that modules evaluated in it get added
2553
# to the loaded_modules table instead of getting bindings.
2554
baremodule __toplevel__
2555
using Base
2556
end
2557

2558
# get a top-level Module from the given key
2559
# this is similar to `require`, but worse in almost every possible way
2560
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
8,162✔
2561
function root_module(where::Module, name::Symbol)
38✔
2562
    key = identify_package(where, String(name))
76✔
2563
    key isa PkgId || throw(KeyError(name))
38✔
2564
    return root_module(key)
38✔
2565
end
2566
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
×
2567
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
20,492✔
2568

2569
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
×
2570

2571
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2572
# reload(m) = (unreference_module(m); require(m))
2573
function unreference_module(key::PkgId)
1✔
2574
    @lock require_lock begin
1✔
2575
    if haskey(loaded_modules, key)
1✔
2576
        m = pop!(loaded_modules, key)
×
2577
        # need to ensure all modules are GC rooted; will still be referenced
2578
        # in loaded_modules_order
2579
    end
2580
    end
2581
end
2582

2583
# whoever takes the package_locks[pkg] must call this function immediately
2584
function set_pkgorigin_version_path(pkg::PkgId, path::String)
1,239✔
2585
    assert_havelock(require_lock)
2,478✔
2586
    pkgorigin = get!(PkgOrigin, pkgorigins, pkg)
1,239✔
2587
    # Pkg needs access to the version of packages in the sysimage.
2588
    if generating_output(#=incremental=#false)
1,239✔
2589
        pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), ".."))
×
2590
    end
2591
    pkgorigin.path = path
1,239✔
2592
    nothing
1,239✔
2593
end
2594

2595
# Unused
2596
const PKG_PRECOMPILE_HOOK = Ref{Function}()
2597
disable_parallel_precompile::Bool = false
2598

2599
# Returns `nothing` or the new(ish) module
2600
function __require_prelocked(pkg::PkgId, env)
322✔
2601
    assert_havelock(require_lock)
644✔
2602

2603
    # perform the search operation to select the module file require intends to load
2604
    path = locate_package(pkg, env)
644✔
2605
    if path === nothing
322✔
2606
        throw(ArgumentError("""
×
2607
            Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2608
             - Run `Pkg.instantiate()` to install all recorded dependencies.
2609
            """))
2610
    end
2611
    set_pkgorigin_version_path(pkg, path)
322✔
2612

2613
    parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop
322✔
2614
    reasons = Dict{String,Int}()
322✔
2615
    # attempt to load the module file via the precompile cache locations
2616
    if JLOptions().use_compiled_modules != 0
322✔
2617
        @label load_from_cache
2618
        loaded = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons)
341✔
2619
        if loaded isa Module
341✔
2620
            return loaded
280✔
2621
        end
2622
    end
2623

2624
    if JLOptions().use_compiled_modules == 3
84✔
2625
        error("Precompiled image $pkg not available with flags $(CacheFlags())")
×
2626
    end
2627

2628
    # if the module being required was supposed to have a particular version
2629
    # but it was not handled by the precompile loader, complain
2630
    for (concrete_pkg, concrete_build_id) in _concrete_dependencies
84✔
2631
        if pkg == concrete_pkg
×
2632
            @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
×
2633
                 This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2634
            if JLOptions().incremental != 0
×
2635
                # during incremental precompilation, this should be fail-fast
2636
                throw(PrecompilableError())
×
2637
            end
2638
        end
2639
    end
×
2640

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

2698
    # just load the file normally via include
2699
    # for unknown dependencies
2700
    uuid = pkg.uuid
27✔
2701
    uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
46✔
2702
    old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
27✔
2703
    if uuid !== old_uuid
27✔
2704
        ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
19✔
2705
    end
2706
    unlock(require_lock)
27✔
2707
    try
27✔
2708
        include(__toplevel__, path)
27✔
2709
        loaded = maybe_root_module(pkg)
27✔
2710
    finally
2711
        lock(require_lock)
27✔
2712
        if uuid !== old_uuid
27✔
2713
            ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
19✔
2714
        end
2715
    end
2716
    return loaded
27✔
2717
end
2718

2719
# load a serialized file directly, including dependencies (without checking staleness except for immediate conflicts)
2720
# this does not call start_loading / end_loading, so can lead to some odd behaviors
2721
function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}, sourcepath::String)
×
2722
    @lock require_lock begin
×
2723
    set_pkgorigin_version_path(uuidkey, sourcepath)
×
2724
    newm = _tryrequire_from_serialized(uuidkey, path, ocachepath)
×
2725
    newm isa Module || throw(newm)
×
2726
    insert_extension_triggers(uuidkey)
×
2727
    # After successfully loading, notify downstream consumers
2728
    run_package_callbacks(uuidkey)
×
2729
    return newm
×
2730
    end
2731
end
2732

2733
# load a serialized file directly from append_bundled_depot_path for uuidkey without stalechecks
2734
"""
2735
    require_stdlib(package_uuidkey::PkgId, [ext::String, from::Module])
2736

2737
!!! warning "May load duplicate copies of stdlib packages."
2738

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

2744
    The specific requirements are:
2745

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

2752
      The imported code (loaded stdlibs) must be very careful about type piracy:
2753
         - It must not access any global state that may differ between stdlib copies in
2754
           type-pirated methods.
2755
         - It must not return any stdlib types from any type-pirated public methods (since
2756
           a loaded duplicate would overwrite the Base method again, returning different
2757
           types that don't correspond to the user-accessible copy of the stdlib).
2758
         - It must not pass / discriminate stdlib types in type-pirated methods, except
2759
           indirectly via methods defined in Base and implemented (w/o type-piracy) in
2760
           all copies of the stdlib over their respective types.
2761

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

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

2770
    For examples of issues like the above, see:
2771
      [1] https://github.com/JuliaLang/Pkg.jl/issues/4017#issuecomment-2377589989
2772
      [2] https://github.com/JuliaLang/StyledStrings.jl/issues/91#issuecomment-2379602914
2773
"""
2774
require_stdlib(package_uuidkey::PkgId) = require_stdlib(package_uuidkey, nothing, Base)
3✔
2775
function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}, from::Module)
3✔
2776
    if generating_output(#=incremental=#true)
3✔
2777
        # Otherwise this would lead to awkward dependency issues by loading a package that isn't in the Project/Manifest
2778
        error("This interactive function requires a stdlib to be loaded, and package code should instead use it directly from that stdlib.")
×
2779
    end
2780
    @lock require_lock begin
3✔
2781
    # the PkgId of the ext, or package if not an ext
2782
    this_uuidkey = ext isa String ? PkgId(uuid5(package_uuidkey.uuid, ext), ext) : package_uuidkey
3✔
2783
    env = Sys.STDLIB
3✔
2784
    newm = start_loading(this_uuidkey, UInt128(0), true)
3✔
2785
    newm === nothing || return newm
4✔
2786
    try
2✔
2787
        depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
2✔
2788
        from_stdlib = true # set to false if `from` is a normal package so we do not want the internal loader for the extension either
2✔
2789
        if ext isa String
2✔
2790
            from_uuid = PkgId(from)
×
2791
            from_m = get(loaded_modules, from_uuid, nothing)
×
2792
            if from_m === from
×
2793
                # if from_uuid is either nothing or points to something else, assume we should use require_stdlib
2794
                # otherwise check cachepath for from to see if it looks like it is from depot_path, since try_build_ids
2795
                cachepath = get(PkgOrigin, pkgorigins, from_uuid).cachepath
×
2796
                entrypath, entryfile = cache_file_entry(from_uuid)
×
2797
                from_stdlib = any(x -> startswith(entrypath, x), depot_path)
×
2798
            end
2799
        end
2800
        if from_stdlib
2✔
2801
            # first since this is a stdlib, try to look there directly first
2802
            if ext === nothing
2✔
2803
                sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl")
2✔
2804
            else
2805
                sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext)
×
2806
            end
2807
            set_pkgorigin_version_path(this_uuidkey, sourcepath)
2✔
2808
            newm = _require_search_from_serialized(this_uuidkey, sourcepath, UInt128(0), false; DEPOT_PATH=depot_path)
2✔
2809
        end
2810
    finally
2811
        end_loading(this_uuidkey, newm)
4✔
2812
    end
2813
    if newm isa Module
2✔
2814
        # After successfully loading, notify downstream consumers
2815
        insert_extension_triggers(env, this_uuidkey)
2✔
2816
        run_package_callbacks(this_uuidkey)
2✔
2817
    else
2818
        # if the user deleted their bundled depot, next try to load it completely normally
2819
        # if it is an extension, we first need to indicate where to find its parant via EXT_PRIMED
2820
        ext isa String && (EXT_PRIMED[this_uuidkey] = PkgId[package_uuidkey])
×
2821
        newm = _require_prelocked(this_uuidkey)
×
2822
    end
2823
    return newm
2✔
2824
    end # release lock
2825
end
2826

2827
# relative-path load
2828

2829
"""
2830
    include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")
2831

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

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

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

2873
include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
43✔
2874
    include_string(identity, m, txt, fname)
2875

2876
function source_path(default::Union{AbstractString,Nothing}="")
1✔
2877
    s = current_task().storage
239✔
2878
    if s !== nothing
234✔
2879
        s = s::IdDict{Any,Any}
158✔
2880
        if haskey(s, :SOURCE_PATH)
158✔
2881
            return s[:SOURCE_PATH]::Union{Nothing,String}
121✔
2882
        end
2883
    end
2884
    return default
113✔
2885
end
2886

2887
function source_dir()
×
2888
    p = source_path(nothing)
×
2889
    return p === nothing ? pwd() : dirname(p)
×
2890
end
2891

2892
"""
2893
    Base.include([mapexpr::Function,] m::Module, path::AbstractString)
2894

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

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

2907
!!! compat "Julia 1.5"
2908
    Julia 1.5 is required for passing the `mapexpr` argument.
2909
"""
2910
Base.include # defined in Base.jl
2911

2912
# Full include() implementation which is used after bootstrap
2913
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
76✔
2914
    @noinline # Workaround for module availability in _simplify_include_frames
76✔
2915
    path, prev = _include_dependency(mod, _path)
76✔
2916
    for callback in include_callbacks # to preserve order, must come before eval in include_string
73✔
2917
        invokelatest(callback, mod, path)
×
2918
    end
×
2919
    code = read(path, String)
73✔
2920
    tls = task_local_storage()
73✔
2921
    tls[:SOURCE_PATH] = path
73✔
2922
    try
73✔
2923
        return include_string(mapexpr, mod, code, path)
73✔
2924
    finally
2925
        if prev === nothing
73✔
2926
            delete!(tls, :SOURCE_PATH)
21✔
2927
        else
2928
            tls[:SOURCE_PATH] = prev
52✔
2929
        end
2930
    end
2931
end
2932

2933
"""
2934
    evalfile(path::AbstractString, args::Vector{String}=String[])
2935

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

2941
# Examples
2942

2943
```jldoctest
2944
julia> write("testfile.jl", \"\"\"
2945
           @show ARGS
2946
           1 + 1
2947
       \"\"\");
2948

2949
julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]);
2950
ARGS = ["ARG1", "ARG2"]
2951

2952
julia> x
2953
2
2954

2955
julia> rm("testfile.jl")
2956
```
2957
"""
2958
function evalfile(path::AbstractString, args::Vector{String}=String[])
1✔
2959
    m = Module(:__anon__)
2✔
2960
    return Core.eval(m,
1✔
2961
        Expr(:toplevel,
2962
             :(const ARGS = $args),
2963
             :(const include = $(Base.IncludeInto(m))),
2964
             :(const eval = $(Core.EvalInto(m))),
2965
             :(include($path))))
2966
end
2967
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
×
2968

2969
function load_path_setup_code(load_path::Bool=true)
×
2970
    code = """
×
2971
    append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH))))
2972
    append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH))))
2973
    """
2974
    if load_path
×
2975
        load_path = map(abspath, Base.load_path())
×
2976
        path_sep = Sys.iswindows() ? ';' : ':'
×
2977
        any(path -> path_sep in path, load_path) &&
×
2978
            error("LOAD_PATH entries cannot contain $(repr(path_sep))")
2979
        code *= """
×
2980
        append!(empty!(Base.LOAD_PATH), $(repr(load_path)))
2981
        ENV["JULIA_LOAD_PATH"] = $(repr(join(load_path, Sys.iswindows() ? ';' : ':')))
2982
        Base.set_active_project(nothing)
2983
        """
2984
    end
2985
    return code
×
2986
end
2987

2988
# Const global for GC root
2989
const newly_inferred = CodeInstance[]
2990

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

2995
    @lock require_lock begin
×
2996
    m = start_loading(pkg, UInt128(0), false)
×
2997
    @assert m === nothing
×
2998
    append!(empty!(Base.DEPOT_PATH), depot_path)
×
2999
    append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
×
3000
    append!(empty!(Base.LOAD_PATH), load_path)
×
3001
    ENV["JULIA_LOAD_PATH"] = join(load_path, Sys.iswindows() ? ';' : ':')
×
3002
    set_active_project(nothing)
×
3003
    Base._track_dependencies[] = true
×
3004
    get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
×
3005
    append!(empty!(Base._concrete_dependencies), concrete_deps)
×
3006
    end
3007

3008
    uuid_tuple = pkg.uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, pkg.uuid)
×
3009

3010
    ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Base.__toplevel__, uuid_tuple)
×
3011
    if source !== nothing
×
3012
        task_local_storage()[:SOURCE_PATH] = source
×
3013
    end
3014

3015
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3016
    try
×
3017
        Base.include(Base.__toplevel__, input)
×
3018
    catch ex
3019
        precompilableerror(ex) || rethrow()
×
3020
        @debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
×
3021
        exit(125) # we define status = 125 means PrecompileableError
×
3022
    finally
3023
        ccall(:jl_set_newly_inferred, Cvoid, (Any,), nothing)
×
3024
    end
3025
    # check that the package defined the expected module so we can give a nice error message if not
3026
    m = maybe_root_module(pkg)
×
3027
    m isa Module || check_package_module_loaded_error(pkg)
×
3028

3029
    # Re-populate the runtime's newly-inferred array, which will be included
3030
    # in the output. We removed it above to avoid including any code we may
3031
    # have compiled for error handling and validation.
3032
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3033
    @lock require_lock end_loading(pkg, m)
×
3034
    # insert_extension_triggers(pkg)
3035
    # run_package_callbacks(pkg)
3036
end
3037

3038
function check_package_module_loaded_error(pkg)
×
3039
    # match compilecache error type for non-125 errors
3040
    error("package `$(pkg.name)` did not define the expected \
×
3041
          module `$(pkg.name)`, check for typos in package module name")
3042
end
3043

3044
# protects against PkgId and UUID being imported and losing Base prefix
3045
_pkg_str(_pkg::PkgId) = (_pkg.uuid === nothing) ? "Base.PkgId($(repr(_pkg.name)))" : "Base.PkgId(Base.UUID(\"$(_pkg.uuid)\"), $(repr(_pkg.name)))"
5,865✔
3046
_pkg_str(_pkg::Vector) = sprint(show, eltype(_pkg); context = :module=>nothing) * "[" * join(map(_pkg_str, _pkg), ",") * "]"
303✔
3047
_pkg_str(_pkg::Pair{PkgId}) = _pkg_str(_pkg.first) * " => " * repr(_pkg.second)
11,131✔
3048
_pkg_str(_pkg::Nothing) = "nothing"
1✔
3049

3050
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
3051
function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String},
148✔
3052
                           concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3053
                           internal_stderr::IO = stderr, internal_stdout::IO = stdout, loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3054
    @nospecialize internal_stderr internal_stdout
148✔
3055
    rm(output, force=true)   # Remove file if it exists
148✔
3056
    output_o === nothing || rm(output_o, force=true)
148✔
3057
    depot_path = String[abspath(x) for x in DEPOT_PATH]
148✔
3058
    dl_load_path = String[abspath(x) for x in DL_LOAD_PATH]
148✔
3059
    load_path = String[abspath(x) for x in Base.load_path()]
148✔
3060
    # if pkg is a stdlib, append its parent Project.toml to the load path
3061
    triggers = get(EXT_PRIMED, pkg, nothing)
158✔
3062
    if triggers !== nothing
148✔
3063
        parentid = triggers[1]
10✔
3064
        for env in load_path
10✔
3065
            project_file = env_project_file(env)
13✔
3066
            if project_file === true
13✔
3067
                _, parent_project_file = entry_point_and_project_file(env, parentid.name)
×
3068
                if parent_project_file !== nothing
×
3069
                    parentproj = project_file_name_uuid(parent_project_file, parentid.name)
×
3070
                    if parentproj == parentid
×
3071
                        push!(load_path, parent_project_file)
×
3072
                    end
3073
                end
3074
            end
3075
        end
13✔
3076
    end
3077
    path_sep = Sys.iswindows() ? ';' : ':'
148✔
3078
    any(path -> path_sep in path, load_path) &&
687✔
3079
        error("LOAD_PATH entries cannot contain $(repr(path_sep))")
3080

3081
    if output_o === nothing
148✔
3082
        # remove options that make no difference given the other cache options
3083
        cacheflags = CacheFlags(cacheflags, opt_level=0)
4✔
3084
    end
3085
    opts = translate_cache_flags(cacheflags, CacheFlags()) # julia_cmd is generated for the running system, and must be fixed if running for precompile instead
295✔
3086
    if output_o !== nothing
148✔
3087
        @debug "Generating object cache file for $(repr("text/plain", pkg))"
144✔
3088
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
144✔
3089
        push!(opts, "--output-o", output_o)
144✔
3090
    else
3091
        @debug "Generating cache file for $(repr("text/plain", pkg))"
4✔
3092
        cpu_target = nothing
4✔
3093
    end
3094
    push!(opts, "--output-ji", output)
148✔
3095
    if isassigned(PRECOMPILE_TRACE_COMPILE)
148✔
3096
        push!(opts, "--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])")
×
3097
        push!(opts, "--trace-compile-timing")
×
3098
    end
3099

3100
    io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd)
228✔
3101
                               $(flags)
3102
                               $(opts)
3103
                               --output-incremental=yes
3104
                               --startup-file=no --history-file=no --warn-overwrite=yes
3105
                               $(have_color === nothing ? "--color=auto" : have_color ? "--color=yes" : "--color=no")
3106
                               -`,
3107
                              "OPENBLAS_NUM_THREADS" => 1,
3108
                              "JULIA_NUM_THREADS" => 1),
3109
                       stderr = internal_stderr, stdout = internal_stdout),
3110
              "w", stdout)
3111
    # write data over stdin to avoid the (unlikely) case of exceeding max command line size
3112
    write(io.in, """
208✔
3113
        empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
3114
        Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg))))
3115
        Base.loadable_extensions = $(_pkg_str(loadable_exts))
3116
        Base.precompiling_extension = $(loading_extension)
3117
        Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
3118
            $(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing))))
3119
        """)
3120
    close(io.in)
148✔
3121
    return io
148✔
3122
end
3123

3124
const precompilation_stack = Vector{PkgId}()
3125
# Helpful for debugging when precompilation is unexpectedly nested.
3126
# Enable with `JULIA_DEBUG=nested_precomp`. Note that it expected to be nested in classical code-load precompilation
3127
# TODO: Add detection if extension precompilation is nested and error / return early?
3128
function track_nested_precomp(pkgs::Vector{PkgId})
×
3129
    append!(precompilation_stack, pkgs)
×
3130
    if length(precompilation_stack) > 1
×
3131
        list() = join(map(p->p.name, precompilation_stack), " > ")
×
3132
        @debug "Nested precompilation: $(list())" _group=:nested_precomp
×
3133
    end
3134
end
3135

3136
function compilecache_dir(pkg::PkgId)
1✔
3137
    entrypath, entryfile = cache_file_entry(pkg)
203✔
3138
    return joinpath(DEPOT_PATH[1], entrypath)
148✔
3139
end
3140

3141
function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String
485✔
3142
    entrypath, entryfile = cache_file_entry(pkg)
320✔
3143
    cachepath = joinpath(DEPOT_PATH[1], entrypath)
213✔
3144
    isdir(cachepath) || mkpath(cachepath)
278✔
3145
    if pkg.uuid === nothing
213✔
3146
        abspath(cachepath, entryfile) * ".ji"
105✔
3147
    else
3148
        crc = _crc32c(project)
108✔
3149
        crc = _crc32c(unsafe_string(JLOptions().image_file), crc)
108✔
3150
        crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc)
108✔
3151
        crc = _crc32c(_cacheflag_to_uint8(flags), crc)
108✔
3152

3153
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
108✔
3154
        if cpu_target === nothing
108✔
3155
            cpu_target = unsafe_string(JLOptions().cpu_target)
108✔
3156
        end
3157
        crc = _crc32c(cpu_target, crc)
108✔
3158

3159
        crc = _crc32c(prefs_hash, crc)
108✔
3160
        project_precompile_slug = slug(crc, 5)
108✔
3161
        abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji"))
108✔
3162
    end
3163
end
3164

3165
"""
3166
    Base.compilecache(module::PkgId)
3167

3168
Creates a precompiled cache file for a module and all of its dependencies.
3169
This can be used to reduce package load times. Cache files are stored in
3170
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
3171
for important notes.
3172
"""
3173
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(), reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
179✔
3174
    @nospecialize internal_stderr internal_stdout
60✔
3175
    path = locate_package(pkg)
60✔
3176
    path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation"))
60✔
3177
    return compilecache(pkg, path, internal_stderr, internal_stdout; flags, cacheflags, reasons, loadable_exts)
60✔
3178
end
3179

3180
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
3181

3182
function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout,
371✔
3183
                      keep_loaded_modules::Bool = true; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3184
                      reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3185

3186
    @nospecialize internal_stderr internal_stdout
148✔
3187
    # decide where to put the resulting cache file
3188
    cachepath = compilecache_dir(pkg)
148✔
3189

3190
    # build up the list of modules that we want the precompile process to preserve
3191
    if keep_loaded_modules
148✔
3192
        concrete_deps = copy(_concrete_dependencies)
147✔
3193
        for (pkgreq, modreq) in loaded_modules
293✔
3194
            if !(pkgreq === Main || pkgreq === Core || pkgreq === Base)
5,749✔
3195
                push!(concrete_deps, pkgreq => module_build_id(modreq))
5,749✔
3196
            end
3197
        end
11,339✔
3198
    else
3199
        concrete_deps = empty(_concrete_dependencies)
1✔
3200
    end
3201
    # run the expression and cache the result
3202
    verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
296✔
3203
    @logmsg verbosity "Precompiling $(repr("text/plain", pkg)) $(list_reasons(reasons))"
148✔
3204

3205
    # create a temporary file in `cachepath` directory, write the cache in it,
3206
    # write the checksum, _and then_ atomically move the file to `cachefile`.
3207
    mkpath(cachepath)
148✔
3208
    cache_objects = JLOptions().use_pkgimages == 1
148✔
3209
    tmppath, tmpio = mktemp(cachepath)
148✔
3210

3211
    if cache_objects
148✔
3212
        tmppath_o, tmpio_o = mktemp(cachepath)
144✔
3213
        tmppath_so, tmpio_so = mktemp(cachepath)
144✔
3214
    else
3215
        tmppath_o = nothing
4✔
3216
    end
3217
    local p
3218
    try
148✔
3219
        close(tmpio)
148✔
3220
        if cache_objects
148✔
3221
            close(tmpio_o)
144✔
3222
            close(tmpio_so)
144✔
3223
        end
3224
        p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, loadable_exts)
292✔
3225

3226
        if success(p)
148✔
3227
            if cache_objects
133✔
3228
                # Run linker over tmppath_o
3229
                Linking.link_image(tmppath_o, tmppath_so)
258✔
3230
            end
3231

3232
            # Read preferences hash back from .ji file (we can't precompute because
3233
            # we don't actually know what the list of compile-time preferences are without compiling)
3234
            prefs_hash = preferences_hash(tmppath)
133✔
3235
            cachefile = compilecache_path(pkg, prefs_hash; flags=cacheflags)
187✔
3236
            ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing
133✔
3237

3238
            # append checksum for so to the end of the .ji file:
3239
            crc_so = UInt32(0)
133✔
3240
            if cache_objects
133✔
3241
                crc_so = open(_crc32c, tmppath_so, "r")
129✔
3242
            end
3243

3244
            # append extra crc to the end of the .ji file:
3245
            open(tmppath, "r+") do f
133✔
3246
                if iszero(isvalid_cache_header(f))
130✔
3247
                    error("Incompatible header for $(repr("text/plain", pkg)) in new cache file $(repr(tmppath)).")
×
3248
                end
3249
                seekend(f)
130✔
3250
                write(f, crc_so)
130✔
3251
                seekstart(f)
130✔
3252
                write(f, _crc32c(f))
130✔
3253
            end
3254

3255
            # inherit permission from the source file (and make them writable)
3256
            chmod(tmppath, filemode(path) & 0o777 | 0o200)
133✔
3257

3258
            # prune the directory with cache files
3259
            if pkg.uuid !== nothing
133✔
3260
                entrypath, entryfile = cache_file_entry(pkg)
111✔
3261
                cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath))
283✔
3262
                if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[]
56✔
3263
                    idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2]
×
3264
                    evicted_cachefile = joinpath(cachepath, cachefiles[idx])
×
3265
                    @debug "Evicting file from cache" evicted_cachefile
×
3266
                    rm(evicted_cachefile; force=true)
×
3267
                    try
×
3268
                        rm(ocachefile_from_cachefile(evicted_cachefile); force=true)
×
3269
                        @static if Sys.isapple()
3270
                            rm(ocachefile_from_cachefile(evicted_cachefile) * ".dSYM"; force=true, recursive=true)
×
3271
                        end
3272
                    catch e
3273
                        e isa IOError || rethrow()
×
3274
                    end
3275
                end
3276
            end
3277

3278
            if cache_objects
133✔
3279
                ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile)
129✔
3280
                if ocachefile_new != ocachefile
258✔
3281
                    cachefile = cachefile_from_ocachefile(ocachefile_new)
×
3282
                    ocachefile = ocachefile_new
×
3283
                end
3284
                @static if Sys.isapple()
3285
                    run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull())
129✔
3286
                end
3287
            end
3288
            # this is atomic according to POSIX (not Win32):
3289
            # but force=true means it will fall back to non atomic
3290
            # move if the initial rename fails.
3291
            mv(tmppath, cachefile; force=true)
133✔
3292
            return cachefile, ocachefile
148✔
3293
        end
3294
    finally
3295
        rm(tmppath, force=true)
148✔
3296
        if cache_objects
148✔
3297
            rm(tmppath_o::String, force=true)
144✔
3298
            rm(tmppath_so, force=true)
144✔
3299
        end
3300
    end
3301
    if p.exitcode == 125
15✔
3302
        return PrecompilableError()
7✔
3303
    else
3304
        error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)) ($(Base.process_status(p))).")
8✔
3305
    end
3306
end
3307

3308
function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0)
125✔
3309
    try
253✔
3310
        mv(tmppath_so, ocachefile; force=true)
124✔
3311
    catch e
3312
        e isa IOError || rethrow()
×
3313
        # If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup
3314
        # on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded
3315
        if !isfile(ocachefile) && e.code != Base.UV_EACCES
×
3316
            rethrow()
×
3317
        end
3318
        # Windows prevents renaming a file that is in use so if there is a Julia session started
3319
        # with a package image loaded, we cannot rename that file.
3320
        # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that
3321
        # that cache file does not exist.
3322
        ocachename, ocacheext = splitext(ocachefile_orig)
×
3323
        ocachefile_unique = ocachename * "_$num" * ocacheext
×
3324
        ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1)
×
3325
    end
3326
    return ocachefile
124✔
3327
end
3328

3329
function object_build_id(obj)
4✔
3330
    mod = ccall(:jl_object_top_module, Any, (Any,), obj)
8✔
3331
    if mod === nothing
8✔
3332
        return nothing
1✔
3333
    end
3334
    return module_build_id(mod::Module)
6✔
3335
end
3336

3337
function isvalid_cache_header(f::IOStream)
4,634✔
3338
    pkgimage = Ref{UInt8}()
4,634✔
3339
    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
4,634✔
3340

3341
    if !iszero(checksum) && pkgimage[] != 0
4,634✔
3342
        @debug "Cache header was for pkgimage"
×
3343
        return UInt64(0) # We somehow read the header for a pkgimage and not a ji
×
3344
    end
3345
    return checksum
4,634✔
3346
end
3347
isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32))
1,240✔
3348

3349
function isvalid_pkgimage_crc(f::IOStream, ocachefile::String)
1,230✔
3350
    seekstart(f) # TODO necessary
1,230✔
3351
    seek(f, filesize(f) - 8)
1,230✔
3352
    expected_crc_so = read(f, UInt32)
1,230✔
3353
    crc_so = open(_crc32c, ocachefile, "r")
1,230✔
3354
    expected_crc_so == crc_so
1,230✔
3355
end
3356

3357
mutable struct CacheHeaderIncludes
3358
    const id::PkgId
22,250✔
3359
    filename::String
3360
    const fsize::UInt64
3361
    const hash::UInt32
3362
    const mtime::Float64
3363
    const modpath::Vector{String}   # seemingly not needed in Base, but used by Revise
3364
end
3365

3366
function CacheHeaderIncludes(dep_tuple::Tuple{Module, String, UInt64, UInt32, Float64})
×
3367
    return CacheHeaderIncludes(PkgId(dep_tuple[1]), dep_tuple[2:end]..., String[])
×
3368
end
3369

3370
function replace_depot_path(path::AbstractString, depots::Vector{String}=normalize_depots_for_relocation())
3371
    for depot in depots
8✔
3372
        if startswith(path, string(depot, Filesystem.pathsep())) || path == depot
3373
            path = replace(path, depot => "@depot"; count=1)
3374
            break
3375
        end
3376
    end
3377
    return path
3378
end
3379

3380
function normalize_depots_for_relocation()
×
3381
    depots = String[]
×
3382
    sizehint!(depots, length(DEPOT_PATH))
×
3383
    for d in DEPOT_PATH
×
3384
        isdir(d) || continue
×
3385
        if isdirpath(d)
×
3386
            d = dirname(d)
×
3387
        end
3388
        push!(depots, abspath(d))
×
3389
    end
×
3390
    return depots
×
3391
end
3392

3393
function restore_depot_path(path::AbstractString, depot::AbstractString)
115✔
3394
    replace(path, r"^@depot" => depot; count=1)
87,395✔
3395
end
3396

3397
function resolve_depot(inc::AbstractString)
22,250✔
3398
    startswith(inc, string("@depot", Filesystem.pathsep())) || return :not_relocatable
22,764✔
3399
    for depot in DEPOT_PATH
21,736✔
3400
        ispath(restore_depot_path(inc, depot)) && return depot
65,788✔
3401
    end
44,189✔
3402
    return :no_depot_found
137✔
3403
end
3404

3405
function read_module_list(f::IO, has_buildid_hi::Bool)
9,012✔
3406
    modules = Vector{Pair{PkgId, UInt128}}()
9,012✔
3407
    while true
79,086✔
3408
        n = read(f, Int32)
79,086✔
3409
        n == 0 && break
79,086✔
3410
        sym = String(read(f, n)) # module name
140,109✔
3411
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
70,074✔
3412
        build_id_hi = UInt128(has_buildid_hi ? read(f, UInt64) : UInt64(0)) << 64
74,580✔
3413
        build_id = (build_id_hi | read(f, UInt64)) # build id (checksum + time - not a UUID)
70,074✔
3414
        push!(modules, PkgId(uuid, sym) => build_id)
70,074✔
3415
    end
70,074✔
3416
    return modules
9,012✔
3417
end
3418

3419
function _parse_cache_header(f::IO, cachefile::AbstractString)
4,506✔
3420
    flags = read(f, UInt8)
4,506✔
3421
    modules = read_module_list(f, false)
4,506✔
3422
    totbytes = Int64(read(f, UInt64)) # total bytes for file dependencies + preferences
4,506✔
3423
    # read the list of requirements
3424
    # and split the list into include and requires statements
3425
    includes = CacheHeaderIncludes[]
4,506✔
3426
    requires = Pair{PkgId, PkgId}[]
4,506✔
3427
    while true
31,943✔
3428
        n2 = read(f, Int32)
31,943✔
3429
        totbytes -= 4
31,943✔
3430
        if n2 == 0
31,943✔
3431
            break
4,506✔
3432
        end
3433
        depname = String(read(f, n2))
54,794✔
3434
        totbytes -= n2
27,437✔
3435
        fsize = read(f, UInt64)
27,437✔
3436
        totbytes -= 8
27,437✔
3437
        hash = read(f, UInt32)
27,437✔
3438
        totbytes -= 4
27,437✔
3439
        mtime = read(f, Float64)
27,437✔
3440
        totbytes -= 8
27,437✔
3441
        n1 = read(f, Int32)
27,437✔
3442
        totbytes -= 4
27,437✔
3443
        # map ids to keys
3444
        modkey = (n1 == 0) ? PkgId("") : modules[n1].first
50,352✔
3445
        modpath = String[]
27,437✔
3446
        if n1 != 0
27,437✔
3447
            # determine the complete module path
3448
            while true
23,605✔
3449
                n1 = read(f, Int32)
23,605✔
3450
                totbytes -= 4
23,605✔
3451
                if n1 == 0
23,605✔
3452
                    break
22,915✔
3453
                end
3454
                push!(modpath, String(read(f, n1)))
1,322✔
3455
                totbytes -= n1
690✔
3456
            end
690✔
3457
        end
3458
        if depname[1] == '\0'
54,794✔
3459
            push!(requires, modkey => binunpack(depname))
5,187✔
3460
        else
3461
            push!(includes, CacheHeaderIncludes(modkey, depname, fsize, hash, mtime, modpath))
22,250✔
3462
        end
3463
    end
27,437✔
3464
    prefs = String[]
4,506✔
3465
    while true
4,506✔
3466
        n2 = read(f, Int32)
4,506✔
3467
        totbytes -= 4
4,506✔
3468
        if n2 == 0
4,506✔
3469
            break
4,506✔
3470
        end
3471
        push!(prefs, String(read(f, n2)))
×
3472
        totbytes -= n2
×
3473
    end
×
3474
    prefs_hash = read(f, UInt64)
4,506✔
3475
    totbytes -= 8
4,506✔
3476
    srctextpos = read(f, Int64)
4,506✔
3477
    totbytes -= 8
4,506✔
3478
    @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))"
4,506✔
3479
    # read the list of modules that are required to be present during loading
3480
    required_modules = read_module_list(f, true)
4,506✔
3481
    l = read(f, Int32)
4,506✔
3482
    clone_targets = read(f, l)
4,506✔
3483

3484
    srcfiles = srctext_files(f, srctextpos, includes)
4,506✔
3485

3486
    return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
4,506✔
3487
end
3488

3489
function parse_cache_header(f::IO, cachefile::AbstractString)
4,506✔
3490
    modules, (includes, srcfiles, requires), required_modules,
4,506✔
3491
        srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile)
3492

3493
    includes_srcfiles = CacheHeaderIncludes[]
4,506✔
3494
    includes_depfiles = CacheHeaderIncludes[]
4,506✔
3495
    for inc in includes
4,506✔
3496
        if inc.filename ∈ srcfiles
44,472✔
3497
            push!(includes_srcfiles, inc)
22,234✔
3498
        else
3499
            push!(includes_depfiles, inc)
16✔
3500
        end
3501
    end
22,250✔
3502

3503

3504
    # The @depot resolution logic for include() files:
3505
    # 1. If the cache is not relocatable because of an absolute path,
3506
    #    we ignore that path for the depot search.
3507
    #    Recompilation will be triggered by stale_cachefile() if that absolute path does not exist.
3508
    # 2. If we can't find a depot for a relocatable path,
3509
    #    we still replace it with the depot we found from other files.
3510
    #    Recompilation will be triggered by stale_cachefile() because the resolved path does not exist.
3511
    # 3. We require that relocatable paths all resolve to the same depot.
3512
    # 4. We explicitly check that all relocatable paths resolve to the same depot. This has two reasons:
3513
    #    - We want to scan all source files in order to provide logs for 1. and 2. above.
3514
    #    - It is possible that a depot might be missing source files.
3515
    #      Assume that we have two depots on DEPOT_PATH, depot_complete and depot_incomplete.
3516
    #      If DEPOT_PATH=["depot_complete","depot_incomplete"] then no recompilation shall happen,
3517
    #      because depot_complete will be picked.
3518
    #      If DEPOT_PATH=["depot_incomplete","depot_complete"] we trigger recompilation and
3519
    #      hopefully a meaningful error about missing files is thrown.
3520
    #      If we were to just select the first depot we find, then whether recompilation happens would
3521
    #      depend on whether the first relocatable file resolves to depot_complete or depot_incomplete.
3522
    srcdepot = nothing
4,506✔
3523
    any_not_relocatable = false
4,506✔
3524
    any_no_depot_found = false
4,506✔
3525
    multiple_depots_found = false
4,506✔
3526
    for src in srcfiles
9,011✔
3527
        depot = resolve_depot(src)
22,234✔
3528
        if depot === :not_relocatable
22,234✔
3529
            any_not_relocatable = true
514✔
3530
        elseif depot === :no_depot_found
21,720✔
3531
            any_no_depot_found = true
137✔
3532
        elseif isnothing(srcdepot)
39,000✔
3533
            srcdepot = depot
4,166✔
3534
        elseif depot != srcdepot
17,417✔
3535
            multiple_depots_found = true
×
3536
        end
3537
    end
44,440✔
3538
    if any_no_depot_found
4,506✔
3539
        @debug("Unable to resolve @depot tag for at least one include() file from cache file $cachefile", srcfiles, _group=:relocatable)
21✔
3540
    end
3541
    if any_not_relocatable
4,506✔
3542
        @debug("At least one include() file from $cachefile is not relocatable", srcfiles, _group=:relocatable)
321✔
3543
    end
3544
    if multiple_depots_found
4,506✔
3545
        @debug("Some include() files from $cachefile are distributed over multiple depots", srcfiles, _group=:relocatable)
×
3546
    elseif !isnothing(srcdepot)
8,672✔
3547
        for inc in includes_srcfiles
4,166✔
3548
            inc.filename = restore_depot_path(inc.filename, srcdepot)
21,585✔
3549
        end
21,585✔
3550
    end
3551

3552
    # unlike include() files, we allow each relocatable include_dependency() file to resolve
3553
    # to a separate depot, #52161
3554
    for inc in includes_depfiles
4,506✔
3555
        depot = resolve_depot(inc.filename)
16✔
3556
        if depot === :no_depot_found
16✔
3557
            @debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", _group=:relocatable)
×
3558
        elseif depot === :not_relocatable
16✔
3559
            @debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", _group=:relocatable)
×
3560
        else
3561
            inc.filename = restore_depot_path(inc.filename, depot)
16✔
3562
        end
3563
    end
16✔
3564

3565
    return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
4,506✔
3566
end
3567

3568
function parse_cache_header(cachefile::String)
5✔
3569
    io = open(cachefile, "r")
5✔
3570
    try
5✔
3571
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
5✔
3572
        ret = parse_cache_header(io, cachefile)
5✔
3573
        return ret
5✔
3574
    finally
3575
        close(io)
5✔
3576
    end
3577
end
3578

3579
preferences_hash(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[6]
128✔
3580
function preferences_hash(cachefile::String)
128✔
3581
    io = open(cachefile, "r")
128✔
3582
    try
128✔
3583
        if iszero(isvalid_cache_header(io))
128✔
3584
            throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3585
        end
3586
        return preferences_hash(io, cachefile)
128✔
3587
    finally
3588
        close(io)
128✔
3589
    end
3590
end
3591

3592
function cache_dependencies(f::IO, cachefile::AbstractString)
1✔
3593
    _, (includes, _, _), modules, _... = parse_cache_header(f, cachefile)
1✔
3594
    return modules, map(chi -> chi.filename, includes)  # return just filename
4✔
3595
end
3596

3597
function cache_dependencies(cachefile::String)
1✔
3598
    io = open(cachefile, "r")
1✔
3599
    try
1✔
3600
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
1✔
3601
        return cache_dependencies(io, cachefile)
1✔
3602
    finally
3603
        close(io)
1✔
3604
    end
3605
end
3606

3607
function read_dependency_src(io::IO, cachefile::AbstractString, filename::AbstractString)
3608
    _, (includes, _, _), _, srctextpos, _, _, _, _ = parse_cache_header(io, cachefile)
3✔
3609
    srctextpos == 0 && error("no source-text stored in cache file")
3✔
3610
    seek(io, srctextpos)
3✔
3611
    return _read_dependency_src(io, filename, includes)
3✔
3612
end
3613

3614
function _read_dependency_src(io::IO, filename::AbstractString, includes::Vector{CacheHeaderIncludes}=CacheHeaderIncludes[])
3✔
3615
    while !eof(io)
5✔
3616
        filenamelen = read(io, Int32)
5✔
3617
        filenamelen == 0 && break
5✔
3618
        depotfn = String(read(io, filenamelen))
6✔
3619
        len = read(io, UInt64)
3✔
3620
        fn = if !startswith(depotfn, string("@depot", Filesystem.pathsep()))
3✔
3621
            depotfn
×
3622
        else
3623
            basefn = restore_depot_path(depotfn, "")
3✔
3624
            idx = findfirst(includes) do inc
6✔
3625
                endswith(inc.filename, basefn)
3✔
3626
            end
3627
            isnothing(idx) ? depotfn : includes[idx].filename
6✔
3628
        end
3629
        if fn == filename
3✔
3630
            return String(read(io, len))
1✔
3631
        end
3632
        seek(io, position(io) + len)
2✔
3633
    end
2✔
3634
    error(filename, " is not stored in the source-text cache")
2✔
3635
end
3636

3637
function read_dependency_src(cachefile::String, filename::AbstractString)
3✔
3638
    io = open(cachefile, "r")
3✔
3639
    try
3✔
3640
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
3✔
3641
        return read_dependency_src(io, cachefile, filename)
3✔
3642
    finally
3643
        close(io)
1✔
3644
    end
3645
end
3646

3647
function srctext_files(f::IO, srctextpos::Int64, includes::Vector{CacheHeaderIncludes})
4,506✔
3648
    files = Set{String}()
4,506✔
3649
    srctextpos == 0 && return files
4,506✔
3650
    seek(f, srctextpos)
4,506✔
3651
    while !eof(f)
26,740✔
3652
        filenamelen = read(f, Int32)
26,740✔
3653
        filenamelen == 0 && break
26,740✔
3654
        filename = String(read(f, filenamelen))
44,440✔
3655
        len = read(f, UInt64)
22,234✔
3656
        push!(files, filename)
22,234✔
3657
        seek(f, position(f) + len)
22,234✔
3658
    end
22,234✔
3659
    return files
4,506✔
3660
end
3661

3662
# Test to see if this UUID is mentioned in this `Project.toml`; either as
3663
# the top-level UUID (e.g. that of the project itself), as a dependency,
3664
# or as an extra/weakdep for Preferences.
3665
function get_uuid_name(project::Dict{String, Any}, uuid::UUID)
136✔
3666
    uuid_p = get(project, "uuid", nothing)::Union{Nothing, String}
244✔
3667
    name = get(project, "name", nothing)::Union{Nothing, String}
244✔
3668
    if name !== nothing && uuid_p !== nothing && UUID(uuid_p) == uuid
136✔
3669
        return name
20✔
3670
    end
3671
    deps = get(project, "deps", nothing)::Union{Nothing, Dict{String, Any}}
232✔
3672
    if deps !== nothing
116✔
3673
        for (k, v) in deps
232✔
3674
            if uuid == UUID(v::String)
253✔
3675
                return k
45✔
3676
            end
3677
        end
345✔
3678
    end
3679
    for subkey in ("deps", "extras", "weakdeps")
71✔
3680
        subsection = get(project, subkey, nothing)::Union{Nothing, Dict{String, Any}}
319✔
3681
        if subsection !== nothing
213✔
3682
            for (k, v) in subsection
212✔
3683
                if uuid == UUID(v::String)
211✔
3684
                    return k
13✔
3685
                end
3686
            end
303✔
3687
        end
3688
    end
258✔
3689
    return nothing
58✔
3690
end
3691

3692
function get_uuid_name(project_toml::String, uuid::UUID)
3693
    project = parsed_toml(project_toml)
1✔
3694
    return get_uuid_name(project, uuid)
1✔
3695
end
3696

3697
# If we've asked for a specific UUID, this function will extract the prefs
3698
# for that particular UUID.  Otherwise, it returns all preferences.
3699
function filter_preferences(prefs::Dict{String, Any}, pkg_name)
3700
    if pkg_name === nothing
78✔
3701
        return prefs
×
3702
    else
3703
        return get(Dict{String, Any}, prefs, pkg_name)::Dict{String, Any}
78✔
3704
    end
3705
end
3706

3707
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing})
136✔
3708
    # We'll return a list of dicts to be merged
3709
    dicts = Dict{String, Any}[]
136✔
3710

3711
    project = parsed_toml(project_toml)
136✔
3712
    pkg_name = nothing
136✔
3713
    if uuid !== nothing
136✔
3714
        # If we've been given a UUID, map that to the name of the package as
3715
        # recorded in the preferences section.  If we can't find that mapping,
3716
        # exit out, as it means there's no way preferences can be set for that
3717
        # UUID, as we only allow actual dependencies to have preferences set.
3718
        pkg_name = get_uuid_name(project, uuid)
136✔
3719
        if pkg_name === nothing
136✔
3720
            return dicts
58✔
3721
        end
3722
    end
3723

3724
    # Look first inside of `Project.toml` to see we have preferences embedded within there
3725
    proj_preferences = get(Dict{String, Any}, project, "preferences")::Dict{String, Any}
78✔
3726
    push!(dicts, filter_preferences(proj_preferences, pkg_name))
78✔
3727

3728
    # Next, look for `(Julia)LocalPreferences.toml` files next to this `Project.toml`
3729
    project_dir = dirname(project_toml)
78✔
3730
    for name in preferences_names
78✔
3731
        toml_path = joinpath(project_dir, name)
156✔
3732
        if isfile(toml_path)
156✔
3733
            prefs = parsed_toml(toml_path)
×
3734
            push!(dicts, filter_preferences(prefs, pkg_name))
×
3735

3736
            # If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
3737
            break
×
3738
        end
3739
    end
156✔
3740

3741
    return dicts
78✔
3742
end
3743

3744
"""
3745
    recursive_prefs_merge(base::Dict, overrides::Dict...)
3746

3747
Helper function to merge preference dicts recursively, honoring overrides in nested
3748
dictionaries properly.
3749
"""
3750
function recursive_prefs_merge(base::Dict{String, Any}, overrides::Dict{String, Any}...)
136✔
3751
    new_base = Base._typeddict(base, overrides...)
136✔
3752

3753
    for override in overrides
136✔
3754
        # Clear entries are keys that should be deleted from any previous setting.
3755
        override_clear = get(override, "__clear__", nothing)
78✔
3756
        if override_clear isa Vector{String}
78✔
3757
            for k in override_clear
×
3758
                delete!(new_base, k)
×
3759
            end
×
3760
        end
3761

3762
        for (k, override_k) in override
78✔
3763
            # Note that if `base` has a mapping that is _not_ a `Dict`, and `override`
3764
            new_base_k = get(new_base, k, nothing)
×
3765
            if new_base_k isa Dict{String, Any} && override_k isa Dict{String, Any}
×
3766
                new_base[k] = recursive_prefs_merge(new_base_k, override_k)
×
3767
            else
3768
                new_base[k] = override_k
×
3769
            end
3770
        end
×
3771
    end
78✔
3772
    return new_base
136✔
3773
end
3774

3775
function get_projects_workspace_to_root(project_file)
1,139✔
3776
    projects = String[project_file]
1,139✔
3777
    while true
1,139✔
3778
        project_file = base_project(project_file)
1,139✔
3779
        if project_file === nothing
1,139✔
3780
            return projects
1,139✔
3781
        end
3782
        push!(projects, project_file)
×
3783
    end
×
3784
end
3785

3786
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
1,139✔
3787
    merged_prefs = Dict{String,Any}()
1,140✔
3788
    loadpath = load_path()
1,139✔
3789
    projects_to_merge_prefs = String[]
1,139✔
3790
    append!(projects_to_merge_prefs, Iterators.drop(loadpath, 1))
1,139✔
3791
    if length(loadpath) >= 1
1,139✔
3792
        prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath)))
2,278✔
3793
    end
3794

3795
    for env in reverse(projects_to_merge_prefs)
1,139✔
3796
        project_toml = env_project_file(env)
1,357✔
3797
        if !isa(project_toml, String)
1,357✔
3798
            continue
1,221✔
3799
        end
3800

3801
        # Collect all dictionaries from the current point in the load path, then merge them in
3802
        dicts = collect_preferences(project_toml, uuid)
136✔
3803
        merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
136✔
3804
    end
1,357✔
3805
    return merged_prefs
1,139✔
3806
end
3807

3808
function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{String})
1,139✔
3809
    # Start from a predictable hash point to ensure that the same preferences always
3810
    # hash to the same value, modulo changes in how Dictionaries are hashed.
3811
    h = UInt(0)
1,227✔
3812
    uuid === nothing && return UInt64(h)
1,227✔
3813

3814
    # Load the preferences
3815
    prefs = get_preferences(uuid)
1,139✔
3816

3817
    # Walk through each name that's called out as a compile-time preference
3818
    for name in prefs_list
1,139✔
3819
        prefs_value = get(prefs, name, nothing)
×
3820
        if prefs_value !== nothing
×
3821
            h = hash(prefs_value, h)::UInt
×
3822
        end
3823
    end
×
3824
    # We always return a `UInt64` so that our serialization format is stable
3825
    return UInt64(h)
1,139✔
3826
end
3827

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

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

3833
# In `Preferences.jl`, if someone calls `load_preference(@__MODULE__, key)` while we're precompiling,
3834
# we mark that usage as a usage at compile-time and call this method, so that at the end of `.ji` generation,
3835
# we can record the list of compile-time preferences and embed that into the `.ji` header
3836
function record_compiletime_preference(uuid::UUID, key::String)
×
3837
    pref = get!(Set{String}, COMPILETIME_PREFERENCES, uuid)
×
3838
    push!(pref, key)
×
3839
    return nothing
×
3840
end
3841
get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIME_PREFERENCES, uuid))
×
3842
get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid)
×
3843
get_compiletime_preferences(::Nothing) = String[]
×
3844

3845
function check_clone_targets(clone_targets)
3846
    rejection_reason = ccall(:jl_check_pkgimage_clones, Any, (Ptr{Cchar},), clone_targets)
1,232✔
3847
    if rejection_reason !== nothing
1,232✔
3848
        return rejection_reason
×
3849
    end
3850
end
3851

3852
# Set by FileWatching.__init__()
3853
global mkpidlock_hook::Any
3854
global trymkpidlock_hook::Any
3855
global parse_pidfile_hook::Any
3856

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

3862
const compilecache_pidlock_stale_age = 10
3863

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

3895
function record_reason(reasons::Dict{String,Int}, reason::String)
21✔
3896
    reasons[reason] = get(reasons, reason, 0) + 1
21✔
3897
end
3898
record_reason(::Nothing, ::String) = nothing
738✔
3899
function list_reasons(reasons::Dict{String,Int})
6✔
3900
    isempty(reasons) && return ""
6✔
3901
    return "(cache misses: $(join(("$k ($v)" for (k,v) in reasons), ", ")))"
2✔
3902
end
3903
list_reasons(::Nothing) = ""
×
3904

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

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

4035
        # Check if transitive dependencies can be fulfilled
4036
        ndeps = length(required_modules)
1,235✔
4037
        depmods = Vector{Any}(undef, ndeps)
1,235✔
4038
        for i in 1:ndeps
2,470✔
4039
            req_key, req_build_id = required_modules[i]
18,175✔
4040
            # Check if module is already loaded
4041
            M = stalecheck ? nothing : maybe_loaded_precompile(req_key, req_build_id)
18,248✔
4042
            if M !== nothing
18,175✔
4043
                @assert PkgId(M) == req_key && module_build_id(M) === req_build_id
65✔
4044
                depmods[i] = M
65✔
4045
                continue
65✔
4046
            end
4047
            M = maybe_root_module(req_key)
18,110✔
4048
            if M isa Module
18,110✔
4049
                if PkgId(M) == req_key && module_build_id(M) === req_build_id
28,630✔
4050
                    depmods[i] = M
16,166✔
4051
                    continue
16,166✔
4052
                elseif M == Core
×
4053
                    @debug "Rejecting cache file $cachefile because it was made with a different julia version"
×
4054
                    record_reason(reasons, "wrong julia version")
×
4055
                    return true # Won't be able to fulfill dependency
×
4056
                elseif ignore_loaded || !stalecheck
×
4057
                    # Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages
4058
                else
4059
                    @debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible."
×
4060
                    record_reason(reasons, "wrong dep version loaded")
×
4061
                    return true # Won't be able to fulfill dependency
×
4062
                end
4063
            end
4064
            path = locate_package(req_key) # TODO: add env and/or skip this when stalecheck is false
1,944✔
4065
            if path === nothing
1,944✔
4066
                @debug "Rejecting cache file $cachefile because dependency $req_key not found."
3✔
4067
                record_reason(reasons, "dep missing source")
3✔
4068
                return true # Won't be able to fulfill dependency
3✔
4069
            end
4070
            depmods[i] = (path, req_key, req_build_id)
1,941✔
4071
        end
35,112✔
4072

4073
        # check if this file is going to provide one of our concrete dependencies
4074
        # or if it provides a version that conflicts with our concrete dependencies
4075
        # or neither
4076
        if stalecheck
1,232✔
4077
            for (req_key, req_build_id) in _concrete_dependencies
1,227✔
4078
                build_id = get(modules, req_key, UInt64(0))
×
4079
                if build_id !== UInt64(0)
×
4080
                    build_id |= UInt128(checksum) << 64
×
4081
                    if build_id === req_build_id
×
4082
                        stalecheck = false
×
4083
                        break
×
4084
                    end
4085
                    @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))"
×
4086
                    record_reason(reasons, "wrong dep buildid")
×
4087
                    return true # cachefile doesn't provide the required version of the dependency
×
4088
                end
4089
            end
×
4090
        end
4091

4092
        # now check if this file's content hash has changed relative to its source files
4093
        if stalecheck
1,232✔
4094
            if !samefile(includes[1].filename, modpath)
1,227✔
4095
                # In certain cases the path rewritten by `fixup_stdlib_path` may
4096
                # point to an unreadable directory, make sure we can `stat` the
4097
                # file before comparing it with `modpath`.
4098
                stdlib_path = fixup_stdlib_path(includes[1].filename)
3✔
4099
                if !(isreadable(stdlib_path) && samefile(stdlib_path, modpath))
3✔
4100
                    !samefile(fixup_stdlib_path(includes[1].filename), modpath)
3✔
4101
                    @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath"
3✔
4102
                    record_reason(reasons, "wrong source")
3✔
4103
                    return true # cache file was compiled from a different path
3✔
4104
                end
4105
            end
4106
            for (modkey, req_modkey) in requires
1,224✔
4107
                # verify that `require(modkey, name(req_modkey))` ==> `req_modkey`
4108
                pkg = identify_package(modkey, req_modkey.name)
3,016✔
4109
                if pkg != req_modkey
3,016✔
4110
                    @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed, expected $modkey => $(repr("text/plain", pkg))"
×
4111
                    record_reason(reasons, "dep uuid changed")
×
4112
                    return true
×
4113
                end
4114
            end
1,508✔
4115
            if any_includes_stale(includes, cachefile, reasons)
1,224✔
4116
                return true
2✔
4117
            end
4118
        end
4119

4120
        if !isvalid_file_crc(io)
1,227✔
4121
            @debug "Rejecting cache file $cachefile because it has an invalid checksum"
1✔
4122
            record_reason(reasons, "invalid checksum")
1✔
4123
            return true
1✔
4124
        end
4125

4126
        if pkgimage
1,226✔
4127
            if !isvalid_pkgimage_crc(io, ocachefile::String)
1,221✔
4128
                @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum"
×
4129
                record_reason(reasons, "ocachefile invalid checksum")
×
4130
                return true
×
4131
            end
4132
        end
4133

4134
        curr_prefs_hash = get_preferences_hash(id.uuid, prefs)
2,365✔
4135
        if prefs_hash != curr_prefs_hash
1,226✔
4136
            @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))"
×
4137
            record_reason(reasons, "preferences hash mismatch")
×
4138
            return true
×
4139
        end
4140

4141
        return depmods, ocachefile, id_build # fresh cachefile
1,226✔
4142
    finally
4143
        close(io)
1,985✔
4144
    end
4145
end
4146

4147
"""
4148
    @__FILE__ -> String
4149

4150
Expand to a string with the path to the file containing the
4151
macrocall, or an empty string if evaluated by `julia -e <expr>`.
4152
Return `nothing` if the macro was missing parser source information.
4153
Alternatively see [`PROGRAM_FILE`](@ref).
4154
"""
4155
macro __FILE__()
142✔
4156
    __source__.file === nothing && return nothing
142✔
4157
    return String(__source__.file::Symbol)
142✔
4158
end
4159

4160
"""
4161
    @__DIR__ -> String
4162

4163
Macro to obtain the absolute path of the current directory as a string.
4164

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

4168
# Examples
4169

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

4173
```julia-repl
4174
julia> cd("/home/JuliaUser") # working directory
4175

4176
julia> # create script at /home/JuliaUser/Projects
4177
       open("/home/JuliaUser/Projects/test.jl","w") do io
4178
           print(io, \"\"\"
4179
               println("@__DIR__ = ", @__DIR__)
4180
               println("pwd() = ", pwd())
4181
           \"\"\")
4182
       end
4183

4184
julia> # outputs script directory and current working directory
4185
       include("/home/JuliaUser/Projects/test.jl")
4186
@__DIR__ = /home/JuliaUser/Projects
4187
pwd() = /home/JuliaUser
4188
```
4189
"""
4190
macro __DIR__()
177✔
4191
    __source__.file === nothing && return nothing
177✔
4192
    _dirname = dirname(String(__source__.file::Symbol))
177✔
4193
    return isempty(_dirname) ? pwd() : abspath(_dirname)
177✔
4194
end
4195

4196
function prepare_compiler_stub_image!()
×
4197
    ccall(:jl_add_to_module_init_list, Cvoid, (Any,), Compiler)
×
4198
    register_root_module(Compiler)
×
4199
    filter!(mod->mod !== Compiler, loaded_modules_order)
×
4200
end
4201

4202
function expand_compiler_path(tup)
×
4203
    (tup[1], joinpath(Sys.BINDIR, DATAROOTDIR, tup[2]), tup[3:end]...)
×
4204
end
4205
compiler_chi(tup::Tuple) = CacheHeaderIncludes(expand_compiler_path(tup))
×
4206

4207
"""
4208
    isprecompilable(f, argtypes::Tuple{Vararg{Any}})
4209

4210
Check, as far as is possible without actually compiling, if the given
4211
function `f` can be compiled for the argument tuple (of types) `argtypes`.
4212
"""
4213
function isprecompilable(@nospecialize(f), @nospecialize(argtypes::Tuple))
×
4214
    isprecompilable(Tuple{Core.Typeof(f), argtypes...})
×
4215
end
4216

4217
function isprecompilable(@nospecialize(argt::Type))
×
4218
    ccall(:jl_is_compilable, Int32, (Any,), argt) != 0
×
4219
end
4220

4221
"""
4222
    precompile(f, argtypes::Tuple{Vararg{Any}})
4223

4224
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
4225
"""
4226
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
2✔
4227
    precompile(Tuple{Core.Typeof(f), argtypes...})
2✔
4228
end
4229

4230
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
4231
function precompile(@nospecialize(argt::Type))
3✔
4232
    ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
3✔
4233
    if !ret && ENABLE_PRECOMPILE_WARNINGS[]
3✔
4234
        @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
×
4235
    end
4236
    return ret
3✔
4237
end
4238

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

4243
"""
4244
    precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
4245

4246
Precompile a specific method for the given argument types. This may be used to precompile
4247
a different method than the one that would ordinarily be chosen by dispatch, thus
4248
mimicking `invoke`.
4249
"""
4250
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
×
4251
    precompile(Tuple{Core.Typeof(f), argtypes...}, m)
×
4252
end
4253

4254
function precompile(@nospecialize(argt::Type), m::Method)
×
4255
    atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
×
4256
    mi = Base.Compiler.specialize_method(m, atype, sparams)
×
4257
    return precompile(mi)
×
4258
end
4259

4260
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) || @assert false
4261
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) || @assert false
4262
precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), Cmd, CacheFlags, IO, IO)) || @assert false
4263
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