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: fix error when building without fiber + improve stacktrace support #5420

Merged
merged 10 commits into from
May 18, 2021
145 changes: 114 additions & 31 deletions recipes/boost/all/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ class BoostConan(ConanFile):
"extra_b2_flags": "ANY", # custom b2 flags
"i18n_backend": ["iconv", "icu", None],
"visibility": ["global", "protected", "hidden"],
"addr2line_location": "ANY",
"with_stacktrace_backtrace": [True, False],
}
options.update({"without_{}".format(_name): [True, False] for _name in CONFIGURE_OPTIONS})

Expand Down Expand Up @@ -124,6 +126,8 @@ class BoostConan(ConanFile):
"extra_b2_flags": "None",
"i18n_backend": "iconv",
"visibility": "hidden",
"addr2line_location": "/usr/bin/addr2line",
Copy link
Contributor

Choose a reason for hiding this comment

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

how exactly does it work? does it hardcode addr2line location into the libraries?
if so, how is it expected to work if addr2line located in different place on the consumer's machine than on CI machine?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ran strings libboost_stacktrace_addr2line.a and did not find an occurrence of /usr/bin/addr2line.
So I think the library only contains the classes and I think it is possible to override the location of addr2line when compiling by defining some environment variable.

"with_stacktrace_backtrace": True,
}
default_options.update({"without_{}".format(_name): False for _name in CONFIGURE_OPTIONS})
default_options.update({"without_{}".format(_name): True for _name in ("graph_parallel", "mpi", "python")})
Expand Down Expand Up @@ -234,6 +238,10 @@ def config_options(self):
if "without_{}".format(opt_name) not in self.options:
raise ConanException("{} has the configure options {} which is not available in conanfile.py".format(self._dependency_filename, opt_name))

# libbacktrace cannot be built on Visual Studio
if self.settings.compiler == "Visual Studio":
del self.options.with_stacktrace_backtrace

# nowide requires a c++11-able compiler + movable std::fstream: change default to not build on compiler with too old default c++ standard or too low compiler.cppstd
# json requires a c++11-able compiler: change default to not build on compiler with too old default c++ standard or too low compiler.cppstd
if self.settings.compiler.cppstd:
Expand Down Expand Up @@ -297,6 +305,10 @@ def _fPIC(self):
def _shared(self):
return self.options.get_safe("shared", self.default_options["shared"])

@property
def _stacktrace_addr2line_available(self):
return not self.options.header_only and not self.options.without_stacktrace and self.settings.compiler != "Visual Studio"

def configure(self):
if self.options.header_only:
del self.options.shared
Expand All @@ -315,6 +327,15 @@ def configure(self):
self.options.python_version = self._detect_python_version()
self.options.python_executable = self._python_executable

if self._stacktrace_addr2line_available:
if os.path.abspath(str(self.options.addr2line_location)) != str(self.options.addr2line_location):
raise ConanInvalidConfiguration("addr2line_location must be an absolute path to addr2line")
else:
del self.options.addr2line_location

if self.options.get_safe("without_stacktrace", True):
del self.options.with_stacktrace_backtrace

if self.options.layout == "b2-default":
self.options.layout = "versioned" if self.settings.os == "Windows" else "system"

Expand All @@ -334,7 +355,7 @@ def validate(self):
if self.settings.compiler == "Visual Studio" and self._shared:
if "MT" in str(self.settings.compiler.runtime):
raise ConanInvalidConfiguration("Boost can not be built as shared library with MT runtime.")
if self.options.numa:
if self.options.get_safe("numa"):
raise ConanInvalidConfiguration("Cannot build a shared boost with numa support on Visual Studio")

# Check, when a boost module is enabled, whether the boost modules it depends on are enabled as well.
Expand Down Expand Up @@ -424,6 +445,10 @@ def _with_icu(self):
def _with_iconv(self):
return not self.options.header_only and self._with_dependency("iconv") and self.options.i18n_backend == "iconv"

