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

JuliaLang / julia / #38162

06 Aug 2025 08:25PM UTC coverage: 25.688% (-43.6%) from 69.336%
#38162

push

local

web-flow
fix runtime cglobal builtin function implementation (#59210)

This had failed to be updated for the LazyLibrary changes to codegen.

12976 of 50513 relevant lines covered (25.69%)

676965.51 hits per line

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

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

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

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

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

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

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

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

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

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

113
## SHA1 ##
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

266

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

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

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

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

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

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

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

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

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

402
_nothing_or_first(x) = x === nothing ? nothing : first(x)
226✔
403

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

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

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

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

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

424
julia> using LinearAlgebra
425

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

623
## generic project & manifest API ##
624

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

634
function locate_project_file(env::String)
635
    for proj in project_names
467✔
636
        project_file = joinpath(env, proj)
934✔
637
        if isfile_casesensitive(project_file)
934✔
638
            return project_file
78✔
639
        end
640
    end
1,245✔
641
    return true
389✔
642
end
643

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

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

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

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

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

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

775

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

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

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

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

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

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

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

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

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

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

907
## explicit project & manifest API ##
908

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

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

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

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

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

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

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

1108
## implicit project & manifest API ##
1109

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

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

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

1166
## other code loading functionality ##
1167

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

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

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

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

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

1238

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

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

1257
        for i in eachindex(depmods)
40✔
1258
            dep = depmods[i]
614✔
1259
            dep isa Module && continue
614✔
1260
            _, depkey, depbuild_id = dep::Tuple{String, PkgId, UInt128}
50✔
1261
            dep = something(maybe_loaded_precompile(depkey, depbuild_id))
100✔
1262
            @assert PkgId(dep) == depkey && module_build_id(dep) === depbuild_id
50✔
1263
            depmods[i] = dep
50✔
1264
        end
1,188✔
1265

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

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

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

1309
    finally
1310
        timing_imports && cumulative_compile_timing(false)
40✔
1311
    end
1312
end
1313

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

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

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

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

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

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

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

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

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

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

1451

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

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

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

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

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

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

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

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

1537
                    total_deps = merge(weakdeps, deps)
×
1538
                    return _insert_extension_triggers(pkg, extensions, total_deps)
×
1539
                end
1540
            end
×
1541
        end
×
1542
    end
1543
    return nothing
×
1544
end
1545

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

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

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

1622
    return
1✔
1623
end
1624

1625
"""
1626
    retry_load_extensions()
1627

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

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

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

1659
# End extensions
1660

1661

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

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

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

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

1732
struct ImageTarget
1733
    name::String
×
1734
    flags::Int32
1735
    ext_features::String
1736
    features_en::Vector{UInt8}
1737
    features_dis::Vector{UInt8}
1738
end
1739

1740
function parse_image_target(io::IO)
×
1741
    flags = read(io, Int32)
×
1742
    nfeature = read(io, Int32)
×
1743
    feature_en = read(io, 4*nfeature)
×
1744
    feature_dis = read(io, 4*nfeature)
×
1745
    name_len = read(io, Int32)
×
1746
    name = String(read(io, name_len))
×
1747
    ext_features_len = read(io, Int32)
×
1748
    ext_features = String(read(io, ext_features_len))
×
1749
    ImageTarget(name, flags, ext_features, feature_en, feature_dis)
×
1750
end
1751

1752
function parse_image_targets(targets::Vector{UInt8})
×
1753
    io = IOBuffer(targets)
×
1754
    ntargets = read(io, Int32)
×
1755
    targets = Vector{ImageTarget}(undef, ntargets)
×
1756
    for i in 1:ntargets
×
1757
        targets[i] = parse_image_target(io)
×
1758
    end
×
1759
    return targets
×
1760
end
1761

1762
function current_image_targets()
1763
    targets = @ccall jl_reflect_clone_targets()::Vector{UInt8}
×
1764
    return parse_image_targets(targets)
×
1765
end
1766

1767
struct FeatureName
1768
    name::Cstring
1769
    bit::UInt32 # bit index into a `uint32_t` array;
1770
    llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support
1771
end
1772

1773
function feature_names()
×
1774
    fnames = Ref{Ptr{FeatureName}}()
×
1775
    nf = Ref{Csize_t}()
×
1776
    @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid
×
1777
    if fnames[] == C_NULL
×
1778
        @assert nf[] == 0
×
1779
        return Vector{FeatureName}(undef, 0)
×
1780
    end
1781
    Base.unsafe_wrap(Array, fnames[], nf[], own=false)
×
1782
end
1783

1784
function test_feature(features::Vector{UInt8}, feat::FeatureName)
×
1785
    bitidx = feat.bit
×
1786
    u8idx = div(bitidx, 8) + 1
×
1787
    bit = bitidx % 8
×
1788
    return (features[u8idx] & (1 << bit)) != 0
×
1789
end
1790

1791
function show(io::IO, it::ImageTarget)
×
1792
    print(io, it.name)
×
1793
    if !isempty(it.ext_features)
×
1794
        print(io, ",", it.ext_features)
×
1795
    end
1796
    print(io, "; flags=", it.flags)
×
1797
    print(io, "; features_en=(")
×
1798
    first = true
×
1799
    for feat in feature_names()
×
1800
        if test_feature(it.features_en, feat)
×
1801
            name = Base.unsafe_string(feat.name)
×
1802
            if first
×
1803
                first = false
×
1804
                print(io, name)
×
1805
            else
1806
                print(io, ", ", name)
×
1807
            end
1808
        end
1809
    end
×
1810
    print(io, ")")
×
1811
    # Is feature_dis useful?
1812
end
1813

1814
# should sync with the types of arguments of `stale_cachefile`
1815
const StaleCacheKey = Tuple{PkgId, UInt128, String, String}
1816

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

1862
"""
1863
    Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
1864

1865
Returns whether a given PkgId within the active project is precompiled.
1866

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

1872
!!! compat "Julia 1.10"
1873
    This function requires at least Julia 1.10.
1874
"""
1875
function isprecompiled(pkg::PkgId;
×
1876
        ignore_loaded::Bool=false,
1877
        stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
1878
        cachepath_cache::Dict{PkgId, Vector{String}}=Dict{PkgId, Vector{String}}(),
1879
        cachepaths::Vector{String}=get!(() -> find_all_in_cache_path(pkg), cachepath_cache, pkg),
1880
        sourcepath::Union{String,Nothing}=Base.locate_package(pkg),
1881
        flags::CacheFlags=CacheFlags())
1882
    path = compilecache_path(pkg; ignore_loaded, stale_cache, cachepath_cache, cachepaths, sourcepath, flags)
×
1883
    return !isnothing(path)
×
1884
end
1885

1886
"""
1887
    Base.isrelocatable(pkg::PkgId)
1888

1889
Returns whether a given PkgId within the active project is precompiled and the
1890
associated cache is relocatable.
1891

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

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

1941
# loads a precompile cache file, ignoring stale_cachefile tests
1942
# load all dependent modules first
1943
function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String})
×
1944
    assert_havelock(require_lock)
