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

JuliaLang / julia / #37997

29 Jan 2025 02:08AM UTC coverage: 17.283% (-68.7%) from 85.981%
#37997

push

local

web-flow
bpart: Start enforcing min_world for global variable definitions (#57150)

This is the analog of #57102 for global variables. Unlike for consants,
there is no automatic global backdate mechanism. The reasoning for this
is that global variables can be declared at any time, unlike constants
which can only be decalared once their value is available. As a result
code patterns using `Core.eval` to declare globals are rarer and likely
incorrect.

1 of 22 new or added lines in 3 files covered. (4.55%)

31430 existing lines in 188 files now uncovered.

7903 of 45728 relevant lines covered (17.28%)

98663.7 hits per line

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

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

3
module BinaryPlatforms
4

5
export AbstractPlatform, Platform, HostPlatform, platform_dlext, tags, arch, os,
6
       os_version, libc, libgfortran_version, libstdcxx_version,
7
       cxxstring_abi, parse_dl_name_version, detect_libgfortran_version,
8
       detect_libstdcxx_version, detect_cxxstring_abi, call_abi, wordsize, triplet,
9
       select_platform, platforms_match, platform_name
10
import .Libc.Libdl
11

12
### Submodule with information about CPU features
13
include("cpuid.jl")
14
using .CPUID
15

16
# This exists to ease compatibility with old-style Platform objects
17
abstract type AbstractPlatform; end
18

19
"""
20
    Platform
21

22
A `Platform` represents all relevant pieces of information that a julia process may need
23
to know about its execution environment, such as the processor architecture, operating
24
system, libc implementation, etc...  It is, at its heart, a key-value mapping of tags
25
(such as `arch`, `os`, `libc`, etc...) to values (such as `"arch" => "x86_64"`, or
26
`"os" => "windows"`, etc...).  `Platform` objects are extensible in that the tag mapping
27
is open for users to add their own mappings to, as long as the mappings do not conflict
28
with the set of reserved tags: `arch`, `os`, `os_version`, `libc`, `call_abi`,
29
`libgfortran_version`, `libstdcxx_version`, `cxxstring_abi` and `julia_version`.
30

31
Valid tags and values are composed of alphanumeric and period characters.  All tags and
32
values will be lowercased when stored to reduce variation.
33

34
Example:
35

36
    Platform("x86_64", "windows"; cuda = "10.1")
37
"""
38
struct Platform <: AbstractPlatform
39
    tags::Dict{String,String}
40
    # The "compare strategy" allows selective overriding on how a tag is compared
41
    compare_strategies::Dict{String,Function}
42

43
    # Passing `tags` as a `Dict` avoids the need to infer different NamedTuple specializations
UNCOV
44
    function Platform(arch::String, os::String, _tags::Dict{String};
×
45
                      validate_strict::Bool = false,
46
                      compare_strategies::Dict{String,<:Function} = Dict{String,Function}())
47
        # A wee bit of normalization
UNCOV
48
        os = lowercase(os)
×
UNCOV
49
        arch = CPUID.normalize_arch(arch)
×
50

UNCOV
51
        tags = Dict{String,String}(
×
52
            "arch" => arch,
53
            "os" => os,
54
        )
UNCOV
55
        for (tag, value) in _tags
×
UNCOV
56
            value = value::Union{String,VersionNumber,Nothing}
×
UNCOV
57
            tag = lowercase(tag)
×
UNCOV
58
            if tag ∈ ("arch", "os")
×
UNCOV
59
                throw(ArgumentError("Cannot double-pass key $(tag)"))
×
60
            end
61

62
            # Drop `nothing` values; this means feature is not present or use default value.
UNCOV
63
            if value === nothing
×
UNCOV
64
                continue
×
65
            end
66

67
            # Normalize things that are known to be version numbers so that comparisons are easy.
68
            # Note that in our effort to be extremely compatible, we actually allow something that
69
            # doesn't parse nicely into a VersionNumber to persist, but if `validate_strict` is
70
            # set to `true`, it will cause an error later on.
UNCOV
71
            if tag ∈ ("libgfortran_version", "libstdcxx_version", "os_version")
×
UNCOV
72
                if isa(value, VersionNumber)
×
UNCOV
73
                    value = string(value)
×
74
                elseif isa(value, String)
×
UNCOV
75
                    v = tryparse(VersionNumber, value)
×
UNCOV
76
                    if isa(v, VersionNumber)
×
UNCOV
77
                        value = string(v)
×
78
                    end
79
                end
80
            end
81

82
            # Use `add_tag!()` to add the tag to our collection of tags
UNCOV
83
            add_tag!(tags, tag, string(value)::String)
×
UNCOV
84
        end
×
85

86
        # Auto-map call_abi and libc where necessary:
UNCOV
87
        if os == "linux" && !haskey(tags, "libc")
×
88
            # Default to `glibc` on Linux
UNCOV
89
            tags["libc"] = "glibc"
×
90
        end
UNCOV
91
        if os == "linux" && arch ∈ ("armv7l", "armv6l") && "call_abi" ∉ keys(tags)
×
92
            # default `call_abi` to `eabihf` on 32-bit ARM
UNCOV
93
            tags["call_abi"] = "eabihf"
×
94
        end
95

96
        # If the user is asking for strict validation, do so.
UNCOV
97
        if validate_strict
×
UNCOV
98
            validate_tags(tags)
×
99
        end
100

101
        # By default, we compare julia_version only against major and minor versions:
UNCOV
102
        if haskey(tags, "julia_version") && !haskey(compare_strategies, "julia_version")
×
UNCOV
103
            compare_strategies["julia_version"] = (a::String, b::String, a_comparator, b_comparator) -> begin
×
UNCOV
104
                a = VersionNumber(a)
×
UNCOV
105
                b = VersionNumber(b)
×
UNCOV
106
                return a.major == b.major && a.minor == b.minor
×
107
            end
108
        end
109

UNCOV
110
        return new(tags, compare_strategies)
×
111
    end
112
end
113

114
# Keyword interface (to avoid inference of specialized NamedTuple methods, use the Dict interface for `tags`)
UNCOV
115
function Platform(arch::String, os::String;
×
116
                  validate_strict::Bool = false,
117
                  compare_strategies::Dict{String,<:Function} = Dict{String,Function}(),
118
                  kwargs...)
UNCOV
119
    tags = Dict{String,Any}(String(tag)::String=>tagvalue(value) for (tag, value) in kwargs)
×
UNCOV
120
    return Platform(arch, os, tags; validate_strict, compare_strategies)
×
121
end
122

UNCOV
123
tagvalue(v::Union{String,VersionNumber,Nothing}) = v
×
UNCOV
124
tagvalue(v::Symbol) = String(v)
×
125
tagvalue(v::AbstractString) = convert(String, v)::String
×
126

127
# Simple tag insertion that performs a little bit of validation
UNCOV
128
function add_tag!(tags::Dict{String,String}, tag::String, value::String)
×
129
    # I know we said only alphanumeric and dots, but let's be generous so that we can expand
130
    # our support in the future while remaining as backwards-compatible as possible.  The
131
    # only characters that are absolutely disallowed right now are `-`, `+`, ` ` and things
132
    # that are illegal in filenames:
133
    nonos = raw"""+- /<>:"'\|?*"""
×
UNCOV
134
    if any(occursin(nono, tag) for nono in nonos)
×
135
        throw(ArgumentError("Invalid character in tag name \"$(tag)\"!"))
×
136
    end
137

138
    # Normalize and reject nonos
UNCOV
139
    value = lowercase(value)
×
UNCOV
140
    if any(occursin(nono, value) for nono in nonos)
×
UNCOV
141
        throw(ArgumentError("Invalid character in tag value \"$(value)\"!"))
×
142
    end
UNCOV
143
    tags[tag] = value
×
UNCOV
144
    return value
×
145
end
146

147
# Other `Platform` types can override this (I'm looking at you, `AnyPlatform`)
UNCOV
148
tags(p::Platform) = p.tags
×
149

150
# Make it act more like a dict
UNCOV
151
Base.getindex(p::AbstractPlatform, k::String) = getindex(tags(p), k)
×
UNCOV
152
Base.haskey(p::AbstractPlatform, k::String) = haskey(tags(p), k)
×
UNCOV
153
function Base.setindex!(p::AbstractPlatform, v::String, k::String)
×
UNCOV
154
    add_tag!(tags(p), k, v)
×
UNCOV
155
    return p
×
156
end
157

158
# Hash definition to ensure that it's stable
UNCOV
159
function Base.hash(p::Platform, h::UInt)
×
160
    h += 0x506c6174666f726d % UInt
×
UNCOV
161
    h = hash(p.tags, h)
×
UNCOV
162
    h = hash(p.compare_strategies, h)
×
163
    return h
×
164
end
165

166
# Simple equality definition; for compatibility testing, use `platforms_match()`
UNCOV
167
function Base.:(==)(a::Platform, b::Platform)
×
UNCOV
168
    return a.tags == b.tags && a.compare_strategies == b.compare_strategies
×
169
end
170

171

172
# Allow us to easily serialize Platform objects
UNCOV
173
function Base.show(io::IO, p::Platform)
×
UNCOV
174
    print(io, "Platform(")
×
UNCOV
175
    show(io, arch(p))
×
UNCOV
176
    print(io, ", ")
×
UNCOV
177
    show(io, os(p))
×
UNCOV
178
    print(io, "; ")
×
UNCOV
179
    join(io, ("$(k) = $(repr(v))" for (k, v) in tags(p) if k ∉ ("arch", "os")), ", ")
×
UNCOV
180
    print(io, ")")
×
181
end
182

183
# Make showing the platform a bit more palatable
184
function Base.show(io::IO, ::MIME"text/plain", p::Platform)
×
185
    str = string(platform_name(p), " ", arch(p))
×
186
    # Add on all the other tags not covered by os/arch:
187
    other_tags = sort!(filter!(kv -> kv[1] ∉ ("os", "arch"), collect(tags(p))))
×
188
    if !isempty(other_tags)
×
189
        str = string(str, " {", join([string(k, "=", v) for (k, v) in other_tags], ", "), "}")
×
190
    end
191
    print(io, str)
×
192
end
193

UNCOV
194
function validate_tags(tags::Dict)
×
UNCOV
195
    throw_invalid_key(k) = throw(ArgumentError("Key \"$(k)\" cannot have value \"$(tags[k])\""))
×
196
    # Validate `arch`
UNCOV
197
    if tags["arch"] ∉ ("x86_64", "i686", "armv7l", "armv6l", "aarch64", "powerpc64le", "riscv64")
×
UNCOV
198
        throw_invalid_key("arch")
×
199
    end
200
    # Validate `os`
UNCOV
201
    if tags["os"] ∉ ("linux", "macos", "freebsd", "openbsd", "windows")
×
UNCOV
202
        throw_invalid_key("os")
×
203
    end
204
    # Validate `os`/`arch` combination
UNCOV
205
    throw_os_mismatch() = throw(ArgumentError("Invalid os/arch combination: $(tags["os"])/$(tags["arch"])"))
×
UNCOV
206
    if tags["os"] == "windows" && tags["arch"] ∉ ("x86_64", "i686", "armv7l", "aarch64")
×
UNCOV
207
        throw_os_mismatch()
×
208
    end
UNCOV
209
    if tags["os"] == "macos" && tags["arch"] ∉ ("x86_64", "aarch64")
×
UNCOV
210
        throw_os_mismatch()
×
211
    end
212

213
    # Validate `os`/`libc` combination
UNCOV
214
    throw_libc_mismatch() = throw(ArgumentError("Invalid os/libc combination: $(tags["os"])/$(tags["libc"])"))
×
UNCOV
215
    if tags["os"] == "linux"
×
216
        # Linux always has a `libc` entry
UNCOV
217
        if tags["libc"] ∉ ("glibc", "musl")
×
UNCOV
218
            throw_libc_mismatch()
×
219
        end
220
    else
221
        # Nothing else is allowed to have a `libc` entry
UNCOV
222
        if haskey(tags, "libc")
×
UNCOV
223
            throw_libc_mismatch()
×
224
        end
225
    end
226

227
    # Validate `os`/`arch`/`call_abi` combination
UNCOV
228
    throw_call_abi_mismatch() = throw(ArgumentError("Invalid os/arch/call_abi combination: $(tags["os"])/$(tags["arch"])/$(tags["call_abi"])"))
×
UNCOV
229
    if tags["os"] == "linux" && tags["arch"] ∈ ("armv7l", "armv6l")
×
230
        # If an ARM linux has does not have `call_abi` set to something valid, be sad.
UNCOV
231
        if !haskey(tags, "call_abi") || tags["call_abi"] ∉ ("eabihf", "eabi")
×
UNCOV
232
            throw_call_abi_mismatch()
×
233
        end
234
    else
235
        # Nothing else should have a `call_abi`.
UNCOV
236
        if haskey(tags, "call_abi")
×
UNCOV
237
            throw_call_abi_mismatch()
×
238
        end
239
    end
240

241
    # Validate `libgfortran_version` is a parsable `VersionNumber`
UNCOV
242
    throw_version_number(k) = throw(ArgumentError("\"$(k)\" cannot have value \"$(tags[k])\", must be a valid VersionNumber"))
×
UNCOV
243
    if "libgfortran_version" in keys(tags) && tryparse(VersionNumber, tags["libgfortran_version"]) === nothing
×
UNCOV
244
        throw_version_number("libgfortran_version")
×
245
    end
246

247
    # Validate `cxxstring_abi` is one of the two valid options:
UNCOV
248
    if "cxxstring_abi" in keys(tags) && tags["cxxstring_abi"] ∉ ("cxx03", "cxx11")
×
UNCOV
249
        throw_invalid_key("cxxstring_abi")
×
250
    end
251

252
    # Validate `libstdcxx_version` is a parsable `VersionNumber`
UNCOV
253
    if "libstdcxx_version" in keys(tags) && tryparse(VersionNumber, tags["libstdcxx_version"]) === nothing
×
UNCOV
254
        throw_version_number("libstdcxx_version")
×
255
    end
256
end
257

UNCOV
258
function set_compare_strategy!(p::Platform, key::String, f::Function)
×
UNCOV
259
    if !haskey(p.tags, key)
×
260
        throw(ArgumentError("Cannot set comparison strategy for nonexistent tag $(key)!"))
×
261
    end
UNCOV
262
    p.compare_strategies[key] = f
×
263
end
264

UNCOV
265
function get_compare_strategy(p::Platform, key::String, default = compare_default)
×
UNCOV
266
    if !haskey(p.tags, key)
×
267
        throw(ArgumentError("Cannot get comparison strategy for nonexistent tag $(key)!"))
×
268
    end
UNCOV
269
    return get(p.compare_strategies, key, default)
×
270
end
271
get_compare_strategy(p::AbstractPlatform, key::String, default = compare_default) = default
×
272

273

274

275
"""
276
    compare_default(a::String, b::String, a_requested::Bool, b_requested::Bool)
277

278
Default comparison strategy that falls back to `a == b`.  This only ever happens if both
279
`a` and `b` request this strategy, as any other strategy is preferable to this one.
280
"""
UNCOV
281
function compare_default(a::String, b::String, a_requested::Bool, b_requested::Bool)
×
UNCOV
282
    return a == b
×
283
end
284

285
"""
286
    compare_version_cap(a::String, b::String, a_comparator, b_comparator)
287

288
Example comparison strategy for `set_comparison_strategy!()` that implements a version
289
cap for host platforms that support _up to_ a particular version number.  As an example,
290
if an artifact is built for macOS 10.9, it can run on macOS 10.11, however if it were
291
built for macOS 10.12, it could not.  Therefore, the host platform of macOS 10.11 has a
292
version cap at `v"10.11"`.
293

294
Note that because both hosts and artifacts are represented with `Platform` objects it
295
is possible to call `platforms_match()` with two artifacts, a host and an artifact, an
296
artifact and a host, and even two hosts.  We attempt to do something intelligent for all
297
cases, but in the case of comparing version caps between two hosts, we return `true` only
298
if the two host platforms are in fact identical.
299
"""
UNCOV
300
function compare_version_cap(a::String, b::String, a_requested::Bool, b_requested::Bool)
×
UNCOV
301
    a = VersionNumber(a)
×
UNCOV
302
    b = VersionNumber(b)
×
303

304
    # If both b and a requested, then we fall back to equality:
UNCOV
305
    if a_requested && b_requested
×
306
        return a == b
×
307
    end
308

309
    # Otherwise, do the comparison between the single version cap and the single version:
UNCOV
310
    if a_requested
×
UNCOV
311
        return b <= a
×
312
    else
313
        return a <= b
×
314
    end
315
end
316

317

318

319
"""
320
    HostPlatform(p::AbstractPlatform)
321

322
Convert a `Platform` to act like a "host"; e.g. if it has a version-bound tag such as
323
`"libstdcxx_version" => "3.4.26"`, it will treat that value as an upper bound, rather
324
than a characteristic.  `Platform` objects that define artifacts generally denote the
325
SDK or version that the artifact was built with, but for platforms, these versions are
326
generally the maximal version the platform can support.  The way this transformation
327
is implemented is to change the appropriate comparison strategies to treat these pieces
328
of data as bounds rather than points in any comparison.
329
"""
UNCOV
330
function HostPlatform(p::AbstractPlatform)
×
UNCOV
331
    if haskey(p, "os_version")
×
UNCOV
332
        set_compare_strategy!(p, "os_version", compare_version_cap)
×
333
    end
UNCOV
334
    if haskey(p, "libstdcxx_version")
×
UNCOV
335
        set_compare_strategy!(p, "libstdcxx_version", compare_version_cap)
×
336
    end
UNCOV
337
    return p
×
338
end
339

340
"""
341
    arch(p::AbstractPlatform)
342

343
Get the architecture for the given `Platform` object as a `String`.
344

345
# Examples
346
```jldoctest
347
julia> arch(Platform("aarch64", "Linux"))
348
"aarch64"
349

350
julia> arch(Platform("amd64", "freebsd"))
351
"x86_64"
352
```
353
"""
UNCOV
354
arch(p::AbstractPlatform) = get(tags(p), "arch", nothing)
×
355

356
"""
357
    os(p::AbstractPlatform)
358

359
Get the operating system for the given `Platform` object as a `String`.
360

361
# Examples
362
```jldoctest
363
julia> os(Platform("armv7l", "Linux"))
364
"linux"
365

366
julia> os(Platform("aarch64", "macos"))
367
"macos"
368
```
369
"""
UNCOV
370
os(p::AbstractPlatform) = get(tags(p), "os", nothing)
×
371

372
# As a special helper, it's sometimes useful to know the current OS at compile-time
UNCOV
373
function os()
×
374
    if Sys.iswindows()
×
375
        return "windows"
×
376
    elseif Sys.isapple()
×
377
        return "macos"
×
378
    elseif Sys.isfreebsd()
×
379
        return "freebsd"
×
380
    elseif Sys.isopenbsd()
×
381
        return "openbsd"
×
382
    else
383
        return "linux"
×
384
    end
385
end
386

387
"""
388
    libc(p::AbstractPlatform)
389

390
Get the libc for the given `Platform` object as a `String`.  Returns `nothing` on
391
platforms with no explicit `libc` choices (which is most platforms).
392

393
# Examples
394
```jldoctest
395
julia> libc(Platform("armv7l", "Linux"))
396
"glibc"
397

398
julia> libc(Platform("aarch64", "linux"; libc="musl"))
399
"musl"
400

401
julia> libc(Platform("i686", "Windows"))
402
```
403
"""
UNCOV
404
libc(p::AbstractPlatform) = get(tags(p), "libc", nothing)
×
405

406
"""
407
    call_abi(p::AbstractPlatform)
408

409
Get the call ABI for the given `Platform` object as a `String`.  Returns `nothing` on
410
platforms with no explicit `call_abi` choices (which is most platforms).
411

412
# Examples
413
```jldoctest
414
julia> call_abi(Platform("armv7l", "Linux"))
415
"eabihf"
416

417
julia> call_abi(Platform("x86_64", "macos"))
418
```
419
"""
UNCOV
420
call_abi(p::AbstractPlatform) = get(tags(p), "call_abi", nothing)
×
421

422
const platform_names = Dict(
423
    "linux" => "Linux",
424
    "macos" => "macOS",
425
    "windows" => "Windows",
426
    "freebsd" => "FreeBSD",
427
    "openbsd" => "OpenBSD",
428
    nothing => "Unknown",
429
)
430

431
"""
432
    platform_name(p::AbstractPlatform)
433

434
Get the "platform name" of the given platform, returning e.g. "Linux" or "Windows".
435
"""
UNCOV
436
function platform_name(p::AbstractPlatform)
×
UNCOV
437
    return platform_names[os(p)]
×
438
end
439

UNCOV
440
function VNorNothing(d::Dict, key)
×
UNCOV
441
    v = get(d, key, nothing)
×
UNCOV
442
    if v === nothing
×
UNCOV
443
        return nothing
×
444
    end
UNCOV
445
    return VersionNumber(v)::VersionNumber
×
446
end
447

448
"""
449
    libgfortran_version(p::AbstractPlatform)
450

451
Get the libgfortran version dictated by this `Platform` object as a `VersionNumber`,
452
or `nothing` if no compatibility bound is imposed.
453
"""
UNCOV
454
libgfortran_version(p::AbstractPlatform) = VNorNothing(tags(p), "libgfortran_version")
×
455

456
"""
457
    libstdcxx_version(p::AbstractPlatform)
458

459
Get the libstdc++ version dictated by this `Platform` object, or `nothing` if no
460
compatibility bound is imposed.
461
"""
UNCOV
462
libstdcxx_version(p::AbstractPlatform) = VNorNothing(tags(p), "libstdcxx_version")
×
463

464
"""
465
    cxxstring_abi(p::AbstractPlatform)
466

467
Get the c++ string ABI dictated by this `Platform` object, or `nothing` if no ABI is imposed.
468
"""
UNCOV
469
cxxstring_abi(p::AbstractPlatform) = get(tags(p), "cxxstring_abi", nothing)
×
470

471
"""
472
    os_version(p::AbstractPlatform)
473

474
Get the OS version dictated by this `Platform` object, or `nothing` if no OS version is
475
imposed/no data is available.  This is most commonly used by MacOS and FreeBSD objects
476
where we have high platform SDK fragmentation, and features are available only on certain
477
platform versions.
478
"""
UNCOV
479
os_version(p::AbstractPlatform) = VNorNothing(tags(p), "os_version")
×
480

481
"""
482
    wordsize(p::AbstractPlatform)
483

484
Get the word size for the given `Platform` object.
485

486
# Examples
487
```jldoctest
488
julia> wordsize(Platform("armv7l", "linux"))
489
32
490

491
julia> wordsize(Platform("x86_64", "macos"))
492
64
493
```
494
"""
UNCOV
495
wordsize(p::AbstractPlatform) = (arch(p) ∈ ("i686", "armv6l", "armv7l")) ? 32 : 64
×
496

497
"""
498
    triplet(p::AbstractPlatform)
499

500
Get the target triplet for the given `Platform` object as a `String`.
501

502
# Examples
503
```jldoctest
504
julia> triplet(Platform("x86_64", "MacOS"))
505
"x86_64-apple-darwin"
506

507
julia> triplet(Platform("i686", "Windows"))
508
"i686-w64-mingw32"
509

510
julia> triplet(Platform("armv7l", "Linux"; libgfortran_version="3"))
511
"armv7l-linux-gnueabihf-libgfortran3"
512
```
513
"""
UNCOV
514
function triplet(p::AbstractPlatform)
×
UNCOV
515
    str = string(
×
516
        arch(p)::Union{Symbol,String},
517
        os_str(p),
518
        libc_str(p),
519
        call_abi_str(p),
520
    )
521

522
    # Tack on optional compiler ABI flags
UNCOV
523
    if libgfortran_version(p) !== nothing
×
UNCOV
524
        str = string(str, "-libgfortran", libgfortran_version(p).major)
×
525
    end
UNCOV
526
    if cxxstring_abi(p) !== nothing
×
UNCOV
527
        str = string(str, "-", cxxstring_abi(p))
×
528
    end
UNCOV
529
    if libstdcxx_version(p) !== nothing
×
UNCOV
530
        str = string(str, "-libstdcxx", libstdcxx_version(p).patch)
×
531
    end
532

533
    # Tack on all extra tags
UNCOV
534
    for (tag, val) in tags(p)
×
UNCOV
535
        if tag ∈ ("os", "arch", "libc", "call_abi", "libgfortran_version", "libstdcxx_version", "cxxstring_abi", "os_version")
×
UNCOV
536
            continue
×
537
        end
UNCOV
538
        str = string(str, "-", tag, "+", val)
×
UNCOV
539
    end
×
UNCOV
540
    return str
×
541
end
542

UNCOV
543
function os_str(p::AbstractPlatform)
×
UNCOV
544
    if os(p) == "linux"
×
UNCOV
545
        return "-linux"
×
UNCOV
546
    elseif os(p) == "macos"
×
UNCOV
547
        osvn = os_version(p)
×
UNCOV
548
        if osvn !== nothing
×
UNCOV
549
            return "-apple-darwin$(osvn.major)"
×
550
        else
UNCOV
551
            return "-apple-darwin"
×
552
        end
UNCOV
553
    elseif os(p) == "windows"
×
UNCOV
554
        return "-w64-mingw32"
×
UNCOV
555
    elseif os(p) == "freebsd"
×
UNCOV
556
        osvn = os_version(p)
×
UNCOV
557
        if osvn !== nothing
×
558
            return "-unknown-freebsd$(osvn.major).$(osvn.minor)"
×
559
        else
UNCOV
560
            return "-unknown-freebsd"
×
561
        end
562
    elseif os(p) == "openbsd"
×
563
        return "-unknown-openbsd"
×
564
    else
565
        return "-unknown"
×
566
    end
567
end
568

569
# Helper functions for Linux and FreeBSD libc/abi mishmashes
UNCOV
570
function libc_str(p::AbstractPlatform)
×
UNCOV
571
    lc = libc(p)
×
UNCOV
572
    if lc === nothing
×
UNCOV
573
        return ""
×
UNCOV
574
    elseif lc === "glibc"
×
UNCOV
575
        return "-gnu"
×
576
    else
UNCOV
577
        return string("-", lc)
×
578
    end
579
end
UNCOV
580
function call_abi_str(p::AbstractPlatform)
×
UNCOV
581
    cabi = call_abi(p)
×
UNCOV
582
    cabi === nothing ? "" : string(cabi::Union{Symbol,String})
×
583
end
584

UNCOV
585
Sys.isapple(p::AbstractPlatform) = os(p) == "macos"
×
UNCOV
586
Sys.islinux(p::AbstractPlatform) = os(p) == "linux"
×
UNCOV
587
Sys.iswindows(p::AbstractPlatform) = os(p) == "windows"
×
588
Sys.isfreebsd(p::AbstractPlatform) = os(p) == "freebsd"
×
589
Sys.isopenbsd(p::AbstractPlatform) = os(p) == "openbsd"
×
UNCOV
590
Sys.isbsd(p::AbstractPlatform) = os(p) ∈ ("freebsd", "openbsd", "macos")
×
591
Sys.isunix(p::AbstractPlatform) = Sys.isbsd(p) || Sys.islinux(p)
×
592

593
const arch_mapping = Dict(
594
    "x86_64" => "(x86_|amd)64",
595
    "i686" => "i\\d86",
596
    "aarch64" => "(aarch64|arm64)",
597
    "armv7l" => "arm(v7l)?", # if we just see `arm-linux-gnueabihf`, we assume it's `armv7l`
598
    "armv6l" => "armv6l",
599
    "powerpc64le" => "p(ower)?pc64le",
600
    "riscv64" => "(rv64|riscv64)",
601
)
602
# Keep this in sync with `CPUID.ISAs_by_family`
603
# These are the CPUID side of the microarchitectures targeted by GCC flags in BinaryBuilder.jl
604
const arch_march_isa_mapping = let
605
    function get_set(arch, name)
×
606
        all = CPUID.ISAs_by_family[arch]
×
607
        return all[findfirst(x -> x.first == name, all)].second
×
608
    end
609
    Dict(
610
        "i686" => [
611
            "pentium4" => get_set("i686", "pentium4"),
612
            "prescott" => get_set("i686", "prescott"),
613
        ],
614
        "x86_64" => [
615
            "x86_64" => get_set("x86_64", "x86_64"),
616
            "avx" => get_set("x86_64", "sandybridge"),
617
            "avx2" => get_set("x86_64", "haswell"),
618
            "avx512" => get_set("x86_64", "skylake_avx512"),
619
        ],
620
        "armv6l" => [
621
            "arm1176jzfs" => get_set("armv6l", "arm1176jzfs"),
622
        ],
623
        "armv7l" => [
624
            "armv7l" => get_set("armv7l", "armv7l"),
625
            "neonvfpv4" => get_set("armv7l", "armv7l+neon+vfpv4"),
626
        ],
627
        "aarch64" => [
628
            "armv8_0" => get_set("aarch64", "armv8.0-a"),
629
            "armv8_1" => get_set("aarch64", "armv8.1-a"),
630
            "armv8_2_crypto" => get_set("aarch64", "armv8.2-a+crypto"),
631
            "a64fx" => get_set("aarch64", "a64fx"),
632
            "apple_m1" => get_set("aarch64", "apple_m1"),
633
        ],
634
        "powerpc64le" => [
635
            "power8" => get_set("powerpc64le", "power8"),
636
        ],
637
        "riscv64" => [
638
            "riscv64" => get_set("riscv64", "riscv64"),
639
        ],
640
    )
641
end
642
const os_mapping = Dict(
643
    "macos" => "-apple-darwin[\\d\\.]*",
644
    "freebsd" => "-(.*-)?freebsd[\\d\\.]*",
645
    "openbsd" => "-(.*-)?openbsd[\\d\\.]*",
646
    "windows" => "-w64-mingw32",
647
    "linux" => "-(.*-)?linux",
648
)
649
const libc_mapping = Dict(
650
    "libc_nothing" => "",
651
    "glibc" => "-gnu",
652
    "musl" => "-musl",
653
)
654
const call_abi_mapping = Dict(
655
    "call_abi_nothing" => "",
656
    "eabihf" => "eabihf",
657
    "eabi" => "eabi",
658
)
659
const libgfortran_version_mapping = Dict(
660
    "libgfortran_nothing" => "",
661
    "libgfortran3" => "(-libgfortran3)|(-gcc4)", # support old-style `gccX` versioning
662
    "libgfortran4" => "(-libgfortran4)|(-gcc7)",
663
    "libgfortran5" => "(-libgfortran5)|(-gcc8)",
664
)
665
const cxxstring_abi_mapping = Dict(
666
    "cxxstring_nothing" => "",
667
    "cxx03" => "-cxx03",
668
    "cxx11" => "-cxx11",
669
)
670
const libstdcxx_version_mapping = Dict{String,String}(
671
    "libstdcxx_nothing" => "",
672
    "libstdcxx" => "-libstdcxx\\d+",
673
)
674

675
const triplet_regex = let
676
    # Helper function to collapse dictionary of mappings down into a regex of
677
    # named capture groups joined by "|" operators
678
    c(mapping) = string("(",join(["(?<$k>$v)" for (k, v) in mapping], "|"), ")")
×
679

680
    Regex(string(
681
        "^",
682
        # First, the core triplet; arch/os/libc/call_abi
683
        c(arch_mapping),
684
        c(os_mapping),
685
        c(libc_mapping),
686
        c(call_abi_mapping),
687
        # Next, optional things, like libgfortran/libstdcxx/cxxstring abi
688
        c(libgfortran_version_mapping),
689
        c(cxxstring_abi_mapping),
690
        c(libstdcxx_version_mapping),
691
        # Finally, the catch-all for extended tags
692
        "(?<tags>(?:-[^-]+\\+[^-]+)*)?",
693
        "\$",
694
    ))
695
end
696

697
"""
698
    parse(::Type{Platform}, triplet::AbstractString)
699

700
Parses a string platform triplet back into a `Platform` object.
701
"""
702
function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = false)
159✔
703
    m = match(triplet_regex, triplet)