@property
def _with_stacktrace_backtrace(self):
return not self.options.header_only and self.options.get_safe("with_stacktrace_backtrace", False)

def requirements(self):
if self._with_zlib:
self.requires("zlib/1.2.11")
Expand All @@ -433,6 +458,9 @@ def requirements(self):
self.requires("xz_utils/5.2.5")
if self._with_zstd:
self.requires("zstd/1.4.9")
if self._with_stacktrace_backtrace:
self.requires("libbacktrace/cci.20210118")
self.requires("libunwind/1.5.0")

if self._with_icu:
self.requires("icu/68.2")
Expand Down Expand Up @@ -672,8 +700,7 @@ def _build_bcp(self):
with tools.vcvars(self.settings) if self._is_msvc else tools.no_op():
with tools.chdir(folder):
command = "%s -j%s --abbreviate-paths toolset=%s" % (self._b2_exe, tools.cpu_count(), self._toolset)
if "debug_level" in self.options:
command += " -d%d" % self.options.debug_level
command += " -d%d" % self.options.debug_level
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for finally fixing this 😄 #4121

Copy link
Contributor

Choose a reason for hiding this comment

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

but it was already fixed in #4121, isn't it?

Copy link
Contributor Author

@madebr madebr May 17, 2021

Choose a reason for hiding this comment

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

Indeed.
This is #4121 (comment)

debug_level is never removed from the options object, so there's no need for get_safe.
It is removed from package_id, but that does not matter.

Copy link
Contributor

Choose a reason for hiding this comment

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

It was my optional suggestion here, #4121 (comment)... just some clean up

self.output.warn(command)
self.run(command, run_environment=True)

Expand Down Expand Up @@ -703,6 +730,22 @@ def _run_bcp(self):
self.run(command)

def build(self):
if tools.cross_building(self.settings, skip_x64_x86=True):
# When cross building, do not attempt to run the test-executable (assume they work)
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "libs", "stacktrace", "build", "Jamfile.v2"),
"$(>) > $(<)",
"echo \"\" > $(<)", strict=False)
# Older clang releases require a thread_local variable to be initialized by a constant value
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* thread_local */", "thread_local", strict=False)
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* static __thread */", "static __thread", strict=False)
if self.settings.compiler == "clang" and tools.Version(self.settings.compiler.version) < 6:
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"thread_local", "/* thread_local */")
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"static __thread", "/* static __thread */")
Comment on lines +738 to +747
Copy link
Contributor

Choose a reason for hiding this comment

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

seems odd to be undoing the previous release

Maybe?

Suggested change
# Older clang releases require a thread_local variable to be initialized by a constant value
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* thread_local */", "thread_local", strict=False)
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* static __thread */", "static __thread", strict=False)
if self.settings.compiler == "clang" and tools.Version(self.settings.compiler.version) < 6:
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"thread_local", "/* thread_local */")
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"static __thread", "/* static __thread */")
# Older clang releases require a thread_local variable to be initialized by a constant value
if not (self.settings.compiler == "clang" and tools.Version(self.settings.compiler.version) < 6):
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* thread_local */", "thread_local", strict=False)
tools.replace_in_file(os.path.join(self.source_folder, self._source_subfolder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
"/* static __thread */", "static __thread", strict=False)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done for a purpose.
The boost conan recipe is a bad recipe in that it modifies and creates sources in the sources folder.
They are not copied to the build folder, or there is not an out-of-tree build going on.
That's why I try to revert the state of the build script to an initial state before patching.
This way when building for e.g. clang-5 and gcc-9, the patch is applied for clang but not for gcc-9.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then probably it is easier if we remove the no_copy_source from the recipe class. Simpler will be better in the long run, although it will take more time to build (but, hey! This is Conan, build once and use a thousand times 😄 ).

Not for this PR, but something to consider.


if self.options.header_only:
self.output.warn("Header only package, skipping build")
return
Expand Down Expand Up @@ -957,18 +1000,22 @@ def add_defines(library):
link_flags = 'linkflags="%s"' % " ".join(link_flags) if link_flags else ""
flags.append(link_flags)

if self.options.get_safe("addr2line_location"):
cxx_flags.append("-DBOOST_STACKTRACE_ADDR2LINE_LOCATION={}".format(self.options.addr2line_location))

cxx_flags = 'cxxflags="%s"' % " ".join(cxx_flags) if cxx_flags else ""
flags.append(cxx_flags)

if self.options.extra_b2_flags:
flags.extend(shlex.split(str(self.options.extra_b2_flags)))

flags.extend(["install",
"--prefix=%s" % self.package_folder,
"-j%s" % tools.cpu_count(),
"--abbreviate-paths"])
if "debug_level" in self.options:
flags.append("-d%d" % self.options.debug_level)
flags.extend([
"install",
"--prefix=%s" % self.package_folder,
"-j%s" % tools.cpu_count(),
"--abbreviate-paths",
"-d%d" % self.options.debug_level,
])
return flags

@property
Expand Down Expand Up @@ -1066,7 +1113,7 @@ def create_library_config(deps_name, name):

# Specify here the toolset with the binary if present if don't empty parameter :
contents += '\nusing "%s" : %s : ' % (self._toolset, self._toolset_version)

if self._is_msvc:
contents += ' "%s"' % self._cxx.replace("\\", "/")
else:
Expand All @@ -1083,16 +1130,27 @@ def create_library_config(deps_name, name):
contents += '<archiver>"%s" ' % tools.which(self._ar).replace("\\", "/")
if self._ranlib:
contents += '<ranlib>"%s" ' % tools.which(self._ranlib).replace("\\", "/")
if "CXXFLAGS" in os.environ:
contents += '<cxxflags>"%s" ' % os.environ["CXXFLAGS"]
if "CFLAGS" in os.environ:
contents += '<cflags>"%s" ' % os.environ["CFLAGS"]
if "CPPFLAGS" in os.environ:
contents += '<compileflags>"%s" ' % os.environ["CPPFLAGS"]
if "LDFLAGS" in os.environ:
contents += '<linkflags>"%s" ' % os.environ["LDFLAGS"]
if "ASFLAGS" in os.environ:
contents += '<asmflags>"%s" ' % os.environ["ASFLAGS"]
cxxflags = tools.get_env("CXXFLAGS", "") + " "
cflags = tools.get_env("CFLAGS", "") + " "
cppflags = tools.get_env("CPPFLAGS", "") + " "
ldflags = tools.get_env("LDFLAGS", "") + " "
asflags = tools.get_env("ASFLAGS", "") + " "

if self._with_stacktrace_backtrace:
for l in ("libbacktrace", "libunwind"):
cppflags += " ".join("-I{}".format(p) for p in self.deps_cpp_info[l].include_paths) + " "
ldflags += " ".join("-L{}".format(p) for p in self.deps_cpp_info[l].lib_paths) + " "

if cxxflags.strip():
contents += '<cxxflags>"%s" ' % cxxflags.strip()
if cflags.strip():
contents += '<cflags>"%s" ' % cflags.strip()
if cppflags.strip():
contents += '<compileflags>"%s" ' % cppflags.strip()
if ldflags.strip():
contents += '<linkflags>"%s" ' % ldflags.strip()
if asflags.strip():
contents += '<asmflags>"%s" ' % asflags.strip()

contents += " ;"

Expand Down Expand Up @@ -1349,14 +1407,8 @@ def filter_transform_module_libraries(names):
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":
if name in ("boost_stacktrace_addr2line", "boost_stacktrace_backtrace", "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)
Expand All @@ -1368,6 +1420,11 @@ def filter_transform_module_libraries(names):
continue

module_libraries = filter_transform_module_libraries(self._dependencies["libs"][module])

# Don't create components for modules that should have libraries, but don't have (because of filter)
if self._dependencies["libs"][module] and not module_libraries:
continue

all_expected_libraries = all_expected_libraries.union(module_libraries)
if set(module_libraries).difference(all_detected_libraries):
incomplete_components.append(module)
Expand All @@ -1380,9 +1437,6 @@ def filter_transform_module_libraries(names):
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)
Expand All @@ -1392,8 +1446,9 @@ def filter_transform_module_libraries(names):
if requirement != self.options.i18n_backend:
continue
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))
self.output.warn("Boost component '{0}' is missing libraries. Try building boost with '-o boost:without_{0}'. (Option is not guaranteed to exist)".format(incomplete_component))