×
1945
    local depmodnames
1946
    io = open(path, "r")
×
1947
    try
×
1948
        iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.")
×
1949
        _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path)
×
1950

1951

1952
        pkgimage = !isempty(clone_targets)
×
1953
        if pkgimage
×
1954
            ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided")
×
1955
            isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.")
×
1956
            ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile")
×
1957
            # TODO: Check for valid clone_targets?
1958
            isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.")
×
1959
        else
1960
            @assert ocachepath === nothing
×
1961
        end
1962
        isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.")
×
1963
    finally
1964
        close(io)
×
1965
    end
1966
    ndeps = length(depmodnames)
×
1967
    depmods = Vector{Any}(undef, ndeps)
×
1968
    for i in 1:ndeps
×
1969
        modkey, build_id = depmodnames[i]
×
1970
        dep = _tryrequire_from_serialized(modkey, build_id)
×
1971
        if !isa(dep, Module)
×
1972
            return dep
×
1973
        end
1974
        depmods[i] = dep
×
1975
    end
×
1976
    # then load the file
1977
    loaded = _include_from_serialized(pkg, path, ocachepath, depmods; register = true)
×
1978
    return loaded
×
1979
end
1980

1981
# returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it or it was stale
1982
# returns the set of modules restored if the cache load succeeded
1983
@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128, stalecheck::Bool; reasons=nothing, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH)
12✔
1984
    assert_havelock(require_lock)
12✔
1985
    paths = find_all_in_cache_path(pkg, DEPOT_PATH)
6✔
1986
    newdeps = PkgId[]
6✔
1987
    try_build_ids = UInt128[build_id]
6✔
1988
    if build_id == UInt128(0)
6✔
1989
        let loaded = get(loaded_precompiles, pkg, nothing)
6✔
1990
            if loaded !== nothing
6✔
1991
                for mod in loaded # try these in reverse original load order to see if one is already valid
×
1992
                    pushfirst!(try_build_ids, module_build_id(mod))
×
1993
                end
×
1994
            end
1995
        end
1996
    end
1997
    for build_id in try_build_ids
6✔
1998
        for path_to_try in paths::Vector{String}
6✔
1999
            staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try; reasons, stalecheck)
14✔
2000
            if staledeps === true
7✔
2001
                continue
1✔
2002
            end
2003
            staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
6✔
2004
            startedloading = length(staledeps) + 1
6✔
2005
            try # any exit from here (goto, break, continue, return) will end_loading
6✔
2006
                # finish checking staledeps module graph, while acquiring all start_loading locks
2007
                # so that concurrent require calls won't make any different decisions that might conflict with the decisions here
2008
                # note that start_loading will drop the loading lock if necessary
2009
                let i = 0
6✔
2010
                    # start_loading here has a deadlock problem if we try to load `A,B,C` and `B,A,D` at the same time:
2011
                    # it will claim A,B have a cycle, but really they just have an ambiguous order and need to be batch-acquired rather than singly
2012
                    # solve that by making sure we can start_loading everything before allocating each of those and doing all the stale checks
2013
                    while i < length(staledeps)
128✔
2014
                        i += 1
122✔
2015
                        dep = staledeps[i]
122✔
2016
                        dep isa Module && continue
122✔
2017
                        _, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
34✔
2018
                        dep = canstart_loading(modkey, modbuild_id, stalecheck)
34✔
2019
                        if dep isa Module
34✔
2020
                            if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
×
2021
                                staledeps[i] = dep
×
2022
                                continue
×
2023
                            else
2024
                                @debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
×
2025
                                @goto check_next_path
×
2026
                            end
2027
                            continue
×
2028
                        elseif dep === nothing
34✔
2029
                            continue
34✔
2030
                        end
2031
                        wait(dep) # releases require_lock, so requires restarting this loop
×
2032
                        i = 0
×
2033
                    end
122✔
2034
                end
2035
                for i in reverse(eachindex(staledeps))
12✔
2036
                    dep = staledeps[i]
122✔
2037
                    dep isa Module && continue
122✔
2038
                    modpath, modkey, modbuild_id = dep::Tuple{String, PkgId, UInt128}
34✔
2039
                    # inline a call to start_loading here
2040
                    @assert canstart_loading(modkey, modbuild_id, stalecheck) === nothing
34✔
2041
                    package_locks[modkey] = (current_task(), Threads.Condition(require_lock), modbuild_id)
34✔
2042
                    startedloading = i
34✔
2043
                    modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
34✔
2044
                    for modpath_to_try in modpaths
34✔
2045
                        modstaledeps = stale_cachefile(modkey, modbuild_id, modpath, modpath_to_try; stalecheck)
82✔
2046
                        if modstaledeps === true
41✔
2047
                            continue
7✔
2048
                        end
2049
                        modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