704
    if m !== nothing
705
        # Helper function to find the single named field within the giant regex
706
        # that is not `nothing` for each mapping we give it.
707
        get_field(m, mapping) = begin
708
            for k in keys(mapping)
709
                if m[k] !== nothing
710
                    # Convert our sentinel `nothing` values to actual `nothing`
711
                    if endswith(k, "_nothing")
712
                        return nothing
713
                    end
714
                    # Convert libgfortran/libstdcxx version numbers
715
                    if startswith(k, "libgfortran")
716
                        return VersionNumber(parse(Int,k[12:end]))
717
                    elseif startswith(k, "libstdcxx")
718
                        return VersionNumber(3, 4, parse(Int,m[k][11:end]))
719
                    else
720
                        return k
721
                    end
722
                end
723
            end
724
        end
725

726
        # Extract the information we're interested in:
727
        tags = Dict{String,Any}()
728
        arch = get_field(m, arch_mapping)
729
        os = get_field(m, os_mapping)
730
        tags["libc"] = get_field(m, libc_mapping)
731
        tags["call_abi"] = get_field(m, call_abi_mapping)
732
        tags["libgfortran_version"] = get_field(m, libgfortran_version_mapping)
733
        tags["libstdcxx_version"] = get_field(m, libstdcxx_version_mapping)
