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

JuliaLang / julia / #37628

22 Sep 2023 05:02AM UTC coverage: 85.993% (+0.2%) from 85.841%
#37628

push

local

web-flow
slot2ssa: consider liveness when inserting `PiNode`s (#51406)

3 of 3 new or added lines in 1 file covered. (100.0%)

72179 of 83936 relevant lines covered (85.99%)

11324978.67 hits per line

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

79.52
/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};
3,284✔
45
                      validate_strict::Bool = false,
46
                      compare_strategies::Dict{String,<:Function} = Dict{String,Function}())
47
        # A wee bit of normalization
48
        os = lowercase(os)
1,642✔
49
        arch = CPUID.normalize_arch(arch)
1,642✔
50

51
        tags = Dict{String,String}(
1,642✔
52
            "arch" => arch,
53
            "os" => os,
54
        )
55
        for (tag, value) in _tags
3,105✔
56
            value = value::Union{String,VersionNumber,Nothing}
3,103✔
57
            tag = lowercase(tag)
4,693✔
58
            if tag ∈ ("arch", "os")
14,074✔
59
                throw(ArgumentError("Cannot double-pass key $(tag)"))
3✔
60
            end
61

62
            # Drop `nothing` values; this means feature is not present or use default value.
63
            if value === nothing
3,100✔
64
                continue
996✔
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")
10,558✔
72
                if isa(value, VersionNumber)
1,013✔
73
                    value = string(value)
1,001✔
74
                elseif isa(value, String)
×
75
                    v = tryparse(VersionNumber, value)
675✔
76
                    if isa(v, VersionNumber)
675✔
77
                        value = string(v)
673✔
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)
3,694✔
84
        end
7,920✔
85

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

96
        # If the user is asking for strict validation, do so.
97
        if validate_strict
1,639✔
98
            validate_tags(tags)
264✔
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")
1,618✔
103
            compare_strategies["julia_version"] = (a::String, b::String, a_comparator, b_comparator) -> begin
93✔
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)
1,618✔
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;
1,458✔
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)
729✔
120
    return Platform(arch, os, tags; validate_strict, compare_strategies)
729✔
121
end
122

123
tagvalue(v::Union{String,VersionNumber,Nothing}) = v
1,955✔
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)
3,697✔
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"""+- /<>:"'\|?*"""
×
134
    if any(occursin(nono, tag) for nono in nonos)
3,697✔
135
        throw(ArgumentError("Invalid character in tag name \"$(tag)\"!"))
×
136
    end
137

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

147
# Other `Platform` types can override this (I'm looking at you, `AnyPlatform`)
148
tags(p::Platform) = p.tags
6,471✔
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)
176✔
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)
540✔
162
    h = hash(p.compare_strategies, h)
540✔
163
    return h
×
164
end
165

166
# Simple equality definition; for compatibility testing, use `platforms_match()`
167
function Base.:(==)(a::Platform, b::Platform)
500✔
168
    return a.tags == b.tags && a.compare_strategies == b.compare_strategies
500✔
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)
264✔
195
    throw_invalid_key(k) = throw(ArgumentError("Key \"$(k)\" cannot have value \"$(tags[k])\""))
267✔
196
    # Validate `arch`
197
    if tags["arch"] ∉ ("x86_64", "i686", "armv7l", "armv6l", "aarch64", "powerpc64le")
603✔
198
        throw_invalid_key("arch")
1✔
199
    end
200
    # Validate `os`
201
    if tags["os"] ∉ ("linux", "macos", "freebsd", "windows")
407✔
202
        throw_invalid_key("os")
1✔
203
    end
204
    # Validate `os`/`arch` combination
205
    throw_os_mismatch() = throw(ArgumentError("Invalid os/arch combination: $(tags["os"])/$(tags["arch"])"))
264✔
206
    if tags["os"] == "windows" && tags["arch"] ∉ ("x86_64", "i686", "armv7l", "aarch64")
276✔
207
        throw_os_mismatch()
1✔
208
    end
