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

JuliaLang / julia / 1431

05 Feb 2026 12:37AM UTC coverage: 76.655% (-0.07%) from 76.729%
1431

push

buildkite

web-flow
doc: Clarify policy on working on issues i.e. anti-cookie licking (#60925)

62930 of 82095 relevant lines covered (76.66%)

23295584.69 hits per line

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

76.62
/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)
36,492✔
11
elseif Sys.iswindows()
12
    # GetLongPathName Win32 function returns the case-preserved filename on NTFS.
13
    function isfile_casesensitive(path)
24,085✔
14
        isaccessiblefile(path) || return false  # Fail fast
37,870✔
15
        basename(Filesystem.longpath(path)) == basename(path)
10,300✔
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)
45,854✔
47
        isaccessiblefile(path) || return false
72,987✔
48
        path_basename = String(basename(path))
18,721✔
49
        local casepreserved_basename
50
        header_size = 12
18,721✔
51
        buf = Vector{UInt8}(undef, length(path_basename) + header_size + 1)
18,721✔
52
        while true
18,721✔
53
            ret = ccall(:getattrlist, Cint,
18,721✔
54
                        (Cstring, Ptr{Cvoid}, Ptr{Cvoid}, Csize_t, Culong),
55
                        path, attr_list, buf, sizeof(buf), FSOPT_NOFOLLOW)
56
            systemerror(:getattrlist, ret ≠ 0)
18,721✔
57
            filename_length = GC.@preserve buf unsafe_load(
18,721✔
58
              convert(Ptr{UInt32}, pointer(buf) + 8))
59
            if (filename_length + header_size) > length(buf)
18,721✔
60
                resize!(buf, filename_length + header_size)
×
61
                continue
×
62
            end
63
            casepreserved_basename =
18,721✔
64
              view(buf, (header_size+1):(header_size+filename_length-1))
65
            break
18,721✔
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
18,721✔
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)
56✔
87
    return try
56✔
88
        isdir(dir)
56✔
89
    catch err
90
        err isa IOError || rethrow()
3✔
91
        false
3✔
92
    end
93
end
94

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

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

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

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

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

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

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

186
function slug(x::UInt32, p::Int)
4✔
187
    sprint(sizehint=p) do io
4,511✔
188
        y = x
4,511✔
189
        n = UInt32(length(slug_chars))
4,511✔
190
        for i = 1:p
9,018✔
191
            y, d = divrem(y, n)
22,552✔
192
            write(io, slug_chars[1+d])
45,084✔
193
        end
22,552✔
194
    end
195
end
196

197
function package_slug(uuid::UUID, p::Int=5)
4,183✔
198
    crc = _crc32c(uuid)
8,360✔
199
    return slug(crc, p)
4,180✔
200
end
201

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

208
mutable struct CachedTOMLDict
209
    path::String
3,336✔
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)
3,336✔
218
    s = stat(path)
3,336✔
219
    content = read(path)
3,336✔
220
    crc32 = _crc32c(content)
3,336✔
221
    TOML.reinit!(p, String(content); filepath=path)
6,671✔
222
    d = TOML.parse(p)
3,336✔
223
    return CachedTOMLDict(
3,336✔
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)
7,077✔
234
    s = stat(f.path)
7,077✔
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
14,154✔
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
7,077✔
251
end
252

253
"""
254
    struct PkgLoadSpec
255

256
A PkgLoadSpec is the result of a `locate_package` operation and specifies how
257
and wherefrom to load a julia package.
258
"""
259
struct PkgLoadSpec
260
    path::String
5,803✔
261
    julia_syntax_version::VersionNumber
262
end
263

264
struct LoadingCache
265
    load_path::Vector{String}
1,503✔
266
    dummy_uuid::Dict{String, UUID}
267
    env_project_file::Dict{String, Union{Bool, String}}
268
    project_file_manifest_path::Dict{String, Union{Nothing, String}}
269
    require_parsed::Set{String}
270
    identified_where::Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, String}}}
271
    identified::Dict{String, Union{Nothing, Tuple{PkgId, String}}}
272
    located::Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{PkgLoadSpec, String}, Nothing}}
273
end
274
const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing) # n.b.: all access to and through this are protected by require_lock
275
LoadingCache() = LoadingCache(
1,503✔
276
    load_path(),
277
    Dict{String, UUID}(),
278
    Dict{String, Union{Bool, String}}(),
279
    Dict{String, Union{Nothing, String}}(),
280
    Set{String}(),
281
    Dict{Tuple{PkgId, String}, Union{Nothing, Tuple{PkgId, String}}}(),
282
    Dict{String, Union{Nothing, Tuple{PkgId, String}}}(),
283
    Dict{Tuple{PkgId, Union{String, Nothing}}, Union{Tuple{PkgLoadSpec, String}, Nothing}}()
284
)
285

286

287
struct TOMLCache{Dates}
288
    p::TOML.Parser{Dates}
289
    d::Dict{String, CachedTOMLDict}
290
end
291
TOMLCache(p::TOML.Parser) = TOMLCache(p, Dict{String, CachedTOMLDict}())
×
292
TOMLCache(p::TOML.Parser, d::Dict{String, Dict{String, Any}}) = TOMLCache(p, convert(Dict{String, CachedTOMLDict}, d))
×
293

294
const TOML_CACHE = TOMLCache(TOML.Parser{nothing}())
295

296
parsed_toml(project_file::AbstractString) = parsed_toml(project_file, TOML_CACHE, require_lock)
34,147✔
297
function parsed_toml(project_file::AbstractString, toml_cache::TOMLCache, toml_lock::ReentrantLock)
3✔
298
    lock(toml_lock) do
34,435✔
299
        cache = LOADING_CACHE[]
34,423✔
300
        dd = if !haskey(toml_cache.d, project_file)
68,425✔
301
            d = CachedTOMLDict(toml_cache.p, project_file)
3,336✔
302
            toml_cache.d[project_file] = d
3,336✔
303
            d.d
3,336✔
304
        else
305
            d = toml_cache.d[project_file]
31,087✔
306
            # We are in a require call and have already parsed this TOML file
307
            # assume that it is unchanged to avoid hitting disk
308
            if cache !== nothing && project_file in cache.require_parsed
55,334✔
309
                d.d
24,010✔
310
            else
311
                get_updated_dict(toml_cache.p, d)
65,510✔
312
            end
313
        end
314
        if cache !== nothing
34,423✔
315
            push!(cache.require_parsed, project_file)
27,983✔
316
        end
317
        return dd
34,423✔
318
    end
319
end
320

321
## package identification: determine unique identity of package to be loaded ##
322

323
# Used by Pkg but not used in loading itself
324
function find_package(arg) # ::Union{Nothing,String}
873✔
325
    @lock require_lock begin
873✔
326
    pkgenv = identify_package_env(arg)
873✔
327
    pkgenv === nothing && return nothing
873✔
328
    pkg, env = pkgenv
873✔
329
    return locate_package(pkg, env)
873✔
330
    end
331
end
332

333
# is there a better/faster ground truth?
334
function is_stdlib(pkgid::PkgId)
26✔
335
    pkgid.name in readdir(Sys.STDLIB) || return false
26✔
336
    stdlib_root = joinpath(Sys.STDLIB, pkgid.name)
26✔
337
    project_file = locate_project_file(stdlib_root)
52✔
338
    if project_file isa String
26✔
339
        d = parsed_toml(project_file)
26✔
340
        uuid = get(d, "uuid", nothing)
52✔
341
        if uuid !== nothing
26✔
342
            return UUID(uuid) == pkgid.uuid
26✔
343
        end
344
    end
345
    return false
×
346
end
347

348
"""
349
    Base.identify_package_env(name::String)::Union{Tuple{PkgId, String}, Nothing}
350
    Base.identify_package_env(where::Union{Module,PkgId}, name::String)::Union{Tuple{PkgId, Union{String, Nothing}}, Nothing}
351

352
Same as [`Base.identify_package`](@ref) except that the path to the environment where the package is identified
353
is also returned, except when the identity is not identified.
354
"""
355
identify_package_env(where::Module, name::String) = identify_package_env(PkgId(where), name)
1,608✔
356
function identify_package_env(where::PkgId, name::String)
5,841✔
357
    # Special cases
358
    if where.name === name
5,841✔
359
        # Project tries to load itself
360
        return (where, nothing)
×
361
    elseif where.uuid === nothing
5,841✔
362
        # Project without Project.toml - treat as toplevel load
363
        return identify_package_env(nothing, name)
1,626✔
364
    end
365

366
    # Check if we have a cached answer for this
367
    assert_havelock(require_lock)
8,430✔
368
    cache = LOADING_CACHE[]
4,215✔
369
    cache_key = (where, name)
4,215✔
370
    if cache !== nothing
4,215✔
371
        pkg_env = get(cache.identified_where, cache_key, missing)
4,399✔
372
        pkg_env === missing || return pkg_env
4,399✔
373
    end
374

375
    # Main part: Search through all environments in the load path to see if we have
376
    # a matching entry.
377
    pkg_env = nothing
3,871✔
378
    for env in load_path()
3,871✔
379
        pkgid = environment_deps_get(env, where, name)
4,204✔
380
        # If we didn't find `where` at all, keep looking through the environment stack
381
        pkgid === nothing && continue
4,204✔
382
        if pkgid.uuid !== nothing
3,871✔
383
            pkg_env = (pkgid, env)
3,848✔
384
        end
385
        # If we don't have pkgid.uuid, still break here - this is a sentinel that indicates
386
        # that we've found `where` but it did not have the required dependency. We terminate the search.
387
        break
3,871✔
388
    end
333✔
389
    if pkg_env === nothing && is_stdlib(where)
3,871✔
390
        # if not found it could be that manifests are from a different julia version/commit
391
        # where stdlib dependencies have changed, so look up deps based on the stdlib Project.toml
392
        # as a fallback
393
        pkg_env = identify_stdlib_project_dep(where, name)
23✔
394
    end
395

396
    # Cache the result
397
    if cache !== nothing
3,871✔
398
        cache.identified_where[cache_key] = pkg_env
7,422✔
399
    end
400
    return pkg_env
3,871✔
401
end
402
function identify_package_env(where::Nothing, name::String)
2,543✔
403
    # Check if we have a cached answer for this
404
    assert_havelock(require_lock)
5,085✔
405
    cache = LOADING_CACHE[]
2,543✔
406
    if cache !== nothing
2,543✔
407
        pkg_env = get(cache.identified, name, missing)
1,509✔
408
        pkg_env === missing || return pkg_env
1,509✔
409
    end
410

411
    # Main part: Search through all environments in the load path to see if we have
412
    # a matching entry.
413
    pkg_env = nothing
2,537✔
414
    for env in load_path()
2,537✔
415
        pkgid = environment_deps_get(env, nothing, name)
3,159✔
416
        # If we didn't find `where` at all, keep looking through the environment stack
417
        pkgid === nothing && continue
2,835✔
418
        pkg_env = (pkgid, env)
2,524✔
419
        break
2,524✔
420
    end
311✔
421

422
    # Cache the result
423
    if cache !== nothing
2,537✔
424
        cache.identified[name] = pkg_env
2,994✔
425
    end
426
    return pkg_env
2,537✔
427
end
428
identify_package_env(name::String) = identify_package_env(nothing, name)
923✔
429

430
function identify_stdlib_project_dep(stdlib::PkgId, depname::String)
23✔
431
    @debug """
23✔
432
    Stdlib $(repr("text/plain", stdlib)) is trying to load `$depname`
433
    which is not listed as a dep in the load path manifests, so resorting to search
434
    in the stdlib Project.tomls for true deps"""
435
    stdlib_projfile = locate_project_file(joinpath(Sys.STDLIB, stdlib.name))
46✔
436
    stdlib_projfile === nothing && return nothing
23✔
437
    found = explicit_project_deps_get(stdlib_projfile, depname)
23✔
438
    if found !== nothing
23✔
439
        @debug "$(repr("text/plain", stdlib)) indeed depends on $depname in project $stdlib_projfile"
23✔
440
        pkgid = PkgId(found, depname)
23✔
441
        return pkgid, stdlib_projfile
23✔
442
    end
443
    return nothing
×
444
end
445

446
_nothing_or_first(x) = x === nothing ? nothing : first(x)
4,369✔
447

448
"""
449
    Base.identify_package(name::String)::Union{PkgId, Nothing}
450
    Base.identify_package(where::Union{Module,PkgId}, name::String)::Union{PkgId, Nothing}
451

452
Identify the package by its name from the current environment stack, returning
453
its `PkgId`, or `nothing` if it cannot be found.
454

455
If only the `name` argument is provided, it searches each environment in the
456
stack and its named direct dependencies.
457

458
The `where` argument provides the context from where to search for the
459
package: in this case it first checks if the name matches the context itself,
460
otherwise it searches all recursive dependencies (from the resolved manifest of
461
each environment) until it locates the context `where`, and from there
462
identifies the dependency with the corresponding name.
463

464
```jldoctest
465
julia> Base.identify_package("Pkg") # Pkg is a dependency of the default environment
466
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
467

468
julia> using LinearAlgebra
469

470
julia> Base.identify_package(LinearAlgebra, "Pkg") # Pkg is not a dependency of LinearAlgebra
471
```
472
"""
473
identify_package(where::Module, name::String) = @lock require_lock _nothing_or_first(identify_package_env(where, name))
114✔
474
identify_package(where::PkgId, name::String)  = @lock require_lock _nothing_or_first(identify_package_env(where, name))
4,221✔
475
identify_package(name::String)                = @lock require_lock _nothing_or_first(identify_package_env(name))
47✔
476

477
function locate_package_env(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,Tuple{PkgLoadSpec, String}}
10,719✔
478
    assert_havelock(require_lock)
24,651✔
479
    cache = LOADING_CACHE[]
10,719✔
480
    if cache !== nothing
10,719✔
481
        specenv = get(cache.located, (pkg, stopenv), missing)
14,291✔
482
        specenv === missing || return specenv
14,291✔
483
    end
484
    (env′, spec) = @label _ begin
5,665✔
485
        if pkg.uuid === nothing
5,665✔
486
            # The project we're looking for does not have a Project.toml (n.b. - present
487
            # `Project.toml` without UUID gets a path-based dummy UUID). It must have
488
            # come from an implicit manifest environment, so go through those only.
489
            # N.B.: Implicitly loaded packages do not participate in syntax versioning.
490
            for env in load_path()
552✔
491
                project_file = env_project_file(env)
564✔
492
                (project_file isa Bool && project_file) || continue
564✔
493
                found = implicit_manifest_pkgid(env, pkg.name)
1,110✔
494
                if found !== nothing && found.uuid === nothing
564✔
495
                    @assert found.name == pkg.name
546✔
496
                    break _ (env, implicit_manifest_uuid_load_spec(env, pkg))
546✔
497
                end
498
                if !(loading_extension || precompiling_extension)
36✔
499
                    stopenv == env && break _ (nothing, nothing)
18✔
500
                end
501
            end
18✔
502
        else
503
            for env in load_path()
5,113✔
504
                spec = manifest_uuid_load_spec(env, pkg)
5,498✔
505
                # missing is used as a sentinel to stop looking further down in envs
506
                if spec === missing
5,498✔
507
                    is_stdlib(pkg) && break
3✔
508
                    break _ (nothing, nothing)
×
509
                end
510
                if spec !== nothing
5,495✔
511
                    break _ (env, spec)
5,107✔
512
                end
513
                if !(loading_extension || precompiling_extension)
770✔
514
                    stopenv == env && break
382✔
515
                end
516
            end
388✔
517
            # Allow loading of stdlibs if the name/uuid are given
518
            # e.g. if they have been explicitly added to the project/manifest
519
            mbyspec = manifest_uuid_load_spec(Sys.STDLIB, pkg)
6✔
520
            if mbyspec isa PkgLoadSpec
6✔
521
                break _ (Sys.STDLIB, mbyspec)
3✔
522
            end
523
        end
524
        (nothing, nothing)
11,330✔
525
    end
526
    if spec !== nothing && !isfile_casesensitive(spec.path)
5,665✔
527
        spec = nothing
×
528
    end
529
    if cache !== nothing
5,665✔
530
        cache.located[(pkg, stopenv)] = spec === nothing ? nothing : (spec, something(env′))
8,363✔
531
    end
532
    spec === nothing && return nothing
5,665✔
533
    return spec, something(env′)
5,656✔
534
end
535

536
"""
537
    Base.locate_package(pkg::PkgId)::Union{String, Nothing}
538

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

542
```julia-repl
543
julia> pkg = Base.identify_package("Pkg")
544
Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f]
545

546
julia> Base.locate_package(pkg)
547
"/path/to/julia/stdlib/v$(VERSION.major).$(VERSION.minor)/Pkg/src/Pkg.jl"
548
```
549
"""
550
function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String}
882✔
551
    @lock require_lock begin
162,404✔
552
        specenv = locate_package_env(pkg, stopenv)
876✔
553
        specenv === nothing && return nothing
876✔
554
        specenv[1].path
873✔
555
    end
556
end
557

558
function locate_package_load_spec(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,PkgLoadSpec}
5,647✔
559
    @lock require_lock begin
11,295✔
560
        specenv = locate_package_env(pkg, stopenv)
5,646✔
561
        specenv === nothing && return nothing
5,646✔
562
        specenv[1]
5,634✔
563
    end
564
end
565

566
"""
567
    pathof(m::Module)
568

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

572
Use [`dirname`](@ref) to get the directory part and [`basename`](@ref)
573
to get the file name part of the path.
574

575
See also [`pkgdir`](@ref).
576
"""
577
function pathof(m::Module)
135✔
578
    @lock require_lock begin
135✔
579
    pkgid = PkgId(m)
135✔
580
    origin = get(pkgorigins, pkgid, nothing)
270✔
581
    origin === nothing && return nothing
135✔
582
    path = origin.path
135✔
583
    path === nothing && return nothing
135✔
584
    return fixup_stdlib_path(path)
135✔
585
    end
586
end
587

588
"""
589
    pkgdir(m::Module[, paths::String...])
590

591
Return the root directory of the package that declared module `m`,
592
or `nothing` if `m` was not declared in a package. Optionally further
593
path component strings can be provided to construct a path within the
594
package root.
595

596
To get the root directory of the package that implements the current module
597
the form `pkgdir(@__MODULE__)` can be used.
598

599
If an extension module is given, the root of the parent package is returned.
600

601
```julia-repl
602
julia> pkgdir(Foo)
603
"/path/to/Foo.jl"
604

605
julia> pkgdir(Foo, "src", "file.jl")
606
"/path/to/Foo.jl/src/file.jl"
607
```
608

609
See also [`pathof`](@ref).
610

611
!!! compat "Julia 1.7"
612
    The optional argument `paths` requires at least Julia 1.7.
613
"""
614
function pkgdir(m::Module, paths::String...)
144✔
615
    rootmodule = moduleroot(m)
156✔
616
    path = pathof(rootmodule)
144✔
617
    path === nothing && return nothing
144✔
618
    original = path
141✔
619
    path, base = splitdir(dirname(path))
141✔
620
    if base == "src"
141✔
621
        # package source in `../src/Foo.jl`
622
    elseif base == "ext"
66✔
623
        # extension source in `../ext/FooExt.jl`
624
    elseif basename(path) == "ext"
12✔
625
        # extension source in `../ext/FooExt/FooExt.jl`
626
        path = dirname(path)
12✔
627
    else
628
        error("Unexpected path structure for module source: $original")
×
629
    end
630
    return joinpath(path, paths...)
141✔
631
end
632

633
function get_pkgversion_from_path(path)
66✔
634
    project_file = locate_project_file(path)
132✔
635
    if project_file isa String
66✔
636
        d = parsed_toml(project_file)
66✔
637
        v = get(d, "version", nothing)
132✔
638
        if v !== nothing
66✔
639
            return VersionNumber(v::String)
66✔
640
        end
641
    end
642
    return nothing
×
643
end
644

645
"""
646
    pkgversion(m::Module)
647

648
If the module `m` belongs to a versioned package, return the
649
version number of that package. Otherwise return `nothing`.
650

651
The version is read from the package's Project.toml during package
652
load.
653

654
To get the version of the package that imported the current module
655
the form `pkgversion(@__MODULE__)` can be used.
656

657
!!! compat "Julia 1.9"
658
    This function was introduced in Julia 1.9.
659
"""
660
function pkgversion(m::Module)
66✔
661
    path = pkgdir(m)
66✔
662
    path === nothing && return nothing
66✔
663
    @lock require_lock begin
66✔
664
        v = get_pkgversion_from_path(path)
66✔
665
        pkgorigin = get(pkgorigins, PkgId(moduleroot(m)), nothing)
132✔
666
        # Cache the version
667
        if pkgorigin !== nothing && pkgorigin.version === nothing
66✔
668
            pkgorigin.version = v
108✔
669
        end
670
        return v
66✔
671
    end
672
end
673

674
## generic project & manifest API ##
675

676
const project_names = ("JuliaProject.toml", "Project.toml")
677
const manifest_names = (
678
    "JuliaManifest-v$(VERSION.major).$(VERSION.minor).toml",
679
    "Manifest-v$(VERSION.major).$(VERSION.minor).toml",
680
    "JuliaManifest.toml",
681
    "Manifest.toml",
682
)
683
const preferences_names = ("JuliaLocalPreferences.toml", "LocalPreferences.toml")
684

685
function locate_project_file(env::String)
2✔
686
    for proj in project_names
18,197✔
687
        project_file = joinpath(env, proj)
36,394✔
688
        if isfile_casesensitive(project_file)
36,394✔
689
            return project_file
1,188✔
690
        end
691
    end
52,213✔
692
    return true
17,009✔
693
end
694

695
# classify the LOAD_PATH entry to be one of:
696
#  - `false`: nonexistent / nothing to see here
697
#  - `true`: `env` is an implicit environment
698
#  - `path`: the path of an explicit project file
699
function env_project_file(env::String)::Union{Bool,String}
61,305✔
700
    @lock require_lock begin
61,305✔
701
    cache = LOADING_CACHE[]
61,305✔
702
    if cache !== nothing
61,305✔
703
        project_file = get(cache.env_project_file, env, nothing)
93,391✔
704
        project_file === nothing || return project_file
93,391✔
705
    end
706
    if isdir(env)
19,092✔
707
        project_file = locate_project_file(env)
53,009✔
708
    elseif basename(env) in project_names && isfile_casesensitive(env)
2,180✔
709
        project_file = env
1,054✔
710
    else
711
        project_file = false
36✔
712
    end
713
    if cache !== nothing
19,092✔
714
        cache.env_project_file[env] = project_file
9,509✔
715
    end
716
    return project_file
19,092✔
717
    end
718
end
719

720
function base_project(project_file)
3,743✔
721
    home_dir = abspath(homedir())
3,743✔
722
    project_dir = abspath(dirname(project_file))
3,743✔
723
    current_dir = project_dir
3,743✔
724
    # Only stop at home boundary if we started under home
725
    started_in_home = startswith(project_dir, home_dir)
3,743✔
726

727
    while true
44,611✔
728
        parent_dir = dirname(current_dir)
44,611✔
729
        # Stop if we've reached root
730
        if parent_dir == current_dir
44,611✔
731
            return nothing
3,602✔
732
        end
733
        # Stop if we started in home and have now left it
734
        if started_in_home && !startswith(parent_dir, home_dir)
41,009✔
735
            return nothing
141✔
736
        end
737

738
        base_project_file = env_project_file(parent_dir)
40,868✔
739
        if base_project_file isa String
40,868✔
740
            d = parsed_toml(base_project_file)
847✔
741
            workspace = get(d, "workspace", nothing)::Union{Dict{String, Any}, Nothing}
847✔
742
            if workspace !== nothing
847✔
743
                projects = get(workspace, "projects", nothing)::Union{Vector{String}, Nothing, String}
×
744
                if projects isa Vector
×
745
                    # Check if any project in the workspace matches the original project
746
                    workspace_root = dirname(base_project_file)
×
747
                    for project in projects
×
748
                        project_path = joinpath(workspace_root, project)
×
749
                        if isdir(project_path)
×
750
                            if samefile(project_path, project_dir)
×
751
                                return base_project_file
×
752
                            end
753
                        end
754
                    end
×
755
                end
756
            end
757
        end
758
        current_dir = parent_dir
40,868✔
759
    end
40,868✔
760
end
761

762
function package_get_here(project_file, name::String)
763
    # if `where` matches the project, use [deps] section as manifest, and stop searching
764
    pkg_uuid = explicit_project_deps_get(project_file, name)
3,891✔
765
    pkg_uuid === nothing && return PkgId(name)
3,891✔
766
    return PkgId(pkg_uuid, name)
3,866✔
767
end
768

769
function package_get(project_file, where::Union{Nothing, PkgId}, name::String)
770
    if where !== nothing
4,265✔
771
        proj = project_file_name_uuid(project_file, where.name)
3,928✔
772
        proj != where && return nothing
7,856✔
773
    end
774
    return package_get_here(project_file, name)
4,167✔
775
end
776

777
ext_may_load_weakdep(exts::String, name::String) = exts == name
54✔
778
ext_may_load_weakdep(exts::Vector{String}, name::String) = name in exts
129✔
779

780
function package_extension_get(project_file, where::PkgId, name::String)
410✔
781
    d = parsed_toml(project_file)
410✔
782
    exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
548✔
783
    if exts !== nothing