734
        tags["cxxstring_abi"] = get_field(m, cxxstring_abi_mapping)
735
        function split_tags(tagstr)
736
            tag_fields = split(tagstr, "-"; keepempty=false)
737
            if isempty(tag_fields)
738
                return Pair{String,String}[]
739
            end
740
            return map(v -> String(v[1]) => String(v[2]), split.(tag_fields, "+"))
741
        end
742
        merge!(tags, Dict(split_tags(m["tags"])))
743

744
        # Special parsing of os version number, if any exists
745
        function extract_os_version(os_name, pattern)
746
            m_osvn = match(pattern, m[os_name])
747
            if m_osvn !== nothing
748
                return VersionNumber(m_osvn.captures[1])
749
            end
750
            return nothing
751
        end
752
        os_version = nothing
753
        if os == "macos"
754
            os_version = extract_os_version("macos", r".*darwin([\d\.]+)"sa)
755
        end
756
        if os == "freebsd"
757
            os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)"sa)
758
        end
759
        if os == "openbsd"
760
            os_version = extract_os_version("openbsd", r".*openbsd([\d.]+)"sa)
761
        end
762
        tags["os_version"] = os_version
763

764
        return Platform(arch, os, tags; validate_strict)
765
    end
766
    throw(ArgumentError("Platform `$(triplet)` is not an officially supported platform"))