209
    if tags["os"] == "macos" && tags["arch"] ∉ ("x86_64", "aarch64")
270✔
210
        throw_os_mismatch()
1✔
211
    end
212

213
    # Validate `os`/`libc` combination
214
    throw_libc_mismatch() = throw(ArgumentError("Invalid os/libc combination: $(tags["os"])/$(tags["libc"])"))
263✔
215
    if tags["os"] == "linux"
260✔
216
        # Linux always has a `libc` entry
217
        if tags["libc"] ∉ ("glibc", "musl")
197✔
218
            throw_libc_mismatch()
1✔
219
        end
220
    else
221
        # Nothing else is allowed to have a `libc` entry
222
        if haskey(tags, "libc")
75✔
223
            throw_libc_mismatch()
2✔
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"])"))
268✔
229
    if tags["os"] == "linux" && tags["arch"] ∈ ("armv7l", "armv6l")
557✔
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")
97✔
232
            throw_call_abi_mismatch()
43✔
233
        end
234
    else
235
        # Nothing else should have a `call_abi`.
236
        if haskey(tags, "call_abi")
214✔
237
            throw_call_abi_mismatch()
8✔
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"))
248✔
243
    if "libgfortran_version" in keys(tags) && tryparse(VersionNumber, tags["libgfortran_version"]) === nothing
246✔
244
        throw_version_number("libgfortran_version")
1✔
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")
294✔
249
        throw_invalid_key("cxxstring_abi")
1✔
250
    end
251

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

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

265
function get_compare_strategy(p::Platform, key::String, default = compare_default)
×
266
    if !haskey(p.tags, key)
4,002✔
267
        throw(ArgumentError("Cannot get comparison strategy for nonexistent tag $(key)!"))
×
268
    end
269
    return get(p.compare_strategies, key, default)
2,016✔
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)
970✔
282
    return a == b
970✔
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)
10✔
301
    a = VersionNumber(a)
10✔
302
    b = VersionNumber(b)
10✔
303

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

309
    # Otherwise, do the comparison between the the single version cap and the single version:
310
    if a_requested
10✔
311
        return b <= a
10✔
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)
88✔
331
    if haskey(p, "os_version")
88✔
332
        set_compare_strategy!(p, "os_version", compare_version_cap)
1✔
333
    end
334
    if haskey(p, "libstdcxx_version")
88✔
335
        set_compare_strategy!(p, "libstdcxx_version", compare_version_cap)
88✔
336
    end
337
    return p
88✔
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)
248✔
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)
284✔
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.isbsd()
×
379
        return "freebsd"
×
380
    else
381
        return "linux"
×
382
    end
383
end
384

385
"""
386
    libc(p::AbstractPlatform)
387

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

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

396
julia> libc(Platform("aarch64", "linux"; libc="musl"))
397
"musl"
398

399
julia> libc(Platform("i686", "Windows"))
400
```
401
"""
402
libc(p::AbstractPlatform) = get(tags(p), "libc", nothing)
201✔
403

404
"""
405
    call_abi(p::AbstractPlatform)
406

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

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

415
julia> call_abi(Platform("x86_64", "macos"))
416
```
417
"""
418
call_abi(p::AbstractPlatform) = get(tags(p), "call_abi", nothing)
121✔
419

420
const platform_names = Dict(
421
    "linux" => "Linux",
422
    "macos" => "macOS",
423
    "windows" => "Windows",
424
    "freebsd" => "FreeBSD",
425
    nothing => "Unknown",
426
)
427

428
"""
429
    platform_name(p::AbstractPlatform)
430

431
Get the "platform name" of the given platform, returning e.g. "Linux" or "Windows".
432
"""
433
function platform_name(p::AbstractPlatform)
4✔
434
    return platform_names[os(p)]
4✔
435
end
436

437
function VNorNothing(d::Dict, key)
×
438
    v = get(d, key, nothing)
499✔
439
    if v === nothing
305✔
440
        return nothing
111✔
441
    end