410✔
784
        proj = project_file_name_uuid(project_file, where.name)
138✔
785
        # Check if `where` is an extension of the project
786
        if where.name in keys(exts) && where.uuid == uuid5(proj.uuid::UUID, where.name)
198✔
787
            # Extensions can load weak deps if they are an extension trigger
788
            if ext_may_load_weakdep(exts[where.name]::Union{String, Vector{String}}, name)
90✔
789
                weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing}
66✔
790
                if weakdeps !== nothing
33✔
791
                    wuuid = get(weakdeps, name, nothing)::Union{String, Nothing}
57✔
792
                    if wuuid !== nothing
33✔
793
                        return PkgId(UUID(wuuid), name)
24✔
794
                    end
795
                end
796
            end
797
            # ... and they can load same deps as the project itself
798
            return package_get_here(project_file, name)
36✔
799
        end
800
    end
801
    return nothing
350✔
802
end
803

804
function environment_deps_get(env::String, where::Union{Nothing,PkgId}, name::String)::Union{Nothing,PkgId}
4,205✔
805
    @assert where === nothing || where.uuid !== nothing
7,026✔
806
    project_file = env_project_file(env)
7,039✔
807
    implicit_manifest = !(project_file isa String)
7,039✔
808
    if implicit_manifest
7,039✔
809
        project_file || return nothing
6,355✔
810
        if where === nothing
6,126✔
811
            # Toplevel load with a directory (implicit manifest) - all we look for is the
812
            # existence of the package name in the directory.
813
            pkg = implicit_manifest_pkgid(env, name)
2,804✔
814
            return pkg
2,480✔
815
        end
816
        project_file = implicit_manifest_project(env, where)
3,659✔
817
        project_file === nothing && return nothing
3,659✔
818
    end
819

820
    # Are we
821
    #    a) loading into a top-level project itself
822
    #    b) loading into a non-top-level project that was part of an implicit
823
    #       manifest environment (and for which we found the project file above)
824
    #    c) performing a top-level load (where === nothing) - i.e. we're looking
825
    #       at an environment's project file.
826
    #
827
    # If so, we may load either:
828
    #   I: the project itself (if name matches where)
829
    #   II: a dependency from [deps] section of the project file
830
    #
831
    # N.B.: Here "top-level" includes package loaded from an implicit manifest, which
832
    #       uses the same code path. Otherwise this is the active project.
833
    pkg = package_get(project_file, where, name)
8,095✔
834
    if pkg !== nothing
4,265✔
835
        if where === nothing && pkg.uuid === nothing
3,855✔
836
            # This is a top-level load - even though we didn't find the dependency
837
            # here, we still want to keep looking through the top-level environment stack.
838
            return nothing
25✔
839
        end
840
        return pkg
3,830✔
841
    end
842

843
    @assert where !== nothing
410✔
844

845
    # Are we an extension of a project from cases a), b) above
846
    # If so, in addition to I, II above, we get:
847
    #   III: A dependency from [weakdeps] section of the project file as long
848
    #        as it is an extension trigger for `where` in the `extensions` section.
849
    pkg = package_extension_get(project_file, where, name)
410✔
850
    pkg === nothing || return pkg
470✔
851

852
    if implicit_manifest
350✔
853
        # With an implicit manifest, getting here means that our (implicit) environment
854
        # *has* the package `where`. If we don't find it, it just means that `where` doesn't
855
        # have `name` as a dependency - c.f. the analogous case in `explicit_manifest_deps_get`.
856
        return PkgId(name)
×
857
    end
858

859
    # All other cases, dependencies come from the (top-level) manifest
860
    return explicit_manifest_deps_get(project_file, where, name)
350✔
861
end
862

863
function manifest_uuid_load_spec(env::String, pkg::PkgId)::Union{Nothing,PkgLoadSpec,Missing}
5,537✔
864
    project_file = env_project_file(env)
5,537✔
865
    if project_file isa String
5,537✔
866
        proj = project_file_name_uuid(project_file, pkg.name)
961✔
867
        if proj == pkg
1,922✔
868
            # if `pkg` matches the project, return the project itself
869
            return project_file_load_spec(project_file, pkg.name)
180✔
870
        end
871
        mby_ext = project_file_ext_load_spec(project_file, pkg)
781✔
872
        mby_ext === nothing || return mby_ext
880✔
873
        # look for manifest file and `where` stanza
874
        return explicit_manifest_uuid_load_spec(project_file, pkg)
682✔
875
    elseif project_file
4,576✔
876
        # if env names a directory, search it
877
        # Implicit environments do not participate in syntax versioning
878
        proj = implicit_manifest_uuid_load_spec(env, pkg)
4,492✔
879
        proj === nothing || return proj
8,798✔
880
        # if not found, this might be an extension - first we fast path needing
881
        # to scan the whole directory for a matching extension by peeking at
882
        # EXT_PRIMED. However, this only works if the parent package was loaded.
883
        # This is usually the case, but not always, e.g. in precompilation.
884
        triggers = get(EXT_PRIMED, pkg, nothing)
195✔
885
        if triggers !== nothing
186✔
886
            parentid = triggers[1]
9✔
887
            _, parent_project_file = entry_point_and_project_file(env, parentid.name)
9✔
888
            if parent_project_file !== nothing
9✔
889
                parentproj = project_file_name_uuid(parent_project_file, parentid.name)
×
890
                if parentproj == parentid
×
891
                    mby_ext = project_file_ext_load_spec(parent_project_file, pkg)
×
892
                    mby_ext === nothing || return mby_ext
×
893
                end
894
            end
895
        else
896
            # We still need to scan the whole directory for extensions.
897
            ext_ls, ext_proj = implicit_env_project_file_extension(env, pkg)
177✔
898
            ext_ls === nothing || return ext_ls
177✔
899
        end
900
    end
901
    return nothing
270✔
902
end
903

904

905
function find_ext_path(project_path::String, extname::String)
210✔
906
    extfiledir = joinpath(project_path, "ext", extname, extname * ".jl")
210✔
907
    isfile(extfiledir) && return extfiledir
210✔
908
    return joinpath(project_path, "ext", extname * ".jl")
174✔
909
end
910

911
function project_file_ext_load_spec(project_file::String, ext::PkgId)
1,153✔
912
    d = parsed_toml(project_file)
1,153✔
913
    p = dirname(project_file)
1,153✔
914
    exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing}
1,477✔
915
    if exts !== nothing
1,153✔
916
        if ext.name in keys(exts) && ext.uuid == uuid5(UUID(d["uuid"]::String), ext.name)
423✔
917
            # Syntax version of the main package applies to its extensions
918
            return PkgLoadSpec(find_ext_path(p, ext.name), project_get_syntax_version(d))
99✔
919
        end
920
    end
921
    return nothing
1,054✔
922
end
923

924
# find project file's top-level UUID entry (or nothing)
925
function project_file_name_uuid(project_file::String, name::String)::PkgId
17,920✔
926
    d = parsed_toml(project_file)
17,920✔
927
    uuid′ = get(d, "uuid", nothing)::Union{String, Nothing}
35,466✔
928
    uuid = uuid′ === nothing ? dummy_uuid(project_file) : UUID(uuid′)
35,468✔
929
    name = get(d, "name", name)::String
35,466✔
930
    return PkgId(uuid, name)
17,920✔
931
end
932

933
const NON_VERSIONED_SYNTAX = v"1.13"
934

935
function project_get_syntax_version(d::Dict)
4,585✔
936
    # Syntax Evolution. First check syntax.julia_version entry
937
    sv = nothing
4,585✔
938
    ds = get(d, "syntax", nothing)
4,585✔
939
    if ds !== nothing
4,585✔
940
        sv = VersionNumber(get(ds, "julia_version", nothing))
×
941
    end
942
    # If not found, default to minimum(compat["julia"])
943
    if sv === nothing
4,585✔
944
        cs = get(d, "compat", nothing)
6,156✔
945
        if cs !== nothing
4,585✔
946
            jv = get(cs, "julia", nothing)
1,572✔
947
            if jv !== nothing
1,572✔
948
                sv = VersionNumber(minimum(semver_spec(jv)).t...)
1,537✔
949
            end
950
        end
951
    end
952
    # Finally, if neither of those are set, default to the current Julia version.
953
    # N.B.: This choice is less "compatible" than defaulting to a fixed older version.
954
    # However, it avoids surprises from moving over scripts and REPL code to packages
955
    if sv === nothing
4,585✔
956
        sv = VERSION
3,049✔
957
    elseif sv <= NON_VERSIONED_SYNTAX
1,536✔
958
        # Syntax versioning was first introduced in Julia 1.14 - we do not support
959
        # going back to versions before syntax version 1.13.
960
        sv = NON_VERSIONED_SYNTAX
1,536✔
961
    end
962
    return sv
4,585✔
963
end
964

965
function project_file_load_spec(project_file::String, name::String)
180✔
966
    d = parsed_toml(project_file)
180✔
967
    entryfile = get(d, "path", nothing)::Union{String, Nothing}
180✔
968
    # "path" entry in project file is soft deprecated
969
    if entryfile === nothing
180✔
970
        entryfile = get(d, "entryfile", nothing)::Union{String, Nothing}
180✔
971
    end
972
    sv = project_get_syntax_version(d)
180✔
973
    return PkgLoadSpec(entry_path(dirname(project_file), name, entryfile), sv)
180✔
974
end
975

976
function workspace_manifest(project_file)
586✔
977
    base = base_project(project_file)
592✔
978
    if base !== nothing
592✔
979
        return project_file_manifest_path(base)
×
980
    end
981
    return nothing
592✔
982
end
983

984
struct VersionedParse
985
    ver::VersionNumber
318✔
986
end
987

988
function (vp::VersionedParse)(code, filename::String, lineno::Int, offset::Int, options::Symbol)
1,289✔
989
    if !isdefined(Base, :JuliaSyntax)
3,282✔
990
        if vp.ver === VERSION
×
991
            return Core._parse
×
992
        end
993
        error("JuliaSyntax module is required for syntax version $(vp.ver), but it is not loaded.")
×
994
    end
995
    Base.JuliaSyntax.core_parser_hook(code, filename, lineno, offset, options; syntax_version=vp.ver)
3,282✔
996
end
997

998
function parser_for_active_project()
8✔
999
    project = active_project()
8✔
1000
    sv = VERSION
8✔
1001
    if project !== nothing && isfile(project)
8✔
1002
        try
×
1003
            sv = project_get_syntax_version(parsed_toml(project))
×
1004
        catch e
1005
            @warn "Failed to read project $project - defaulting to latest syntax. err=$e"
×
1006
        end
1007
    end
1008
    VersionedParse(sv)
8✔
1009
end
1010

1011
# find project file's corresponding manifest file
1012
function project_file_manifest_path(project_file::String)::Union{Nothing,String}
1,476✔
1013
    @lock require_lock begin
1,476✔
1014
    cache = LOADING_CACHE[]
1,476✔
1015
    if cache !== nothing
1,476✔
1016
        manifest_path = get(cache.project_file_manifest_path, project_file, missing)
1,964✔
1017
        manifest_path === missing || return manifest_path
1,964✔
1018
    end
1019
    dir = abspath(dirname(project_file))
592✔
1020
    isfile_casesensitive(project_file) || return nothing
592✔
1021
    d = parsed_toml(project_file)
592✔
1022
    base_manifest = workspace_manifest(project_file)
598✔
1023
    if base_manifest !== nothing
592✔
1024
        return base_manifest
×
1025
    end
1026
    explicit_manifest = get(d, "manifest", nothing)::Union{String, Nothing}
592✔
1027
    manifest_path = nothing
592✔
1028
    if explicit_manifest !== nothing
592✔
1029
        manifest_file = normpath(joinpath(dir, explicit_manifest))
×
1030
        if isfile_casesensitive(manifest_file)
×
1031
            manifest_path = manifest_file
×
1032
        end
1033
    end
1034
    if manifest_path === nothing
592✔
1035
        for mfst in manifest_names
592✔
1036
            manifest_file = joinpath(dir, mfst)
2,368✔
1037
            if isfile_casesensitive(manifest_file)
2,368✔
1038
                manifest_path = manifest_file
576✔
1039
                break
576✔
1040
            end
1041
        end
1,792✔
1042
    end
1043
    if cache !== nothing
592✔
1044
        cache.project_file_manifest_path[project_file] = manifest_path
376✔
1045
    end
1046
    return manifest_path
592✔
1047
    end
1048
end
1049

1050
# given a directory (implicit env from LOAD_PATH) and a name,
1051
# check if it is an implicit package
1052
function entry_point_and_project_file_inside(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
2✔
1053
    path = normpath(joinpath(dir, "src", "$name.jl"))
16,601✔
1054
    isfile_casesensitive(path) || return nothing, nothing
20,705✔
1055
    for proj in project_names
12,497✔
1056
        project_file = normpath(joinpath(dir, proj))
24,994✔
1057
        isfile_casesensitive(project_file) || continue
24,994✔
1058
        return path, project_file
12,497✔
1059
    end
12,497✔
1060
    return path, nothing
×
1061
end
1062

1063
# given a project directory (implicit env from LOAD_PATH) and a name,
1064
# find an entry point for `name`, and see if it has an associated project file
1065
function entry_point_and_project_file(dir::String, name::String)::Union{Tuple{Nothing,Nothing},Tuple{String,Nothing},Tuple{String,String}}
14,549✔
1066
    dir_name = joinpath(dir, name)
14,549✔
1067
    path, project_file = entry_point_and_project_file_inside(dir_name, name)
27,046✔
1068
    path === nothing || return path, project_file
27,046✔
1069
    dir_jl = dir_name * ".jl"
2,052✔
1070
    path, project_file = entry_point_and_project_file_inside(dir_jl, name)
2,052✔
1071
    path === nothing || return path, project_file
2,052✔
1072
    # check for less likely case with a bare file and no src directory last to minimize stat calls
1073
    path = normpath(joinpath(dir, "$name.jl"))
2,052✔
1074
    isfile_casesensitive(path) && return path, nothing
2,052✔
1075
    return nothing, nothing
654✔
1076
end
1077

1078
# Find the project file for the extension `ext` in the implicit env `dir``
1079
function implicit_env_project_file_extension(dir::String, ext::PkgId)
1080
    for pkg in readdir(dir; join=true)
363✔
1081
        project_file = env_project_file(pkg)
372✔
1082
        project_file isa String || continue
372✔
1083
        ls = project_file_ext_load_spec(project_file, ext)
372✔
1084
        if ls !== nothing
372✔
1085
            return ls, project_file
×
1086
        end
1087
    end
372✔
1088
    return nothing, nothing
363✔
1089
end
1090

1091
# given a path, name, and possibly an entryfile, return the entry point
1092
function entry_path(path::String, name::String, entryfile::Union{Nothing,String})::String
705✔
1093
    isfile_casesensitive(path) && return normpath(path)
705✔
1094
    entrypoint = entryfile === nothing ? joinpath("src", "$name.jl") : entryfile
705✔
1095
    return normpath(joinpath(path, entrypoint))
705✔
1096
end
1097

1098
## explicit project & manifest API ##
1099

1100
# find project file root or deps `name => uuid` mapping
1101
# `ext` is the name of the extension if `name` is loaded from one
1102
# return `nothing` if `name` is not found
1103
function explicit_project_deps_get(project_file::String, name::String)::Union{Nothing,UUID}
3,914✔
1104
    d = parsed_toml(project_file)
3,914✔
1105
    if get(d, "name", nothing)::Union{String, Nothing} === name
7,788✔
1106
        root_uuid = dummy_uuid(project_file)
204✔
1107
        uuid = get(d, "uuid", nothing)::Union{String, Nothing}
408✔
1108
        return uuid === nothing ? root_uuid : UUID(uuid)
204✔
1109
    end
1110
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
7,410✔
1111
    if deps !== nothing
3,710✔
1112
        uuid = get(deps, name, nothing)::Union{String, Nothing}
7,385✔
1113
        uuid === nothing || return UUID(uuid)
7,385✔
1114
    end
1115
    return nothing
25✔
1116
end
1117

1118
function is_v1_format_manifest(raw_manifest::Dict{String})
1,345✔
1119
    if haskey(raw_manifest, "manifest_format")
2,690✔
1120
        mf = raw_manifest["manifest_format"]
1,342✔
1121
        if mf isa Dict{String} && haskey(mf, "uuid")
1,342✔
1122
            # the off-chance where an old format manifest has a dep called "manifest_format"
1123
            return true
×
1124
        end
1125
        return false
1,342✔
1126
    else
1127
        return true
3✔
1128
    end
1129
end
1130

1131
# returns a deps list for both old and new manifest formats
1132
function get_deps(raw_manifest::Dict)
6✔
1133
    if is_v1_format_manifest(raw_manifest)
1,351✔
1134
        return raw_manifest
6✔
1135
    else
1136
        # if the manifest has no deps, there won't be a `deps` field
1137
        return get(Dict{String, Any}, raw_manifest, "deps")::Dict{String, Any}
1,345✔
1138
    end
1139
end
1140

1141
function dep_stanza_get(stanza::Dict{String, Any}, name::String)::Union{Nothing, PkgId}
×
1142
    for (dep, uuid) in stanza
×
1143
        uuid::String
×
1144
        if dep === name
×
1145
            return PkgId(UUID(uuid), name)
×
1146
        end
1147
    end
×
1148
    return nothing
×
1149
end
1150

1151
function dep_stanza_get(stanza::Vector{String}, name::String)::Union{Nothing, PkgId}
1152
    name in stanza && return PkgId(name)
475✔
1153
    return nothing
41✔
1154
end
1155

1156
dep_stanza_get(stanza::Nothing, name::String) = nothing
×
1157

1158
function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId}
350✔
1159
    manifest_file = project_file_manifest_path(project_file)
350✔
1160
    manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH
350✔
1161
    d = get_deps(parsed_toml(manifest_file))
648✔
1162
    for (dep_name, entries) in d
648✔
1163
        entries::Vector{Any}
1,280✔
1164
        for entry in entries
1,280✔
1165
            entry = entry::Dict{String, Any}
1,280✔
1166
            uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
2,560✔
1167
            uuid === nothing && continue
1,280✔
1168
            # deps is either a list of names (deps = ["DepA", "DepB"]) or
1169
            # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."}
1170
            deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}
1,926✔
1171
            local dep::Union{Nothing, PkgId}
1172
            @label _ begin
1173
                if UUID(uuid) === where.uuid
1,280✔
1174
                    dep = dep_stanza_get(deps, name)
304✔
1175

1176
                    # We found `where` in this environment, but it did not have a deps entry for
1177
                    # `name`. This is likely because the dependency was modified without a corresponding
1178
                    # change to dependency's Project or our Manifest. Return a sentinel here indicating
1179
                    # that we know the package, but do not know its UUID. The caller will terminate the
1180
                    # search and provide an appropriate error to the user.
1181
                    dep === nothing && return PkgId(name)
152✔
1182
                else
1183
                    # Check if we're trying to load into an extension of this package
1184
                    extensions = get(entry, "extensions", nothing)
1,290✔
1185
                    if extensions !== nothing
1,128✔
1186
                        if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name)
303✔
1187
                            if name == dep_name
141✔
1188
                                # Extension loads its base package
1189
                                return PkgId(UUID(uuid), name)
63✔
1190
                            end
1191
                            exts = extensions[where.name]::Union{String, Vector{String}}
78✔
1192
                            # Extensions are allowed to load:
1193
                            # 1. Any ordinary dep of the parent package
1194
                            # 2. Any weakdep of the parent package declared as an extension trigger
1195
                            for deps′ in (ext_may_load_weakdep(exts, name) ?
78✔
1196
                                    (get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing}, deps) :
1197
                                    (deps,))
1198
                                dep = dep_stanza_get(deps′, name)
192✔
1199
                                dep === nothing && continue
96✔
1200
                                break _
78✔
1201
                            end
18✔
1202
                            return PkgId(name)
×
1203
                        end
1204
                    end
1205
                    continue
987✔
1206
                end
1207
            end
1208

1209
            dep.uuid !== nothing && return dep
207✔
1210

1211
            # We have the dep, but it did not specify a UUID. In this case,
1212
            # it must be that the name is unique in the manifest - so lookup
1213
            # the UUID at the lop level by name
1214
            name_deps = get(d, name, nothing)::Union{Nothing, Vector{Any}}
414✔
1215
            if name_deps === nothing || length(name_deps) != 1
414✔
1216
                error("expected a single entry for $(repr(name)) in $(repr(project_file))")
×
1217
            end
1218
            entry = first(name_deps::Vector{Any})::Dict{String, Any}
207✔
1219
            uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
414✔
1220
            uuid === nothing && return PkgId(name)
207✔
1221
            return PkgId(UUID(uuid), name)
207✔
1222
        end
987✔
1223
    end
1,943✔
1224

1225
    # We did not find `where` in this environment, either as a package or as an extension.
1226
    # The caller should continue searching the environment stack.
1227
    return nothing
31✔
1228
end
1229

1230
# find `uuid` stanza, return the corresponding path
1231
function explicit_manifest_uuid_load_spec(project_file::String, pkg::PkgId)::Union{Nothing,PkgLoadSpec,Missing}
682✔
1232
    manifest_file = project_file_manifest_path(project_file)
682✔
1233
    manifest_file === nothing && return nothing # no manifest, skip env
682✔
1234

1235
    d = get_deps(parsed_toml(manifest_file))
1,280✔
1236
    entries = get(d, pkg.name, nothing)::Union{Nothing, Vector{Any}}
1,090✔
1237
    if entries !== nothing
640✔
1238
        for entry in entries
450✔
1239
            entry = entry::Dict{String, Any}
450✔
1240
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
900✔
1241
            uuid === nothing && continue
450✔
1242
            if UUID(uuid) === pkg.uuid
450✔
1243
                return explicit_manifest_entry_load_spec(manifest_file, pkg, entry)
450✔
1244
            end
1245
        end
×
1246
    end
1247
    # Extensions
1248
    for (name, entries) in d
380✔
1249
        entries = entries::Vector{Any}
742✔
1250
        for entry in entries
742✔
1251
            entry = entry::Dict{String, Any}
742✔
1252
            uuid = get(entry, "uuid", nothing)::Union{Nothing, String}
1,484✔
1253
            extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
862✔
1254
            if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid
853✔
1255
                parent_load_spec = explicit_manifest_entry_load_spec(manifest_file, PkgId(UUID(uuid), name), entry)
111✔
1256
                if parent_load_spec === nothing || parent_load_spec === missing
222✔
1257
                    error("failed to find source of parent package: \"$name\"")
×
1258
                end
1259
                parent_path = parent_load_spec.path
111✔
1260
                p = normpath(dirname(parent_path), "..")
111✔
1261
                return PkgLoadSpec(find_ext_path(p, pkg.name), parent_load_spec.julia_syntax_version)
111✔
1262
            end
1263
        end
631✔
1264
    end
1,183✔
1265
    return nothing
79✔
1266
end
1267

1268
function explicit_manifest_entry_load_spec(manifest_file::String, pkg::PkgId, entry::Dict{String,Any})::Union{Nothing, Missing, PkgLoadSpec}
561✔
1269
    # Resolve syntax version. N.B.: Unlike in project files, an absent syntax.julia_version
1270
    # entry in manifest files means defaulting to 1.13. This is because we assume the
1271
    # manifest was created by an older version of julia that did not support syntax versioning.
1272
    # Newer versions of Pkg will provide syntax version information in the manifest,
1273
    # even if absent from the project file.
1274
    syntax_version = NON_VERSIONED_SYNTAX
561✔
1275
    syntax_table = get(entry, "syntax", nothing)
561✔
1276
    if syntax_table !== nothing
561✔
1277
        syntax_version = VersionNumber(get(syntax_table, "julia_version", nothing))
×
1278
        # Clamp to minimum supported syntax version
1279
        if syntax_version <= NON_VERSIONED_SYNTAX
×
1280
            syntax_version = NON_VERSIONED_SYNTAX
×
1281
        end
1282
    end
1283

1284
    # Resolve path
1285
    path = get(entry, "path", nothing)::Union{Nothing, String}
1,086✔
1286
    entryfile = get(entry, "entryfile", nothing)::Union{Nothing, String}
561✔
1287
    if path !== nothing
561✔
1288
        path = entry_path(normpath(abspath(dirname(manifest_file), path)), pkg.name, entryfile)
525✔
1289
        return PkgLoadSpec(path, syntax_version)
525✔
1290
    end
1291
    hash = get(entry, "git-tree-sha1", nothing)::Union{Nothing, String}
39✔
1292
    if hash === nothing
36✔
1293
        # stdlibs do not have a git-hash so cannot be loaded from depots. As
1294
        # a special case, we allow loading these directly from the stdlib location
1295
        # (treated as an implicit environment).
1296
        mbyspec = manifest_uuid_load_spec(Sys.STDLIB, pkg)
33✔
1297
        if mbyspec isa PkgLoadSpec && isfile(mbyspec.path)
33✔
1298
            return mbyspec
33✔
1299
        end
1300
        return nothing
×
1301
    end
1302
    hash = SHA1(hash)
3✔
1303
    # Keep the 4 since it used to be the default
1304
    uuid = pkg.uuid::UUID # checked within `explicit_manifest_uuid_path`
3✔
1305
    for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4))
3✔
1306
        for depot in DEPOT_PATH
6✔
1307
            path = joinpath(depot, "packages", pkg.name, slug)
