Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

boost: be more strict about boost libraries #3999

Merged
merged 9 commits into from
Jan 2, 2021
106 changes: 75 additions & 31 deletions recipes/boost/all/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class BoostConan(ConanFile):
"namespace": "ANY", # custom boost namespace for bcp, e.g. myboost
"namespace_alias": [True, False], # enable namespace alias for bcp, boost=myboost
"multithreading": [True, False], # enables multithreading support
"numa": [True, False],
"zlib": [True, False],
"bzip2": [True, False],
"lzma": [True, False],
Expand Down Expand Up @@ -111,6 +112,7 @@ class BoostConan(ConanFile):
"namespace": "boost",
"namespace_alias": False,
"multithreading": True,
"numa": True,
"zlib": True,
"bzip2": True,
"lzma": False,
Expand Down Expand Up @@ -233,6 +235,10 @@ def config_options(self):
if dep_name not in self._configure_options:
delattr(self.options, "without_{}".format(dep_name))

if self.settings.compiler == "Visual Studio":
# Shared builds of numa do not link on Visual Studio due to missing symbols
self.options.numa = False

@property
def _configure_options(self):
return self._dependencies["configure_options"]
Expand All @@ -256,6 +262,9 @@ def configure(self):
if self.settings.compiler == "Visual Studio" and "MT" in str(self.settings.compiler.runtime) and self.options.shared:
raise ConanInvalidConfiguration("Boost can not be built as shared library with MT runtime.")

if self.settings.compiler == "Visual Studio" and self.options.shared and self.options.numa:
raise ConanInvalidConfiguration("Cannot build a shared boost with numa support on Visual Studio")
Comment on lines +265 to +266
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prince-chrismc
The limitation to shared is intentional.
dll's don't allow missing symbols, static libraries allow it.
This means a static numa library can be built, but not a shared library.

I said: it can be built. I didn't say that it will work 😄


# Check, when a boost module is enabled, whether the boost modules it depends on are enabled as well.
for mod_name, mod_deps in self._dependencies["dependencies"].items():
if not self.options.get_safe("without_{}".format(mod_name), True):
Expand Down Expand Up @@ -306,6 +315,9 @@ def configure(self):
if self.options.layout == "b2-default":
self.options.layout = "versioned" if self.settings.os == "Windows" else "system"

if self.options.without_fiber:
del self.options.numa

def build_requirements(self):
if not self.options.header_only:
self.build_requires("b2/4.2.0")
Expand Down Expand Up @@ -735,6 +747,9 @@ def _build_flags(self):
# Stop at the first error. No need to continue building.
flags.append("-q")

if self.options.get_safe("numa"):
flags.append("numa=on")

# https://www.boost.org/doc/libs/1_70_0/libs/context/doc/html/context/architectures.html
if self._b2_os:
flags.append("target-os=%s" % self._b2_os)
Expand Down Expand Up @@ -1133,6 +1148,7 @@ def package_info(self):
self.cpp_info.components["headers"].libs = []
self.cpp_info.components["headers"].names["cmake_find_package"] = "headers"
self.cpp_info.components["headers"].names["cmake_find_package_multi"] = "headers"
self.cpp_info.components["headers"].names["pkg_config"] = "boost"

if self.options.system_no_deprecated:
self.cpp_info.components["headers"].defines.append("BOOST_SYSTEM_NO_DEPRECATED")
Expand Down Expand Up @@ -1165,13 +1181,15 @@ def package_info(self):
self.cpp_info.components["diagnostic_definitions"].libs = []
self.cpp_info.components["diagnostic_definitions"].names["cmake_find_package"] = "diagnostic_definitions"
self.cpp_info.components["diagnostic_definitions"].names["cmake_find_package_multi"] = "diagnostic_definitions"
self.cpp_info.components["diagnostic_definitions"].names["pkg_config"] = "boost_diagnostic_definitions" # FIXME: disable on pkg_config
self.cpp_info.components["_libboost"].requires.append("diagnostic_definitions")
if self.options.diagnostic_definitions:
self.cpp_info.components["diagnostic_definitions"].defines = ["BOOST_LIB_DIAGNOSTIC"]

self.cpp_info.components["disable_autolinking"].libs = []
self.cpp_info.components["disable_autolinking"].names["cmake_find_package"] = "disable_autolinking"
self.cpp_info.components["disable_autolinking"].names["cmake_find_package_multi"] = "disable_autolinking"
self.cpp_info.components["disable_autolinking"].names["pkg_config"] = "boost_disable_autolinking" # FIXME: disable on pkg_config
self.cpp_info.components["_libboost"].requires.append("disable_autolinking")
if self._is_msvc or self._is_clang_cl:
if self.options.magic_autolink:
Expand All @@ -1188,6 +1206,7 @@ def package_info(self):
self.cpp_info.components["dynamic_linking"].libs = []
self.cpp_info.components["dynamic_linking"].names["cmake_find_package"] = "dynamic_linking"
self.cpp_info.components["dynamic_linking"].names["cmake_find_package_multi"] = "dynamic_linking"
self.cpp_info.components["dynamic_linking"].names["pkg_config"] = "boost_dynamic_linking" # FIXME: disable on pkg_config
self.cpp_info.components["_libboost"].requires.append("dynamic_linking")
if self.options.shared:
# A Boost::dynamic_linking cmake target does only make sense for a shared boost package
Expand Down Expand Up @@ -1231,7 +1250,8 @@ def package_info(self):
else:
libsuffix_data["version"] = "-{}_{}_{}".format(version.major, version.minor, version.patch)
libsuffix = libsuffix_lut[str(self.options.layout)].format(**libsuffix_data)
self.output.info("Library layout suffix: {}".format(repr(libsuffix)))
if libsuffix:
self.output.info("Library layout suffix: {}".format(repr(libsuffix)))