non_used = all_detected_libraries.difference(all_expected_libraries)
if non_used:
Expand All @@ -1403,6 +1458,33 @@ def filter_transform_module_libraries(names):
if non_built:
raise ConanException("These libraries were expected to be built, but were not built: {}".format(non_built))

if not self.options.without_stacktrace:
if self.settings.os in ("Linux", "FreeBSD"):
self.cpp_info.components["stacktrace_basic"].system_libs.append("dl")
self.cpp_info.components["stacktrace_addr2line"].system_libs.append("dl")
if self._with_stacktrace_backtrace:
self.cpp_info.components["stacktrace_backtrace"].system_libs.append("dl")

if self._stacktrace_addr2line_available:
self.cpp_info.components["stacktrace_addr2line"].defines.extend([
"BOOST_STACKTRACE_ADDR2LINE_LOCATION=\"{}\"".format(self.options.addr2line_location),
"BOOST_STACKTRACE_USE_ADDR2LINE",
])

if self._with_stacktrace_backtrace:
self.cpp_info.components["stacktrace_backtrace"].defines.append("BOOST_STACKTRACE_USE_BACKTRACE")
self.cpp_info.components["stacktrace_backtrace"].system_libs.append("dl")
self.cpp_info.components["stacktrace_backtrace"].requires.extend([
"libunwind::libunwind",
"libbacktrace::libbacktrace",
])

self.cpp_info.components["stacktrace_noop"].defines.append("BOOST_STACKTRACE_USE_NOOP")

if self.settings.os == "Windows":
self.cpp_info.components["stacktrace_windb"].defines.append("BOOST_STACKTRACE_USE_WINDBG")
self.cpp_info.components["stacktrace_windb_cached"].defines.append("BOOST_STACKTRACE_USE_WINDBG_CACHED")

if not self.options.without_python:
pyversion = tools.Version(self._python_version)
self.cpp_info.components["python{}{}".format(pyversion.major, pyversion.minor)].requires = ["python"]
Expand Down Expand Up @@ -1438,3 +1520,4 @@ def filter_transform_module_libraries(names):
self.cpp_info.components["headers"].defines.extend(["BOOST_AC_USE_PTHREADS", "BOOST_SP_USE_PTHREADS"])
else:
self.cpp_info.components["headers"].defines.extend(["BOOST_AC_DISABLE_THREADS", "BOOST_SP_DISABLE_THREADS"])
self.user_info.stacktrace_addr2line_available = self._stacktrace_addr2line_available
37 changes: 37 additions & 0 deletions recipes/boost/all/test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,43 @@ if(NOT HEADER_ONLY)
target_link_libraries(locale_exe PRIVATE Boost::locale)
endif()

if(WITH_STACKTRACE_ADDR2LINE)
find_package(Boost COMPONENTS stacktrace REQUIRED)
add_executable(stacktrace_addr2line_exe stacktrace.cpp)
target_compile_definitions(stacktrace_addr2line_exe PRIVATE TEST_STACKTRACE_IMPL=1)
target_link_libraries(stacktrace_addr2line_exe PRIVATE Boost::stacktrace_addr2line)
endif()