767
end
768
Base.parse(::Type{Platform}, triplet::AbstractString; kwargs...) =
×
769
    parse(Platform, convert(String, triplet)::String; kwargs...)
770

771
function Base.tryparse(::Type{Platform}, triplet::AbstractString)
×
772
    try
×
773
        parse(Platform, triplet)
×
774
    catch e
775
        if isa(e, InterruptException)
×
776
            rethrow(e)
×
777
        end
778
        return nothing
×
779
    end
780
end
781

782
"""
783
    platform_dlext(p::AbstractPlatform = HostPlatform())
784

785
Return the dynamic library extension for the given platform, defaulting to the
786
currently running platform.  E.g. returns "so" for a Linux-based platform,
787
"dll" for a Windows-based platform, etc...
788
"""
UNCOV
789
function platform_dlext(p::AbstractPlatform = HostPlatform())
×
UNCOV
790
    if os(p) == "windows"
×
UNCOV
791
        return "dll"
×
UNCOV
792
    elseif os(p) == "macos"
×
UNCOV
793
        return "dylib"
×
794
    else
UNCOV
795
        return "so"
×
796
    end
797
end
798

799
"""
800
    parse_dl_name_version(path::String, platform::AbstractPlatform)
801

802
Given a path to a dynamic library, parse out what information we can
803
from the filename.  E.g. given something like "lib/libfoo.so.3.2",
804
this function returns `"libfoo", v"3.2"`.  If the path name is not a
805
valid dynamic library, this method throws an error.  If no soversion
806
can be extracted from the filename, as in "libbar.so" this method
807
returns `"libbar", nothing`.
808
"""
UNCOV
809
function parse_dl_name_version(path::String, os::String)
×
810
    # Use an extraction regex that matches the given OS