18✔
1308
            ispath(path) && return PkgLoadSpec(entry_path(abspath(path), pkg.name, entryfile), syntax_version)
18✔
1309
        end
18✔
1310
    end
9✔
1311
    # no depot contains the package, return missing to stop looking
1312
    return missing
3✔
1313
end
1314

1315
## implicit project & manifest API ##
1316
function implicit_manifest_pkgid(dir::String, name::String)::Union{Nothing,PkgId}
1✔
1317
    path, project_file = entry_point_and_project_file(dir, name)
5,802✔
1318
    if project_file === nothing
3,044✔
1319
        path === nothing && return nothing
1,156✔
1320
        return PkgId(name)
870✔
1321
    end
1322
    proj = project_file_name_uuid(project_file, name)
1,888✔
1323
    proj.name == name || return nothing
1,888✔
1324
    return proj
1,888✔
1325
end
1326

1327
function implicit_manifest_project(dir::String, pkg::PkgId)::Union{Nothing, String}
3,659✔
1328
    @assert pkg.uuid !== nothing
3,659✔
1329
    project_file = entry_point_and_project_file(dir, pkg.name)[2]
7,132✔
1330
    if project_file === nothing
3,659✔
1331
        # `where` could be an extension
1332
        return implicit_env_project_file_extension(dir, pkg)[2]
186✔
1333
    end
1334
    proj = project_file_name_uuid(project_file, pkg.name)
3,473✔
1335
    proj == pkg || return nothing
3,473✔
1336
    return project_file
3,473✔
1337
end
1338

1339
# look for an entry-point for `pkg` and return its path if UUID matches
1340
function implicit_manifest_uuid_load_spec(dir::String, pkg::PkgId)::Union{Nothing, PkgLoadSpec}
5,038✔
1341
    path, project_file = entry_point_and_project_file(dir, pkg.name)
9,890✔
1342
    if project_file === nothing
5,038✔
1343
        pkg.uuid === nothing || return nothing
918✔
1344
        # Without a project file, treat as empty - which defaults to VERSION
1345
        return PkgLoadSpec(path, VERSION)
546✔
1346
    end
1347
    proj = project_file_name_uuid(project_file, pkg.name)
4,306✔
1348
    proj == pkg || return nothing
4,306✔
1349
    return PkgLoadSpec(path, project_get_syntax_version(parsed_toml(project_file)))
4,306✔
1350
end
1351

1352
## other code loading functionality ##
1353

1354
function find_source_file(path::AbstractString)
×
1355
    (isabspath(path) || isfile(path)) && return path
×
1356
    base_path = joinpath(Sys.BINDIR, DATAROOTDIR, "julia", "base", path)
×
1357
    return isfile(base_path) ? normpath(base_path) : nothing
×
1358
end
1359

1360
function cache_file_entry(pkg::PkgId)
3✔
1361
    uuid = pkg.uuid
5,207✔
1362
    return joinpath(
9,384✔
1363
        "compiled",
1364
        "v$(VERSION.major).$(VERSION.minor)",
1365
        uuid === nothing ? ""       : pkg.name),
1366
        uuid === nothing ? pkg.name : package_slug(uuid)
1367
end
1368

1369
function find_all_in_cache_path(pkg::PkgId, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH)
3,979✔
1370
    paths = String[]
4,383✔
1371
    entrypath, entryfile = cache_file_entry(pkg)
7,502✔
1372
    for path in DEPOT_PATH
3,976✔
1373
        path = joinpath(path, entrypath)
12,012✔
1374
        isdir(path) || continue
12,012✔
1375
        for file in readdir(path, sort = false) # no sort given we sort later
6,018✔
1376
            if !((pkg.uuid === nothing && file == entryfile * ".ji") ||
134,645✔
1377
                 (pkg.uuid !== nothing && startswith(file, entryfile * "_") &&
1378
                  endswith(file, ".ji")))
1379
                 continue
60,262✔
1380
            end
1381
            filepath = joinpath(path, file)
7,167✔
1382
            isfile_casesensitive(filepath) && push!(paths, filepath)
7,167✔
1383
        end
67,429✔
1384
    end
12,012✔
1385
    if length(paths) > 1
3,976✔
1386
        function sort_by(path)
9,520✔
1387
            # when using pkgimages, consider those cache files first
1388
            pkgimage = if JLOptions().use_pkgimages != 0
6,657✔
1389
                io = open(path, "r")
6,657✔
1390
                try
6,657✔
1391
                    if iszero(isvalid_cache_header(io))
6,657✔
1392
                        false
×
1393
                    else
1394
                        _, _, _, _, _, _, _, flags = parse_cache_header(io, path)
6,657✔
1395
                        CacheFlags(flags).use_pkgimages
13,314✔
1396
                    end
1397
                finally
1398
                    close(io)
6,657✔
1399
                end
1400
            else
1401
                false
6,657✔
1402
            end
1403
            (; pkgimage, mtime=mtime(path))
6,657✔
1404
        end
1405
        function sort_lt(a, b)
2,863✔
1406
            if a.pkgimage != b.pkgimage
7,860✔
1407
                return a.pkgimage < b.pkgimage
×
1408
            end
1409
            return a.mtime < b.mtime
3,930✔
1410
        end
1411

1412
        # allocating the sort vector is less expensive than using sort!(.. by=sort_by),
1413
        # which would call the relatively slow mtime multiple times per path
1414
        p = sortperm(sort_by.(paths), lt=sort_lt, rev=true)
2,863✔
1415
        return paths[p]
2,863✔
1416
    else
1417
        return paths
1,113✔
1418
    end
1419
end
1420

1421
ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Libc.Libdl.dlext)
4,080✔
1422
cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Libc.Libdl.dlext)"), ".ji")
×
1423

1424

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

1428
# loads a precompile cache file, ignoring stale_cachefile tests
1429
# assuming all depmods are already loaded and everything is valid
1430
# these return either the array of modules loaded from the path / content given
1431
# or an Exception that describes why it couldn't be loaded
1432
# and it reconnects the Base.Docs.META
1433
function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}; register::Bool=true)
6,830✔
1434
    assert_havelock(require_lock)
6,830✔
1435
    timing_imports = TIMING_IMPORTS[] > 0
3,415✔
1436
    try
3,415✔
1437
        if timing_imports
3,415✔
1438
            t_before = time_ns()
×
1439
            cumulative_compile_timing(true)
×
1440
            t_comp_before = cumulative_compile_time_ns()
×
1441
        end
1442

1443
        for i in eachindex(depmods)
6,830✔
1444
            dep = depmods[i]
50,160✔
1445
            dep isa Module && continue
50,160✔
1446
            _, depkey, depbuild_id = dep::Tuple{PkgLoadSpec, PkgId, UInt128}
2,637✔
1447
            dep = something(maybe_loaded_precompile(depkey, depbuild_id))
5,274✔
1448
            @assert PkgId(dep) == depkey && module_build_id(dep) === depbuild_id
2,637✔
1449
            depmods[i] = dep
2,637✔
1450
        end
96,905✔
1451

1452
        ignore_native = false
3,415✔
1453
        unlock(require_lock) # temporarily _unlock_ during these operations
3,415✔
1454
        sv = try
3,415✔
1455
            if ocachepath !== nothing
3,415✔
1456
                @debug "Loading object cache file $ocachepath for $(repr("text/plain", pkg))"
3,397✔
1457
                ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint),
3,397✔
1458
                    ocachepath, depmods, #=completeinfo=#false, pkg.name, ignore_native)
1459
            else
1460
                @debug "Loading cache file $path for $(repr("text/plain", pkg))"
18✔
1461
                ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring),
3,415✔
1462
                    path, depmods, #=completeinfo=#false, pkg.name)
1463
            end
1464
        finally
1465
            lock(require_lock)
3,424✔
1466
        end
1467
        if isa(sv, Exception)
3,415✔
1468
            return sv
×
1469
        end
1470

1471
        sv = sv::SimpleVector
3,415✔
1472
        internal_methods = sv[3]::Vector{Any}
3,415✔
1473
        Compiler.@zone "CC: INSERT_BACKEDGES" begin
1474
            ReinferUtils.insert_backedges_typeinf(internal_methods)
3,415✔
1475
        end
1476
        restored = register_restored_modules(sv, pkg, path)
3,415✔
1477

1478
        for M in restored
3,415✔
1479
            M = M::Module
4,585✔
1480
            if is_root_module(M) && PkgId(M) == pkg
7,754✔
1481
                register && register_root_module(M)
3,415✔
1482
                if timing_imports
3,415✔
1483
                    elapsed_time = time_ns() - t_before
×
1484
                    comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before
×
1485
                    print_time_imports_report(M, elapsed_time, comp_time, recomp_time)
×
1486
                end
1487
                return M
3,415✔
1488
            end
1489
        end
1,170✔
1490
        return ErrorException("Required dependency $(repr("text/plain", pkg)) failed to load from a cache file.")
×
1491

1492
    finally
1493
        timing_imports && cumulative_compile_timing(false)
3,415✔
1494
    end
1495
end
1496

1497
# printing functions for @time_imports
1498
# note that the time inputs are UInt64 on all platforms. Give default values here so that we don't have
1499
# confusing UInt64 types in generate_precompile.jl
1500
function print_time_imports_report(
×
1501
        mod::Module,
1502
        elapsed_time::UInt64=UInt64(1),
1503
        comp_time::UInt64=UInt64(1),
1504
        recomp_time::UInt64=UInt64(1)
1505
    )
1506
    print(lpad(round(elapsed_time / 1e6, digits=1), 9), " ms  ")
×
1507
    ext_parent = extension_parent_name(mod)
×
1508
    if ext_parent !== nothing
×
1509
        print(ext_parent::String, " → ")
×
1510
    end
1511
    print(string(mod))
×
1512
    if comp_time > 0
×
1513
        perc = Ryu.writefixed(Float64(100 * comp_time / (elapsed_time)), 2)
×
1514
        printstyled(" $perc% compilation time", color = Base.info_color())
×
1515
    end
1516
    if recomp_time > 0
×
1517
        perc = Float64(100 * recomp_time / comp_time)
×
1518
        perc_show = perc < 1 ? "<1" : Ryu.writefixed(perc, 0)
×
1519
        printstyled(" ($perc_show% recompilation)", color = Base.warn_color())
×
1520
    end
1521
    println()
×
1522
end
1523
function print_time_imports_report_init(
×
1524
        mod::Module, i::Int=1,
1525
        elapsed_time::UInt64=UInt64(1),
1526
        comp_time::UInt64=UInt64(1),
1527
        recomp_time::UInt64=UInt64(1)
1528
    )
1529
    connector = i > 1 ? "├" : "┌"
×
1530
    printstyled("               $connector ", color = :light_black)
×
1531
    print("$(round(elapsed_time / 1e6, digits=1)) ms $mod.__init__() ")
×
1532
    if comp_time > 0
×
1533
        perc = Ryu.writefixed(Float64(100 * (comp_time) / elapsed_time), 2)
×
1534
        printstyled("$perc% compilation time", color = Base.info_color())
×
1535
    end
1536
    if recomp_time > 0
×
1537
        perc = Float64(100 * recomp_time / comp_time)
×
1538
        printstyled(" ($(perc < 1 ? "<1" : Ryu.writefixed(perc, 0))% recompilation)", color = Base.warn_color())
×
1539
    end
1540
    println()
×
1541
end
1542

1543
# if M is an extension, return the string name of the parent. Otherwise return nothing
1544
function extension_parent_name(M::Module)
×
1545
    rootmodule = moduleroot(M)
×
1546
    src_path = pathof(rootmodule)
×
1547
    src_path === nothing && return nothing
×
1548
    pkgdir_parts = splitpath(src_path)
×
1549
    ext_pos = findlast(==("ext"), pkgdir_parts)
×
1550
    if ext_pos !== nothing && ext_pos >= length(pkgdir_parts) - 2
×
1551
        parent_package_root = joinpath(pkgdir_parts[1:ext_pos-1]...)
×
1552
        parent_package_project_file = locate_project_file(parent_package_root)
×
1553
        if parent_package_project_file isa String
×
1554
            d = parsed_toml(parent_package_project_file)
×
1555
            name = get(d, "name", nothing)
×
1556
            if name !== nothing
×
1557
                return name
×
1558
            end
1559
        end
1560
    end
1561
    return nothing
×
1562
end
1563

1564
function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String)
3,415✔
1565
    # This function is also used by PkgCacheInspector.jl
1566
    assert_havelock(require_lock)
6,830✔
1567
    restored = sv[1]::Vector{Any}
3,415✔
1568
    for M in restored
3,415✔
1569
        M = M::Module
4,585✔
1570
        if isdefinedglobal(M, Base.Docs.META)
4,585✔
1571
            push!(Base.Docs.modules, M)
3,258✔
1572
        end
1573
        if is_root_module(M)
5,755✔
1574
            push!(loaded_modules_order, M)
3,415✔
1575
            push!(get!(Vector{Module}, loaded_precompiles, pkg), M)
3,415✔
1576
        end
1577
    end
4,585✔
1578

1579
    # Register this cache path now - If Requires.jl is loaded, Revise may end
1580
    # up looking at the cache path during the init callback.
1581
    get!(PkgOrigin, pkgorigins, pkg).cachepath = path
3,415✔
1582

1583
    inits = sv[2]::Vector{Any}
3,415✔
1584
    if !isempty(inits)
3,415✔
1585
        unlock(require_lock) # temporarily _unlock_ during these callbacks
1,005✔
1586
        try
1,005✔
1587
            for (i, mod) in pairs(inits)
2,010✔
1588
                run_module_init(mod, i)
1,011✔
1589
            end
1,017✔
1590
        finally
1591
            lock(require_lock)
1,005✔
1592
        end
1593
    end
1594
    return restored
3,415✔
1595
end
1596

1597
function run_module_init(mod::Module, i::Int=1)
1,011✔
1598
    # `i` informs ordering for the `@time_imports` report formatting
1599
    if TIMING_IMPORTS[] == 0
1,011✔
1600
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
1,011✔
1601
    elseif isdefined(mod, :__init__)
×
1602
        elapsed_time = time_ns()
×
1603
        cumulative_compile_timing(true)
×
1604
        compile_elapsedtimes = cumulative_compile_time_ns()
×
1605

1606
        ccall(:jl_init_restored_module, Cvoid, (Any,), mod)
×
1607

1608
        elapsed_time = time_ns() - elapsed_time
×
1609
        cumulative_compile_timing(false);
×
1610
        comp_time, recomp_time = cumulative_compile_time_ns() .- compile_elapsedtimes
×
1611

1612
        print_time_imports_report_init(mod, i, elapsed_time, comp_time, recomp_time)
×
1613
    end
1614
end
1615

1616
function run_package_callbacks(modkey::PkgId)
3,484✔
1617
    run_extension_callbacks(modkey)
3,484✔
1618
    assert_havelock(require_lock)
6,968✔
1619
    unlock(require_lock)
3,484✔
1620
    try
3,484✔
1621
        for callback in package_callbacks
3,484✔
1622
            invokelatest(callback, modkey)
513✔
1623
        end
513✔
1624
    catch
1625
        # Try to continue loading if a callback errors
1626
        errs = current_exceptions()
×
1627
        @error "Error during package callback" exception=errs
3,484✔
1628
    finally
1629
        lock(require_lock)
3,484✔
1630
    end
1631
    nothing
3,484✔
1632
end
1633

1634

1635
##############
1636
# Extensions #
1637
##############
1638

1639
mutable struct ExtensionId
1640
    const id::PkgId
117✔
1641
    const parentid::PkgId # just need the name, for printing
1642
    const n_total_triggers::Int
1643
    ntriggers::Int # how many more packages must be defined until this is loaded
1644
end
1645

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

1650
function insert_extension_triggers(pkg::PkgId)
1651
    pkg.uuid === nothing && return
3,484✔
1652
    path_env_loc = locate_package_env(pkg)
3,214✔
1653
    path_env_loc === nothing && return
3,214✔
1654
    _, env_loc = path_env_loc
3,214✔
1655
    insert_extension_triggers(env_loc, pkg)
3,214✔
1656
end
1657

1658
function insert_extension_triggers(env::String, pkg::PkgId)::Union{Nothing,Missing}
3,226✔
1659
    project_file = env_project_file(env)
3,226✔
1660
    if project_file isa String || project_file
6,056✔
1661
        implicit_project_file = project_file
3,226✔
1662
        if !(implicit_project_file isa String)
3,226✔
1663
            # if env names a directory, search it for an implicit project file (for stdlibs)
1664
            path, implicit_project_file = entry_point_and_project_file(env, pkg.name)
5,660✔
1665
            if !(implicit_project_file isa String)
2,830✔
1666
                return nothing
×
1667
            end
1668
        end
1669
        # Look in project for extensions to insert
1670
        proj_pkg = project_file_name_uuid(implicit_project_file, pkg.name)
3,226✔
1671
        if pkg == proj_pkg
6,452✔
1672
            d_proj = parsed_toml(implicit_project_file)
2,902✔
1673
            extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
2,932✔
1674
            extensions === nothing && return
2,902✔
1675
            weakdeps = get(Dict{String, Any}, d_proj, "weakdeps")::Dict{String,Any}
30✔
1676
            deps = get(Dict{String, Any}, d_proj, "deps")::Dict{String,Any}
30✔
1677
            total_deps = merge(weakdeps, deps)
30✔
1678
            return _insert_extension_triggers(pkg, extensions, total_deps)
30✔
1679
        end
1680

1681
        # Now look in manifest
1682
        project_file isa String || return nothing
324✔
1683
        manifest_file = project_file_manifest_path(project_file)
324✔
1684
        manifest_file === nothing && return
324✔
1685
        d = get_deps(parsed_toml(manifest_file))
648✔
1686
        for (dep_name, entries) in d
648✔
1687
            entries::Vector{Any}
1,134✔
1688
            for entry in entries
1,134✔
1689
                entry = entry::Dict{String, Any}
1,134✔
1690
                uuid = get(entry, "uuid", nothing)::Union{String, Nothing}
2,268✔
1691
                uuid === nothing && continue
1,134✔
1692
                if UUID(uuid) == pkg.uuid
2,268✔
1693
                    extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}}
246✔
1694
                    extensions === nothing && return
216✔
1695
                    weakdeps = get(Dict{String, Any}, entry, "weakdeps")::Union{Vector{String}, Dict{String,Any}}
30✔
1696
                    deps = get(Dict{String, Any}, entry, "deps")::Union{Vector{String}, Dict{String,Any}}
30✔
1697

1698
                    function expand_deps_list(deps′::Vector{String})
81✔
1699
                        deps′_expanded = Dict{String, Any}()
51✔
1700
                        for (dep_name, entries) in d
102✔
1701
                            dep_name in deps′ || continue
513✔
1702
                            entries::Vector{Any}
66✔
1703
                            if length(entries) != 1
66✔
1704
                                error("expected a single entry for $(repr(dep_name)) in $(repr(project_file))")
×
1705
                            end
1706
                            local entry = first(entries)::Dict{String, Any}
66✔
1707
                            local uuid = entry["uuid"]::String
66✔
1708
                            deps′_expanded[dep_name] = uuid
66✔
1709
                        end
453✔
1710
                        return deps′_expanded
51✔
1711
                    end
1712

1713
                    if weakdeps isa Vector{String}
30✔
1714
                        weakdeps = expand_deps_list(weakdeps)
30✔
1715
                    end
1716
                    if deps isa Vector{String}
30✔
1717
                        deps = expand_deps_list(deps)
21✔
1718
                    end
1719

1720
                    total_deps = merge(weakdeps, deps)
30✔
1721
                    return _insert_extension_triggers(pkg, extensions, total_deps)
30✔
1722
                end
1723
            end
918✔
1724
        end
1,728✔
1725
    end
1726
    return nothing
108✔
1727
end
1728

1729
function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any}, totaldeps::Dict{String, Any})
60✔
1730
    for (ext, triggers) in extensions
120✔
1731
        triggers = triggers::Union{String, Vector{String}}
117✔
1732
        triggers isa String && (triggers = [triggers])
117✔
1733
        id = PkgId(uuid5(parent.uuid::UUID, ext), ext)
234✔
1734
        if haskey(EXT_PRIMED, id) || haskey(Base.loaded_modules, id)
234✔
1735
            continue  # extension is already primed or loaded, don't add it again
×
1736
        end
1737
        EXT_PRIMED[id] = trigger_ids = PkgId[parent]
117✔
1738
        gid = ExtensionId(id, parent, 1 + length(triggers), 1 + length(triggers))
117✔
1739
        trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent)
117✔
1740
        push!(trigger1, gid)
117✔
1741
        for trigger in triggers
117✔
1742
            # TODO: Better error message if this lookup fails?
1743
            uuid_trigger = UUID(totaldeps[trigger]::String)
150✔
1744
            trigger_id = PkgId(uuid_trigger, trigger)
150✔
1745
            push!(trigger_ids, trigger_id)
150✔
1746
            if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id)
198✔
1747
                trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id)
102✔
1748
                push!(trigger1, gid)
102✔
1749
            else
1750
                gid.ntriggers -= 1
48✔
1751
            end
1752
        end
150✔
1753
    end
117✔
1754
end
1755

1756
loading_extension::Bool = false
1757
loadable_extensions::Union{Nothing,Vector{PkgId}} = nothing
1758
precompiling_extension::Bool = false
1759
function run_extension_callbacks(extid::ExtensionId)
108✔
1760
    assert_havelock(require_lock)
216✔
1761
    succeeded = try
108✔
1762
        # Used by Distributed to now load extensions in the package callback
1763
        global loading_extension = true
108✔
1764
        _require_prelocked(extid.id)
108✔
1765
        @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded"
108✔
1766
        true
108✔
1767
    catch
1768
        # Try to continue loading if loading an extension errors
1769
        if JLOptions().incremental != 0
×
1770
            # during incremental precompilation, this should be fail-fast
1771
            rethrow()
×
1772
        else
1773
            errs = current_exceptions()
×
1774
            @error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name), \
×
1775
                use `Base.retry_load_extensions()` to retry." exception=errs
1776
        end
1777
        false
108✔
1778
    finally
1779
        global loading_extension = false
108✔
1780
    end
1781
    return succeeded
108✔
1782
end
1783

1784
function run_extension_callbacks(pkgid::PkgId)
3,484✔
1785
    assert_havelock(require_lock)
6,968✔
1786
    # take ownership of extids that depend on this pkgid
1787
    extids = pop!(EXT_DORMITORY, pkgid, nothing)
3,484✔
1788
    extids === nothing && return
3,484✔
1789
    extids_to_load = Vector{ExtensionId}()
132✔
1790
    for extid in extids
132✔
1791
        @assert extid.ntriggers > 0
210✔
1792
        extid.ntriggers -= 1
210✔
1793
        if extid.ntriggers == 0 && (loadable_extensions === nothing || extid.id in loadable_extensions)
210✔
1794
            push!(extids_to_load, extid)
108✔
1795
        end
1796
    end
210✔
1797
    # Load extensions with the fewest triggers first
1798
    sort!(extids_to_load, by=extid->extid.n_total_triggers)
162✔
1799
    for extid in extids_to_load
132✔
1800
        # actually load extid, now that all dependencies are met,
1801
        succeeded = run_extension_callbacks(extid)
108✔
1802
        succeeded || push!(EXT_DORMITORY_FAILED, extid)
108✔
1803
    end
108✔
1804

1805
    return
132✔
1806
end
1807

1808
"""
1809
    retry_load_extensions()
1810

1811
Loads all the (not yet loaded) extensions that have their extension-dependencies loaded.
1812
This is used in cases where the automatic loading of an extension failed
1813
due to some problem with the extension. Instead of restarting the Julia session,
1814
the extension can be fixed, and this function run.
1815
"""
1816
function retry_load_extensions()
×
1817
    @lock require_lock begin
×
1818
    # this copy is desired since run_extension_callbacks will release this lock
1819
    # so this can still mutate the list to drop successful ones
1820
    failed = copy(EXT_DORMITORY_FAILED)
×
1821
    empty!(EXT_DORMITORY_FAILED)
×
1822
    filter!(failed) do extid
×
1823
        return !run_extension_callbacks(extid)
×
1824
    end
1825
    prepend!(EXT_DORMITORY_FAILED, failed)
×
1826
    end
1827
    return
×
1828
end
1829

1830
"""
1831
    get_extension(parent::Module, extension::Symbol)
1832

1833
Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded.
1834
"""
1835
get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext)
129✔
1836
function get_extension(parentid::PkgId, ext::Symbol)
123✔
1837
    parentid.uuid === nothing && return nothing
123✔
1838
    extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext))
246✔
1839
    return maybe_root_module(extid)
123✔
1840
end
1841

1842
# End extensions
1843

1844

1845
struct CacheFlags
1846
    # OOICCDDP - see jl_cache_flags
1847
    use_pkgimages::Bool
19,822✔
1848
    debug_level::Int
1849
    check_bounds::Int
1850
    inline::Bool
1851
    opt_level::Int
1852
end
1853
function CacheFlags(f::UInt8)
2✔
1854
    use_pkgimages = Bool(f & 1)
26,509✔
1855
    debug_level = Int((f >> 1) & 3)
13,284✔
1856
    check_bounds = Int((f >> 3) & 3)
13,284✔
1857
    inline = Bool((f >> 5) & 1)
25,651✔
1858
    opt_level = Int((f >> 6) & 3) # define OPT_LEVEL in statiddata_utils
