From b09e1d996f5b049c0d5398de5b4de0f858739867 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 17 Jul 2025 01:12:21 +0000 Subject: [PATCH] WIP/RFC: Add abi tags for ucrt/cxxlib This is a prepratory commit for reflecting windows/mingw32 libc information in our binary platforms, as well as adding the basic detection for ucrt in our Makefiles (so it doesn't accidentally try to use binaries with the wrong ABI). Of course there are policy choices to be made about what we want to ship and test. This does not make any of those policy choices, it simply aims to provide the capability to represent these ABIs in case we decide to add them later. To reflect the possibility of a libc++ difference (mingw comes in both libc++ and libstdc++ flavors these days and the same is true for linux of course, although less prominently so), this adds a new `cxxlib` abi tag that can be either `libstdcxx` or `libcxx` (perhaps `msvc` in the future, since clang does support that). There is also `cxxlib_version`. For compatibility the existing `libstdcxx_version` implies both `cxxlib=libstdcxx` as well as the corresponding versions. I'm not weeded to this representation, I just figured it was a reasonable first attempt. Fixes #59009 --- Make.inc | 7 +++++ base/binaryplatforms.jl | 45 +++++++++++++++++++++++++------ contrib/normalize_triplet.py | 11 +++++--- stdlib/Artifacts/test/runtests.jl | 15 ++++++----- test/binaryplatforms.jl | 9 ++++--- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/Make.inc b/Make.inc index a3543a5e276f0..0baac4c8d8da6 100644 --- a/Make.inc +++ b/Make.inc @@ -941,6 +941,13 @@ BUILD_MACHINE := $(shell $(HOSTCC) -dumpmachine) # don't recognize that, so canonicalize to mingw32 BUILD_MACHINE := $(subst windows-gnu,mingw32,$(BUILD_MACHINE)) +# Detect a request for ucrt libc +ifeq (,$(findstring MINGW,$(RAW_BUILD_OS))) +ifeq (UCRT64,$(MSYSTEM)) +BUILD_MACHINE := $(subst mingw32,ucrt-mingw32,$(BUILD_MACHINE)) +endif +endif + ifeq ($(ARCH),) override ARCH := $(shell $(CC) -dumpmachine | sed "s/\([^-]*\).*$$/\1/") else diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index c2f019c4d4eea..6c4c4cfb362e3 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -64,11 +64,22 @@ struct Platform <: AbstractPlatform continue end + # For compatibility, libstdcxx_version counts as both cxxlib=libstdcxx and + # cxxlib_version, but don't override an explicit existing cxxlib tag (the + # verifier will check for inconsistencies). + if tag == "libstdcxx_version" + haskey(tags, "cxxlib") || add_tag!(tags, "cxxlib", "libstdcxx") + tag = "cxxlib_version" + elseif tag == "cxxstring_abi" + # Implies cxxlib=libstdcxx++ for compatibility + haskey(tags, "cxxlib") || add_tag!(tags, "cxxlib", "libstdcxx") + end + # Normalize things that are known to be version numbers so that comparisons are easy. # Note that in our effort to be extremely compatible, we actually allow something that # doesn't parse nicely into a VersionNumber to persist, but if `validate_strict` is # set to `true`, it will cause an error later on. - if tag ∈ ("libgfortran_version", "libstdcxx_version", "os_version") + if tag ∈ ("libgfortran_version", "cxxlib_version", "os_version") if isa(value, VersionNumber) value = string(value) elseif isa(value, String) @@ -88,6 +99,10 @@ struct Platform <: AbstractPlatform # Default to `glibc` on Linux tags["libc"] = "glibc" end + if os == "windows" && !haskey(tags, libc) + # Default to `msvcrt` on Windows + tags["libc"] = "msvcrt" + end if os == "linux" && arch ∈ ("armv7l", "armv6l") && "call_abi" ∉ keys(tags) # default `call_abi` to `eabihf` on 32-bit ARM tags["call_abi"] = "eabihf" @@ -217,6 +232,10 @@ function validate_tags(tags::Dict) if tags["libc"] ∉ ("glibc", "musl") throw_libc_mismatch() end + elseif tags["os"] == "windows" + if tags["libc"] ∉ ("msvcrt", "ucrt") + throw_libc_mismatch() + end else # Nothing else is allowed to have a `libc` entry if haskey(tags, "libc") @@ -245,13 +264,13 @@ function validate_tags(tags::Dict) end # Validate `cxxstring_abi` is one of the two valid options: - if "cxxstring_abi" in keys(tags) && tags["cxxstring_abi"] ∉ ("cxx03", "cxx11") + if "cxxstring_abi" in keys(tags) && (tags["cxxstring_abi"] ∉ ("cxx03", "cxx11") || !haskey(tags, "cxxlib") || tags["cxxlib"] !== "libstdcxx") throw_invalid_key("cxxstring_abi") end # Validate `libstdcxx_version` is a parsable `VersionNumber` - if "libstdcxx_version" in keys(tags) && tryparse(VersionNumber, tags["libstdcxx_version"]) === nothing - throw_version_number("libstdcxx_version") + if "cxxlib_version" in keys(tags) && tryparse(VersionNumber, tags["cxxlib_version"]) === nothing + throw_version_number("cxxlib_version") end end @@ -331,8 +350,8 @@ function HostPlatform(p::AbstractPlatform) if haskey(p, "os_version") set_compare_strategy!(p, "os_version", compare_version_cap) end - if haskey(p, "libstdcxx_version") - set_compare_strategy!(p, "libstdcxx_version", compare_version_cap) + if haskey(p, "cxxlib") && p["cxxlib"] == "libstdcxx" && haskey(p, "cxxlib_version") + set_compare_strategy!(p, "cxxlib_version", compare_version_cap) end return p end @@ -535,6 +554,10 @@ function triplet(p::AbstractPlatform) if tag ∈ ("os", "arch", "libc", "call_abi", "libgfortran_version", "libstdcxx_version", "cxxstring_abi", "os_version") continue end + if tag == "cxxlib" && (cxxstring_abi(p) !== nothing || libstdcxx_version(p) !== nothing) + # Implied by above + continue + end str = string(str, "-", tag, "+", val) end return str @@ -551,7 +574,7 @@ function os_str(p::AbstractPlatform) return "-apple-darwin" end elseif os(p) == "windows" - return "-w64-mingw32" + return "-w64" elseif os(p) == "freebsd" osvn = os_version(p) if osvn !== nothing @@ -573,6 +596,10 @@ function libc_str(p::AbstractPlatform) return "" elseif lc === "glibc" return "-gnu" + elseif lc === "msvcrt" + return "-mingw32" + elseif lc === "ucrt" + return "-ucrt-mingw32" else return string("-", lc) end @@ -643,11 +670,13 @@ const os_mapping = Dict( "macos" => "-apple-darwin[\\d\\.]*", "freebsd" => "-(.*-)?freebsd[\\d\\.]*", "openbsd" => "-(.*-)?openbsd[\\d\\.]*", - "windows" => "-w64-mingw32", + "windows" => "-w64", "linux" => "-(.*-)?linux", ) const libc_mapping = Dict( "libc_nothing" => "", + "ucrt" => "-ucrt-mingw32", + "msvcrt" => "-mingw32", # We default to msvcrt for plain -mingw32 on Windows "glibc" => "-gnu", "musl" => "-musl", ) diff --git a/contrib/normalize_triplet.py b/contrib/normalize_triplet.py index 833b725480996..17fc5dab55657 100755 --- a/contrib/normalize_triplet.py +++ b/contrib/normalize_triplet.py @@ -21,13 +21,14 @@ 'darwin': "-apple-darwin[\\d\\.]*", 'freebsd': "-(.*-)?freebsd[\\d\\.]*", 'openbsd': "-(.*-)?openbsd[\\d\\.]*", - 'windows': "-w64-mingw32", + 'windows': "-w64", 'linux': "-(.*-)?linux", } libc_mapping = { 'blank_libc': "", 'gnu': "-gnu", 'musl': "-musl", + 'ucrt': "-ucrt-mingw32", } call_abi_mapping = { 'blank_call_abi': "", @@ -88,7 +89,10 @@ def r(x): x = x.replace("blank_call_abi", "") x = x.replace("blank_libgfortran", "") x = x.replace("blank_cxx_abi", "") - x = x.replace("blank_libc", "") + # We combine platform and libc below, since the windows mapping + # needs to know the platform for the correct default libc, so + # replace this one with `-` included. + x = x.replace("-blank_libc", "") return x def p(x): @@ -96,6 +100,7 @@ def p(x): # capture group names, unfortunately: os_remapping = { 'darwin': 'apple-darwin', + 'windows-ucrt': 'w64-ucrt-mingw32', 'windows': 'w64-mingw32', 'freebsd': 'unknown-freebsd', 'openbsd': 'unknown-openbsd', @@ -141,7 +146,7 @@ def p(x): "": "", }[sys.argv[3]] -print(arch+p(platform)+p(libc)+r(call_abi)+p(libgfortran_version)+p(cxx_abi)) +print(arch+p(platform+"-"+libc)+r(call_abi)+p(libgfortran_version)+p(cxx_abi)) # Testing suite: # triplets="i686-w64-mingw32 x86_64-pc-linux-musl arm-linux-musleabihf x86_64-linux-gnu arm-linux-gnueabihf x86_64-apple-darwin14 x86_64-unknown-freebsd11.1" diff --git a/stdlib/Artifacts/test/runtests.jl b/stdlib/Artifacts/test/runtests.jl index cb81c16347abf..5cdf6ed9ebd31 100644 --- a/stdlib/Artifacts/test/runtests.jl +++ b/stdlib/Artifacts/test/runtests.jl @@ -130,19 +130,20 @@ end # Next, fuzz it out! Ensure that we exactly reconstruct our platforms! platforms = Platform[] for libgfortran_version in (v"3", v"4", v"5", nothing), - libstdcxx_version in (v"3.4.11", v"3.4.19", nothing), + cxxlib in ("libstdcxx",) + cxxlib_version in (v"3.4.11", v"3.4.19", nothing), cxxstring_abi in ("cxx03", "cxx11", nothing) for arch in ("x86_64", "i686", "aarch64", "armv7l"), libc in ("glibc", "musl") - push!(platforms, Platform(arch, "linux"; libc, libgfortran_version, libstdcxx_version, cxxstring_abi)) + push!(platforms, Platform(arch, "linux"; libc, libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) end - push!(platforms, Platform("x86_64", "windows"; libgfortran_version, libstdcxx_version, cxxstring_abi)) - push!(platforms, Platform("i686", "windows"; libgfortran_version, libstdcxx_version, cxxstring_abi)) - push!(platforms, Platform("x86_64", "macOS"; libgfortran_version, libstdcxx_version, cxxstring_abi)) - push!(platforms, Platform("aarch64", "macOS"; libgfortran_version, libstdcxx_version, cxxstring_abi)) - push!(platforms, Platform("x86_64", "FreeBSD"; libgfortran_version, libstdcxx_version, cxxstring_abi)) + push!(platforms, Platform("x86_64", "windows"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) + push!(platforms, Platform("i686", "windows"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) + push!(platforms, Platform("x86_64", "macOS"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) + push!(platforms, Platform("aarch64", "macOS"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) + push!(platforms, Platform("x86_64", "FreeBSD"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi)) end for p in platforms diff --git a/test/binaryplatforms.jl b/test/binaryplatforms.jl index 8de522e9c6c8b..501abf5eec0a2 100644 --- a/test/binaryplatforms.jl +++ b/test/binaryplatforms.jl @@ -46,7 +46,7 @@ P(args...; kwargs...) = Platform(args...; validate_strict=true, kwargs...) @test_throws ArgumentError P("armv6l", "linux"; call_abi="kekeke") @test_throws ArgumentError P("armv6l", "linux"; libgfortran_version="lel") @test_throws ArgumentError P("x86_64", "linux"; cxxstring_abi="lel") - @test_throws ArgumentError P("x86_64", "windows"; libstdcxx_version="lel") + @test_throws ArgumentError P("x86_64", "windows"; cxxlib_version="lel") @test_throws ArgumentError P("i686", "macos") @test_throws ArgumentError P("x86_64", "macos"; libc="glibc") @test_throws ArgumentError P("x86_64", "macos"; call_abi="eabihf") @@ -183,7 +183,7 @@ end @test R("x86_64-linux-gnu-gcc4-cxx11") == P("x86_64", "linux"; libgfortran_version=v"3", cxxstring_abi="cxx11") @test R("x86_64-linux-gnu-cxx11") == P("x86_64", "linux"; cxxstring_abi="cxx11") @test R("x86_64-linux-gnu-libgfortran3-cxx03") == P("x86_64", "linux"; libgfortran_version=v"3", cxxstring_abi="cxx03") - @test R("x86_64-linux-gnu-libstdcxx26") == P("x86_64", "linux"; libstdcxx_version=v"3.4.26") + @test R("x86_64-linux-gnu-libstdcxx26") == P("x86_64", "linux"; cxxlib="libstdcxx", cxxlib_version=v"3.4.26") @test_throws ArgumentError R("totally FREEFORM text!!1!!!1!") @test_throws ArgumentError R("invalid-triplet-here") @@ -210,10 +210,11 @@ end # Just do a quick combinatorial sweep for completeness' sake for platform matching linux = P("x86_64", "linux") for libgfortran_version in (nothing, v"3", v"5"), - libstdcxx_version in (nothing, v"3.4.18", v"3.4.26"), + cxxlib in ("libstdcxx",), + cxxlib_version in (nothing, v"3.4.18", v"3.4.26"), cxxstring_abi in (nothing, :cxx03, :cxx11) - p = P("x86_64", "linux"; libgfortran_version, libstdcxx_version, cxxstring_abi) + p = P("x86_64", "linux"; libgfortran_version, cxxlib, cxxlib_version, cxxstring_abi) @test platforms_match(linux, p) @test platforms_match(p, linux)