diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py
index 0887a1859da..4c105bfc2ce 100644
--- a/src/packagedcode/__init__.py
+++ b/src/packagedcode/__init__.py
@@ -21,6 +21,7 @@
from packagedcode import debian_copyright
from packagedcode import distro
from packagedcode import conda
+from packagedcode import conan
from packagedcode import cocoapods
from packagedcode import cran
from packagedcode import freebsd
@@ -77,6 +78,8 @@
conda.CondaYamlHandler,
conda.CondaMetaYamlHandler,
+ conan.ConanFileHandler,
+
cran.CranDescriptionFileHandler,
debian_copyright.DebianCopyrightFileInPackageHandler,
diff --git a/src/packagedcode/conan.py b/src/packagedcode/conan.py
new file mode 100644
index 00000000000..528cff45ee7
--- /dev/null
+++ b/src/packagedcode/conan.py
@@ -0,0 +1,188 @@
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# ScanCode is a trademark of nexB Inc.
+# SPDX-License-Identifier: Apache-2.0
+# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
+# See https://github.com/nexB/scancode-toolkit for support or download.
+# See https://aboutcode.org for more information about nexB OSS projects.
+#
+import ast
+import io
+import logging
+import os
+
+from packageurl import PackageURL
+
+from packagedcode import models
+
+"""
+Handle conanfile recipes for conan packages
+https://docs.conan.io/2/reference/conanfile.html
+"""
+
+
+SCANCODE_DEBUG_PACKAGE = os.environ.get("SCANCODE_DEBUG_PACKAGE", False)
+
+TRACE = SCANCODE_DEBUG_PACKAGE
+
+
+def logger_debug(*args):
+ pass
+
+
+logger = logging.getLogger(__name__)
+
+if TRACE:
+ import sys
+
+ logging.basicConfig(stream=sys.stdout)
+ logger.setLevel(logging.DEBUG)
+
+ def logger_debug(*args):
+ return logger.debug(" ".join(isinstance(a, str) and a or repr(a) for a in args))
+
+
+
+class ConanFileParser(ast.NodeVisitor):
+ def __init__(self):
+ self.name = None
+ self.version = None
+ self.description = None
+ self.author = None
+ self.homepage_url = None
+ self.vcs_url = None
+ self.license = None
+ self.keywords = []
+ self.requires = []
+
+ def to_dict(self):
+ return {
+ 'name': self.name,
+ 'version': self.version,
+ 'description': self.description,
+ 'author':self.author,
+ 'homepage_url':self.homepage_url,
+ 'vcs_url':self.vcs_url,
+ 'license':self.license,
+ 'keywords':self.keywords,
+ 'requires':self.requires,
+ }
+
+
+ def visit_Assign(self, node):
+ if not node.targets or not isinstance(node.targets[0], ast.Name):
+ return
+ if not node.value or not (isinstance(node.value, ast.Constant) or isinstance(node.value, ast.Tuple)):
+ return
+ variable_name = node.targets[0].id
+ values = node.value
+ if variable_name == "name":
+ self.name = values.value
+ elif variable_name == "version":
+ self.version = values.value
+ elif variable_name == "description":
+ self.description = values.value
+ elif variable_name == "author":
+ self.author = values.value
+ elif variable_name == "homepage":
+ self.homepage_url = values.value
+ elif variable_name == "url":
+ self.vcs_url = values.value
+ elif variable_name == "license":
+ self.license = values.value
+ elif variable_name == "topics":
+ self.keywords.extend(
+ [el.value for el in values.elts if isinstance(el, ast.Constant)]
+ )
+ elif variable_name == "requires":
+ if isinstance(values, ast.Tuple):
+ self.requires.extend(
+ [el.value for el in values.elts if isinstance(el, ast.Constant)]
+ )
+ elif isinstance(values, ast.Constant):
+ self.requires.append(values.value)
+
+ def visit_Call(self, node):
+ if not isinstance(node.func, ast.Attribute) or not isinstance(
+ node.func.value, ast.Name
+ ):
+ return
+ if node.func.value.id == "self" and node.func.attr == "requires":
+ if node.args and isinstance(node.args[0], ast.Constant):
+ self.requires.append(node.args[0].value)
+
+
+class ConanFileHandler(models.DatafileHandler):
+ datasource_id = "conan_conanfile_py"
+ path_patterns = ("*/conanfile.py",)
+ default_package_type = "conan"
+ default_primary_language = "C++"
+ description = "conan recipe"
+ documentation_url = "https://docs.conan.io/2.0/reference/conanfile.html"
+
+ @classmethod
+ def parse(cls, location):
+ with io.open(location, encoding="utf-8") as loc:
+ conan_recipe = loc.read()
+
+ try:
+ tree = ast.parse(conan_recipe)
+ recipe_class_def = next(
+ (
+ node
+ for node in tree.body
+ if isinstance(node, ast.ClassDef)
+ and node.bases
+ and isinstance(node.bases[0], ast.Name)
+ and node.bases[0].id == "ConanFile"
+ ),
+ None,
+ )
+
+ parser = ConanFileParser()
+ parser.visit(recipe_class_def)
+ except SyntaxError as e:
+ if TRACE:
+ logger_debug(f"Syntax error in conan recipe: {e}")
+ return
+
+ if TRACE:
+ logger_debug(f"ConanFileHandler: parse: package: {parser.to_dict()}")
+
+ dependencies = get_dependencies(parser.requires)
+
+ yield models.PackageData(
+ datasource_id=cls.datasource_id,
+ type=cls.default_package_type,
+ primary_language=cls.default_primary_language,
+ namespace=None,
+ name=parser.name,
+ version=parser.version,
+ description=parser.description,
+ homepage_url=parser.homepage_url,
+ vcs_url=parser.vcs_url,
+ keywords=parser.keywords,
+ declared_license_expression=parser.license,
+ dependencies=dependencies,
+ )
+
+def is_constraint_resolved(constraint):
+ range_characters = {'>', '<', '[', ']', '>=','<='}
+ return not any(char in range_characters for char in constraint)
+
+def get_dependencies(requires):
+ dependent_packages=[]
+ for req in requires:
+ name, constraint = req.split('/', 1)
+ is_resolved = is_constraint_resolved(constraint)
+ purl = PackageURL(type='pypi', name=name)
+ dependent_packages.append(
+ models.DependentPackage(
+ purl=purl.to_string(),
+ scope='install',
+ is_runtime=True,
+ is_optional=False,
+ is_resolved=is_resolved,
+ extracted_requirement=constraint
+ )
+ )
+ return dependent_packages
diff --git a/tests/packagedcode/data/conan/recipes/boost/conanfile.py b/tests/packagedcode/data/conan/recipes/boost/conanfile.py
new file mode 100644
index 00000000000..3b5363de538
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/boost/conanfile.py
@@ -0,0 +1,1831 @@
+from conan import ConanFile
+from conan.errors import ConanException, ConanInvalidConfiguration
+from conan.tools.apple import is_apple_os, to_apple_arch, XCRun
+from conan.tools.build import build_jobs, check_min_cppstd, cross_building, valid_min_cppstd, supported_cppstd
+from conan.tools.env import VirtualBuildEnv
+from conan.tools.files import (
+ apply_conandata_patches, chdir, collect_libs, copy, export_conandata_patches,
+ get, mkdir, rename, replace_in_file, rm, rmdir, save
+)
+from conan.tools.gnu import AutotoolsToolchain
+from conan.tools.layout import basic_layout
+from conan.tools.microsoft import is_msvc, is_msvc_static_runtime, MSBuildToolchain, msvc_runtime_flag, VCVars
+from conan.tools.scm import Version
+
+import glob
+from io import StringIO
+import os
+import re
+import shlex
+import shutil
+import sys
+import yaml
+
+required_conan_version = ">=1.53.0"
+
+
+# When adding (or removing) an option, also add this option to the list in
+# `rebuild-dependencies.yml` and re-run that script.
+CONFIGURE_OPTIONS = (
+ "atomic",
+ "chrono",
+ "cobalt",
+ "container",
+ "context",
+ "contract",
+ "coroutine",
+ "date_time",
+ "exception",
+ "fiber",
+ "filesystem",
+ "graph",
+ "graph_parallel",
+ "iostreams",
+ "json",
+ "locale",
+ "log",
+ "math",
+ "mpi",
+ "nowide",
+ "program_options",
+ "python",
+ "random",
+ "regex",
+ "serialization",
+ "stacktrace",
+ "system",
+ "test",
+ "thread",
+ "timer",
+ "type_erasure",
+ "url",
+ "wave",
+)
+
+
+class BoostConan(ConanFile):
+ name = "boost"
+ description = "Boost provides free peer-reviewed portable C++ source libraries"
+ url = "https://github.com/conan-io/conan-center-index"
+ homepage = "https://www.boost.org"
+ license = "BSL-1.0"
+ topics = ("libraries", "cpp")
+
+ settings = "os", "arch", "compiler", "build_type"
+ options = {
+ "shared": [True, False],
+ "fPIC": [True, False],
+ "header_only": [True, False],
+ "error_code_header_only": [True, False],
+ "system_no_deprecated": [True, False],
+ "asio_no_deprecated": [True, False],
+ "filesystem_no_deprecated": [True, False],
+ "filesystem_use_std_fs": [True, False],
+ "filesystem_version": [None, "3", "4"],
+ "layout": ["system", "versioned", "tagged", "b2-default"],
+ "magic_autolink": [True, False], # enables BOOST_ALL_NO_LIB
+ "diagnostic_definitions": [True, False], # enables BOOST_LIB_DIAGNOSTIC
+ "python_executable": [None, "ANY"], # system default python installation is used, if None
+ "python_version": [None, "ANY"], # major.minor; computed automatically, if None
+ "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],
+ "zstd": [True, False],
+ "segmented_stacks": [True, False],
+ "debug_level": list(range(0, 14)),
+ "pch": [True, False],
+ "extra_b2_flags": [None, "ANY"], # custom b2 flags
+ "i18n_backend": ["iconv", "icu", None, "deprecated"],
+ "i18n_backend_iconv": ["libc", "libiconv", "off"],
+ "i18n_backend_icu": [True, False],
+ "visibility": ["global", "protected", "hidden"],
+ "addr2line_location": ["ANY"],
+ "with_stacktrace_backtrace": [True, False],
+ "buildid": [None, "ANY"],
+ "python_buildid": [None, "ANY"],
+ "system_use_utf8": [True, False],
+ }
+ options.update({f"without_{_name}": [True, False] for _name in CONFIGURE_OPTIONS})
+
+ default_options = {
+ "shared": False,
+ "fPIC": True,
+ "header_only": False,
+ "error_code_header_only": False,
+ "system_no_deprecated": False,
+ "asio_no_deprecated": False,
+ "filesystem_no_deprecated": False,
+ "filesystem_use_std_fs": False,
+ "filesystem_version": None,
+ "layout": "system",
+ "magic_autolink": False,
+ "diagnostic_definitions": False,
+ "python_executable": None,
+ "python_version": None,
+ "namespace": "boost",
+ "namespace_alias": False,
+ "multithreading": True,
+ "numa": True,
+ "zlib": True,
+ "bzip2": True,
+ "lzma": False,
+ "zstd": False,
+ "segmented_stacks": False,
+ "debug_level": 0,
+ "pch": True,
+ "extra_b2_flags": None,
+ "i18n_backend": "deprecated",
+ "i18n_backend_iconv": "libc",
+ "i18n_backend_icu": False,
+ "visibility": "hidden",
+ "addr2line_location": "/usr/bin/addr2line",
+ "with_stacktrace_backtrace": True,
+ "buildid": None,
+ "python_buildid": None,
+ "system_use_utf8": False,
+ }
+ default_options.update({f"without_{_name}": False for _name in CONFIGURE_OPTIONS})
+ default_options.update({f"without_{_name}": True for _name in ("graph_parallel", "mpi", "python")})
+
+ short_paths = True
+ no_copy_source = True
+ _cached_dependencies = None
+
+ def export(self):
+ copy(self, f"dependencies/{self._dependency_filename}", src=self.recipe_folder, dst=self.export_folder)
+
+ def export_sources(self):
+ export_conandata_patches(self)
+
+ @property
+ def _min_compiler_version_default_cxx11(self):
+ """ Minimum compiler version having c++ standard >= 11
+ """
+ return {
+ "gcc": 6,
+ "clang": 6,
+ "apple-clang": 99, # still uses C++98 by default. XCode does not reflect apple-clang
+ "Visual Studio": 14, # guess
+ "msvc": 190, # guess
+ }.get(str(self.settings.compiler))
+
+ @property
+ def _min_compiler_version_default_cxx20(self):
+ return {
+ "gcc": 99,
+ "clang": 99,
+ "apple-clang": 99,
+ "Visual Studio": 99,
+ "msvc": 999,
+ }.get(str(self.settings.compiler))
+
+ def _has_cppstd_11_supported(self):
+ cppstd = self.settings.compiler.get_safe("cppstd")
+ if cppstd:
+ return valid_min_cppstd(self, 11)
+ compiler_version = self._min_compiler_version_default_cxx11
+ if compiler_version:
+ return (Version(self.settings.compiler.version) >= compiler_version) or "11" in supported_cppstd(self)
+
+ @property
+ def _min_compiler_version_nowide(self):
+ # Nowide needs c++11 + swappable std::fstream
+ return {
+ "gcc": 5,
+ "clang": 5,
+ "Visual Studio": 14, # guess
+ "msvc": 190, # guess
+ }.get(str(self.settings.compiler))
+
+ @property
+ def _dependency_filename(self):
+ return f"dependencies-{self.version}.yml"
+
+ @property
+ def _dependencies(self):
+ if self._cached_dependencies is None:
+ dependencies_filepath = os.path.join(self.recipe_folder, "dependencies", self._dependency_filename)
+ if not os.path.isfile(dependencies_filepath):
+ raise ConanException(f"Cannot find {dependencies_filepath}")
+ with open(dependencies_filepath, encoding='utf-8') as f:
+ self._cached_dependencies = yaml.safe_load(f)
+ return self._cached_dependencies
+
+ def _all_dependent_modules(self, name):
+ dependencies = {name}
+ while True:
+ new_dependencies = set()
+ for dependency in dependencies:
+ new_dependencies.update(set(self._dependencies["dependencies"][dependency]))
+ new_dependencies.update(dependencies)
+ if len(new_dependencies) > len(dependencies):
+ dependencies = new_dependencies
+ else:
+ break
+ return dependencies
+
+ def _all_super_modules(self, name):
+ dependencies = {name}
+ while True:
+ new_dependencies = set(dependencies)
+ for module in self._dependencies["dependencies"]:
+ if dependencies.intersection(set(self._dependencies["dependencies"][module])):
+ new_dependencies.add(module)
+ if len(new_dependencies) > len(dependencies):
+ dependencies = new_dependencies
+ else:
+ break
+ return dependencies
+
+ @property
+ def _bcp_dir(self):
+ return "custom-boost"
+
+ @property
+ def _settings_build(self):
+ return getattr(self, "settings_build", self.settings)
+
+ @property
+ def _is_clang_cl(self):
+ return self.settings.os == "Windows" and self.settings.compiler == "clang"
+
+ @property
+ def _python_executable(self):
+ """
+ obtain full path to the python interpreter executable
+ :return: path to the python interpreter executable, either set by option, or system default
+ """
+ exe = self.options.python_executable if self.options.python_executable else sys.executable
+ return str(exe).replace("\\", "/")
+
+ @property
+ def _is_windows_platform(self):
+ return self.settings.os in ["Windows", "WindowsStore", "WindowsCE"]
+
+ @property
+ def _is_apple_embedded_platform(self):
+ return self.settings.os in ["iOS", "watchOS", "tvOS"]
+
+ def config_options(self):
+ if self.settings.os == "Windows":
+ del self.options.fPIC
+
+ # Test whether all config_options from the yml are available in CONFIGURE_OPTIONS
+ for opt_name in self._configure_options:
+ if f"without_{opt_name}" not in self.options:
+ raise ConanException(f"{self._dependency_filename} has the configure options {opt_name} which is not available in conanfile.py")
+
+ # stacktrace_backtrace not supported on Windows
+ if self.settings.os == "Windows":
+ 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.get_safe("cppstd"):
+ if not valid_min_cppstd(self, 11):
+ self.options.without_fiber = True
+ self.options.without_nowide = True
+ self.options.without_json = True
+ self.options.without_url = True
+ else:
+ version_cxx11_standard_json = self._min_compiler_version_default_cxx11
+ if version_cxx11_standard_json:
+ if not self._has_cppstd_11_supported:
+ self.options.without_fiber = True
+ self.options.without_json = True
+ self.options.without_nowide = True
+ self.options.without_url = True
+ else:
+ self.options.without_fiber = True
+ self.options.without_json = True
+ self.options.without_nowide = True
+ self.options.without_url = True
+
+ # iconv is off by default on Windows and Solaris
+ if self._is_windows_platform or self.settings.os == "SunOS":
+ self.options.i18n_backend_iconv = "off"
+ elif is_apple_os(self):
+ self.options.i18n_backend_iconv = "libiconv"
+ elif self.settings.os == "Android":
+ # bionic provides iconv since API level 28
+ api_level = self.settings.get_safe("os.api_level")
+ if api_level and Version(api_level) < "28":
+ self.options.i18n_backend_iconv = "libiconv"
+
+ # Remove options not supported by this version of boost
+ for dep_name in CONFIGURE_OPTIONS:
+ if dep_name not in self._configure_options:
+ delattr(self.options, f"without_{dep_name}")
+
+ if Version(self.version) >= "1.76.0":
+ # Starting from 1.76.0, Boost.Math requires a c++11 capable compiler
+ # ==> disable it by default for older compilers or c++ standards
+
+ def disable_math():
+ super_modules = self._all_super_modules("math")
+ for smod in super_modules:
+ try:
+ setattr(self.options, f"without_{smod}", True)
+ except ConanException:
+ pass
+
+ if self.settings.compiler.get_safe("cppstd"):
+ if not valid_min_cppstd(self, 11):
+ disable_math()
+ else:
+ min_compiler_version = self._min_compiler_version_default_cxx11
+ if min_compiler_version is None:
+ self.output.warning("Assuming the compiler supports c++11 by default")
+ elif not self._has_cppstd_11_supported:
+ disable_math()
+
+ if Version(self.version) >= "1.79.0":
+ # Starting from 1.79.0, Boost.Wave requires a c++11 capable compiler
+ # ==> disable it by default for older compilers or c++ standards
+
+ def disable_wave():
+ super_modules = self._all_super_modules("wave")
+ for smod in super_modules:
+ try:
+ setattr(self.options, f"without_{smod}", True)
+ except ConanException:
+ pass
+
+ if self.settings.compiler.get_safe("cppstd"):
+ if not valid_min_cppstd(self, 11):
+ disable_wave()
+ else:
+ min_compiler_version = self._min_compiler_version_default_cxx11
+ if min_compiler_version is None:
+ self.output.warning("Assuming the compiler supports c++11 by default")
+ elif not self._has_cppstd_11_supported:
+ disable_wave()
+
+ if Version(self.version) >= "1.81.0":
+ # Starting from 1.81.0, Boost.Locale requires a c++11 capable compiler
+ # ==> disable it by default for older compilers or c++ standards
+
+ def disable_locale():
+ super_modules = self._all_super_modules("locale")
+ for smod in super_modules:
+ try:
+ setattr(self.options, f"without_{smod}", True)
+ except ConanException:
+ pass
+
+ if self.settings.compiler.get_safe("cppstd"):
+ if not valid_min_cppstd(self, 11):
+ disable_locale()
+ else:
+ min_compiler_version = self._min_compiler_version_default_cxx11
+ if min_compiler_version is None:
+ self.output.warning("Assuming the compiler supports c++11 by default")
+ elif not self._has_cppstd_11_supported:
+ disable_locale()
+
+ if Version(self.version) >= "1.84.0":
+ # Starting from 1.84.0, Boost.Cobalt requires a c++20 capable compiler
+ # ==> disable it by default for older compilers or c++ standards
+
+ def disable_cobalt():
+ super_modules = self._all_super_modules("cobalt")
+ for smod in super_modules:
+ try:
+ setattr(self.options, f"without_{smod}", True)
+ except ConanException:
+ pass
+
+ if self.settings.compiler.get_safe("cppstd"):
+ if not valid_min_cppstd(self, 20):
+ disable_cobalt()
+ else:
+ min_compiler_version = self._min_compiler_version_default_cxx20
+ if min_compiler_version is None:
+ self.output.warning("Assuming the compiler supports c++20 by default")
+ elif Version(self.settings.compiler.version) < min_compiler_version:
+ disable_cobalt()
+
+ # FIXME: Compilation errors on msvc shared build for boost.fiber https://github.com/boostorg/fiber/issues/314
+ if is_msvc(self):
+ self.options.without_fiber = True
+
+ @property
+ def _configure_options(self):
+ return self._dependencies["configure_options"]
+
+ @property
+ def _fPIC(self):
+ return self.options.get_safe("fPIC", self.default_options["fPIC"])
+
+ @property
+ def _shared(self):
+ return self.options.get_safe("shared", self.default_options["shared"])
+
+ @property
+ def _stacktrace_addr2line_available(self):
+ if (self._is_apple_embedded_platform or self.settings.get_safe("os.subsystem") == "catalyst"):
+ # sandboxed environment - cannot launch external processes (like addr2line), system() function is forbidden
+ return False
+ return not self.options.header_only and not self.options.without_stacktrace and self.settings.os != "Windows"
+
+ def configure(self):
+ if self.options.header_only:
+ self.options.rm_safe("shared")
+ self.options.rm_safe("fPIC")
+ elif self.options.shared:
+ self.options.rm_safe("fPIC")
+
+ if self.options.i18n_backend != "deprecated":
+ self.output.warning("i18n_backend option is deprecated, do not use anymore.")
+ if self.options.i18n_backend == "iconv":
+ self.options.i18n_backend_iconv = "libiconv"
+ self.options.i18n_backend_icu = False
+ if self.options.i18n_backend == "icu":
+ self.options.i18n_backend_iconv = "off"
+ self.options.i18n_backend_icu = True
+ if self.options.i18n_backend == "None":
+ self.options.i18n_backend_iconv = "off"
+ self.options.i18n_backend_icu = False
+ if self.options.without_locale:
+ self.options.rm_safe("i18n_backend_iconv")
+ self.options.rm_safe("i18n_backend_icu")
+
+ if not self.options.without_python:
+ if not self.options.python_version:
+ self.options.python_version = self._detect_python_version()
+ self.options.python_executable = self._python_executable
+ else:
+ self.options.rm_safe("python_buildid")
+
+ if not self._stacktrace_addr2line_available:
+ self.options.rm_safe("addr2line_location")
+
+ if self.options.get_safe("without_stacktrace", True):
+ self.options.rm_safe("with_stacktrace_backtrace")
+
+ if self.options.layout == "b2-default":
+ self.options.layout = "versioned" if self.settings.os == "Windows" else "system"
+
+ if self.options.without_fiber:
+ self.options.rm_safe("numa")
+
+ def layout(self):
+ basic_layout(self, src_folder="src")
+
+ @property
+ def _cxx11_boost_libraries(self):
+ libraries = ["fiber", "json", "nowide", "url"]
+ if Version(self.version) >= "1.76.0":
+ libraries.append("math")
+ if Version(self.version) >= "1.79.0":
+ libraries.append("wave")
+ if Version(self.version) >= "1.81.0":
+ libraries.append("locale")
+ if Version(self.version) >= "1.84.0":
+ libraries.append("atomic")
+ libraries.append("filesystem")
+ libraries.append("log")
+ libraries.append("random")
+ libraries.append("stacktrace")
+ libraries.append("test")
+ libraries.append("thread")
+ libraries.sort()
+ return filter(lambda library: f"without_{library}" in self.options, libraries)
+
+ def validate(self):
+ if not self.options.multithreading:
+ # * For the reason 'thread' is deactivate look at https://stackoverflow.com/a/20991533
+ # Look also on the comments of the answer for more details
+ # * Although the 'context' and 'atomic' library does not mention anything about threading,
+ # when being build the compiler uses the -pthread flag, which makes it quite dangerous
+ for lib in ["locale", "coroutine", "wave", "type_erasure", "fiber", "thread", "context", "atomic"]:
+ if not self.options.get_safe(f"without_{lib}"):
+ raise ConanInvalidConfiguration(f"Boost '{lib}' library requires multi threading")
+
+ if is_msvc(self) and self._shared and is_msvc_static_runtime(self):
+ raise ConanInvalidConfiguration("Boost can not be built as shared library with MT runtime.")
+
+ # FIXME: In 1.84.0, there are compilation errors on msvc shared build for boost.fiber. https://github.com/boostorg/fiber/issues/314
+ if Version(self.version) >= "1.84.0" and is_msvc(self) and self._shared and not self.options.without_fiber:
+ raise ConanInvalidConfiguration("Boost.fiber can not be built as shared library on MSVC.")
+
+ if not self.options.without_locale and self.options.i18n_backend_iconv == "off" and \
+ not self.options.i18n_backend_icu and not self._is_windows_platform:
+ raise ConanInvalidConfiguration(
+ "Boost.Locale library needs either iconv or ICU library to be built on non windows platforms"
+ )
+
+ if self._stacktrace_addr2line_available:
+ if not os.path.isabs(str(self.options.addr2line_location)):
+ raise ConanInvalidConfiguration("addr2line_location must be an absolute path to addr2line")
+
+ # 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(f"without_{mod_name}", True):
+ for mod_dep in mod_deps:
+ if self.options.get_safe(f"without_{mod_dep}", False):
+ raise ConanInvalidConfiguration(f"{mod_name} requires {mod_deps}: {mod_dep} is disabled")
+
+ if not self.options.get_safe("without_nowide", True):
+ # nowide require a c++11-able compiler with movable std::fstream
+ mincompiler_version = self._min_compiler_version_nowide
+ if mincompiler_version and Version(self.settings.compiler.version) < mincompiler_version:
+ raise ConanInvalidConfiguration("This compiler is too old to build Boost.nowide.")
+
+ if any([not self.options.get_safe(f"without_{library}", True) for library in self._cxx11_boost_libraries]):
+ if self.settings.compiler.get_safe("cppstd"):
+ check_min_cppstd(self, 11)
+ else:
+ if not self._has_cppstd_11_supported:
+ raise ConanInvalidConfiguration(
+ f"Boost.{{{','.join(self._cxx11_boost_libraries)}}} requires a c++11 compiler "
+ "(please set compiler.cppstd or use a newer compiler)"
+ )
+
+ def _with_dependency(self, dependency):
+ """
+ Return true when dependency is required according to the dependencies-x.y.z.yml file
+ """
+ for name, reqs in self._dependencies["requirements"].items():
+ if dependency in reqs:
+ if not self.options.get_safe(f"without_{name}", True):
+ return True
+ return False
+
+ @property
+ def _with_zlib(self):
+ return not self.options.header_only and self._with_dependency("zlib") and self.options.zlib
+
+ @property
+ def _with_bzip2(self):
+ return not self.options.header_only and self._with_dependency("bzip2") and self.options.bzip2
+
+ @property
+ def _with_lzma(self):
+ return not self.options.header_only and self._with_dependency("lzma") and self.options.lzma
+
+ @property
+ def _with_zstd(self):
+ return not self.options.header_only and self._with_dependency("zstd") and self.options.zstd
+
+ @property
+ def _with_icu(self):
+ return not self.options.header_only and self._with_dependency("icu") and self.options.get_safe("i18n_backend_icu")
+
+ @property
+ def _with_iconv(self):
+ return not self.options.header_only and self._with_dependency("iconv") and self.options.get_safe("i18n_backend_iconv") == "libiconv"
+
+ @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 <2]")
+ if self._with_bzip2:
+ self.requires("bzip2/1.0.8")
+ if self._with_lzma:
+ self.requires("xz_utils/5.4.4")
+ if self._with_zstd:
+ self.requires("zstd/1.5.5")
+ if self._with_stacktrace_backtrace:
+ self.requires("libbacktrace/cci.20210118", transitive_headers=True, transitive_libs=True)
+
+ if self._with_icu:
+ self.requires("icu/73.2")
+ if self._with_iconv:
+ self.requires("libiconv/1.17")
+
+ def package_id(self):
+ del self.info.options.i18n_backend
+
+ if self.info.options.header_only:
+ self.info.clear()
+ else:
+ del self.info.options.debug_level
+ del self.info.options.filesystem_version
+ del self.info.options.pch
+ del self.info.options.python_executable # PATH to the interpreter is not important, only version matters
+ if self.info.options.without_python:
+ del self.info.options.python_version
+
+ def build_requirements(self):
+ if not self.options.header_only:
+ self.tool_requires("b2/4.10.1")
+
+ def source(self):
+ get(self, **self.conan_data["sources"][self.version],
+ destination=self.source_folder, strip_root=True)
+ apply_conandata_patches(self)
+
+ def generate(self):
+ if not self.options.header_only:
+ env = VirtualBuildEnv(self)
+ env.generate()
+ vc = VCVars(self)
+ vc.generate()
+
+ ##################### BUILDING METHODS ###########################
+
+ def _run_python_script(self, script):
+ """
+ execute python one-liner script and return its output
+ :param script: string containing python script to be executed
+ :return: output of the python script execution, or None, if script has failed
+ """
+ output = StringIO()
+ command = f'"{self._python_executable}" -c "{script}"'
+ self.output.info(f"running {command}")
+ try:
+ self.run(command, output, scope="run")
+ except ConanException:
+ self.output.info("(failed)")
+ return None
+ output = output.getvalue()
+ # Conan is broken when run_to_output = True
+ if "\n-----------------\n" in output:
+ output = output.split("\n-----------------\n", 1)[1]
+ output = output.strip()
+ return output if output != "None" else None
+
+ def _get_python_path(self, name):
+ """
+ obtain path entry for the python installation
+ :param name: name of the python config entry for path to be queried (such as "include", "platinclude", etc.)
+ :return: path entry from the sysconfig
+ """
+ # https://docs.python.org/3/library/sysconfig.html
+ # https://docs.python.org/2.7/library/sysconfig.html
+ return self._run_python_script("from __future__ import print_function; "
+ "import sysconfig; "
+ f"print(sysconfig.get_path('{name}'))")
+
+ def _get_python_sc_var(self, name):
+ """
+ obtain value of python sysconfig variable
+ :param name: name of variable to be queried (such as LIBRARY or LDLIBRARY)
+ :return: value of python sysconfig variable
+ """
+ return self._run_python_script("from __future__ import print_function; "
+ "import sysconfig; "
+ f"print(sysconfig.get_config_var('{name}'))")
+
+ def _get_python_du_var(self, name):
+ """
+ obtain value of python distutils sysconfig variable
+ (sometimes sysconfig returns empty values, while python.sysconfig provides correct values)
+ :param name: name of variable to be queried (such as LIBRARY or LDLIBRARY)
+ :return: value of python sysconfig variable
+ """
+ return self._run_python_script("from __future__ import print_function; "
+ "import distutils.sysconfig as du_sysconfig; "
+ f"print(du_sysconfig.get_config_var('{name}'))")
+
+ def _get_python_var(self, name):
+ """
+ obtain value of python variable, either by sysconfig, or by distutils.sysconfig
+ :param name: name of variable to be queried (such as LIBRARY or LDLIBRARY)
+ :return: value of python sysconfig variable
+
+ NOTE: distutils is deprecated and breaks the recipe since Python 3.10
+ """
+ python_version_parts = str(self.info.options.python_version).split('.')
+ python_major = int(python_version_parts[0])
+ python_minor = int(python_version_parts[1])
+ if(python_major >= 3 and python_minor >= 10):
+ return self._get_python_sc_var(name)
+
+ return self._get_python_sc_var(name) or self._get_python_du_var(name)
+
+ def _detect_python_version(self):
+ """
+ obtain version of python interpreter
+ :return: python interpreter version, in format major.minor
+ """
+ return self._run_python_script("from __future__ import print_function; "
+ "import sys; "
+ "print('{}.{}'.format(sys.version_info[0], sys.version_info[1]))")
+
+ @property
+ def _python_version(self):
+ version = self._detect_python_version()
+ if self.options.python_version and version != self.options.python_version:
+ raise ConanInvalidConfiguration(f"detected python version {version} doesn't match conan option {self.options.python_version}")
+ return version
+
+ @property
+ def _python_inc(self):
+ """
+ obtain the result of the "sysconfig.get_python_inc()" call
+ :return: result of the "sysconfig.get_python_inc()" execution
+ """
+ return self._run_python_script("from __future__ import print_function; "
+ "import sysconfig; "
+ "print(sysconfig.get_python_inc())")
+
+ @property
+ def _python_abiflags(self):
+ """
+ obtain python ABI flags, see https://www.python.org/dev/peps/pep-3149/ for the details
+ :return: the value of python ABI flags
+ """
+ return self._run_python_script("from __future__ import print_function; "
+ "import sys; "
+ "print(getattr(sys, 'abiflags', ''))")
+
+ @property
+ def _python_includes(self):
+ """
+ attempt to find directory containing Python.h header file
+ :return: the directory with python includes
+ """
+ include = self._get_python_path("include")
+ plat_include = self._get_python_path("platinclude")
+ include_py = self._get_python_var("INCLUDEPY")
+ include_dir = self._get_python_var("INCLUDEDIR")
+ python_inc = self._python_inc
+
+ candidates = [include,
+ plat_include,
+ include_py,
+ include_dir,
+ python_inc]
+ for candidate in candidates:
+ if candidate:
+ python_h = os.path.join(candidate, 'Python.h')
+ self.output.info(f"checking {python_h}")
+ if os.path.isfile(python_h):
+ self.output.info(f"found Python.h: {python_h}")
+ return candidate.replace("\\", "/")
+ raise Exception("couldn't locate Python.h - make sure you have installed python development files")
+
+ @property
+ def _python_library_dir(self):
+ """
+ attempt to find python development library
+ :return: the full path to the python library to be linked with
+ """
+ library = self._get_python_var("LIBRARY")
+ ldlibrary = self._get_python_var("LDLIBRARY")
+ libdir = self._get_python_var("LIBDIR")
+ multiarch = self._get_python_var("MULTIARCH")
+ masd = self._get_python_var("multiarchsubdir")
+ with_dyld = self._get_python_var("WITH_DYLD")
+ if libdir and multiarch and masd and not libdir.endswith(masd):
+ if masd.startswith(os.sep):
+ masd = masd[len(os.sep):]
+ self.output.warning(f"Python libdir candidate thingy: {libdir}")
+ libdir = os.path.join(libdir, masd)
+
+ if not libdir:
+ libdest = self._get_python_var("LIBDEST")
+ libdir = os.path.join(os.path.dirname(libdest), "libs")
+
+ candidates = [ldlibrary, library]
+ library_prefixes = [""] if is_msvc(self) else ["", "lib"]
+ library_suffixes = [".lib"] if is_msvc(self) else [".so", ".dll.a", ".a"]
+ if with_dyld:
+ library_suffixes.insert(0, ".dylib")
+
+ python_version = self._python_version
+ python_version_no_dot = python_version.replace(".", "")
+ versions = ["", python_version, python_version_no_dot]
+ abiflags = self._python_abiflags
+
+ for prefix in library_prefixes:
+ for suffix in library_suffixes:
+ for version in versions:
+ candidates.append(f"{prefix}python{version}{abiflags}{suffix}")
+
+ for candidate in candidates:
+ if candidate:
+ python_lib = os.path.join(libdir, candidate)
+ self.output.info(f"checking {python_lib}")
+ if os.path.isfile(python_lib):
+ self.output.info(f"found python library: {python_lib}")
+ return libdir.replace("\\", "/")
+ raise ConanInvalidConfiguration("couldn't locate python libraries - make sure you have installed python development files")
+
+ def _clean(self):
+ clean_dirs = [
+ os.path.join(self.build_folder, "bin.v2"),
+ os.path.join(self.build_folder, "architecture"),
+ os.path.join(self.source_folder, self._bcp_dir),
+ os.path.join(self.source_folder, "dist", "bin"),
+ os.path.join(self.source_folder, "stage"),
+ os.path.join(self.source_folder, "tools", "build", "src", "engine", "bootstrap"),
+ os.path.join(self.source_folder, "tools", "build", "src", "engine", "bin.ntx86"),
+ os.path.join(self.source_folder, "tools", "build", "src", "engine", "bin.ntx86_64"),
+ ]
+ for d in clean_dirs:
+ if os.path.isdir(d):
+ self.output.warning(f"removing '{d}'")
+ shutil.rmtree(d)
+
+ @property
+ def _b2_exe(self):
+ return "b2"
+
+ @property
+ def _bcp_exe(self):
+ folder = os.path.join(self.source_folder, "dist", "bin")
+ return os.path.join(folder, "bcp")
+
+ @property
+ def _use_bcp(self):
+ return self.options.namespace != "boost"
+
+ @property
+ def _boost_build_dir(self):
+ return os.path.join(self.source_folder, "tools", "build")
+
+ def _build_bcp(self):
+ folder = os.path.join(self.source_folder, "tools", "bcp")
+ with chdir(self, folder):
+ command = f"{self._b2_exe} -j{build_jobs(self)} --abbreviate-paths toolset={self._toolset}"
+ command += f" -d{self.options.debug_level}"
+ self.output.warning(command)
+ self.run(command)
+
+ def _run_bcp(self):
+ with chdir(self, self.source_folder):
+ mkdir(self, self._bcp_dir)
+ namespace = f"--namespace={self.options.namespace}"
+ alias = "--namespace-alias" if self.options.namespace_alias else ""
+ boostdir = f"--boost={self.source_folder}"
+ libraries = {"build", "boost-build.jam", "boostcpp.jam", "boost_install", "headers"}
+ for d in os.listdir(os.path.join(self.source_folder, "boost")):
+ if os.path.isdir(os.path.join(self.source_folder, "boost", d)):
+ libraries.add(d)
+ for d in os.listdir(os.path.join(self.source_folder, "libs")):
+ if os.path.isdir(os.path.join(self.source_folder, "libs", d)):
+ libraries.add(d)
+ libraries = " ".join(libraries)
+ command = f"{self._bcp_exe} {namespace} {alias} {boostdir} {libraries} {self._bcp_dir}"
+ self.output.warning(command)
+ self.run(command)
+
+ def build(self):
+ stacktrace_jamfile = os.path.join(self.source_folder, "libs", "stacktrace", "build", "Jamfile.v2")
+ if cross_building(self, skip_x64_x86=True):
+ # When cross building, do not attempt to run the test-executable (assume they work)
+ replace_in_file(self, stacktrace_jamfile, "$(>) > $(<)", "echo \"\" > $(<)", strict=False)
+ if self._with_stacktrace_backtrace and self.settings.os != "Windows" and not cross_building(self):
+ # When libbacktrace is shared, give extra help to the test-executable
+ linker_var = "DYLD_LIBRARY_PATH" if self.settings.os == "Macos" else "LD_LIBRARY_PATH"
+ libbacktrace_libdir = self.dependencies["libbacktrace"].cpp_info.aggregated_components().libdirs[0]
+ patched_run_rule = f"{linker_var}={libbacktrace_libdir} $(>) > $(<)"
+ replace_in_file(self, stacktrace_jamfile, "$(>) > $(<)", patched_run_rule, strict=False)
+ if self.dependencies["libbacktrace"].options.shared:
+ replace_in_file(self, stacktrace_jamfile, "static", "shared", strict=False)
+
+ # Older clang releases require a thread_local variable to be initialized by a constant value
+ replace_in_file(self, os.path.join(self.source_folder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
+ "/* thread_local */", "thread_local", strict=False)
+ replace_in_file(self, os.path.join(self.source_folder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
+ "/* static __thread */", "static __thread", strict=False)
+ if self.settings.compiler == "apple-clang" or (self.settings.compiler == "clang" and Version(self.settings.compiler.version) < 6):
+ replace_in_file(self, os.path.join(self.source_folder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
+ "thread_local", "/* thread_local */")
+ replace_in_file(self, os.path.join(self.source_folder, "boost", "stacktrace", "detail", "libbacktrace_impls.hpp"),
+ "static __thread", "/* static __thread */")
+ replace_in_file(self, os.path.join(self.source_folder, "tools", "build", "src", "tools", "gcc.jam"),
+ "local generic-os = [ set.difference $(all-os) : aix darwin vxworks solaris osf hpux ] ;",
+ "local generic-os = [ set.difference $(all-os) : aix darwin vxworks solaris osf hpux iphone appletv ] ;",
+ strict=False)
+ replace_in_file(self, os.path.join(self.source_folder, "tools", "build", "src", "tools", "gcc.jam"),
+ "local no-threading = android beos haiku sgi darwin vxworks ;",
+ "local no-threading = android beos haiku sgi darwin vxworks iphone appletv ;",
+ strict=False)
+ replace_in_file(self, os.path.join(self.source_folder, "libs", "fiber", "build", "Jamfile.v2"),
+ " @numa",
+ " shared:.//boost_fiber : @numa",
+ strict=False)
+ if self.settings.os == "Android":
+ # force versionless soname from boostorg/boost#206
+ # this can be applied to all versions and it's easier with a replace
+ replace_in_file(self, os.path.join(self.source_folder, "boostcpp.jam"),
+ "! [ $(property-set).get ] in windows cygwin darwin aix &&",
+ "! [ $(property-set).get ] in windows cygwin darwin aix android &&",
+ strict=False)
+
+ if self.options.header_only:
+ self.output.warning("Header only package, skipping build")
+ return
+
+ self._clean()
+
+ if self._use_bcp:
+ self._build_bcp()
+ self._run_bcp()
+
+ self._create_user_config_jam(self._boost_build_dir)
+
+ # JOIN ALL FLAGS
+ b2_flags = " ".join(self._build_flags)
+ full_command = f"{self._b2_exe} {b2_flags}"
+ # -d2 is to print more debug info and avoid travis timing out without output
+ sources = os.path.join(self.source_folder, self._bcp_dir) if self._use_bcp else self.source_folder
+ full_command += f' --debug-configuration --build-dir="{self.build_folder}"'
+ self.output.warning(full_command)
+
+ # If sending a user-specified toolset to B2, setting the vcvars
+ # interferes with the compiler selection.
+ with chdir(self, sources):
+ # To show the libraries *1
+ # self.run("%s --show-libraries" % b2_exe)
+ self.run(full_command)
+
+ @property
+ def _b2_os(self):
+ return {
+ "Windows": "windows",
+ "WindowsStore": "windows",
+ "Linux": "linux",
+ "Android": "android",
+ "Macos": "darwin",
+ "iOS": "iphone",
+ "watchOS": "iphone",
+ "tvOS": "appletv",
+ "FreeBSD": "freebsd",
+ "SunOS": "solaris",
+ }.get(str(self.settings.os))
+
+ @property
+ def _b2_address_model(self):
+ if self.settings.arch in ("x86_64", "ppc64", "ppc64le", "mips64", "armv8", "armv8.3", "sparcv9"):
+ return "64"
+
+ return "32"
+
+ @property
+ def _b2_binary_format(self):
+ return {
+ "Windows": "pe",
+ "WindowsStore": "pe",
+ "Linux": "elf",
+ "Android": "elf",
+ "Macos": "mach-o",
+ "iOS": "mach-o",
+ "watchOS": "mach-o",
+ "tvOS": "mach-o",
+ "FreeBSD": "elf",
+ "SunOS": "elf",
+ }.get(str(self.settings.os))
+
+ @property
+ def _b2_architecture(self):
+ if str(self.settings.arch).startswith("x86"):
+ return "x86"
+ if str(self.settings.arch).startswith("ppc"):
+ return "power"
+ if str(self.settings.arch).startswith("arm"):
+ return "arm"
+ if str(self.settings.arch).startswith("sparc"):
+ return "sparc"
+ if str(self.settings.arch).startswith("mips64"):
+ return "mips64"
+ if str(self.settings.arch).startswith("mips"):
+ return "mips1"
+ if str(self.settings.arch).startswith("s390"):
+ return "s390x"
+
+ return None
+
+ @property
+ def _b2_abi(self):
+ if str(self.settings.arch).startswith("x86"):
+ return "ms" if str(self.settings.os) in ["Windows", "WindowsStore"] else "sysv"
+ if str(self.settings.arch).startswith("ppc"):
+ return "sysv"
+ if str(self.settings.arch).startswith("arm"):
+ return "aapcs"
+ if str(self.settings.arch).startswith("mips"):
+ return "o32"
+
+ return None
+
+ @property
+ def _gnu_cxx11_abi(self):
+ """Checks libcxx setting and returns value for the GNU C++11 ABI flag
+ _GLIBCXX_USE_CXX11_ABI= . Returns None if C++ library cannot be
+ determined.
+ """
+ try:
+ if str(self.settings.compiler.libcxx) == "libstdc++":
+ return "0"
+ if str(self.settings.compiler.libcxx) == "libstdc++11":
+ return "1"
+ except ConanException:
+ pass
+ return None
+
+ @property
+ def _build_flags(self):
+ flags = self._build_cross_flags
+
+ # 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 not self._is_apple_embedded_platform and self._b2_os:
+ flags.append(f"target-os={self._b2_os}")
+ if self._b2_architecture:
+ flags.append(f"architecture={self._b2_architecture}")
+ if self._b2_address_model:
+ flags.append(f"address-model={self._b2_address_model}")
+ if self._b2_binary_format:
+ flags.append(f"binary-format={self._b2_binary_format}")
+ if self._b2_abi:
+ flags.append(f"abi={self._b2_abi}")
+
+ flags.append(f"--layout={self.options.layout}")
+ flags.append(f"--user-config={os.path.join(self._boost_build_dir, 'user-config.jam')}")
+ flags.append(f"-sNO_ZLIB={'0' if self._with_zlib else '1'}")
+ flags.append(f"-sNO_BZIP2={'0' if self._with_bzip2 else '1'}")
+ flags.append(f"-sNO_LZMA={'0' if self._with_lzma else '1'}")
+ flags.append(f"-sNO_ZSTD={'0' if self._with_zstd else '1'}")
+
+ if self.options.get_safe("i18n_backend_icu"):
+ flags.append("boost.locale.icu=on")
+ else:
+ flags.append("boost.locale.icu=off")
+ flags.append("--disable-icu")
+ if self.options.get_safe("i18n_backend_iconv") in ["libc", "libiconv"]:
+ flags.append("boost.locale.iconv=on")
+ if self.options.get_safe("i18n_backend_iconv") == "libc":
+ flags.append("boost.locale.iconv.lib=libc")
+ else:
+ flags.append("boost.locale.iconv.lib=libiconv")
+ else:
+ flags.append("boost.locale.iconv=off")
+ flags.append("--disable-iconv")
+
+ def add_defines(library):
+ for define in self.dependencies[library].cpp_info.aggregated_components().defines:
+ flags.append(f"define={define}")
+
+ if self._with_zlib:
+ add_defines("zlib")
+ if self._with_bzip2:
+ add_defines("bzip2")
+ if self._with_lzma:
+ add_defines("xz_utils")
+ if self._with_zstd:
+ add_defines("zstd")
+
+ if is_msvc(self):
+ flags.append(f"runtime-link={'static' if is_msvc_static_runtime(self) else 'shared'}")
+ flags.append(f"runtime-debugging={'on' if 'd' in msvc_runtime_flag(self) else 'off'}")
+
+ # For details https://boostorg.github.io/build/manual/master/index.html
+ flags.append(f"threading={'single' if not self.options.multithreading else 'multi'}")
+ flags.append(f"visibility={self.options.visibility}")
+
+ flags.append(f"link={'shared' if self._shared else 'static'}")
+ if self.settings.build_type == "Debug":
+ flags.append("variant=debug")
+ else:
+ flags.append("variant=release")
+
+ for libname in self._configure_options:
+ if not getattr(self.options, f"without_{libname}"):
+ flags.append(f"--with-{libname}")
+
+ flags.append(f"toolset={self._toolset}")
+
+ safe_cppstd = self.settings.get_safe("compiler.cppstd")
+ if safe_cppstd:
+ cppstd_version = safe_cppstd.replace("gnu", "")
+ flags.append(f"cxxstd={cppstd_version}")
+ if "gnu" in safe_cppstd:
+ flags.append("cxxstd-dialect=gnu")
+ elif self._has_cppstd_11_supported:
+ flags.append("cxxstd=11")
+
+ # LDFLAGS
+ link_flags = []
+
+ # CXX FLAGS
+ cxx_flags = []
+ # fPIC DEFINITION
+ if self._fPIC:
+ cxx_flags.append("-fPIC")
+ if self.settings.build_type == "RelWithDebInfo":
+ if self.settings.compiler == "gcc" or "clang" in str(self.settings.compiler):
+ cxx_flags.append("-g")
+ elif is_msvc(self):
+ cxx_flags.append("/Z7")
+
+ # Standalone toolchain fails when declare the std lib
+ if self.settings.os not in ("Android", "Emscripten"):
+ try:
+ if self._gnu_cxx11_abi:
+ flags.append(f"define=_GLIBCXX_USE_CXX11_ABI={self._gnu_cxx11_abi}")
+
+ if self.settings.compiler in ("clang", "apple-clang"):
+ libcxx = {
+ "libstdc++11": "libstdc++",
+ }.get(str(self.settings.compiler.libcxx), str(self.settings.compiler.libcxx))
+ cxx_flags.append(f"-stdlib={libcxx}")
+ link_flags.append(f"-stdlib={libcxx}")
+ except ConanException:
+ pass
+
+ if self.options.error_code_header_only:
+ flags.append("define=BOOST_ERROR_CODE_HEADER_ONLY=1")
+ if self.options.system_no_deprecated:
+ flags.append("define=BOOST_SYSTEM_NO_DEPRECATED=1")
+ if self.options.asio_no_deprecated:
+ flags.append("define=BOOST_ASIO_NO_DEPRECATED=1")
+ if self.options.filesystem_no_deprecated:
+ flags.append("define=BOOST_FILESYSTEM_NO_DEPRECATED=1")
+ if self.options.filesystem_use_std_fs:
+ flags.append("define=BOOST_DLL_USE_STD_FS=1")
+ if self.options.system_use_utf8:
+ flags.append("define=BOOST_SYSTEM_USE_UTF8=1")
+ if self.options.segmented_stacks:
+ flags.extend(["segmented-stacks=on",
+ "define=BOOST_USE_SEGMENTED_STACKS=1",
+ "define=BOOST_USE_UCONTEXT=1"])
+ flags.append("pch=on" if self.options.pch else "pch=off")
+
+ if is_apple_os(self):
+ apple_min_version_flag = AutotoolsToolchain(self).apple_min_version_flag
+ if apple_min_version_flag:
+ cxx_flags.append(apple_min_version_flag)
+ link_flags.append(apple_min_version_flag)
+ os_subsystem = self.settings.get_safe("os.subsystem")
+ if os_subsystem == "catalyst":
+ cxx_flags.append("--target=arm64-apple-ios-macabi")
+ link_flags.append("--target=arm64-apple-ios-macabi")
+
+ if self.settings.os == "iOS":
+ if self.options.multithreading:
+ cxx_flags.append("-DBOOST_SP_USE_SPINLOCK")
+
+ if self.conf.get("tools.apple:enable_bitcode", check_type=bool):
+ cxx_flags.append("-fembed-bitcode")
+ if self._with_stacktrace_backtrace:
+ flags.append(f"-sLIBBACKTRACE_PATH={self.dependencies['libbacktrace'].package_folder}")
+ if self._with_iconv:
+ flags.append(f"-sICONV_PATH={self.dependencies['libiconv'].package_folder}")
+ if self._with_icu:
+ flags.append(f"-sICU_PATH={self.dependencies['icu'].package_folder}")
+ if not self.dependencies["icu"].options.shared:
+ # Using ICU_OPTS to pass ICU system libraries is not possible due to Boost.Regex disallowing it.
+ icu_system_libs = self.dependencies["icu"].cpp_info.aggregated_components().system_libs
+ if is_msvc(self):
+ icu_ldflags = " ".join(f"{l}.lib" for l in icu_system_libs)
+ else:
+ icu_ldflags = " ".join(f"-l{l}" for l in icu_system_libs)
+ link_flags.append(icu_ldflags)
+
+ link_flags = f'linkflags="{" ".join(link_flags)}"'
+ flags.append(link_flags)
+
+ if self.options.get_safe("addr2line_location"):
+ cxx_flags.append(f"-DBOOST_STACKTRACE_ADDR2LINE_LOCATION={self.options.addr2line_location}")
+
+ cxx_flags = f'cxxflags="{" ".join(cxx_flags)}"'
+ flags.append(cxx_flags)
+
+ if self.options.buildid:
+ flags.append(f"--buildid={self.options.buildid}")
+ if not self.options.without_python and self.options.python_buildid:
+ flags.append(f"--python-buildid={self.options.python_buildid}")
+
+ if self.options.extra_b2_flags:
+ flags.extend(shlex.split(str(self.options.extra_b2_flags)))
+
+ flags.extend([
+ "install",
+ f"--prefix={self.package_folder}",
+ f"-j{build_jobs(self)}",
+ "--abbreviate-paths",
+ f"-d{self.options.debug_level}",
+ ])
+ return flags
+
+ @property
+ def _build_cross_flags(self):
+ flags = []
+ if not cross_building(self):
+ return flags
+ arch = self.settings.get_safe("arch")
+ self.output.info("Cross building, detecting compiler...")
+
+ if arch.startswith("arm"):
+ if "hf" in arch:
+ flags.append("-mfloat-abi=hard")
+ elif self.settings.os == "Emscripten":
+ pass
+ elif arch in ["x86", "x86_64"]:
+ pass
+ elif arch.startswith("ppc"):
+ pass
+ elif arch.startswith("mips"):
+ pass
+ else:
+ self.output.warning(f"Unable to detect the appropriate ABI for {arch} architecture.")
+ self.output.info(f"Cross building flags: {flags}")
+
+ return flags
+
+ @property
+ def _ar(self):
+ ar = VirtualBuildEnv(self).vars().get("AR")
+ if ar:
+ return ar
+ if is_apple_os(self) and self.settings.compiler == "apple-clang":
+ return XCRun(self).ar
+ return None
+
+ @property
+ def _ranlib(self):
+ ranlib = VirtualBuildEnv(self).vars().get("RANLIB")
+ if ranlib:
+ return ranlib
+ if is_apple_os(self) and self.settings.compiler == "apple-clang":
+ return XCRun(self).ranlib
+ return None
+
+ @property
+ def _cxx(self):
+ compilers_by_conf = self.conf.get("tools.build:compiler_executables", default={}, check_type=dict)
+ cxx = compilers_by_conf.get("cpp") or VirtualBuildEnv(self).vars().get("CXX")
+ if cxx:
+ return cxx
+ if is_apple_os(self) and self.settings.compiler == "apple-clang":
+ return XCRun(self).cxx
+ compiler_version = str(self.settings.compiler.version)
+ major = compiler_version.split(".", maxsplit=1)[0]
+ if self.settings.compiler == "gcc":
+ return shutil.which(f"g++-{compiler_version}") or shutil.which(f"g++-{major}") or shutil.which("g++") or ""
+ if self.settings.compiler == "clang":
+ return shutil.which(f"clang++-{compiler_version}") or shutil.which(f"clang++-{major}") or shutil.which("clang++") or ""
+ return ""
+
+ def _create_user_config_jam(self, folder):
+ self.output.warning("Patching user-config.jam")
+
+ def create_library_config(deps_name, name):
+ aggregated_cpp_info = self.dependencies[deps_name].cpp_info.aggregated_components()
+ includedir = aggregated_cpp_info.includedirs[0].replace("\\", "/")
+ includedir = f"\"{includedir}\""
+ libdir = aggregated_cpp_info.libdirs[0].replace("\\", "/")
+ libdir = f"\"{libdir}\""
+ lib = aggregated_cpp_info.libs[0]
+ version = self.dependencies[deps_name].ref.version
+ return f"\nusing {name} : {version} : " \
+ f"{includedir} " \
+ f"{libdir} " \
+ f"{lib} ;"
+
+ contents = ""
+
+ if self._with_zlib:
+ contents += create_library_config("zlib", "zlib")
+ if self._with_bzip2:
+ contents += create_library_config("bzip2", "bzip2")
+ if self._with_lzma:
+ contents += create_library_config("xz_utils", "lzma")
+ if self._with_zstd:
+ contents += create_library_config("zstd", "zstd")
+
+ if not self.options.without_python:
+ # https://www.boost.org/doc/libs/1_70_0/libs/python/doc/html/building/configuring_boost_build.html
+ contents += f'\nusing python : {self._python_version} : "{self._python_executable}" : "{self._python_includes}" : "{self._python_library_dir}" ;'
+
+ if not self.options.without_mpi:
+ # https://www.boost.org/doc/libs/1_72_0/doc/html/mpi/getting_started.html
+ contents += "\nusing mpi ;"
+
+ # Specify here the toolset with the binary if present if don't empty parameter :
+ contents += f'\nusing "{self._toolset}" : {self._toolset_version} : '
+
+ cxx_fwd_slahes = self._cxx.replace("\\", "/")
+ if cxx_fwd_slahes:
+ contents += f" \"{cxx_fwd_slahes}\""
+
+ if is_apple_os(self):
+ if self.settings.compiler == "apple-clang":
+ contents += f" -isysroot {XCRun(self).sdk_path}"
+ if self.settings.get_safe("arch"):
+ contents += f" -arch {to_apple_arch(self)}"
+
+ contents += " : \n"
+ if self._ar:
+ ar_path = self._ar.replace("\\", "/")
+ contents += f'"{ar_path}" '
+ if self._ranlib:
+ ranlib_path = self._ranlib.replace("\\", "/")
+ contents += f'"{ranlib_path}" '
+ cxxflags = " ".join(self.conf.get("tools.build:cxxflags", default=[], check_type=list)) + " "
+ cflags = " ".join(self.conf.get("tools.build:cflags", default=[], check_type=list)) + " "
+ buildenv_vars = VirtualBuildEnv(self).vars()
+ cppflags = buildenv_vars.get("CPPFLAGS", "") + " "
+ ldflags = " ".join(self.conf.get("tools.build:sharedlinkflags", default=[], check_type=list)) + " "
+ asflags = buildenv_vars.get("ASFLAGS", "") + " "
+
+ sysroot = self.conf.get("tools.build:sysroot")
+ if sysroot and not is_msvc(self):
+ sysroot = sysroot.replace("\\", "/")
+ sysroot = f'"{sysroot}"' if ' ' in sysroot else sysroot
+ cppflags += f"--sysroot={sysroot} "
+ ldflags += f"--sysroot={sysroot} "
+
+ if self._with_stacktrace_backtrace:
+ backtrace_aggregated_cpp_info = self.dependencies["libbacktrace"].cpp_info.aggregated_components()
+ cppflags += " ".join(f"-I{p}" for p in backtrace_aggregated_cpp_info.includedirs) + " "
+ ldflags += " ".join(f"-L{p}" for p in backtrace_aggregated_cpp_info.libdirs) + " "
+
+ if cxxflags.strip():
+ contents += f'"{cxxflags.strip()}" '
+ if cflags.strip():
+ contents += f'"{cflags.strip()}" '
+ if cppflags.strip():
+ contents += f'"{cppflags.strip()}" '
+ if ldflags.strip():
+ contents += f'"{ldflags.strip()}" '
+ if asflags.strip():
+ contents += f'"{asflags.strip()}" '
+
+ if self._is_apple_embedded_platform:
+ contents += f'"{self._b2_os}" '
+
+ contents += " ;"
+
+ self.output.warning(contents)
+ filename = f"{folder}/user-config.jam"
+ save(self, filename, contents)
+
+ @property
+ def _toolset_version(self):
+ toolset = MSBuildToolchain(self).toolset
+ if toolset:
+ match = re.match(r"v(\d+)(\d)$", toolset)
+ if match:
+ return f"{match.group(1)}.{match.group(2)}"
+ return ""
+
+ @property
+ def _toolset(self):
+ if is_msvc(self):
+ return "clang-win" if self.settings.compiler.get_safe("toolset") == "ClangCL" else "msvc"
+ if self.settings.os == "Windows" and self.settings.compiler == "clang":
+ return "clang-win"
+ if self.settings.os == "Emscripten" and self.settings.compiler == "clang":
+ return "emscripten"
+ if self.settings.compiler == "gcc" and is_apple_os(self):
+ return "darwin"
+ if self.settings.compiler == "apple-clang":
+ return "clang-darwin"
+ if self.settings.os == "Android" and self.settings.compiler == "clang":
+ return "clang-linux"
+ if self.settings.compiler in ["clang", "gcc"]:
+ return str(self.settings.compiler)
+ if self.settings.compiler == "sun-cc":
+ return "sunpro"
+ if self.settings.compiler == "intel":
+ return {
+ "Macos": "intel-darwin",
+ "Windows": "intel-win",
+ "Linux": "intel-linux",
+ }[str(self.settings.os)]
+
+ return str(self.settings.compiler)
+
+ @property
+ def _toolset_tag(self):
+ # compiler | compiler.version | os | toolset_tag | remark
+ # ---------------+------------------+-------------+----------------+-----------------------------
+ # apple-clang | 12 | Macos | darwin12 |
+ # clang | 12 | Macos | clang-darwin12 |
+ # gcc | 11 | Linux | gcc8 |
+ # gcc | 8 | Windows | mgw8 |
+ # Visual Studio | 17 | Windows | vc142 | depends on compiler.toolset
+ compiler = {
+ "apple-clang": "",
+ "Visual Studio": "vc",
+ "msvc": "vc",
+ }.get(str(self.settings.compiler), str(self.settings.compiler))
+ if (self.settings.compiler, self.settings.os) == ("gcc", "Windows"):
+ compiler = "mgw"
+ os_ = ""
+ if self.settings.os == "Macos":
+ os_ = "darwin"
+ if is_msvc(self):
+ toolset_version = self._toolset_version.replace(".", "")
+ else:
+ toolset_version = str(Version(self.settings.compiler.version).major)
+
+ toolset_parts = [compiler, os_]
+ toolset_tag = "-".join(part for part in toolset_parts if part) + toolset_version
+ return toolset_tag
+
+ ####################################################################
+
+ def package(self):
+ # This stage/lib is in source_folder... Face palm, looks like it builds in build but then
+ # copy to source with the good lib name
+ copy(self, "LICENSE_1_0.txt", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
+ rmdir(self, os.path.join(self.package_folder, "lib", "cmake"))
+ if self.options.header_only:
+ copy(self, "*", src=os.path.join(self.source_folder, "boost"),
+ dst=os.path.join(self.package_folder, "include", "boost"))
+
+ if self.settings.os == "Emscripten" and not self.options.header_only:
+ self._create_emscripten_libs()
+
+ if is_msvc(self) and self._shared:
+ # Some boost releases contain both static and shared variants of some libraries (if shared=True)
+ all_libs = set(collect_libs(self, "lib"))
+ static_libs = set(l for l in all_libs if l.startswith("lib"))
+ shared_libs = all_libs.difference(static_libs)
+ static_libs = set(l[3:] for l in static_libs)
+ common_libs = static_libs.intersection(shared_libs)
+ for common_lib in common_libs:
+ common_lib_fullname = f"lib{common_lib}.lib"
+ self.output.info(f'Unlinking static duplicate library: {os.path.join(self.package_folder, "lib", common_lib_fullname)}')
+ os.unlink(os.path.join(self.package_folder, "lib", common_lib_fullname))
+
+ dll_pdbs = glob.glob(os.path.join(self.package_folder, "lib", "*.dll")) + \
+ glob.glob(os.path.join(self.package_folder, "lib", "*.pdb"))
+ if dll_pdbs:
+ mkdir(self, os.path.join(self.package_folder, "bin"))
+ for bin_file in dll_pdbs:
+ rename(self, bin_file, os.path.join(self.package_folder, "bin", os.path.basename(bin_file)))
+
+ rm(self, "*.pdb", os.path.join(self.package_folder, "bin"))
+
+ def _create_emscripten_libs(self):
+ # Boost Build doesn't create the libraries, but it gets close,
+ # leaving .bc files where the libraries would be.
+ staged_libs = os.path.join(
+ self.package_folder, "lib"
+ )
+ if not os.path.exists(staged_libs):
+ self.output.warning(f"Lib folder doesn't exist, can't collect libraries: {staged_libs}")
+ return
+ for bc_file in os.listdir(staged_libs):
+ if bc_file.startswith("lib") and bc_file.endswith(".bc"):
+ a_file = bc_file[:-3] + ".a"
+ cmd = f"emar q {os.path.join(staged_libs, a_file)} {os.path.join(staged_libs, bc_file)}"
+ self.output.info(cmd)
+ self.run(cmd)
+
+ @staticmethod
+ def _option_to_conan_requirement(name):
+ return {
+ "lzma": "xz_utils",
+ "iconv": "libiconv",
+ "python": None, # FIXME: change to cpython when it becomes available
+ }.get(name, name)
+
+ def package_info(self):
+ self.env_info.BOOST_ROOT = self.package_folder
+
+ self.cpp_info.set_property("cmake_file_name", "Boost")
+ self.cpp_info.filenames["cmake_find_package"] = "Boost"
+ self.cpp_info.filenames["cmake_find_package_multi"] = "Boost"
+ self.cpp_info.names["cmake_find_package"] = "Boost"
+ self.cpp_info.names["cmake_find_package_multi"] = "Boost"
+
+ # - Use 'headers' component for all includes + defines
+ # - Use '_libboost' component to attach extra system_libs, ...
+
+ self.cpp_info.components["headers"].libs = []
+ self.cpp_info.components["headers"].libdirs = []
+ self.cpp_info.components["headers"].set_property("cmake_target_name", "Boost::headers")
+ 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")
+
+ if self.options.asio_no_deprecated:
+ self.cpp_info.components["headers"].defines.append("BOOST_ASIO_NO_DEPRECATED")
+
+ if self.options.filesystem_no_deprecated:
+ self.cpp_info.components["headers"].defines.append("BOOST_FILESYSTEM_NO_DEPRECATED")
+
+ if self.options.filesystem_use_std_fs:
+ self.cpp_info.components["headers"].defines.append("BOOST_DLL_USE_STD_FS")
+
+ if self.options.filesystem_version:
+ self.cpp_info.components["headers"].defines.append(f"BOOST_FILESYSTEM_VERSION={self.options.filesystem_version}")
+
+ if self.options.segmented_stacks:
+ self.cpp_info.components["headers"].defines.extend(["BOOST_USE_SEGMENTED_STACKS", "BOOST_USE_UCONTEXT"])
+
+ if self.options.system_use_utf8:
+ self.cpp_info.components["headers"].defines.append("BOOST_SYSTEM_USE_UTF8")
+
+ if self.options.buildid:
+ # If you built Boost using the --buildid option then set this macro to the same value
+ # as you passed to bjam.
+ # For example if you built using bjam address-model=64 --buildid=amd64 then compile your code with
+ # -DBOOST_LIB_BUILDID=amd64 to ensure the correct libraries are selected at link time.
+ self.cpp_info.components["headers"].defines.append(f"BOOST_LIB_BUILDID={self.options.buildid}")
+
+ if not self.options.header_only:
+ if self.options.error_code_header_only:
+ self.cpp_info.components["headers"].defines.append("BOOST_ERROR_CODE_HEADER_ONLY")
+
+ if self.options.layout == "versioned":
+ version = Version(self.version)
+ self.cpp_info.components["headers"].includedirs.append(os.path.join("include", f"boost-{version.major}_{version.minor}"))
+
+ # Boost::boost is an alias of Boost::headers
+ self.cpp_info.components["_boost_cmake"].requires = ["headers"]
+ self.cpp_info.components["_boost_cmake"].set_property("cmake_target_name", "Boost::boost")
+ self.cpp_info.components["_boost_cmake"].names["cmake_find_package"] = "boost"
+ self.cpp_info.components["_boost_cmake"].names["cmake_find_package_multi"] = "boost"
+ if self.options.header_only:
+ self.cpp_info.components["_boost_cmake"].libdirs = []
+
+ if not self.options.header_only:
+ self.cpp_info.components["_libboost"].requires = ["headers"]
+
+ self.cpp_info.components["diagnostic_definitions"].libs = []
+ self.cpp_info.components["diagnostic_definitions"].set_property("cmake_target_name", "Boost::diagnostic_definitions")
+ 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
+ # I would assume headers also need the define BOOST_LIB_DIAGNOSTIC, as a header can trigger an autolink,
+ # and this definition triggers a print out of the library selected. See notes below on autolink and headers.
+ self.cpp_info.components["headers"].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"].set_property("cmake_target_name", "Boost::disable_autolinking")
+ 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
+
+ # Even headers needs to know the flags for disabling autolinking ...
+ # magic_autolink is an option in the recipe, so if a consumer wants this version of boost,
+ # then they should not get autolinking.
+ # Note that autolinking can sneak in just by some file #including a header with (eg) boost/atomic.hpp,
+ # even if it doesn't use any part that requires linking with libboost_atomic in order to compile.
+ # So a boost-header-only library that links to Boost::headers needs to see BOOST_ALL_NO_LIB
+ # in order to avoid autolinking to libboost_atomic
+
+ # This define is already imported into all of the _libboost libraries from this recipe anyway,
+ # so it would be better to be consistent and ensure ANYTHING using boost (headers or libs) has consistent #defines.
+
+ # Same applies for for BOOST_AUTO_LINK_{layout}:
+ # consumer libs that use headers also need to know what is the layout/filename of the libraries.
+ #
+ # eg, if using the "tagged" naming scheme, and a header triggers an autolink,
+ # then that header's autolink request had better be configured to request the "tagged" library name.
+ # Otherwise, the linker will be looking for a (eg) "versioned" library name, and there will be a link error.
+
+ # Note that "_libboost" requires "headers" so these defines will be applied to all the libraries too.
+ self.cpp_info.components["headers"].requires.append("disable_autolinking")
+ if is_msvc(self) or self._is_clang_cl:
+ if self.options.magic_autolink:
+ if self.options.layout == "system":
+ self.cpp_info.components["headers"].defines.append("BOOST_AUTO_LINK_SYSTEM")
+ elif self.options.layout == "tagged":
+ self.cpp_info.components["headers"].defines.append("BOOST_AUTO_LINK_TAGGED")
+ self.output.info("Enabled magic autolinking (smart and magic decisions)")
+ else:
+ # DISABLES AUTO LINKING! NO SMART AND MAGIC DECISIONS THANKS!
+ self.cpp_info.components["disable_autolinking"].defines = ["BOOST_ALL_NO_LIB"]
+ self.output.info("Disabled magic autolinking (smart and magic decisions)")
+
+ self.cpp_info.components["dynamic_linking"].libs = []
+ self.cpp_info.components["dynamic_linking"].set_property("cmake_target_name", "Boost::dynamic_linking")
+ 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
+ # A library that only links to Boost::headers can be linked into another library that links a Boost::library,
+ # so for this reasons, the header-only library should know the BOOST_ALL_DYN_LINK definition as it will likely
+ # change some important part of the boost code and cause linking errors downstream.
+ # This is in the same theme as the notes above, re autolinking.
+ self.cpp_info.components["headers"].requires.append("dynamic_linking")
+ if self._shared:
+ # A Boost::dynamic_linking cmake target does only make sense for a shared boost package
+ self.cpp_info.components["dynamic_linking"].defines = ["BOOST_ALL_DYN_LINK"]
+
+ # https://www.boost.org/doc/libs/1_73_0/more/getting_started/windows.html#library-naming
+ # libsuffix for MSVC:
+ # - system: ""
+ # - versioned: "-vc142-mt-d-x64-1_74"
+ # - tagged: "-mt-d-x64"
+ libsuffix_lut = {
+ "system": "",
+ "versioned": "{toolset}{threading}{abi}{arch}{version}",
+ "tagged": "{threading}{abi}{arch}",
+ }
+ libsuffix_data = {
+ "toolset": f"-{self._toolset_tag}",
+ "threading": "-mt" if self.options.multithreading else "",
+ "abi": "",
+ "ach": "",
+ "version": "",
+ }
+ if is_msvc(self): # FIXME: mingw?
+ # FIXME: add 'y' when using cpython cci package and when python is built in debug mode
+ static_runtime_key = "s" if is_msvc_static_runtime(self) else ""
+ debug_runtime_key = "g" if "d" in msvc_runtime_flag(self) else ""
+ debug_key = "d" if self.settings.build_type == "Debug" else ""
+ abi = static_runtime_key + debug_runtime_key + debug_key
+ if abi:
+ libsuffix_data["abi"] = f"-{abi}"
+ else:
+ debug_tag = "d" if self.settings.build_type == "Debug" else ""
+ abi = debug_tag
+ if abi:
+ libsuffix_data["abi"] = f"-{abi}"
+
+ if self._b2_architecture:
+ libsuffix_data["arch"] = f"-{self._b2_architecture[0]}{self._b2_address_model}"
+ version = Version(self.version)
+ if not version.patch or version.patch == "0":
+ libsuffix_data["version"] = f"-{version.major}_{version.minor}"
+ else:
+ libsuffix_data["version"] = f"-{version.major}_{version.minor}_{version.patch}"
+ libsuffix = libsuffix_lut[str(self.options.layout)].format(**libsuffix_data)
+ if libsuffix:
+ self.output.info(f"Library layout suffix: {repr(libsuffix)}")
+
+ libformatdata = {}
+ if not self.options.without_python:
+ pyversion = Version(self._python_version)
+ libformatdata["py_major"] = pyversion.major
+ libformatdata["py_minor"] = pyversion.minor
+
+ def add_libprefix(n):
+ """ On MSVC, static libraries are built with a 'lib' prefix. Some libraries do not support shared, so are always built as a static library. """
+ libprefix = ""
+ if is_msvc(self) and (not self._shared or n in self._dependencies["static_only"]):
+ libprefix = "lib"
+ return libprefix + n
+
+ all_detected_libraries = set(l[:-4] if l.endswith(".dll") else l for l in collect_libs(self))
+ all_expected_libraries = set()
+ incomplete_components = []
+
+ 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_backtrace", "boost_stacktrace_basic",) and self.settings.os == "Windows":
+ continue
+ if name == "boost_stacktrace_addr2line" and not self._stacktrace_addr2line_available:
+ continue
+ if name == "boost_stacktrace_backtrace" and self.options.get_safe("with_stacktrace_backtrace") == False:
+ continue
+ if not self.options.get_safe("numa") and "_numa" in name:
+ continue
+ new_name = add_libprefix(name.format(**libformatdata)) + libsuffix
+ if self.options.namespace != 'boost':
+ new_name = new_name.replace("boost_", str(self.options.namespace) + "_")
+ if name.startswith("boost_python") or name.startswith("boost_numpy"):
+ if self.options.python_buildid:
+ new_name += f"-{self.options.python_buildid}"
+ if self.options.buildid:
+ new_name += f"-{self.options.buildid}"
+ libs.append(new_name)
+ 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(f"without_{depmodule}", False))
+ if missing_depmodules:
+ 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)
+
+ # Starting v1.69.0 Boost.System is header-only. A stub library is
+ # still built for compatibility, but linking to it is no longer
+ # necessary.
+ # https://www.boost.org/doc/libs/1_75_0/libs/system/doc/html/system.html#changes_in_boost_1_69
+ if module == "system":
+ 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].set_property("cmake_target_name", "Boost::" + module)
+ 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"] = f"boost_{module}"
+
+ # extract list of names of direct host dependencies to check for dependencies
+ # of components that exist in other packages
+ dependencies = [d.ref.name for d, _ in self.dependencies.direct_host.items()]
+
+ for requirement in self._dependencies.get("requirements", {}).get(module, []):
+ if self.options.get_safe(requirement, None) == False:
+ continue
+ conan_requirement = self._option_to_conan_requirement(requirement)
+ if conan_requirement not in dependencies:
+ continue
+ if module == "locale" and requirement in ("icu", "iconv"):
+ if requirement == "icu" and not self._with_icu:
+ continue
+ if requirement == "iconv" and not self._with_iconv:
+ continue
+ self.cpp_info.components[module].requires.append(f"{conan_requirement}::{conan_requirement}")
+
+ for incomplete_component in incomplete_components:
+ self.output.warning(f"Boost component '{incomplete_component}' is missing libraries. Try building boost with '-o boost:without_{incomplete_component}'. (Option is not guaranteed to exist)")
+
+ non_used = all_detected_libraries.difference(all_expected_libraries)
+ if non_used:
+ raise ConanException(f"These libraries were built, but were not used in any boost module: {non_used}")
+
+ non_built = all_expected_libraries.difference(all_detected_libraries)
+ if non_built:
+ raise ConanException(f"These libraries were expected to be built, but were not built: {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")
+ if self._stacktrace_addr2line_available:
+ 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([
+ f"BOOST_STACKTRACE_ADDR2LINE_LOCATION=\"{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"].requires.append("libbacktrace::libbacktrace")
+
+ self.cpp_info.components["stacktrace_noop"].defines.append("BOOST_STACKTRACE_USE_NOOP")
+
+ if self.settings.os == "Windows":
+ self.cpp_info.components["stacktrace_windbg"].defines.append("BOOST_STACKTRACE_USE_WINDBG")
+ self.cpp_info.components["stacktrace_windbg"].system_libs.extend(["ole32", "dbgeng"])
+ self.cpp_info.components["stacktrace_windbg_cached"].defines.append("BOOST_STACKTRACE_USE_WINDBG_CACHED")
+ self.cpp_info.components["stacktrace_windbg_cached"].system_libs.extend(["ole32", "dbgeng"])
+ elif is_apple_os(self) or self.settings.os == "FreeBSD":
+ self.cpp_info.components["stacktrace"].defines.append("BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED")
+
+ if not self.options.without_python:
+ pyversion = Version(self._python_version)
+ self.cpp_info.components[f"python{pyversion.major}{pyversion.minor}"].requires = ["python"]
+ if not self._shared:
+ self.cpp_info.components["python"].defines.append("BOOST_PYTHON_STATIC_LIB")
+
+ self.cpp_info.components[f"numpy{pyversion.major}{pyversion.minor}"].requires = ["numpy"]
+
+ if is_msvc(self) or self._is_clang_cl:
+ # https://github.com/conan-community/conan-boost/issues/127#issuecomment-404750974
+ self.cpp_info.components["_libboost"].system_libs.append("bcrypt")
+ elif self.settings.os == "Linux":
+ # https://github.com/conan-community/community/issues/135
+ self.cpp_info.components["_libboost"].system_libs.append("rt")
+ if self.options.multithreading:
+ self.cpp_info.components["_libboost"].system_libs.append("pthread")
+ elif self.settings.os == "Emscripten":
+ if self.options.multithreading:
+ arch = str(self.settings.arch)
+ # https://emscripten.org/docs/porting/pthreads.html
+ # The documentation mentions that we should be using the "-s USE_PTHREADS=1"
+ # but it was causing problems with the target based configurations in conan
+ # So instead we are using the raw compiler flags (that are being activated
+ # from the aforementioned flag)
+ if arch.startswith("x86") or arch.startswith("wasm"):
+ self.cpp_info.components["_libboost"].cxxflags.append("-pthread")
+ self.cpp_info.components["_libboost"].sharedlinkflags.extend(["-pthread","--shared-memory"])
+ self.cpp_info.components["_libboost"].exelinkflags.extend(["-pthread","--shared-memory"])
+ elif self.settings.os == "iOS":
+ if self.options.multithreading:
+ # https://github.com/conan-io/conan-center-index/issues/3867
+ # runtime crashes occur when using the default platform-specific reference counter/atomic
+ # https://github.com/boostorg/filesystem/issues/147
+ # iOS should use spinlocks to avoid filesystem crashes
+ self.cpp_info.components["headers"].defines.append("BOOST_SP_USE_SPINLOCK")
+ else:
+ self.cpp_info.components["headers"].defines.extend(["BOOST_AC_DISABLE_THREADS", "BOOST_SP_DISABLE_THREADS"])
+ #TODO: remove in the future, user_info deprecated in conan2, but kept for compatibility while recipe is cross-compatible.
+ self.user_info.stacktrace_addr2line_available = self._stacktrace_addr2line_available
+ self.conf_info.define("user.boost:stacktrace_addr2line_available", self._stacktrace_addr2line_available)
diff --git a/tests/packagedcode/data/conan/recipes/boost/conanfile.py.ast.expected b/tests/packagedcode/data/conan/recipes/boost/conanfile.py.ast.expected
new file mode 100644
index 00000000000..253cf4f2379
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/boost/conanfile.py.ast.expected
@@ -0,0 +1,22 @@
+{
+ "name": "boost",
+ "version": null,
+ "description": "Boost provides free peer-reviewed portable C++ source libraries",
+ "author": null,
+ "homepage_url": "https://www.boost.org",
+ "vcs_url": "https://github.com/conan-io/conan-center-index",
+ "license": "BSL-1.0",
+ "keywords": [
+ "libraries",
+ "cpp"
+ ],
+ "requires": [
+ "zlib/[>=1.2.11 <2]",
+ "bzip2/1.0.8",
+ "xz_utils/5.4.4",
+ "zstd/1.5.5",
+ "libbacktrace/cci.20210118",
+ "icu/73.2",
+ "libiconv/1.17"
+ ]
+}
\ No newline at end of file
diff --git a/tests/packagedcode/data/conan/recipes/boost/conanfile.py.expected b/tests/packagedcode/data/conan/recipes/boost/conanfile.py.expected
new file mode 100644
index 00000000000..8ee27292865
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/boost/conanfile.py.expected
@@ -0,0 +1,118 @@
+[
+ {
+ "type": "conan",
+ "namespace": null,
+ "name": "boost",
+ "version": null,
+ "qualifiers": {},
+ "subpath": null,
+ "primary_language": "C++",
+ "description": "Boost provides free peer-reviewed portable C++ source libraries",
+ "release_date": null,
+ "parties": [],
+ "keywords": [
+ "libraries",
+ "cpp"
+ ],
+ "homepage_url": "https://www.boost.org",
+ "download_url": null,
+ "size": null,
+ "sha1": null,
+ "md5": null,
+ "sha256": null,
+ "sha512": null,
+ "bug_tracking_url": null,
+ "code_view_url": null,
+ "vcs_url": "https://github.com/conan-io/conan-center-index",
+ "copyright": null,
+ "holder": null,
+ "declared_license_expression": "BSL-1.0",
+ "declared_license_expression_spdx": null,
+ "license_detections": [],
+ "other_license_expression": null,
+ "other_license_expression_spdx": null,
+ "other_license_detections": [],
+ "extracted_license_statement": null,
+ "notice_text": null,
+ "source_packages": [],
+ "file_references": [],
+ "extra_data": {},
+ "dependencies": [
+ {
+ "purl": "pkg:pypi/zlib",
+ "extracted_requirement": "[>=1.2.11 <2]",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": false,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/bzip2",
+ "extracted_requirement": "1.0.8",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/xz-utils",
+ "extracted_requirement": "5.4.4",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/zstd",
+ "extracted_requirement": "1.5.5",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/libbacktrace",
+ "extracted_requirement": "cci.20210118",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/icu",
+ "extracted_requirement": "73.2",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/libiconv",
+ "extracted_requirement": "1.17",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ }
+ ],
+ "repository_homepage_url": null,
+ "repository_download_url": null,
+ "api_data_url": null,
+ "datasource_id": "conan_conanfile_py",
+ "purl": "pkg:conan/boost"
+ }
+]
\ No newline at end of file
diff --git a/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py b/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py
new file mode 100644
index 00000000000..a3d65fe027c
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py
@@ -0,0 +1,230 @@
+import glob
+import os
+
+from conan import ConanFile
+from conan.tools.apple import is_apple_os
+from conan.tools.build import cross_building
+from conan.tools.env import VirtualBuildEnv, VirtualRunEnv, Environment
+from conan.tools.files import (
+ apply_conandata_patches,
+ copy,
+ export_conandata_patches,
+ get,
+ rename
+)
+from conan.tools.gnu import Autotools, AutotoolsDeps, AutotoolsToolchain
+from conan.tools.layout import basic_layout
+from conan.tools.microsoft import is_msvc, unix_path
+from conan.tools.scm import Version
+
+required_conan_version = ">=1.53.0"
+
+
+class GetTextConan(ConanFile):
+ name = "libgettext"
+ description = "An internationalization and localization system for multilingual programs"
+ topics = ("gettext", "intl", "libintl", "i18n")
+ url = "https://github.com/conan-io/conan-center-index"
+ homepage = "https://www.gnu.org/software/gettext"
+ # Some parts of the project are GPL-3.0-or-later and some are LGPL-2.1-or-later.
+ # At this time, only libintl is packaged, which is licensed under the LGPL-2.1-or-later.
+ # If you modify this package to include other portions of the library, please configure the license accordingly.
+ # The licensing of the project is documented here: https://www.gnu.org/software/gettext/manual/gettext.html#Licenses
+ license = "LGPL-2.1-or-later"
+
+ package_type = "library"
+ settings = "os", "arch", "compiler", "build_type"
+ options = {
+ "shared": [True, False],
+ "fPIC": [True, False],
+ "threads": ["posix", "solaris", "pth", "windows", "disabled"],
+ }
+ default_options = {
+ "shared": False,
+ "fPIC": True,
+ # Handle default value for `threads` in `config_options` method
+ }
+
+ @property
+ def _is_clang_cl(self):
+ return self.settings.os == "Windows" and self.settings.compiler == "clang" and \
+ self.settings.compiler.get_safe("runtime")
+
+ @property
+ def _gettext_folder(self):
+ return "gettext-tools"
+
+ def export_sources(self):
+ export_conandata_patches(self)
+
+ def config_options(self):
+ if self.settings.os == "Windows":
+ self.options.rm_safe("fPIC")
+
+ self.options.threads = {"Solaris": "solaris", "Windows": "windows"}.get(str(self.settings.os), "posix")
+
+ def configure(self):
+ if self.options.shared:
+ self.options.rm_safe("fPIC")
+ self.settings.rm_safe("compiler.libcxx")
+ self.settings.rm_safe("compiler.cppstd")
+
+ def layout(self):
+ basic_layout(self, src_folder="src")
+
+ def requirements(self):
+ self.requires("libiconv/1.17")
+
+ @property
+ def _settings_build(self):
+ return getattr(self, "settings_build", self.settings)
+
+ def build_requirements(self):
+ if self._settings_build.os == "Windows":
+ self.win_bash = True
+ if not self.conf.get("tools.microsoft.bash:path", default=False, check_type=str):
+ self.tool_requires("msys2/cci.latest")
+ if is_msvc(self) or self._is_clang_cl:
+ self.tool_requires("automake/1.16.5")
+
+ def source(self):
+ get(self, **self.conan_data["sources"][self.version], strip_root=True)
+
+ def generate(self):
+ VirtualBuildEnv(self).generate()
+
+ if not cross_building(self):
+ VirtualRunEnv(self).generate(scope="build")
+
+ tc = AutotoolsToolchain(self)
+ tc.configure_args += [
+ "HELP2MAN=/bin/true",
+ "EMACS=no",
+ "--disable-nls",
+ "--disable-dependency-tracking",
+ "--enable-relocatable",
+ "--disable-c++",
+ "--disable-java",
+ "--disable-csharp",
+ "--disable-libasprintf",
+ "--disable-curses",
+ "--disable-threads" if self.options.threads == "disabled" else ("--enable-threads=" + str(self.options.threads)),
+ f"--with-libiconv-prefix={unix_path(self, self.dependencies['libiconv'].package_folder)}",
+ ]
+ if is_msvc(self) or self._is_clang_cl:
+ target = None
+ if self.settings.arch == "x86_64":
+ target = "x86_64-w64-mingw32"
+ elif self.settings.arch == "x86":
+ target = "i686-w64-mingw32"
+
+ if target is not None:
+ tc.configure_args += [f"--host={target}", f"--build={target}"]
+
+ if (str(self.settings.compiler) == "Visual Studio" and Version(self.settings.compiler.version) >= "12") or \
+ (str(self.settings.compiler) == "msvc" and Version(self.settings.compiler.version) >= "180"):
+ tc.extra_cflags += ["-FS"]
+ tc.make_args += ["-C", "intl"]
+ env = tc.environment()
+ if is_msvc(self) or self._is_clang_cl:
+ def programs():
+ rc = None
+ if self.settings.arch == "x86_64":
+ rc = "windres --target=pe-x86-64"
+ elif self.settings.arch == "x86":
+ rc = "windres --target=pe-i386"
+ if self._is_clang_cl:
+ return os.environ.get("CC", "clang-cl"), os.environ.get("AR", "llvm-lib"), os.environ.get("LD", "lld-link"), rc
+ if is_msvc(self):
+ return "cl -nologo", "lib", "link", rc
+
+ compile_wrapper = unix_path(self, self.conf.get("user.automake:compile-wrapper", check_type=str))
+ ar_wrapper = unix_path(self, self.conf.get("user.automake:lib-wrapper", check_type=str))
+ cc, ar, link, rc = programs()
+ env.define("CC", f"{compile_wrapper} {cc}")
+ env.define("CXX", f"{compile_wrapper} {cc}")
+ env.define("LD", link)
+ env.define("AR", f"{ar_wrapper} {ar}")
+ env.define("NM", "dumpbin -symbols")
+ env.define("RANLIB", ":")
+ env.define("STRIP", ":")
+ if rc is not None:
+ env.define("RC", rc)
+ env.define("WINDRES", rc)
+ tc.generate(env)
+
+ if is_msvc(self) or self._is_clang_cl:
+ # Custom AutotoolsDeps for cl like compilers
+ # workaround for https://github.com/conan-io/conan/issues/12784
+ includedirs = []
+ defines = []
+ libs = []
+ libdirs = []
+ linkflags = []
+ cxxflags = []
+ cflags = []
+ for dependency in self.dependencies.values():
+ deps_cpp_info = dependency.cpp_info.aggregated_components()
+ includedirs.extend(deps_cpp_info.includedirs)
+ defines.extend(deps_cpp_info.defines)
+ libs.extend(deps_cpp_info.libs + deps_cpp_info.system_libs)
+ libdirs.extend(deps_cpp_info.libdirs)
+ linkflags.extend(deps_cpp_info.sharedlinkflags + deps_cpp_info.exelinkflags)
+ cxxflags.extend(deps_cpp_info.cxxflags)
+ cflags.extend(deps_cpp_info.cflags)
+
+ env = Environment()
+ env.append("CPPFLAGS", [f"-I{unix_path(self, p)}" for p in includedirs] + [f"-D{d}" for d in defines])
+ env.append("_LINK_", [lib if lib.endswith(".lib") else f"{lib}.lib" for lib in libs])
+ env.append("LDFLAGS", [f"-L{unix_path(self, p)}" for p in libdirs] + linkflags)
+ env.append("CXXFLAGS", cxxflags)
+ env.append("CFLAGS", cflags)
+ env.vars(self).save_script("conanautotoolsdeps_cl_workaround")
+ else:
+ deps = AutotoolsDeps(self)
+ deps.generate()
+
+ def build(self):
+ apply_conandata_patches(self)
+ autotools = Autotools(self)
+ autotools.configure("gettext-runtime")
+ autotools.make()
+
+ def package(self):
+ dest_lib_dir = os.path.join(self.package_folder, "lib")
+ dest_runtime_dir = os.path.join(self.package_folder, "bin")
+ dest_include_dir = os.path.join(self.package_folder, "include")
+ copy(self, "COPYING", self.source_folder, os.path.join(self.package_folder, "licenses"))
+ copy(self, "*gnuintl*.dll", self.build_folder, dest_runtime_dir, keep_path=False)
+ copy(self, "*gnuintl*.lib", self.build_folder, dest_lib_dir, keep_path=False)
+ copy(self, "*gnuintl*.a", self.build_folder, dest_lib_dir, keep_path=False)
+ copy(self, "*gnuintl*.so*", self.build_folder, dest_lib_dir, keep_path=False)
+ copy(self, "*gnuintl*.dylib", self.build_folder, dest_lib_dir, keep_path=False)
+ copy(self, "*libgnuintl.h", self.build_folder, dest_include_dir, keep_path=False)
+ rename(self, os.path.join(dest_include_dir, "libgnuintl.h"), os.path.join(dest_include_dir, "libintl.h"))
+ fix_msvc_libname(self)
+
+ def package_info(self):
+ self.cpp_info.set_property("cmake_find_mode", "both")
+ self.cpp_info.set_property("cmake_file_name", "Intl")
+ self.cpp_info.set_property("cmake_target_name", "Intl::Intl")
+ self.cpp_info.libs = ["gnuintl"]
+ if is_apple_os(self):
+ self.cpp_info.frameworks.append("CoreFoundation")
+
+ self.cpp_info.names["cmake_find_package"] = "Intl"
+ self.cpp_info.names["cmake_find_package_multi"] = "Intl"
+
+def fix_msvc_libname(conanfile, remove_lib_prefix=True):
+ """remove lib prefix & change extension to .lib in case of cl like compiler"""
+ if not conanfile.settings.get_safe("compiler.runtime"):
+ return
+ libdirs = getattr(conanfile.cpp.package, "libdirs")
+ for libdir in libdirs:
+ for ext in [".dll.a", ".dll.lib", ".a"]:
+ full_folder = os.path.join(conanfile.package_folder, libdir)
+ for filepath in glob.glob(os.path.join(full_folder, f"*{ext}")):
+ libname = os.path.basename(filepath)[0:-len(ext)]
+ if remove_lib_prefix and libname[0:3] == "lib":
+ libname = libname[3:]
+ rename(conanfile, filepath, os.path.join(os.path.dirname(filepath), f"{libname}.lib"))
diff --git a/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py.expected b/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py.expected
new file mode 100644
index 00000000000..d07a3d97267
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/libgettext/conanfile.py.expected
@@ -0,0 +1,60 @@
+[
+ {
+ "type": "conan",
+ "namespace": null,
+ "name": "libgettext",
+ "version": null,
+ "qualifiers": {},
+ "subpath": null,
+ "primary_language": "C++",
+ "description": "An internationalization and localization system for multilingual programs",
+ "release_date": null,
+ "parties": [],
+ "keywords": [
+ "gettext",
+ "intl",
+ "libintl",
+ "i18n"
+ ],
+ "homepage_url": "https://www.gnu.org/software/gettext",
+ "download_url": null,
+ "size": null,
+ "sha1": null,
+ "md5": null,
+ "sha256": null,
+ "sha512": null,
+ "bug_tracking_url": null,
+ "code_view_url": null,
+ "vcs_url": "https://github.com/conan-io/conan-center-index",
+ "copyright": null,
+ "holder": null,
+ "declared_license_expression": "LGPL-2.1-or-later",
+ "declared_license_expression_spdx": null,
+ "license_detections": [],
+ "other_license_expression": null,
+ "other_license_expression_spdx": null,
+ "other_license_detections": [],
+ "extracted_license_statement": null,
+ "notice_text": null,
+ "source_packages": [],
+ "file_references": [],
+ "extra_data": {},
+ "dependencies": [
+ {
+ "purl": "pkg:pypi/libiconv",
+ "extracted_requirement": "1.17",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ }
+ ],
+ "repository_homepage_url": null,
+ "repository_download_url": null,
+ "api_data_url": null,
+ "datasource_id": "conan_conanfile_py",
+ "purl": "pkg:conan/libgettext"
+ }
+]
\ No newline at end of file
diff --git a/tests/packagedcode/data/conan/recipes/libzip/conanfile.py b/tests/packagedcode/data/conan/recipes/libzip/conanfile.py
new file mode 100644
index 00000000000..ef4f7c2c575
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/libzip/conanfile.py
@@ -0,0 +1,152 @@
+from conan import ConanFile
+from conan.errors import ConanInvalidConfiguration
+from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
+from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get, rmdir
+from conan.tools.scm import Version
+import os
+
+required_conan_version = ">=1.54.0"
+
+
+class LibZipConan(ConanFile):
+ name = "libzip"
+ description = "A C library for reading, creating, and modifying zip archives"
+ url = "https://github.com/conan-io/conan-center-index"
+ homepage = "https://github.com/nih-at/libzip"
+ license = "BSD-3-Clause"
+ topics = ("zip", "zip-archives", "zip-editing")
+ package_type = "library"
+ settings = "os", "arch", "compiler", "build_type"
+ options = {
+ "shared": [True, False],
+ "fPIC": [True, False],
+ "with_bzip2": [True, False],
+ "with_lzma": [True, False],
+ "with_zstd": [True, False],
+ "crypto": [False, "win32", "openssl", "mbedtls"],
+ "tools": [True, False],
+ }
+ default_options = {
+ "shared": False,
+ "fPIC": True,
+ "with_bzip2": True,
+ "with_lzma": True,
+ "with_zstd": True,
+ "crypto": "openssl",
+ "tools": True,
+ }
+
+ @property
+ def _has_zstd_support(self):
+ return Version(self.version) >= "1.8.0"
+
+ def export_sources(self):
+ export_conandata_patches(self)
+
+ def config_options(self):
+ if self.settings.os == "Windows":
+ del self.options.fPIC
+ if not self._has_zstd_support:
+ del self.options.with_zstd
+ # Default crypto backend on windows
+ if self.settings.os == "Windows":
+ self.options.crypto = "win32"
+
+ def configure(self):
+ if self.options.shared:
+ self.options.rm_safe("fPIC")
+ self.settings.rm_safe("compiler.libcxx")
+ self.settings.rm_safe("compiler.cppstd")
+
+ def layout(self):
+ cmake_layout(self, src_folder="src")
+
+ def requirements(self):
+ self.requires("zlib/[>=1.2.11 <2]")
+
+ if self.options.with_bzip2:
+ self.requires("bzip2/1.0.8")
+
+ if self.options.with_lzma:
+ self.requires("xz_utils/5.4.5")
+
+ if self.options.get_safe("with_zstd"):
+ self.requires("zstd/1.5.5")
+
+ if self.options.crypto == "openssl":
+ self.requires("openssl/[>=1.1 <4]")
+ elif self.options.crypto == "mbedtls":
+ self.requires("mbedtls/3.5.0")
+
+ def validate(self):
+ if self.options.crypto == "win32" and self.settings.os != "Windows":
+ raise ConanInvalidConfiguration("Windows is required to use win32 crypto libraries")
+
+ def source(self):
+ get(self, **self.conan_data["sources"][self.version], strip_root=True)
+
+ def generate(self):
+ tc = CMakeToolchain(self)
+ tc.variables["BUILD_TOOLS"] = self.options.tools
+ tc.variables["BUILD_REGRESS"] = False
+ tc.variables["BUILD_EXAMPLES"] = False
+ tc.variables["BUILD_DOC"] = False
+ tc.variables["ENABLE_LZMA"] = self.options.with_lzma
+ tc.variables["ENABLE_BZIP2"] = self.options.with_bzip2
+ if self._has_zstd_support:
+ tc.variables["ENABLE_ZSTD"] = self.options.with_zstd
+ tc.variables["ENABLE_COMMONCRYPTO"] = False # TODO: We need CommonCrypto package
+ tc.variables["ENABLE_GNUTLS"] = False # TODO: We need GnuTLS package
+ tc.variables["ENABLE_MBEDTLS"] = self.options.crypto == "mbedtls"
+ tc.variables["ENABLE_OPENSSL"] = self.options.crypto == "openssl"
+ tc.variables["ENABLE_WINDOWS_CRYPTO"] = self.options.crypto == "win32"
+ tc.generate()
+
+ deps = CMakeDeps(self)
+ deps.generate()
+
+ def build(self):
+ apply_conandata_patches(self)
+ cmake = CMake(self)
+ cmake.configure()
+ cmake.build()
+
+ def package(self):
+ copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
+ cmake = CMake(self)
+ cmake.install()
+ rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig"))
+ rmdir(self, os.path.join(self.package_folder, "lib", "cmake"))
+
+ def package_info(self):
+ self.cpp_info.set_property("cmake_file_name", "libzip")
+ self.cpp_info.set_property("cmake_target_name", "libzip::zip")
+ self.cpp_info.set_property("pkg_config_name", "libzip")
+
+ # TODO: back to global scope in conan v2 once cmake_find_package* generators removed
+ self.cpp_info.components["_libzip"].libs = ["zip"]
+ if self.settings.os == "Windows":
+ self.cpp_info.components["_libzip"].system_libs = ["advapi32"]
+ if self.options.crypto == "win32":
+ self.cpp_info.components["_libzip"].system_libs.append("bcrypt")
+
+ # TODO: to remove in conan v2 once cmake_find_package* generators removed
+ self.cpp_info.names["cmake_find_package"] = "libzip"
+ self.cpp_info.names["cmake_find_package_multi"] = "libzip"
+ self.cpp_info.components["_libzip"].names["cmake_find_package"] = "zip"
+ self.cpp_info.components["_libzip"].names["cmake_find_package_multi"] = "zip"
+ self.cpp_info.components["_libzip"].set_property("cmake_target_name", "libzip::zip")
+ self.cpp_info.components["_libzip"].set_property("pkg_config_name", "libzip")
+ self.cpp_info.components["_libzip"].requires = ["zlib::zlib"]
+ if self.options.with_bzip2:
+ self.cpp_info.components["_libzip"].requires.append("bzip2::bzip2")
+ if self.options.with_lzma:
+ self.cpp_info.components["_libzip"].requires.append("xz_utils::xz_utils")
+ if self.options.get_safe("with_zstd"):
+ self.cpp_info.components["_libzip"].requires.append("zstd::zstd")
+ if self.options.crypto == "openssl":
+ self.cpp_info.components["_libzip"].requires.append("openssl::crypto")
+ elif self.options.crypto == "mbedtls":
+ self.cpp_info.components["_libzip"].requires.append("mbedtls::mbedtls")
+ if self.options.tools:
+ self.env_info.PATH.append(os.path.join(self.package_folder, "bin"))
diff --git a/tests/packagedcode/data/conan/recipes/libzip/conanfile.py.expected b/tests/packagedcode/data/conan/recipes/libzip/conanfile.py.expected
new file mode 100644
index 00000000000..bde51d3485e
--- /dev/null
+++ b/tests/packagedcode/data/conan/recipes/libzip/conanfile.py.expected
@@ -0,0 +1,109 @@
+[
+ {
+ "type": "conan",
+ "namespace": null,
+ "name": "libzip",
+ "version": null,
+ "qualifiers": {},
+ "subpath": null,
+ "primary_language": "C++",
+ "description": "A C library for reading, creating, and modifying zip archives",
+ "release_date": null,
+ "parties": [],
+ "keywords": [
+ "zip",
+ "zip-archives",
+ "zip-editing"
+ ],
+ "homepage_url": "https://github.com/nih-at/libzip",
+ "download_url": null,
+ "size": null,
+ "sha1": null,
+ "md5": null,
+ "sha256": null,
+ "sha512": null,
+ "bug_tracking_url": null,
+ "code_view_url": null,
+ "vcs_url": "https://github.com/conan-io/conan-center-index",
+ "copyright": null,
+ "holder": null,
+ "declared_license_expression": "BSD-3-Clause",
+ "declared_license_expression_spdx": null,
+ "license_detections": [],
+ "other_license_expression": null,
+ "other_license_expression_spdx": null,
+ "other_license_detections": [],
+ "extracted_license_statement": null,
+ "notice_text": null,
+ "source_packages": [],
+ "file_references": [],
+ "extra_data": {},
+ "dependencies": [
+ {
+ "purl": "pkg:pypi/zlib",
+ "extracted_requirement": "[>=1.2.11 <2]",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": false,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/bzip2",
+ "extracted_requirement": "1.0.8",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/xz-utils",
+ "extracted_requirement": "5.4.5",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/zstd",
+ "extracted_requirement": "1.5.5",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/openssl",
+ "extracted_requirement": "[>=1.1 <4]",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": false,
+ "resolved_package": {},
+ "extra_data": {}
+ },
+ {
+ "purl": "pkg:pypi/mbedtls",
+ "extracted_requirement": "3.5.0",
+ "scope": "install",
+ "is_runtime": true,
+ "is_optional": false,
+ "is_resolved": true,
+ "resolved_package": {},
+ "extra_data": {}
+ }
+ ],
+ "repository_homepage_url": null,
+ "repository_download_url": null,
+ "api_data_url": null,
+ "datasource_id": "conan_conanfile_py",
+ "purl": "pkg:conan/libzip"
+ }
+]
\ No newline at end of file
diff --git a/tests/packagedcode/data/plugin/help.txt b/tests/packagedcode/data/plugin/help.txt
index 99352afae17..520afd9ef7e 100755
--- a/tests/packagedcode/data/plugin/help.txt
+++ b/tests/packagedcode/data/plugin/help.txt
@@ -174,6 +174,13 @@ Package type: composer
description: PHP composer lockfile
path_patterns: '*composer.lock'
--------------------------------------------
+Package type: conan
+ datasource_id: conan_conanfile_py
+ documentation URL: https://docs.conan.io/2.0/reference/conanfile.html
+ primary language: C++
+ description: conan recipe
+ path_patterns: '*/conanfile.py'
+--------------------------------------------
Package type: conda
datasource_id: conda_meta_yaml
documentation URL: https://docs.conda.io/
diff --git a/tests/packagedcode/test_conan.py b/tests/packagedcode/test_conan.py
new file mode 100644
index 00000000000..6b0587e1270
--- /dev/null
+++ b/tests/packagedcode/test_conan.py
@@ -0,0 +1,108 @@
+#
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# ScanCode is a trademark of nexB Inc.
+# SPDX-License-Identifier: Apache-2.0
+# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
+# See https://github.com/nexB/scancode-toolkit for support or download.
+# See https://aboutcode.org for more information about nexB OSS projects.
+#
+
+import ast
+import os.path
+
+from commoncode import testcase
+from packages_test_utils import PackageTester
+from packages_test_utils import check_result_equals_expected_json
+
+from packagedcode import conan
+from packagedcode import models
+from scancode_config import REGEN_TEST_FIXTURES
+
+
+class TestConan(PackageTester):
+ test_data_dir = os.path.join(os.path.dirname(__file__), "data")
+
+ def test_parse_for_boost(self):
+ test_file = self.get_test_loc("conan/recipes/boost/conanfile.py")
+ expected_loc = self.get_test_loc("conan/recipes/boost/conanfile.py.expected")
+ packages = conan.ConanFileHandler.parse(test_file)
+ self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
+
+ def test_parse_for_libgettext(self):
+ test_file = self.get_test_loc("conan/recipes/libgettext/conanfile.py")
+ expected_loc = self.get_test_loc(
+ "conan/recipes/libgettext/conanfile.py.expected"
+ )
+ packages = conan.ConanFileHandler.parse(test_file)
+ self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
+
+ def test_parse_for_libzip(self):
+ test_file = self.get_test_loc("conan/recipes/libzip/conanfile.py")
+ expected_loc = self.get_test_loc("conan/recipes/libzip/conanfile.py.expected")
+ packages = conan.ConanFileHandler.parse(test_file)
+ self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES)
+
+
+def test_is_constraint_resolved():
+ constraint1 = "[>=1.2.11 <2]"
+ expected1 = False
+
+ constraint2 = "cci.20210118"
+ expected2 = True
+
+ assert conan.is_constraint_resolved(constraint1) == expected1
+ assert conan.is_constraint_resolved(constraint2) == expected2
+
+
+def test_get_dependencies():
+ requires = ["zlib/[>=1.2.11 <2]", "bzip2/1.0.8", "xz_utils/5.4.4"]
+ expected = [
+ models.DependentPackage(
+ purl="pkg:pypi/zlib",
+ scope="install",
+ is_runtime=True,
+ is_optional=False,
+ is_resolved=False,
+ extracted_requirement="[>=1.2.11 <2]",
+ ),
+ models.DependentPackage(
+ purl="pkg:pypi/bzip2",
+ scope="install",
+ is_runtime=True,
+ is_optional=False,
+ is_resolved=True,
+ extracted_requirement="1.0.8",
+ ),
+ models.DependentPackage(
+ purl="pkg:pypi/xz-utils",
+ scope="install",
+ is_runtime=True,
+ is_optional=False,
+ is_resolved=True,
+ extracted_requirement="5.4.4",
+ ),
+ ]
+
+ assert conan.get_dependencies(requires) == expected
+
+
+class TestConanFileParser(testcase.FileBasedTesting):
+ test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
+
+ def test_ast_conanfileparser_boost(self):
+ test_file = self.get_test_loc("conan/recipes/boost/conanfile.py")
+ expected_loc = self.get_test_loc("conan/recipes/boost/conanfile.py.ast.expected")
+
+ with open(test_file, encoding="utf-8") as loc:
+ conan_recipe = loc.read()
+ tree = ast.parse(conan_recipe)
+ parser = conan.ConanFileParser()
+ parser.visit(tree)
+
+ results = parser.to_dict()
+ check_result_equals_expected_json(
+ result=results,
+ expected_loc=expected_loc,
+ regen=REGEN_TEST_FIXTURES,
+ )
+ pass