13,284✔
1859
    CacheFlags(use_pkgimages, debug_level, check_bounds, inline, opt_level)
13,192✔
1860
end
1861
CacheFlags(f::Int) = CacheFlags(UInt8(f))
×
1862
function CacheFlags(cf::CacheFlags=CacheFlags(ccall(:jl_cache_flags, UInt8, ()));
19,854✔
1863
            use_pkgimages::Union{Nothing,Bool}=nothing,
1864
            debug_level::Union{Nothing,Int}=nothing,
1865
            check_bounds::Union{Nothing,Int}=nothing,
1866
            inline::Union{Nothing,Bool}=nothing,
1867
            opt_level::Union{Nothing,Int}=nothing
1868
        )
1869
    return CacheFlags(
6,630✔
1870
        use_pkgimages === nothing ? cf.use_pkgimages : use_pkgimages,
1871
        debug_level === nothing ? cf.debug_level : debug_level,
1872
        check_bounds === nothing ? cf.check_bounds : check_bounds,
1873
        inline === nothing ? cf.inline : inline,
1874
        opt_level === nothing ? cf.opt_level : opt_level
1875
    )
1876
end
1877
# reflecting jloptions.c defaults
1878
const DefaultCacheFlags = CacheFlags(use_pkgimages=true, debug_level=isdebugbuild() ? 2 : 1, check_bounds=0, inline=true, opt_level=2)
1879

1880
function _cacheflag_to_uint8(cf::CacheFlags)::UInt8
1✔
1881
    f = UInt8(0)
5,964✔
1882
    f |= cf.use_pkgimages << 0
5,964✔
1883
    f |= cf.debug_level << 1
5,964✔
1884
    f |= cf.check_bounds << 3
5,964✔
1885
    f |= cf.inline << 5
5,964✔
1886
    f |= cf.opt_level << 6
5,964✔
1887
    return f
5,964✔
1888
end
1889

1890
function translate_cache_flags(cacheflags::CacheFlags, defaultflags::CacheFlags)
424✔
1891
    opts = String[]
424✔
1892
    cacheflags.use_pkgimages    != defaultflags.use_pkgimages   && push!(opts, cacheflags.use_pkgimages ? "--pkgimages=yes" : "--pkgimages=no")
424✔
1893
    cacheflags.debug_level      != defaultflags.debug_level     && push!(opts, "-g$(cacheflags.debug_level)")
424✔
1894
    cacheflags.check_bounds     != defaultflags.check_bounds    && push!(opts, ("--check-bounds=auto", "--check-bounds=yes", "--check-bounds=no")[cacheflags.check_bounds + 1])
424✔
1895
    cacheflags.inline           != defaultflags.inline          && push!(opts, cacheflags.inline ? "--inline=yes" : "--inline=no")
424✔
1896
    cacheflags.opt_level        != defaultflags.opt_level       && push!(opts, "-O$(cacheflags.opt_level)")
424✔
1897
    return opts
424✔
1898
end
1899

1900
function show(io::IO, cf::CacheFlags)
36✔
1901
    print(io, "CacheFlags(")
36✔
1902
    print(io, "; use_pkgimages=")
36✔
1903
    print(io, cf.use_pkgimages)
36✔
1904
    print(io, ", debug_level=")
36✔
1905
    print(io, cf.debug_level)
36✔
1906
    print(io, ", check_bounds=")
36✔
1907
    print(io, cf.check_bounds)
36✔
1908
    print(io, ", inline=")
36✔
1909
    print(io, cf.inline)
36✔
1910
    print(io, ", opt_level=")
36✔
1911
    print(io, cf.opt_level)
36✔
1912
    print(io, ")")
36✔
1913
end
1914

1915
function Base.parse(::Type{CacheFlags}, s::AbstractString)
3✔
1916
    e = Meta.parse(s)
3✔
1917
    if !(e isa Expr && e.head === :call && length(e.args) == 2 &&
6✔
1918
        e.args[1] === :CacheFlags &&
1919
        e.args[2] isa Expr && e.args[2].head == :parameters)
1920
        throw(ArgumentError("Malformed CacheFlags string"))
×
1921
    end
1922
    params = Dict{Symbol, Any}(p.args[1] => p.args[2] for p in e.args[2].args)
3✔
1923
    use_pkgimages = get(params, :use_pkgimages, nothing)
3✔
1924
    debug_level = get(params, :debug_level, nothing)
3✔
1925
    check_bounds = get(params, :check_bounds, nothing)
3✔
1926
    inline = get(params, :inline, nothing)
3✔
1927
    opt_level = get(params, :opt_level, nothing)
3✔
1928
    return CacheFlags(; use_pkgimages, debug_level, check_bounds, inline, opt_level)
3✔
1929
end
1930

1931
struct ImageTarget
1932
    name::String
3✔
1933
    flags::Int32
1934
    ext_features::String
1935
    features_en::Vector{UInt8}
1936
    features_dis::Vector{UInt8}
1937
end
1938

1939
function parse_image_target(io::IO)
3✔
1940
    flags = read(io, Int32)
3✔
1941
    nfeature = read(io, Int32)
3✔
1942
    feature_en = read(io, 4*nfeature)
3✔
1943
    feature_dis = read(io, 4*nfeature)
3✔
1944
    name_len = read(io, Int32)
3✔
1945
    name = String(read(io, name_len))
6✔
1946
    ext_features_len = read(io, Int32)
3✔
1947
    ext_features = String(read(io, ext_features_len))
3✔
1948
    ImageTarget(name, flags, ext_features, feature_en, feature_dis)
3✔
1949
end
1950

1951
function parse_image_targets(targets::Vector{UInt8})
3✔
1952
    io = IOBuffer(targets)
3✔
1953
    ntargets = read(io, Int32)
3✔
1954
    targets = Vector{ImageTarget}(undef, ntargets)
3✔
1955
    for i in 1:ntargets
6✔
1956
        targets[i] = parse_image_target(io)
3✔
1957
    end
3✔
1958
    return targets
3✔
1959
end
1960

1961
function current_image_targets()
1962
    targets = @ccall jl_reflect_clone_targets()::Vector{UInt8}
×
1963
    return parse_image_targets(targets)
×
1964
end
1965

1966
struct FeatureName
1967
    name::Cstring
1968
    bit::UInt32 # bit index into a `uint32_t` array;
1969
    llvmver::UInt32 # 0 if it is available on the oldest LLVM version we support
1970
end
1971

1972
function feature_names()
×
1973
    fnames = Ref{Ptr{FeatureName}}()
×
1974
    nf = Ref{Csize_t}()
×
1975
    @ccall jl_reflect_feature_names(fnames::Ptr{Ptr{FeatureName}}, nf::Ptr{Csize_t})::Cvoid
×
1976
    if fnames[] == C_NULL
×
1977
        @assert nf[] == 0
×
1978
        return Vector{FeatureName}(undef, 0)
×
1979
    end
1980
    Base.unsafe_wrap(Array, fnames[], nf[], own=false)
×
1981
end
1982

1983
function test_feature(features::Vector{UInt8}, feat::FeatureName)
×
1984
    bitidx = feat.bit
×
1985
    u8idx = div(bitidx, 8) + 1
×
1986
    bit = bitidx % 8
×
1987
    return (features[u8idx] & (1 << bit)) != 0
×
1988
end
1989

1990
function show(io::IO, it::ImageTarget)
×
1991
    print(io, it.name)
×
1992
    if !isempty(it.ext_features)
×
1993
        print(io, ",", it.ext_features)
×
1994
    end
1995
    print(io, "; flags=", it.flags)
×
1996
    print(io, "; features_en=(")
×
1997
    first = true
×
1998
    for feat in feature_names()
×
1999
        if test_feature(it.features_en, feat)
×
2000
            name = Base.unsafe_string(feat.name)
×
2001
            if first
×
2002
                first = false
×
2003
                print(io, name)
×
2004
            else
2005
                print(io, ", ", name)
×
2006
            end
2007
        end
2008
    end
×
2009
    print(io, ")")
×
2010
    # Is feature_dis useful?
2011
end
2012

2013
# should sync with the types of arguments of `stale_cachefile`
2014
const StaleCacheKey = Tuple{PkgId, UInt128, PkgLoadSpec, String, Bool, CacheFlags}
2015

2016
function compilecache_freshest_path(pkg::PkgId;
834✔
2017
        ignore_loaded::Bool=false,
2018
        stale_cache::Dict{StaleCacheKey,Bool}=Dict{StaleCacheKey, Bool}(),
2019
        cachepath_cache::Dict{PkgId, Vector{String}}=Dict{PkgId, Vector{String}}(),
2020
        cachepaths::Vector{String}=get(() -> find_all_in_cache_path(pkg), cachepath_cache, pkg),
12✔
2021
        sourcespec::Union{PkgLoadSpec,Nothing}=Base.locate_package_load_spec(pkg),
2022
        flags::CacheFlags=CacheFlags())
2023
    isnothing(sourcespec) && error("Cannot locate source for $(repr("text/plain", pkg))")
369✔
2024
    try_build_ids = UInt128[UInt128(0)]
369✔
2025
    if !ignore_loaded
369✔
2026
        let loaded = get(loaded_precompiles, pkg, nothing)
369✔
2027
            if loaded !== nothing
339✔
2028
                for mod in loaded # try these in reverse original load order to see if one is already valid
30✔
2029
                    pushfirst!(try_build_ids, module_build_id(mod))
60✔
2030
                end
30✔
2031
            end
2032
        end
2033
    end
2034
    for build_id in try_build_ids
369✔
2035
        @label next_path for path_to_try in cachepaths
369✔
2036
            staledeps = stale_cachefile(pkg, build_id, sourcespec, path_to_try; ignore_loaded, requested_flags=flags)
60✔
2037
            if staledeps === true
60✔
2038
                continue
9✔
2039
            end
2040
            staledeps, _, _ = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
51✔
2041
            # finish checking staledeps module graph
2042
            @label next_dep for dep in staledeps
51✔
2043
                dep isa Module && continue
708✔
2044
                modspec, modkey, modbuild_id = dep::Tuple{PkgLoadSpec, PkgId, UInt128}
9✔
2045
                modpaths = get(() -> find_all_in_cache_path(modkey), cachepath_cache, modkey)
18✔
2046
                for modpath_to_try in modpaths::Vector{String}
9✔
2047
                    stale_cache_key = (modkey, modbuild_id, modspec, modpath_to_try, ignore_loaded, flags)::StaleCacheKey
9✔
2048
                    if get!(() -> stale_cachefile(modkey, modbuild_id, modspec, modpath_to_try; ignore_loaded, requested_flags=flags) === true,
18✔
2049
                            stale_cache, stale_cache_key)
2050
                        continue
×
2051
                    end
2052
                    continue next_dep
9✔
2053
                end
×
2054
                continue next_path
×
2055
            end
708✔
2056
            try
51✔
2057
                # update timestamp of precompilation file so that it is the first to be tried by code loading
2058
                touch(path_to_try)
51✔
2059
            catch ex
2060
                # file might be read-only and then we fail to update timestamp, which is fine
2061
                ex isa IOError || rethrow()
×
2062
            end
2063
            return path_to_try
51✔
2064
        end
9✔
2065
    end
318✔
2066
end
2067

2068
"""
2069
    Base.isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
2070

2071
Return whether a given PkgId within the active project is precompiled.
2072

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

2078
!!! compat "Julia 1.10"
2079
    This function requires at least Julia 1.10.
2080
"""
2081
function isprecompiled(pkg::PkgId; ignore_loaded::Bool=false)
48✔
2082
    path = compilecache_freshest_path(pkg; ignore_loaded)
36✔
2083
    return !isnothing(path)
54✔
2084
end
2085

2086
"""
2087
    Base.isrelocatable(pkg::PkgId)
2088

2089
Return whether a given PkgId within the active project is precompiled and the
2090
associated cache is relocatable.
2091

2092
!!! compat "Julia 1.11"
2093
    This function requires at least Julia 1.11.
2094
"""
2095
function isrelocatable(pkg::PkgId)
×
2096
    path = compilecache_freshest_path(pkg)
×
2097
    isnothing(path) && return false
×
2098
    io = open(path, "r")
×
2099
    try
×
2100
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
2101
        _, (includes, includes_srcfiles, _), _... = _parse_cache_header(io, path)
×
2102
        for inc in includes
×
2103
            !startswith(inc.filename, "@depot") && return false
×
2104
            if inc ∉ includes_srcfiles
×
2105
                # its an include_dependency
2106
                track_content = inc.mtime == -1.0
×
2107
                track_content || return false
×
2108
            end
2109
        end
×
2110
    finally
2111
        close(io)
×
2112
    end
2113
    return true
×
2114
end
2115

2116
function parse_cache_buildid(cachepath::String)
192✔
2117
    f = open(cachepath, "r")
192✔
2118
    try
192✔
2119
        checksum = isvalid_cache_header(f)
192✔
2120
        iszero(checksum) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
192✔
2121
        flags = read(f, UInt8)
192✔
2122
        syntax_version = read(f, UInt8)
192✔
2123
        n = read(f, Int32)
192✔
2124
        n == 0 && error("no module defined in $cachefile")
192✔
2125
        skip(f, n) # module name
192✔
2126
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
192✔
2127
        build_id = (UInt128(checksum) << 64) | read(f, UInt64)
192✔
2128
        return build_id, uuid
192✔
2129
    finally
2130
        close(f)
192✔
2131
    end
2132
end
2133

2134
# search for a precompile cache file to load, after some various checks
2135
function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128)
2,205✔
2136
    assert_havelock(require_lock)
4,410✔
2137
    loaded = start_loading(modkey, build_id, false)
2,205✔
2138
    if loaded === nothing
2,205✔
2139
        try
96✔
2140
            modspec = locate_package_load_spec(modkey)
96✔
2141
            isnothing(modspec) && error("Cannot locate source for $(repr("text/plain", modkey))")
192✔
2142
            set_pkgorigin_version_path(modkey, modspec.path)
96✔
2143
            loaded = _require_search_from_serialized(modkey, modspec, build_id, true)
96✔
2144
        finally
2145
            end_loading(modkey, loaded)
192✔
2146
        end
2147
        if loaded isa Module
96✔
2148
            insert_extension_triggers(modkey)
168✔
2149
            run_package_callbacks(modkey)
96✔
2150
        end
2151
    end
2152
    if loaded isa Module && PkgId(loaded) == modkey && module_build_id(loaded) === build_id
3,924✔
2153
        return loaded
2,205✔
2154
    end
2155
    return ErrorException("Required dependency $modkey failed to load from a cache file.")
×
2156
end
2157

2158
# loads a precompile cache file, ignoring stale_cachefile tests
2159
# load all dependent modules first
2160
function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String})
147✔
2161
    assert_havelock(require_lock)
294✔
2162
    local depmodnames
2163
    io = open(path, "r")
147✔
2164
    try
147✔
2165
        iszero(isvalid_cache_header(io)) && return ArgumentError("Incompatible header in cache file $path.")
147✔
2166
        _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path)
147✔
2167

2168

2169
        pkgimage = !isempty(clone_targets)
147✔
2170
        if pkgimage
147✔
2171
            ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided")
135✔
2172
            isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.")
135✔
2173
            ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile")
135✔
2174
            # TODO: Check for valid clone_targets?
2175
            isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.")
135✔
2176
        else
2177
            @assert ocachepath === nothing
12✔
2178
        end
2179
        isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.")
147✔
2180
    finally
2181
        close(io)
147✔
2182
    end
2183
    ndeps = length(depmodnames)
147✔
2184
    depmods = Vector{Any}(undef, ndeps)
147✔
2185
    for i in 1:ndeps
294✔
2186
        modkey, build_id = depmodnames[i]
2,166✔
2187
        dep = _tryrequire_from_serialized(modkey, build_id)
2,166✔
2188
        if !isa(dep, Module)
2,166✔
2189
            return dep
×
2190
        end
2191
        depmods[i] = dep
2,166✔
2192
    end
4,185✔
2193
    # then load the file
2194
    loaded = _include_from_serialized(pkg, path, ocachepath, depmods; register = true)
147✔
2195
    return loaded
147✔
2196
end
2197

2198
# returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it or it was stale
2199
# returns the set of modules restored if the cache load succeeded
2200
@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcespec::PkgLoadSpec, build_id::UInt128, stalecheck::Bool; reasons=nothing, DEPOT_PATH::typeof(DEPOT_PATH)=DEPOT_PATH)
2,368✔
2201
    assert_havelock(require_lock)
2,368✔
2202
    paths = find_all_in_cache_path(pkg, DEPOT_PATH)
1,184✔
2203
    newdeps = PkgId[]
1,184✔
2204
    try_build_ids = UInt128[build_id]
1,184✔
2205
    if build_id == UInt128(0)
1,184✔
2206
        let loaded = get(loaded_precompiles, pkg, nothing)
1,088✔
2207
            if loaded !== nothing
1,088✔
2208
                for mod in loaded # try these in reverse original load order to see if one is already valid
×
2209
                    pushfirst!(try_build_ids, module_build_id(mod))
×
2210
                end
×
2211
            end
2212
        end
2213
    end
2214
    for build_id in try_build_ids
1,184✔
2215
        @label next_path for path_to_try in paths::Vector{String}
1,184✔
2216
            staledeps = stale_cachefile(pkg, build_id, sourcespec, path_to_try; reasons, stalecheck)
1,860✔
2217
            if staledeps === true
930✔
2218
                continue
82✔
2219
            end
2220
            staledeps, ocachefile, newbuild_id = staledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
848✔
2221
            startedloading = length(staledeps) + 1
848✔
2222
            try # any exit from here (goto, break, continue, return) will end_loading
848✔
2223
                # finish checking staledeps module graph, while acquiring all start_loading locks
2224
                # so that concurrent require calls won't make any different decisions that might conflict with the decisions here
2225
                # note that start_loading will drop the loading lock if necessary
2226
                let i = 0
848✔
2227
                    # start_loading here has a deadlock problem if we try to load `A,B,C` and `B,A,D` at the same time:
2228
                    # 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
2229
                    # solve that by making sure we can start_loading everything before allocating each of those and doing all the stale checks
2230
                    while i < length(staledeps)
14,730✔
2231
                        i += 1
13,882✔
2232
                        dep = staledeps[i]
13,882✔
2233
                        dep isa Module && continue
13,882✔
2234
                        _, modkey, modbuild_id = dep::Tuple{PkgLoadSpec, PkgId, UInt128}
2,432✔
2235
                        dep = canstart_loading(modkey, modbuild_id, stalecheck)
2,432✔
2236
                        if dep isa Module
2,432✔
2237
                            if PkgId(dep) == modkey && module_build_id(dep) === modbuild_id
×
2238
                                staledeps[i] = dep
×
2239
                                continue
×
2240
                            else
2241
                                @debug "Rejecting cache file $path_to_try because module $modkey got loaded at a different version than expected."
×
2242
                                continue next_path
×
2243
                            end
2244
                            continue
×
2245
                        elseif dep === nothing
2,432✔
2246
                            continue
2,432✔
2247
                        end
2248
                        wait(dep) # releases require_lock, so requires restarting this loop
×
2249
                        i = 0
×
2250
                    end
13,882✔
2251
                end
2252
                @label next_dep for i in reverse(eachindex(staledeps))
1,696✔
2253
                    dep = staledeps[i]
13,804✔
2254
                    dep isa Module && continue
13,804✔
2255
                    modspec, modkey, modbuild_id = dep::Tuple{PkgLoadSpec, PkgId, UInt128}
2,432✔
2256
                    # inline a call to start_loading here
2257
                    @assert canstart_loading(modkey, modbuild_id, stalecheck) === nothing
2,432✔
2258
                    package_locks[modkey] = (current_task(), Threads.Condition(require_lock), modbuild_id)
2,432✔
2259
                    startedloading = i
2,432✔
2260
                    modpaths = find_all_in_cache_path(modkey, DEPOT_PATH)
2,432✔
2261
                    for modpath_to_try in modpaths
2,432✔
2262
                        modstaledeps = stale_cachefile(modkey, modbuild_id, modspec, modpath_to_try; stalecheck)
9,196✔
2263
                        if modstaledeps === true
4,598✔
2264
                            continue
2,172✔
2265
                        end
2266
                        modstaledeps, modocachepath, _ = modstaledeps::Tuple{Vector{Any}, Union{Nothing, String}, UInt128}
2,426✔
2267
                        staledeps[i] = (modspec, modkey, modbuild_id, modpath_to_try, modstaledeps, modocachepath)
2,426✔
2268
                        continue next_dep
2,426✔
2269
                    end
2,172✔
2270
                    @debug "Rejecting cache file $path_to_try because required dependency $modkey with build ID $(UUID(modbuild_id)) is missing from the cache."
6✔
2271
                    continue next_path
6✔
2272
                end
26,754✔
2273
                M = maybe_loaded_precompile(pkg, newbuild_id)
842✔
2274
                if isa(M, Module)
842✔
2275
                    stalecheck && register_root_module(M)
×
2276
                    return M
×
2277
                end
2278
                if stalecheck
842✔
2279
                    try
836✔
2280
                        touch(path_to_try) # update timestamp of precompilation file
836✔
2281
                    catch ex # file might be read-only and then we fail to update timestamp, which is fine
2282
                        ex isa IOError || rethrow()
×
2283
                    end
2284
                end
2285
                # finish loading module graph into staledeps
2286
                # 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
2287
                for i in eachindex(staledeps)
1,684✔
2288
                    dep = staledeps[i]
13,798✔
2289
                    dep isa Module && continue
13,798✔
2290
                    modspec, modkey, modbuild_id, modcachepath, modstaledeps, modocachepath = dep::Tuple{PkgLoadSpec, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
2,426✔
2291
                    set_pkgorigin_version_path(modkey, modspec.path)
2,426✔
2292
                    dep = _include_from_serialized(modkey, modcachepath, modocachepath, modstaledeps; register = stalecheck)
4,852✔
2293
                    if !isa(dep, Module)
2,426✔
2294
                        @debug "Rejecting cache file $path_to_try because required dependency $modkey failed to load from cache file for $modcachepath." exception=dep
×
2295
                        continue next_path
×
2296
                    else
2297
                        startedloading = i + 1
2,426✔
2298
                        end_loading(modkey, dep)
2,426✔
2299
                        staledeps[i] = dep
2,426✔
2300
                        push!(newdeps, modkey)
2,426✔
2301
                    end
2302
                end
26,754✔
2303
                restored = maybe_loaded_precompile(pkg, newbuild_id)
842✔
2304
                if !isa(restored, Module)
842✔
2305
                    restored = _include_from_serialized(pkg, path_to_try, ocachefile, staledeps; register = stalecheck)
1,678✔
2306
                end
2307
                isa(restored, Module) && return restored
842✔
2308
                @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored
×
2309
            finally
2310
                # cancel all start_loading locks that were taken but not fulfilled before failing
2311
                for i in startedloading:length(staledeps)
866✔
2312
                    dep = staledeps[i]
45✔
2313
                    dep isa Module && continue
45✔
2314
                    if dep isa Tuple{PkgLoadSpec, PkgId, UInt128}
6✔
2315
                        _, modkey, _ = dep
6✔
2316
                    else
2317
                        _, modkey, _ = dep::Tuple{PkgLoadSpec, PkgId, UInt128, String, Vector{Any}, Union{Nothing, String}}
×
2318
                    end
2319
                    end_loading(modkey, nothing)
6✔
2320
                end
72✔
2321
                for modkey in newdeps
848✔
2322
                    insert_extension_triggers(modkey)
4,831✔
2323
                    stalecheck && run_package_callbacks(modkey)
2,426✔
2324
                end
2,426✔
2325
            end
2326
        end
88✔
2327
    end
342✔
2328
    return nothing
342✔
2329
end
2330

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

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

2338
function canstart_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
8,704✔
2339
    assert_havelock(require_lock)
17,408✔
2340
    require_lock.reentrancy_cnt == 1 || throw(ConcurrencyViolationError("recursive call to start_loading"))
8,704✔
2341
    loading = get(package_locks, modkey, nothing)
8,750✔
2342
    if loading === nothing
8,704✔
2343
        loaded = stalecheck ? maybe_root_module(modkey) : nothing
8,658✔
2344
        loaded isa Module && return loaded
8,658✔
2345
        if build_id != UInt128(0)
8,046✔
2346
            loaded = maybe_loaded_precompile(modkey, build_id)
7,069✔
2347
            loaded isa Module && return loaded
7,069✔
2348
        end
2349
        return nothing
5,937✔
2350
    end
2351
    if !stalecheck && build_id != UInt128(0) && loading[3] != build_id
46✔
2352
        # don't block using an existing specific loaded module on needing a different concurrently loaded one
2353
        loaded = maybe_loaded_precompile(modkey, build_id)
×
2354
        loaded isa Module && return loaded
×
2355
    end
2356
    # load already in progress for this module on the task
2357
    task, cond = loading
46✔
2358
    deps = String[modkey.name]
46✔
2359
    assert_havelock(cond.lock)
92✔
2360
    if debug_loading_deadlocks && current_task() !== task
46✔
2361
        waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks
43✔
2362
        for each in package_locks
86✔
2363
            cond2 = each[2][2]
43✔
2364
            assert_havelock(cond2.lock)
86✔
2365
            for waiting in cond2.waitq
43✔
2366
                push!(waiters, waiting => (each[2][1] => each[1]))
42✔
2367
            end
42✔
2368
        end
43✔
2369
        while true
43✔
2370
            running = get(waiters, task, nothing)
43✔
2371
            running === nothing && break
43✔
2372
            task, pkgid = running
×
2373
            push!(deps, pkgid.name)
×
2374
            task === current_task() && break
×
2375
        end
×
2376
    end
2377
    if current_task() === task
46✔
2378
        push!(deps, modkey.name) # repeat this to emphasize the cycle here
3✔
2379
        others = Set{String}()
3✔
2380
        for each in package_locks # list the rest of the packages being loaded too
6✔
2381
            if each[2][1] === task
3✔
2382
                other = each[1].name
3✔
2383
                other == modkey.name || push!(others, other)
3✔
2384
            end
2385
        end
3✔
2386
        # remove duplicates from others already in deps
2387
        for dep in deps
3✔
2388
            delete!(others, dep)
6✔
2389
        end
6✔
2390
        msg = sprint(deps, others) do io, deps, others
3✔
2391
            print(io, "deadlock detected in loading ")
3✔
2392
            join(io, deps, " using ")
3✔
2393
            if !isempty(others)
3✔
2394
                print(io, " (while loading ")
×
2395
                join(io, others, " and ")
×
2396
                print(io, ")")
×
2397
            end
2398
        end
2399
        throw(ConcurrencyViolationError(msg))
3✔
2400
    end
2401
    return cond
43✔
2402
end
2403

2404
function start_loading(modkey::PkgId, build_id::UInt128, stalecheck::Bool)
3,840✔
2405
    # handle recursive and concurrent calls to require
2406
    while true
3,840✔
2407
        loaded = canstart_loading(modkey, build_id, stalecheck)
3,840✔
2408
        if loaded === nothing
3,837✔
2409
            package_locks[modkey] = (current_task(), Threads.Condition(require_lock), build_id)
1,073✔
2410
            return nothing
1,073✔
2411
        elseif loaded isa Module
2,764✔
2412
            return loaded
2,721✔
2413
        end
2414
        loaded = wait(loaded)
43✔
2415
        loaded isa Module && return loaded
43✔
2416
    end
×
2417
end
2418

2419
function end_loading(modkey::PkgId, @nospecialize loaded)
3,511✔
2420
    assert_havelock(require_lock)
7,022✔
2421
    loading = pop!(package_locks, modkey)
3,511✔
2422
    notify(loading[2], loaded, all=true)
3,511✔
2423
    nothing
3,511✔
2424
end
2425

2426
# to notify downstream consumers that a module was successfully loaded
2427
# Callbacks take the form (mod::Base.PkgId) -> nothing.
2428
# WARNING: This is an experimental feature and might change later, without deprecation.
2429
const package_callbacks = Any[]
2430
# to notify downstream consumers that a file has been included into a particular module
2431
# Callbacks take the form (mod::Module, filename::String) -> nothing
2432
# WARNING: This is an experimental feature and might change later, without deprecation.
2433
const include_callbacks = Any[]
2434

2435
# used to optionally track dependencies when requiring a module:
2436
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
2437
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
2438
const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies
2439

2440
function _include_dependency(mod::Module, _path::AbstractString; track_content::Bool=true,
297✔
2441
                             path_may_be_dir::Bool=false)
2442
    _include_dependency!(_require_dependencies, _track_dependencies[], mod, _path, track_content, path_may_be_dir)
297✔
2443
end
2444

2445
function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool,
288✔
2446
                              mod::Module, _path::AbstractString,
2447
                              track_content::Bool, path_may_be_dir::Bool)