442
    return VersionNumber(v)::VersionNumber
194✔
443
end
444

445
"""
446
    libgfortran_version(p::AbstractPlatform)
447

448
Get the libgfortran version dictated by this `Platform` object as a `VersionNumber`,
449
or `nothing` if no compatibility bound is imposed.
450
"""
451
libgfortran_version(p::AbstractPlatform) = VNorNothing(tags(p), "libgfortran_version")
259✔
452

453
"""
454
    libstdcxx_version(p::AbstractPlatform)
455

456
Get the libstdc++ version dictated by this `Platform` object, or `nothing` if no
457
compatibility bound is imposed.
458
"""
459
libstdcxx_version(p::AbstractPlatform) = VNorNothing(tags(p), "libstdcxx_version")
234✔
460

461
"""
462
    cxxstring_abi(p::AbstractPlatform)
463

464
Get the c++ string ABI dictated by this `Platform` object, or `nothing` if no ABI is imposed.
465
"""
466
cxxstring_abi(p::AbstractPlatform) = get(tags(p), "cxxstring_abi", nothing)
237✔
467

468
"""
469
    os_version(p::AbstractPlatform)
470

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

478
"""
479
    wordsize(p::AbstractPlatform)
480

481
Get the word size for the given `Platform` object.
482

483
# Examples
484
```jldoctest
485
julia> wordsize(Platform("armv7l", "linux"))
486
32
487

488
julia> wordsize(Platform("x86_64", "macos"))
489
64
490
```
491
"""
492
wordsize(p::AbstractPlatform) = (arch(p) ∈ ("i686", "armv6l", "armv7l")) ? 32 : 64
10✔
493

494
"""
495
    triplet(p::AbstractPlatform)
496

497
Get the target triplet for the given `Platform` object as a `String`.
498

499
# Examples
500
```jldoctest
501
julia> triplet(Platform("x86_64", "MacOS"))
502
"x86_64-apple-darwin"
503

504
julia> triplet(Platform("i686", "Windows"))
505
"i686-w64-mingw32"
506

507
julia> triplet(Platform("armv7l", "Linux"; libgfortran_version="3"))
508
"armv7l-linux-gnueabihf-libgfortran3"
509
```
510
"""
511
function triplet(p::AbstractPlatform)
102✔
512
    str = string(
107✔
513
        arch(p)::Union{Symbol,String},
514
        os_str(p),
515
        libc_str(p),
516
        call_abi_str(p),
517
    )
518

519
    # Tack on optional compiler ABI flags
520
    if libgfortran_version(p) !== nothing
154✔
521
        str = string(str, "-libgfortran", libgfortran_version(p).major)
104✔
522
    end
523
    if cxxstring_abi(p) !== nothing
147✔
524
        str = string(str, "-", cxxstring_abi(p))
90✔
525
    end
526
    if libstdcxx_version(p) !== nothing
146✔
527
        str = string(str, "-libstdcxx", libstdcxx_version(p).patch)
88✔
528
    end
529

530
    # Tack on all extra tags
531
    for (tag, val) in tags(p)
204✔
532
        if tag ∈ ("os", "arch", "libc", "call_abi", "libgfortran_version", "libstdcxx_version", "cxxstring_abi", "os_version")
1,707✔
533
            continue
448✔
534
        end
535
        str = string(str, "-", tag, "+", val)
27✔
536
    end
848✔
537
    return str
102✔
538
end
539

540
function os_str(p::AbstractPlatform)
102✔
541
    if os(p) == "linux"
204✔
542
        return "-linux"
97✔
543
    elseif os(p) == "macos"
10✔
544
        osvn = os_version(p)
3✔
545
        if osvn !== nothing
2✔
546
            return "-apple-darwin$(osvn.major)"
1✔
547
        else
548
            return "-apple-darwin"
1✔
549
        end
550
    elseif os(p) == "windows"
6✔
551
        return "-w64-mingw32"
1✔
552
    elseif os(p) == "freebsd"
4✔
553
        osvn = os_version(p)
2✔
554
        if osvn !== nothing
2✔
555
            return "-unknown-freebsd$(osvn.major).$(osvn.minor)"
×
556
        else
557
            return "-unknown-freebsd"
2✔
558
        end
559
    else
560
        return "-unknown"
×
561
    end
562
end
563

564
# Helper functions for Linux and FreeBSD libc/abi mishmashes
565
function libc_str(p::AbstractPlatform)
×
566
    lc = libc(p)
199✔
567
    if lc === nothing
102✔
568
        return ""
5✔
569
    elseif lc === "glibc"
97✔
570
        return "-gnu"
93✔
571
    else
572
        return string("-", lc)
4✔
573
    end
574
end
575
function call_abi_str(p::AbstractPlatform)
×
576
    cabi = call_abi(p)
107✔
577
    cabi === nothing ? "" : string(cabi::Union{Symbol,String})
107✔
578
end
579

580
Sys.isapple(p::AbstractPlatform) = os(p) == "macos"
2✔
581
Sys.islinux(p::AbstractPlatform) = os(p) == "linux"
2✔
582
Sys.iswindows(p::AbstractPlatform) = os(p) == "windows"
2✔
583
Sys.isfreebsd(p::AbstractPlatform) = os(p) == "freebsd"
×
584
Sys.isbsd(p::AbstractPlatform) = os(p) ∈ ("freebsd", "macos")
3✔
585
Sys.isunix(p::AbstractPlatform) = Sys.isbsd(p) || Sys.islinux(p)
×
586

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

664
"""
665
    parse(::Type{Platform}, triplet::AbstractString)