34✔
2050
                        staledeps[i] = (modpath, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
34✔
2051
                        @goto check_next_dep
34✔
2052
                    end
7✔
2053
                    @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
×
2054
                    @goto check_next_path
×
2055
                    @label check_next_dep
2056
                end
238✔
2057
                M = maybe_loaded_precompile(pkg, newbuild_id)
6✔
2058
                if isa(M, Module)
6✔
2059
                    stalecheck && register_root_module(M)
×
2060
                    return M
×
2061
                end
2062
                if stalecheck
6✔
2063
                    try
4✔
2064
                        touch(path_to_try) # update timestamp of precompilation file
4✔
2065
                    catch ex # file might be read-only and then we fail to update timestamp, which is fine
2066
                        ex isa IOError || rethrow()
×
2067
                    end
2068
                end
2069
                # finish loading module graph into staledeps
2070
                # n.b. this runs __init__ methods too early, so it is very unwise to have those, as they may see inconsistent loading state, causing them to fail unpredictably here
2071
                for i in eachindex(staledeps)
6✔
2072
                    dep = staledeps[i]
122✔
2073
                    dep isa Module && continue
122✔
2074
                    modpath, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
34✔
2075
                    set_pkgorigin_version_path(modkey, modpath)
34✔
2076
                    dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
68✔
2077
                    if !isa(dep, Module)
34✔
2078
                        @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
×
2079
                        @goto check_next_path
×
2080
                    else
2081
                        startedloading = i + 1
34✔
2082
                        end_loading(modkey, dep)
34✔
2083
                        staledeps[i] = dep
34✔
2084
                        push!(newdeps, modkey)
34✔
2085
                    end
2086
                end
238✔
2087
                restored = maybe_loaded_precompile(pkg, newbuild_id)
6✔
2088
                if !isa(restored, Module)
6✔
2089
                    restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
12✔
2090
                end
2091
                isa(restored, Module) && return restored
6✔
2092
                @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
×
2093
                @label check_next_path
×
2094
            finally
2095
                # cancel all start_loading locks that were taken but not fulfilled before failing
2096
                for i in startedloading:length(staledeps)
11✔
2097
                    dep = staledeps[i]
4✔
2098
                    dep isa Module && continue
4✔
2099
                    if dep isa Tuple{String, PkgId, UInt128}
×
2100
                        _, modkey, _ = dep
×
2101
                    else
2102
                        _, modkey, _ = dep::Tuple{String, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
×
2103
                    end
2104
                    end_loading(modkey, nothing)
×
2105
                end
7✔
2106
                for modkey in newdeps
6✔
2107
                    insert_extension_triggers(modkey)
68✔
2108
                    stalecheck && run_package_callbacks(modkey)
34✔
2109
                end
34✔
2110
            end
2111
        end
1✔
2112
    end
×
2113
    return nothing
×
2114
end
2115

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

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

2123
function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
38✔
2124
    assert_havelock(require_lock)
76✔
2125
    require_lock.reentrancy_cnt == 1 || throw(ConcurrencyViolationError("recursive call to start_loading"))
38✔
2126
    loading = get(package_locks, modkey, nothing)
38✔
2127
    if loading === nothing
38✔
2128
        loaded = stalecheck ? maybe_root_module(modkey) : nothing
38✔
2129
        loaded isa Module && return loaded
38✔
2130
        if build_id != UInt128(0)
31✔
2131
            loaded = maybe_loaded_precompile(modkey, build_id)
26✔
2132
            loaded isa Module && return loaded
26✔
2133
        end
2134
        return nothing
31✔
2135
    end
2136
    if !stalecheck && build_id != UInt128(0) && loading[3] != build_id
×
2137
        # don't block using an existing specific loaded module on needing a different concurrently loaded one
2138
        loaded = maybe_loaded_precompile(modkey, build_id)
×
2139
        loaded isa Module && return loaded
×
2140
    end
2141
    # load already in progress for this module on the task
2142
    task, cond = loading
×
2143
    deps = String[modkey.name]
×
2144
    assert_havelock(cond.lock)
×
2145
    if debug_loading_deadlocks && current_task() !== task
×
2146
        waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
×
2147
        for each in package_locks
×
2148
            cond2 = each[2][2]
×
2149
            assert_havelock(cond2.lock)
×
2150
            for waiting in cond2.waitq
×
2151
                push!(waiters, waiting => (each[2][1] => each[1]))
×
2152
            end
×
2153
        end
×
2154
        while true
×
2155
            running = get(waiters, task, nothing)
×
2156
            running === nothing && break
×
2157
            task, pkgid = running
×
2158
            push!(deps, pkgid.name)
×
2159
            task === current_task() && break
×
2160
        end
×
2161
    end
2162
    if current_task() === task
×
2163
        push!(deps, modkey.name) # repeat this to emphasize the cycle here
×
2164
        others = Set{String}()
×
2165
        for each in package_locks # list the rest of the packages being loaded too
×
2166
            if each[2][1] === task
×
2167
                other = each[1].name
×
2168
                other == modkey.name || push!(others, other)
×
2169
            end
2170
        end
×
2171
        # remove duplicates from others already in deps
2172
        for dep in deps
×
2173
            delete!(others, dep)
×
2174
        end
×
2175
        msg = sprint(deps, others) do io, deps, others
×
2176
            print(io, "deadlock detected in loading ")
×
2177
            join(io, deps, " using ")
×
2178
            if !isempty(others)
×
2179
                print(io, " (while loading ")
×
2180
                join(io, others, " and ")
×
2181
                print(io, ")")
×
2182
            end
2183
        end
2184
        throw(ConcurrencyViolationError(msg))
×
2185
    end
2186
    return cond
×
2187
end
2188

2189
function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
12✔
2190
    # handle recursive and concurrent calls to require
2191
    while true
12✔
2192
        loaded = canstart_loading(modkey, build_id, stalecheck)
12✔
2193
        if loaded === nothing
12✔
2194
            package_locks[modkey] = (current_task(), Threads.Condition(require_lock), build_id)
5✔
2195
            return nothing
5✔
2196
        elseif loaded isa Module
7✔
2197
            return loaded
7✔
2198
        end
2199
        loaded = wait(loaded)
×
2200
        loaded isa Module && return loaded
×
2201
    end
×
2202
end
2203

2204
function end_loading(modkey::PkgId, @nospecialize loaded)
18✔
2205
    assert_havelock(require_lock)
36✔
2206
    loading = pop!(package_locks, modkey)
18✔
2207
    notify(loading[2], loaded, all=true)
18✔
2208
    nothing
18✔
2209
end
2210

2211
# to notify downstream consumers that a module was successfully loaded
2212
# Callbacks take the form (mod::Base.PkgId) -> nothing.
2213
# WARNING: This is an experimental feature and might change later, without deprecation.
2214
const package_callbacks = Any[]
2215
# to notify downstream consumers that a file has been included into a particular module
2216
# Callbacks take the form (mod::Module, filename::String) -> nothing
2217
# WARNING: This is an experimental feature and might change later, without deprecation.
2218
const include_callbacks = Any[]
2219

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

2225
function _include_dependency(mod::Module, _path::AbstractString; track_content::Bool=true,
6✔
2226
                             path_may_be_dir::Bool=false)
2227
    _include_dependency!(_require_dependencies, _track_dependencies[], mod, _path, track_content, path_may_be_dir)
6✔
2228
end
2229

2230
function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool,
6✔
2231
                              mod::Module, _path::AbstractString,
2232
                              track_content::Bool, path_may_be_dir::Bool)
2233
    prev = source_path(nothing)
9✔
2234
    if prev === nothing
6✔
2235
        path = abspath(_path)
3✔
2236
    else
2237
        path = normpath(joinpath(dirname(prev), _path))
3✔
2238
    end
2239
    if !track_dependencies[]
6✔
2240
        if !path_may_be_dir && !isfile(path)
6✔
2241
            throw(SystemError("opening file $(repr(path))", Libc.ENOENT))
×
2242
        elseif path_may_be_dir && !Filesystem.isreadable(path)
6✔
2243
            throw(SystemError("opening file or folder $(repr(path))", Libc.ENOENT))
×
2244
        end
2245
    else
2246
        @lock require_lock begin
×
2247
            if track_content
×
2248
                hash = (isdir(path) ? _crc32c(join(readdir(path))) : open(_crc32c, path, "r"))::UInt32
×
2249
                # use mtime=-1.0 here so that fsize==0 && mtime==0.0 corresponds to a missing include_dependency
2250
                push!(dep_list, (mod, path, UInt64(filesize(path)), hash, -1.0))
×
2251
            else
2252
                push!(dep_list, (mod, path, UInt64(0), UInt32(0), mtime(path)))
×
2253
            end
2254
        end
2255
    end
2256
    return path, prev
6✔
2257
end
2258

2259
"""
2260
    include_dependency(path::AbstractString; track_content::Bool=true)
2261

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

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

2271
!!! compat "Julia 1.11"
2272
    Keyword argument `track_content` requires at least Julia 1.11.
2273
    An error is now thrown if `path` is not readable.
2274
"""
2275
function include_dependency(path::AbstractString; track_content::Bool=true)
×
2276
    _include_dependency(Main, path, track_content=track_content, path_may_be_dir=true)
×
2277
    return nothing
×
2278
end
2279

2280
# we throw PrecompilableError when a module doesn't want to be precompiled
2281
import Core: PrecompilableError
2282
function show(io::IO, ex::PrecompilableError)
×
2283
    print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.")
×
2284
end
2285
precompilableerror(ex::PrecompilableError) = true
×
2286
precompilableerror(ex::WrappedException) = precompilableerror(ex.error)
×
2287
precompilableerror(@nospecialize ex) = false
×
2288

2289
# Call __precompile__(false) at the top of a tile prevent it from being precompiled (false)
2290
"""
2291
    __precompile__(isprecompilable::Bool)
2292

2293
Specify whether the file calling this function is precompilable, defaulting to `true`.
2294
If a module or file is *not* safely precompilable, it should call `__precompile__(false)` in
2295
order to throw an error if Julia attempts to precompile it.
2296
"""
2297
@noinline function __precompile__(isprecompilable::Bool=true)
×
2298
    if !isprecompilable && generating_output()
×
2299
        throw(PrecompilableError())
×
2300
    end
2301
    nothing
×
2302
end
2303

2304
# require always works in Main scope and loads files from node 1
2305
# XXX: (this is deprecated, but still used by Distributed)
2306
const toplevel_load = Ref(true)
2307

2308
const _require_world_age = Ref{UInt}(typemax(UInt))
2309

2310
"""
2311
    require(into::Module, module::Symbol)
2312

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

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

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

2328
For more details regarding code loading, see the manual sections on [modules](@ref modules) and
2329
[parallel computing](@ref code-availability).
2330
"""
2331
function require(into::Module, mod::Symbol)
2332
    world = _require_world_age[]
11✔
2333
    if world == typemax(UInt)
11✔
2334
        world = get_world_counter()
×
2335
    end
2336
    return Compiler.@zone "LOAD_Require" invoke_in_world(world, __require, into, mod)
11✔
2337
end
2338

2339
function check_for_hint(into, mod)
×
2340
    return begin
×
2341
        if isdefined(into, mod) && getfield(into, mod) isa Module
×
2342
            true, "."
×
2343
        elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
×
2344
            true, ".."
×
2345
        else
2346
            false, ""
×
2347
        end
2348
    end
2349
end
2350

2351
function __require(into::Module, mod::Symbol)
11✔
2352
    if into === __toplevel__ && generating_output(#=incremental=#true)
11✔
2353
        error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
×
2354
         is not allowed during package precompilation.")
2355
    end
2356
    topmod = moduleroot(into)
14✔
2357
    if nameof(topmod) === mod
11✔
2358
        return topmod
×
2359
    end
2360
    @lock require_lock begin
11✔
2361
    LOADING_CACHE[] = LoadingCache()
11✔
2362
    try
11✔
2363
        uuidkey_env = identify_package_env(into, String(mod))
11✔
2364
        # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env")
2365
        if uuidkey_env === nothing
11✔
2366
            where = PkgId(into)
×
2367
            if where.uuid === nothing
×
2368
                hint, dots = invokelatest(check_for_hint, into, mod)
×
2369
                hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
×
2370
                install_message = if mod != :Pkg
×
2371
                    start_sentence = hint ? "Otherwise, run" : "Run"
×
2372
                    "\n- $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package."
×
2373
                else  # for some reason Pkg itself isn't availability so do not tell them to use Pkg to install it.
2374
                    ""
×
2375
                end
2376

2377
                throw(ArgumentError("Package $mod not found in current path$hint_message.$install_message"))
×
2378
            else
2379
                manifest_warnings = collect_manifest_warnings()
×
2380
                throw(ArgumentError("""
×
2381
                Cannot load (`using/import`) module $mod into module $into in package $(where.name)
2382
                because package $(where.name) does not have $mod in its dependencies:
2383
                $manifest_warnings- You may have a partially installed environment. Try `Pkg.instantiate()`
2384
                  to ensure all packages in the environment are installed.
2385
                - Or, if you have $(where.name) checked out for development and have
2386
                  added $mod as a dependency but haven't updated your primary
2387
                  environment's manifest file, try `Pkg.resolve()`.
2388
                - Otherwise you may need to report an issue with $(where.name)"""))
2389
            end
2390
        end
2391
        uuidkey, env = uuidkey_env
22✔
2392
        if _track_dependencies[]
11✔
2393
            path = binpack(uuidkey)
×
2394
            push!(_require_dependencies, (into, path, UInt64(0), UInt32(0), 0.0))
×
2395
        end
2396
        return _require_prelocked(uuidkey, env)
22✔
2397
    finally
2398
        LOADING_CACHE[] = nothing
11✔
2399
    end
2400
    end
2401
end
2402

2403
function find_unsuitable_manifests_versions()
×
2404
    unsuitable_manifests = String[]
×
2405
    dev_manifests = String[]
×
2406
    for env in load_path()
×
2407
        project_file = env_project_file(env)
×
2408
        project_file isa String || continue # no project file
×
2409
        manifest_file = project_file_manifest_path(project_file)
×
2410
        manifest_file isa String || continue # no manifest file
×
2411
        m = parsed_toml(manifest_file)
×
2412
        man_julia_version = get(m, "julia_version", nothing)
×
2413
        man_julia_version isa String || @goto mark
×
2414
        man_julia_version = VersionNumber(man_julia_version)
×
2415
        thispatch(man_julia_version) != thispatch(VERSION) && @goto mark
×
2416
        isempty(man_julia_version.prerelease) != isempty(VERSION.prerelease) && @goto mark
×
2417
        isempty(man_julia_version.prerelease) && continue
×
2418
        man_julia_version.prerelease[1] != VERSION.prerelease[1] && @goto mark
×
2419
        if VERSION.prerelease[1] == "DEV"
×
2420
            # manifests don't store the 2nd part of prerelease, so cannot check further
2421
            # so treat them specially in the warning
2422
            push!(dev_manifests, manifest_file)
×
2423
        end
2424
        continue
×
2425
        @label mark
2426
        push!(unsuitable_manifests, string(manifest_file, " (v", man_julia_version, ")"))
×
2427
    end
×
2428
    return unsuitable_manifests, dev_manifests
×
2429
end
2430

2431
function collect_manifest_warnings()
×
2432
    unsuitable_manifests, dev_manifests = find_unsuitable_manifests_versions()
×
2433
    msg = ""
×
2434
    if !isempty(unsuitable_manifests)
×
2435
        msg *= """
×
2436
        - Note that the following manifests in the load path were resolved with a different
2437
          julia version, which may be the cause of the error. Try to re-resolve them in the
2438
          current version, or consider deleting them if that fails:
2439
            $(join(unsuitable_manifests, "\n    "))
2440
        """
2441
    end
2442
    if !isempty(dev_manifests)
×
2443
        msg *= """
×
2444
        - Note that the following manifests in the load path were resolved with a potentially
2445
          different DEV version of the current version, which may be the cause of the error.
2446
          Try to re-resolve them in the current version, or consider deleting them if that fails:
2447
            $(join(dev_manifests, "\n    "))
2448
        """
2449
    end
2450
    return msg
×
2451
end
2452

2453
function require(uuidkey::PkgId)
2454
    world = _require_world_age[]
1✔
2455
    if world == typemax(UInt)
1✔
2456
        world = get_world_counter()
×
2457
    end
2458
    return invoke_in_world(world, __require, uuidkey)
1✔
2459
end
2460
__require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
×
2461
function _require_prelocked(uuidkey::PkgId, env=nothing)
11✔
2462
    assert_havelock(require_lock)
22✔
2463
    m = start_loading(uuidkey, UInt128(0), true)
11✔
2464
    if m === nothing
11✔
2465
        last = toplevel_load[]
4✔
2466
        try
4✔
2467
            toplevel_load[] = false
4✔
2468
            m = __require_prelocked(uuidkey, env)
4✔
2469
            m isa Module || check_package_module_loaded_error(uuidkey)
4✔
2470
        finally
2471
            toplevel_load[] = last
4✔
2472
            end_loading(uuidkey, m)
4✔
2473
        end
2474
        insert_extension_triggers(uuidkey)
8✔
2475
        # After successfully loading, notify downstream consumers
2476
        run_package_callbacks(uuidkey)
4✔
2477
    end
2478
    return m
11✔
2479
end
2480

2481
mutable struct PkgOrigin
2482
    path::Union{String,Nothing}
18✔
2483
    cachepath::Union{String,Nothing}
2484
    version::Union{VersionNumber,Nothing}
2485
end
2486
PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
18✔
2487
const pkgorigins = Dict{PkgId,PkgOrigin}()
2488

2489
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
2490
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
2491
const loaded_modules_order = Vector{Module}()
2492

2493
root_module_key(m::Module) = PkgId(m)
34✔
2494

2495
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
134✔
2496
    @lock require_lock begin
134✔
2497
    mods = get(loaded_precompiles, key, nothing)
224✔
2498
    mods === nothing && return
134✔
2499
    for mod in mods
90✔
2500
        module_build_id(mod) == buildid && return mod
90✔
2501
    end
×
2502
    end
2503
end
2504

2505
function module_build_id(m::Module)
2506
    hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
393✔
2507
    return (UInt128(hi) << 64) | lo
393✔
2508
end
2509

2510
@constprop :none function register_root_module(m::Module)
13✔
2511
    # n.b. This is called from C after creating a new module in `Base.__toplevel__`,
2512
    # instead of adding them to the binding table there.
2513
    @lock require_lock begin
13✔
2514
    key = PkgId(m, String(nameof(m)))
26✔
2515
    if haskey(loaded_modules, key)
13✔
2516
        oldm = loaded_modules[key]
×
2517
        if oldm !== m
×
2518
            if generating_output(#=incremental=#true)
×
2519
                error("Replacing module `$(key.name)`")
×
2520
            else
2521
                @warn "Replacing module `$(key.name)`"
×
2522
            end
2523
        end
2524
    end
2525
    maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
13✔
2526
    loaded_modules[key] = m
13✔
2527
    end
2528
    nothing
13✔
2529
end
2530

2531
register_root_module(Core)
2532
register_root_module(Base)
2533
register_root_module(Main)
2534

2535
# This is used as the current module when loading top-level modules.
2536
# It has the special behavior that modules evaluated in it get added
2537
# to the loaded_modules table instead of getting bindings.
2538
baremodule __toplevel__
2539
using Base
2540
end
2541

2542
# get a top-level Module from the given key
2543
# this is similar to `require`, but worse in almost every possible way
2544
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
×
2545
function root_module(where::Module, name::Symbol)
×
2546
    key = identify_package(where, String(name))
×
2547
    key isa PkgId || throw(KeyError(name))
×
2548
    return root_module(key)
×
2549
end
2550
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
×
2551
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
230✔
2552

2553
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
×
2554

2555
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2556
# reload(m) = (unreference_module(m); require(m))
2557
function unreference_module(key::PkgId)
×
2558
    @lock require_lock begin
×
2559
    if haskey(loaded_modules, key)
×
2560
        m = pop!(loaded_modules, key)
×
2561
        # need to ensure all modules are GC rooted; will still be referenced
2562
        # in loaded_modules_order
2563
    end
2564
    end
2565
end
2566

2567
# whoever takes the package_locks[pkg] must call this function immediately
2568
function set_pkgorigin_version_path(pkg::PkgId, path::String)
18✔
2569
    assert_havelock(require_lock)
36✔
2570
    pkgorigin = get!(PkgOrigin, pkgorigins, pkg)
18✔
2571
    # Pkg needs access to the version of packages in the sysimage.
2572
    if generating_output(#=incremental=#false)
18✔
2573
        pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), ".."))
×
2574
    end
2575
    pkgorigin.path = path
18✔
2576
    nothing
18✔
2577
end
2578

2579
# Unused
2580
const PKG_PRECOMPILE_HOOK = Ref{Function}()
2581
disable_parallel_precompile::Bool = false
2582

2583
# Returns `nothing` or the new(ish) module
2584
function __require_prelocked(pkg::PkgId, env)
4✔
2585
    assert_havelock(require_lock)
8✔
2586

2587
    # perform the search operation to select the module file require intends to load
2588
    path = locate_package(pkg, env)
8✔
2589
    if path === nothing
4✔
2590
        throw(ArgumentError("""
×
2591
            Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2592
             - Run `Pkg.instantiate()` to install all recorded dependencies.
2593
            """))
2594
    end
2595
    set_pkgorigin_version_path(pkg, path)
4✔
2596

2597
    parallel_precompile_attempted = false # being safe to avoid getting stuck in a precompilepkgs loop
4✔
2598
    reasons = Dict{String,Int}()
4✔
2599
    # attempt to load the module file via the precompile cache locations
2600
    if JLOptions().use_compiled_modules != 0
4✔
2601
        @label load_from_cache
2602
        loaded = _require_search_from_serialized(pkg, path, UInt128(0), true; reasons)
4✔
2603
        if loaded isa Module
4✔
2604
            return loaded
4✔
2605
        end
2606
    end
2607

2608
    if JLOptions().use_compiled_modules == 3
×
2609
        error("Precompiled image $pkg not available with flags $(CacheFlags())")
×
2610
    end
2611

2612
    # if the module being required was supposed to have a particular version
2613
    # but it was not handled by the precompile loader, complain
2614
    for (concrete_pkg, concrete_build_id) in _concrete_dependencies
×
2615
        if pkg == concrete_pkg
×
2616
            @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
×
2617
                 This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2618
            if JLOptions().incremental != 0
×
2619
                # during incremental precompilation, this should be fail-fast
2620
                throw(PrecompilableError())
×
2621
            end
2622
        end
2623
    end
×
2624

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

2682
    # just load the file normally via include
2683
    # for unknown dependencies
2684
    uuid = pkg.uuid
×
2685
    uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
×
2686
    old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
×
2687
    if uuid !== old_uuid
×
2688
        ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
×
2689
    end
2690
    unlock(require_lock)
×
2691
    try
×
2692
        include(__toplevel__, path)
×
2693
        loaded = maybe_root_module(pkg)
×
2694
    finally
2695
        lock(require_lock)
×
2696
        if uuid !== old_uuid
×
2697
            ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
×
2698
        end
2699
    end
2700
    return loaded
×
2701
end
2702

2703
# load a serialized file directly, including dependencies (without checking staleness except for immediate conflicts)
2704
# this does not call start_loading / end_loading, so can lead to some odd behaviors
2705
function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}, sourcepath::String)
×
2706
    @lock require_lock begin