2448
    prev = source_path(nothing)
384✔
2449
    if prev === nothing
288✔
2450
        path = abspath(_path)
96✔
2451
    else
2452
        path = normpath(joinpath(dirname(prev), _path))
192✔
2453
    end
2454
    if !track_dependencies[]
288✔
2455
        if !path_may_be_dir && !isfile(path)
288✔
2456
            throw(SystemError("opening file $(repr(path))", Libc.ENOENT))
9✔
2457
        elseif path_may_be_dir && !Filesystem.isreadable(path)
279✔
2458
            throw(SystemError("opening file or folder $(repr(path))", Libc.ENOENT))
12✔
2459
        end
2460
    else
2461
        @lock require_lock begin
×
2462
            if track_content
×
2463
                hash = (isdir(path) ? _crc32c(join(readdir(path))) : open(_crc32c, path, "r"))::UInt32
×
2464
                # use mtime=-1.0 here so that fsize==0 && mtime==0.0 corresponds to a missing include_dependency
2465
                push!(dep_list, (mod, path, UInt64(filesize(path)), hash, -1.0))
×
2466
            else
2467
                push!(dep_list, (mod, path, UInt64(0), UInt32(0), mtime(path)))
×
2468
            end
2469
        end
2470
    end
2471
    return path, prev
267✔
2472
end
2473

2474
"""
2475
    include_dependency(path::AbstractString; track_content::Bool=true)
2476

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

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

2486
!!! compat "Julia 1.11"
2487
    Keyword argument `track_content` requires at least Julia 1.11.
2488
    An error is now thrown if `path` is not readable.
2489
"""
2490
function include_dependency(path::AbstractString; track_content::Bool=true)
30✔
2491
    _include_dependency(Main, path, track_content=track_content, path_may_be_dir=true)
30✔
2492
    return nothing
15✔
2493
end
2494

2495
# we throw PrecompilableError when a module doesn't want to be precompiled
2496
import Core: PrecompilableError
2497
function show(io::IO, ex::PrecompilableError)
×
2498
    print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.")
×
2499
end
2500
precompilableerror(ex::PrecompilableError) = true
×
2501
precompilableerror(ex::WrappedException) = precompilableerror(ex.error)
×
2502
precompilableerror(@nospecialize ex) = false
×
2503

2504
# Call __precompile__(false) at the top of a tile prevent it from being precompiled (false)
2505
"""
2506
    __precompile__(isprecompilable::Bool)
2507

2508
Specify whether the file calling this function is precompilable, defaulting to `true`.
2509
If a module or file is *not* safely precompilable, it should call `__precompile__(false)` in
2510
order to throw an error if Julia attempts to precompile it.
2511
"""
2512
@noinline function __precompile__(isprecompilable::Bool=true)
6✔
2513
    if !isprecompilable && generating_output()
6✔
2514
        throw(PrecompilableError())
×
2515
    end
2516
    nothing
6✔
2517
end
2518

2519
# require always works in Main scope and loads files from node 1
2520
# XXX: (this is deprecated, but still used by Distributed)
2521
const toplevel_load = Ref(true)
2522

2523
const _require_world_age = Ref{UInt}(typemax(UInt))
2524

2525
"""
2526
    Base.TRACE_EVAL
2527

2528
Global control for expression tracing during top-level evaluation. This setting takes priority
2529
over the `--trace-eval` command-line option.
2530

2531
Set to:
2532
- `nothing` - use the command-line `--trace-eval` setting (default)
2533
- `:no` - disable expression tracing
2534
- `:loc` - show only location information during evaluation
2535
- `:full` - show full expressions being evaluated
2536

2537
# Examples
2538
```julia
2539
# Enable full expression tracing
2540
Base.TRACE_EVAL = :full
2541

2542
# Show only locations
2543
Base.TRACE_EVAL = :loc
2544

2545
# Disable tracing (overrides command-line setting)
2546
Base.TRACE_EVAL = :no
2547

2548
# Reset to use command-line setting
2549
Base.TRACE_EVAL = nothing
2550
```
2551

2552
See also: [Command-line Interface](@ref cli) for the `--trace-eval` option.
2553
"""
2554
TRACE_EVAL::Union{Symbol,Nothing} = nothing
2555

2556
function require(into::Module, mod::Symbol)
2557
    world = _require_world_age[]
1,503✔
2558
    if world == typemax(UInt)
1,503✔
2559
        world = get_world_counter()
1,503✔
2560
    end
2561
    return Compiler.@zone "LOAD_Require" invoke_in_world(world, __require, into, mod)
1,503✔
2562
end
2563

2564
function check_for_hint(into, mod)
12✔
2565
    return begin
12✔
2566
        if isdefined(into, mod) && getfield(into, mod) isa Module
12✔
2567
            true, "."
4✔
2568
        elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
8✔
2569
            true, ".."
2✔
2570
        else
2571
            false, ""
6✔
2572
        end
2573
    end
2574
end
2575

2576
function __require(into::Module, mod::Symbol)
1,503✔
2577
    if into === __toplevel__ && generating_output(#=incremental=#true)
1,503✔
2578
        error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
×
2579
         is not allowed during package precompilation.")
2580
    end
2581
    topmod = moduleroot(into)
1,866✔
2582
    if nameof(topmod) === mod
1,503✔
2583
        return topmod
9✔
2584
    end
2585
    @lock require_lock begin
1,494✔
2586
    LOADING_CACHE[] = LoadingCache()
1,494✔
2587
    try
1,494✔
2588
        uuidkey_env = identify_package_env(into, String(mod))
1,494✔
2589
        # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env")
2590
        if uuidkey_env === nothing
1,494✔
2591
            where = PkgId(into)
×
2592
            if where.uuid === nothing
×
2593
                hint, dots = invokelatest(check_for_hint, into, mod)
×
2594
                hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
×
2595
                install_message = if mod != :Pkg
×
2596
                    start_sentence = hint ? "Otherwise, run" : "Run"
×
2597
                    "\n- $start_sentence `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package."
×
2598
                else  # for some reason Pkg itself isn't availability so do not tell them to use Pkg to install it.
2599
                    ""
×
2600
                end
2601

2602
                throw(ArgumentError("Package $mod not found in current path$hint_message.$install_message"))
×
2603
            else
2604
                manifest_warnings = collect_manifest_warnings()
×
2605
                throw(ArgumentError("""
×
2606
                Cannot load (`using/import`) module $mod into module $into in package $(where.name)
2607
                because package $(where.name) does not have $mod in its dependencies:
2608
                $manifest_warnings- You may have a partially installed environment. Try `Pkg.instantiate()`
2609
                  to ensure all packages in the environment are installed.
2610
                - Or, if you have $(where.name) checked out for development and have
2611
                  added $mod as a dependency but haven't updated your primary
2612
                  environment's manifest file, try `Pkg.resolve()`.
2613
                - Otherwise you may need to report an issue with $(where.name)"""))
2614
            end
2615
        end
2616
        uuidkey, env = uuidkey_env
2,988✔
2617
        if _track_dependencies[]
1,494✔
2618
            path = binpack(uuidkey)
×
2619
            push!(_require_dependencies, (into, path, UInt64(0), UInt32(0), 0.0))
×
2620
        end
2621
        return _require_prelocked(uuidkey, env)
2,988✔
2622
    finally
2623
        LOADING_CACHE[] = nothing
1,494✔
2624
    end
2625
    end
2626
end
2627

2628
function find_unsuitable_manifests_versions()
×
2629
    unsuitable_manifests = String[]
×
2630
    dev_manifests = String[]
×
2631
    for env in load_path()
×
2632
        project_file = env_project_file(env)
×
2633
        project_file isa String || continue # no project file
×
2634
        manifest_file = project_file_manifest_path(project_file)
×
2635
        manifest_file isa String || continue # no manifest file
×
2636
        m = parsed_toml(manifest_file)
×
2637
        man_julia_version = get(m, "julia_version", nothing)
×
2638
        @label _ begin
2639
            man_julia_version isa String || break _
×
2640
            man_julia_version = VersionNumber(man_julia_version)
×
2641
            thispatch(man_julia_version) != thispatch(VERSION) && break _
×
2642
            isempty(man_julia_version.prerelease) != isempty(VERSION.prerelease) && break _
×
2643
            isempty(man_julia_version.prerelease) && continue
×
2644
            man_julia_version.prerelease[1] != VERSION.prerelease[1] && break _
×
2645
            if VERSION.prerelease[1] == "DEV"
×
2646
                # manifests don't store the 2nd part of prerelease, so cannot check further
2647
                # so treat them specially in the warning
2648
                push!(dev_manifests, manifest_file)
×
2649
            end
2650
            continue
×
2651
        end
2652
        push!(unsuitable_manifests, string(manifest_file, " (v", man_julia_version, ")"))
×
2653
    end
×
2654
    return unsuitable_manifests, dev_manifests
×
2655
end
2656

2657
function collect_manifest_warnings()
×
2658
    unsuitable_manifests, dev_manifests = find_unsuitable_manifests_versions()
×
2659
    msg = ""
×
2660
    if !isempty(unsuitable_manifests)
×
2661
        msg *= """
×
2662
        - Note that the following manifests in the load path were resolved with a different
2663
          julia version, which may be the cause of the error. Try to re-resolve them in the
2664
          current version, or consider deleting them if that fails:
2665
            $(join(unsuitable_manifests, "\n    "))
2666
        """
2667
    end
2668
    if !isempty(dev_manifests)
×
2669
        msg *= """
×
2670
        - Note that the following manifests in the load path were resolved with a potentially
2671
          different DEV version of the current version, which may be the cause of the error.
2672
          Try to re-resolve them in the current version, or consider deleting them if that fails:
2673
            $(join(dev_manifests, "\n    "))
2674
        """
2675
    end
2676
    return msg
×
2677
end
2678

2679
function require(uuidkey::PkgId)
2680
    world = _require_world_age[]
85✔
2681
    if world == typemax(UInt)
85✔
2682
        world = get_world_counter()
15✔
2683
    end
2684
    return invoke_in_world(world, __require, uuidkey)
85✔
2685
end
2686
__require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey)
15✔
2687
function _require_prelocked(uuidkey::PkgId, env=nothing)
1,725✔
2688
    assert_havelock(require_lock)
3,357✔
2689
    m = start_loading(uuidkey, UInt128(0), true)
1,617✔
2690
    if m === nothing
1,614✔
2691
        last = toplevel_load[]
971✔
2692
        try
971✔
2693
            toplevel_load[] = false
971✔
2694
            m = __require_prelocked(uuidkey, env)
971✔
2695
            m isa Module || check_package_module_loaded_error(uuidkey)
962✔
2696
        finally
2697
            toplevel_load[] = last
971✔
2698
            end_loading(uuidkey, m)
971✔
2699
        end
2700
        insert_extension_triggers(uuidkey)
1,699✔
2701
        # After successfully loading, notify downstream consumers
2702
        run_package_callbacks(uuidkey)
962✔
2703
    end
2704
    return m
1,605✔
2705
end
2706

2707
mutable struct PkgOrigin
2708
    path::Union{String,Nothing}
3,502✔
2709
    cachepath::Union{String,Nothing}
2710
    version::Union{VersionNumber,Nothing}
2711
end
2712
PkgOrigin() = PkgOrigin(nothing, nothing, nothing)
3,502✔
2713
const pkgorigins = Dict{PkgId,PkgOrigin}()
2714

2715
const loaded_modules = Dict{PkgId,Module}() # available to be explicitly loaded
2716
const loaded_precompiles = Dict{PkgId,Vector{Module}}() # extended (complete) list of modules, available to be loaded
2717
const loaded_modules_order = Vector{Module}()
2718

2719
root_module_key(m::Module) = PkgId(m)
319,910✔
2720

2721
function maybe_loaded_precompile(key::PkgId, buildid::UInt128)
15,126✔
2722
    @lock require_lock begin
15,126✔
2723
    mods = get(loaded_precompiles, key, nothing)
23,503✔
2724
    mods === nothing && return
15,126✔
2725
    for mod in mods
8,377✔
2726
        module_build_id(mod) == buildid && return mod
8,377✔
2727
    end
×
2728
    end
2729
end
2730

2731
function module_build_id(m::Module)
19✔
2732
    hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m)
77,841✔
2733
    return (UInt128(hi) << 64) | lo
77,841✔
2734
end
2735

2736
@constprop :none function register_root_module(m::Module)
3,478✔
2737
    # n.b. This is called from C after creating a new module in `Base.__toplevel__`,
2738
    # instead of adding them to the binding table there.
2739
    @lock require_lock begin
3,478✔
2740
    key = PkgId(m, String(nameof(m)))
6,686✔
2741
    if haskey(loaded_modules, key)
3,478✔
2742
        oldm = loaded_modules[key]
×
2743
        if oldm !== m
×
2744
            if generating_output(#=incremental=#true)
×
2745
                error("Replacing module `$(key.name)`")
×
2746
            else
2747
                @warn "Replacing module `$(key.name)`"
×
2748
            end
2749
        end
2750
    end
2751
    maybe_loaded_precompile(key, module_build_id(m)) === nothing && push!(loaded_modules_order, m)
3,478✔
2752
    loaded_modules[key] = m
3,478✔
2753
    end
2754
    nothing
3,478✔
2755
end
2756

2757
register_root_module(Core)
2758
register_root_module(Base)
2759
register_root_module(Main)
2760

2761
# This is used as the current module when loading top-level modules.
2762
# It has the special behavior that modules evaluated in it get added
2763
# to the loaded_modules table instead of getting bindings.
2764
baremodule __toplevel__
2765
using Base
2766
global var"#_internal_julia_parse" = Core._parse
2767
global _internal_julia_lower = Core._lower
2768

2769
# Used for version checking of precompiled cache files only
2770
global _internal_syntax_version::UInt8 = 0
2771
end
2772

2773
# get a top-level Module from the given key
2774
# this is similar to `require`, but worse in almost every possible way
2775
root_module(key::PkgId) = @lock require_lock loaded_modules[key]
29,055✔
2776
function root_module(where::Module, name::Symbol)
21✔
2777
    key = identify_package(where, String(name))
84✔
2778
    key isa PkgId || throw(KeyError(name))
84✔
2779
    return root_module(key)
84✔
2780
end
2781
root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key)
×
2782
maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing)
56,043✔
2783

2784
loaded_modules_array() = @lock require_lock copy(loaded_modules_order)
1✔
2785

2786
# after unreference_module, a subsequent require call will try to load a new copy of it, if stale
2787
# reload(m) = (unreference_module(m); require(m))
2788
function unreference_module(key::PkgId)
1✔
2789
    @lock require_lock begin
1✔
2790
    if haskey(loaded_modules, key)
1✔
2791
        m = pop!(loaded_modules, key)
×
2792
        # need to ensure all modules are GC rooted; will still be referenced
2793
        # in loaded_modules_order
2794
    end
2795
    end
2796
end
2797

2798
# whoever takes the package_locks[pkg] must call this function immediately
2799
function set_pkgorigin_version_path(pkg::PkgId, path::String)
3,505✔
2800
    assert_havelock(require_lock)
7,010✔
2801
    pkgorigin = get!(PkgOrigin, pkgorigins, pkg)
3,505✔
2802
    # Pkg needs access to the version of packages in the sysimage.
2803
    if generating_output(#=incremental=#false)
3,505✔
2804
        pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), ".."))
×
2805
    end
2806
    pkgorigin.path = path
3,505✔
2807
    nothing
3,505✔
2808
end
2809

2810
disable_parallel_precompile::Bool = false
2811

2812
# Returns `nothing` or the new(ish) module
2813
function __require_prelocked(pkg::PkgId, env)
977✔
2814
    assert_havelock(require_lock)
1,954✔
2815

2816
    # perform the search operation to select the module file require intends to load
2817
    specenv = locate_package_env(pkg, env)
977✔
2818
    if specenv === nothing
977✔
2819
        throw(ArgumentError("""
×
2820
            Package $(repr("text/plain", pkg)) is required but does not seem to be installed:
2821
             - Run `Pkg.instantiate()` to install all recorded dependencies.
2822
            """))
2823
    end
2824
    spec = specenv[1]
977✔
2825
    path = spec.path
977✔
2826
    set_pkgorigin_version_path(pkg, path)
977✔
2827

2828
    parallel_precompile_attempted = Ref(false) # being safe to avoid getting stuck in a precompilepkgs loop
977✔
2829
    reasons = Dict{String,Int}()
977✔
2830
    # attempt to load the module file via the precompile cache locations
2831
    if JLOptions().use_compiled_modules != 0
977✔
2832
        @label load_from_cache
2833
        loaded = _require_search_from_serialized(pkg, spec, UInt128(0), true; reasons)
908✔
2834
        if loaded isa Module
908✔
2835
            return loaded
740✔
2836
        end
2837
    end
2838

2839
    if JLOptions().use_compiled_modules == 3
237✔
2840
        error("Precompiled image $pkg not available with flags $(CacheFlags())")
×
2841
    end
2842

2843
    # if the module being required was supposed to have a particular version
2844
    # but it was not handled by the precompile loader, complain
2845
    for (concrete_pkg, concrete_build_id) in _concrete_dependencies
237✔
2846
        if pkg == concrete_pkg
×
2847
            @warn """Module $(pkg.name) with build ID $((UUID(concrete_build_id))) is missing from the cache.
×
2848
                 This may mean $(repr("text/plain", pkg)) does not support precompilation but is imported by a module that does."""
2849
            if JLOptions().incremental != 0
×
2850
                # during incremental precompilation, this should be fail-fast
2851
                throw(PrecompilableError())
×
2852
            end
2853
        end
2854
    end
×
2855

2856
    if JLOptions().use_compiled_modules == 1
237✔
2857
        if !generating_output(#=incremental=#false)
165✔
2858
            # spawn off a new incremental pre-compile task for recursive `require` calls
2859
            loaded = let spec = spec, reasons = reasons, parallel_precompile_attempted = parallel_precompile_attempted
165✔
2860
                maybe_cachefile_lock(pkg, spec.path) do
165✔
2861
                    # double-check the search now that we have lock
2862
                    m = _require_search_from_serialized(pkg, spec, UInt128(0), true)
174✔
2863
                    m isa Module && return m
174✔
2864

2865
                    local verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
348✔
2866
                    @logmsg verbosity "Precompiling $(repr("text/plain", pkg))$(list_reasons(reasons))"
174✔
2867

2868
                    unlock(require_lock)
174✔
2869
                    try
174✔
2870
                        if !generating_output() && !parallel_precompile_attempted[] && !disable_parallel_precompile && @isdefined(Precompilation)
174✔
2871
                            parallel_precompile_attempted[] = true
138✔
2872
                            # Note that we use @invokelatest here to avoid world
2873
                            # age issues when printing, see:
2874
                            # https://github.com/JuliaLang/julia/issues/60223
2875
                            precompiled = @invokelatest Precompilation.precompilepkgs([pkg]; _from_loading=true, ignore_loaded=false)
138✔
2876
                            # prcompiled returns either nothing, indicating it needs serial precompile,
2877
                            # or the entry(ies) that it found would be best to load (possibly because it just created it)
2878
                            # or an empty set of entries (indicating the precompile should be skipped)
2879
                            if precompiled !== nothing
129✔
2880
                                isempty(precompiled) && return PrecompilableError() # oops, Precompilation forgot to report what this might actually be
129✔
2881
                                local cachefile = precompiled[1]
120✔
2882
                                local ocachefile = nothing
120✔
2883
                                if JLOptions().use_pkgimages == 1
120✔
2884
                                    ocachefile = ocachefile_from_cachefile(cachefile)
117✔
2885
                                end
2886
                                return cachefile, ocachefile
120✔
2887
                            end
2888
                        end
2889
                        triggers = get(EXT_PRIMED, pkg, nothing)
48✔
2890
                        loadable_exts = nothing
36✔
2891
                        if triggers !== nothing # extension
36✔
2892
                            loadable_exts = PkgId[]
12✔
2893
                            for (ext′, triggers′) in EXT_PRIMED
24✔
2894
                                if triggers′ ⊊ triggers
24✔
2895
                                    push!(loadable_exts, ext′)
9✔
2896
                                end
2897
                            end
36✔
2898
                        end
2899
                        return compilecache(pkg, spec; loadable_exts)
36✔
2900
                    finally
2901
                        lock(require_lock)
174✔
2902
                    end
2903
                end
2904
            end
2905
            loaded isa Module && return loaded
156✔
2906
            if isnothing(loaded) # maybe_cachefile_lock returns nothing if it had to wait for another process
156✔
2907
                @goto load_from_cache # the new cachefile will have the newest mtime so will come first in the search
×
2908
            elseif isa(loaded, Exception)
156✔
2909
                if precompilableerror(loaded)
9✔
2910
                    # Intentionally not logging - __precompile__(false) is not an error
2911
                else
2912
                    @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
×
2913
                end
2914
                # fall-through to loading the file locally if not incremental
2915
            else
2916
                cachefile, ocachefile = loaded::Tuple{String, Union{Nothing, String}}
147✔
2917
                loaded = _tryrequire_from_serialized(pkg, cachefile, ocachefile)
282✔
2918
                if !isa(loaded, Module)
147✔
2919
                    @warn "The call to compilecache failed to create a usable precompiled cache file for $(repr("text/plain", pkg))" exception=loaded
×
2920
                else
2921
                    return loaded
147✔
2922
                end
2923
            end
2924
            if JLOptions().incremental != 0
9✔
2925
                # during incremental precompilation, this should be fail-fast
2926
                throw(PrecompilableError())
×
2927
            end
2928
        end
2929
    end
2930

2931
    # just load the file normally via include
2932
    # for unknown dependencies
2933
    uuid = pkg.uuid
81✔
2934
    uuid = (uuid === nothing ? (UInt64(0), UInt64(0)) : convert(NTuple{2, UInt64}, uuid))
138✔
2935
    old_uuid = ccall(:jl_module_uuid, NTuple{2, UInt64}, (Any,), __toplevel__)
81✔
2936
    if uuid !== old_uuid
81✔
2937
        ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, uuid)