811
    local dlregex
×
UNCOV
812
    if os == "windows"
×
813
        # On Windows, libraries look like `libnettle-6.dll`
814
        dlregex = r"^(.*?)(?:-((?:[\.\d]+)*))?\.dll$"sa
×
UNCOV
815
    elseif os == "macos"
×
816
        # On OSX, libraries look like `libnettle.6.3.dylib`
817
        dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$"sa
×
818
    else
819
        # On Linux and others BSD, libraries look like `libnettle.so.6.3.0`
820
        dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$"sa
×
821
    end
822

UNCOV
823
    m = match(dlregex, basename(path))
×
UNCOV
824
    if m === nothing
×
UNCOV
825
        throw(ArgumentError("Invalid dynamic library path '$path'"))
×
826
    end
827

828
    # Extract name and version
UNCOV
829
    name = m.captures[1]
×
UNCOV
830
    version = m.captures[2]
×
UNCOV
831
    if version === nothing || isempty(version)
×
832
        version = nothing
×
833
    else
UNCOV
834
        version = VersionNumber(strip(version, '.'))
×
835
    end
UNCOV
836
    return name, version
×
837
end
838

839
# Adapter for `AbstractString`
840
function parse_dl_name_version(path::AbstractString, os::AbstractString)
×
841
    return parse_dl_name_version(string(path)::String, string(os)::String)