×
2707
    set_pkgorigin_version_path(uuidkey, sourcepath)
×
2708
    newm = _tryrequire_from_serialized(uuidkey, path, ocachepath)
×
2709
    newm isa Module || throw(newm)
×
2710
    insert_extension_triggers(uuidkey)
×
2711
    # After successfully loading, notify downstream consumers
2712
    run_package_callbacks(uuidkey)
×
2713
    return newm
×
2714
    end
2715
end
2716

2717
# load a serialized file directly from append_bundled_depot_path for uuidkey without stalechecks
2718
"""
2719
    require_stdlib(package_uuidkey::PkgId, [ext::String, from::Module])
2720

2721
!!! warning "May load duplicate copies of stdlib packages."
2722

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

2728
    The specific requirements are:
2729

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

2736
      The imported code (loaded stdlibs) must be very careful about type piracy:
2737
         - It must not access any global state that may differ between stdlib copies in
2738
           type-pirated methods.
2739
         - It must not return any stdlib types from any type-pirated public methods (since
2740
           a loaded duplicate would overwrite the Base method again, returning different
2741
           types that don't correspond to the user-accessible copy of the stdlib).
2742
         - It must not pass / discriminate stdlib types in type-pirated methods, except
2743
           indirectly via methods defined in Base and implemented (w/o type-piracy) in
2744
           all copies of the stdlib over their respective types.
2745

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

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

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

2811
# relative-path load
2812

2813
"""
2814
    include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")