666

667
Parses a string platform triplet back into a `Platform` object.
668
"""
669
function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = false)
352✔
670
    # Helper function to collapse dictionary of mappings down into a regex of
671
    # named capture groups joined by "|" operators
672
    c(mapping) = string("(",join(["(?<$k>$v)" for (k, v) in mapping], "|"), ")")
1,232✔
673

674
    # We're going to build a mondo regex here to parse everything:
675
    triplet_regex = Regex(string(
176✔
676
        "^",
677
        # First, the core triplet; arch/os/libc/call_abi
678
        c(arch_mapping),
679
        c(os_mapping),
680
        c(libc_mapping),
681
        c(call_abi_mapping),
682
        # Next, optional things, like libgfortran/libstdcxx/cxxstring abi
683
        c(libgfortran_version_mapping),
684
        c(cxxstring_abi_mapping),
685
        c(libstdcxx_version_mapping),
686
        # Finally, the catch-all for extended tags
687
        "(?<tags>(?:-[^-]+\\+[^-]+)*)?",
688
        "\$",
689
    ))
690

691
    m = match(triplet_regex, triplet)
176✔
692
    if m !== nothing
176✔
693
        # Helper function to find the single named field within the giant regex
694
        # that is not `nothing` for each mapping we give it.
695
        get_field(m, mapping) = begin
1,384✔
696
            for k in keys(mapping)
2,422✔
697
                if m[k] !== nothing
2,588✔
698
                    # Convert our sentinel `nothing` values to actual `nothing`
699
                    if endswith(k, "_nothing")
1,211✔
700
                        return nothing
375✔
701
                    end
702
                    # Convert libgfortran/libstdcxx version numbers
703
                    if startswith(k, "libgfortran")
946✔
704
                        return VersionNumber(parse(Int,k[12:end]))
108✔
705
                    elseif startswith(k, "libstdcxx")
836✔
706
                        return VersionNumber(3, 4, parse(Int,m[k][11:end]))
106✔
707
                    else
708
                        return k
622✔
709
                    end
710
                end
711
            end
1,377✔
712
        end
713

714
        # Extract the information we're interested in:
715
        tags = Dict{String,Any}()
173✔
716
        arch = get_field(m, arch_mapping)
173✔
717
        os = get_field(m, os_mapping)
173✔
718
        tags["libc"] = get_field(m, libc_mapping)
335✔
719
        tags["call_abi"] = get_field(m, call_abi_mapping)
179✔
720
        tags["libgfortran_version"] = get_field(m, libgfortran_version_mapping)
281✔
721
        tags["libstdcxx_version"] = get_field(m, libstdcxx_version_mapping)
279✔
722
        tags["cxxstring_abi"] = get_field(m, cxxstring_abi_mapping)
281✔
723
        function split_tags(tagstr)
173✔
724
            tag_fields = split(tagstr, "-"; keepempty=false)
173✔
725
            if isempty(tag_fields)
173✔
726
                return Pair{String,String}[]
83✔
727
            end
728
            return map(v -> String(v[1]) => String(v[2]), split.(tag_fields, "+"))
181✔
729
        end
730
        merge!(tags, Dict(split_tags(m["tags"])))
346✔
731

732
        # Special parsing of os version number, if any exists
733
        function extract_os_version(os_name, pattern)
182✔
734
            m_osvn = match(pattern, m[os_name])
9✔
735
            if m_osvn !== nothing
9✔
736
                return VersionNumber(m_osvn.captures[1])
8✔
737
            end
738
            return nothing
1✔
739
        end
740
        os_version = nothing
×
741
        if os == "macos"
346✔
742
            os_version = extract_os_version("macos", r".*darwin([\d\.]+)"sa)
5✔
743
        end
744
        if os == "freebsd"
346✔
745
            os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)"sa)
4✔
746
        end
747
        tags["os_version"] = os_version
181✔
748

749
        return Platform(arch, os, tags; validate_strict)
173✔
750
    end
751
    throw(ArgumentError("Platform `$(triplet)` is not an officially supported platform"))
3✔
752
end
753
Base.parse(::Type{Platform}, triplet::AbstractString; kwargs...) =
×
754
    parse(Platform, convert(String, triplet)::String; kwargs...)
755

756
function Base.tryparse(::Type{Platform}, triplet::AbstractString)
×
757
    try
×
758
        parse(Platform, triplet)
×
759
    catch e
760
        if isa(e, InterruptException)
×
761
            rethrow(e)
×
762
        end
763
        return nothing
×
764
    end
765
end
766

767
"""
768
    platform_dlext(p::AbstractPlatform = HostPlatform())