57✔
2938
    end
2939
    __toplevel__.var"#_internal_julia_parse" = VersionedParse(spec.julia_syntax_version)
81✔
2940
    unlock(require_lock)
81✔
2941
    try
81✔
2942
        include(__toplevel__, path)
81✔
2943
        loaded = maybe_root_module(pkg)
81✔
2944
    finally
2945
        __toplevel__.var"#_internal_julia_parse" = Core._parse
81✔
2946
        lock(require_lock)
81✔
2947
        if uuid !== old_uuid
81✔
2948
            ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), __toplevel__, old_uuid)
57✔
2949
        end
2950
    end
2951
    return loaded
81✔
2952
end
2953

2954
# load a serialized file directly, including dependencies (without checking staleness except for immediate conflicts)
2955
# this does not call start_loading / end_loading, so can lead to some odd behaviors
2956
function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}, sourcepath::String)
×
2957
    @lock require_lock begin
×
2958
    set_pkgorigin_version_path(uuidkey, sourcepath)
×
2959
    newm = _tryrequire_from_serialized(uuidkey, path, ocachepath)
×
2960
    newm isa Module || throw(newm)
×
2961
    insert_extension_triggers(uuidkey)
×
2962
    # After successfully loading, notify downstream consumers
2963
    run_package_callbacks(uuidkey)
×
2964
    return newm
×
2965
    end
2966
end
2967

2968
# load a serialized file directly from append_bundled_depot_path for uuidkey without stalechecks
2969
"""
2970
    require_stdlib(package_uuidkey::PkgId, [ext::String, from::Module])
2971

2972
Load a standard library package from the bundled Julia depot, loading precompiled cache
2973
files without requiring source files to be present. This function is designed to load
2974
stdlib packages even when `JULIA_DEPOT_PATH` doesn't include the bundled depot directory,
2975
enabling stdlib usage in isolated or restricted environments.
2976

2977
Unlike `require`, this function loads `.ji` cache files directly from the bundled depot
2978
without source staleness checks (since stdlibs are immutable for a given Julia version).
2979
If the bundled depot cache is unavailable, it falls back to normal package loading.
2980

2981
!!! warning "May load duplicate copies of stdlib packages."
2982

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

2988
    The specific requirements are:
2989

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

2996
      The imported code (loaded stdlibs) must be very careful about type piracy:
2997
         - It must not access any global state that may differ between stdlib copies in
2998
           type-pirated methods.
2999
         - It must not return any stdlib types from any type-pirated public methods (since
3000
           a loaded duplicate would overwrite the Base method again, returning different
3001
           types that don't correspond to the user-accessible copy of the stdlib).
3002
         - It must not pass / discriminate stdlib types in type-pirated methods, except
3003
           indirectly via methods defined in Base and implemented (w/o type-piracy) in
3004
           all copies of the stdlib over their respective types.
3005

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

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

3014
    For examples of issues like the above, see:
3015
      [1] https://github.com/JuliaLang/Pkg.jl/issues/4017#issuecomment-2377589989
3016
      [2] https://github.com/JuliaLang/StyledStrings.jl/issues/91#issuecomment-2379602914
3017
"""
3018
require_stdlib(package_uuidkey::PkgId) = require_stdlib(package_uuidkey, nothing, Base)
9✔
3019
function require_stdlib(package_uuidkey::PkgId, ext::Union{Nothing, String}, from::Module)
6✔
3020
    if generating_output(#=incremental=#true)
6✔
3021
        # Otherwise this would lead to awkward dependency issues by loading a package that isn't in the Project/Manifest
3022
        error("This interactive function requires a stdlib to be loaded, and package code should instead use it directly from that stdlib.")
×
3023
    end
3024
    @lock require_lock begin
6✔
3025
    # the PkgId of the ext, or package if not an ext
3026
    this_uuidkey = ext isa String ? PkgId(uuid5(package_uuidkey.uuid, ext), ext) : package_uuidkey
6✔
3027
    env = Sys.STDLIB
6✔
3028
    newm = start_loading(this_uuidkey, UInt128(0), true)
6✔
3029
    newm === nothing || return newm
6✔
3030
    try
6✔
3031
        depot_path = append_bundled_depot_path!(empty(DEPOT_PATH))
6✔
3032
        from_stdlib = true # set to false if `from` is a normal package so we do not want the internal loader for the extension either
6✔
3033
        if ext isa String
6✔
3034
            from_uuid = PkgId(from)
×
3035
            from_m = get(loaded_modules, from_uuid, nothing)
×
3036
            if from_m === from
×
3037
                # if from_uuid is either nothing or points to something else, assume we should use require_stdlib
3038
                # otherwise check cachepath for from to see if it looks like it is from depot_path, since try_build_ids
3039
                cachepath = get(PkgOrigin, pkgorigins, from_uuid).cachepath
×
3040
                entrypath, entryfile = cache_file_entry(from_uuid)
×
3041
                from_stdlib = any(x -> startswith(entrypath, x), depot_path)
×
3042
            end
3043
        end
3044
        if from_stdlib
6✔
3045
            # first since this is a stdlib, try to look there directly first
3046
            if ext === nothing
6✔
3047
                sourcepath = normpath(env, this_uuidkey.name, "src", this_uuidkey.name * ".jl")
6✔
3048
            else
3049
                sourcepath = find_ext_path(normpath(joinpath(env, package_uuidkey.name)), ext)
×
3050
            end
3051
            set_pkgorigin_version_path(this_uuidkey, sourcepath)
6✔
3052
            newm = _require_search_from_serialized(this_uuidkey, PkgLoadSpec(sourcepath, VERSION), UInt128(0), false; DEPOT_PATH=depot_path)
6✔
3053
        end
3054
    finally
3055
        end_loading(this_uuidkey, newm)
12✔
3056
    end
3057
    if newm isa Module
6✔
3058
        # After successfully loading, notify downstream consumers
3059
        insert_extension_triggers(env, this_uuidkey)
6✔
3060
        run_package_callbacks(this_uuidkey)
6✔
3061
    else
3062
        # if the user deleted their bundled depot, next try to load it completely normally
3063
        # if it is an extension, we first need to indicate where to find its parent via EXT_PRIMED
3064
        ext isa String && (EXT_PRIMED[this_uuidkey] = PkgId[package_uuidkey])
×
3065
        newm = _require_prelocked(this_uuidkey)
×
3066
    end
3067
    return newm
6✔
3068
    end # release lock
3069
end
3070

3071
# relative-path load
3072

3073
"""
3074
    include_string([mapexpr::Function,] m::Module, code::AbstractString, filename::AbstractString="string")
3075

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

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

3082
!!! compat "Julia 1.5"
3083
    Julia 1.5 is required for passing the `mapexpr` argument.
3084
"""
3085
function include_string(mapexpr::Function, mod::Module, code::AbstractString,
270✔
3086
                        filename::AbstractString="string")
3087
    loc = LineNumberNode(1, Symbol(filename))
270✔
3088
    try
267✔
3089
        _parse = invokelatest(Meta.parser_for_module, mod)
267✔
3090
        ast = Meta.parseall(code; filename, _parse)
267✔
3091
        if !Meta.isexpr(ast, :toplevel)
267✔
3092
            @assert Core._lower != fl_lower
×
3093
            # Only reached when JuliaLowering and alternate parse functions are activated
3094
            return Core.eval(mod, ast)
×
3095
        end
3096
        result = nothing
267✔
3097
        line_and_ex = Expr(:toplevel, loc, nothing)
267✔
3098
        for ex in ast.args
267✔
3099
            if ex isa LineNumberNode
5,556✔
3100
                loc = ex
2,778✔
3101
                line_and_ex.args[1] = ex
2,778✔
3102
                continue
2,778✔
3103
            end
3104
            ex = mapexpr(ex)
2,790✔
3105
            # Wrap things to be eval'd in a :toplevel expr to carry line
3106
            # information as part of the expr.
3107
            line_and_ex.args[2] = ex
2,778✔
3108
            # Check global TRACE_EVAL first, fall back to command line option
3109
            trace_eval_setting = TRACE_EVAL
2,778✔
3110
            trace_eval = if trace_eval_setting !== nothing
2,778✔
3111
                # Convert symbol to integer value
3112
                setting = trace_eval_setting
×
3113
                if setting === :no
×
3114
                    0
×
3115
                elseif setting === :loc
×
3116
                    1
×
3117
                elseif setting === :full
×
3118
                    2
×
3119
                else
3120
                    error("Invalid TRACE_EVAL value: $(setting). Must be :no, :loc, or :full")
×
3121
                end
3122
            else
3123
                JLOptions().trace_eval
5,556✔
3124
            end
3125
            if trace_eval == 2 # show everything
5,556✔
3126
                println(stderr, "eval: ", line_and_ex)
×
3127
            elseif trace_eval == 1 # show top location only
5,556✔
3128
                println(stderr, "eval: ", line_and_ex.args[1])
×
3129
            end
3130
            result = Core.eval(mod, line_and_ex)
2,778✔
3131
        end
5,556✔
3132
        return result
267✔
3133
    catch exc
3134
        # TODO: Now that stacktraces are more reliable we should remove
3135
        # LoadError and expose the real error type directly.
3136
        rethrow(LoadError(filename, loc.line, exc))
×
3137
    end
3138
end
3139

3140
include_string(m::Module, txt::AbstractString, fname::AbstractString="string") =
153✔
3141
    include_string(identity, m, txt, fname)
3142

3143
function source_path(default::Union{AbstractString,Nothing}="")
1✔
3144
    s = current_task().storage
746✔
3145
    if s !== nothing
731✔
3146
        s = s::IdDict{Any,Any}
500✔
3147
        if haskey(s, :SOURCE_PATH)
500✔
3148
            return s[:SOURCE_PATH]::Union{Nothing,String}
361✔
3149
        end
3150
    end
3151
    return default
370✔
3152
end
3153

3154
function source_dir()
×
3155
    p = source_path(nothing)
×
3156
    return p === nothing ? pwd() : dirname(p)
×
3157
end
3158

3159
"""
3160
    Base.include([mapexpr::Function,] m::Module, path::AbstractString)
3161

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

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

3174
!!! compat "Julia 1.5"
3175
    Julia 1.5 is required for passing the `mapexpr` argument.
3176
"""
3177
Base.include # defined in Base.jl
3178

3179
# Full include() implementation which is used after bootstrap
3180
function _include(mapexpr::Function, mod::Module, _path::AbstractString)
267✔
3181
    @noinline # Workaround for module availability in _simplify_include_frames
267✔
3182
    path, prev = _include_dependency(mod, _path)
267✔
3183
    for callback in include_callbacks # to preserve order, must come before eval in include_string
258✔
3184
        invokelatest(callback, mod, path)
×
3185
    end
×
3186
    code = read(path, String)
258✔
3187
    tls = task_local_storage()
258✔
3188
    tls[:SOURCE_PATH] = path
258✔
3189
    try
258✔
3190
        return include_string(mapexpr, mod, code, path)
258✔
3191
    finally
3192
        if prev === nothing
258✔
3193
            delete!(tls, :SOURCE_PATH)
93✔
3194
        else
3195
            tls[:SOURCE_PATH] = prev
165✔
3196
        end
3197
    end
3198
end
3199

3200
"""
3201
    evalfile(path::AbstractString, args::Vector{String}=String[])
3202

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

3208
# Examples
3209

3210
```jldoctest
3211
julia> write("testfile.jl", \"\"\"
3212
           @show ARGS
3213
           1 + 1
3214
       \"\"\");
3215

3216
julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]);
3217
ARGS = ["ARG1", "ARG2"]
3218

3219
julia> x
3220
2
3221

3222
julia> rm("testfile.jl")
3223
```
3224
"""
3225
function evalfile(path::AbstractString, args::Vector{String}=String[])
3✔
3226
    m = Module(:__anon__)
6✔
3227
    return Core.eval(m,
3✔
3228
        Expr(:toplevel,
3229
             :(const ARGS = $args),
3230
             :(const include = $(Base.IncludeInto(m))),
3231
             :(const eval = $(Core.EvalInto(m))),
3232
             :(include($path))))
3233
end
3234
evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
×
3235

3236
function load_path_setup_code(load_path::Bool=true)
×
3237
    code = """
×
3238
    append!(empty!(Base.DEPOT_PATH), $(repr(map(abspath, DEPOT_PATH))))
3239
    append!(empty!(Base.DL_LOAD_PATH), $(repr(map(abspath, DL_LOAD_PATH))))
3240
    """
3241
    if load_path
×
3242
        load_path = map(abspath, Base.load_path())
×
3243
        path_sep = Sys.iswindows() ? ';' : ':'
×
3244
        any(path -> path_sep in path, load_path) &&
×
3245
            error("LOAD_PATH entries cannot contain $(repr(path_sep))")
3246
        code *= """
×
3247
        append!(empty!(Base.LOAD_PATH), $(repr(load_path)))
3248
        ENV["JULIA_LOAD_PATH"] = $(repr(join(load_path, Sys.iswindows() ? ';' : ':')))
3249
        Base.set_active_project(nothing)
3250
        """
3251
    end
3252
    return code
×
3253
end
3254

3255
# Const global for GC root
3256
const newly_inferred = CodeInstance[]
3257

3258
# this is called in the external process that generates precompiled package files
3259
function include_package_for_output(pkg::PkgId, input::String, syntax_version::VersionNumber, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String},
×
3260
                                    concrete_deps::typeof(_concrete_dependencies), source::Union{Nothing,String})
3261

3262
    @lock require_lock begin
×
3263
    m = start_loading(pkg, UInt128(0), false)
×
3264
    @assert m === nothing
×
3265
    append!(empty!(Base.DEPOT_PATH), depot_path)
×
3266
    append!(empty!(Base.DL_LOAD_PATH), dl_load_path)
×
3267
    append!(empty!(Base.LOAD_PATH), load_path)
×
3268
    ENV["JULIA_LOAD_PATH"] = join(load_path, Sys.iswindows() ? ';' : ':')
×
3269
    set_active_project(nothing)
×
3270
    Base._track_dependencies[] = true
×
3271
    get!(Base.PkgOrigin, Base.pkgorigins, pkg).path = input
×
3272
    append!(empty!(Base._concrete_dependencies), concrete_deps)
×
3273
    end
3274

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

3277
    ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Base.__toplevel__, uuid_tuple)
×
3278
    if source !== nothing
×
3279
        task_local_storage()[:SOURCE_PATH] = source
×
3280
    end
3281

3282
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3283
    # This one changes the parser behavior
3284
    __toplevel__.var"#_internal_julia_parse" = VersionedParse(syntax_version)
×
3285
    # This one is the compatibility marker for cache loading
3286
    __toplevel__._internal_syntax_version = cache_syntax_version(syntax_version)
×
3287
    try
×
3288
        Base.include(Base.__toplevel__, input)
×
3289
    catch ex
3290
        precompilableerror(ex) || rethrow()
×
3291
        @debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
×
3292
        exit(125) # we define status = 125 means PrecompileableError
×
3293
    finally
3294
        ccall(:jl_set_newly_inferred, Cvoid, (Any,), nothing)
×
3295
    end
3296
    # check that the package defined the expected module so we can give a nice error message if not
3297
    m = maybe_root_module(pkg)
×
3298
    m isa Module || check_package_module_loaded_error(pkg)
×
3299

3300
    # Re-populate the runtime's newly-inferred array, which will be included
3301
    # in the output. We removed it above to avoid including any code we may
3302
    # have compiled for error handling and validation.
3303
    ccall(:jl_set_newly_inferred, Cvoid, (Any,), newly_inferred)
×
3304
    @lock require_lock end_loading(pkg, m)
×
3305
    # insert_extension_triggers(pkg)
3306
    # run_package_callbacks(pkg)
3307
end
3308

3309
function check_package_module_loaded_error(pkg)
×
3310
    # match compilecache error type for non-125 errors