2815

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

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

2822
!!! compat "Julia 1.5"
2823
    Julia 1.5 is required for passing the `mapexpr` argument.
2824
"""
2825
function include_string(mapexpr::Function, mod::Module, code::AbstractString,
6✔
2826
                        filename::AbstractString="string")
2827
    loc = LineNumberNode(1, Symbol(filename))
6✔
2828
    try
6✔
2829
        ast = Meta.parseall(code, filename=filename)
6✔
2830
        if !Meta.isexpr(ast, :toplevel)
6✔
2831
            @assert Core._lower != fl_lower
×
2832
            # Only reached when JuliaLowering and alternate parse functions are activated
2833
            return Core.eval(mod, ast)
×
2834
        end
2835
        result = nothing
6✔
2836
        line_and_ex = Expr(:toplevel, loc, nothing)
6✔
2837
        for ex in ast.args
6✔
2838
            if ex isa LineNumberNode
86✔
2839
                loc = ex
44✔
2840
                line_and_ex.args[1] = ex
44✔
2841
                continue
44✔
2842
            end
2843
            ex = mapexpr(ex)
42✔
2844
            # Wrap things to be eval'd in a :toplevel expr to carry line
2845
            # information as part of the expr.
2846
            line_and_ex.args[2] = ex
42✔
2847
            result = Core.eval(mod, line_and_ex)
42✔
2848
        end
85✔
2849
        return result
6✔
2850
    catch exc
2851
        # TODO: Now that stacktraces are more reliable we should remove
2852
        # LoadError and expose the real error type directly.
2853
        rethrow(LoadError(filename, loc.line, exc))
1✔
2854
    end
2855
end
2856

2857
include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
×
2858
    include_string(identity, m, txt, fname)
2859

2860
function source_path(default::Union{AbstractString,Nothing}="")
2861
    s = current_task().storage
6✔
2862
    if s !== nothing
6✔
2863
        s = s::IdDict{Any,Any}
6✔
2864
        if haskey(s, :SOURCE_PATH)
6✔
2865
            return s[:SOURCE_PATH]::Union{Nothing,String}
3✔
2866
        end
2867
    end
2868
    return default
3✔
2869
end
2870

2871
function source_dir()
×
2872
    p = source_path(nothing)
×
2873
    return p === nothing ? pwd() : dirname(p)
×
2874
end
2875

2876
"""
2877
    Base.include([mapexpr::Function,] m::Module, path::AbstractString)