769

770
Return the dynamic library extension for the given platform, defaulting to the
771
currently running platform.  E.g. returns "so" for a Linux-based platform,
772
"dll" for a Windows-based platform, etc...
773
"""
774
function platform_dlext(p::AbstractPlatform = HostPlatform())
7✔
775
    if os(p) == "windows"
13✔
776
        return "dll"
1✔
777
    elseif os(p) == "macos"
10✔
778
        return "dylib"
1✔
779
    else
780
        return "so"
4✔
781
    end
782
end
783

784
"""
785
    parse_dl_name_version(path::String, platform::AbstractPlatform)
786

787
Given a path to a dynamic library, parse out what information we can
788
from the filename.  E.g. given something like "lib/libfoo.so.3.2",
789
this function returns `"libfoo", v"3.2"`.  If the path name is not a
790
valid dynamic library, this method throws an error.  If no soversion
791
can be extracted from the filename, as in "libbar.so" this method
792
returns `"libbar", nothing`.
793
"""
794
function parse_dl_name_version(path::String, os::String)
27✔
795
    # Use an extraction regex that matches the given OS
796
    local dlregex
×
797
    if os == "windows"
27✔
798
        # On Windows, libraries look like `libnettle-6.dll`
799
        dlregex = r"^(.*?)(?:-((?:[\.\d]+)*))?\.dll$"sa
6✔
800
    elseif os == "macos"
21✔
801
        # On OSX, libraries look like `libnettle.6.3.dylib`
802
        dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$"sa
6✔
803
    else
804
        # On Linux and FreeBSD, libraries look like `libnettle.so.6.3.0`
805
        dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$"sa
×
806
    end
807

808
    m = match(dlregex, basename(path))
27✔
809
    if m === nothing
27✔
810
        throw(ArgumentError("Invalid dynamic library path '$path'"))
10✔
811
    end
812

813
    # Extract name and version
814
    name = m.captures[1]
17✔
815
    version = m.captures[2]
17✔
816
    if version === nothing || isempty(version)
32✔
817
        version = nothing
×
818
    else
819
        version = VersionNumber(strip(version, '.'))
11✔
820
    end
821
    return name, version
17✔
822
end
823

824
# Adapter for `AbstractString`
825
function parse_dl_name_version(path::AbstractString, os::AbstractString)
×
826
    return parse_dl_name_version(string(path)::String, string(os)::String)
×
827
end
828

829
"""
830
    detect_libgfortran_version()