×
842
end
843

844
"""
845
    detect_libgfortran_version()
846

847
Inspects the current Julia process to determine the libgfortran version this Julia is
848
linked against (if any).
849
"""
UNCOV
850
function detect_libgfortran_version()
×
UNCOV
851
    libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist())
×
UNCOV
852
    if isempty(libgfortran_paths)
×
853
        # One day, I hope to not be linking against libgfortran in base Julia
854
        return nothing
×
855
    end
UNCOV
856
    libgfortran_path = first(libgfortran_paths)
×
857

UNCOV
858
    name, version = parse_dl_name_version(libgfortran_path, os())
×
UNCOV
859
    if version === nothing
×
860
        # Even though we complain about this, we allow it to continue in the hopes that
861
        # we shall march on to a BRIGHTER TOMORROW.  One in which we are not shackled
862
        # by the constraints of libgfortran compiler ABIs upon our precious programming
863
        # languages; one where the mistakes of yesterday are mere memories and not
864
        # continual maintenance burdens upon the children of the dawn; one where numeric
865
        # code may be cleanly implemented in a modern language and not bestowed onto the
866
        # next generation by grizzled ancients, documented only with a faded yellow
867
        # sticky note that bears a hastily-scribbled "good luck".
868
        @warn("Unable to determine libgfortran version from '$(libgfortran_path)'")