2878

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

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

2891
!!! compat "Julia 1.5"
2892
    Julia 1.5 is required for passing the `mapexpr` argument.
2893
"""
2894
Base.include # defined in Base.jl
2895

2896
# Full include() implementation which is used after bootstrap
2897
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
6✔
2898
    @noinline # Workaround for module availability in _simplify_include_frames
6✔
2899
    path, prev = _include_dependency(mod, _path)
6✔
2900
    for callback in include_callbacks # to preserve order, must come before eval in include_string
6✔
2901
        invokelatest(callback, mod, path)
×
2902
    end
×
2903
    code = read(path, String)
6✔
2904
    tls = task_local_storage()
6✔
2905
    tls[:SOURCE_PATH] = path
6✔
2906
    try
6✔
2907
        return include_string(mapexpr, mod, code, path)
6✔
2908
    finally
2909
        if prev === nothing
6✔
2910
            delete!(tls, :SOURCE_PATH)
4✔
2911
        else
2912
            tls[:SOURCE_PATH] = prev
4✔
2913
        end
2914
    end
2915
end
2916

2917
"""
2918
    evalfile(path::AbstractString, args::Vector{String}=String[])
2919

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

2925
# Examples
2926

2927
```jldoctest
2928
julia> write("testfile.jl", \"\"\"
2929
           @show ARGS
2930
           1 + 1
2931
       \"\"\");
2932

2933
julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]);
2934
ARGS = ["ARG1", "ARG2"]
2935

2936
julia> x
2937
2
2938

2939
julia> rm("testfile.jl")
2940
```
2941
"""
2942
function evalfile(path::AbstractString, args::Vector{String}=String[])
×
2943
    m = Module(:__anon__)
×
2944
    return Core.eval(m,
×
2945
        Expr(:toplevel,
2946
             :(const ARGS = $args),
2947
             :(const include = $(Base.IncludeInto(m))),
2948
             :(const eval = $(Core.EvalInto(m))),
2949
             :(include($path))))
2950
end
2951
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
×
2952

2953
function load_path_setup_code(load_path::Bool=true)
×
2954
    code = """