831

832
Inspects the current Julia process to determine the libgfortran version this Julia is
833
linked against (if any).
834
"""
835
function detect_libgfortran_version()
3✔
836
    libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist())
99✔
837
    if isempty(libgfortran_paths)
3✔
838
        # One day, I hope to not be linking against libgfortran in base Julia
839
        return nothing
×
840
    end
841
    libgfortran_path = first(libgfortran_paths)
3✔
842

843
    name, version = parse_dl_name_version(libgfortran_path, os())
6✔
844
    if version === nothing
3✔
845
        # Even though we complain about this, we allow it to continue in the hopes that
846
        # we shall march on to a BRIGHTER TOMORROW.  One in which we are not shackled
847
        # by the constraints of libgfortran compiler ABIs upon our precious programming
848
        # languages; one where the mistakes of yesterday are mere memories and not
849
        # continual maintenance burdens upon the children of the dawn; one where numeric
850
        # code may be cleanly implemented in a modern language and not bestowed onto the
851
        # next generation by grizzled ancients, documented only with a faded yellow
852
        # sticky note that bears a hastily-scribbled "good luck".
853
        @warn("Unable to determine libgfortran version from '$(libgfortran_path)'")
×
854
    end
855
    return version
3✔
856
end
857

858
"""
859
    detect_libstdcxx_version(max_minor_version::Int=30)
860

861
Inspects the currently running Julia process to find out what version of libstdc++
862
it is linked against (if any).  `max_minor_version` is the latest version in the
863
3.4 series of GLIBCXX where the search is performed.
864
"""
865
function detect_libstdcxx_version(max_minor_version::Int=30)
89✔
866
    libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
4,009✔
867
    if isempty(libstdcxx_paths)
88✔
868
        # This can happen if we were built by clang, so we don't link against
869
        # libstdc++ at all.
870
        return nothing
×
871
    end
872

873
    # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against
874
    hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid}
88✔
875
    # Try all GLIBCXX versions down to GCC v4.8:
876
    # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
877
    for minor_version in max_minor_version:-1:18
176✔
878
        if Libdl.dlsym(hdl, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing
88✔
879
            Libdl.dlclose(hdl)
88✔
880
            return VersionNumber("3.4.$(minor_version)")
88✔
881
        end
882
    end
×
883
    Libdl.dlclose(hdl)
×
884
    return nothing
×
885
end
886

887
"""
888
    detect_cxxstring_abi()
889

