Skip to content

Commit c12e784

Browse files
giordanostaticfloat
authored andcommitted
Setup dependencies that match the GCC ABI of the compiler (#604)
* Add `shards` as keyword argument to the runners * Add functions to query preferred ABIs for the given compiler shard * Setup dependencies that match the GCC ABI of the compiler * Move here definition of Pkg.BinaryPlatforms.gcc_versions * Define custom structs for compilers builds * Simplify logic of selection of GCC builds * Changes suggested by Elliot * Make sure the compiler wrappers have the requested C++ string ABI
1 parent f4ba291 commit c12e784

File tree

9 files changed

+307
-33
lines changed

9 files changed

+307
-33
lines changed

src/AutoBuild.jl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,25 +629,41 @@ function autobuild(dir::AbstractString,
629629
build_path = joinpath(dir, "build", triplet(platform))
630630
mkpath(build_path)
631631

632+
shards = choose_shards(platform; extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:bootstrap_list,:compilers))...)
633+
# We want to get dependencies that have exactly the same GCC ABI as the
634+
# choosen compiler, otherwise we risk, e.g., to build in an environment
635+
# with libgfortran3 a dependency built with libgfortran5.
636+
# `concrete_platform` is needed only to setup the dependencies and the
637+
# runner. We _don't_ want the platform passed to `audit()` or
638+
# `package()` to be more specific than it is.
639+
concrete_platform = platform
640+
gccboostrap_shard_idx = findfirst(x -> x.name == "GCCBootstrap" && x.target.arch == platform.arch && x.target.libc == platform.libc, shards)
641+
if !isnothing(gccboostrap_shard_idx)
642+
libgfortran_version = preferred_libgfortran_version(platform, shards[gccboostrap_shard_idx])
643+
cxxstring_abi = preferred_cxxstring_abi(platform, shards[gccboostrap_shard_idx])
644+
concrete_platform = replace_cxxstring_abi(replace_libgfortran_version(platform, libgfortran_version), cxxstring_abi)
645+
end
646+
632647
prefix = setup_workspace(
633648
build_path,
634649
src_paths,
635650
src_hashes;
636651
verbose=verbose,
637652
)
638-
artifact_paths = setup_dependencies(prefix, dependencies, platform)
653+
artifact_paths = setup_dependencies(prefix, dependencies, concrete_platform)
639654

640655
# Create a runner to work inside this workspace with the nonce built-in
641656
ur = preferred_runner()(
642657
prefix.path;
643658
cwd = "/workspace/srcdir",
644-
platform = platform,
659+
platform = concrete_platform,
645660
verbose = verbose,
646661
workspaces = [
647662
joinpath(prefix, "metadir") => "/meta",
648663
],
649664
compiler_wrapper_dir = joinpath(prefix, "compiler_wrappers"),
650665
src_name = src_name,
666+
shards = shards,
651667
extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:compilers,:allow_unsafe_flags))...,
652668
)
653669

src/DockerRunner.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ function DockerRunner(workspace_root::String;
7272
verbose::Bool = false,
7373
compiler_wrapper_path::String = mktempdir(),
7474
src_name::AbstractString = "",
75+
shards = nothing,
7576
kwargs...)
7677
global use_ccache
7778

@@ -97,8 +98,10 @@ function DockerRunner(workspace_root::String;
9798
push!(workspaces, "binarybuilder_ccache" => "/root/.ccache")
9899
end
99100

100-
# Choose the shards we're going to mount
101-
shards = choose_shards(platform; extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:bootstrap_list,:compilers))...)
101+
if isnothing(shards)
102+
# Choose the shards we're going to mount
103+
shards = choose_shards(platform; extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:bootstrap_list,:compilers))...)
104+
end
102105

103106
# Import docker image
104107
import_docker_image(shards[1], workspace_root; verbose=verbose)