3311
    error("package `$(pkg.name)` did not define the expected \
×
3312
          module `$(pkg.name)`, check for typos in package module name")
3313
end
3314

3315
# protects against PkgId and UUID being imported and losing Base prefix
3316
_pkg_str(_pkg::PkgId) = (_pkg.uuid === nothing) ? "Base.PkgId($(repr(_pkg.name)))" : "Base.PkgId(Base.UUID(\"$(_pkg.uuid)\"), $(repr(_pkg.name)))"
17,013✔
3317
_pkg_str(_pkg::Vector) = sprint(show, eltype(_pkg); context = :module=>nothing) * "[" * join(map(_pkg_str, _pkg), ",") * "]"
899✔
3318
_pkg_str(_pkg::Pair{PkgId}) = _pkg_str(_pkg.first) * " => " * repr(_pkg.second)
32,275✔
3319
_pkg_str(_pkg::Nothing) = "nothing"
1✔
3320

3321
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
3322
function create_expr_cache(pkg::PkgId, input::PkgLoadSpec, output::String, output_o::Union{Nothing, String},
434✔
3323
                           concrete_deps::typeof(_concrete_dependencies), flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3324
                           internal_stderr::IO = stderr, internal_stdout::IO = stdout, loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3325
    @nospecialize internal_stderr internal_stdout
434✔
3326
    rm(output, force=true)   # Remove file if it exists
434✔
3327
    output_o === nothing || rm(output_o, force=true)
434✔
3328
    depot_path = String[abspath(x) for x in DEPOT_PATH]
434✔
3329
    dl_load_path = String[abspath(x) for x in DL_LOAD_PATH]
434✔
3330
    load_path = String[abspath(x) for x in Base.load_path()]
434✔
3331
    # if pkg is a stdlib, append its parent Project.toml to the load path
3332
    triggers = get(EXT_PRIMED, pkg, nothing)
464✔
3333
    if triggers !== nothing
434✔
3334
        parentid = triggers[1]
30✔
3335
        for env in load_path
30✔
3336
            project_file = env_project_file(env)
39✔
3337
            if project_file === true
39✔
3338
                _, parent_project_file = entry_point_and_project_file(env, parentid.name)
×
3339
                if parent_project_file !== nothing
×
3340
                    parentproj = project_file_name_uuid(parent_project_file, parentid.name)
×
3341
                    if parentproj == parentid
×
3342
                        push!(load_path, parent_project_file)
×
3343
                    end
3344
                end
3345
            end
3346
        end
39✔
3347
    end
3348
    path_sep = Sys.iswindows() ? ';' : ':'
434✔
3349
    any(path -> path_sep in path, load_path) &&
2,011✔
3350
        error("LOAD_PATH entries cannot contain $(repr(path_sep))")
3351

3352
    if output_o === nothing
434✔
3353
        # remove options that make no difference given the other cache options
3354
        cacheflags = CacheFlags(cacheflags, opt_level=0)
12✔
3355
    end
3356
    opts = translate_cache_flags(cacheflags, CacheFlags()) # julia_cmd is generated for the running system, and must be fixed if running for precompile instead
867✔
3357
    if output_o !== nothing
434✔
3358
        @debug "Generating object cache file for $(repr("text/plain", pkg))"
422✔
3359
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
422✔
3360
        push!(opts, "--output-o", output_o)
422✔
3361
    else
3362
        @debug "Generating cache file for $(repr("text/plain", pkg))"
12✔
3363
        cpu_target = nothing
12✔
3364
    end
3365
    push!(opts, "--output-ji", output)
434✔
3366
    if isassigned(PRECOMPILE_TRACE_COMPILE)
434✔
3367
        push!(opts, "--trace-compile=$(PRECOMPILE_TRACE_COMPILE[])")
×
3368
        push!(opts, "--trace-compile-timing")
×
3369
    end
3370

3371
    io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd)
868✔
3372
                               $(flags)
3373
                               $(opts)
3374
                               --output-incremental=yes
3375
                               --startup-file=no --history-file=no --warn-overwrite=yes
3376
                               $(have_color === nothing ? "--color=auto" : have_color ? "--color=yes" : "--color=no")
3377
                               -`,
3378
                              "OPENBLAS_NUM_THREADS" => 1,
3379
                              "JULIA_NUM_THREADS" => 1),
3380
                       stderr = internal_stderr, stdout = internal_stdout),
3381
              "w", stdout)
3382
    # write data over stdin to avoid the (unlikely) case of exceeding max command line size
3383
    write(io.in, """
603✔
3384
        empty!(Base.EXT_DORMITORY) # If we have a custom sysimage with `EXT_DORMITORY` prepopulated
3385
        Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg))))
3386
        Base.loadable_extensions = $(_pkg_str(loadable_exts))
3387
        Base.precompiling_extension = $(loading_extension)
3388
        Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input.path))), $(repr(input.julia_syntax_version)), $(repr(depot_path)), $(repr(dl_load_path)),
3389
            $(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing))))
3390
        """)
3391
    close(io.in)
434✔
3392
    return io
434✔
3393
end
3394

3395
const precompilation_stack = Vector{PkgId}()
3396
# Helpful for debugging when precompilation is unexpectedly nested.
3397
# Enable with `JULIA_DEBUG=nested_precomp`. Note that it expected to be nested in classical code-load precompilation
3398
# TODO: Add detection if extension precompilation is nested and error / return early?
3399
function track_nested_precomp(pkgs::Vector{PkgId})
×
3400
    append!(precompilation_stack, pkgs)
×
3401
    if length(precompilation_stack) > 1
×
3402
        list() = join(map(p->p.name, precompilation_stack), " > ")
×
3403
        @debug "Nested precompilation: $(list())" _group=:nested_precomp
×
3404
    end
3405
end
3406

3407
function compilecache_dir(pkg::PkgId)
1✔
3408
    entrypath, entryfile = cache_file_entry(pkg)
602✔
3409
    return joinpath(DEPOT_PATH[1], entrypath)
434✔
3410
end
3411

3412
function compilecache_path(pkg::PkgId, prefs_hash::UInt64; flags::CacheFlags=CacheFlags(), project::String=something(Base.active_project(), ""))::String
1,458✔
3413
    entrypath, entryfile = cache_file_entry(pkg)
961✔
3414
    cachepath = joinpath(DEPOT_PATH[1], entrypath)
637✔
3415
    isdir(cachepath) || mkpath(cachepath)
835✔
3416
    if pkg.uuid === nothing
637✔
3417
        abspath(cachepath, entryfile) * ".ji"
312✔
3418
    else
3419
        crc = _crc32c(project)
325✔
3420
        crc = _crc32c(unsafe_string(JLOptions().image_file), crc)
325✔
3421
        crc = _crc32c(unsafe_string(JLOptions().julia_bin), crc)
325✔
3422
        crc = _crc32c(_cacheflag_to_uint8(flags), crc)
325✔
3423

3424
        cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing)
325✔
3425
        if cpu_target === nothing
325✔
3426
            cpu_target = unsafe_string(JLOptions().cpu_target)
325✔
3427
        end
3428
        crc = _crc32c(cpu_target, crc)
325✔
3429

3430
        crc = _crc32c(prefs_hash, crc)
325✔
3431
        project_precompile_slug = slug(crc, 5)
325✔
3432
        abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji"))
325✔
3433
    end
3434
end
3435

3436
"""
3437
    Base.compilecache(module::PkgId)
3438

3439
Create a precompiled cache file for a module and all of its dependencies.
3440
This can be used to reduce package load times. Cache files are stored in
3441
`DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref)
3442
for important notes.
3443
"""
3444
function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
533✔
3445
    @nospecialize internal_stderr internal_stdout
176✔
3446
    spec = locate_package_load_spec(pkg)
176✔
3447
    spec === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation"))
176✔
3448
    return compilecache(pkg, spec, internal_stderr, internal_stdout; flags, cacheflags, loadable_exts)
176✔
3449
end
3450

3451
const MAX_NUM_PRECOMPILE_FILES = Ref(10)
3452

3453
function compilecache(pkg::PkgId, spec::PkgLoadSpec, internal_stderr::IO = stderr, internal_stdout::IO = stdout,
1,167✔
3454
                      keep_loaded_modules::Bool = true; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(),
3455
                      loadable_exts::Union{Vector{PkgId},Nothing}=nothing)
3456

3457
    @nospecialize internal_stderr internal_stdout
434✔
3458
    # decide where to put the resulting cache file
3459
    cachepath = compilecache_dir(pkg)
434✔
3460

3461
    # build up the list of modules that we want the precompile process to preserve
3462
    if keep_loaded_modules
434✔
3463
        concrete_deps = copy(_concrete_dependencies)
419✔
3464
        for (pkgreq, modreq) in loaded_modules
837✔
3465
            if !(pkgreq === Main || pkgreq === Core || pkgreq === Base)
16,419✔
3466
                push!(concrete_deps, pkgreq => module_build_id(modreq))
16,419✔
3467
            end
3468
        end
32,407✔
3469
    else
3470
        concrete_deps = empty(_concrete_dependencies)
15✔
3471
    end
3472
    # run the expression and cache the result
3473

3474
    # create a temporary file in `cachepath` directory, write the cache in it,
3475
    # write the checksum, _and then_ atomically move the file to `cachefile`.
3476
    mkpath(cachepath)
434✔
3477
    cache_objects = JLOptions().use_pkgimages == 1
434✔
3478
    tmppath, tmpio = mktemp(cachepath)
434✔
3479

3480
    if cache_objects
434✔
3481
        tmppath_o, tmpio_o = mktemp(cachepath)
422✔
3482
        tmppath_so, tmpio_so = mktemp(cachepath)
422✔
3483
    else
3484
        tmppath_o = nothing
12✔
3485
    end
3486
    local p
3487
    try
434✔
3488
        close(tmpio)
434✔
3489
        if cache_objects
434✔
3490
            close(tmpio_o)
422✔
3491
            close(tmpio_so)
422✔
3492
        end
3493
        p = create_expr_cache(pkg, spec, tmppath, tmppath_o, concrete_deps, flags, cacheflags, internal_stderr, internal_stdout, loadable_exts)
856✔
3494

3495
        if success(p)
434✔
3496
            if cache_objects
389✔
3497
                # Run linker over tmppath_o
3498
                Linking.link_image(tmppath_o, tmppath_so)
754✔
3499
            end
3500

3501
            # Read preferences hash back from .ji file (we can't precompute because
3502
            # we don't actually know what the list of compile-time preferences are without compiling)
3503
            prefs_hash = preferences_hash(tmppath)
389✔
3504
            cachefile = compilecache_path(pkg, prefs_hash; flags=cacheflags)
548✔
3505
            ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing
389✔
3506

3507
            # append checksum for so to the end of the .ji file:
3508
            crc_so = if cache_objects
389✔
3509
                open(_crc32c, tmppath_so, "r")
377✔
3510
            else
3511
                UInt32(0)
401✔
3512
            end
3513

3514
            # append extra crc to the end of the .ji file:
3515
            open(tmppath, "r+") do f
389✔
3516
                if iszero(isvalid_cache_header(f))
380✔
3517
                    error("Incompatible header for $(repr("text/plain", pkg)) in new cache file $(repr(tmppath)).")
×
3518
                end
3519
                seekend(f)
380✔
3520
                write(f, crc_so)
380✔
3521
                seekstart(f)
380✔
3522
                write(f, _crc32c(f))
380✔
3523
            end
3524

3525
            # inherit permission from the source file (and make them writable)
3526
            chmod(tmppath, filemode(spec.path) & 0o777 | 0o200)
389✔
3527

3528
            # prune the directory with cache files
3529
            if pkg.uuid !== nothing
389✔
3530
                entrypath, entryfile = cache_file_entry(pkg)
319✔
3531
                cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath))
817✔
3532
                if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[]
160✔
3533
                    idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2]
×
3534
                    evicted_cachefile = joinpath(cachepath, cachefiles[idx])
×
3535
                    @debug "Evicting file from cache" evicted_cachefile
×
3536
                    rm(evicted_cachefile; force=true)
×
3537
                    try
×
3538
                        rm(ocachefile_from_cachefile(evicted_cachefile); force=true)
×
3539
                        @static if Sys.isapple()
3540
                            rm(ocachefile_from_cachefile(evicted_cachefile) * ".dSYM"; force=true, recursive=true)
3541
                        end
3542
                    catch e
3543
                        e isa IOError || rethrow()
×
3544
                    end
3545
                end
3546
            end
3547

3548
            if cache_objects
389✔
3549
                ocachefile_new = rename_unique_ocachefile(tmppath_so, ocachefile)
377✔
3550
                if ocachefile_new != ocachefile
754✔
3551
                    cachefile = cachefile_from_ocachefile(ocachefile_new)
×
3552
                    ocachefile = ocachefile_new
×
3553
                end
3554
                @static if Sys.isapple()
3555
                    run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull())
126✔
3556
                end
3557
            end
3558
            # this is atomic according to POSIX (not Win32):
3559
            # but force=true means it will fall back to non atomic
3560
            # move if the initial rename fails.
3561
            mv(tmppath, cachefile; force=true)
389✔
3562
            return cachefile, ocachefile
434✔
3563
        end
3564
    finally
3565
        rm(tmppath, force=true)
434✔
3566
        if cache_objects
434✔
3567
            rm(tmppath_o::String, force=true)
422✔
3568
            rm(tmppath_so, force=true)
422✔
3569
        end
3570
    end
3571
    if p.exitcode == 125
45✔
3572
        return PrecompilableError()
12✔
3573
    else
3574
        error("Failed to precompile $(repr("text/plain", pkg)) to $(repr(tmppath)) ($(Base.process_status(p))).")
33✔
3575
    end
3576
end
3577

3578
function rename_unique_ocachefile(tmppath_so::String, ocachefile_orig::String, ocachefile::String = ocachefile_orig, num = 0)
368✔
3579
    try
744✔
3580
        mv(tmppath_so, ocachefile; force=true)
367✔
3581
    catch e
3582
        e isa IOError || rethrow()
×
3583
        # If `rm` was called on a dir containing a loaded DLL, we moved it to temp for cleanup
3584
        # on restart. However the old path cannot be used (UV_EACCES) while the DLL is loaded
3585
        if !isfile(ocachefile) && e.code != Base.UV_EACCES
×
3586
            rethrow()
×
3587
        end
3588
        # Windows prevents renaming a file that is in use so if there is a Julia session started
3589
        # with a package image loaded, we cannot rename that file.
3590
        # The code below appends a `_i` to the name of the cache file where `i` is the smallest number such that
3591
        # that cache file does not exist.
3592
        ocachename, ocacheext = splitext(ocachefile_orig)
×
3593
        ocachefile_unique = ocachename * "_$num" * ocacheext
×
3594
        ocachefile = rename_unique_ocachefile(tmppath_so, ocachefile_orig, ocachefile_unique, num + 1)
×
3595
    end
3596
    return ocachefile
367✔
3597
end
3598

3599
function object_build_id(obj)
6✔
3600
    mod = ccall(:jl_object_top_module, Any, (Any,), obj)
18✔
3601
    if mod === nothing
18✔
3602
        return nothing
3✔
3603
    end
3604
    return module_build_id(mod::Module)
12✔
3605
end
3606

3607
function isvalid_cache_header(f::IOStream)
13,411✔
3608
    pkgimage = Ref{UInt8}()
13,411✔
3609
    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
13,411✔
3610

3611
    if !iszero(checksum) && pkgimage[] != 0
13,411✔
3612
        @debug "Cache header was for pkgimage"
×
3613
        return UInt64(0) # We somehow read the header for a pkgimage and not a ji
×
3614
    end
3615
    return checksum
13,411✔
3616
end
3617
isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32))
3,506✔
3618

3619
function isvalid_pkgimage_crc(f::IOStream, ocachefile::String)
3,476✔
3620
    seekstart(f) # TODO necessary
3,476✔
3621
    seek(f, filesize(f) - 8)
3,476✔
3622
    expected_crc_so = read(f, UInt32)
3,476✔
3623
    crc_so = open(_crc32c, ocachefile, "r")
3,476✔
3624
    expected_crc_so == crc_so
3,476✔
3625
end
3626

3627
mutable struct CacheHeaderIncludes
3628
    const id::PkgId
60,696✔
3629
    filename::String
3630
    const fsize::UInt64
3631
    const hash::UInt32
3632
    const mtime::Float64
3633
    const modpath::Vector{String}   # seemingly not needed in Base, but used by Revise
3634
end
3635

3636
function CacheHeaderIncludes(dep_tuple::Tuple{Module, String, UInt64, UInt32, Float64})
×
3637
    return CacheHeaderIncludes(PkgId(dep_tuple[1]), dep_tuple[2:end]..., String[])
×
3638
end
3639

3640
function replace_depot_path(path::AbstractString, depots::Vector{String}=normalize_depots_for_relocation())
3641
    for depot in depots
24✔
3642
        if startswith(path, string(depot, Filesystem.pathsep())) || path == depot
3643
            path = replace(path, depot => "@depot"; count=1)
3644
            break
3645
        end
3646
    end
3647
    return path
3648
end
3649

3650
function normalize_depots_for_relocation()
×
3651
    depots = String[]
×
3652
    sizehint!(depots, length(DEPOT_PATH))
×
3653
    for d in DEPOT_PATH
×
3654
        isdir(d) || continue
×
3655
        if isdirpath(d)
×
3656
            d = dirname(d)
×
3657
        end
3658
        push!(depots, abspath(d))
×
3659
    end
×
3660
    return depots
×
3661
end
3662

3663
function restore_depot_path(path::AbstractString, depot::AbstractString)
121✔
3664
    replace(path, r"^@depot" => depot; count=1)
242,268✔
3665
end
3666

3667
function resolve_depot(inc::AbstractString)
60,696✔
3668
    startswith(inc, string("@depot", Filesystem.pathsep())) || return :not_relocatable
61,550✔
3669
    for depot in DEPOT_PATH
59,842✔
3670
        ispath(restore_depot_path(inc, depot)) && return depot
182,452✔
3671
    end
122,667✔
3672
    return :no_depot_found
57✔
3673
end
3674

3675
function read_module_list(f::IO, has_buildid_hi::Bool)
25,680✔
3676
    modules = Vector{Pair{PkgId, UInt128}}()
25,680✔
3677
    while true
225,104✔
3678
        n = read(f, Int32)
225,104✔
3679
        n == 0 && break
225,104✔
3680
        sym = String(read(f, n)) # module name
398,809✔
3681
        uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
199,424✔
3682
        build_id_hi = UInt128(has_buildid_hi ? read(f, UInt64) : UInt64(0)) << 64
212,264✔
3683
        build_id = (build_id_hi | read(f, UInt64)) # build id (checksum + time - not a UUID)
199,424✔
3684
        push!(modules, PkgId(uuid, sym) => build_id)
199,424✔
3685
    end
199,424✔
3686
    return modules
25,680✔
3687
end
3688

3689
function _parse_cache_header(f::IO, cachefile::AbstractString)
12,840✔
3690
    flags = read(f, UInt8)
12,840✔
3691
    syntax_version = read(f, UInt8)
12,840✔
3692
    modules = read_module_list(f, false)
12,840✔
3693
    totbytes = Int64(read(f, UInt64)) # total bytes for file dependencies + preferences
12,840✔
3694
    # read the list of requirements
3695
    # and split the list into include and requires statements
3696
    includes = CacheHeaderIncludes[]
12,840✔
3697
    requires = Pair{PkgId, PkgId}[]
12,840✔
3698
    while true
89,221✔
3699
        n2 = read(f, Int32)
89,221✔
3700
        totbytes -= 4
89,221✔
3701
        if n2 == 0
89,221✔
3702
            break
12,840✔
3703
        end
3704
        depname = String(read(f, n2))
152,678✔
3705
        totbytes -= n2
76,381✔
3706
        fsize = read(f, UInt64)
76,381✔
3707
        totbytes -= 8
76,381✔
3708
        hash = read(f, UInt32)
76,381✔
3709
        totbytes -= 4
76,381✔
3710
        mtime = read(f, Float64)
76,381✔
3711
        totbytes -= 8
76,381✔
3712
        n1 = read(f, Int32)
76,381✔
3713
        totbytes -= 4
76,381✔
3714
        # map ids to keys
3715
        modkey = (n1 == 0) ? PkgId("") : modules[n1].first
139,874✔
3716
        modpath = String[]
76,381✔
3717
        if n1 != 0
76,381✔
3718
            # determine the complete module path
3719
            while true
65,529✔
3720
                n1 = read(f, Int32)
65,529✔
3721
                totbytes -= 4
65,529✔
3722
                if n1 == 0
65,529✔
3723
                    break
63,493✔
3724
                end
3725
                push!(modpath, String(read(f, n1)))
4,010✔
3726
                totbytes -= n1
2,036✔
3727
            end
2,036✔
3728
        end
3729
        if depname[1] == '\0'
152,678✔
3730
            push!(requires, modkey => binunpack(depname))
15,685✔
3731
        else
3732
            push!(includes, CacheHeaderIncludes(modkey, depname, fsize, hash, mtime, modpath))
60,696✔
3733
        end
3734
    end
76,381✔
3735
    prefs = String[]
12,840✔
3736
    while true
12,840✔
3737
        n2 = read(f, Int32)
12,840✔
3738
        totbytes -= 4
12,840✔
3739
        if n2 == 0
12,840✔
3740
            break
12,840✔
3741
        end
3742
        push!(prefs, String(read(f, n2)))
×
3743
        totbytes -= n2
×
3744
    end
×
3745
    prefs_hash = read(f, UInt64)
12,840✔
3746
    totbytes -= 8
12,840✔
3747
    srctextpos = read(f, Int64)
12,840✔
3748
    totbytes -= 8
12,840✔
3749
    @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))"
12,840✔
3750
    # read the list of modules that are required to be present during loading
3751
    required_modules = read_module_list(f, true)
12,840✔
3752
    l = read(f, Int32)
12,840✔
3753
    clone_targets = read(f, l)
12,840✔
3754

3755
    srcfiles = srctext_files(f, srctextpos, includes)
12,840✔
3756

3757
    return modules, (includes, srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags, syntax_version
12,840✔
3758
end
3759

3760
function parse_cache_header(f::IO, cachefile::AbstractString)
12,840✔
3761
    modules, (includes, srcfiles, requires), required_modules,
12,840✔
3762
        srctextpos, prefs, prefs_hash, clone_targets, flags, syntax_version = _parse_cache_header(f, cachefile)
3763

3764
    includes_srcfiles = CacheHeaderIncludes[]
12,840✔
3765
    includes_depfiles = CacheHeaderIncludes[]
12,840✔
3766
    for inc in includes
12,840✔
3767
        if inc.filename ∈ srcfiles
121,364✔
3768
            push!(includes_srcfiles, inc)
60,648✔
3769
        else
3770
            push!(includes_depfiles, inc)
48✔
3771
        end
3772
    end
60,696✔
3773

3774

3775
    # The @depot resolution logic for include() files:
3776
    # 1. If the cache is not relocatable because of an absolute path,
3777
    #    we ignore that path for the depot search.
3778
    #    Recompilation will be triggered by stale_cachefile() if that absolute path does not exist.
3779
    # 2. If we can't find a depot for a relocatable path,
3780
    #    we still replace it with the depot we found from other files.
3781
    #    Recompilation will be triggered by stale_cachefile() because the resolved path does not exist.
3782
    # 3. We require that relocatable paths all resolve to the same depot.
3783
    # 4. We explicitly check that all relocatable paths resolve to the same depot. This has two reasons:
3784
    #    - We want to scan all source files in order to provide logs for 1. and 2. above.
3785
    #    - It is possible that a depot might be missing source files.
3786
    #      Assume that we have two depots on DEPOT_PATH, depot_complete and depot_incomplete.
3787
    #      If DEPOT_PATH=["depot_complete","depot_incomplete"] then no recompilation shall happen,
3788
    #      because depot_complete will be picked.
3789
    #      If DEPOT_PATH=["depot_incomplete","depot_complete"] we trigger recompilation and
3790
    #      hopefully a meaningful error about missing files is thrown.
3791
    #      If we were to just select the first depot we find, then whether recompilation happens would
3792
    #      depend on whether the first relocatable file resolves to depot_complete or depot_incomplete.
3793
    srcdepot = nothing
12,840✔
3794
    any_not_relocatable = false
12,840✔
3795
    any_no_depot_found = false
12,840✔
3796
    multiple_depots_found = false
12,840✔
3797
    for src in srcfiles
25,679✔
3798
        depot = resolve_depot(src)
60,648✔
3799
        if depot === :not_relocatable
60,648✔
3800
            any_not_relocatable = true
854✔
3801
        elseif depot === :no_depot_found
59,794✔
3802
            any_no_depot_found = true
57✔
3803
        elseif isnothing(srcdepot)
107,480✔
3804
            srcdepot = depot
11,994✔
3805
        elseif depot != srcdepot
47,743✔
3806
            multiple_depots_found = true
×
3807
        end
3808
    end
121,268✔
3809
    if any_no_depot_found
12,840✔
3810
        @debug("Unable to resolve @depot tag for at least one include() file from cache file $cachefile", srcfiles, _group=:relocatable)
21✔
3811
    end
3812
    if any_not_relocatable
12,840✔
3813
        @debug("At least one include() file from $cachefile is not relocatable", srcfiles, _group=:relocatable)
838✔
3814
    end
3815
    if multiple_depots_found
12,840✔
3816
        @debug("Some include() files from $cachefile are distributed over multiple depots", srcfiles, _group=:relocatable)
×
3817
    elseif !isnothing(srcdepot)
24,834✔
3818
        for inc in includes_srcfiles
11,994✔
3819
            inc.filename = restore_depot_path(inc.filename, srcdepot)
59,750✔
3820
        end
59,750✔
3821
    end
3822

3823
    # unlike include() files, we allow each relocatable include_dependency() file to resolve
3824
    # to a separate depot, #52161
3825
    for inc in includes_depfiles
12,840✔
3826
        depot = resolve_depot(inc.filename)
48✔
3827
        if depot === :no_depot_found
48✔
3828
            @debug("Unable to resolve @depot tag for include_dependency() file $(inc.filename) from cache file $cachefile", _group=:relocatable)
×
3829
        elseif depot === :not_relocatable
48✔
3830
            @debug("include_dependency() file $(inc.filename) from $cachefile is not relocatable", _group=:relocatable)
×
3831
        else
3832
            inc.filename = restore_depot_path(inc.filename, depot)
48✔
3833
        end
3834
    end
48✔
3835

3836
    return modules, (includes, includes_srcfiles, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags, syntax_version
12,840✔
3837
end
3838

3839
function parse_cache_header(cachefile::String)
15✔
3840
    io = open(cachefile, "r")
15✔
3841
    try
15✔
3842
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
15✔
3843
        ret = parse_cache_header(io, cachefile)
15✔
3844
        return ret
15✔
3845
    finally
3846
        close(io)
15✔
3847
    end
3848
end
3849

3850
preferences_hash(f::IO, cachefile::AbstractString) = parse_cache_header(f, cachefile)[6]
379✔
3851
function preferences_hash(cachefile::String)
379✔
3852
    io = open(cachefile, "r")
379✔
3853
    try
379✔
3854
        if iszero(isvalid_cache_header(io))
379✔
3855
            throw(ArgumentError("Incompatible header in cache file $cachefile."))
×
3856
        end
3857
        return preferences_hash(io, cachefile)
379✔
3858
    finally
3859
        close(io)
379✔
3860
    end
3861
end
3862

3863
function cache_dependencies(f::IO, cachefile::AbstractString)
3✔
3864
    _, (includes, _, _), modules, _... = parse_cache_header(f, cachefile)
3✔
3865
    return modules, map(chi -> chi.filename, includes)  # return just filename
12✔
3866
end
3867

3868
function cache_dependencies(cachefile::String)
3✔
3869
    io = open(cachefile, "r")
3✔
3870
    try
3✔
3871
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
3✔
3872
        return cache_dependencies(io, cachefile)
3✔
3873
    finally
3874
        close(io)
3✔
3875
    end
3876
end
3877

3878
function read_dependency_src(io::IO, cachefile::AbstractString, filename::AbstractString)
3879
    _, (includes, _, _), _, srctextpos, _, _, _, _ = parse_cache_header(io, cachefile)
9✔
3880
    srctextpos == 0 && error("no source-text stored in cache file")
9✔
3881
    seek(io, srctextpos)
9✔
3882
    return _read_dependency_src(io, filename, includes)
9✔
3883
end
3884

3885
function _read_dependency_src(io::IO, filename::AbstractString, includes::Vector{CacheHeaderIncludes}=CacheHeaderIncludes[])
9✔
3886
    while !eof(io)
15✔
3887
        filenamelen = read(io, Int32)
15✔
3888
        filenamelen == 0 && break
15✔
3889
        depotfn = String(read(io, filenamelen))
18✔
3890
        len = read(io, UInt64)
9✔
3891
        fn = if !startswith(depotfn, string("@depot", Filesystem.pathsep()))
9✔
3892
            depotfn
×
3893
        else
3894
            basefn = restore_depot_path(depotfn, "")
9✔
3895
            idx = findfirst(includes) do inc
18✔
3896
                endswith(inc.filename, basefn)
9✔
3897
            end
3898
            isnothing(idx) ? depotfn : includes[idx].filename
18✔
3899
        end
3900
        if fn == filename
9✔
3901
            return String(read(io, len))
3✔
3902
        end
3903
        seek(io, position(io) + len)
6✔
3904
    end
6✔
3905
    error(filename, " is not stored in the source-text cache")
6✔
3906
end
3907

3908
function read_dependency_src(cachefile::String, filename::AbstractString)
9✔
3909
    io = open(cachefile, "r")
9✔
3910
    try
9✔
3911
        iszero(isvalid_cache_header(io)) && throw(ArgumentError("Incompatible header in cache file $cachefile."))
9✔
3912
        return read_dependency_src(io, cachefile, filename)
9✔
3913
    finally
3914
        close(io)
3✔
3915
    end
3916
end
3917

3918
function srctext_files(f::IO, srctextpos::Int64, includes::Vector{CacheHeaderIncludes})
12,840✔
3919
    files = Set{String}()
12,840✔
3920
    srctextpos == 0 && return files
12,840✔
3921
    seek(f, srctextpos)
12,840✔
3922
    while !eof(f)
73,488✔
3923
        filenamelen = read(f, Int32)
73,488✔
3924
        filenamelen == 0 && break
73,488✔
3925
        filename = String(read(f, filenamelen))
121,268✔
3926
        len = read(f, UInt64)
60,648✔
3927
        push!(files, filename)
60,648✔
3928
        seek(f, position(f) + len)
60,648✔
3929
    end
60,648✔
3930
    return files
12,840✔
3931
end
3932

3933
# Test to see if this UUID is mentioned in this `Project.toml`; either as
3934
# the top-level UUID (e.g. that of the project itself), as a dependency,
3935
# or as an extra/weakdep for Preferences.
3936
function get_uuid_name(project::Dict{String, Any}, uuid::UUID)
411✔
3937
    uuid_p = get(project, "uuid", nothing)::Union{Nothing, String}
690✔
3938
    name = get(project, "name", nothing)::Union{Nothing, String}
690✔
3939
    if name !== nothing && uuid_p !== nothing && UUID(uuid_p) == uuid
411✔
3940
        return name
33✔
3941
    end
3942
    deps = get(project, "deps", nothing)::Union{Nothing, Dict{String, Any}}
724✔
3943
    if deps !== nothing
378✔
3944
        for (k, v) in deps
692✔
3945
            if uuid == UUID(v::String)
700✔
3946
                return k
123✔
3947
            end
3948
        end
931✔
3949
    end
3950
    for subkey in ("deps", "extras", "weakdeps")
255✔
3951
        subsection = get(project, subkey, nothing)::Union{Nothing, Dict{String, Any}}
1,078✔
3952
        if subsection !== nothing
765✔
3953
            for (k, v) in subsection
626✔
3954
                if uuid == UUID(v::String)
586✔
3955
                    return k
33✔
3956
                end
3957
            end
826✔
3958
        end
3959
    end
954✔
3960
    return nothing
222✔
3961
end
3962

3963
function get_uuid_name(project_toml::String, uuid::UUID)
3964
    project = parsed_toml(project_toml)
3✔
3965
    return get_uuid_name(project, uuid)
3✔
3966
end
3967

3968
# If we've asked for a specific UUID, this function will extract the prefs
3969
# for that particular UUID.  Otherwise, it returns all preferences.
3970
function filter_preferences(prefs::Dict{String, Any}, pkg_name)
3971
    if pkg_name === nothing
189✔
3972
        return prefs
×
3973
    else
3974
        return get(Dict{String, Any}, prefs, pkg_name)::Dict{String, Any}
189✔
3975
    end
3976
end
3977

3978
function collect_preferences(project_toml::String, uuid::Union{UUID,Nothing})
411✔
3979
    # We'll return a list of dicts to be merged
3980
    dicts = Dict{String, Any}[]
411✔
3981

3982
    project = parsed_toml(project_toml)
411✔
3983
    pkg_name = nothing
411✔
3984
    if uuid !== nothing
411✔
3985
        # If we've been given a UUID, map that to the name of the package as
3986
        # recorded in the preferences section.  If we can't find that mapping,
3987
        # exit out, as it means there's no way preferences can be set for that
3988
        # UUID, as we only allow actual dependencies to have preferences set.
3989
        pkg_name = get_uuid_name(project, uuid)
411✔
3990
        if pkg_name === nothing
411✔
3991
            return dicts
222✔
3992
        end
3993
    end
3994

3995
    # Look first inside of `Project.toml` to see we have preferences embedded within there
3996
    proj_preferences = get(Dict{String, Any}, project, "preferences")::Dict{String, Any}
189✔
3997
    push!(dicts, filter_preferences(proj_preferences, pkg_name))
189✔
3998

3999
    # Next, look for `(Julia)LocalPreferences.toml` files next to this `Project.toml`
4000
    project_dir = dirname(project_toml)
189✔
4001
    for name in preferences_names
189✔
4002
        toml_path = joinpath(project_dir, name)
378✔
4003
        if isfile(toml_path)
378✔
4004
            prefs = parsed_toml(toml_path)
×
4005
            push!(dicts, filter_preferences(prefs, pkg_name))
×
4006

4007
            # If we find `JuliaLocalPreferences.toml`, don't look for `LocalPreferences.toml`
4008
            break
×
4009
        end
4010
    end
378✔
4011

4012
    return dicts
189✔
4013
end
4014

4015
"""
4016
    recursive_prefs_merge(base::Dict, overrides::Dict...)
4017