890
Inspects the currently running Julia process to see what version of the C++11 string ABI
891
it was compiled with (this is only relevant if compiled with `g++`; `clang` has no
892
incompatibilities yet, bless its heart).  In reality, this actually checks for symbols
893
within LLVM, but that is close enough for our purposes, as you can't mix configurations
894
between Julia and LLVM; they must match.
895
"""
896
function detect_cxxstring_abi()
1✔
897
    # First, if we're not linked against libstdc++, then early-exit because this doesn't matter.
898
    libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
33✔
899
    if isempty(libstdcxx_paths)
1✔
900
        # We were probably built by `clang`; we don't link against `libstdc++`` at all.
901
        return nothing
×
902
    end
903

904
    function open_libllvm(f::Function)
1✔
905
        for lib_name in (Base.libllvm_name, "libLLVM", "LLVM", "libLLVMSupport")
1✔
906
            hdl = Libdl.dlopen_e(lib_name)
2✔
907
            if hdl != C_NULL
1✔
908
                try
1✔
909
                    return f(hdl)
1✔
910
                finally
911
                    Libdl.dlclose(hdl)
1✔
912
                end
913
            end
914
        end
×
915
        error("Unable to open libLLVM!")
×
916
    end
917

918
    return open_libllvm() do hdl
1✔
919
        # Check for llvm::sys::getProcessTriple(), first without cxx11 tag:
920
        if Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleEv") != C_NULL
1✔
921
            return "cxx03"
×
922
        elseif Libdl.dlsym_e(hdl, "_ZN4llvm3sys16getProcessTripleB5cxx11Ev") != C_NULL
2✔
923
            return "cxx11"
1✔
924
        else
925
            @warn("Unable to find llvm::sys::getProcessTriple() in libLLVM!")
×
926
            return nothing
×
927
        end
928
    end
929
end
930

931
"""
932
    host_triplet()
933

934
Build host triplet out of `Sys.MACHINE` and various introspective utilities that
935
detect compiler ABI values such as `libgfortran_version`, `libstdcxx_version` and
936
`cxxstring_abi`.  We do this without using any `Platform` tech as it must run before
937
we have much of that built.
938
"""
939
function host_triplet()
87✔
940
    str = Base.BUILD_TRIPLET
×
941

942
    if !occursin("-libgfortran", str)
87✔
943
        libgfortran_version = detect_libgfortran_version()
×
944
        if libgfortran_version !== nothing
×
945
            str = string(str, "-libgfortran", libgfortran_version.major)
×
946
        end
947
    end
948

949
    if !occursin("-cxx", str)
87✔
950
        cxxstring_abi = detect_cxxstring_abi()
×
951
        if cxxstring_abi !== nothing
×
952
            str = string(str, "-", cxxstring_abi)
×
953
        end
954
    end
955

956
    if !occursin("-libstdcxx", str)
87✔
957
        libstdcxx_version = detect_libstdcxx_version()
87✔
958
        if libstdcxx_version !== nothing
87✔
959
            str = string(str, "-libstdcxx", libstdcxx_version.patch)
87✔
960
        end
961
    end
962

963
    # Add on julia_version extended tag
964
    if !occursin("-julia_version+", str)
87✔
965
        str = string(str, "-julia_version+", VersionNumber(VERSION.major, VERSION.minor, VERSION.patch))
87✔
966
    end
967
    return str
87✔
968
end
969

970
"""
971
    HostPlatform()
972

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

976
    HostPlatform(parse(Platform, Base.BinaryPlatforms.host_triplet()))
977
"""
978
function HostPlatform()
25✔
979
    return HostPlatform(parse(Platform, host_triplet()))::Platform
87✔
980
end
981

982
"""
983
    platforms_match(a::AbstractPlatform, b::AbstractPlatform)
984

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

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

