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

JuliaLang / julia / #37527

pending completion
#37527

push

local

web-flow
make `IRShow.method_name` inferrable (#49607)

18 of 18 new or added lines in 3 files covered. (100.0%)

68710 of 81829 relevant lines covered (83.97%)

33068903.12 hits per line

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

82.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)
1,258,033✔
11
elseif Sys.iswindows()
12
    # GetLongPathName Win32 function returns the case-preserved filename on NTFS.
13
    function isfile_casesensitive(path)
×
14
        isaccessiblefile(path) || return false  # Fail fast
×
15
        basename(Filesystem.longpath(path)) == basename(path)
×
16
    end
17
elseif Sys.isapple()
18
    # HFS+ filesystem is case-preserving. The getattrlist API returns
19
    # a case-preserved filename. In the rare event that HFS+ is operating
20
    # in case-sensitive mode, this will still work but will be redundant.
21

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

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

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

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

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

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

95
function isaccessiblefile(file)
1,258,034✔
96
    return try
1,258,034✔
97
        isfile(file)
1,258,034✔
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}
29,158✔
117
end
118
function SHA1(bytes::Vector{UInt8})
29,158✔
119
    length(bytes) == 20 ||
29,158✔
120
        throw(ArgumentError("wrong number of bytes for SHA1 hash: $(length(bytes))"))
121
    return SHA1(ntuple(i->bytes[i], Val(20)))
612,318✔
122
end
123
SHA1(s::AbstractString) = SHA1(hex2bytes(s))
28,950✔
124
parse(::Type{SHA1}, s::AbstractString) = SHA1(s)
2✔
125
function tryparse(::Type{SHA1}, s::AbstractString)
1✔
126
    try
1✔
127
        return parse(SHA1, s)
1✔
128
    catch e
129
        if isa(e, ArgumentError)
1✔
130
            return nothing
1✔
131
        end
132
        rethrow(e)
×
133
    end
134
end
135

136
string(hash::SHA1) = bytes2hex(hash.bytes)
197✔
137
print(io::IO, hash::SHA1) = bytes2hex(io, hash.bytes)
27✔
138
show(io::IO, hash::SHA1) = print(io, "SHA1(\"", hash, "\")")
1✔
139

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

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

161
function dummy_uuid(project_file::String)
123,245✔
162
    @lock require_lock begin
123,245✔
163
    cache = LOADING_CACHE[]
123,245✔
164
    if cache !== nothing
123,245✔
165
        uuid = get(cache.dummy_uuid, project_file, nothing)
2,818✔
166
        uuid === nothing || return uuid
2,818✔
167
    end
168
    project_path = try
122,293✔
169
        realpath(project_file)
122,293✔
170
    catch ex
171
        ex isa IOError || rethrow()
×
172
        project_file
122,293✔
173
    end
174
    uuid = uuid5(ns_dummy_uuid, project_path)
122,293✔
175
    if cache !== nothing
122,293✔
176
        cache.dummy_uuid[project_file] = uuid
914✔
177
    end
178
    return uuid
122,293✔
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)
×
187
    y::UInt32 = x
58,330✔
188
    sprint(sizehint=p) do io
58,330✔
189
        n = length(slug_chars)
58,329✔
190
        for i = 1:p
116,658✔
191
            y, d = divrem(y, n)
262,862✔
192
            write(io, slug_chars[1+d])
262,862✔
193
        end
262,862✔
194
    end
195
end
196

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

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

208
mutable struct CachedTOMLDict
209
    path::String
1,201✔
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,201✔
218
    s = stat(path)
1,201✔
219
    content = read(path)
1,201✔
220
    crc32 = _crc32c(content)
1,201✔
221
    TOML.reinit!(p, String(content); filepath=path)
1,201✔
222
    d = TOML.parse(p)
1,201✔
223
    return CachedTOMLDict(
1,201✔
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)
396,001✔
234
    s = stat(f.path)
396,001✔
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
792,002✔
239
        content = read(f.path)
2✔
240
        new_hash = _crc32c(content)
2✔
241
        if new_hash != f.hash
2✔
242
            f.inode = s.inode
2✔
243
            f.mtime = s.mtime
2✔
244
            f.size = s.size
2✔
245
            f.hash = new_hash
2✔
246
            TOML.reinit!(p, String(content); filepath=f.path)
2✔
247
            return f.d = TOML.parse(p)
2✔
248
        end
249
    end
250
    return f.d
395,999✔
251
end
252

253
struct LoadingCache
254
    load_path::Vector{String}
1,586✔
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, Union{Nothing, String}}}}
260
    identified::Dict{String, Union{Nothing, Tuple{PkgId, Union{Nothing, String}}}}
261
    located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{Union{String, Nothing}, Union{String, Nothing}}, Nothing}}
262
end
263
const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing)
264
LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict())
1,586✔
265

266

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

273
parsed_toml(project_file::AbstractString) = parsed_toml(project_file, TOML_CACHE, require_lock)
400,230✔
274
function parsed_toml(project_file::AbstractString, toml_cache::TOMLCache, toml_lock::ReentrantLock)
1✔
275
    lock(toml_lock) do
400,370✔
276
        cache = LOADING_CACHE[]
400,370✔
277
        dd = if !haskey(toml_cache.d, project_file)
400,370✔
278
            d = CachedTOMLDict(toml_cache.p, project_file)
1,201✔
279
            toml_cache.d[project_file] = d
1,201✔
280
            d.d
1,201✔
281
        else
282
            d = toml_cache.d[project_file]
399,169✔
283
            # We are in a require call and have already parsed this TOML file
284
            # assume that it is unchanged to avoid hitting disk
285
            if cache !== nothing && project_file in cache.require_parsed
399,169✔
286
                d.d
3,168✔
287
            else
288
                get_updated_dict(toml_cache.p, d)
799,539✔
289
            end
290
        end
291
        if cache !== nothing
400,370✔
292
            push!(cache.require_parsed, project_file)
5,596✔
293
        end
294
        return dd
400,370✔
295
    end
296
end
297

298
## package identification: determine unique identity of package to be loaded ##
299

300
# Used by Pkg but not used in loading itself
301
function find_package(arg)
2✔
302
    pkgenv = identify_package_env(arg)
2✔
303
    pkgenv === nothing && return nothing
2✔
304
    pkg, env = pkgenv
1✔
305
    return locate_package(pkg, env)
1✔
306
end
307

308
"""
309
    Base.identify_package_env(name::String)::Union{Tuple{PkgId, String}, Nothing}
310
    Base.identify_package_env(where::Union{Module,PkgId}, name::String)::Union{Tuple{PkgId, String} Nothing}
311

312
Same as [`Base.identify_package`](@ref) except that the path to the environment where the package is identified
313
is also returned.
314
"""
315
identify_package_env(where::Module, name::String) = identify_package_env(PkgId(where), name)
1,631✔
316
function identify_package_env(where::PkgId, name::String)
74,673✔
317
    cache = LOADING_CACHE[]
74,673✔
318
    if cache !== nothing
74,673✔
319
        pkg_env = get(cache.identified_where, (where, name), nothing)
2,434✔
320
        pkg_env === nothing || return pkg_env
2,434✔
321
    end
322
    pkg_env = nothing
1✔
323
    if where.name === name
74,614✔
324
        pkg_env = where, nothing
23,699✔
325
    elseif where.uuid === nothing
50,915✔
326
        pkg_env = identify_package_env(name) # ignore `where`
1,526✔
327
    else
328
        for env in load_path()
49,389✔
329
            pkgid = manifest_deps_get(env, where, name)
73,894✔
330
            pkgid === nothing && continue # not found--keep looking
73,894✔
331
            if pkgid.uuid !== nothing
49,389✔
332
                pkg_env = pkgid, env # found in explicit environment--use it
24,905✔
333
            end
334
            break # found in implicit environment--return "not found"
49,389✔
335
        end
24,505✔
336
    end
337
    if cache !== nothing
74,614✔
338
        cache.identified_where[(where, name)] = pkg_env
4,632✔
339
    end
340
    return pkg_env
74,614✔
341
end
342
function identify_package_env(name::String)
11,515✔
343
    cache = LOADING_CACHE[]
11,515✔
344
    if cache !== nothing
11,515✔
345
        pkg_env = get(cache.identified, name, nothing)
1,478✔
346
        pkg_env === nothing || return pkg_env
1,478✔
347
    end
348
    pkg_env = nothing
1✔
349
    for env in load_path()
11,514✔
350
        pkg = project_deps_get(env, name)
21,064✔
351
        if pkg !== nothing
14,399✔
352
            pkg_env = pkg, env # found--return it
11,290✔
353
            break
11,290✔
354
        end
355
    end
3,109✔
356
    if cache !== nothing
11,514✔
357
        cache.identified[name] = pkg_env
2,952✔
358
    end
359
    return pkg_env
11,514✔
360
end
361

362
_nothing_or_first(x) = x === nothing ? nothing : first(x)
114,995✔
363

364
"""
365
    Base.identify_package(name::String)::Union{PkgId, Nothing}
366
    Base.identify_package(where::Union{Module,PkgId}, name::String)::Union{PkgId, Nothing}
367

368
Identify the package by its name from the current environment stack, returning
369
its `PkgId`, or `nothing` if it cannot be found.
370

371
If only the `name` argument is provided, it searches each environment in the
372
stack and its named direct dependencies.
373

374
There `where` argument provides the context from where to search for the
375
package: in this case it first checks if the name matches the context itself,
376
otherwise it searches all recursive dependencies (from the resolved manifest of
377
each environment) until it locates the context `where`, and from there
378
identifies the dependency with the corresponding name.
379

380
```julia-repl
381
julia> Base.identify_package("Pkg") # Pkg is a dependency of the default environment
382
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
383

384
julia> using LinearAlgebra
385

386
julia> Base.identify_package(LinearAlgebra, "Pkg") # Pkg is not a dependency of LinearAlgebra
387
```
388
"""
389
identify_package(where::Module, name::String) = _nothing_or_first(identify_package_env(where, name))
91✔
390
identify_package(where::PkgId, name::String)  = _nothing_or_first(identify_package_env(where, name))
121,520✔
391
identify_package(name::String)                = _nothing_or_first(identify_package_env(name))
19,749✔
392

393
function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)
57,038✔
394
    cache = LOADING_CACHE[]
57,407✔
395
    if cache !== nothing
57,038✔
396
        pathenv = get(cache.located, (pkg, stopenv), nothing)
780✔
397
        pathenv === nothing || return pathenv
780✔
398
    end
399
    path = nothing
×
400
    env′ = nothing
×
401
    if pkg.uuid === nothing
56,957✔
402
        for env in load_path()
1,944✔
403
            env′ = env
×
404
            # look for the toplevel pkg `pkg.name` in this entry
405
            found = project_deps_get(env, pkg.name)
2,248✔
406
            if found !== nothing
2,248✔
407
                @assert found.name == pkg.name
1,941✔
408
                if found.uuid === nothing
1,941✔
409
                    # pkg.name is present in this directory or project file,
410
                    # return the path the entry point for the code, if it could be found
411
                    # otherwise, signal failure
412
                    path = implicit_manifest_uuid_path(env, pkg)
1,941✔
413
                    @goto done
1,941✔
414
                end
415
            end
416
            stopenv == env && @goto done
2✔
417
        end
307✔
418
    else
419
        for env in load_path()
55,013✔
420
            env′ = env
×
421
            path = manifest_uuid_path(env, pkg)
83,549✔
422
            # missing is used as a sentinel to stop looking further down in envs
423
            if path === missing
83,549✔
424
                path = nothing
×
425
                @goto done
1✔
426
            end
427
            if path !== nothing
83,548✔
428
                path = entry_path(path, pkg.name)
48,363✔
429
                @goto done
48,363✔
430
            end
431
            stopenv == env && break
204✔
432
        end
41,828✔
433
        # Allow loading of stdlibs if the name/uuid are given
434
        # e.g. if they have been explicitly added to the project/manifest
435
        mbypath = manifest_uuid_path(Sys.STDLIB, pkg)
6,649✔
436
        if mbypath isa String
6,649✔
437
            path = entry_path(mbypath, pkg.name)
2✔
438
            @goto done
×
439
        end
440
    end
441
    @label done
×
442
    if cache !== nothing
56,957✔
443
        cache.located[(pkg, stopenv)] = path, env′
618✔
444
    end
445
    return path, env′
56,957✔
446
end
447

448
"""
449
    Base.locate_package(pkg::PkgId)::Union{String, Nothing}
450

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

454
```julia-repl
455
julia> pkg = Base.identify_package("Pkg")
456
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
457

458
julia> Base.locate_package(pkg)
459
"/path/to/julia/stdlib/v$(VERSION.major).$(VERSION.minor)/Pkg/src/Pkg.jl"
460
```
461
"""
462
function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String}
112,350✔
463
    _nothing_or_first(locate_package_env(pkg, stopenv))
112,876✔
464
end
465

466
"""
467
    pathof(m::Module)
468

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

472
Use [`dirname`](@ref) to get the directory part and [`basename`](@ref)
473
to get the file name part of the path.
474
"""
475
function pathof(m::Module)
21✔
476
    @lock require_lock begin
21✔
477
    pkgid = get(module_keys, m, nothing)
41✔
478
    pkgid === nothing && return nothing
21✔
479
    origin = get(pkgorigins, pkgid, nothing)
37✔
480
    origin === nothing && return nothing
20✔
481
    path = origin.path
17✔
482
    path === nothing && return nothing
17✔
483
    return fixup_stdlib_path(path)
17✔
484
    end
485
end
486

487
"""
488
    pkgdir(m::Module[, paths::String...])
489

490
Return the root directory of the package that imported module `m`,
491
or `nothing` if `m` was not imported from a package. Optionally further
492
path component strings can be provided to construct a path within the
493
package root.
494

495
To get the root directory of the package that imported the current module
496
the form `pkgdir(@__MODULE__)` can be used.
497

498
```julia-repl
499
julia> pkgdir(Foo)
500
"/path/to/Foo.jl"
501

502
julia> pkgdir(Foo, "src", "file.jl")
503
"/path/to/Foo.jl/src/file.jl"
504
```
505

506
!!! compat "Julia 1.7"
507
    The optional argument `paths` requires at least Julia 1.7.
508
"""
509
function pkgdir(m::Module, paths::String...)
16✔
510
    rootmodule = moduleroot(m)
16✔
511
    path = pathof(rootmodule)
16✔
512
    path === nothing && return nothing
16✔
513
    return joinpath(dirname(dirname(path)), paths...)
13✔
514
end
515

516
function get_pkgversion_from_path(path)
4✔
517
    project_file = locate_project_file(path)
4✔
518
    if project_file isa String
4✔
519
        d = parsed_toml(project_file)
4✔
520
        v = get(d, "version", nothing)
8✔
521
        if v !== nothing
4✔
522
            return VersionNumber(v::String)
4✔
523
        end
524
    end
525
    return nothing
×
526
end
527