4018
Helper function to merge preference dicts recursively, honoring overrides in nested
4019
dictionaries properly.
4020
"""
4021
function recursive_prefs_merge(base::Dict{String, Any}, overrides::Dict{String, Any}...)
411✔
4022
    new_base = Base._typeddict(base, overrides...)
411✔
4023

4024
    for override in overrides
411✔
4025
        # Clear entries are keys that should be deleted from any previous setting.
4026
        override_clear = get(override, "__clear__", nothing)
189✔
4027
        if override_clear isa Vector{String}
189✔
4028
            for k in override_clear
×
4029
                delete!(new_base, k)
×
4030
            end
×
4031
        end
4032

4033
        for (k, override_k) in override
189✔
4034
            # Note that if `base` has a mapping that is _not_ a `Dict`, and `override`
4035
            new_base_k = get(new_base, k, nothing)
×
4036
            if new_base_k isa Dict{String, Any} && override_k isa Dict{String, Any}
×
4037
                new_base[k] = recursive_prefs_merge(new_base_k, override_k)
×
4038
            else
4039
                new_base[k] = override_k
×
4040
            end
4041
        end
×
4042
    end
189✔
4043
    return new_base
411✔
4044
end
4045

4046
function get_projects_workspace_to_root(project_file)
3,151✔
4047
    projects = String[project_file]
3,151✔
4048
    while true
3,151✔
4049
        project_file = base_project(project_file)
3,151✔
4050
        if project_file === nothing
3,151✔
4051
            return projects
3,151✔
4052
        end
4053
        push!(projects, project_file)
×
4054
    end
×
4055
end
4056

4057
function get_preferences(uuid::Union{UUID,Nothing} = nothing)
3,151✔
4058
    merged_prefs = Dict{String,Any}()
3,154✔
4059
    loadpath = load_path()
3,151✔
4060
    projects_to_merge_prefs = String[]
3,151✔
4061
    append!(projects_to_merge_prefs, Iterators.drop(loadpath, 1))
3,151✔
4062
    if length(loadpath) >= 1
3,151✔
4063
        prepend!(projects_to_merge_prefs, get_projects_workspace_to_root(first(loadpath)))
6,302✔
4064
    end
4065

4066
    for env in reverse(projects_to_merge_prefs)
3,151✔
4067
        project_toml = env_project_file(env)
3,682✔
4068
        if !isa(project_toml, String)
3,682✔
4069
            continue
3,271✔
4070
        end
4071

4072
        # Collect all dictionaries from the current point in the load path, then merge them in
4073
        dicts = collect_preferences(project_toml, uuid)
411✔
4074
        merged_prefs = recursive_prefs_merge(merged_prefs, dicts...)
411✔
4075
    end
3,682✔
4076
    return merged_prefs
3,151✔
4077
end
4078

4079
function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{String})
3,151✔
4080
    # Start from a predictable hash point to ensure that the same preferences always
4081
    # hash to the same value, modulo changes in how Dictionaries are hashed.
4082
    h = UInt(0)
3,356✔
4083
    uuid === nothing && return UInt64(h)
3,356✔
4084

4085
    # Load the preferences
4086
    prefs = get_preferences(uuid)
3,151✔
4087

4088
    # Walk through each name that's called out as a compile-time preference
4089
    for name in prefs_list
3,151✔
4090
        prefs_value = get(prefs, name, nothing)
×
4091
        if prefs_value !== nothing
×
4092
            h = hash(prefs_value, h)::UInt
×
4093
        end
4094
    end
×
4095
    # We always return a `UInt64` so that our serialization format is stable
4096
    return UInt64(h)
3,151✔
4097
end
4098

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

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

4104
# In `Preferences.jl`, if someone calls `load_preference(@__MODULE__, key)` while we're precompiling,
4105
# we mark that usage as a usage at compile-time and call this method, so that at the end of `.ji` generation,
4106
# we can record the list of compile-time preferences and embed that into the `.ji` header
4107
function record_compiletime_preference(uuid::UUID, key::String)
×
4108
    pref = get!(Set{String}, COMPILETIME_PREFERENCES, uuid)
×
4109
    push!(pref, key)
×
4110
    return nothing
×
4111
end
4112
get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIME_PREFERENCES, uuid))
×
4113
get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid)
×
4114
get_compiletime_preferences(::Nothing) = String[]
×
4115

4116
function check_clone_targets(clone_targets)
4117
    rejection_reason = ccall(:jl_check_pkgimage_clones, Any, (Ptr{Cchar},), clone_targets)
3,382✔
4118
    if rejection_reason !== nothing
3,382✔
4119
        return rejection_reason
×
4120
    end
4121
end
4122

4123
# Set by FileWatching.__init__()
4124
global mkpidlock_hook::Any
4125
global trymkpidlock_hook::Any
4126
global parse_pidfile_hook::Any
4127

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

4133
const compilecache_pidlock_stale_age = 10
4134

4135
# Allows processes to wait if another process is precompiling a given source already.
4136
# The lock file mtime will be updated when held at most every `stale_age/2` seconds, with expected
4137
# variance of 10 seconds or more being infrequent but not unusual.
4138
# After `stale_age` seconds beyond the mtime of the lock file, the lock file is deleted and
4139
# precompilation will proceed if the locking process no longer exists or after `stale_age * 5`
4140
# seconds if the process does still exist.
4141
# If the lock is held by another host, it will conservatively wait `stale_age * 5`
4142
# seconds since processes cannot be checked remotely
4143
function maybe_cachefile_lock(f, pkg::PkgId, srcpath::String; stale_age=compilecache_pidlock_stale_age)
330✔
4144
    if @isdefined(mkpidlock_hook) && @isdefined(trymkpidlock_hook) && @isdefined(parse_pidfile_hook)
165✔
4145
        pidfile = compilecache_pidfile_path(pkg)
165✔
4146
        cachefile = @invokelatest trymkpidlock_hook(f, pidfile; stale_age)
165✔
4147
        if cachefile === false
156✔
4148
            pid, hostname, age = @invokelatest parse_pidfile_hook(pidfile)
×
4149
            verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
×
4150
            if isempty(hostname) || hostname == gethostname()
×
4151
                @logmsg verbosity "Waiting for another process (pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile"
×
4152
            else
4153
                @logmsg verbosity "Waiting for another machine (hostname: $hostname, pid: $pid) to finish precompiling $(repr("text/plain", pkg)). Pidfile: $pidfile"
×
4154
            end
4155
            # wait until the lock is available, but don't actually acquire it
4156
            # returning nothing indicates a process waited for another
4157
            return @invokelatest mkpidlock_hook(Returns(nothing), pidfile; stale_age)
×
4158
        end
4159
        return cachefile
156✔
4160
    else
4161
        # for packages loaded before FileWatching.__init__()
4162
        f()
×
4163
    end
4164
end
4165

4166
function record_reason(reasons::Dict{String,Int}, reason::String)
64✔
4167
    reasons[reason] = get(reasons, reason, 0) + 1
64✔
4168
end
4169
record_reason(::Nothing, ::String) = nothing
2,207✔
4170
function list_reasons(reasons::Dict{String,Int})
15✔
4171
    isempty(reasons) && return ""
15✔
4172
    return " (caches not reused: $(join(("$v for $k" for (k,v) in reasons), ", ")))"
6✔
4173
end
4174
list_reasons(::Nothing) = ""
×
4175

4176
function any_includes_stale(includes::Vector{CacheHeaderIncludes}, cachefile::String, reasons::Union{Dict{String,Int},Nothing}=nothing)
3,347✔
4177
    for chi in includes
3,347✔
4178
        f, fsize_req, hash_req, ftime_req = chi.filename, chi.fsize, chi.hash, chi.mtime
14,693✔
4179
        if startswith(f, string("@depot", Filesystem.pathsep()))
14,693✔
4180
            @debug("Rejecting stale cache file $cachefile because its depot could not be resolved")
×
4181
            record_reason(reasons, "file location uses unresolved depot path")
×
4182
            return true
×
4183
        end
4184
        if !ispath(f)
14,693✔
4185
            _f = fixup_stdlib_path(f)
×
4186
            if _f != f && isfile(_f) && startswith(_f, Sys.STDLIB)
×
4187
                continue
×
4188
            end
4189
            @debug "Rejecting stale cache file $cachefile because file $f does not exist"
×
4190
            record_reason(reasons, "source file not found")
×
4191
            return true
×
4192
        end
4193
        if ftime_req >= 0.0
14,693✔
4194
            # this is an include_dependency for which we only recorded the mtime
4195
            ftime = mtime(f)
×
4196
            is_stale = ( ftime != ftime_req ) &&
×
4197
                       ( ftime != floor(ftime_req) ) &&           # Issue #13606, PR #13613: compensate for Docker images rounding mtimes
4198
                       ( ftime != ceil(ftime_req) ) &&            # PR: #47433 Compensate for CirceCI's truncating of timestamps in its caching
4199
                       ( ftime != trunc(ftime_req, digits=6) ) && # Issue #20837, PR #20840: compensate for GlusterFS truncating mtimes to microseconds
4200
                       ( ftime != 1.0 )  &&                       # PR #43090: provide compatibility with Nix mtime.
4201
                       !( 0 < (ftime_req - ftime) < 1e-6 )        # PR #45552: Compensate for Windows tar giving mtimes that may be incorrect by up to one microsecond
4202
            if is_stale
×
4203
                @debug "Rejecting stale cache file $cachefile because mtime of include_dependency $f has changed (mtime $ftime, before $ftime_req)"
×
4204
                record_reason(reasons, "file modification time changed")
×
4205
                return true
×
4206
            end
4207
        else
4208
            fstat = stat(f)
14,693✔
4209
            fsize = filesize(fstat)
14,693✔
4210
            if fsize != fsize_req
14,693✔
4211
                @debug "Rejecting stale cache file $cachefile because file size of $f has changed (file size $fsize, before $fsize_req)"
9✔
4212
                record_reason(reasons, "file size changed")
9✔
4213
                return true
9✔
4214
            end
4215
            hash = isdir(fstat) ? _crc32c(join(readdir(f))) : open(_crc32c, f, "r")
29,368✔
4216
            if hash != hash_req
14,684✔
4217
                @debug "Rejecting stale cache file $cachefile because hash of $f has changed (hash $hash, before $hash_req)"
×
4218
                record_reason(reasons, "file content changed")
×
4219
                return true
×
4220
            end
4221
        end
4222
    end
14,684✔
4223
    return false
3,338✔
4224
end
4225

4226
function cache_syntax_version(ver::VersionNumber)
4227
    UInt8(clamp(ver.minor - 13, 0, 255))
3,376✔
4228
end
4229

4230
# returns true if it "cachefile.ji" is stale relative to "modpath.jl" and build_id for modkey
4231
# otherwise returns the list of dependencies to also check
4232
@constprop :none function stale_cachefile(modpath::String, cachefile::String; kwargs...)
60✔
4233
    return stale_cachefile(PkgLoadSpec(modpath, VERSION), cachefile; kwargs...)
60✔
4234
end
4235
@constprop :none function stale_cachefile(modspec::PkgLoadSpec, cachefile::String; ignore_loaded::Bool = false, requested_flags::CacheFlags=CacheFlags(), reasons=nothing)
60✔
4236
    return stale_cachefile(PkgId(""), UInt128(0), modspec, cachefile; ignore_loaded, requested_flags, reasons)
30✔
4237
end
4238
@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modspec::PkgLoadSpec, cachefile::String;
16,782✔
4239
                                          ignore_loaded::Bool=false, requested_flags::CacheFlags=CacheFlags(),
4240
                                          reasons::Union{Dict{String,Int},Nothing}=nothing, stalecheck::Bool=true)
4241
    # n.b.: this function does nearly all of the file validation, not just those checks related to stale, so the name is potentially unclear
4242
    io = try
5,627✔
4243
        open(cachefile, "r")
5,630✔
4244
    catch ex
4245
        ex isa IOError || ex isa SystemError || rethrow()
6✔
4246
        @debug "Rejecting cache file $cachefile for $modkey because it could not be opened" isfile(cachefile)
3✔
4247
        return true
3✔
4248
    end
4249
    try
5,624✔
4250
        checksum = isvalid_cache_header(io)
5,624✔
4251
        if iszero(checksum)
5,624✔
4252
            @debug "Rejecting cache file $cachefile due to it containing an incompatible cache header"
×
4253
            record_reason(reasons, "different Julia build configuration")
×
4254
            return true # incompatible cache file
×
4255
        end
4256
        modules, (includes, _, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, actual_flags, syntax_version = parse_cache_header(io, cachefile)
5,624✔
4257
        if isempty(modules)
5,624✔
4258
            return true # ignore empty file
×
4259
        end
4260
        if @ccall(jl_match_cache_flags(_cacheflag_to_uint8(requested_flags)::UInt8, actual_flags::UInt8)::UInt8) == 0
5,624✔
4261
            @debug """
2,230✔
4262
            Rejecting cache file $cachefile for $modkey since the flags are mismatched
4263
              requested flags: $(requested_flags) [$(_cacheflag_to_uint8(requested_flags))]
4264
              cache file:      $(CacheFlags(actual_flags)) [$actual_flags]
4265
            """
4266
            record_reason(reasons, "different compilation options")
2,230✔
4267
            return true
2,230✔
4268
        end
4269
        if stalecheck && syntax_version != cache_syntax_version(modspec.julia_syntax_version)
3,394✔
4270
            @debug "Rejecting cache file $cachefile for $modkey since it was parsed for a different Julia syntax version"
×
4271
            record_reason(reasons, "different Julia syntax version")
×
4272
            return true
×
4273
        end
4274
        pkgimage = !isempty(clone_targets)
3,394✔
4275
        if pkgimage
3,394✔
4276
            ocachefile = ocachefile_from_cachefile(cachefile)
3,382✔
4277
            if JLOptions().use_pkgimages == 0
3,382✔
4278
                # presence of clone_targets means native code cache
4279
                @debug "Rejecting cache file $cachefile for $modkey since it would require usage of pkgimage"
×
4280
                record_reason(reasons, "native code caching disabled")
×
4281
                return true
×
4282
            end
4283
            rejection_reasons = check_clone_targets(clone_targets)
3,382✔
4284
            if !isnothing(rejection_reasons)
3,382✔
4285
                @debug("Rejecting cache file $cachefile for $modkey:",
×
4286
                    Reasons=rejection_reasons,
4287
                    var"Image Targets"=parse_image_targets(clone_targets),
4288
                    var"Current Targets"=current_image_targets())
4289
                record_reason(reasons, "different system or CPU target")
×
4290
                return true
×
4291
            end
4292
            if !isfile(ocachefile)
3,382✔
4293
                @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found"
×
4294
                record_reason(reasons, "native code cache file not found")
×
4295
                return true
×
4296
            end
4297
        else
4298
            ocachefile = nothing
12✔
4299
        end
4300
        id = first(modules)
3,394✔
4301
        if id.first != modkey && modkey != PkgId("")
3,394✔
4302
            @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead"
×
4303
            record_reason(reasons, "different package identifier")
×
4304
            return true
×
4305
        end
4306
        id_build = id.second
3,394✔
4307
        id_build = (UInt128(checksum) << 64) | (id_build % UInt64)
3,394✔
4308
        if build_id != UInt128(0)
3,394✔
4309
            if id_build != build_id
2,567✔
4310
                @debug "Ignoring cache file $cachefile for $modkey ($(UUID(id_build))) since it does not provide desired build_id ($((UUID(build_id))))"
6✔
4311
                record_reason(reasons, "different build identifier")
6✔
4312
                return true
6✔
4313
            end
4314
        end
4315
        id = id.first
3,388✔
4316
        modules = Dict{PkgId, UInt64}(modules)
3,388✔
4317

4318
        # Check if transitive dependencies can be fulfilled
4319
        ndeps = length(required_modules)
3,388✔
4320
        depmods = Vector{Any}(undef, ndeps)
3,388✔
4321
        for i in 1:ndeps
6,776✔
4322
            req_key, req_build_id = required_modules[i]
49,644✔
4323
            # Check if module is already loaded
4324
            M = stalecheck ? nothing : maybe_loaded_precompile(req_key, req_build_id)
49,902✔
4325
            if M !== nothing
49,644✔
4326
                @assert PkgId(M) == req_key && module_build_id(M) === req_build_id
234✔
4327
                depmods[i] = M
234✔
4328
                continue
234✔
4329
            end
4330
            M = maybe_root_module(req_key)
49,410✔
4331
            if M isa Module
49,410✔
4332
                if PkgId(M) == req_key && module_build_id(M) === req_build_id
78,506✔
4333
                    depmods[i] = M
44,320✔
4334
                    continue
44,320✔
4335
                elseif M == Core
×
4336
                    @debug "Rejecting cache file $cachefile because it was made with a different julia version"
×
4337
                    record_reason(reasons, "different Julia version")
×
4338
                    return true # Won't be able to fulfill dependency
×
4339
                elseif ignore_loaded || !stalecheck
×
4340
                    # Used by Pkg.precompile given that there it's ok to precompile different versions of loaded packages
4341
                else
4342
                    @debug "Rejecting cache file $cachefile because module $req_key is already loaded and incompatible."
×
4343
                    record_reason(reasons, "different dependency version already loaded")
×
4344
                    return true # Won't be able to fulfill dependency
×
4345
                end
4346
            end
4347
            spec = locate_package_load_spec(req_key) # TODO: add env and/or skip this when stalecheck is false
5,090✔
4348
            if spec === nothing
5,090✔
4349
                @debug "Rejecting cache file $cachefile because dependency $req_key not found."
12✔
4350
                record_reason(reasons, "dependency source file not found")
12✔
4351
                return true # Won't be able to fulfill dependency
12✔
4352
            end
4353
            depmods[i] = (spec, req_key, req_build_id)
5,078✔
4354
        end
95,888✔
4355

4356
        # check if this file is going to provide one of our concrete dependencies
4357
        # or if it provides a version that conflicts with our concrete dependencies
4358
        # or neither
4359
        if stalecheck
3,376✔
4360
            for (req_key, req_build_id) in _concrete_dependencies
3,358✔
4361
                build_id = get(modules, req_key, UInt64(0))
×
4362
                if build_id !== UInt64(0)
×
4363
                    build_id |= UInt128(checksum) << 64
×
4364
                    if build_id === req_build_id
×
4365
                        stalecheck = false
×
4366
                        break
×
4367
                    end
4368
                    @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))"
×
4369
                    record_reason(reasons, "different dependency build identifier")
×
4370
                    return true # cachefile doesn't provide the required version of the dependency
×
4371
                end
4372
            end
×
4373
        end
4374

4375
        # now check if this file's content hash has changed relative to its source files
4376
        if stalecheck
3,376✔
4377
            if !samefile(includes[1].filename, modspec.path)
3,358✔
4378
                # In certain cases the path rewritten by `fixup_stdlib_path` may
4379
                # point to an unreadable directory, make sure we can `stat` the
4380
                # file before comparing it with `modpath`.
4381
                stdlib_path = fixup_stdlib_path(includes[1].filename)
11✔
4382
                if !(isreadable(stdlib_path) && samefile(stdlib_path, modspec.path))
11✔
4383
                    @debug "Rejecting cache file $cachefile because it is for file $(includes[1].filename) not file $modpath"
11✔
4384
                    record_reason(reasons, "different source file path")
11✔
4385
                    return true # cache file was compiled from a different path
11✔
4386
                end
4387
            end
4388
            for (modkey, req_modkey) in requires
3,347✔
4389
                # verify that `require(modkey, name(req_modkey))` ==> `req_modkey`
4390
                pkg = identify_package(modkey, req_modkey.name)
4,221✔
4391
                if pkg != req_modkey
8,442✔
4392
                    @debug "Rejecting cache file $cachefile because uuid mapping for $modkey => $req_modkey has changed, expected $modkey => $(repr("text/plain", pkg))"
×
4393
                    record_reason(reasons, "dependency identifier changed")
×
4394
                    return true
×
4395
                end
4396
            end
4,221✔
4397
            if any_includes_stale(includes, cachefile, reasons)
3,347✔
4398
                return true
9✔
4399
            end
4400
        end
4401

4402
        if !isvalid_file_crc(io)
3,356✔
4403
            @debug "Rejecting cache file $cachefile because it has an invalid checksum"
3✔
4404
            record_reason(reasons, "cache file checksum is invalid")
3✔
4405
            return true
3✔
4406
        end
4407

4408
        if pkgimage
3,353✔
4409
            if !isvalid_pkgimage_crc(io, ocachefile::String)
3,341✔
4410
                @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum"
×
4411
                record_reason(reasons, "native code cache checksum is invalid")
×
4412
                return true
×
4413
            end
4414
        end
4415

4416
        curr_prefs_hash = get_preferences_hash(id.uuid, prefs)
6,504✔
4417
        if prefs_hash != curr_prefs_hash
3,353✔
4418
            @debug "Rejecting cache file $cachefile because preferences hash does not match 0x$(string(prefs_hash, base=16)) != 0x$(string(curr_prefs_hash, base=16))"
×
4419
            record_reason(reasons, "package preferences changed")
×
4420
            return true
×
4421
        end
4422

4423
        return depmods, ocachefile, id_build # fresh cachefile
3,353✔
4424
    finally
4425
        close(io)
5,624✔
4426
    end
4427
end
4428

4429
"""
4430
    @__FILE__ -> String
4431

4432
Expand to a string with the path to the file containing the
4433
macrocall, or an empty string if evaluated by `julia -e <expr>`.
4434
Return `nothing` if the macro was missing parser source information.
4435
Alternatively see [`PROGRAM_FILE`](@ref).
4436
"""
4437
macro __FILE__()
399✔
4438
    __source__.file === nothing && return nothing
399✔
4439
    return String(__source__.file::Symbol)
399✔
4440
end
4441

4442
"""
4443
    @__DIR__ -> String
4444

4445
Macro to obtain the absolute path of the current directory as a string.
4446

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

4450
# Examples
4451

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

4455
```julia-repl
4456
julia> cd("/home/JuliaUser") # working directory
4457

4458
julia> # create script at /home/JuliaUser/Projects
4459
       open("/home/JuliaUser/Projects/test.jl","w") do io
4460
           print(io, \"\"\"
4461
               println("@__DIR__ = ", @__DIR__)
4462
               println("pwd() = ", pwd())
4463
           \"\"\")
4464
       end
4465

4466
julia> # outputs script directory and current working directory
4467
       include("/home/JuliaUser/Projects/test.jl")
4468
@__DIR__ = /home/JuliaUser/Projects
4469
pwd() = /home/JuliaUser
4470
```
4471
"""
4472
macro __DIR__()
715✔
4473
    __source__.file === nothing && return nothing
715✔
4474
    _dirname = dirname(String(__source__.file::Symbol))
715✔
4475
    return isempty(_dirname) ? pwd() : abspath(_dirname)
715✔
4476
end
4477

4478
function prepare_compiler_stub_image!()
×
4479
    ccall(:jl_add_to_module_init_list, Cvoid, (Any,), Compiler)
×
4480
    register_root_module(Compiler)
×
4481
    filter!(mod->mod !== Compiler, loaded_modules_order)
×
4482
end
4483

4484
function expand_compiler_path(tup)
×
4485
    (tup[1], joinpath(Sys.BINDIR, DATAROOTDIR, tup[2]), tup[3:end]...)
×
4486
end
4487
compiler_chi(tup::Tuple) = CacheHeaderIncludes(expand_compiler_path(tup))
×
4488

4489
"""
4490
    isprecompilable(f, argtypes::Tuple{Vararg{Any}})
4491

4492
Check, as far as is possible without actually compiling, if the given
4493
function `f` can be compiled for the argument tuple (of types) `argtypes`.
4494
"""
4495
function isprecompilable(@nospecialize(f), @nospecialize(argtypes::Tuple))
×
4496
    isprecompilable(Tuple{Core.Typeof(f), argtypes...})
×
4497
end
4498

4499
function isprecompilable(@nospecialize(argt::Type))
×
4500
    ccall(:jl_is_compilable, Int32, (Any,), argt) != 0
×
4501
end
4502

4503
"""
4504
    precompile(f, argtypes::Tuple{Vararg{Any}})
4505

4506
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
4507
"""
4508
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
6✔
4509
    precompile(Tuple{Core.Typeof(f), argtypes...})
6✔
4510
end
4511

4512
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
4513
function precompile(@nospecialize(argt::Type))
9✔
4514
    ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
9✔
4515
    if !ret && ENABLE_PRECOMPILE_WARNINGS[]
9✔
4516
        @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
×
4517
    end
4518
    return ret
9✔
4519
end
4520

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

4525
"""
4526
    precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
4527

4528
Precompile a specific method for the given argument types. This may be used to precompile
4529
a different method than the one that would ordinarily be chosen by dispatch, thus
4530
mimicking `invoke`.
4531
"""
4532
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
×
4533
    precompile(Tuple{Core.Typeof(f), argtypes...}, m)
×
4534
end
4535

4536
function precompile(@nospecialize(argt::Type), m::Method)
×
4537
    atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
×
4538
    mi = Base.Compiler.specialize_method(m, atype, sparams)
×
4539
    return precompile(mi)
×
4540
end
4541

4542
precompile(include_package_for_output, (PkgId, String, VersionNumber, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing)) || @assert false
4543
precompile(include_package_for_output, (PkgId, String, VersionNumber, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String)) || @assert false
4544
precompile(create_expr_cache, (PkgId, PkgLoadSpec, String, String, typeof(_concrete_dependencies), Cmd, CacheFlags, IO, IO)) || @assert false
4545
precompile(create_expr_cache, (PkgId, PkgLoadSpec, 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