diff --git a/recipes/boost/all/conanfile.py b/recipes/boost/all/conanfile.py index d6558c4513b06..5f33545a9d63d 100644 --- a/recipes/boost/all/conanfile.py +++ b/recipes/boost/all/conanfile.py @@ -1,19 +1,19 @@ -from conan.tools.build import cross_building -from conan.tools.files import rename, rmdir, get -from conan.tools.files.patches import apply_conandata_patches -from conan.tools.microsoft import msvc_runtime_flag -from conan import ConanFile -from conan.errors import ConanException, ConanInvalidConfiguration -from conans import tools -from conan.tools.scm import Version - import glob import os import re -import sys import shlex import shutil +import sys + import yaml +from conan import ConanFile +from conan.errors import ConanException, ConanInvalidConfiguration +from conan.tools.build import cross_building +from conan.tools.files import get, rename, rmdir +from conan.tools.files.patches import apply_conandata_patches +from conan.tools.microsoft import msvc_runtime_flag +from conan.tools.scm import Version +from conans import tools try: from cStringIO import StringIO @@ -60,6 +60,20 @@ ) +class PythonVersion: + def __init__(self, v): + self.v = v + + @property + def xy(self): + return self.v.replace('.', '') + + +PYTHON_VERSIONS = [PythonVersion(v) for v in ( + "2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10" +)] + + class BoostConan(ConanFile): name = "boost" settings = "os", "arch", "compiler", "build_type" @@ -83,8 +97,6 @@ class BoostConan(ConanFile): "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": "ANY", # system default python installation is used, if None - "python_version": "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 @@ -108,6 +120,9 @@ class BoostConan(ConanFile): "system_use_utf8": [True, False], } options.update({f"without_{_name}": [True, False] for _name in CONFIGURE_OPTIONS}) + # extra boost_python libs to build + options.update({f"python{v.xy}": [True, False] for v in PYTHON_VERSIONS}) + options.update({f"python{v.xy}_executable": "ANY" for v in PYTHON_VERSIONS}) default_options = { "shared": False, @@ -121,8 +136,6 @@ class BoostConan(ConanFile): "layout": "system", "magic_autolink": False, "diagnostic_definitions": False, - "python_executable": "None", - "python_version": "None", "namespace": "boost", "namespace_alias": False, "multithreading": True, @@ -147,10 +160,14 @@ class BoostConan(ConanFile): } 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")}) + # extra boost_python libs to build + default_options.update({f"python{v.xy}": False for v in PYTHON_VERSIONS}) + default_options.update({f"python{v.xy}_executable": "None" for v in PYTHON_VERSIONS}) short_paths = True no_copy_source = True _cached_dependencies = None + _pythons = [] # -> list[PythonTool] def export_sources(self): for patch in self.conan_data.get("patches", {}).get(self.version, []): @@ -240,15 +257,6 @@ def _is_clang_cl(self): def _zip_bzip2_requires_needed(self): return not self.options.without_iostreams and not self.options.header_only - @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"] @@ -360,7 +368,7 @@ def _shared(self): @property def _stacktrace_addr2line_available(self): if (self.settings.os in ["iOS", "watchOS", "tvOS"] or self.settings.get_safe("os.subsystem") == "catalyst"): - # sandboxed environment - cannot launch external processes (like addr2line), system() function is forbidden + # 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" @@ -387,9 +395,12 @@ def configure(self): del self.options.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 + enabled_pythons = [self.options.get_safe(f"python{version.xy}") for version in PYTHON_VERSIONS] + if not any(enabled_pythons): + # No specified pythons so detect and set + python = self._detect_python() + setattr(self.options, f"python{python.version.xy}", True) + setattr(self.options, f"python{python.version.xy}_executable", python.executable) else: del self.options.python_buildid @@ -543,6 +554,36 @@ def requirements(self): if self._with_iconv: self.requires("libiconv/1.17") + def get_enabled_pythons(self): + enabled_pythons = {version: self.options.get_safe(f"python{version.xy}") for version in PYTHON_VERSIONS} + + if not any(enabled_pythons.values()): + enabled_pythons[self._python.version] = True + setattr(self.info.options, f"python{self._python.version.xy}", True) + + return enabled_pythons + + def add_python_compatible_packages(self, info): + # Should be compatible with *any* extra versions + # (max 2^len(PYTHON_VERSIONS) packages) + # but limiting it to just any one extra + for (version, enabled) in self.get_enabled_pythons().items(): + if not enabled: + compatible_pkg = info.clone() + setattr(compatible_pkg.options, f"python{version.xy}", True) + self.compatible_packages.append(compatible_pkg) + + def python_package_id(self): + for version in PYTHON_VERSIONS: + # PATH to the interpreter is not important, only version matters + delattr(self.info.options, f"python{version.xy}_executable") + + if self.options.without_python: + for version in PYTHON_VERSIONS: + delattr(self.info.options, f"python{version.xy}") + else: + self.add_python_compatible_packages(self.info) + def package_id(self): del self.info.options.i18n_backend @@ -553,11 +594,7 @@ def package_id(self): 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.options.without_python: - del self.info.options.python_version - else: - self.info.options.python_version = self._python_version + self.python_package_id() def build_requirements(self): if not self.options.header_only: @@ -565,189 +602,25 @@ def build_requirements(self): def source(self): get(self, **self.conan_data["sources"][self.version], - destination=self._source_subfolder, strip_root=True) + destination=self._source_subfolder, strip_root=True) apply_conandata_patches(self) ##################### 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=command, output=output) - 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 = 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): + def _detect_python(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]))") - + return detect_python(context=self) @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 + def _python(self): + if not self._pythons: + detected_python = self._detect_python() + self._pythons.append(detected_python) - @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: - if masd.startswith(os.sep): - masd = masd[len(os.sep):] - 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 self._is_msvc else ["", "lib"] - library_suffixes = [".lib"] if self._is_msvc 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") + return next(iter(self._pythons), None) def _clean(self): src = os.path.join(self.source_folder, self._source_subfolder) @@ -1052,7 +925,6 @@ def add_defines(library): elif self._is_msvc: cxx_flags.append("/Z7") - # Standalone toolchain fails when declare the std lib if self.settings.os not in ("Android", "Emscripten"): try: @@ -1125,8 +997,13 @@ def add_defines(library): 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 not self.options.without_python: + if self.options.python_buildid: + flags.append(f"--python-buildid={self.options.python_buildid}") + + # b2 needs a comma separated list of pythons to build. + python_var = "python=%s" % ",".join([p.version.v for p in self._pythons]) + flags.append(python_var) if self.options.extra_b2_flags: flags.extend(shlex.split(str(self.options.extra_b2_flags))) @@ -1223,7 +1100,25 @@ def create_library_config(deps_name, name): 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}" ;' + def create_python_config(python_tool): + version = python_tool.version.v + executable = python_tool.executable + includes = python_tool.includes + library_dir = python_tool.library_dir + return f'\nusing python : {version} : "{executable}" : "{includes}" : "{library_dir}" ;' + + for version in reversed(PYTHON_VERSIONS): # default to latest python version + if self.options.get_safe(f"python{version.xy}"): + executable = self.options.get_safe(f"python{version.xy}_executable") + self._pythons.append(PythonTool(version, executable.value, self)) + + if not self._pythons: + # No specified pythons so detect and set + python = self._detect_python() + self._pythons.append(PythonTool(python.version, python.executable, self)) + + for python_xy in self._pythons: + contents += create_python_config(python_xy) if not self.options.without_mpi: # https://www.boost.org/doc/libs/1_72_0/doc/html/mpi/getting_started.html @@ -1369,7 +1264,7 @@ def package(self): 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")) + glob.glob(os.path.join(self.package_folder, "lib", "*.pdb")) if dll_pdbs: tools.mkdir(os.path.join(self.package_folder, "bin")) for bin_file in dll_pdbs: @@ -1565,12 +1460,6 @@ def package_info(self): 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 = "" @@ -1582,6 +1471,20 @@ def add_libprefix(n): all_expected_libraries = set() incomplete_components = [] + def expand_python_lib_names(name): + """ A list of names, one for each enabled python version """ + libs = [] + enabled_pythons = [version for version in PYTHON_VERSIONS if self.options.get_safe(f"python{version.xy}")] + for p in enabled_pythons: + xy_version = tools.Version(p.v) + xy_formatdata = { + "py_major": xy_version.major, + "py_minor": xy_version.minor + } + xy_name = name.format(**xy_formatdata) + libs.append(xy_name) + return libs + def filter_transform_module_libraries(names): libs = [] for name in names: @@ -1595,15 +1498,21 @@ def filter_transform_module_libraries(names): continue if not self.options.get_safe("numa") and "_numa" in name: continue - new_name = add_libprefix(name.format(**libformatdata)) + libsuffix + new_name = add_libprefix(name) + 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"): + # must be applied before buildid 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) + + if name.startswith("boost_python") or name.startswith("boost_numpy"): + libs.extend(expand_python_lib_names(new_name)) + else: + libs.append(new_name) return libs for module in self._dependencies["dependencies"].keys(): @@ -1689,7 +1598,7 @@ def filter_transform_module_libraries(names): self.cpp_info.components["stacktrace"].defines.append("BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED") if not self.options.without_python: - pyversion = Version(self._python_version) + pyversion = Version(self._python.version.v) 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") @@ -1714,8 +1623,8 @@ def filter_transform_module_libraries(names): # 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"]) + 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 @@ -1724,3 +1633,205 @@ def filter_transform_module_libraries(names): else: self.cpp_info.components["headers"].defines.extend(["BOOST_AC_DISABLE_THREADS", "BOOST_SP_DISABLE_THREADS"]) self.user_info.stacktrace_addr2line_available = self._stacktrace_addr2line_available + + +def detect_python(context, executable=sys.executable): + """ + Factory method. + """ + exe = executable.replace("\\", "/") + version_str = run_script( + context, + exe, + "from __future__ import print_function; " + "import sys; " + "print('{}.{}'.format(sys.version_info[0], sys.version_info[1]))", + ) + version = PythonVersion(version_str) + + return PythonTool(version, exe, context) + + +def run_script(context, executable, script): + """ + execute python one-liner script and return its output + :param context: an object with run() and output() methods + :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'"{executable}" -c "{script}"' + context.output.info(f"running {command}") + try: + context.run(command=command, output=output) + except ConanException: + context.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 + + +class PythonTool: + """Encapsulate a python executable""" + + def __init__(self, version, executable, context): + self.version = version + self.executable = executable.replace("\\", "/") + self.context = context + + def _run_script(self, script): + return run_script(self.context, self.executable, script) + + def _get_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_script( + "from __future__ import print_function; " + "import sysconfig; " + f"print(sysconfig.get_path('{name}'))" + ) + + def _get_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_script( + "from __future__ import print_function; " + "import sysconfig; " + f"print(sysconfig.get_config_var('{name}'))" + ) + + def _get_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_script( + "from __future__ import print_function; " + "import distutils.sysconfig as du_sysconfig; " + f"print(du_sysconfig.get_config_var('{name}'))" + ) + + def _get_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 = self.version.v.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_sc_var(name) + + return self._get_sc_var(name) or self._get_du_var(name) + + @property + def inc(self): + """ + obtain the result of the "sysconfig.get_python_inc()" call + :return: result of the "sysconfig.get_python_inc()" execution + """ + return self._run_script( + "from __future__ import print_function; " + "import sysconfig; " + "print(sysconfig.get_python_inc())", + ) + + @property + def 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_script( + "from __future__ import print_function; " + "import sys; " + "print(getattr(sys, 'abiflags', ''))", + ) + + @property + def includes(self): + """ + attempt to find directory containing Python.h header file + :return: the directory with python includes + """ + include = self._get_path("include") + plat_include = self._get_path("platinclude") + include_py = self._get_var("INCLUDEPY") + include_dir = self._get_var("INCLUDEDIR") + python_inc = self.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.context.output.info(f"checking {python_h}") + if os.path.isfile(python_h): + self.context.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 library_dir(self): + """ + attempt to find python development library + :return: the full path to the python library to be linked with + """ + library = self._get_var("LIBRARY") + ldlibrary = self._get_var("LDLIBRARY") + libdir = self._get_var("LIBDIR") + multiarch = self._get_var("MULTIARCH") + masd = self._get_var("multiarchsubdir") + with_dyld = self._get_var("WITH_DYLD") + if libdir and multiarch and masd: + if masd.startswith(os.sep): + masd = masd[len(os.sep):] + libdir = os.path.join(libdir, masd) + + if not libdir: + libdest = self._get_var("LIBDEST") + libdir = os.path.join(os.path.dirname(libdest), "libs") + + candidates = [ldlibrary, library] + library_prefixes = [""] if self.context._is_msvc else ["", "lib"] + library_suffixes = [".lib"] if self.context._is_msvc else [".so", ".dll.a", ".a"] + if with_dyld: + library_suffixes.insert(0, ".dylib") + + versions = ["", self.version.v, self.version.xy] + abiflags = self.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.context.output.info(f"checking {python_lib}") + if os.path.isfile(python_lib): + self.context.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" + ) diff --git a/recipes/boost/all/test_package/conanfile.py b/recipes/boost/all/test_package/conanfile.py index 54e431f7b3b68..d3500ec871ab3 100644 --- a/recipes/boost/all/test_package/conanfile.py +++ b/recipes/boost/all/test_package/conanfile.py @@ -22,6 +22,19 @@ def _boost_option(self, name, default): except (AttributeError, ConanException): return default + def _all_pythons(self): + """Search pythonXY_executable options and parse the version""" + pythons = [] + + # try to return the later versions of python first + for k, exe in reversed(self.options["boost"].items()): + if exe and k.startswith("python") and k.endswith("_executable"): + xy = k.split("_")[0].split("python")[1] + ver = tools.Version(f"{xy[0]}.{xy[1:]}") + pythons.append((ver, exe)) + + return pythons + def build(self): # FIXME: tools.vcvars added for clang-cl. Remove once conan supports clang-cl properly. (https://github.com/conan-io/conan-center-index/pull/1453) with tools.vcvars(self.settings) if (self.settings.os == "Windows" and self.settings.compiler == "clang") else tools.no_op(): @@ -31,9 +44,9 @@ def build(self): cmake.definitions["Boost_USE_STATIC_LIBS"] = not self.options["boost"].shared cmake.definitions["WITH_PYTHON"] = not self.options["boost"].without_python if not self.options["boost"].without_python: - pyversion = tools.Version(self.options["boost"].python_version) - cmake.definitions["Python_ADDITIONAL_VERSIONS"] = "{}.{}".format(pyversion.major, pyversion.minor) - cmake.definitions["PYTHON_COMPONENT_SUFFIX"] = "{}{}".format(pyversion.major, pyversion.minor) + (v, _) = self._all_pythons()[0] + cmake.definitions["Python_ADDITIONAL_VERSIONS"] = "{}.{}".format(v.major, v.minor) + cmake.definitions["PYTHON_COMPONENT_SUFFIX"] = "{}{}".format(v.major, v.minor) cmake.definitions["WITH_RANDOM"] = not self.options["boost"].without_random cmake.definitions["WITH_REGEX"] = not self.options["boost"].without_regex cmake.definitions["WITH_TEST"] = not self.options["boost"].without_test @@ -79,7 +92,8 @@ def test(self): self.run(os.path.join("bin", "json_exe"), run_environment=True) if not self.options["boost"].without_python: with tools.environment_append({"PYTHONPATH": "{}:{}".format("bin", "lib")}): - self.run("{} {}".format(self.options["boost"].python_executable, os.path.join(self.source_folder, "python.py")), run_environment=True) + (_, exe) = self._all_pythons()[0] + self.run("{} {}".format(exe, os.path.join(self.source_folder, "python.py")), run_environment=True) self.run(os.path.join("bin", "numpy_exe"), run_environment=True) if not self.options["boost"].without_stacktrace: self.run(os.path.join("bin", "stacktrace_noop_exe"), run_environment=True)