528
"""
529
    pkgversion(m::Module)
530

531
Return the version of the package that imported module `m`,
532
or `nothing` if `m` was not imported from a package, or imported
533
from a package without a version field set.
534

535
The version is read from the package's Project.toml during package
536
load.
537

538
To get the version of the package that imported the current module
539
the form `pkgversion(@__MODULE__)` can be used.
540

541
!!! compat "Julia 1.9"
542
    This function was introduced in Julia 1.9.
543
"""
544
function pkgversion(m::Module)
5✔
545
    path = pkgdir(m)
9✔
546
    path === nothing && return nothing
5✔
547
    @lock require_lock begin
4✔
548
        v = get_pkgversion_from_path(path)
4✔
549
        pkgorigin = get(pkgorigins, PkgId(moduleroot(m)), nothing)
8✔
550
        # Cache the version
551
        if pkgorigin !== nothing && pkgorigin.version === nothing
4✔
552
            pkgorigin.version = v
4✔
553
        end
554
        return v
4✔
555
    end
556
end
557

558
## generic project & manifest API ##
559

560
const project_names = ("JuliaProject.toml", "Project.toml")
561
const manifest_names = ("JuliaManifest.toml", "Manifest.toml")
562
const preferences_names = ("JuliaLocalPreferences.toml", "LocalPreferences.toml")
563

564
function locate_project_file(env::String)
45,779✔
565
    for proj in project_names
45,779✔
566
        project_file = joinpath(env, proj)
91,558✔
567
        if isfile_casesensitive(project_file)
91,558✔
568
            return project_file
4✔
569
        end
570
    end
137,329✔
571
    return true
45,775✔
572
end
573

574
# classify the LOAD_PATH entry to be one of:
575
#  - `false`: nonexistent / nothing to see here
576
#  - `true`: `env` is an implicit environment
577
#  - `path`: the path of an explicit project file
578
function env_project_file(env::String)::Union{Bool,String}
181,858✔
579
    @lock require_lock begin
181,858✔
580
    cache = LOADING_CACHE[]
181,858✔
581
    if cache !== nothing
181,858✔
582
        project_file = get(cache.env_project_file, env, nothing)
8,471✔
583
        project_file === nothing || return project_file
8,471✔
584
    end
585
    if isdir(env)
179,130✔
586
        project_file = locate_project_file(env)
45,775✔
587
    elseif basename(env) in project_names && isfile_casesensitive(env)
266,710✔
588
        project_file = env
132,447✔
589
    else
590
        project_file = false
×
591
    end
592
    if cache !== nothing
179,130✔
593
        cache.env_project_file[env] = project_file
3,810✔
594
    end
595
    return project_file
179,130✔
596
    end
597
end
598

599
function project_deps_get(env::String, name::String)::Union{Nothing,PkgId}
2✔
600
    project_file = env_project_file(env)
16,647✔
601
    if project_file isa String
16,647✔
602
        pkg_uuid = explicit_project_deps_get(project_file, name)
9,005✔
603
        pkg_uuid === nothing || return PkgId(pkg_uuid, name)
15,670✔
604
    elseif project_file
7,642✔
605
        return implicit_project_deps_get(env, name)
6,987✔
606
    end
607
    return nothing
2,995✔
608
end
609

610
function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothing,PkgId}
73,894✔
611
    uuid = where.uuid
73,894✔
612
    @assert uuid !== nothing
73,894✔
613
    project_file = env_project_file(env)
73,894✔
614
    if project_file isa String
73,894✔
615
        # first check if `where` names the Project itself
616
        proj = project_file_name_uuid(project_file, where.name)
58,094✔
617
        if proj == where
111,155✔
618
            # if `where` matches the project, use [deps] section as manifest, and stop searching
619
            pkg_uuid = explicit_project_deps_get(project_file, name)
5,033✔
620
            return PkgId(pkg_uuid, name)
5,033✔
621
        end
622
        # look for manifest file and `where` stanza
623
        return explicit_manifest_deps_get(project_file, where, name)
53,061✔
624
    elseif project_file
15,800✔
625
        # if env names a directory, search it
626
        return implicit_manifest_deps_get(env, where, name)
15,496✔
627
    end
628
    return nothing
304✔
629
end
630

631
function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missing}
90,198✔
632
    project_file = env_project_file(env)
90,198✔
633
    if project_file isa String
90,198✔
634
        proj = project_file_name_uuid(project_file, pkg.name)
66,168✔
635
        if proj == pkg
128,182✔
636
            # if `pkg` matches the project, return the project itself
637
            return project_file_path(project_file)
4,154✔
638
        end
639
        # look for manifest file and `where` stanza
640
        return explicit_manifest_uuid_path(project_file, pkg)
62,014✔
641
    elseif project_file
24,030✔
642
        # if env names a directory, search it
643
        return implicit_manifest_uuid_path(env, pkg)
23,866✔
644
    end
645
    return nothing
164✔
646
end
647

648
# find project file's top-level UUID entry (or nothing)
649
function project_file_name_uuid(project_file::String, name::String)::PkgId
146,043✔
650
    d = parsed_toml(project_file)
146,043✔
651
    uuid′ = get(d, "uuid", nothing)::Union{String, Nothing}
189,091✔
652
    uuid = uuid′ === nothing ? dummy_uuid(project_file) : UUID(uuid′)
189,091✔
653
    name = get(d, "name", name)::String
245,013✔
654
    return PkgId(uuid, name)
146,043✔
655
end
656

657
function project_file_path(project_file::String)
×
658
    d = parsed_toml(project_file)
4,154✔
659
    joinpath(dirname(project_file), get(d, "path", "")::String)
6,485✔
660
end
661

662
# find project file's corresponding manifest file
663
function project_file_manifest_path(project_file::String)::Union{Nothing,String}
115,160✔
664
    @lock require_lock begin
115,160✔
665
    cache = LOADING_CACHE[]
115,160✔
666
    if cache !== nothing
115,160✔
667
        manifest_path = get(cache.project_file_manifest_path, project_file, missing)
1,858✔
668
        manifest_path === missing || return manifest_path
1,858✔
669
    end
670
    dir = abspath(dirname(project_file))
114,341✔
671
    d = parsed_toml(project_file)
114,341✔
672
    explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing}
114,341✔
673
    manifest_path = nothing
×
674
    if explicit_manifest !== nothing
114,341✔
675
        manifest_file = normpath(joinpath(dir, explicit_manifest))
×
676
        if isfile_casesensitive(manifest_file)
×
677
            manifest_path = manifest_file
×
678
        end
679
    end
680
    if manifest_path === nothing
114,341✔
681
        for mfst in manifest_names
114,341✔
682
            manifest_file = joinpath(dir, mfst)
228,682✔
683
            if isfile_casesensitive(manifest_file)
228,682✔
684
                manifest_path = manifest_file
×
685
                break
114,341✔
686
            end
687
        end
114,341✔
688
    end
689
    if cache !== nothing
114,341✔
690
        cache.project_file_manifest_path[project_file] = manifest_path
440✔
691
    end
692
    return manifest_path
114,341✔
693
    end
694
end
695

696
# given a directory (implicit env from LOAD_PATH) and a name,
697
# check if it is an implicit package
698
function entry_point_and_project_file_inside(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
×
699
    path = normpath(joinpath(dir, "src", "$name.jl"))
56,746✔
700
    isfile_casesensitive(path) || return nothing, nothing
74,366✔
701
    for proj in project_names
39,126✔
702
        project_file = normpath(joinpath(dir, proj))
78,252✔
703
        isfile_casesensitive(project_file) || continue
78,252✔
704
        return path, project_file
21,615✔
705
    end
74,148✔
706
    return path, nothing
17,511✔
707
end
708

709
# given a project directory (implicit env from LOAD_PATH) and a name,
710
# find an entry point for `name`, and see if it has an associated project file
711
function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
48,290✔
712
    path = normpath(joinpath(dir, "$name.jl"))
48,290✔
713
    isfile_casesensitive(path) && return path, nothing
48,290✔
714
    dir = joinpath(dir, name)
47,936✔
715
    path, project_file = entry_point_and_project_file_inside(dir, name)
87,062✔
716
    path === nothing || return path, project_file
87,062✔
717
    dir = dir * ".jl"
8,810✔
718
    path, project_file = entry_point_and_project_file_inside(dir, name)
8,810✔
719
    path === nothing || return path, project_file
8,810✔
720
    return nothing, nothing
8,810✔
721
end
722

723
# given a path and a name, return the entry point
724
function entry_path(path::String, name::String)::Union{Nothing,String}
48,365✔
725
    isfile_casesensitive(path) && return normpath(path)
48,365✔
726
    path = normpath(joinpath(path, "src", "$name.jl"))
30,702✔
727
    isfile_casesensitive(path) && return path
30,702✔
728
    return nothing # source not found
688✔
729
end
730

731
## explicit project & manifest API ##
732

733
# find project file root or deps `name => uuid` mapping
734
# return `nothing` if `name` is not found
735
function explicit_project_deps_get(project_file::String, name::String)::Union{Nothing,UUID}
20,206✔
736
    d = parsed_toml(project_file)
20,206✔
737
    root_uuid = dummy_uuid(project_file)
20,206✔
738
    if get(d, "name", nothing)::Union{String, Nothing} === name
36,259✔
739
        uuid = get(d, "uuid", nothing)::Union{String, Nothing}
2,000✔
740
        return uuid === nothing ? root_uuid : UUID(uuid)
1,462✔
741
    end
742
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
33,578✔
743
    if deps !== nothing
18,744✔
744
        uuid = get(deps, name, nothing)::Union{String, Nothing}
26,228✔
745
        uuid === nothing || return UUID(uuid)
26,228✔
746
    end
747
    return nothing
7,350✔
748
end
749

750
function is_v1_format_manifest(raw_manifest::Dict{String})
115,165✔
751
    if haskey(raw_manifest, "manifest_format")
115,165✔
752
        mf = raw_manifest["manifest_format"]
1,227✔
753
        if mf isa Dict{String} && haskey(mf, "uuid")
1,227✔
754
            # the off-chance where an old format manifest has a dep called "manifest_format"
755
            return true
×
756
        end
757
        return false
1,227✔
758
    else
759
        return true
113,938✔
760
    end
761
end
762

763
# returns a deps list for both old and new manifest formats
764
function get_deps(raw_manifest::Dict)
2✔
765
    if is_v1_format_manifest(raw_manifest)
115,162✔
766
        return raw_manifest
113,937✔
767
    else
768
        # if the manifest has no deps, there won't be a `deps` field
769
        return get(Dict{String, Any}, raw_manifest, "deps")::Dict{String, Any}
1,225✔
770
    end
771
end
772

773
# find `where` stanza and return the PkgId for `name`
774
# return `nothing` if it did not find `where` (indicating caller should continue searching)
775
function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId}
53,061✔
776
    manifest_file = project_file_manifest_path(project_file)
53,061✔
777
    manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
53,061✔
778
    d = get_deps(parsed_toml(manifest_file))
53,737✔
779
    found_where = false
×
780
    found_name = false
×
781
    for (dep_name, entries) in d
106,122✔
782
        entries::Vector{Any}
146,841✔
783
        for entry in entries
146,841✔
784
            entry = entry::Dict{String, Any}
275,393✔
785
            uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
550,786✔
786
            uuid === nothing && continue
275,393✔
787
            if UUID(uuid) === where.uuid
275,393✔
788
                found_where = true
×
789
                # deps is either a list of names (deps = ["DepA", "DepB"]) or
790
                # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
791
                deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
62,688✔
792
                if deps isa Vector{String}
38,122✔
793
                    found_name = name in deps
4,226✔
794
                    break
2,582✔
795
                elseif deps isa Dict{String, Any}
35,540✔
796
                    deps = deps::Dict{String, Any}
21,984✔
797
                    for (dep, uuid) in deps
43,968✔
798
                        uuid::String
28,047✔
799
                        if dep === name
28,047✔
800
                            return PkgId(UUID(uuid), name)
16,946✔
801
                        end
802
                    end
11,101✔
803
                end
804
            else # Check for extensions
805
                extensions = get(entry, "extensions", nothing)
237,946✔
806
                if extensions !== nothing
237,271✔
807
                    if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name)
741✔
808
                        found_where = true
×
809
                        if name == dep_name
66✔
810
                            return PkgId(UUID(uuid), name)
31✔
811
                        end
812
                        exts = extensions[where.name]::Union{String, Vector{String}}
35✔
813
                        if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts)
58✔
814
                            weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
70✔
815
                            if weakdeps !== nothing
35✔
816
                                if weakdeps isa Vector{String}
35✔
817
                                    found_name = name in weakdeps
44✔
818
                                    break
35✔
819
                                elseif weakdeps isa Dict{String, Any}
×
820
                                    weakdeps = weakdeps::Dict{String, Any}
×
821
                                    for (dep, uuid) in weakdeps
×
822
                                        uuid::String
×
823
                                        if dep === name
×
824
                                            return PkgId(UUID(uuid), name)
×
825
                                        end
826
                                    end
×
827
                                end
828
                            end
829
                        end
830
                        # `name` is not an ext, do standard lookup as if this was the parent
831
                        return identify_package(PkgId(UUID(uuid), dep_name), name)
×
832
                    end
833
                end
834
            end
835
        end
255,799✔
836
    end
223,644✔
837
    found_where || return nothing
50,957✔
838
    found_name || return PkgId(name)
40,685✔
839
    # Only reach here if deps was not a dict which mean we have a unique name for the dep
840
    name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
3,474✔
841
    if name_deps === nothing || length(name_deps) != 1
3,474✔
842
        error("expected a single entry for $(repr(name)) in $(repr(project_file))")
×
843
    end
844
    entry = first(name_deps::Vector{Any})::Dict{String, Any}
1,737✔
845
    uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
3,474✔
846
    uuid === nothing && return nothing
1,737✔
847
    return PkgId(UUID(uuid), name)
1,737✔
848
end
849

850
# find `uuid` stanza, return the corresponding path
851
function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{Nothing,String,Missing}
62,014✔
852
    manifest_file = project_file_manifest_path(project_file)
62,014✔
853
    manifest_file === nothing && return nothing # no manifest, skip env
62,014✔
854

855
    d = get_deps(parsed_toml(manifest_file))
62,502✔
856
    entries = get(d, pkg.name, nothing)::Union{Nothing, Vector{Any}}
119,056✔
857
    if entries !== nothing
62,014✔
858
        for entry in entries
57,042✔
859
            entry = entry::Dict{String, Any}
96,067✔
860
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
192,134✔
861
            uuid === nothing && continue
96,067✔
862
            if UUID(uuid) === pkg.uuid
96,067✔
863
                return explicit_manifest_entry_path(manifest_file, pkg, entry)
47,570✔
864
            end
865
        end
57,969✔
866
    end
867
    # Extensions
868
    for (name, entries) in d
28,887✔
869
        entries = entries::Vector{Any}
35,526✔
870
        for entry in entries
35,526✔
871
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
54,511✔
872
            extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
54,511✔
873
            if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid
54,531✔
874
                parent_path = locate_package(PkgId(UUID(uuid), name))
20✔
875
                if parent_path === nothing
20✔
876
                    error("failed to find source of parent package: \"$name\"")
×
877
                end
878
                p = normpath(dirname(parent_path), "..")