src/Rootfs.jl

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,78 @@ function select_closest_version(preferred::VersionNumber, versions::Vector{Versi
319319
return versions[closest_idx]
320320
end
321321

322-
const available_gcc_builds = [v"4.8.5", v"5.2.0", v"6.1.0", v"7.1.0", v"8.1.0", v"9.1.0"]
323-
const available_llvm_builds = [v"6.0.1", v"7.1.0", v"8.0.1", v"9.0.1"]
322+
abstract type CompilerBuild end
323+
324+
struct GCCBuild <: CompilerBuild
325+
version::VersionNumber
326+
abi::CompilerABI
327+
end
328+
GCCBuild(v::VersionNumber) = GCCBuild(v, CompilerABI())
329+
330+
struct LLVMBuild <: CompilerBuild
331+
version::VersionNumber
332+
abi::CompilerABI
333+
end
334+
LLVMBuild(v::VersionNumber) = LLVMBuild(v, CompilerABI())
335+
336+
getversion(c::CompilerBuild) = c.version
337+
getabi(c::CompilerBuild) = c.abi
338+
339+
const available_gcc_builds = [GCCBuild(v"4.8.5", CompilerABI(libgfortran_version = v"3", libstdcxx_version = v"3.4.19", cxxstring_abi = :cxx03)),
340+
GCCBuild(v"5.2.0", CompilerABI(libgfortran_version = v"3", libstdcxx_version = v"3.4.21", cxxstring_abi = :cxx11)),
341+
GCCBuild(v"6.1.0", CompilerABI(libgfortran_version = v"3", libstdcxx_version = v"3.4.22", cxxstring_abi = :cxx11)),
342+
GCCBuild(v"7.1.0", CompilerABI(libgfortran_version = v"4", libstdcxx_version = v"3.4.23", cxxstring_abi = :cxx11)),
343+
GCCBuild(v"8.1.0", CompilerABI(libgfortran_version = v"5", libstdcxx_version = v"3.4.25", cxxstring_abi = :cxx11)),
344+
GCCBuild(v"9.1.0", CompilerABI(libgfortran_version = v"5", libstdcxx_version = v"3.4.26", cxxstring_abi = :cxx11))]
345+
const available_llvm_builds = [LLVMBuild(v"6.0.1"),
346+
LLVMBuild(v"7.1.0"),
347+
LLVMBuild(v"8.0.1"),
348+
LLVMBuild(v"9.0.1")]
349+
350+
"""
351+
gcc_version(cabi::CompilerABI, GCC_builds::Vector{GCCBuild})
352+
353+
Returns the closest matching GCC version number for the given CompilerABI
354+
representing a particular platofrm, from the given set of options. If no match
355+
is found, returns an empty list. This method assumes that `cabi` represents a
356+
platform that binaries will be run on, and thus versions are always rounded
357+
down; e.g. if the platform supports a `libstdc++` version that corresponds to
358+
`GCC 5.1.0`, but the only GCC versions available to be picked from are `4.8.5`
359+
and `5.2.0`, it will return `4.8.5`, as binaries compiled with that version
360+
will run on this platform, whereas binaries compiled with `5.2.0` may not.
361+
"""
362+
function gcc_version(cabi::CompilerABI, GCC_builds::Vector{GCCBuild})
363+
# First, filter by libgfortran version.
364+
if cabi.libgfortran_version !== nothing
365+
GCC_builds = filter(b -> getabi(b).libgfortran_version == cabi.libgfortran_version, GCC_builds)
366+
end
367+
368+
# Next, filter by libstdc++ GLIBCXX symbol version. Note that this
369+
# mapping is conservative; it is often the case that we return a
370+
# version that is slightly lower than what is actually installed on
371+
# a system. See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
372+
# for the whole list, as well as many other interesting factoids.
373+
if cabi.libstdcxx_version !== nothing
374+
GCC_builds = filter(b -> getabi(b).libstdcxx_version <= cabi.libstdcxx_version, GCC_builds)
375+
end
376+
377+
# Finally, enforce cxxstring_abi guidelines. It is possible to build
378+
# :cxx03 binaries on GCC 5+, (although increasingly rare) so the only
379+
# filtering we do is that if the platform is explicitly :cxx11, we
380+
# disallow running on < GCC 5.
381+
if cabi.cxxstring_abi !== nothing && cabi.cxxstring_abi === :cxx11
382+
GCC_builds = filter(b -> getversion(b) >= v"5", GCC_builds)
383+
end
384+
385+
return getversion.(GCC_builds)
386+
end
324387

325388
function select_gcc_version(p::Platform,
326-
GCC_builds::Vector{VersionNumber} = available_gcc_builds,
327-
preferred_gcc_version::VersionNumber = GCC_builds[1],
389+
GCC_builds::Vector{GCCBuild} = available_gcc_builds,
390+
preferred_gcc_version::VersionNumber = getversion(GCC_builds[1]),
328391
)
329392
# Determine which GCC build we're going to match with this CompilerABI:
330-
GCC_builds = Pkg.BinaryPlatforms.gcc_version(compiler_abi(p), GCC_builds)
393+
GCC_builds = gcc_version(compiler_abi(p), GCC_builds)
331394

332395
if isempty(GCC_builds)
333396
error("Impossible CompilerABI constraints $(cabi)!")
@@ -350,19 +413,22 @@ function choose_shards(p::Platform;
350413
compilers::Vector{Symbol} = [:c],
351414
rootfs_build::VersionNumber=v"2020.01.07",
352415
ps_build::VersionNumber=v"2020.01.15",
353-
GCC_builds::Vector{VersionNumber}=available_gcc_builds,
354-
LLVM_builds::Vector{VersionNumber}=available_llvm_builds,
416+
GCC_builds::Vector{GCCBuild}=available_gcc_builds,
417+
LLVM_builds::Vector{LLVMBuild}=available_llvm_builds,
355418
Rust_build::VersionNumber=v"1.18.3",
356419
Go_build::VersionNumber=v"1.13",
357420
archive_type::Symbol = (use_squashfs ? :squashfs : :unpacked),
358421
bootstrap_list::Vector{Symbol} = bootstrap_list,
359-
# We prefer the oldest GCC version by default
360-
preferred_gcc_version::VersionNumber = GCC_builds[1],
361-
preferred_llvm_version::VersionNumber = LLVM_builds[end],
422+
# Because GCC has lots of compatibility issues, we always default to
423+
# the earliest version possible.
424+
preferred_gcc_version::VersionNumber = getversion(GCC_builds[1]),
425+
# Because LLVM doesn't have compatibilty issues, we always default
426+
# to the newest version possible.
427+
preferred_llvm_version::VersionNumber = getversion(LLVM_builds[end]),
362428
)
363429

364430
GCC_build = select_gcc_version(p, GCC_builds, preferred_gcc_version)
365-
LLVM_build = select_closest_version(preferred_llvm_version, LLVM_builds)
431+
LLVM_build = select_closest_version(preferred_llvm_version, getversion.(LLVM_builds))
366432
# Our host platform is x86_64-linux-musl
367433
host_platform = Linux(:x86_64; libc=:musl)
368434

@@ -531,6 +597,58 @@ function expand_cxxstring_abis(ps::Vector{<:Platform})
531597
return Platform[p for p in Iterators.flatten(expand_cxxstring_abis.(ps))]
532598
end
533599

600+
"""
601+
preferred_libgfortran_version(platform::Platform, shard::CompilerShard)
602+
603+
Return the libgfortran version preferred by the given platform or GCCBootstrap shard.
604+
"""
605+
function preferred_libgfortran_version(platform::Platform, shard::CompilerShard)
606+
# Some input validation
607+
if shard.name != "GCCBootstrap"
608+
error("Shard must be `GCCBootstrap`")
609+
end
610+
if shard.target.arch != platform.arch || shard.target.libc != platform.libc
611+
error("Incompatible platform and shard target")
612+
end
613+
614+
if compiler_abi(platform).libgfortran_version != nothing
615+
# Here we can't use `shard.target` because the shard always has the
616+
# target as ABI-agnostic, thus we have also to ask for the platform.
617+
return compiler_abi(platform).libgfortran_version
618+
elseif shard.version < v"7"
619+
return v"3"
620+
elseif v"7" <= shard.version < v"8"
621+
return v"4"
622+
else
623+
return v"5"
624+
end
625+
end
626+
627+
"""
628+
preferred_cxxstring_abi(platform::Platform, shard::CompilerShard)
629+
630+
Return the C++ string ABI preferred by the given platform or GCCBootstrap shard.
631+
"""
632+
function preferred_cxxstring_abi(platform::Platform, shard::CompilerShard)
633+
# Some input validation
634+
if shard.name != "GCCBootstrap"
635+
error("Shard must be `GCCBootstrap`")
636+
end
637+
if shard.target.arch != platform.arch || shard.target.libc != platform.libc
638+
error("Incompatible platform and shard target")
639+
end
640+
641+
if compiler_abi(platform).cxxstring_abi != nothing
642+
# Here we can't use `shard.target` because the shard always has the
643+
# target as ABI-agnostic, thus we have also to ask for the platform.
644+
return compiler_abi(platform).cxxstring_abi
645+
elseif shard.version < v"5"
646+
return :cxx03
647+
else
648+
return :cxx11
649+
end
650+
end
651+
534652
"""
535653
download_all_shards(; verbose::Bool=false)
536654

src/Runner.jl

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ exeext(p::Union{Linux,FreeBSD,MacOS}) = ""
3333
exeext(p::Platform) = error("Unknown exeext for platform $(p)")
3434

3535
"""
36-
generate_compiler_wrappers(p::Platform, bin_path::AbstractString)
36+
generate_compiler_wrappers!(platform::Platform; bin_path::AbstractString,
37+
host_platform::Platform = Linux(:x86_64; libc=:musl),
38+
rust_platform::Platform = Linux(:x86_64; libc=:glibc),
39+
compilers::Vector{Symbol} = [:c],
40+
allow_unsafe_flags::Bool = false)
3741
3842
We generate a set of compiler wrapper scripts within our build environment to force all
3943
build systems to honor the necessary sets of compiler flags to build for our systems.
@@ -61,6 +65,32 @@ function generate_compiler_wrappers!(platform::Platform; bin_path::AbstractStrin
6165
host_target = aatriplet(host_platform)
6266
rust_target = aatriplet(rust_platform)
6367

68+
69+
# If the ABI-agnostic triplets of the target and the host platform are the
70+
# same, then we have to be very careful: we can't have distinct wrappers, so
71+
# we have to be sure that their ABIs are consistent, in particular that
72+
# we're correctly writing the wrappers for the target platform. In
73+
# particular, what we care about with regard to the wrappers is the C++
74+
# string ABI. We have the following situations:
75+
# * they are equal: this is fine
76+
# * they're different and the host has a prefernce for the C++ string ABI:
77+
# we can't deal with this situation as the host wrappers will be always
78+
# overwritten, then error out
79+
# * in all other cases we don't do anything here but later below we'll let
80+
# the host wrappers to be overwritten by the wrappers for the target
81+
if target == host_target
82+
target_cxxabi = compiler_abi(platform).cxxstring_abi
83+
host_cxxabi = compiler_abi(host_platform).cxxstring_abi
84+
if target_cxxabi !== host_cxxabi
85+
if host_cxxabi !== nothing
86+
# This is a very unlikely situation as ideally the host
87+
# shouldn't have particular preferences for the ABI, thus in
88+
# practice we shouldn't never reach this.
89+
error("Incompatible C++ string ABIs between the host and target platforms")
90+
end
91+
end
92+
end
93+
6494
# If we should use ccache, prepend this to every compiler invocation
6595
ccache = use_ccache ? "ccache" : ""
6696

@@ -73,7 +103,7 @@ function generate_compiler_wrappers!(platform::Platform; bin_path::AbstractStrin
73103
unsafe_flags = String[])
74104
write(io, """
75105
#!/bin/bash
76-
# This compiler wrapper script brought into existence by `generate_compiler_wrappers()`
106+
# This compiler wrapper script brought into existence by `generate_compiler_wrappers!()`
77107
78108
if [ "x\${SUPER_VERBOSE}" = "x" ]; then
79109
vrun() { "\$@"; }
@@ -308,8 +338,11 @@ function generate_compiler_wrappers!(platform::Platform; bin_path::AbstractStrin
308338
chmod(joinpath(bin_path, fname), 0o775)
309339
end
310340

311-
## Generate compiler wrappers for both our target and our host
312-
for p in unique((platform, host_platform))
341+
## Generate compiler wrappers for both our host and our target. In
342+
## particular, we write the wrapper for the target after those for the host,
343+
## to override host-specific ABI in case this is incompatible with that of
344+
## the target
345+
for p in unique((host_platform, platform))
313346
t = aatriplet(p)
314347

315348
# Generate `:c` compilers

src/UserNSRunner.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function UserNSRunner(workspace_root::String;
2727
verbose::Bool = false,
2828
compiler_wrapper_path::String = mktempdir(),
2929
src_name::AbstractString = "",
30+
shards = nothing,
3031
kwargs...)
3132
global use_ccache, use_squashfs, runner_override
3233

@@ -55,8 +56,10 @@ function UserNSRunner(workspace_root::String;
5556
push!(workspaces, ccache_dir() => "/root/.ccache")
5657
end
5758

58-
# Choose the shards we're going to mount
59-
shards = choose_shards(platform; extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:bootstrap_list,:compilers))...)
59+
if isnothing(shards)
60+
# Choose the shards we're going to mount
61+
shards = choose_shards(platform; extract_kwargs(kwargs, (:preferred_gcc_version,:preferred_llvm_version,:bootstrap_list,:compilers))...)
62+
end
6063

6164
# Construct sandbox command to look at the location it'll be mounted under
6265
mpath = mount_path(shards[1], workspace_root)

src/wizard/deploy.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ function print_build_tarballs(io::IO, state::WizardState)
4343
push!(kwargs_vec, "compilers = [$(join(map(x -> ":$(x)", state.compilers), ", "))]")
4444
end
4545
# Default GCC version is the oldest one
46-
if state.preferred_gcc_version != available_gcc_builds[1]
46+
if state.preferred_gcc_version != getversion(available_gcc_builds[1])
4747
push!(kwargs_vec, "preferred_gcc_version = v\"$(state.preferred_gcc_version)\"")
4848
end
4949
# Default LLVM version is the latest one
50-
if state.preferred_llvm_version != available_llvm_builds[end]
50+
if state.preferred_llvm_version != getversion(available_llvm_builds[end])
5151
push!(kwargs_vec, "preferred_llvm_version = v\"$(state.preferred_llvm_version)\"")
5252
end
5353
kwargs = ""

src/wizard/obtain_source.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,13 +404,13 @@ function step2(state::WizardState)
404404
get_name_and_version(state)
405405
if yn_prompt(state, "Do you want to customize the set of compilers?", :n) == :y
406406
get_compilers(state)
407-
get_preferred_version(state, "GCC", available_gcc_builds)
408-
get_preferred_version(state, "LLVM", available_llvm_builds)
407+
get_preferred_version(state, "GCC", getversion.(available_gcc_builds))
408+
get_preferred_version(state, "LLVM", getversion.(available_llvm_builds))
409409
else
410410
state.compilers = [:c]
411411
# Default GCC version is the oldest one
412-
state.preferred_gcc_version = available_gcc_builds[1]
412+
state.preferred_gcc_version = getversion(available_gcc_builds[1])
413413
# Default LLVM version is the latest one
414-
state.preferred_llvm_version = available_llvm_builds[end]
414+
state.preferred_llvm_version = getversion(available_llvm_builds[end])
415415
end
416416
end

0 commit comments

Comments
 (0)