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

JuliaLang / julia / #38194

22 Aug 2025 06:29AM UTC coverage: 78.186% (+0.3%) from 77.913%
#38194

push

local

web-flow
test: add missing closewrite call (#59356)

This test would previously just hang if there was any error, instead of
printing the error as intended.

This might be better as an IOBuffer so closewrite is implicit, but
that's behavior is not well-specified right now with multiple writers,
as is done here.

48632 of 62200 relevant lines covered (78.19%)

9409310.73 hits per line

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

44.64
/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
44
    function Platform(arch::String, os::String, _tags::Dict{String};
788✔
45
                      validate_strict::Bool = false,
46
                      compare_strategies::Dict{String,<:Function} = Dict{String,Function}())
47
        # A wee bit of normalization
48
        os = lowercase(os)
35✔
49
        arch = CPUID.normalize_arch(arch)
65✔
50

51
        tags = Dict{String,String}(
35✔
52
            "arch" => arch,
53
            "os" => os,
54
        )
55
        for (tag, value) in _tags
69✔
56
            value = value::Union{String,VersionNumber,Nothing}
208✔
57
            tag = lowercase(tag)
208✔
58
            if tag ∈ ("arch", "os")
416✔
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.
63
            if value === nothing
208✔
64
                continue
87✔
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.
71
            if tag ∈ ("libgfortran_version", "libstdcxx_version", "os_version")
213✔
72
                if isa(value, VersionNumber)
29✔
73
                    value = string(value)
29✔
74
                elseif isa(value, String)
×
75
                    v = tryparse(VersionNumber, value)
×
76
                    if isa(v, VersionNumber)
×
77
                        value = string(v)
×
78
                    end
79
                end
80
            end
81

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

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

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

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

110
        return new(tags, compare_strategies)
35✔
111
    end
112
end
113

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

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

127
# Simple tag insertion that performs a little bit of validation
128
function add_tag!(tags::Dict{String,String}, tag::String, value::String)
116✔
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"""+- /<>:"'\|?*"""
116✔
134
    if any(occursin(nono, tag) for nono in nonos)
116✔
135
        throw(ArgumentError("Invalid character in tag name \"$(tag)\"!"))
×
136
    end
137

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

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

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

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

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

171

172
# Allow us to easily serialize Platform objects
173
function Base.show(io::IO, p::Platform)
1✔
174
    print(io, "Platform(")
1✔
175
    show(io, arch(p))
2✔
176
    print(io, ", ")
1✔
177
    show(io, os(p))
2✔
178
    print(io, "; ")
1✔
179
    join(io, ("$(k) = $(repr(v))" for (k, v) in tags(p) if k ∉ ("arch", "os")), ", ")
1✔
180
    print(io, ")")
1✔
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

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

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

227
    # Validate `os`/`arch`/`call_abi` combination
228
    throw_call_abi_mismatch() = throw(ArgumentError("Invalid os/arch/call_abi combination: $(tags["os"])/$(tags["arch"])/$(tags["call_abi"])"))
×
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.
231
        if !haskey(tags, "call_abi") || tags["call_abi"] ∉ ("eabihf", "eabi")
×
232
            throw_call_abi_mismatch()
×
233
        end
234
    else
235
        # Nothing else should have a `call_abi`.
236
        if haskey(tags, "call_abi")
×
237
            throw_call_abi_mismatch()
×
238
        end
239
    end
240

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

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

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

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

265
function get_compare_strategy(p::Platform, key::String, default = compare_default)
×
266
    if !haskey(p.tags, key)
×
267
        throw(ArgumentError("Cannot get comparison strategy for nonexistent tag $(key)!"))
×
268
    end
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
"""
281
function compare_default(a::String, b::String, a_requested::Bool, b_requested::Bool)
×
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
"""
300
function compare_version_cap(a::String, b::String, a_requested::Bool, b_requested::Bool)
×
301
    a = VersionNumber(a)
×
302
    b = VersionNumber(b)
×
303

304
    # If both b and a requested, then we fall back to equality:
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:
310
    if a_requested
×
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
"""
330
function HostPlatform(p::AbstractPlatform)
29✔
331
    if haskey(p, "os_version")
58✔
332
        set_compare_strategy!(p, "os_version", compare_version_cap)
×
333
    end
334
    if haskey(p, "libstdcxx_version")
58✔
335
        set_compare_strategy!(p, "libstdcxx_version", compare_version_cap)
×
336
    end
337
    return p
29✔
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
"""
354
arch(p::AbstractPlatform) = get(tags(p), "arch", nothing)
55✔
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
"""
370
os(p::AbstractPlatform) = get(tags(p), "os", nothing)
72✔
371

372
# As a special helper, it's sometimes useful to know the current OS at compile-time
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
"""
404
libc(p::AbstractPlatform) = get(tags(p), "libc", nothing)
48✔
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
"""
420
call_abi(p::AbstractPlatform) = get(tags(p), "call_abi", nothing)
33✔
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
"""
436
function platform_name(p::AbstractPlatform)
×
437
    return platform_names[os(p)]
×
438
end
439

440
function VNorNothing(d::Dict, key)
48✔
441
    v = get(d, key, nothing)
72✔
442
    if v === nothing
48✔
443
        return nothing
24✔
444
    end
445
    return VersionNumber(v)::VersionNumber
24✔
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
"""
454
libgfortran_version(p::AbstractPlatform) = VNorNothing(tags(p), "libgfortran_version")
24✔
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
"""
462
libstdcxx_version(p::AbstractPlatform) = VNorNothing(tags(p), "libstdcxx_version")
24✔
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
"""
469
cxxstring_abi(p::AbstractPlatform) = get(tags(p), "cxxstring_abi", nothing)
48✔
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
"""
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
"""
495
wordsize(p::AbstractPlatform) = (arch(p) ∈ ("i686", "armv6l", "armv7l")) ? 32 : 64
6✔
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
"""
514
function triplet(p::AbstractPlatform)
24✔
515
    str = string(
24✔
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
523
    libgfortran_version_ = libgfortran_version(p)
24✔
524
    if libgfortran_version_ !== nothing
24✔
525
        str = string(str, "-libgfortran", libgfortran_version_.major)
24✔
526
    end
527
    cxxstring_abi_ = cxxstring_abi(p)
48✔
528
    if cxxstring_abi_ !== nothing
24✔
529
        str = string(str, "-", cxxstring_abi_)
24✔
530
    end
531
    libstdcxx_version_ = libstdcxx_version(p)
24✔
532
    if libstdcxx_version_ !== nothing
24✔
533
        str = string(str, "-libstdcxx", libstdcxx_version_.patch)
×
534
    end
535

536
    # Tack on all extra tags
537
    for (tag, val) in tags(p)
48✔
538
        if tag ∈ ("os", "arch", "libc", "call_abi", "libgfortran_version", "libstdcxx_version", "cxxstring_abi", "os_version")
264✔
539
            continue
120✔
540
        end
541
        str = string(str, "-", tag, "+", val)
24✔
542
    end
264✔
543
    return str
24✔
544
end
545

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

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

588
Sys.isapple(p::AbstractPlatform) = os(p) == "macos"
2✔
589
Sys.islinux(p::AbstractPlatform) = os(p) == "linux"
2✔
590
Sys.iswindows(p::AbstractPlatform) = os(p) == "windows"
2✔
591
Sys.isfreebsd(p::AbstractPlatform) = os(p) == "freebsd"
×
592
Sys.isopenbsd(p::AbstractPlatform) = os(p) == "openbsd"
×
593
Sys.isbsd(p::AbstractPlatform) = os(p) ∈ ("freebsd", "openbsd", "macos")
3✔
594
Sys.isunix(p::AbstractPlatform) = Sys.isbsd(p) || Sys.islinux(p)
×
595

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

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

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

700
"""
701
    parse(::Type{Platform}, triplet::AbstractString)
702

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

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

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

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

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

785
"""
786
    platform_dlext(p::AbstractPlatform = HostPlatform())
787

788
Return the dynamic library extension for the given platform, defaulting to the
789
currently running platform.  E.g. returns "so" for a Linux-based platform,
790
"dll" for a Windows-based platform, etc...
791
"""
792
function platform_dlext(p::AbstractPlatform = HostPlatform())
5✔
793
    if os(p) == "windows"
10✔
794
        return "dll"
1✔
795
    elseif os(p) == "macos"
8✔
796
        return "dylib"
1✔
797
    else
798
        return "so"
3✔
799
    end
800
end
801

802
# Not general purpose, just for parse_dl_name_version
803
function _this_os_name()
804
    if Sys.iswindows()
×
805
        return "windows"
×
806
    elseif Sys.isapple()
×
807
        return "macos"
×
808
    else
809
        return "other"
×
810
    end
811
end
812

813
"""
814
    parse_dl_name_version(path::String, platform::AbstractPlatform)
815

816
Given a path to a dynamic library, parse out what information we can
817
from the filename.  E.g. given something like "lib/libfoo.so.3.2",
818
this function returns `"libfoo", v"3.2"`.  If the path name is not a
819
valid dynamic library, this method throws an error.  If no soversion
820
can be extracted from the filename, as in "libbar.so" this method
821
returns `"libbar", nothing`.
822
"""
823
function parse_dl_name_version(path::String, os::String=_this_os_name())
824
    # Use an extraction regex that matches the given OS
825
    local dlregex
366✔
826
    # Keep this up to date with _this_os_name
827
    if os == "windows"
183✔
828
        # On Windows, libraries look like `libnettle-6.dll`.
829
        # Stay case-insensitive, the suffix might be `.DLL`.
830
        dlregex = r"^(.*?)(?:-((?:[\.\d]+)*))?\.dll$"isa
×
831
    elseif os == "macos"
183✔
832
        # On OSX, libraries look like `libnettle.6.3.dylib`
833
        dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$"sa
×
834
    else
835
        # On Linux and others BSD, libraries look like `libnettle.so.6.3.0`
836
        dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$"sa
×
837
    end
838

839
    m = match(dlregex, basename(path))
183✔
840
    if m === nothing
183✔
841
        throw(ArgumentError("Invalid dynamic library path '$path'"))
×
842
    end
843

844
    # Extract name and version
845
    name = m.captures[1]
183✔
846
    version = m.captures[2]
183✔
847
    if version === nothing || isempty(version)
366✔
848
        version = nothing
×
849
    else
850
        version = VersionNumber(strip(version, '.'))
181✔
851
    end
852
    return name, version
183✔
853
end
854

855
# Adapter for `AbstractString`
856
function parse_dl_name_version(path::AbstractString, os::AbstractString=_this_os_name())
×
857
    return parse_dl_name_version(string(path)::String, string(os)::String)
×
858
end
859

860
function get_csl_member(member::Symbol)
29✔
861
    # If CompilerSupportLibraries_jll is an stdlib, we can just grab things from it
862
    csl_pkgids = filter(pkgid -> pkgid.name == "CompilerSupportLibraries_jll", keys(Base.loaded_modules))
1,166✔
863
    if !isempty(csl_pkgids)
29✔
864
        CSL_mod = Base.loaded_modules[first(csl_pkgids)]
29✔
865

866
        # This can fail during bootstrap, so we skip in that case.
867
        if isdefined(CSL_mod, member)
29✔
868
            return getproperty(CSL_mod, member)
29✔
869
        end
870
    end
871

872
    return nothing
×
873
end
874

875
"""
876
    detect_libgfortran_version()
877

878
Inspects the current Julia process to determine the libgfortran version this Julia is
879
linked against (if any).  Returns `nothing` if no libgfortran version dependence is
880
detected.
881
"""
882
function detect_libgfortran_version()
×
883
    function get_libgfortran_path()
×
884
        # If CompilerSupportLibraries_jll is an stdlib, we can just directly ask for
885
        # the path here, without checking `dllist()`:
886
        libgfortran_path = get_csl_member(:libgfortran_path)
×
887
        if libgfortran_path !== nothing
×
888
            return libgfortran_path::String
×
889
        end
890

891
        # Otherwise, look for it having already been loaded by something
892
        libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist())
×
893
        if !isempty(libgfortran_paths)
×
894
            return first(libgfortran_paths)::String
×
895
        end
896

897
        # One day, I hope to not be linking against libgfortran in base Julia
898
        return nothing
×
899
    end
900

901
    libgfortran_path = get_libgfortran_path()
×
902
    name, version = parse_dl_name_version(libgfortran_path, os())
×
903
    if version === nothing
×
904
        # Even though we complain about this, we allow it to continue in the hopes that
905
        # we shall march on to a BRIGHTER TOMORROW.  One in which we are not shackled
906
        # by the constraints of libgfortran compiler ABIs upon our precious programming
907
        # languages; one where the mistakes of yesterday are mere memories and not
908
        # continual maintenance burdens upon the children of the dawn; one where numeric
909
        # code may be cleanly implemented in a modern language and not bestowed onto the
910
        # next generation by grizzled ancients, documented only with a faded yellow
911
        # sticky note that bears a hastily-scribbled "good luck".
912
        @warn("Unable to determine libgfortran version from '$(libgfortran_path)'")
×
913
    end
914
    return version
×
915
end
916

917
"""
918
    detect_libstdcxx_version(max_minor_version::Int=30)
919

920
Inspects the currently running Julia process to find out what version of libstdc++
921
it is linked against (if any).  `max_minor_version` is the latest version in the
922
3.4 series of GLIBCXX where the search is performed.
923
"""
924
function detect_libstdcxx_version(max_minor_version::Int=30)
29✔
925
    function get_libstdcxx_handle()
87✔
926
        # If CompilerSupportLibraries_jll is an stdlib, we can just directly open it
927
        libstdcxx = get_csl_member(:libstdcxx)
29✔
928
        if libstdcxx !== nothing
29✔
929
            return nothing
29✔
930
        end
931

932
        # Otherwise, look for it having already been loaded by something
933
        libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
×
934
        if !isempty(libstdcxx_paths)
×
935
            return Libdl.dlopen(first(libstdcxx_paths), Libdl.RTLD_NOLOAD)::Ptr{Cvoid}
×
936
        end
937

938
        # One day, I hope to not be linking against libgfortran in base Julia
939
        return nothing
×
940
    end
941

942
    # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against
943
    libstdcxx = get_libstdcxx_handle()
29✔
944

945
    if libstdcxx !== nothing
29✔
946
        # Try all GLIBCXX versions down to GCC v4.8:
947
        # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
948
        for minor_version in max_minor_version:-1:18
×
949
            if Libdl.dlsym(libstdcxx, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing
×
950
                return VersionNumber("3.4.$(minor_version)")
×
951
            end
952
        end
×
953
    end
954
    return nothing
29✔
955
end
956

957
"""
958
    detect_cxxstring_abi()
959

960
Inspects the currently running Julia process to see what version of the C++11 string ABI
961
it was compiled with (this is only relevant if compiled with `g++`; `clang` has no
962
incompatibilities yet, bless its heart).  In reality, this actually checks for symbols
963
within LLVM, but that is close enough for our purposes, as you can't mix configurations
964
between Julia and LLVM; they must match.
965
"""
966
function detect_cxxstring_abi()
×
967
    # First, if we're not linked against libstdc++, then early-exit because this doesn't matter.
968
    libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
×
969
    if isempty(libstdcxx_paths)
×
970
        # We were probably built by `clang`; we don't link against `libstdc++`` at all.
971
        return nothing
×
972
    end
973

974
    function open_libllvm(f::Function)
×
975
        for lib_name in (Base.libllvm_name, "libLLVM", "LLVM", "libLLVMSupport")
×
976
            hdl = Libdl.dlopen_e(lib_name)
×
977
            if hdl != C_NULL
×
978
                try
×
979
                    return f(hdl)
×
980
                finally
981
                    Libdl.dlclose(hdl)
×
982
                end
983
            end
984
        end
×
985
        error("Unable to open libLLVM!")
×
986
    end
987

988
    return open_libllvm() do hdl
×
989
        # Check for llvm::sys::getProcessTriple(), first without cxx11 tag:
990
        if Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleEv") != C_NULL
×
991
            return "cxx03"
×
992
        elseif Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleB5cxx11Ev") != C_NULL
×
993
            return "cxx11"
×
994
        else
995
            @warn("Unable to find llvm::sys::getProcessTriple() in libLLVM!")
×
996
            return nothing
×
997
        end
998
    end
999
end
1000

1001
"""
1002
    host_triplet()
1003

1004
Build host triplet out of `Sys.MACHINE` and various introspective utilities that
1005
detect compiler ABI values such as `libgfortran_version`, `libstdcxx_version` and
1006
`cxxstring_abi`.  We do this without using any `Platform` tech as it must run before
1007
we have much of that built.
1008
"""
1009
function host_triplet()
29✔
1010
    str = Base.BUILD_TRIPLET
29✔
1011

1012
    if !occursin("-libgfortran", str)
29✔
1013
        libgfortran_version = detect_libgfortran_version()
×
1014
        if libgfortran_version !== nothing
×
1015
            str = string(str, "-libgfortran", libgfortran_version.major)
×
1016
        end
1017
    end
1018

1019
    if !occursin("-cxx", str)
29✔
1020
        cxxstring_abi = detect_cxxstring_abi()
×
1021
        if cxxstring_abi !== nothing
×
1022
            str = string(str, "-", cxxstring_abi)
×
1023
        end
1024
    end
1025

1026
    if !occursin("-libstdcxx", str)
29✔
1027
        libstdcxx_version = detect_libstdcxx_version()
29✔
1028
        if libstdcxx_version !== nothing
29✔
1029
            str = string(str, "-libstdcxx", libstdcxx_version.patch)
×
1030
        end
1031
    end
1032

1033
    # Add on julia_version extended tag
1034
    if !occursin("-julia_version+", str)
29✔
1035
        str = string(str, "-julia_version+", VersionNumber(VERSION.major, VERSION.minor, VERSION.patch))
29✔
1036
    end
1037
    return str
29✔
1038
end
1039

1040
"""
1041
    HostPlatform()
1042

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

1046
    HostPlatform(parse(Platform, Base.BinaryPlatforms.host_triplet()))
1047
"""
1048
function HostPlatform()
1✔
1049
    return HostPlatform(parse(Platform, host_triplet()))::Platform
211✔
1050
end
1051

1052
"""
1053
    platforms_match(a::AbstractPlatform, b::AbstractPlatform)
1054

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

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

1067
The reserved tags `os_version` and `libstdcxx_version` use this mechanism to provide
1068
bounded version constraints, where an artifact can specify that it was built using APIs
1069
only available in macOS `v"10.11"` and later, or an artifact can state that it requires
1070
a libstdc++ that is at least `v"3.4.22"`, etc...
1071
"""
1072
function platforms_match(a::AbstractPlatform, b::AbstractPlatform)
×
1073
    for k in union(keys(tags(a)::Dict{String,String}), keys(tags(b)::Dict{String,String}))
×
1074
        ak = get(tags(a), k, nothing)
×
1075
        bk = get(tags(b), k, nothing)
×
1076

1077
        # Only continue if both `ak` and `bk` are not `nothing`
1078
        if ak === nothing || bk === nothing
×
1079
            continue
×
1080
        end
1081

1082
        a_comp = get_compare_strategy(a, k)
×
1083
        b_comp = get_compare_strategy(b, k)
×
1084

1085
        # Throw an error if `a` and `b` have both set non-default comparison strategies for `k`
1086
        # and they're not the same strategy.
1087
        if a_comp !== compare_default && b_comp !== compare_default && a_comp !== b_comp
×
1088
            throw(ArgumentError("Cannot compare Platform objects with two different non-default comparison strategies for the same key \"$(k)\""))
×
1089
        end
1090

1091
        # Select the custom comparator, if we have one.
1092
        comparator = a_comp
×
1093
        if b_comp !== compare_default
×
1094
            comparator = b_comp
×
1095
        end
1096

1097
        # Call the comparator, passing in which objects requested this comparison (one, the other, or both)
1098
        # For some comparators this doesn't matter, but for non-symmetrical comparisons, it does.
1099
        if !(comparator(ak, bk, a_comp === comparator, b_comp === comparator)::Bool)
×
1100
            return false
×
1101
        end
1102
    end
×
1103
    return true
×
1104
end
1105

1106
function platforms_match(a::String, b::AbstractPlatform)
27✔
1107
    return platforms_match(parse(Platform, a), b)
27✔
1108
end
1109
function platforms_match(a::AbstractPlatform, b::String)
27✔
1110
    return platforms_match(a, parse(Platform, b))
27✔
1111
end
1112
platforms_match(a::String, b::String) = platforms_match(parse(Platform, a), parse(Platform, b))
×
1113

1114
# Adapters for AbstractString backedge avoidance
1115
platforms_match(a::AbstractString, b::AbstractPlatform) = platforms_match(string(a)::String, b)
×
1116
platforms_match(a::AbstractPlatform, b::AbstractString) = platforms_match(a, string(b)::String)
×
1117
platforms_match(a::AbstractString, b::AbstractString) = platforms_match(string(a)::String, string(b)::String)
×
1118

1119

1120
"""
1121
    select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
1122

1123
Given a `download_info` dictionary mapping platforms to some value, choose
1124
the value whose key best matches `platform`, returning `nothing` if no matches
1125
can be found.
1126

1127
Platform attributes such as architecture, libc, calling ABI, etc... must all
1128
match exactly, however attributes such as compiler ABI can have wildcards
1129
within them such as `nothing` which matches any version of GCC.
1130
"""
1131
function select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
×
1132
    ps = collect(filter(p -> platforms_match(p, platform), keys(download_info)))
×
1133

1134
    if isempty(ps)
×
1135
        return nothing
×
1136
    end
1137

1138
    # At this point, we may have multiple possibilities.  We now engage a multi-
1139
    # stage selection algorithm, where we first sort the matches by how complete
1140
    # the match is, e.g. preferring matches where the intersection of tags is
1141
    # equal to the union of the tags:
1142
    function match_loss(a, b)
×
1143
        a_tags = Set(keys(tags(a)))
×
1144
        b_tags = Set(keys(tags(b)))
×
1145
        return length(union(a_tags, b_tags)) - length(intersect(a_tags, b_tags))
×
1146
    end
1147

1148
    # We prefer these better matches, and secondarily reverse-sort by triplet so
1149
    # as to generally choose the latest release (e.g. a `libgfortran5` tarball
1150
    # over a `libgfortran3` tarball).
1151
    sort!(ps, lt = (a, b) -> begin
×
1152
        loss_a = match_loss(a, platform)
×
1153
        loss_b = match_loss(b, platform)
×
1154
        if loss_a != loss_b
×
1155
            return loss_a < loss_b
×
1156
        end
1157
        return triplet(a) > triplet(b)
×
1158
    end)
1159

1160
    # @invokelatest here to not get invalidated by new defs of `==(::Function, ::Function)`
1161
    return @invokelatest getindex(download_info, first(ps))
×
1162
end
1163

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

1169
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