if(WITH_STACKTRACE_BACKTRACE)
add_executable(stacktrace_backtrace_exe stacktrace.cpp)
target_compile_definitions(stacktrace_backtrace_exe PRIVATE TEST_STACKTRACE_IMPL=2)
target_link_libraries(stacktrace_backtrace_exe PRIVATE Boost::stacktrace_backtrace)
endif()

if(WITH_STACKTRACE)
find_package(Boost COMPONENTS stacktrace REQUIRED)

if(NOT MSVC)
add_executable(stacktrace_basic_exe stacktrace.cpp)
target_compile_definitions(stacktrace_basic_exe PRIVATE TEST_STACKTRACE_IMPL=3)
target_link_libraries(stacktrace_basic_exe PRIVATE Boost::stacktrace_basic)
endif()

add_executable(stacktrace_noop_exe stacktrace.cpp)
target_compile_definitions(stacktrace_noop_exe PRIVATE TEST_STACKTRACE_IMPL=4)
target_link_libraries(stacktrace_noop_exe PRIVATE Boost::stacktrace_noop)

if(WIN32)
add_executable(stacktrace_windbg_exe stacktrace.cpp)
target_compile_definitions(stacktrace_windbg_exe PRIVATE TEST_STACKTRACE_IMPL=5)
target_link_libraries(stacktrace_windbg_exe PRIVATE Boost::stacktrace_windbg)

add_executable(stacktrace_windbg_cached_exe stacktrace.cpp)
target_compile_definitions(stacktrace_windbg_cached_exe PRIVATE TEST_STACKTRACE_IMPL=6)
target_link_libraries(stacktrace_windbg_cached_exe PRIVATE Boost::stacktrace_windbg_cached)
endif()
endif()

if(WITH_PYTHON)
find_package(Boost COMPONENTS python REQUIRED)
add_library(hello_ext MODULE python.cpp)
Expand Down
16 changes: 16 additions & 0 deletions recipes/boost/all/test_package/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ def build(self):
cmake.definitions["WITH_LOCALE"] = not self.options["boost"].without_locale
cmake.definitions["WITH_NOWIDE"] = not self._boost_option("without_nowide", True)
cmake.definitions["WITH_JSON"] = not self._boost_option("without_json", True)
cmake.definitions["WITH_STACKTRACE"] = not self.options["boost"].without_stacktrace
cmake.definitions["WITH_STACKTRACE_ADDR2LINE"] = self.deps_user_info["boost"].stacktrace_addr2line_available
cmake.definitions["WITH_STACKTRACE_BACKTRACE"] = self._boost_option("with_stacktrace_backtrace", False)
cmake.configure()
# Disable parallel builds because c3i (=conan-center's test/build infrastructure) seems to choke here
cmake.parallel = False
cmake.build()

def test(self):
Expand Down Expand Up @@ -65,3 +70,14 @@ def test(self):
with tools.environment_append({"PYTHONPATH": "{}:{}".format("bin", "lib")}):
self.run("{} {}".format(self.options["boost"].python_executable, os.path.join(self.source_folder, "python.py")), run_environment=True)
self.run(os.path.join("bin", "numpy_exe"), run_environment=True)
if not self.options["boost"].without_stacktrace:
if self.settings.compiler != "Visual Studio":
self.run(os.path.join("bin", "stacktrace_basic_exe"), run_environment=True)
self.run(os.path.join("bin", "stacktrace_noop_exe"), run_environment=True)
if str(self.deps_user_info["boost"].stacktrace_addr2line_available) == "True":
self.run(os.path.join("bin", "stacktrace_addr2line_exe"), run_environment=True)
if self.settings.os == "Windows":
self.run(os.path.join("bin", "stacktrace_windbg_exe"), run_environment=True)
self.run(os.path.join("bin", "stacktrace_windbg_cached_exe"), run_environment=True)
if self._boost_option("with_stacktrace_backtrace", False):
self.run(os.path.join("bin", "stacktrace_backtrace_exe"), run_environment=True)
Loading