20✔
879
                extfiledir = joinpath(p, "ext", pkg.name, pkg.name * ".jl")
20✔
880
                isfile(extfiledir) && return extfiledir
20✔
881
                return joinpath(p, "ext", pkg.name * ".jl")
12✔
882
            end
883
        end
89,997✔
884
    end
56,589✔
885
    return nothing
14,424✔
886
end
887

888
function explicit_manifest_entry_path(manifest_file::String, pkg::PkgId, entry::Dict{String,Any})
47,570✔
889
    path = get(entry, "path", nothing)::Union{Nothing, String}
57,238✔
890
    if path !== nothing
47,570✔
891
        path = normpath(abspath(dirname(manifest_file), path))
9,668✔
892
        return path
9,668✔
893
    end
894
    hash = get(entry, "git-tree-sha1", nothing)::Union{Nothing, String}
66,672✔
895
    hash === nothing && return nothing
37,902✔
896
    hash = SHA1(hash)
28,770✔
897
    # Keep the 4 since it used to be the default
898
    uuid = pkg.uuid::UUID # checked within `explicit_manifest_uuid_path`
28,770✔
899
    for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4))
28,770✔
900
        for depot in DEPOT_PATH
28,771✔
901
            path = joinpath(depot, "packages", pkg.name, slug)
53,929✔
902
            ispath(path) && return abspath(path)
53,929✔
903
        end
25,162✔
904
    end
3✔
905
    # no depot contains the package, return missing to stop looking
906
    return missing
1✔
907
end
908

909
## implicit project & manifest API ##
910

911
# look for an entry point for `name` from a top-level package (no environment)
912
# otherwise return `nothing` to indicate the caller should keep searching
913
function implicit_project_deps_get(dir::String, name::String)::Union{Nothing,PkgId}
1✔
914
    path, project_file = entry_point_and_project_file(dir, name)
13,553✔
915
    if project_file === nothing
6,987✔
916
        path === nothing && return nothing
4,273✔
917
        return PkgId(name)
3,852✔
918
    end
919
    proj = project_file_name_uuid(project_file, name)
2,714✔
920
    proj.name == name || return nothing
2,714✔
921
    return proj
2,714✔
922
end
923

924
# look for an entry-point for `name`, check that UUID matches
925
# if there's a project file, look up `name` in its deps and return that
926
# otherwise return `nothing` to indicate the caller should keep searching
927
function implicit_manifest_deps_get(dir::String, where::PkgId, name::String)::Union{Nothing,PkgId}
15,496✔
928
    @assert where.uuid !== nothing
15,496✔
929
    project_file = entry_point_and_project_file(dir, where.name)[2]
30,340✔
930
    project_file === nothing && return nothing # a project file is mandatory for a package with a uuid
15,496✔
931
    proj = project_file_name_uuid(project_file, where.name)
9,338✔
932
    proj == where || return nothing # verify that this is the correct project file
12,508✔
933
    # this is the correct project, so stop searching here
934
    pkg_uuid = explicit_project_deps_get(project_file, name)
6,168✔
935
    return PkgId(pkg_uuid, name)
6,168✔
936
end
937

938
# look for an entry-point for `pkg` and return its path if UUID matches
939
function implicit_manifest_uuid_path(dir::String, pkg::PkgId)::Union{Nothing,String}
×
940
    path, project_file = entry_point_and_project_file(dir, pkg.name)
43,877✔
941
    if project_file === nothing
25,807✔
942
        pkg.uuid === nothing || return nothing
30,547✔
943
        return path
1,941✔
944
    end
945
    proj = project_file_name_uuid(project_file, pkg.name)
9,563✔
946
    proj == pkg || return nothing
13,372✔
947
    return path
5,754✔
948
end
949

950
## other code loading functionality ##
951

952
function find_source_file(path::AbstractString)
35✔
953
    (isabspath(path) || isfile(path)) && return path
35✔
954
    base_path = joinpath(Sys.BINDIR, DATAROOTDIR, "julia", "base", path)
16✔
955
    return isfile(base_path) ? normpath(base_path) : nothing
16✔
956
end
957

958
cache_file_entry(pkg::PkgId) = joinpath(
745✔
959
    "compiled",
960
    "v$(VERSION.major).$(VERSION.minor)",
961
    pkg.uuid === nothing ? ""       : pkg.name),
962
    pkg.uuid === nothing ? pkg.name : package_slug(pkg.uuid)
963

964
function find_all_in_cache_path(pkg::PkgId)
462✔
965
    paths = String[]
462✔
966
    entrypath, entryfile = cache_file_entry(pkg)
462✔
967
    for path in joinpath.(DEPOT_PATH, entrypath)
462✔
968
        isdir(path) || continue
1,828✔
969
        for file in readdir(path, sort = false) # no sort given we sort later
868✔
970
            if !((pkg.uuid === nothing && file == entryfile * ".ji") ||
11,137✔
971
                 (pkg.uuid !== nothing && startswith(file, entryfile * "_") &&
972
                  endswith(file, ".ji")))
973
                 continue
4,691✔
974
            end
975
            filepath = joinpath(path, file)
906✔
976
            isfile_casesensitive(filepath) && push!(paths, filepath)
906✔
977
        end
6,445✔
978
    end
2,290✔
979
    if length(paths) > 1
462✔
980
        # allocating the sort vector is less expensive than using sort!(.. by=mtime), which would
981
        # call the relatively slow mtime multiple times per path
982
        p = sortperm(mtime.(paths), rev = true)
524✔
983
        return paths[p]
262✔
984
    else
985
        return paths
200✔
986
    end
987
end
988

989
ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Base.Libc.dlext)
1✔
990
cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Base.Libc.dlext)"), ".ji")
×
991

992

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

996
# these return either the array of modules loaded from the path / content given
997
# or an Exception that describes why it couldn't be loaded
998
# and it reconnects the Base.Docs.META
999
function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any})
449✔
1000
    assert_havelock(require_lock)
898✔
1001
    timing_imports = TIMING_IMPORTS[] > 0
449✔
1002
    try
449✔
1003
    if timing_imports
449✔
1004
        t_before = time_ns()
1✔
1005
        cumulative_compile_timing(true)
1✔
1006
        t_comp_before = cumulative_compile_time_ns()
1✔
1007
    end
1008

1009
    if ocachepath !== nothing
449✔
1010
        @debug "Loading object cache file $ocachepath for $pkg"
×
1011
        sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachepath, depmods, false, pkg.name)
×
1012
    else
1013
        @debug "Loading cache file $path for $pkg"
449✔
1014
        sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), path, depmods, false, pkg.name)
449✔
1015
    end
1016
    if isa(sv, Exception)
449✔
1017
        return sv
×
1018
    end
1019

1020
    restored = register_restored_modules(sv, pkg, path)
449✔
1021

1022
    for M in restored
449✔
1023
        M = M::Module
528✔
1024
        if parentmodule(M) === M && PkgId(M) == pkg
528✔
1025
            if timing_imports
448✔
1026
                elapsed = round((time_ns() - t_before) / 1e6, digits = 1)
1✔
1027
                comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before
1✔
1028
                print(lpad(elapsed, 9), " ms  ")
1✔
1029
                parentid = get(EXT_PRIMED, pkg, nothing)
1✔
1030
                if parentid !== nothing
1✔
1031
                    print(parentid.name, " → ")
×
1032
                end
1033
                print(pkg.name)
1✔
1034
                if comp_time > 0
1✔
1035
                    printstyled(" ", Ryu.writefixed(Float64(100 * comp_time / (elapsed * 1e6)), 2), "% compilation time", color = Base.info_color())
×
1036
                end
1037
                if recomp_time > 0
1✔
1038
                    perc = Float64(100 * recomp_time / comp_time)
×
1039
                    printstyled(" (", perc < 1 ? "<1" : Ryu.writefixed(perc, 0), "% recompilation)", color = Base.warn_color())
×
1040
                end
1041
                println()
1✔
1042
            end
1043
            return M
448✔
1044
        end
1045
    end
81✔
1046
    return ErrorException("Required dependency $pkg failed to load from a cache file.")
1✔
1047

1048
    finally
1049
        timing_imports && cumulative_compile_timing(false)
449✔
1050
    end
1051
end
1052

1053
function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
449✔
1054
    # This function is also used by PkgCacheInspector.jl
1055
    restored = sv[1]::Vector{Any}
449✔
1056
    for M in restored
449✔
1057
        M = M::Module
528✔
1058
        if isdefined(M, Base.Docs.META) && getfield(M, Base.Docs.META) !== nothing
528✔
1059
            push!(Base.Docs.modules, M)
322✔
1060
        end
1061
        if parentmodule(M) === M
528✔
1062
            register_root_module(M)
449✔
1063
        end
1064
    end
977✔
1065

1066
    # Register this cache path now - If Requires.jl is loaded, Revise may end
1067
    # up looking at the cache path during the init callback.
1068
    get!(PkgOrigin, pkgorigins, pkg).cachepath = path
449✔
1069

1070
    inits = sv[2]::Vector{Any}
449✔
1071
    if !isempty(inits)
449✔
1072
        unlock(require_lock) # temporarily _unlock_ during these callbacks
291✔
1073
        try
152✔
1074
            for (i, mod) in pairs(inits)
304✔
1075
                run_module_init(mod, i)
152✔
1076
            end
152✔
1077
        finally
1078
            lock(require_lock)
152✔
1079
        end
1080
    end
1081
    return restored
449✔
1082
end
1083

1084
function run_module_init(mod::Module, i::Int=1)
152✔
1085
    # `i` informs ordering for the `@time_imports` report formatting
1086
    if TIMING_IMPORTS[] == 0
152✔
1087
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
152✔
1088
    else
1089
        if isdefined(mod, :__init__)
×
1090
            connector = i > 1 ? "├" : "┌"
×
1091
            printstyled("               $connector ", color = :light_black)
×
1092

1093
            elapsedtime = time_ns()
×
1094
            cumulative_compile_timing(true)
×
1095
            compile_elapsedtimes = cumulative_compile_time_ns()
×
1096

1097
            ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
×
1098

1099
            elapsedtime = (time_ns() - elapsedtime) / 1e6
×
1100
            cumulative_compile_timing(false);
×
1101
            comp_time, recomp_time = (cumulative_compile_time_ns() .- compile_elapsedtimes) ./ 1e6
×
1102

1103
            print(round(elapsedtime, digits=1), " ms $mod.__init__() ")
×
1104
            if comp_time > 0
×
1105
                printstyled(Ryu.writefixed(Float64(100 * comp_time / elapsedtime), 2), "% compilation time", color = Base.info_color())
×
1106
            end
1107
            if recomp_time > 0
×
1108
                perc = Float64(100 * recomp_time / comp_time)
×
1109
                printstyled(" (", perc < 1 ? "<1" : Ryu.writefixed(perc, 0), "% recompilation)", color = Base.warn_color())
×
1110
            end
1111
            println()
×
1112
        end
1113
    end
1114
end
1115

1116
function run_package_callbacks(modkey::PkgId)
458✔
1117
    run_extension_callbacks(modkey)
458✔
1118
    assert_havelock(require_lock)
916✔
1119
    unlock(require_lock)
910✔
1120
    try
458✔
1121
        for callback in package_callbacks
737✔
1122
            invokelatest(callback, modkey)
205✔
1123
        end
384✔
1124
    catch
1125
        # Try to continue loading if a callback errors
1126
        errs = current_exceptions()
×
1127
        @error "Error during package callback" exception=errs
458✔
1128
    finally
1129
        lock(require_lock)
458✔
1130
    end
1131
    nothing
458✔
1132
end
1133

1134

1135
##############
1136
# Extensions #
1137
##############
1138

1139
mutable struct ExtensionId
1140
    const id::PkgId
17✔
1141
    const parentid::PkgId # just need the name, for printing
1142
    ntriggers::Int # how many more packages must be defined until this is loaded
1143
end
1144

1145
const EXT_PRIMED = Dict{PkgId, PkgId}() # Extension -> Parent
1146
const EXT_DORMITORY = Dict{PkgId,Vector{ExtensionId}}() # Trigger -> Extensions that can be triggered by it
1147
const EXT_DORMITORY_FAILED = ExtensionId[]
1148

1149
function insert_extension_triggers(pkg::PkgId)
1✔
1150
    pkg.uuid === nothing && return
457✔
1151
    path_env_loc = locate_package_env(pkg)
369✔
1152
    path_env_loc === nothing && return
×
1153
    path, env_loc = path_env_loc
369✔
1154
    if path === nothing || env_loc === nothing
738✔
1155
        return
×
1156
    end
1157
    insert_extension_triggers(env_loc, pkg)
369✔
1158
end
1159

1160
function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing}
370✔
1161
    project_file = env_project_file(env)
370✔
1162
    if project_file isa String
370✔
1163
        manifest_file = project_file_manifest_path(project_file)
85✔
1164
        manifest_file === nothing && return
85✔
1165
        d = get_deps(parsed_toml(manifest_file))
145✔
1166
        for (dep_name, entries) in d
170✔
1167
            entries::Vector{Any}
250✔
1168
            for entry in entries
250✔
1169
                entry = entry::Dict{String, Any}
259✔
1170
                uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
518✔
1171
                uuid === nothing && continue
259✔
1172
                if UUID(uuid) == pkg.uuid
518✔
1173
                    weakdeps = get(entry, "weakdeps", nothing)::Union{Nothing, Vector{String}, Dict{String,Any}}
64✔
1174
                    extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
64✔
1175
                    extensions === nothing && return
54✔
1176
                    weakdeps === nothing && return
10✔
1177
                    if weakdeps isa Dict{String, Any}
10✔
1178
                        return _insert_extension_triggers(pkg, extensions, weakdeps)
×
1179
                    end
1180

1181
                    d_weakdeps = Dict{String, String}()
10✔
1182
                    for (dep_name, entries) in d
20✔
1183
                        dep_name in weakdeps || continue
86✔
1184
                        entries::Vector{Any}
19✔
1185
                        if length(entries) != 1
19✔
1186
                            error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
×
1187
                        end
1188
                        entry = first(entries)::Dict{String, Any}
19✔
1189
                        uuid = entry["uuid"]::String
19✔
1190
                        d_weakdeps[dep_name] = uuid
19✔
1191
                    end
68✔
1192
                    @assert length(d_weakdeps) == length(weakdeps)
10✔
1193
                    return _insert_extension_triggers(pkg, extensions, d_weakdeps)
10✔
1194
                end
1195
            end
205✔
1196
        end
361✔
1197
    end
1198
    return nothing
316✔
1199
end
1200

1201
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any})
10✔
1202
    for (ext::String, triggers::Union{String, Vector{String}}) in extensions
10✔
1203
        triggers isa String && (triggers = [triggers])
19✔
1204
        id = PkgId(uuid5(parent.uuid, ext), ext)
19✔
1205
        if id in keys(EXT_PRIMED) || haskey(Base.loaded_modules, id)
38✔
1206
            continue  # extension is already primed or loaded, don't add it again
×
1207
        end
1208
        EXT_PRIMED[id] = parent
17✔
1209
        gid = ExtensionId(id, parent, 1 + length(triggers))
17✔
1210
        trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent)
17✔
1211
        push!(trigger1, gid)
17✔
1212
        for trigger in triggers
17✔
1213
            # TODO: Better error message if this lookup fails?
1214
            uuid_trigger = UUID(weakdeps[trigger]::String)
25✔
1215
            trigger_id = PkgId(uuid_trigger, trigger)
25✔
1216
            if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
26✔
1217
                trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
24✔
1218
                push!(trigger1, gid)
24✔
1219
            else
1220
                gid.ntriggers -= 1
1✔
1221
            end
1222
        end
25✔
1223
    end
19✔
1224
end
1225

1226
loading_extension::Bool = false
1227
function run_extension_callbacks(extid::ExtensionId)
15✔
1228
    assert_havelock(require_lock)
30✔
1229
    succeeded = try
15✔
1230
        # Used by Distributed to now load extensions in the package callback
1231
        global loading_extension = true
15✔
1232
        _require_prelocked(extid.id)
15✔
1233
        @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded"
15✔
1234
        true
15✔
1235
    catch
1236
        # Try to continue loading if loading an extension errors
1237
        errs = current_exceptions()
×
1238
        @error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name), \
×
1239
                use `Base.retry_load_extensions()` to retry." exception=errs
1240
        false
15✔
1241
    finally
1242
        global loading_extension = false
15✔
1243
    end
1244
    return succeeded
15✔
1245
end
1246

1247
function run_extension_callbacks(pkgid::PkgId)
458✔
1248
    assert_havelock(require_lock)
916✔
1249
    # take ownership of extids that depend on this pkgid
1250
    extids = pop!(EXT_DORMITORY, pkgid, nothing)
891✔
1251
    extids === nothing && return
458✔
1252
    for extid in extids
25✔
1253
        if extid.ntriggers > 0
39✔
1254
            # It is possible that pkgid was loaded in an environment
1255
            # below the one of the parent. This will cause a load failure when the
1256
            # pkg ext tries to load the triggers. Therefore, check this first
1257
            # before loading the pkg ext.
1258
            pkgenv = identify_package_env(extid.id, pkgid.name)
39✔
1259
            ext_not_allowed_load = false
×
1260
            if pkgenv === nothing
39✔
1261
                ext_not_allowed_load = true
×
1262
            else
1263
                pkg, env = pkgenv
39✔
1264
                path = locate_package(pkg, env)
78✔
1265
                if path === nothing
39✔
1266
                    ext_not_allowed_load = true
×
1267
                end
1268
            end
1269
            if ext_not_allowed_load
39✔
1270
                @debug "Extension $(extid.id.name) of $(extid.parentid.name) will not be loaded \
×
1271
                        since $(pkgid.name) loaded in environment lower in load path"
1272
                # indicate extid is expected to fail
1273
                extid.ntriggers *= -1
×
1274
            else
1275
                # indicate pkgid is loaded
1276
                extid.ntriggers -= 1
39✔
1277
            end
1278
        end
1279
        if extid.ntriggers < 0
39✔
1280
            # indicate pkgid is loaded
1281
            extid.ntriggers += 1
×
1282
            succeeded = false
×
1283
        else
1284
            succeeded = true
×
1285
        end
1286
        if extid.ntriggers == 0
39✔
1287
            # actually load extid, now that all dependencies are met,
1288
            # and record the result
1289
            succeeded = succeeded && run_extension_callbacks(extid)
15✔
1290
            succeeded || push!(EXT_DORMITORY_FAILED, extid)
15✔
1291
        end
1292
    end
64✔
1293
    return
25✔
1294
end
1295

1296
"""
1297
    retry_load_extensions()
1298

1299
Loads all the (not yet loaded) extensions that have their extension-dependencies loaded.
1300
This is used in cases where the automatic loading of an extension failed
1301
due to some problem with the extension. Instead of restarting the Julia session,
1302
the extension can be fixed, and this function run.
1303
"""
1304
function retry_load_extensions()
×
1305
    @lock require_lock begin
×
1306
    # this copy is desired since run_extension_callbacks will release this lock
1307
    # so this can still mutate the list to drop successful ones
1308
    failed = copy(EXT_DORMITORY_FAILED)
×
1309
    empty!(EXT_DORMITORY_FAILED)
×
1310
    filter!(failed) do extid
×
1311
        return !run_extension_callbacks(extid)
×
1312
    end
1313
    prepend!(EXT_DORMITORY_FAILED, failed)
×
1314
    end
1315
    return
×
1316
end
1317

1318
"""
1319
    get_extension(parent::Module, extension::Symbol)
1320

1321
Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded.
1322
"""
1323
get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext)
22✔
1324
function get_extension(parentid::PkgId, ext::Symbol)
23✔
1325
    parentid.uuid === nothing && return nothing
23✔
1326
    extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext))
23✔
1327
    return get(loaded_modules, extid, nothing)
23✔
1328
end
1329

1330
# End extensions
1331

1332
# loads a precompile cache file, after checking stale_cachefile tests
1333
function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128)
2,744✔
1334
    assert_havelock(require_lock)
5,488✔
1335
    loaded = nothing
×
1336
    if root_module_exists(modkey)
2,744✔
1337
        loaded = root_module(modkey)
2,720✔
1338
    else
1339
        loaded = start_loading(modkey)
24✔
1340
        if loaded === nothing
24✔
1341
            try
24✔
1342
                modpath = locate_package(modkey)
24✔
1343
                modpath === nothing && return nothing
24✔
1344
                set_pkgorigin_version_path(modkey, String(modpath))
24✔
1345
                loaded = _require_search_from_serialized(modkey, String(modpath), build_id)
24✔
1346
            finally
1347
                end_loading(modkey, loaded)
24✔
1348
            end
1349
            if loaded isa Module
24✔
1350
                insert_extension_triggers(modkey)
42✔
1351
                run_package_callbacks(modkey)
24✔
1352
            end
1353
        end
1354
    end
1355
    if !(loaded isa Module) || PkgId(loaded) != modkey
5,488✔
1356
        return ErrorException("Required dependency $modkey failed to load from a cache file.")
×
1357
    end
1358
    return loaded
2,744✔
1359
end
1360

1361
# loads a precompile cache file, ignoring stale_cachefile tests
1362
# assuming all depmods are already loaded and everything is valid
1363
function _tryrequire_from_serialized(modkey::PkgId, path::String, ocachepath::Union{Nothing, String}, sourcepath::String, depmods::Vector{Any})
22✔
1364
    assert_havelock(require_lock)
44✔
1365
    loaded = nothing
×
1366
    if root_module_exists(modkey)
22✔
1367
        loaded = root_module(modkey)
4✔
1368
    else
1369
        loaded = start_loading(modkey)
18✔
1370
        if loaded === nothing
18✔
1371
            try
18✔
1372
                for i in 1:length(depmods)
36✔
1373
                    dep = depmods[i]
690✔
1374
                    dep isa Module && continue
690✔
1375
                    _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128}
5✔
1376
                    @assert root_module_exists(depkey)
5✔
1377
                    dep = root_module(depkey)
5✔
1378
                    depmods[i] = dep
5✔
1379
                end
1,362✔
1380
                set_pkgorigin_version_path(modkey, sourcepath)
18✔
1381
                loaded = _include_from_serialized(modkey, path, ocachepath, depmods)
18✔
1382
            finally
1383
                end_loading(modkey, loaded)
18✔
1384
            end
1385
            if loaded isa Module
18✔
1386
                insert_extension_triggers(modkey)
30✔
1387
                run_package_callbacks(modkey)
18✔
1388
            end
1389
        end
1390
    end
1391
    if !(loaded isa Module) || PkgId(loaded) != modkey
44✔
1392
        return ErrorException("Required dependency $modkey failed to load from a cache file.")
×
1393
    end
1394
    return loaded
22✔
1395
end
1396

1397
# loads a precompile cache file, ignoring stale_cachefile tests
1398
# load the best available (non-stale) version of all dependent modules first
1399
function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String})
70✔
1400
    assert_havelock(require_lock)
140✔
1401
    local depmodnames
×
1402
    io = open(path, "r")
70✔
1403
    try
70✔
1404
        iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.")
70✔
1405
        _, _, depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io)
70✔
1406
        pkgimage = !isempty(clone_targets)
70✔
1407
        if pkgimage
70✔
1408
            ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided")
×
1409
            isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.")
×
1410
            ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile")
×
1411
            # TODO: Check for valid clone_targets?
1412
            isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.")
×
1413
        else
1414
            @assert ocachepath === nothing
×
1415
        end
1416
        isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.")
70✔
1417
    finally
1418
        close(io)
70✔
1419
    end
1420
    ndeps = length(depmodnames)
70✔
1421
    depmods = Vector{Any}(undef, ndeps)
70✔
1422
    for i in 1:ndeps
140✔
1423
        modkey, build_id = depmodnames[i]
2,706✔
1424
        dep = _tryrequire_from_serialized(modkey, build_id)
2,706✔
1425
        if !isa(dep, Module)
2,706✔
1426
            return dep
×
1427
        end
1428
        depmods[i] = dep
2,706✔
1429
    end
5,342✔
1430
    # then load the file
1431
    return _include_from_serialized(pkg, path, ocachepath, depmods)
70✔
1432
end
1433

1434
# returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it
1435
# returns the set of modules restored if the cache load succeeded
1436
@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128)
434✔
1437
    assert_havelock(require_lock)
868✔
1438
    paths = find_all_in_cache_path(pkg)
434✔
1439
    for path_to_try in paths::Vector{String}
488✔
1440
        staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try)
396✔
1441
        if staledeps === true
396✔
1442
            continue
35✔
1443
        end
1444
        staledeps, ocachefile = staledeps::Tuple{Vector{Any}, Union{Nothing, String}}
361✔
1445
        # finish checking staledeps module graph
1446
        for i in 1:length(staledeps)
722✔
1447
            dep = staledeps[i]
13,808✔
1448
            dep isa Module && continue
13,808✔
1449
            modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
22✔
1450
            modpaths = find_all_in_cache_path(modkey)
22✔
1451
            for modpath_to_try in modpaths
22✔
1452
                modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try)
22✔
1453
                if modstaledeps === true
22✔
1454
                    continue
×
1455
                end
1456
                modstaledeps, modocachepath = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}}
22✔
1457
                staledeps[i] = (modpath, modkey, modpath_to_try, modstaledeps, modocachepath)
22✔
1458
                @goto check_next_dep
22✔
1459
            end
×
1460
            @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
×
1461
            @goto check_next_path
×
1462
            @label check_next_dep
×
1463
        end
27,255✔
1464
        try
361✔
1465
            touch(path_to_try) # update timestamp of precompilation file
361✔
1466
        catch ex # file might be read-only and then we fail to update timestamp, which is fine
1467
            ex isa IOError || rethrow()
×
1468
        end
1469
        # finish loading module graph into staledeps
1470
        for i in 1:length(staledeps)
722✔
1471
            dep = staledeps[i]
13,808✔
1472
            dep isa Module && continue
13,808✔
1473
            modpath, modkey, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, String, Vector{Any}, Union{Nothing, String}}
22✔
1474
            dep = _tryrequire_from_serialized(modkey, modcachepath, modocachepath, modpath, modstaledeps)
22✔
1475
            if !isa(dep, Module)
22✔
1476
                @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
×
1477
                @goto check_next_path
×
1478
            end
1479
            staledeps[i] = dep
22✔
1480
        end
27,255✔
1481
        restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps)
361✔
1482
        isa(restored, Module) && return restored
361✔
1483
        @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
×
1484
        continue
×
1485
        @label check_next_path
×
1486
    end
54✔
1487
    return nothing
73✔
1488
end
1489

1490
# to synchronize multiple tasks trying to import/using something
1491
const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}()
1492

1493
debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks.
1494
                               # This only triggers if you have multiple tasks trying to load the same package at the same time,
1495
                               # so it is unlikely to make a difference normally.
1496
function start_loading(modkey::PkgId)
469✔
1497
    # handle recursive calls to require
1498
    assert_havelock(require_lock)
938✔
1499
    loading = get(package_locks, modkey, nothing)
473✔
1500
    if loading !== nothing
469✔
1501
        # load already in progress for this module on the task
1502
        task, cond = loading
4✔
1503
        deps = String[modkey.name]
4✔
1504
        pkgid = modkey
×
1505
        assert_havelock(cond.lock)
8✔
1506
        if debug_loading_deadlocks && current_task() !== task
4✔
1507
            waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
3✔
1508
            for each in package_locks
6✔
1509
                cond2 = each[2][2]
11✔
1510
                assert_havelock(cond2.lock)
22✔
1511
                for waiting in cond2.waitq
14✔
1512
                    push!(waiters, waiting => (each[2][1] => each[1]))
3✔
1513
                end
3✔
1514
            end
19✔
1515
            while true
5✔
1516
                running = get(waiters, task, nothing)
8✔
1517
                running === nothing && break
5✔
1518
                task, pkgid = running
3✔
1519
                push!(deps, pkgid.name)
3✔
1520
                task === current_task() && break
3✔
1521
            end
2✔
1522
        end
1523
        if current_task() === task
4✔
1524
            others = String[modkey.name] # repeat this to emphasize the cycle here
2✔
1525
            for each in package_locks # list the rest of the packages being loaded too
4✔
1526
                if each[2][1] === task
8✔
1527
                    other = each[1].name
4✔
1528
                    other == modkey.name || other == pkgid.name || push!(others, other)
7✔
1529
                end
1530
            end
14✔
1531
            msg = sprint(deps, others) do io, deps, others
2✔
1532
                print(io, "deadlock detected in loading ")
2✔
1533
                join(io, deps, " -> ")
2✔
1534
                print(io, " -> ")
2✔
1535
                join(io, others, " && ")
2✔
1536
            end
1537
            throw(ConcurrencyViolationError(msg))
2✔
1538
        end
1539
        return wait(cond)
2✔
1540
    end
1541
    package_locks[modkey] = current_task() => Threads.Condition(require_lock)
465✔
1542
    return
465✔
1543
end
1544

1545
function end_loading(modkey::PkgId, @nospecialize loaded)
4✔
1546
    loading = pop!(package_locks, modkey)
465✔
1547
    notify(loading[2], loaded, all=true)
465✔
1548
    nothing
465✔
1549
end
1550

1551
# to notify downstream consumers that a module was successfully loaded
1552
# Callbacks take the form (mod::Base.PkgId) -> nothing.
1553
# WARNING: This is an experimental feature and might change later, without deprecation.
1554
const package_callbacks = Any[]
1555
# to notify downstream consumers that a file has been included into a particular module
1556
# Callbacks take the form (mod::Module, filename::String) -> nothing
1557
# WARNING: This is an experimental feature and might change later, without deprecation.
1558
const include_callbacks = Any[]
1559

1560
# used to optionally track dependencies when requiring a module:
1561
const _concrete_dependencies = Pair{PkgId,UInt128}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them
1562
const _require_dependencies = Any[] # a list of (mod, path, mtime) tuples that are the file dependencies of the module currently being precompiled
1563
const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies
1564
function _include_dependency(mod::Module, _path::AbstractString)
779✔
1565
    prev = source_path(nothing)
1,114✔
1566
    if prev === nothing
779✔
1567
        path = abspath(_path)
335✔
1568
    else
1569
        path = normpath(joinpath(dirname(prev), _path))
444✔
1570
    end
1571
    if _track_dependencies[]
779✔
1572
        @lock require_lock begin
360✔
1573
        push!(_require_dependencies, (mod, path, mtime(path)))
180✔
1574
        end
1575
    end
1576
    return path, prev
779✔
1577
end
1578

1579
"""
1580
    include_dependency(path::AbstractString)
1581

1582
In a module, declare that the file, directory, or symbolic link specified by `path`
1583
(relative or absolute) is a dependency for precompilation; that is, the module will need
1584
to be recompiled if the modification time of `path` changes.
1585

1586
This is only needed if your module depends on a path that is not used via [`include`](@ref). It has
1587
no effect outside of compilation.
1588
"""
1589
function include_dependency(path::AbstractString)
23✔
1590
    _include_dependency(Main, path)
23✔
1591
    return nothing
23✔
1592
end
1593

1594
# we throw PrecompilableError when a module doesn't want to be precompiled
1595
struct PrecompilableError <: Exception end
1596
function show(io::IO, ex::PrecompilableError)
×
1597
    print(io, "Declaring __precompile__(false) is not allowed in files that are being precompiled.")
×
1598
end
1599
precompilableerror(ex::PrecompilableError) = true
×
1600
precompilableerror(ex::WrappedException) = precompilableerror(ex.error)
6✔
1601
precompilableerror(@nospecialize ex) = false
×
1602

1603
# Call __precompile__(false) at the top of a tile prevent it from being precompiled (false)
1604
"""
1605
    __precompile__(isprecompilable::Bool)
1606

1607
Specify whether the file calling this function is precompilable, defaulting to `true`.
1608
If a module or file is *not* safely precompilable, it should call `__precompile__(false)` in
1609
order to throw an error if Julia attempts to precompile it.
1610
"""
1611
@noinline function __precompile__(isprecompilable::Bool=true)
4✔
1612
    if !isprecompilable && ccall(:jl_generating_output, Cint, ()) != 0
4✔
1613
        throw(PrecompilableError())
2✔
1614
    end
1615
    nothing
2✔
1616
end
1617

1618
# require always works in Main scope and loads files from node 1
1619
const toplevel_load = Ref(true)
1620

1621
"""
1622
    require(into::Module, module::Symbol)
1623

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

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

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

1639
For more details regarding code loading, see the manual sections on [modules](@ref modules) and
1640
[parallel computing](@ref code-availability).
1641
"""
1642
function require(into::Module, mod::Symbol)
1,585✔
1643
    @lock require_lock begin
1,584✔
1644
    LOADING_CACHE[] = LoadingCache()
1,585✔
1645
    try
1,585✔
1646
        uuidkey_env = identify_package_env(into, String(mod))
1,585✔
1647
        # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env")
1648
        if uuidkey_env === nothing
1,585✔
1649
            where = PkgId(into)
×
1650
            if where.uuid === nothing
×
1651
                hint, dots = begin
×
1652
                    if isdefined(into, mod) && getfield(into, mod) isa Module
×
1653
                        true, "."
×
1654
                    elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
×
1655
                        true, ".."
×
1656
                    else
1657
                        false, ""
×
1658
                    end
1659
                end
1660
                hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
×
1661
                start_sentence = hint ? "Otherwise, run" : "Run"
×
1662
                throw(ArgumentError("""
×
1663
                    Package $mod not found in current path$hint_message.
1664
                    - $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package."""))
1665
            else
1666
                throw(ArgumentError("""
×
1667
                Package $(where.name) does not have $mod in its dependencies:
1668
                - You may have a partially installed environment. Try `Pkg.instantiate()`
1669
                  to ensure all packages in the environment are installed.
1670
                - Or, if you have $(where.name) checked out for development and have
1671
                  added $mod as a dependency but haven't updated your primary
1672
                  environment's manifest file, try `Pkg.resolve()`.
1673
                - Otherwise you may need to report an issue with $(where.name)"""))
1674
            end
1675
        end
1676
        uuidkey, env = uuidkey_env
1,585✔
1677
        if _track_dependencies[]
1,585✔
1678
            push!(_require_dependencies, (into, binpack(uuidkey), 0.0))
137✔
1679
        end
1680
        return _require_prelocked(uuidkey, env)
3,159✔
1681
    finally
1682
        LOADING_CACHE[] = nothing
1,585✔
1683
    end
1684
    end
1685
end
1686

1687
require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
100✔
1688

1689
const REPL_PKGID = PkgId(UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL")
1690

1691
function _require_prelocked(uuidkey::PkgId, env=nothing)
1,715✔
1692
    assert_havelock(require_lock)
3,515✔
1693
    if !root_module_exists(uuidkey)
1,700✔
1694
        newm = _require(uuidkey, env)
418✔
1695
        if newm === nothing
415✔
1696
            error("package `$(uuidkey.name)` did not define the expected \
1✔
1697
                  module `$(uuidkey.name)`, check for typos in package module name")
1698
        end
1699
        insert_extension_triggers(uuidkey)
753✔
1700
        # After successfully loading, notify downstream consumers
1701
        run_package_callbacks(uuidkey)
414✔
1702
        if uuidkey == REPL_PKGID
828✔
1703
            REPL_MODULE_REF[] = newm
×
1704
        end
1705
    else
1706
        newm = root_module(uuidkey)
1,282✔
1707
    end
1708
    return newm
1,696✔
1709
end
1710

1711
mutable struct PkgOrigin
1712
    path::Union{String,Nothing}
577✔
1713
    cachepath::Union{String,Nothing}
1714
    version::Union{VersionNumber,Nothing}
1715
end
1716
PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
577✔
1717
const pkgorigins = Dict{PkgId,PkgOrigin}()
1718

1719
const loaded_modules = Dict{PkgId,Module}()
1720
const loaded_modules_order = Vector{Module}()
1721
const module_keys = IdDict{Module,PkgId}() # the reverse
1722

1723
is_root_module(m::Module) = @lock require_lock haskey(module_keys, m)
72,844✔
1724
root_module_key(m::Module) = @lock require_lock module_keys[m]
57,761✔
1725

1726
@constprop :none function register_root_module(m::Module)
573✔
1727
    # n.b. This is called from C after creating a new module in `Base.__toplevel__`,
1728
    # instead of adding them to the binding table there.
1729
    @lock require_lock begin
697✔
1730
    key = PkgId(m, String(nameof(m)))
989✔
1731
    if haskey(loaded_modules, key)
573✔
1732
        oldm = loaded_modules[key]
2✔
1733
        if oldm !== m
2✔
1734
            if (0 != ccall(:jl_generating_output, Cint, ())) && (JLOptions().incremental != 0)
2✔
1735
                error("Replacing module `$(key.name)`")
×
1736
            else
1737
                @warn "Replacing module `$(key.name)`"
2✔
1738
            end
1739
        end
1740
    end
1741
    push!(loaded_modules_order, m)
573✔
1742
    loaded_modules[key] = m
573✔
1743
    module_keys[m] = key
573✔
1744
    end
1745
    nothing
573✔
1746
end
1747

1748
register_root_module(Core)
1749
register_root_module(Base)
1750
register_root_module(Main)
1751

1752
# This is used as the current module when loading top-level modules.
1753
# It has the special behavior that modules evaluated in it get added
1754
# to the loaded_modules table instead of getting bindings.
1755
baremodule __toplevel__
1756
using Base
1757
end
1758

1759
# get a top-level Module from the given key
1760
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
76,995✔
1761
function root_module(where::Module, name::Symbol)
46✔
1762
    key = identify_package(where, String(name))
91✔
1763
    key isa PkgId || throw(KeyError(name))
47✔
1764
    return root_module(key)
45✔
1765
end
1766
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
627✔
1767

1768
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
19,635✔
1769
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
257✔
1770

1771
function unreference_module(key::PkgId)
1✔
1772
    if haskey(loaded_modules, key)
1✔
1773
        m = pop!(loaded_modules, key)
1✔
1774
        # need to ensure all modules are GC rooted; will still be referenced
1775
        # in module_keys
1776
    end
1777
end
1778

1779
# whoever takes the package_locks[pkg] must call this function immediately
1780
function set_pkgorigin_version_path(pkg::PkgId, path::Union{String,Nothing})
462✔
1781
    assert_havelock(require_lock)
924✔
1782
    pkgorigin = get!(PkgOrigin, pkgorigins, pkg)
462✔
1783
    if path !== nothing
462✔
1784
        # Pkg needs access to the version of packages in the sysimage.
1785
        if Core.Compiler.generating_sysimg()
865✔
1786
            pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), ".."))
×
1787
        end
1788
    end
1789
    pkgorigin.path = path
462✔
1790
    nothing
462✔
1791
end
1792

1793
# A hook to allow code load to use Pkg.precompile
1794
const PKG_PRECOMPILE_HOOK = Ref{Function}()
1795

1796
# Returns `nothing` or the new(ish) module
1797
function _require(pkg::PkgId, env=nothing)
419✔
1798
    assert_havelock(require_lock)
838✔
1799
    loaded = start_loading(pkg)
419✔
1800
    loaded === nothing || return loaded
419✔
1801

1802
    last = toplevel_load[]
419✔
1803
    try
419✔
1804
        toplevel_load[] = false
419✔
1805
        # perform the search operation to select the module file require intends to load
1806
        path = locate_package(pkg, env)
419✔
1807
        if path === nothing
419✔
1808
            throw(ArgumentError("""
×
1809
                Package $pkg is required but does not seem to be installed:
1810
                 - Run `Pkg.instantiate()` to install all recorded dependencies.
1811
                """))
1812
        end
1813
        set_pkgorigin_version_path(pkg, path)
419✔
1814

1815
        pkg_precompile_attempted = false # being safe to avoid getting stuck in a Pkg.precompile loop
×
1816

1817
        # attempt to load the module file via the precompile cache locations
1818
        if JLOptions().use_compiled_modules != 0
419✔
1819
            @label load_from_cache
×
1820
            m = _require_search_from_serialized(pkg, path, UInt128(0))
410✔
1821
            if m isa Module
410✔
1822
                return m
337✔
1823
            end
1824
        end
1825

1826
        # if the module being required was supposed to have a particular version
1827
        # but it was not handled by the precompile loader, complain
1828
        for (concrete_pkg, concrete_build_id) in _concrete_dependencies
146✔
1829
            if pkg == concrete_pkg
2,057✔
1830
                @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
×
1831
                     This may mean $pkg does not support precompilation but is imported by a module that does."""
1832
                if JLOptions().incremental != 0
×
1833
                    # during incremental precompilation, this should be fail-fast
1834
                    throw(PrecompilableError())
×
1835
                end
1836
            end
1837
        end
1,126✔
1838

1839
        if JLOptions().use_compiled_modules != 0
82✔
1840
            if (0 == ccall(:jl_generating_output, Cint, ())) || (JLOptions().incremental != 0)
91✔
1841
                if !pkg_precompile_attempted && isinteractive() && isassigned(PKG_PRECOMPILE_HOOK)
73✔
1842
                    pkg_precompile_attempted = true
×
1843
                    unlock(require_lock)
×
1844
                    try
×
1845
                        PKG_PRECOMPILE_HOOK[](pkg.name, _from_loading = true)
×
1846
                    finally
1847
                        lock(require_lock)
×
1848
                    end
1849
                    @goto load_from_cache
×
1850
                end
1851
                # spawn off a new incremental pre-compile task for recursive `require` calls
1852
                cachefile = compilecache(pkg, path)
73✔
1853
                if isa(cachefile, Exception)
70✔
1854
                    if precompilableerror(cachefile)
×
1855
                        verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
1✔
1856
                        @logmsg verbosity "Skipping precompilation since __precompile__(false). Importing $pkg."
1✔
1857
                    else
1858
                        @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m
1✔
1859
                    end
1860
                    # fall-through to loading the file locally
1861
                else
1862
                    cachefile, ocachefile = cachefile::Tuple{String, Union{Nothing, String}}
69✔
1863
                    m = _tryrequire_from_serialized(pkg, cachefile, ocachefile)
69✔
1864
                    if !isa(m, Module)
69✔
1865
                        @warn "The call to compilecache failed to create a usable precompiled cache file for $pkg" exception=m
1✔
1866
                    else
1867
                        return m
68✔
1868
                    end
1869
                end
1870
            end
1871
        end
1872

1873
        # just load the file normally via include
1874
        # for unknown dependencies
1875
        uuid = pkg.uuid
11✔
1876
        uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
18✔
1877
        old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
11✔
1878
        if uuid !== old_uuid
11✔
1879
            ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
7✔
1880
        end
1881
        unlock(require_lock)
22✔
1882
        try
11✔
1883
            include(__toplevel__, path)
11✔
1884
            loaded = get(loaded_modules, pkg, nothing)
21✔
1885
        finally
1886
            lock(require_lock)
11✔
1887
            if uuid !== old_uuid
11✔
1888
                ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
10✔
1889
            end
1890
        end
1891
    finally
1892
        toplevel_load[] = last
419✔
1893
        end_loading(pkg, loaded)
419✔
1894
    end
1895
    return loaded
11✔
1896
end
1897

1898
# Only used from test/precompile.jl
1899
function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing})
1✔
1900
    @lock require_lock begin
1✔
1901
    set_pkgorigin_version_path(uuidkey, nothing)
2✔
1902
    newm = _tryrequire_from_serialized(uuidkey, path, ocachepath)
1✔
1903
    newm isa Module || throw(newm)
1✔
1904
    insert_extension_triggers(uuidkey)
1✔
1905
    # After successfully loading, notify downstream consumers
1906
    run_package_callbacks(uuidkey)
1✔
1907
    return newm
1✔
1908
    end
1909
end
1910

1911

1912

1913
# relative-path load
1914

1915
"""
1916
    include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")
1917

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

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

1924
!!! compat "Julia 1.5"
1925
    Julia 1.5 is required for passing the `mapexpr` argument.
1926
"""
1927
function include_string(mapexpr::Function, mod::Module, code::AbstractString,
896✔
1928
                        filename::AbstractString="string")
1929
    loc = LineNumberNode(1, Symbol(filename))
896✔
1930
    try
895✔
1931
        ast = Meta.parseall(code, filename=filename)
895✔
1932
        @assert Meta.isexpr(ast, :toplevel)
895✔
1933
        result = nothing
3✔
1934
        line_and_ex = Expr(:toplevel, loc, nothing)
895✔
1935
        for ex in ast.args
901✔
1936
            if ex isa LineNumberNode
34,744✔
1937
                loc = ex
17,372✔
1938
                line_and_ex.args[1] = ex
17,372✔
1939
                continue
17,372✔
1940
            end
1941
            ex = mapexpr(ex)
8✔
1942
            # Wrap things to be eval'd in a :toplevel expr to carry line
1943
            # information as part of the expr.
1944
            line_and_ex.args[2] = ex
17,372✔
1945
            result = Core.eval(mod, line_and_ex)
17,372✔
1946
        end
35,497✔
1947
        return result
867✔
1948
    catch exc
1949
        # TODO: Now that stacktraces are more reliable we should remove
1950
        # LoadError and expose the real error type directly.
1951
        rethrow(LoadError(filename, loc.line, exc))
40✔
1952
    end
1953
end
1954

1955
include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
164✔
1956
    include_string(identity, m, txt, fname)
1957

1958
function source_path(default::Union{AbstractString,Nothing}="")
17✔
1959
    s = current_task().storage
915✔
1960
    if s !== nothing
904✔
1961
        s = s::IdDict{Any,Any}
870✔
1962
        if haskey(s, :SOURCE_PATH)
1,420✔
1963
            return s[:SOURCE_PATH]::Union{Nothing,String}
550✔
1964
        end
1965
    end
1966
    return default
354✔
1967
end
1968

1969
function source_dir()
1✔
1970
    p = source_path(nothing)
1✔
1971
    return p === nothing ? pwd() : dirname(p)
1✔
1972
end
1973

1974
"""
1975
    Base.include([mapexpr::Function,] m::Module, path::AbstractString)
1976

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

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

1989
!!! compat "Julia 1.5"
1990
    Julia 1.5 is required for passing the `mapexpr` argument.
1991
"""
1992
Base.include # defined in Base.jl
1993

1994
# Full include() implementation which is used after bootstrap
1995
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
756✔
1996
    @noinline # Workaround for module availability in _simplify_include_frames
×
1997
    path, prev = _include_dependency(mod, _path)
756✔
1998
    for callback in include_callbacks # to preserve order, must come before eval in include_string
1,511✔
1999
        invokelatest(callback, mod, path)
1✔
2000
    end
2✔
2001
    code = read(path, String)
756✔
2002
    tls = task_local_storage()
752✔
2003
    tls[:SOURCE_PATH] = path
752✔
2004
    try
752✔
2005
        return include_string(mapexpr, mod, code, path)
774✔
2006
    finally
2007
        if prev === nothing
726✔
2008
            delete!(tls, :SOURCE_PATH)
616✔
2009
        else
2010
            tls[:SOURCE_PATH] = prev
1,144✔
2011
        end
2012
    end
2013
end
2014

2015
"""
2016
    evalfile(path::AbstractString, args::Vector{String}=String[])
2017

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

2023
# Example
2024

2025
```jldoctest
2026
julia> write("testfile.jl", \"\"\"
2027
           @show ARGS
2028
           1 + 1
2029
       \"\"\");
2030

2031
julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]);
2032
ARGS = ["ARG1", "ARG2"]
2033

2034
julia> x
2035
2
2036

2037
julia> rm("testfile.jl")
2038
```
2039
"""
2040
function evalfile(path::AbstractString, args::Vector{String}=String[])
×
2041
    return Core.eval(Module(:__anon__),
×
2042
        Expr(:toplevel,
2043
             :(const ARGS = $args),
2044
             :(eval(x) = $(Expr(:core, :eval))(__anon__, x)),
×
2045
             :(include(x) = $(Expr(:top, :include))(__anon__, x)),
×
2046
             :(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, __anon__, x)),
×
2047
             :(include($path))))
2048
end
2049
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
×
2050

2051
function load_path_setup_code(load_path::Bool=true)
2✔
2052
    code = """
2✔
2053
    append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH))))
2054
    append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH))))
2055
    """
2056
    if load_path
1✔
2057
        load_path = map(abspath, Base.load_path())
1✔
2058
        path_sep = Sys.iswindows() ? ';' : ':'
×
2059
        any(path -> path_sep in path, load_path) &&
5✔
2060
            error("LOAD_PATH entries cannot contain $(repr(path_sep))")
2061
        code *= """
1✔
2062
        append!(empty!(Base.LOAD_PATH), $(repr(load_path)))
2063
        ENV["JULIA_LOAD_PATH"] = $(repr(join(load_path, Sys.iswindows() ? ';' : ':')))
2064
        Base.set_active_project(nothing)
2065
        """
2066
    end
2067
    return code
1✔
2068
end
2069

2070
# this is called in the external process that generates precompiled package files
2071
function include_package_for_output(pkg::PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String},
117✔
2072
                                    concrete_deps::typeof(_concrete_dependencies), source::Union{Nothing,String})
2073
    append!(empty!(Base.DEPOT_PATH), depot_path)
117✔
2074
    append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
117✔
2075
    append!(empty!(Base.LOAD_PATH), load_path)
117✔
2076
    ENV["JULIA_LOAD_PATH"] = join(load_path, Sys.iswindows() ? ';' : ':')
117✔
2077
    set_active_project(nothing)
117✔
2078
    Base._track_dependencies[] = true
117✔
2079
    get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
117✔
2080
    append!(empty!(Base._concrete_dependencies), concrete_deps)
117✔
2081
    uuid_tuple = pkg.uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, pkg.uuid)
163✔
2082

2083
    ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Base.__toplevel__, uuid_tuple)
117✔
2084
    if source !== nothing
101✔
2085
        task_local_storage()[:SOURCE_PATH] = source
101✔
2086
    end
2087

2088
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred)
117✔
2089
    Core.Compiler.track_newly_inferred.x = true
117✔
2090
    try
117✔
2091
        Base.include(Base.__toplevel__, input)
122✔
2092
    catch ex
2093
        precompilableerror(ex) || rethrow()
8✔
2094
        @debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
2✔
2095
        exit(125) # we define status = 125 means PrecompileableError
117✔
2096
    finally
2097
        Core.Compiler.track_newly_inferred.x = false
115✔
2098
    end
2099
end
2100

2101
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
2102
function create_expr_cache(pkg::PkgId, input::String, output::String, output_o::Union{Nothing, String},
116✔
2103
                           concrete_deps::typeof(_concrete_dependencies), internal_stderr::IO = stderr, internal_stdout::IO = stdout)
2104
    @nospecialize internal_stderr internal_stdout
×
2105
    rm(output, force=true)   # Remove file if it exists
116✔
2106
    output_o === nothing || rm(output_o, force=true)
×
2107
    depot_path = map(abspath, DEPOT_PATH)
116✔
2108
    dl_load_path = map(abspath, DL_LOAD_PATH)
116✔
2109
    load_path = map(abspath, Base.load_path())
116✔
2110
    path_sep = Sys.iswindows() ? ';' : ':'
×
2111
    any(path -> path_sep in path, load_path) &&
742✔
2112
        error("LOAD_PATH entries cannot contain $(repr(path_sep))")
2113

2114
    deps_strs = String[]
116✔
2115
    function pkg_str(_pkg::PkgId)
6,927✔
2116
        if _pkg.uuid === nothing
6,811✔
2117
            "Base.PkgId($(repr(_pkg.name)))"
1,693✔
2118
        else
2119
            "Base.PkgId(Base.UUID(\"$(_pkg.uuid)\"), $(repr(_pkg.name)))"
5,118✔
2120
        end
2121
    end
2122
    for (pkg, build_id) in concrete_deps
121✔
2123
        push!(deps_strs, "$(pkg_str(pkg)) => $(repr(build_id))")
6,695✔
2124
    end
6,806✔
2125

2126
    if output_o !== nothing
×
2127
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
×
2128
        opt_level = Base.JLOptions().opt_level
×
2129
        opts = `-O$(opt_level) --output-o $(output_o) --output-ji $(output) --output-incremental=yes`
×
2130
    else
2131
        cpu_target = nothing
×
2132
        opts = `-O0 --output-ji $(output) --output-incremental=yes`
116✔
2133
    end
2134

2135
    deps_eltype = sprint(show, eltype(concrete_deps); context = :module=>nothing)
116✔
2136
    deps = deps_eltype * "[" * join(deps_strs, ",") * "]"
116✔
2137
    trace = isassigned(PRECOMPILE_TRACE_COMPILE) ? `--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])` : ``
232✔
2138
    io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd) $(opts)
116✔
2139
                              --startup-file=no --history-file=no --warn-overwrite=yes
2140
                              --color=$(have_color === nothing ? "auto" : have_color ? "yes" : "no")
2141
                              $trace
2142
                              -`,
2143
                              "OPENBLAS_NUM_THREADS" => 1,
2144
                              "JULIA_WAIT_FOR_TRACY" => nothing,
2145
                              "JULIA_NUM_THREADS" => 1),
2146
                       stderr = internal_stderr, stdout = internal_stdout),
2147
              "w", stdout)
2148
    # write data over stdin to avoid the (unlikely) case of exceeding max command line size
2149
    write(io.in, """
216✔
2150
        empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
2151
        Base.include_package_for_output($(pkg_str(pkg)), $(repr(abspath(input))), $(repr(depot_path)), $(repr(dl_load_path)),
2152
            $(repr(load_path)), $deps, $(repr(source_path(nothing))))
2153
        """)
2154
    close(io.in)
116✔
2155
    return io
116✔
2156
end
2157

2158
function compilecache_dir(pkg::PkgId)
×
2159
    entrypath, entryfile = cache_file_entry(pkg)
116✔
2160
    return joinpath(DEPOT_PATH[1], entrypath)
116✔
2161
end
2162

2163
function compilecache_path(pkg::PkgId, prefs_hash::UInt64)::String
122✔
2164
    entrypath, entryfile = cache_file_entry(pkg)
122✔
2165
    cachepath = joinpath(DEPOT_PATH[1], entrypath)
122✔
2166
    isdir(cachepath) || mkpath(cachepath)
126✔
2167
    if pkg.uuid === nothing
122✔
2168
        abspath(cachepath, entryfile) * ".ji"
67✔
2169
    else
2170
        crc = _crc32c(something(Base.active_project(), ""))
108✔
2171
        crc = _crc32c(unsafe_string(JLOptions().image_file), crc)
55✔
2172
        crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc)
55✔
2173
        crc = _crc32c(ccall(:jl_cache_flags, UInt8, ()), crc)
55✔
2174

2175
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
55✔
2176
        if cpu_target === nothing
55✔
2177
            cpu_target = unsafe_string(JLOptions().cpu_target)
55✔
2178
        end
2179
        crc = _crc32c(cpu_target, crc)
55✔
2180

2181
        crc = _crc32c(prefs_hash, crc)
55✔
2182
        project_precompile_slug = slug(crc, 5)
55✔
2183
        abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji"))
55✔
2184
    end
2185
end
2186

2187
"""
2188
    Base.compilecache(module::PkgId)
2189

2190
Creates a precompiled cache file for a module and all of its dependencies.
2191
This can be used to reduce package load times. Cache files are stored in
2192
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
2193
for important notes.
2194
"""
2195
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout)
76✔
2196
    @nospecialize internal_stderr internal_stdout
76✔
2197
    path = locate_package(pkg)
76✔
2198
    path === nothing && throw(ArgumentError("$pkg not found during precompilation"))
38✔
2199
    return compilecache(pkg, path, internal_stderr, internal_stdout)
38✔
2200
end
2201

2202
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
2203

2204
function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout,
154✔
2205
                      keep_loaded_modules::Bool = true)
2206

2207
    @nospecialize internal_stderr internal_stdout
38✔
2208
    # decide where to put the resulting cache file
2209
    cachepath = compilecache_dir(pkg)
227✔
2210

2211
    # build up the list of modules that we want the precompile process to preserve
2212
    concrete_deps = copy(_concrete_dependencies)
116✔
2213
    if keep_loaded_modules
116✔
2214
        for mod in loaded_modules_array()
111✔
2215
            if !(mod === Main || mod === Core || mod === Base)
11,729✔
2216
                push!(concrete_deps, PkgId(mod) => module_build_id(mod))
5,587✔
2217
            end
2218
        end
6,031✔
2219
    end
2220
    # run the expression and cache the result
2221
    verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
116✔
2222
    @logmsg verbosity "Precompiling $pkg"
116✔
2223

2224
    # create a temporary file in `cachepath` directory, write the cache in it,
2225
    # write the checksum, _and then_ atomically move the file to `cachefile`.
2226
    mkpath(cachepath)
116✔
2227
    cache_objects = JLOptions().use_pkgimages != 0
116✔
2228
    tmppath, tmpio = mktemp(cachepath)
116✔
2229

2230
    if cache_objects
116✔
2231
        tmppath_o, tmpio_o = mktemp(cachepath)
×
2232
        tmppath_so, tmpio_so = mktemp(cachepath)
×
2233
    else
2234
        tmppath_o = nothing
×
2235
    end
2236
    local p
×
2237
    try
116✔
2238
        close(tmpio)
116✔
2239
        if cache_objects
116✔
2240
            close(tmpio_o)
×
2241
            close(tmpio_so)
×
2242
        end
2243
        p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, internal_stderr, internal_stdout)
116✔
2244

2245
        if success(p)
116✔
2246
            if cache_objects
111✔
2247
                # Run linker over tmppath_o
2248
                Linking.link_image(tmppath_o, tmppath_so)
×
2249
            end
2250

2251
            # Read preferences hash back from .ji file (we can't precompute because
2252
            # we don't actually know what the list of compile-time preferences are without compiling)
2253
            prefs_hash = preferences_hash(tmppath)
111✔
2254
            cachefile = compilecache_path(pkg, prefs_hash)
111✔
2255
            ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing
111✔
2256

2257
            # append checksum for so to the end of the .ji file:
2258
            crc_so = UInt32(0)
111✔
2259
            if cache_objects
111✔
2260
                crc_so = open(_crc32c, tmppath_so, "r")
×
2261
            end
2262

2263
            # append extra crc to the end of the .ji file:
2264
            open(tmppath, "r+") do f
111✔
2265
                if iszero(isvalid_cache_header(f))
111✔
2266
                    error("Invalid header for $pkg in new cache file $(repr(tmppath)).")
×
2267
                end
2268
                seekend(f)
111✔
2269
                write(f, crc_so)
111✔
2270
                seekstart(f)
111✔
2271
                write(f, _crc32c(f))
111✔
2272
            end
2273

2274
            # inherit permission from the source file (and make them writable)
2275
            chmod(tmppath, filemode(path) & 0o777 | 0o200)
111✔
2276
            if cache_objects
111✔
2277
                # Ensure that the user can execute the `.so` we're generating
2278
                # Note that on windows, `filemode(path)` typically returns `0o666`, so this
2279
                # addition of the execute bit for the user is doubly needed.
2280
                chmod(tmppath_so, filemode(path) & 0o777 | 0o333)
×
2281
            end
2282

2283
            # prune the directory with cache files
2284
            if pkg.uuid !== nothing
111✔
2285
                entrypath, entryfile = cache_file_entry(pkg)
45✔
2286
                cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath))
93✔
2287
                if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[]
45✔
2288
                    idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2]
×
2289
                    evicted_cachefile = joinpath(cachepath, cachefiles[idx])
×
2290
                    @debug "Evicting file from cache" evicted_cachefile
×
2291
                    rm(evicted_cachefile; force=true)
×
2292
                    try
×
2293
                        rm(ocachefile_from_cachefile(evicted_cachefile); force=true)
×
2294
                        @static if Sys.isapple()
×
2295
                            rm(ocachefile_from_cachefile(evicted_cachefile) * ".dSYM"; force=true, recursive=true)
2296
                        end
2297
                    catch e
2298
                        e isa IOError || rethrow()
×
2299
                    end
2300
                end
2301
            end
2302

2303
            if cache_objects
111✔
2304
                try
×
2305
                    rename(tmppath_so, ocachefile::String; force=true)
×
2306
                catch e
2307
                    e isa IOError || rethrow()
×
2308
                    isfile(ocachefile::String) || rethrow()
×
2309
                    # Windows prevents renaming a file that is in use so if there is a Julia session started
2310
                    # with a package image loaded, we cannot rename that file.
2311
                    # The code belows append a `_i` to the name of the cache file where `i` is the smallest number such that
2312
                    # that cache file does not exist.
2313
                    ocachename, ocacheext = splitext(ocachefile::String)
×
2314
                    old_cachefiles = Set(readdir(cachepath))
×
2315
                    num = 1
×
2316
                    while true
×
2317
                        ocachefile = ocachename * "_$num" * ocacheext
×
2318
                        in(basename(ocachefile), old_cachefiles) || break
×
2319
                        num += 1
×
2320
                    end
×
2321
                    # TODO: Risk for a race here if some other process grabs this name before us
2322
                    cachefile = cachefile_from_ocachefile(ocachefile)
×
2323
                    rename(tmppath_so, ocachefile::String; force=true)
×
2324
                end
2325
                @static if Sys.isapple()
×
2326
                    run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull())
2327
                end
2328
            end
2329
            # this is atomic according to POSIX (not Win32):
2330
            rename(tmppath, cachefile; force=true)
111✔
2331
            return cachefile, ocachefile
116✔
2332
        end
2333
    finally
2334
        rm(tmppath, force=true)
116✔
2335
        if cache_objects
116✔
2336
            rm(tmppath_o::String, force=true)
×
2337
            rm(tmppath_so, force=true)
×
2338
        end
2339
    end
2340
    if p.exitcode == 125
5✔
2341
        return PrecompilableError()
2✔
2342
    else
2343
        error("Failed to precompile $pkg to $(repr(tmppath)).")
3✔
2344
    end
2345
end
2346

2347
function module_build_id(m::Module)
82✔
2348
    hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
20,754✔
2349
    return (UInt128(hi) << 64) | lo
20,754✔
2350
end
2351

2352
function isvalid_cache_header(f::IOStream)
732✔
2353
    pkgimage = Ref{UInt8}()
732✔
2354
    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
732✔
2355

2356
    if !iszero(checksum) && pkgimage[] != 0
732✔
2357
        @debug "Cache header was for pkgimage"
×
2358
        return UInt64(0) # We somehow read the header for a pkgimage and not a ji
×
2359
    end
2360
    return checksum
732✔
2361
end
2362
isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32))
459✔
2363

2364
function isvalid_pkgimage_crc(f::IOStream, ocachefile::String)
×
2365
    seekstart(f) # TODO necessary
×
2366
    seek(f, filesize(f) - 8)
×
2367
    expected_crc_so = read(f, UInt32)
×
2368
    crc_so = open(_crc32c, ocachefile, "r")
×
2369
    expected_crc_so == crc_so
×
2370
end
2371

2372
struct CacheHeaderIncludes
2373
    id::PkgId
2,128✔
2374
    filename::String
2375
    mtime::Float64
2376
    modpath::Vector{String}   # seemingly not needed in Base, but used by Revise
2377
end
2378

2379
function parse_cache_header(f::IO)
621✔
2380
    flags = read(f, UInt8)
621✔
2381
    modules = Vector{Pair{PkgId, UInt64}}()
621✔
2382
    while true
1,242✔
2383
        n = read(f, Int32)
1,242✔
2384
        n == 0 && break
1,242✔
2385
        sym = String(read(f, n)) # module name
621✔
2386
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
621✔
2387
        build_id = read(f, UInt64) # build UUID (mostly just a timestamp)
621✔
2388
        push!(modules, PkgId(uuid, sym) => build_id)
621✔
2389
    end
621✔
2390
    totbytes = read(f, Int64) # total bytes for file dependencies + preferences
621✔
2391
    # read the list of requirements
2392
    # and split the list into include and requires statements
2393
    includes = CacheHeaderIncludes[]
621✔
2394
    requires = Pair{PkgId, PkgId}[]
621✔
2395
    while true
4,100✔
2396
        n2 = read(f, Int32)
4,100✔
2397
        totbytes -= 4
4,100✔
2398
        if n2 == 0
4,100✔
2399
            break
621✔
2400
        end
2401
        depname = String(read(f, n2))
3,479✔
2402
        totbytes -= n2
3,479✔
2403
        mtime = read(f, Float64)
3,479✔
2404
        totbytes -= 8
3,479✔
2405
        n1 = read(f, Int32)
3,479✔
2406
        totbytes -= 4
3,479✔
2407
        # map ids to keys
2408
        modkey = (n1 == 0) ? PkgId("") : modules[n1].first
6,315✔
2409
        modpath = String[]
3,479✔
2410
        if n1 != 0
3,479✔
2411
            # determine the complete module path
2412
            while true
2,985✔
2413
                n1 = read(f, Int32)
2,985✔
2414
                totbytes -= 4
2,985✔
2415
                if n1 == 0
2,985✔
2416
                    break
2,836✔
2417
                end
2418
                push!(modpath, String(read(f, n1)))
149✔
2419
                totbytes -= n1
149✔
2420
            end
149✔
2421
        end
2422
        if depname[1] == '\0'
6,958✔
2423
            push!(requires, modkey => binunpack(depname))
1,351✔
2424
        else
2425
            push!(includes, CacheHeaderIncludes(modkey, depname, mtime, modpath))
2,128✔
2426
        end
2427
    end
3,479✔
2428
    prefs = String[]
621✔
2429
    while true
621✔
2430
        n2 = read(f, Int32)
621✔
2431
        totbytes -= 4
621✔
2432
        if n2 == 0
621✔
2433
            break
621✔
2434
        end
2435
        push!(prefs, String(read(f, n2)))
×
2436
        totbytes -= n2
×
2437
    end
×
2438
    prefs_hash = read(f, UInt64)
621✔
2439
    totbytes -= 8
621✔
2440
    srctextpos = read(f, Int64)
621✔
2441
    totbytes -= 8
621✔
2442
    @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))"
621✔
2443
    # read the list of modules that are required to be present during loading
2444
    required_modules = Vector{Pair{PkgId, UInt128}}()
621✔
2445
    while true
24,460✔
2446
        n = read(f, Int32)
24,460✔
2447
        n == 0 && break
24,460✔
2448
        sym = String(read(f, n)) # module name
23,839✔
2449
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
23,839✔
2450
        build_id = UInt128(read(f, UInt64)) << 64
23,839✔
2451
        build_id |= read(f, UInt64)
23,839✔
2452
        push!(required_modules, PkgId(uuid, sym) => build_id)
23,839✔
2453
    end
23,839✔
2454
    l = read(f, Int32)
621✔
2455
    clone_targets = read(f, l)
621✔
2456

2457
    return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
621✔
2458
end
2459

2460
function parse_cache_header(cachefile::String; srcfiles_only::Bool=false)
8✔
2461
    io = open(cachefile, "r")
4✔
2462
    try
4✔
2463
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile."))
4✔
2464
        ret = parse_cache_header(io)
4✔
2465
        srcfiles_only || return ret
7✔
2466
        _, (includes, _), _, srctextpos, _... = ret
1✔
2467
        srcfiles = srctext_files(io, srctextpos)
1✔
2468
        delidx = Int[]
1✔
2469
        for (i, chi) in enumerate(includes)
2✔
2470
            chi.filename ∈ srcfiles || push!(delidx, i)
5✔
2471
        end
5✔
2472
        deleteat!(includes, delidx)
1✔
2473
        return ret
1✔
2474
    finally
2475
        close(io)
4✔
2476
    end
2477
end
2478

2479
preferences_hash(f::IO) = parse_cache_header(f)[6]
111✔
2480
function preferences_hash(cachefile::String)
111✔
2481
    io = open(cachefile, "r")
111✔
2482
    try
111✔
2483
        if iszero(isvalid_cache_header(io))
111✔
2484
            throw(ArgumentError("Invalid header in cache file $cachefile."))
×
2485
        end
2486
        return preferences_hash(io)
111✔
2487
    finally
2488
        close(io)
111✔
2489
    end
2490
end
2491

2492
function cache_dependencies(f::IO)
1✔
2493
    _, (includes, _), modules, _... = parse_cache_header(f)
1✔
2494
    return modules, map(chi -> (chi.filename, chi.mtime), includes)  # return just filename and mtime
4✔
2495
end
2496

2497
function cache_dependencies(cachefile::String)
1✔
2498
    io = open(cachefile, "r")
1✔
2499
    try
1✔
2500
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile."))
1✔
2501
        return cache_dependencies(io)
1✔
2502
    finally
2503
        close(io)
1✔
2504
    end
2505
end
2506

2507
function read_dependency_src(io::IO, filename::AbstractString)
×
2508
    srctextpos = parse_cache_header(io)[4]
3✔
2509
    srctextpos == 0 && error("no source-text stored in cache file")
3✔
2510
    seek(io, srctextpos)
3✔
2511
    return _read_dependency_src(io, filename)
3✔
2512
end
2513

2514
function _read_dependency_src(io::IO, filename::AbstractString)
3✔
2515
    while !eof(io)
5✔
2516
        filenamelen = read(io, Int32)
5✔
2517
        filenamelen == 0 && break
5✔
2518
        fn = String(read(io, filenamelen))
3✔
2519
        len = read(io, UInt64)
3✔
2520
        if fn == filename
3✔
2521
            return String(read(io, len))
1✔
2522
        end
2523
        seek(io, position(io) + len)
2✔
2524
    end
2✔
2525
    error(filename, " is not stored in the source-text cache")
2✔
2526
end
2527

2528
function read_dependency_src(cachefile::String, filename::AbstractString)
3✔
2529
    io = open(cachefile, "r")
3✔
2530
    try
3✔
2531
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile."))
3✔
2532
        return read_dependency_src(io, filename)
5✔
2533
    finally
2534
        close(io)
3✔
2535
    end
2536
end
2537

2538
function srctext_files(f::IO, srctextpos::Int64)
1✔
2539
    files = Set{String}()
1✔
2540
    srctextpos == 0 && return files
1✔
2541
    seek(f, srctextpos)
1✔
2542
    while !eof(f)
2✔
2543
        filenamelen = read(f, Int32)
2✔
2544
        filenamelen == 0 && break
2✔
2545
        fn = String(read(f, filenamelen))
1✔
2546
        len = read(f, UInt64)
1✔
2547
        push!(files, fn)
1✔
2548
        seek(f, position(f) + len)
1✔
2549
    end
1✔
2550
    return files
1✔
2551
end
2552

2553
# Test to see if this UUID is mentioned in this `Project.toml`; either as
2554
# the top-level UUID (e.g. that of the project itself), as a dependency,
2555
# or as an extra/weakdep for Preferences.
2556
function get_uuid_name(project::Dict{String, Any}, uuid::UUID)
291✔
2557
    uuid_p = get(project, "uuid", nothing)::Union{Nothing, String}
373✔
2558
    name = get(project, "name", nothing)::Union{Nothing, String}
376✔
2559
    if name !== nothing && uuid_p !== nothing && UUID(uuid_p) == uuid
291✔
2560
        return name
16✔
2561
    end
2562
    deps = get(project, "deps", nothing)::Union{Nothing, Dict{String, Any}}
548✔
2563
    if deps !== nothing
275✔
2564
        for (k, v) in deps
546✔
2565
            if uuid == UUID(v::String)
374✔
2566
                return k
34✔
2567
            end
2568
        end
441✔
2569
    end
2570
    for subkey in ("deps", "extras", "weakdeps")
241✔
2571
        subsection = get(project, subkey, nothing)::Union{Nothing, Dict{String, Any}}
962✔
2572
        if subsection !== nothing
721✔
2573
            for (k, v) in subsection
482✔
2574
                if uuid == UUID(v::String)
314✔
2575
                    return k
2✔
2576
                end
2577
            end
385✔
2578
        end
2579
    end
958✔
2580
    return nothing
239✔
2581
end
2582

2583
function get_uuid_name(project_toml::String, uuid::UUID)
1✔
2584
    project = parsed_toml(project_toml)
1✔
2585
    return get_uuid_name(project, uuid)
1✔
2586
end
2587

2588
# If we've asked for a specific UUID, this function will extract the prefs
2589
# for that particular UUID.  Otherwise, it returns all preferences.
2590
function filter_preferences(prefs::Dict{String, Any}, pkg_name)
×
2591
    if pkg_name === nothing
×
2592
        return prefs
×
2593
    else
2594
        return get(Dict{String, Any}, prefs, pkg_name)::Dict{String, Any}
57✔
2595
    end
2596
end
2597

2598
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing})
292✔
2599
    # We'll return a list of dicts to be merged
2600
    dicts = Dict{String, Any}[]
292✔
2601

2602
    project = parsed_toml(project_toml)
292✔
2603
    pkg_name = nothing
×
2604
    if uuid !== nothing
290✔
2605
        # If we've been given a UUID, map that to the name of the package as
2606
        # recorded in the preferences section.  If we can't find that mapping,
2607
        # exit out, as it means there's no way preferences can be set for that
2608
        # UUID, as we only allow actual dependencies to have preferences set.
2609
        pkg_name = get_uuid_name(project, uuid)
290✔
2610
        if pkg_name === nothing
290✔
2611
            return dicts
239✔
2612
        end
2613
    end
2614

2615
    # Look first inside of `Project.toml` to see we have preferences embedded within there
2616
    proj_preferences = get(Dict{String, Any}, project, "preferences")::Dict{String, Any}
57✔
2617
    push!(dicts, filter_preferences(proj_preferences, pkg_name))
55✔
2618

2619
    # Next, look for `(Julia)LocalPreferences.toml` files next to this `Project.toml`
2620
    project_dir = dirname(project_toml)
53✔
2621
    for name in preferences_names
53✔
2622
        toml_path = joinpath(project_dir, name)
104✔
2623
        if isfile(toml_path)
104✔
2624
            prefs = parsed_toml(toml_path)
4✔
2625
            push!(dicts, filter_preferences(prefs, pkg_name))
6✔
2626

2627
            # If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
2628
            break
4✔
2629
        end
2630
    end
100✔
2631

2632
    return dicts
53✔
2633
end
2634

2635
"""
2636
    recursive_prefs_merge(base::Dict, overrides::Dict...)
2637

