diff --git a/build/fbcode_builder/getdeps/shared_lib.py b/build/fbcode_builder/getdeps/shared_lib.py index 9d3e5593b..33a6cf94f 100644 --- a/build/fbcode_builder/getdeps/shared_lib.py +++ b/build/fbcode_builder/getdeps/shared_lib.py @@ -12,6 +12,7 @@ from __future__ import annotations +import os import typing if typing.TYPE_CHECKING: @@ -36,14 +37,20 @@ def apply_shared_lib_top_level_cmake_defines( - On Linux: append -Wl,--exclude-libs=ALL to the shared/module linker flags so the deps' symbols stay out of the produced .so's dynamic export table (avoids ODR clashes if the host process loads another - copy of any dep). + copy of any dep). Defining CMAKE_*_LINKER_FLAGS on the command line + stops CMake from seeding them from $LDFLAGS, so start from $LDFLAGS + (as CMake would) to keep the caller's linker flags, e.g. a distro's + hardening flags, on the shared library links. """ if not build_opts.shared_lib: return defines.setdefault("BUILD_SHARED_LIBS", "ON") if build_opts.is_linux(): + ldflags = os.environ.get("LDFLAGS", "") for var in ("CMAKE_SHARED_LINKER_FLAGS", "CMAKE_MODULE_LINKER_FLAGS"): - defines[var] = _append_token(defines.get(var, ""), "-Wl,--exclude-libs=ALL") + defines[var] = _append_token( + defines.get(var, ldflags), "-Wl,--exclude-libs=ALL" + ) def apply_shared_lib_dep_env( diff --git a/build/fbcode_builder/getdeps/test/shared_lib_test.py b/build/fbcode_builder/getdeps/test/shared_lib_test.py index ef81943b7..785389436 100644 --- a/build/fbcode_builder/getdeps/test/shared_lib_test.py +++ b/build/fbcode_builder/getdeps/test/shared_lib_test.py @@ -4,8 +4,9 @@ # LICENSE file in the root directory of this source tree. +import os import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from ..envfuncs import Env from ..shared_lib import ( @@ -79,6 +80,31 @@ def test_idempotent(self) -> None: apply_shared_lib_top_level_cmake_defines(defines, opts) self.assertEqual(defines["CMAKE_SHARED_LINKER_FLAGS"], "-Wl,--exclude-libs=ALL") + def test_env_ldflags_seed_the_linker_flags(self) -> None: + defines = {} + with patch.dict(os.environ, {"LDFLAGS": "-Wl,-z,relro -Wl,-z,now"}): + apply_shared_lib_top_level_cmake_defines( + defines, make_build_opts(is_linux=True) + ) + for var in ("CMAKE_SHARED_LINKER_FLAGS", "CMAKE_MODULE_LINKER_FLAGS"): + self.assertEqual( + defines[var], "-Wl,-z,relro -Wl,-z,now -Wl,--exclude-libs=ALL" + ) + + def test_explicit_linker_flags_win_over_env_ldflags(self) -> None: + defines = {"CMAKE_SHARED_LINKER_FLAGS": "-Wl,--as-needed"} + with patch.dict(os.environ, {"LDFLAGS": "-Wl,-z,now"}): + apply_shared_lib_top_level_cmake_defines( + defines, make_build_opts(is_linux=True) + ) + self.assertEqual( + defines["CMAKE_SHARED_LINKER_FLAGS"], + "-Wl,--as-needed -Wl,--exclude-libs=ALL", + ) + self.assertEqual( + defines["CMAKE_MODULE_LINKER_FLAGS"], "-Wl,-z,now -Wl,--exclude-libs=ALL" + ) + class ApplyDepEnvTest(unittest.TestCase): def test_no_op_when_shared_lib_off(self) -> None: