diff --git a/docs/src/pythoncall.md b/docs/src/pythoncall.md index 2ffd3bc1..ec9a0a8a 100644 --- a/docs/src/pythoncall.md +++ b/docs/src/pythoncall.md @@ -285,6 +285,23 @@ into it. If you want to use a pre-existing Conda environment, see the previous s If `conda`, `mamba` or `micromamba` is not in your `PATH` you will also need to set `JULIA_CONDAPKG_EXE` to its path. +#### If you installed a newer version of libstdc++ +PythonCall injects a dependency to bound the allowed versions of the `libstdcxx-ng` +Conda package. It finds the bound by runtime discovery of the libstdc++ version. To +override this value, use: + +```julia +[PythonCall] +ENV["JULIA_PYTHONCALL_LIBSTDCXX_VERSION_BOUND"] = ">=3.4,<=12" +``` + +To figure out installed version, run +```bash +strings /path/to/julia/lib/julia/libstdc++.so.6 | grep GLIBCXX +``` +Then look at +for the GCC version compatible with the GLIBCXX version. + ## [Installing Python packages](@id python-deps) Assuming you haven't [opted out](@ref pythoncall-config), PythonCall uses diff --git a/src/cpython/context.jl b/src/cpython/context.jl index 176fba12..cb3851e3 100644 --- a/src/cpython/context.jl +++ b/src/cpython/context.jl @@ -30,6 +30,54 @@ function _atpyexit() return end +# By default, ensure libstdc++ in the Conda environment is compatible with +# the one linked in Julia. This is platform/version dependent, so needs to +# occur at runtime. +# +# Allow the user to override the default. This is useful when the version +# of libstdcxx linked in Julia is customized in the local installation of +# Julia. +# +# To figure out cxx_version for a given Julia version, run +# strings /path/to/julia/lib/julia/libstdc++.so.6 | grep GLIBCXX +# then look at +# https://gcc.gnu.org/onlinedocs/gcc-12.1.0/libstdc++/manual/manual/abi.html +# for the highest GCC version compatible with the highest GLIBCXX version. +function get_libstdcxx_version_bound() + # This list comes from: https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + # Start with GCC 4.8, as it's extremely difficult to build Julia with anything older + vers_mapping = Dict( + 18 => v"4.8.0", + 19 => v"4.8.3", + 20 => v"4.9.0", + 21 => v"5.1.0", + 22 => v"6.1.0", + 23 => v"7.1.0", + 24 => v"7.2.0", + 25 => v"8.1.0", + 26 => v"9.1.0", + 27 => v"9.2.0", + 28 => v"9.5.0", + 29 => v"11.3.0", + 30 => v"12.2.0", + 31 => v"13.1.0", + ) + # Get the libstdcxx version that is currently loaded in this Julia process + loaded_libstdcxx_version = Base.BinaryPlatforms.detect_libstdcxx_version() + + if loaded_libstdcxx_version !== nothing + # Map it through to get a GCC version; if the version is unknown, we simply return + # the highest GCC version we know about, which should be a fairly safe choice. + max_version = get(vers_mapping, loaded_libstdcxx_version.patch, vers_mapping[maximum(keys(vers_mapping))]) + return get(ENV, "JULIA_PYTHONCALL_LIBSTDCXX_VERSION_BOUND", ">=3.4,<=$(max_version.major).$(max_version.minor)") + elseif haskey(ENV, "JULIA_PYTHONCALL_LIBSTDCXX_VERSION_BOUND") + return ENV["JULIA_PYTHONCALL_LIBSTDCXX_VERSION_BOUND"] + else + # Julia does not link against any version of libstdc++ known to Julia (e.g. using clang instead, or something not in the 3.4.x series) + return nothing + end +end + function init_context() CTX.is_embedded = haskey(ENV, "JULIA_PYTHONCALL_LIBPTR") @@ -60,24 +108,12 @@ function init_context() exe_path::String else if Sys.islinux() - # Ensure libstdc++ in the Conda environment is compatible with the one - # linked in Julia. This is platform/version dependent, so needs to occur at - # runtime. - # - # To figure out cxx_version for a given Julia version, run - # strings /path/to/julia/lib/julia/libstdc++.so.6 | grep GLIBCXX - # then look at - # https://gcc.gnu.org/onlinedocs/gcc-12.1.0/libstdc++/manual/manual/abi.html - # for the highest GCC version compatible with the highest GLIBCXX version. - if Base.VERSION <= v"1.6.2" - # GLIBCXX_3.4.26 - cxx_version = ">=3.4,<9.2" - else - # GLIBCXX_3.4.29 - # checked up to v1.8.0 - cxx_version = ">=3.4,<11.4" + cxx_version = get_libstdcxx_version_bound() + if cxx_version !== nothing + CondaPkg.add("libstdcxx-ng", version=cxx_version, channel="conda-forge", temp=true, file=joinpath(@__DIR__, "..", "..", "CondaPkg.toml"), resolve=false) end - CondaPkg.add("libstdcxx-ng", version=cxx_version, channel="conda-forge", temp=true, file=joinpath(@__DIR__, "..", "..", "CondaPkg.toml"), resolve=false) + # if cxx_version is nothing, then we assume that Julia does not link against any version ob libstdc++ known by Julia, and so we do not + # enforce a version bound. end # By default, we use Python installed by CondaPkg. exe_path = Sys.iswindows() ? joinpath(CondaPkg.envdir(), "python.exe") : joinpath(CondaPkg.envdir(), "bin", "python") diff --git a/test/context.jl b/test/context.jl new file mode 100644 index 00000000..7bc9c44f --- /dev/null +++ b/test/context.jl @@ -0,0 +1,6 @@ +@testitem "libstdc++ version" begin + ENV["JULIA_PYTHONCALL_LIBSTDCXX_VERSION_BOUND"] = ">=3.4,<=12" + + cxxversion = PythonCall.C.get_libstdcxx_version_bound() + @test cxxversion == ">=3.4,<=12" +end \ No newline at end of file