libformatdata = {}
if not self.options.without_python:
Expand All @@ -1246,45 +1266,69 @@ def add_libprefix(n):
libprefix = "lib"
return libprefix + n

modules_seen = set()
detected_libraries = set(tools.collect_libs(self))
used_libraries = set()
all_detected_libraries = set(tools.collect_libs(self))
all_expected_libraries = set()
incomplete_components = list()

def filter_transform_module_libraries(names):
libs = []
for name in names:
if name in ("boost_stacktrace_windbg", "boost_stacktrace_windbg_cached") and self.settings.os != "Windows":
continue
if name in ("boost_stacktrace_addr2line", "boost_stacktrace_basic") and self.settings.compiler == "Visual Studio":
continue
if name == "boost_stacktrace_backtrace":
if "boost_stacktrace_backtrace" not in all_detected_libraries:
continue
# FIXME: Boost.Build sometimes picks up a system libbacktrace library.
# How to avoid this and force using a conan packaged libbacktrace package.
self.output.warn("Picked up a system libbacktrace library")
if not self.options.get_safe("numa") and "_numa" in name:
continue
libs.append(add_libprefix(name.format(**libformatdata)) + libsuffix)
return libs

for module in self._dependencies["dependencies"].keys():
missing_depmodules = list(depmodule for depmodule in self._all_dependent_modules(module) if self.options.get_safe("without_{}".format(depmodule), False))
if missing_depmodules:
continue

module_libraries = [add_libprefix(lib.format(**libformatdata)) + libsuffix for lib in self._dependencies["libs"][module]]
if all(l in detected_libraries for l in module_libraries):
modules_seen.add(module)
used_libraries.update(module_libraries)
self.cpp_info.components[module].libs = module_libraries

self.cpp_info.components[module].requires = self._dependencies["dependencies"][module] + ["_libboost"]
self.cpp_info.components[module].names["cmake_find_package"] = module
self.cpp_info.components[module].names["cmake_find_package_multi"] = module

for requirement in self._dependencies.get("requirements", {}).get(module, []):
if requirement == "backtrace":
# FIXME: backtrace not (yet) available in cci
continue
if self.options.get_safe(requirement, None) == False:
module_libraries = filter_transform_module_libraries(self._dependencies["libs"][module])
all_expected_libraries = all_expected_libraries.union(module_libraries)
if set(module_libraries).difference(all_detected_libraries):
incomplete_components.append(module)

self.cpp_info.components[module].libs = module_libraries

self.cpp_info.components[module].requires = self._dependencies["dependencies"][module] + ["_libboost"]
self.cpp_info.components[module].names["cmake_find_package"] = module
self.cpp_info.components[module].names["cmake_find_package_multi"] = module
self.cpp_info.components[module].names["pkg_config"] = "boost_{}".format(module)

for requirement in self._dependencies.get("requirements", {}).get(module, []):
if requirement == "backtrace":
# FIXME: backtrace not (yet) available in cci
continue
if self.options.get_safe(requirement, None) == False:
continue
conan_requirement = self._option_to_conan_requirement(requirement)
if conan_requirement not in self.requires:
continue
if module == "locale" and requirement in ("icu", "iconv"):
if requirement != self.options.i18n_backend:
continue
conan_requirement = self._option_to_conan_requirement(requirement)
if conan_requirement not in self.requires:
continue
if module == "locale" and requirement in ("icu", "iconv"):
if requirement != self.options.i18n_backend:
continue
self.cpp_info.components[module].requires.append("{0}::{0}".format(conan_requirement))
self.cpp_info.components[module].requires.append("{0}::{0}".format(conan_requirement))

for incomplete_component in incomplete_components:
self.output.warn("Boost component '{0}' is missing libraries. Try building boost with '-o boost:without_{0}'.".format(incomplete_component))

if used_libraries != detected_libraries:
non_used = detected_libraries.difference(used_libraries)
assert len(non_used) == 0, "These libraries were not used in conan components: {}".format(non_used)
non_used = all_detected_libraries.difference(all_expected_libraries)
if non_used:
raise ConanException("These libraries were built, but were not used in any boost module: {}".format(non_used))

non_existing = used_libraries.difference(detected_libraries)
assert len(non_existing) == 0, "These libraries were used, but not built: {}".format(non_existing)
non_built = all_expected_libraries.difference(all_detected_libraries)
if non_built:
raise ConanException("These libraries were expected to be built, but were not built: {}".format(non_built))

if not self.options.without_python:
pyversion = tools.Version(self._python_version)
Expand Down
3 changes: 3 additions & 0 deletions recipes/boost/all/test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ if(NOT HEADER_ONLY)
find_package(Boost COMPONENTS fiber REQUIRED)
add_executable(fiber_exe fiber.cpp)
target_link_libraries(fiber_exe PRIVATE Boost::fiber)
set_property(TARGET fiber_exe PROPERTY CXX_STANDARD 11)
endif()

if(WITH_JSON)
find_package(Boost COMPONENTS json REQUIRED)
add_executable(json_exe json.cpp)
target_link_libraries(json_exe PRIVATE Boost::json)
set_property(TARGET json_exe PROPERTY CXX_STANDARD 11)
endif()

if(WITH_NOWIDE)
find_package(Boost COMPONENTS nowide REQUIRED)
add_executable(nowide_exe nowide.cpp)
target_link_libraries(nowide_exe PRIVATE Boost::nowide)
set_property(TARGET nowide_exe PROPERTY CXX_STANDARD 11)
endif()

if(WITH_LOCALE)
Expand Down