×
2955
    append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH))))
2956
    append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH))))
2957
    """
2958
    if load_path
×
2959
        load_path = map(abspath, Base.load_path())
×
2960
        path_sep = Sys.iswindows() ? ';' : ':'
×
2961
        any(path -> path_sep in path, load_path) &&
×
2962
            error("LOAD_PATH entries cannot contain $(repr(path_sep))")
2963
        code *= """
×
2964
        append!(empty!(Base.LOAD_PATH), $(repr(load_path)))
2965
        ENV["JULIA_LOAD_PATH"] = $(repr(join(load_path, Sys.iswindows() ? ';' : ':')))
2966
        Base.set_active_project(nothing)
2967
        """
2968
    end
2969
    return code
×
2970
end
2971

2972
# Const global for GC root
2973
const newly_inferred = CodeInstance[]
2974

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

2979
    @lock require_lock begin
×
2980
    m = start_loading(pkg, UInt128(0), false)
×
2981
    @assert m === nothing
×
2982
    append!(empty!(Base.DEPOT_PATH), depot_path)
×
2983
    append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
×
2984
    append!(empty!(Base.LOAD_PATH), load_path)
×
2985
    ENV["JULIA_LOAD_PATH"] = join(load_path, Sys.iswindows() ? ';' : ':')
×
2986
    set_active_project(nothing)
×
2987
    Base._track_dependencies[] = true
×
2988
    get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
×
2989
    append!(empty!(Base._concrete_dependencies), concrete_deps)
×
2990
    end
2991

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

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

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

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

3022
function check_package_module_loaded_error(pkg)
×
3023
    # match compilecache error type for non-125 errors
3024
    error("package `$(pkg.name)` did not define the expected \
×
3025
          module `$(pkg.name)`, check for typos in package module name")
3026
end
3027

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

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

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

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

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

3120
function compilecache_dir(pkg::PkgId)
×
3121
    entrypath, entryfile = cache_file_entry(pkg)
×
3122
    return joinpath(DEPOT_PATH[1], entrypath)
×
3123
end
3124

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

3137
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
×
3138
        if cpu_target === nothing
×
3139
            cpu_target = unsafe_string(JLOptions().cpu_target)
×
3140
        end
3141
        crc = _crc32c(cpu_target, crc)
×
3142

3143
        crc = _crc32c(prefs_hash, crc)
×
3144
        project_precompile_slug = slug(crc, 5)
×
3145
        abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji"))
×
3146
    end
3147
end
3148

3149
"""
3150
    Base.compilecache(module::PkgId)
3151

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

3164
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
3165

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

3170
    @nospecialize internal_stderr internal_stdout
×
3171
    # decide where to put the resulting cache file
3172
    cachepath = compilecache_dir(pkg)
×
3173

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

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

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

3210
        if success(p)
×
3211
            if cache_objects
×
3212
                # Run linker over tmppath_o
3213
                Linking.link_image(tmppath_o, tmppath_so)
×
3214
            end
3215

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

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

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

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

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

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

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

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

3321
function isvalid_cache_header(f::IOStream)
54✔
3322
    pkgimage = Ref{UInt8}()
54✔
3323
    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
54✔
3324

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

3333
function isvalid_pkgimage_crc(f::IOStream, ocachefile::String)
18✔
3334
    seekstart(f) # TODO necessary
18✔
3335
    seek(f, filesize(f) - 8)
18✔
3336
    expected_crc_so = read(f, UInt32)
18✔
3337
    crc_so = open(_crc32c, ocachefile, "r")
18✔
3338
    expected_crc_so == crc_so
18✔
3339
end
3340

3341
mutable struct CacheHeaderIncludes
3342
    const id::PkgId
357✔
3343
    filename::String
3344
    const fsize::UInt64
3345
    const hash::UInt32
3346
    const mtime::Float64
3347
    const modpath::Vector{String}   # seemingly not needed in Base, but used by Revise
3348
end
3349

3350
function CacheHeaderIncludes(dep_tuple::Tuple{Module, String, UInt64, UInt32, Float64})
×
3351
    return CacheHeaderIncludes(PkgId(dep_tuple[1]), dep_tuple[2:end]..., String[])
×
3352
end
3353

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

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

3377
function restore_depot_path(path::AbstractString, depot::AbstractString)
3378
    replace(path, r"^@depot" => depot; count=1)
1,113✔
3379
end
3380

3381
function resolve_depot(inc::AbstractString)
357✔
3382
    startswith(inc, string("@depot", Filesystem.pathsep())) || return :not_relocatable
357✔
3383
    for depot in DEPOT_PATH
357✔
3384
        ispath(restore_depot_path(inc, depot)) && return depot
861✔
3385
    end
609✔
3386
    return :no_depot_found
105✔
3387
end
3388

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

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

3468
    srcfiles = srctext_files(f, srctextpos, includes)
54✔
3469

3470
    return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
54✔
3471
end
3472