997
The reserved tags `os_version` and `libstdcxx_version` use this mechanism to provide
998
bounded version constraints, where an artifact can specify that it was built using APIs
999
only available in macOS `v"10.11"` and later, or an artifact can state that it requires
1000
a libstdc++ that is at least `v"3.4.22"`, etc...
1001
"""
1002
function platforms_match(a::AbstractPlatform, b::AbstractPlatform)
526✔
1003
    for k in union(keys(tags(a)::Dict{String,String}), keys(tags(b)::Dict{String,String}))
1,052✔
1004
        ak = get(tags(a), k, nothing)
3,017✔
1005
        bk = get(tags(b), k, nothing)
3,484✔
1006

1007
        # Only continue if both `ak` and `bk` are not `nothing`
1008
        if ak === nothing || bk === nothing
3,017✔
1009
            continue
×
1010
        end
1011

1012
        a_comp = get_compare_strategy(a, k)
1,013✔
1013
        b_comp = get_compare_strategy(b, k)
1,003✔
1014

1015
        # Throw an error if `a` and `b` have both set non-default comparison strategies for `k`
1016
        # and they're not the same strategy.
1017
        if a_comp !== compare_default && b_comp !== compare_default && a_comp !== b_comp
993✔
1018
            throw(ArgumentError("Cannot compare Platform objects with two different non-default comparison strategies for the same key \"$(k)\""))
×
1019
        end
1020

1021
        # Select the custom comparator, if we have one.
1022
        comparator = a_comp
×
1023
        if b_comp !== compare_default
993✔
1024
            comparator = b_comp
×
1025
        end
1026

1027
        # Call the comparator, passing in which objects requested this comparison (one, the other, or both)
1028
        # For some comparators this doesn't matter, but for non-symmetrical comparisons, it does.
1029
        if !(comparator(ak, bk, a_comp === comparator, b_comp === comparator)::Bool)
993✔
1030
            return false
366✔
1031
        end
1032
    end
2,940✔
1033
    return true
160✔
1034
end
1035

1036
function platforms_match(a::String, b::AbstractPlatform)
27✔
1037
    return platforms_match(parse(Platform, a), b)
27✔
1038
end
1039
function platforms_match(a::AbstractPlatform, b::String)
27✔
1040
    return platforms_match(a, parse(Platform, b))
27✔
1041
end
1042
platforms_match(a::String, b::String) = platforms_match(parse(Platform, a), parse(Platform, b))
×
1043

1044
# Adapters for AbstractString backedge avoidance
1045
platforms_match(a::AbstractString, b::AbstractPlatform) = platforms_match(string(a)::String, b)
×
1046
platforms_match(a::AbstractPlatform, b::AbstractString) = platforms_match(a, string(b)::String)
×
1047
platforms_match(a::AbstractString, b::AbstractString) = platforms_match(string(a)::String, string(b)::String)
×
1048

1049

1050
"""
1051
    select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
1052

1053
Given a `download_info` dictionary mapping platforms to some value, choose
1054
the value whose key best matches `platform`, returning `nothing` if no matches
1055
can be found.
1056

1057
Platform attributes such as architecture, libc, calling ABI, etc... must all
1058
match exactly, however attributes such as compiler ABI can have wildcards
1059
within them such as `nothing` which matches any version of GCC.
1060
"""
1061
function select_platform(download_info::Dict, platform::AbstractPlatform = HostPlatform())
32✔
1062
    ps = collect(filter(p -> platforms_match(p, platform), keys(download_info)))
400✔
1063

1064
    if isempty(ps)
32✔
1065
        return nothing
4✔
1066
    end
1067

1068
    # At this point, we may have multiple possibilities.  We now engage a multi-
1069
    # stage selection algorithm, where we first sort the matches by how complete
1070
    # the match is, e.g. preferring matches where the intersection of tags is
1071
    # equal to the union of the tags:
1072
    function match_loss(a, b)
26✔
1073
        a_tags = Set(keys(tags(a)))
26✔
1074
        b_tags = Set(keys(tags(b)))
26✔
1075
        return length(union(a_tags, b_tags)) - length(intersect(a_tags, b_tags))
26✔
1076
    end
1077

1078
    # We prefer these better matches, and secondarily reverse-sort by triplet so
1079
    # as to generally choose the latest release (e.g. a `libgfortran5` tarball
1080
    # over a `libgfortran3` tarball).
1081
    sort!(ps, lt = (a, b) -> begin
41✔
1082
        loss_a = match_loss(a, platform)
13✔
1083
        loss_b = match_loss(b, platform)
13✔
1084
        if loss_a != loss_b
13✔
1085
            return loss_a < loss_b
9✔
1086
        end
1087
        return triplet(a) > triplet(b)
4✔
1088
    end)
1089

1090
    # @invokelatest here to not get invalidated by new defs of `==(::Function, ::Function)`
1091
    return @invokelatest getindex(download_info, first(ps))
28✔
1092
end
1093

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

1099
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