×
869
    end
UNCOV
870
    return version
×
871
end
872

873
"""
874
    detect_libstdcxx_version(max_minor_version::Int=30)
875

876
Inspects the currently running Julia process to find out what version of libstdc++
877
it is linked against (if any).  `max_minor_version` is the latest version in the
878
3.4 series of GLIBCXX where the search is performed.
879
"""
UNCOV
880
function detect_libstdcxx_version(max_minor_version::Int=30)
×
UNCOV
881
    libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
×
UNCOV
882
    if isempty(libstdcxx_paths)
×
883
        # This can happen if we were built by clang, so we don't link against
884
        # libstdc++ at all.
885
        return nothing
×
886
    end
887

888
    # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against
UNCOV
889
    hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid}
×
890
    # Try all GLIBCXX versions down to GCC v4.8:
891
    # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
UNCOV
892
    for minor_version in max_minor_version:-1:18
×
UNCOV
893
        if Libdl.dlsym(hdl, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing
×
UNCOV
894
            Libdl.dlclose(hdl)
×
UNCOV
895
            return VersionNumber("3.4.$(minor_version)")
×
896
        end
897
    end
×
898
    Libdl.dlclose(hdl)
×
899
    return nothing
×
900
end
901

902
"""
903
    detect_cxxstring_abi()
904

905
Inspects the currently running Julia process to see what version of the C++11 string ABI
906
it was compiled with (this is only relevant if compiled with `g++`; `clang` has no
907
incompatibilities yet, bless its heart).  In reality, this actually checks for symbols
908
within LLVM, but that is close enough for our purposes, as you can't mix configurations
909
between Julia and LLVM; they must match.
910
"""
UNCOV
911
function detect_cxxstring_abi()
×
912
    # First, if we're not linked against libstdc++, then early-exit because this doesn't matter.
UNCOV
913
    libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
×
UNCOV
914
    if isempty(libstdcxx_paths)
×
915
        # We were probably built by `clang`; we don't link against `libstdc++`` at all.
916
        return nothing
×
917
    end
918

UNCOV
919
    function open_libllvm(f::Function)
×
UNCOV
920
        for lib_name in (Base.libllvm_name, "libLLVM", "LLVM", "libLLVMSupport")
×
UNCOV
921
            hdl = Libdl.dlopen_e(lib_name)
×
UNCOV
922
            if hdl != C_NULL
×
UNCOV
923
                try
×
UNCOV
924
                    return f(hdl)
×
925
                finally
UNCOV
926
                    Libdl.dlclose(hdl)
×
927
                end
928
            end
929
        end
×
930
        error("Unable to open libLLVM!")
×
931
    end
932

UNCOV
933
    return open_libllvm() do hdl
×
934
        # Check for llvm::sys::getProcessTriple(), first without cxx11 tag:
UNCOV
935
        if Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleEv") != C_NULL
×
936
            return "cxx03"
×
UNCOV
937
        elseif Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleB5cxx11Ev") != C_NULL
×
UNCOV
938
            return "cxx11"
×
939
        else
940
            @warn("Unable to find llvm::sys::getProcessTriple() in libLLVM!")
×
941
            return nothing
×
942
        end
943
    end
944
end
945

946
"""
947
    host_triplet()
948

949
Build host triplet out of `Sys.MACHINE` and various introspective utilities that
950
detect compiler ABI values such as `libgfortran_version`, `libstdcxx_version` and
951
`cxxstring_abi`.  We do this without using any `Platform` tech as it must run before
952
we have much of that built.
953
"""
UNCOV
954
function host_triplet()
×
UNCOV
955
    str = Base.BUILD_TRIPLET
×
956

UNCOV
957
    if !occursin("-libgfortran", str)
×
958
        libgfortran_version = detect_libgfortran_version()
×
959
        if libgfortran_version !== nothing
×
960
            str = string(str, "-libgfortran", libgfortran_version.major)
×
961
        end
962
    end
963

UNCOV
964
    if !occursin("-cxx", str)
×
965
        cxxstring_abi = detect_cxxstring_abi()
×
966
        if cxxstring_abi !== nothing
×
967
            str = string(str, "-", cxxstring_abi)
×
968
        end
969
    end
970

UNCOV
971
    if !occursin("-libstdcxx", str)
×
UNCOV
972
        libstdcxx_version = detect_libstdcxx_version()
×
UNCOV
973
        if libstdcxx_version !== nothing
×
UNCOV
974
            str = string(str, "-libstdcxx", libstdcxx_version.patch)
×
975
        end
976
    end
977

978
    # Add on julia_version extended tag
UNCOV
979
    if !occursin("-julia_version+", str)
×
UNCOV
980
        str = string(str, "-julia_version+", VersionNumber(VERSION.major, VERSION.minor, VERSION.patch))
×
981
    end
UNCOV
982
    return str
×
983
end
984

985
"""
986
    HostPlatform()
987

988
Return the `Platform` object that corresponds to the current host system, with all
989
relevant comparison strategies set to host platform mode.  This is equivalent to:
990

991
    HostPlatform(parse(Platform, Base.BinaryPlatforms.host_triplet()))
992
"""
993
function HostPlatform()
994
    return HostPlatform(parse(Platform, host_triplet()))::Platform
159✔
995
end
996

997
"""
998
    platforms_match(a::AbstractPlatform, b::AbstractPlatform)
999

1000
Return `true` if `a` and `b` are matching platforms, where matching is determined by
1001
comparing all keys contained within the platform objects, and if both objects contain
1002
entries for that key, they must match.  Comparison, by default, is performed using
1003
the `==` operator, however this can be overridden on a key-by-key basis by adding
1004
"comparison strategies" through `set_compare_strategy!(platform, key, func)`.
1005

1006
Note that as the comparison strategy is set on the `Platform` object, and not globally,
1007
a custom comparison strategy is first looked for within the `a` object, then if none
1008
is found, it is looked for in the `b` object.  Finally, if none is found in either, the
1009
default of `==(ak, bk)` is used.  We throw an error if custom comparison strategies are
1010
used on both `a` and `b` and they are not the same custom comparison.
1011

1012
The reserved tags `os_version` and `libstdcxx_version` use this mechanism to provide
1013
bounded version constraints, where an artifact can specify that it was built using APIs
1014
only available in macOS `v"10.11"` and later, or an artifact can state that it requires
1015
a libstdc++ that is at least `v"3.4.22"`, etc...
1016
"""
UNCOV
1017
function platforms_match(a::AbstractPlatform, b::AbstractPlatform)
×
UNCOV
1018
    for k in union(keys(tags(a)::Dict{String,String}), keys(tags(b)::Dict{String,String}))
×
UNCOV
1019
        ak = get(tags(a), k, nothing)
×
UNCOV
1020
        bk = get(tags(b), k, nothing)
×
1021

1022
        # Only continue if both `ak` and `bk` are not `nothing`
UNCOV
1023
        if ak === nothing || bk === nothing
×
UNCOV
1024
            continue
×
1025
        end
1026

UNCOV
1027
        a_comp = get_compare_strategy(a, k)
×
UNCOV
1028
        b_comp = get_compare_strategy(b, k)
×
1029

1030
        # Throw an error if `a` and `b` have both set non-default comparison strategies for `k`
1031
        # and they're not the same strategy.
UNCOV
1032
        if a_comp !== compare_default && b_comp !== compare_default && a_comp !== b_comp
×
1033
            throw(ArgumentError("Cannot compare Platform objects with two different non-default comparison strategies for the same key \"$(k)\""))
×
1034
        end
1035

1036
        # Select the custom comparator, if we have one.
UNCOV
1037
        comparator = a_comp
×
UNCOV
1038
        if b_comp !== compare_default
×
UNCOV
1039
            comparator = b_comp
×
1040
        end
1041

1042
        # Call the comparator, passing in which objects requested this comparison (one, the other, or both)
1043
        # For some comparators this doesn't matter, but for non-symmetrical comparisons, it does.
UNCOV
1044
        if !(comparator(ak, bk, a_comp === comparator, b_comp === comparator)::Bool)
×
UNCOV
1045
            return false
×
1046
        end
UNCOV
1047
    end
×
UNCOV
1048
    return true
×
1049
end
1050

UNCOV
1051
function platforms_match(a::String, b::AbstractPlatform)
×
UNCOV
1052
    return platforms_match(parse(Platform, a), b)
×
1053
end
UNCOV
1054
function platforms_match(a::AbstractPlatform, b::String)
×
UNCOV
1055
    return platforms_match(a, parse(Platform, b))
×
1056
end
1057
platforms_match(a::String, b::String) = platforms_match(parse(Platform, a), parse(Platform, b))
×
1058

1059
# Adapters for AbstractString backedge avoidance
1060
platforms_match(a::AbstractString, b::AbstractPlatform) = platforms_match(string(a)::String, b)
×
1061
platforms_match(a::AbstractPlatform, b::AbstractString) = platforms_match(a, string(b)::String)
×
1062
platforms_match(a::AbstractString, b::AbstractString) = platforms_match(string(a)::String, string(b)::String)
×
1063

1064

1065
"""
1066
    select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
1067

1068
Given a `download_info` dictionary mapping platforms to some value, choose
1069
the value whose key best matches `platform`, returning `nothing` if no matches
1070
can be found.
1071

1072
Platform attributes such as architecture, libc, calling ABI, etc... must all
1073
match exactly, however attributes such as compiler ABI can have wildcards
1074
within them such as `nothing` which matches any version of GCC.
1075
"""
UNCOV
1076
function select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
×
UNCOV
1077
    ps = collect(filter(p -> platforms_match(p, platform), keys(download_info)))
×
1078

UNCOV
1079
    if isempty(ps)
×
UNCOV
1080
        return nothing
×
1081
    end
1082

1083
    # At this point, we may have multiple possibilities.  We now engage a multi-
1084
    # stage selection algorithm, where we first sort the matches by how complete
1085
    # the match is, e.g. preferring matches where the intersection of tags is
1086
    # equal to the union of the tags:
UNCOV
1087
    function match_loss(a, b)
×
UNCOV
1088
        a_tags = Set(keys(tags(a)))
×
UNCOV
1089
        b_tags = Set(keys(tags(b)))
×
UNCOV
1090
        return length(union(a_tags, b_tags)) - length(intersect(a_tags, b_tags))
×
1091
    end
1092

1093
    # We prefer these better matches, and secondarily reverse-sort by triplet so
1094
    # as to generally choose the latest release (e.g. a `libgfortran5` tarball
1095
    # over a `libgfortran3` tarball).
UNCOV
1096
    sort!(ps, lt = (a, b) -> begin
×
UNCOV
1097
        loss_a = match_loss(a, platform)
×
UNCOV
1098
        loss_b = match_loss(b, platform)
×
UNCOV
1099
        if loss_a != loss_b
×
UNCOV
1100
            return loss_a < loss_b
×
1101
        end
UNCOV
1102
        return triplet(a) > triplet(b)
×
1103
    end)
1104

1105
    # @invokelatest here to not get invalidated by new defs of `==(::Function, ::Function)`
UNCOV
1106
    return @invokelatest getindex(download_info, first(ps))
×
1107
end
1108

1109
# precompiles to reduce latency (see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1025692379)
1110
Dict{Platform,String}()[HostPlatform()] = ""
1111
Platform("x86_64", "linux", Dict{String,Any}(); validate_strict=true)
1112
Platform("x86_64", "linux", Dict{String,String}(); validate_strict=false)  # called this way from Artifacts.unpack_platform
1113

1114
end # module
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