3473
function parse_cache_header(f::IO, cachefile::AbstractString)
54✔
3474
    modules, (includes, srcfiles, requires), required_modules,
54✔
3475
        srctextpos, prefs, prefs_hash, clone_targets, flags = _parse_cache_header(f, cachefile)
3476

3477
    includes_srcfiles = CacheHeaderIncludes[]
54✔
3478
    includes_depfiles = CacheHeaderIncludes[]
54✔
3479
    for inc in includes
54✔
3480
        if inc.filename ∈ srcfiles
714✔
3481
            push!(includes_srcfiles, inc)
357✔
3482
        else
3483
            push!(includes_depfiles, inc)
×
3484
        end
3485
    end
357✔
3486

3487

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

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

3549
    return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags
54✔
3550
end
3551

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

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

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

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

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

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

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

3631
function srctext_files(f::IO, srctextpos::Int64, includes::Vector{CacheHeaderIncludes})
54✔
3632
    files = Set{String}()
54✔
3633
    srctextpos == 0 && return files
54✔
3634
    seek(f, srctextpos)
54✔
3635
    while !eof(f)
411✔
3636
        filenamelen = read(f, Int32)
411✔
3637
        filenamelen == 0 && break
411✔
3638
        filename = String(read(f, filenamelen))
714✔
3639
        len = read(f, UInt64)
357✔
3640
        push!(files, filename)
357✔
3641
        seek(f, position(f) + len)
357✔
3642
    end
357✔
3643
    return files
54✔
3644
end
3645

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

3676
function get_uuid_name(project_toml::String, uuid::UUID)
×
3677
    project = parsed_toml(project_toml)
×
3678
    return get_uuid_name(project, uuid)
×
3679
end
3680

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

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

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

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

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

3720
            # If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
3721
            break
×
3722
        end
3723
    end
×
3724

3725
    return dicts
×
3726
end
3727

3728
"""
3729
    recursive_prefs_merge(base::Dict, overrides::Dict...)
3730

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

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

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

3759
function get_projects_workspace_to_root(project_file)
18✔
3760
    projects = String[project_file]
18✔
3761
    while true
18✔
3762
        project_file = base_project(project_file)
18✔
3763
        if project_file === nothing
18✔
3764
            return projects
18✔
3765
        end
3766
        push!(projects, project_file)
×
3767
    end
×
3768
end
3769

3770
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
18✔
3771
    merged_prefs = Dict{String,Any}()
18✔
3772
    loadpath = load_path()
18✔
3773
    projects_to_merge_prefs = String[]
18✔
3774
    append!(projects_to_merge_prefs, Iterators.drop(loadpath, 1))
18✔
3775
    if length(loadpath) >= 1
18✔
3776
        prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath)))
36✔
3777
    end
3778

3779
    for env in reverse(projects_to_merge_prefs)
18✔
3780
        project_toml = env_project_file(env)
23✔
3781
        if !isa(project_toml, String)
23✔
3782
            continue
23✔
3783
        end
3784

3785
        # Collect all dictionaries from the current point in the load path, then merge them in
3786
        dicts = collect_preferences(project_toml, uuid)
×
3787
        merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
×
3788
    end
23✔
3789
    return merged_prefs
18✔
3790
end
3791

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

3798
    # Load the preferences
3799
    prefs = get_preferences(uuid)
18✔
3800

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

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

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

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

3829
function check_clone_targets(clone_targets)
3830
    rejection_reason = ccall(:jl_check_pkgimage_clones, Any, (Ptr{Cchar},), clone_targets)
18✔
3831
    if rejection_reason !== nothing
18✔
3832
        return rejection_reason
×
3833
    end
3834
end
3835

3836
# Set by FileWatching.__init__()
3837
global mkpidlock_hook::Any
3838
global trymkpidlock_hook::Any
3839
global parse_pidfile_hook::Any
3840

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

3846
const compilecache_pidlock_stale_age = 10
3847

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

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

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

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

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

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

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

4104
        if !isvalid_file_crc(io)
18✔
4105
            @debug "Rejecting cache file $cachefile because it has an invalid checksum"
×
4106
            record_reason(reasons, "invalid checksum")
×
4107
            return true
×
4108
        end
4109

4110
        if pkgimage
18✔
4111
            if !isvalid_pkgimage_crc(io, ocachefile::String)
18✔
4112
                @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum"
×
4113
                record_reason(reasons, "ocachefile invalid checksum")
×
4114
                return true
×
4115
            end
4116
        end
4117

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

4125
        return depmods, ocachefile, id_build # fresh cachefile
18✔
4126
    finally
4127
        close(io)
18✔
4128
    end
4129
end
4130

4131
"""
4132
    @__FILE__ -> String
4133

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

4144
"""
4145
    @__DIR__ -> String
4146

4147
Macro to obtain the absolute path of the current directory as a string.
4148

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

4152
# Examples
4153

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

4157
```julia-repl
4158
julia> cd("/home/JuliaUser") # working directory
4159

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

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

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

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

4191
"""
4192
    isprecompilable(f, argtypes::Tuple{Vararg{Any}})
4193

4194
Check, as far as is possible without actually compiling, if the given
4195
function `f` can be compiled for the argument tuple (of types) `argtypes`.
4196
"""
4197
function isprecompilable(@nospecialize(f), @nospecialize(argtypes::Tuple))
×
4198
    isprecompilable(Tuple{Core.Typeof(f), argtypes...})
×
4199
end
4200

4201
function isprecompilable(@nospecialize(argt::Type))
×
4202
    ccall(:jl_is_compilable, Int32, (Any,), argt) != 0
×
4203
end
4204

4205
"""
4206
    precompile(f, argtypes::Tuple{Vararg{Any}})
4207

4208
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
4209
"""
4210
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
4211
    precompile(Tuple{Core.Typeof(f), argtypes...})
×
4212
end
4213

4214
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
4215
function precompile(@nospecialize(argt::Type))
×
4216
    ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
×
4217
    if !ret && ENABLE_PRECOMPILE_WARNINGS[]
×
4218
        @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
×
4219
    end
4220
    return ret
×
4221
end
4222

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

4227
"""
4228
    precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
4229

4230
Precompile a specific method for the given argument types. This may be used to precompile
4231
a different method than the one that would ordinarily be chosen by dispatch, thus
4232
mimicking `invoke`.
4233
"""
4234
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
×
4235
    precompile(Tuple{Core.Typeof(f), argtypes...}, m)
×
4236
end
4237

4238
function precompile(@nospecialize(argt::Type), m::Method)
×
4239
    atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
×
4240
    mi = Base.Compiler.specialize_method(m, atype, sparams)
×
4241
    return precompile(mi)
×
4242
end
4243

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

© 2026 Coveralls, Inc