2638
Helper function to merge preference dicts recursively, honoring overrides in nested
2639
dictionaries properly.
2640
"""
2641
function recursive_prefs_merge(base::Dict{String, Any}, overrides::Dict{String, Any}...)
295✔
2642
    new_base = Base._typeddict(base, overrides...)
295✔
2643

2644
    for override in overrides
56✔
2645
        # Clear entries are keys that should be deleted from any previous setting.
2646
        override_clear = get(override, "__clear__", nothing)
62✔
2647
        if override_clear isa Vector{String}
60✔
2648
            for k in override_clear
2✔
2649
                delete!(new_base, k)
2✔
2650
            end
4✔
2651
        end
2652

2653
        for (k, override_k) in override
71✔
2654
            # Note that if `base` has a mapping that is _not_ a `Dict`, and `override`
2655
            new_base_k = get(new_base, k, nothing)
32✔
2656
            if new_base_k isa Dict{String, Any} && override_k isa Dict{String, Any}
21✔
2657
                new_base[k] = recursive_prefs_merge(new_base_k, override_k)
3✔
2658
            else
2659
                new_base[k] = override_k
18✔
2660
            end
2661
        end
31✔
2662
    end
12✔
2663
    return new_base
295✔
2664
end
2665

2666
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
378✔
2667
    merged_prefs = Dict{String,Any}()
378✔
2668
    for env in reverse(load_path())
377✔
2669
        project_toml = env_project_file(env)
749✔
2670
        if !isa(project_toml, String)
749✔
2671
            continue
457✔
2672
        end
2673

2674
        # Collect all dictionaries from the current point in the load path, then merge them in
2675
        dicts = collect_preferences(project_toml, uuid)
292✔
2676
        merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
292✔
2677
    end
1,126✔
2678
    return merged_prefs
377✔
2679
end
2680

2681
function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{String})
375✔
2682
    # Start from a predictable hash point to ensure that the same preferences always
2683
    # hash to the same value, modulo changes in how Dictionaries are hashed.
2684
    h = UInt(0)
×
2685
    uuid === nothing && return UInt64(h)
×
2686

2687
    # Load the preferences
2688
    prefs = get_preferences(uuid)
375✔
2689

2690
    # Walk through each name that's called out as a compile-time preference
2691
    for name in prefs_list
750✔
2692
        prefs_value = get(prefs, name, nothing)
×
2693
        if prefs_value !== nothing
×
2694
            h = hash(prefs_value, h)::UInt
×
2695
        end
2696
    end
×
2697
    # We always return a `UInt64` so that our serialization format is stable
2698
    return UInt64(h)
375✔
2699
end
2700

2701
get_preferences_hash(m::Module, prefs_list::Vector{String}) = get_preferences_hash(PkgId(m).uuid, prefs_list)
112✔
2702

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

2706
# In `Preferences.jl`, if someone calls `load_preference(@__MODULE__, key)` while we're precompiling,
2707
# we mark that usage as a usage at compile-time and call this method, so that at the end of `.ji` generation,
2708
# we can record the list of compile-time preferences and embed that into the `.ji` header
2709
function record_compiletime_preference(uuid::UUID, key::String)
×
2710
    pref = get!(Set{String}, COMPILETIME_PREFERENCES, uuid)
×
2711
    push!(pref, key)
×
2712
    return nothing
×
2713
end
2714
get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIME_PREFERENCES, uuid))
46✔
2715
get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid)
112✔
2716
get_compiletime_preferences(::Nothing) = String[]
66✔
2717

2718
function check_clone_targets(clone_targets)
×
2719
    try
×
2720
        ccall(:jl_check_pkgimage_clones, Cvoid, (Ptr{Cchar},), clone_targets)
×
2721
        return true
×
2722
    catch
2723
        return false
×
2724
    end
2725
end
2726

2727
struct CacheFlags
2728
    # OOICCDDP - see jl_cache_flags
2729
    use_pkgimages::Bool
2730
    debug_level::Int
2731
    check_bounds::Int
2732
    inline::Bool
2733
    opt_level::Int
2734

2735
    function CacheFlags(f::UInt8)
1✔
2736
        use_pkgimages = Bool(f & 1)
3✔
2737
        debug_level = Int((f >> 1) & 3)
2✔
2738
        check_bounds = Int((f >> 3) & 3)
2✔
2739
        inline = Bool((f >> 5) & 1)
4✔
2740
        opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils
2✔
2741
        new(use_pkgimages, debug_level, check_bounds, inline, opt_level)
2✔
2742
    end
2743
end
2744
CacheFlags(f::Int) = CacheFlags(UInt8(f))
1✔
2745
CacheFlags() = CacheFlags(ccall(:jl_cache_flags, UInt8, ()))
2✔
2746

2747
function show(io::IO, cf::CacheFlags)
1✔
2748
    print(io, "use_pkgimages = ", cf.use_pkgimages)
1✔
2749
    print(io, ", debug_level = ", cf.debug_level)
1✔
2750
    print(io, ", check_bounds = ", cf.check_bounds)
1✔
2751
    print(io, ", inline = ", cf.inline)
1✔
2752
    print(io, ", opt_level = ", cf.opt_level)
1✔
2753
end
2754

2755
# returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey
2756
# otherwise returns the list of dependencies to also check
2757
@constprop :none function stale_cachefile(modpath::String, cachefile::String; ignore_loaded::Bool = false)
21✔
2758
    return stale_cachefile(PkgId(""), UInt128(0), modpath, cachefile; ignore_loaded)
13✔
2759
end
2760
@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String; ignore_loaded::Bool = false)
862✔
2761
    io = open(cachefile, "r")
431✔
2762
    try
431✔
2763
        checksum = isvalid_cache_header(io)
431✔
2764
        if iszero(checksum)
431✔
2765
            @debug "Rejecting cache file $cachefile due to it containing an invalid cache header"
×
2766
            return true # invalid cache file
×
2767
        end
2768
        modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags = parse_cache_header(io)
431✔
2769
        if isempty(modules)
431✔
2770
            return true # ignore empty file
×
2771
        end
2772
        if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0
431✔
2773
            @debug """
36✔
2774
            Rejecting cache file $cachefile for $modkey since the flags are mismatched
2775
              current session: $(CacheFlags())
2776
              cache file:      $(CacheFlags(flags))
2777
            """
2778
            return true
36✔
2779
        end
2780
        pkgimage = !isempty(clone_targets)
395✔
2781
        if pkgimage
395✔
2782
            ocachefile = ocachefile_from_cachefile(cachefile)
×
2783
            if JLOptions().use_pkgimages == 0
×
2784
                # presence of clone_targets means native code cache
2785
                @debug "Rejecting cache file $cachefile for $modkey since it would require usage of pkgimage"
×
2786
                return true
×
2787
            end
2788
            if !check_clone_targets(clone_targets)
×
2789
                @debug "Rejecting cache file $cachefile for $modkey since pkgimage can't be loaded on this target"
×
2790
                return true
×
2791
            end
2792
            if !isfile(ocachefile)
×
2793
                @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found"
×
2794
                return true
×
2795
            end
2796
        else
2797
            ocachefile = nothing
×
2798
        end
2799
        id = first(modules)
395✔
2800
        if id.first != modkey && modkey != PkgId("")
395✔
2801
            @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead"
×
2802
            return true
×
2803
        end
2804
        if build_id != UInt128(0)
395✔
2805
            id_build = (UInt128(checksum) << 64) | id.second
46✔
2806
            if id_build != build_id
46✔
2807
                @debug "Ignoring cache file $cachefile for $modkey ($((UUID(id_build)))) since it is does not provide desired build_id ($((UUID(build_id))))"
×
2808
                return true
×
2809
            end
2810
        end
2811
        id = id.first
395✔
2812
        modules = Dict{PkgId, UInt64}(modules)
395✔
2813

2814
        # Check if transitive dependencies can be fulfilled
2815
        ndeps = length(required_modules)
395✔
2816
        depmods = Vector{Any}(undef, ndeps)
395✔
2817
        for i in 1:ndeps
790✔
2818
            req_key, req_build_id = required_modules[i]
15,123✔
2819
            # Module is already loaded
2820
            if root_module_exists(req_key)
15,123✔
2821
                M = root_module(req_key)
15,085✔
2822
                if PkgId(M) == req_key && module_build_id(M) === req_build_id
15,085✔
2823
                    depmods[i] = M
15,085✔
2824
                elseif ignore_loaded
×
2825
                    # Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages
2826
                    @goto locate_branch
×
2827
                else
2828
                    @debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible."
×
2829
                    return true # Won't be able to fulfill dependency
15,085✔
2830
                end
2831
            else
2832
                @label locate_branch
×
2833
                path = locate_package(req_key)
38✔
2834
                if path === nothing
38✔
2835
                    @debug "Rejecting cache file $cachefile because dependency $req_key not found."
3✔
2836
                    return true # Won't be able to fulfill dependency
3✔
2837
                end
2838
                depmods[i] = (path, req_key, req_build_id)
35✔
2839
            end
2840
        end
29,848✔
2841

2842
        # check if this file is going to provide one of our concrete dependencies
2843
        # or if it provides a version that conflicts with our concrete dependencies
2844
        # or neither
2845
        skip_timecheck = false
×
2846
        for (req_key, req_build_id) in _concrete_dependencies
747✔
2847
            build_id = get(modules, req_key, UInt64(0))
1,865✔
2848
            if build_id !== UInt64(0)
1,845✔
2849
                build_id |= UInt128(checksum) << 64
20✔
2850
                if build_id === req_build_id
20✔
2851
                    skip_timecheck = true
×
2852
                    break
20✔
2853
                end
2854
                @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))"
×
2855
                return true # cachefile doesn't provide the required version of the dependency
×
2856
            end
2857
        end
1,825✔
2858

2859
        # now check if this file is fresh relative to its source files
2860
        if !skip_timecheck
392✔
2861
            if !samefile(includes[1].filename, modpath) && !samefile(fixup_stdlib_path(includes[1].filename), modpath)
372✔
2862
                @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath"
2✔
2863
                return true # cache file was compiled from a different path
2✔
2864
            end
2865
            for (modkey, req_modkey) in requires
434✔
2866
                # verify that `require(modkey, name(req_modkey))` ==> `req_modkey`
2867
                if identify_package(modkey, req_modkey.name) != req_modkey
2,068✔
2868
                    @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed"
×
2869
                    return true
×
2870
                end
2871
            end
1,340✔
2872
            for chi in includes
370✔
2873
                f, ftime_req = chi.filename, chi.mtime
1,658✔
2874
                if !ispath(f)
1,658✔
2875
                    _f = fixup_stdlib_path(f)
×
2876
                    if isfile(_f) && startswith(_f, Sys.STDLIB)
×
2877
                        # mtime is changed by extraction
2878
                        @debug "Skipping mtime check for file $f used by $cachefile, since it is a stdlib"
×
2879
                        continue
×
2880
                    end
2881
                    @debug "Rejecting stale cache file $cachefile because file $f does not exist"
×
2882
                    return true
×
2883
                end
2884
                ftime = mtime(f)
1,658✔
2885
                is_stale = ( ftime != ftime_req ) &&
1,658✔
2886
                           ( ftime != floor(ftime_req) ) &&           # Issue #13606, PR #13613: compensate for Docker images rounding mtimes
2887
                           ( ftime != ceil(ftime_req) ) &&            # PR: #47433 Compensate for CirceCI's truncating of timestamps in its caching
2888
                           ( ftime != trunc(ftime_req, digits=6) ) && # Issue #20837, PR #20840: compensate for GlusterFS truncating mtimes to microseconds
2889
                           ( ftime != 1.0 )  &&                       # PR #43090: provide compatibility with Nix mtime.
2890
                           !( 0 < (ftime_req - ftime) < 1e-6 )        # PR #45552: Compensate for Windows tar giving mtimes that may be incorrect by up to one microsecond
2891
                if is_stale
1,658✔
2892
                    @debug "Rejecting stale cache file $cachefile (mtime $ftime_req) because file $f (mtime $ftime) has changed"
2✔
2893
                    return true
2✔
2894
                end
2895
            end
2,024✔
2896
        end
2897

2898
        if !isvalid_file_crc(io)
388✔
2899
            @debug "Rejecting cache file $cachefile because it has an invalid checksum"
1✔
2900
            return true
1✔
2901
        end
2902

2903
        if pkgimage
387✔
2904
            if !isvalid_pkgimage_crc(io, ocachefile::String)
×
2905
                @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum"
×
2906
                return true
×
2907
            end
2908
        end
2909

2910
        curr_prefs_hash = get_preferences_hash(id.uuid, prefs)
716✔
2911
        if prefs_hash != curr_prefs_hash
387✔
2912
            @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))"
×
2913
            return true
×
2914
        end
2915

2916
        return depmods, ocachefile # fresh cachefile
387✔
2917
    finally
2918
        close(io)
431✔
2919
    end
2920
end
2921

2922
"""
2923
    @__FILE__ -> String
2924

2925
Expand to a string with the path to the file containing the
2926
macrocall, or an empty string if evaluated by `julia -e <expr>`.
2927
Return `nothing` if the macro was missing parser source information.
2928
Alternatively see [`PROGRAM_FILE`](@ref).
2929
"""
2930
macro __FILE__()
78✔
2931
    __source__.file === nothing && return nothing
78✔
2932
    return String(__source__.file::Symbol)
78✔
2933
end
2934

2935
"""
2936
    @__DIR__ -> String
2937

2938
Expand to a string with the absolute path to the directory of the file
2939
containing the macrocall.
2940
Return the current working directory if run from a REPL or if evaluated by `julia -e <expr>`.
2941
"""
2942
macro __DIR__()
71✔
2943
    __source__.file === nothing && return nothing
71✔
2944
    _dirname = dirname(String(__source__.file::Symbol))
71✔
2945
    return isempty(_dirname) ? pwd() : abspath(_dirname)
71✔
2946
end
2947

2948
"""
2949
    precompile(f, argtypes::Tuple{Vararg{Any}})
2950

2951
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
2952
"""
2953
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
17✔
2954
    precompile(Tuple{Core.Typeof(f), argtypes...})
17✔
2955
end
2956

2957
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
2958
function precompile(@nospecialize(argt::Type))
44✔
2959
    ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
44✔
2960
    if !ret && ENABLE_PRECOMPILE_WARNINGS[]
44✔
2961
        @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
×
2962
    end
2963
    return ret
44✔
2964
end
2965

2966
# Variants that work for `invoke`d calls for which the signature may not be sufficient
2967
precompile(mi::Core.MethodInstance, world::UInt=get_world_counter()) =
2✔
2968
    (ccall(:jl_compile_method_instance, Cvoid, (Any, Any, UInt), mi, C_NULL, world); return true)
1✔
2969

2970
"""
2971
    precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
2972

2973
Precompile a specific method for the given argument types. This may be used to precompile
2974
a different method than the one that would ordinarily be chosen by dispatch, thus
2975
mimicking `invoke`.
2976
"""
2977
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
1✔
2978
    precompile(Tuple{Core.Typeof(f), argtypes...}, m)
1✔
2979
end
2980

2981
function precompile(@nospecialize(argt::Type), m::Method)
1✔
2982
    atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
2✔
2983
    mi = Core.Compiler.specialize_method(m, atype, sparams)
1✔
2984
    return precompile(mi)
1✔
2985
end
2986

2987
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing))
2988
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String))
2989
precompile(create_expr_cache, (PkgId, String, String, String, typeof(_concrete_dependencies), IO, IO))
2990
precompile(create_expr_cache, (PkgId, String, String, Nothing, typeof(_concrete_dependencies), IO, IO))
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

© 2025 Coveralls, Inc