Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/ciimage/gentoo/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ cat <<-EOF > /etc/portage/package.use/ci
x11-libs/libxkbcommon X
dev-lang/rust clippy rustfmt
dev-lang/rust-bin clippy rustfmt
dev-libs/boost python
dev-libs/boost python numpy
sci-libs/hdf5 cxx

# slimmed binpkg, nomesa
Expand Down
2 changes: 1 addition & 1 deletion ci/ciimage/opensuse/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pkgs=(
libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel
boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel
libboost_headers-devel libboost_test-devel libboost_log-devel libboost_regex-devel
libboost_python3-devel libboost_regex-devel libgcrypt-devel
libboost_python3-devel libboost_numpy3-devel libboost_regex-devel libgcrypt-devel
# HACK: remove npm once we switch back to hotdoc sdist
npm
)
Expand Down
2 changes: 1 addition & 1 deletion ci/ciimage/ubuntu-rolling/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pkgs=(
libgcrypt20-dev
libgpgme-dev
libhdf5-dev
libboost-python-dev libboost-regex-dev
libboost-python-dev libboost-numpy-dev libboost-regex-dev
libblocksruntime-dev
libperl-dev
liblapack-dev libscalapack-mpi-dev
Expand Down
17 changes: 17 additions & 0 deletions docs/markdown/Dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,23 @@ that specifying any of these disables system-wide search for boost.
You can set the argument `threading` to `single` to use boost
libraries that have been compiled for single-threaded use instead.

### Boost Python and Boost Numpy

In order to use these libraries you need to link against the version
that matches the major/minor version of python you are compiling
against. They are named `boost_pythonXYY` and `libboost_numpyXYY`
where `X` is the python major version and `YY` is the minor version.

The best way to ensure that you get the correct versions to match get
the language version from your python installation.

```meson
py = import('python').find_installation()
pyver = ''.join(py.language_version().split('.'))
boost_dep = dependency('boost', modules: ['python'+pyver, 'numpy'+pyver])
py.extension_module('my_mod', 'my_mod.cpp', dependencies: boost_dep)
```

## CUDA

*(added 0.53.0)*
Expand Down
8 changes: 8 additions & 0 deletions docs/markdown/snippets/improve_boost_numpy_guessing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Improve Version Guessing for boost_python and boost_numpy

Previously if you specified `boost_python` or `boost_numpy` as modules to
`dependency('boost')` it would pick a random version based on which file it
found first. Now, if there are multiple versions it will first try to find
the one matches the current python interpreter. If that fails, it will use
the most recent version. There is also a warning that this behavior will
be removed in a future release.
144 changes: 61 additions & 83 deletions mesonbuild/dependencies/boost.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import functools
import typing as T
from pathlib import Path
from sys import version_info

from .. import mlog
from .. import mesonlib
Expand Down Expand Up @@ -103,11 +104,6 @@ def __lt__(self, other: object) -> bool:

@functools.total_ordering
class BoostLibraryFile():
# Python libraries are special because of the included
# minor version in the module name.
boost_python_libs = ['boost_python', 'boost_numpy']
reg_python_mod_split = re.compile(r'(boost_[a-zA-Z]+)([0-9]*)')

reg_abi_tag = re.compile(r'^s?g?y?d?p?n?$')
reg_ver_tag = re.compile(r'^[0-9_]+$')

Expand Down Expand Up @@ -157,11 +153,20 @@ def __init__(self, path: Path):
if self.basename.startswith('boost_') and self.nvsuffix == 'lib':
self.static = False

# Python libraries are special because of the included
# minor version in the module name.
# Circa 2019-2020 some distros incorrectly renamed boost_python library files,
# meson used to support this but it has been removed
self.python_version: tuple[int, int] | None = None
for bpl in BoostDependency.boost_python_libs:
if self.mod_name.startswith(bpl):
python_version_str = self.mod_name[len(bpl):]
if not python_version_str.isdigit():
continue
self.python_version = (int(python_version_str[:1]), int(python_version_str[1:]))
Comment on lines +156 to +166

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looses the handling of versioned names spelled in different ways that are currently handled in fix_python_name below. If this is intentional, it need to be documented, and it needs to be checked that the "alternative" naming schemes handled in fix_python_name are not in use on any supported platform.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The more complicated behavior was due to Gentoo and OpenSUSE renameing the libraries circa 2020-2019 because they did not understand how boost_python was supposed to work. I do not believe there is any utility in meson continuing to support this behavior. I added a short note to the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think a comment in the code is useful for this. Anyone interested in the history of this could simply look at the commit log. The change should be reported in the commit message. Also, "circa 2020-2019" is not very informative. Which distribution releases are affected? Are any of these still supported?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can see in the reports in #4788 and #6886 when and which distribution were renaming library filenames, they all stopped doing it around 2020 and these platforms are already EOL. I really don't think that doing anymore detective work is worth it for a library that hardly anyone uses.


# Process tags
tags = self.nametags[1:]
# Filter out the python version tag and fix modname
if self.is_python_lib():
tags = self.fix_python_name(tags)
if not tags:
return

Expand Down Expand Up @@ -233,67 +238,6 @@ def abitag(self) -> str:
def is_boost(self) -> bool:
return any(self.name.startswith(x) for x in ['libboost_', 'boost_'])

def is_python_lib(self) -> bool:
return any(self.mod_name.startswith(x) for x in BoostLibraryFile.boost_python_libs)

def fix_python_name(self, tags: T.List[str]) -> T.List[str]:
# Handle the boost_python naming madness.
# See https://github.com/mesonbuild/meson/issues/4788 for some distro
# specific naming variations.
other_tags: T.List[str] = []

# Split the current modname into the base name and the version
m_cur = BoostLibraryFile.reg_python_mod_split.match(self.mod_name)
cur_name = m_cur.group(1)
cur_vers = m_cur.group(2)

# Update the current version string if the new version string is longer
def update_vers(new_vers: str) -> None:
nonlocal cur_vers
new_vers = new_vers.replace('_', '')
new_vers = new_vers.replace('.', '')
if not new_vers.isdigit():
return
if len(new_vers) > len(cur_vers):
cur_vers = new_vers

for i in tags:
if i.startswith('py'):
update_vers(i[2:])
elif i.isdigit():
update_vers(i)
elif len(i) >= 3 and i[0].isdigit() and i[2].isdigit() and i[1] == '.':
update_vers(i)
else:
other_tags += [i]

self.mod_name = cur_name + cur_vers
return other_tags

def mod_name_matches(self, mod_name: str) -> bool:
if self.mod_name == mod_name:
return True
if not self.is_python_lib():
return False

m_cur = BoostLibraryFile.reg_python_mod_split.match(self.mod_name)
m_arg = BoostLibraryFile.reg_python_mod_split.match(mod_name)

if not m_cur or not m_arg:
return False

if m_cur.group(1) != m_arg.group(1):
return False

cur_vers = m_cur.group(2)
arg_vers = m_arg.group(2)

# Always assume python 2 if nothing is specified
if not arg_vers:
arg_vers = '2'

return cur_vers.startswith(arg_vers)

def version_matches(self, version_lib: str) -> bool:
# If no version tag is present, assume that it fits
if not self.version_lib or not version_lib:
Expand Down Expand Up @@ -340,6 +284,18 @@ def get_link_args(self) -> T.List[str]:
return [self.path.as_posix()]

class BoostDependency(SystemDependency):

boost_python_libs: list[str] = ['boost_python', 'boost_numpy']

@staticmethod
def is_unversioned_python_lib(mod_name: str) -> bool:
"""Checks if this is an unversioned python module that might need version detection.
"""
if mod_name[-1] in set(('2', '3')):
return mod_name[:-1] in BoostDependency.boost_python_libs
else:
return mod_name in BoostDependency.boost_python_libs

def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None:
kwargs['language'] = 'cpp'
super().__init__(name, environment, kwargs)
Expand Down Expand Up @@ -436,6 +392,34 @@ def detect_boost_machine_file(self, props: 'Properties') -> None:

self.check_and_set_roots(paths, use_system=False)

@staticmethod
def find_python_lib(mod_name: str, f_libs: list[BoostLibraryFile]) -> BoostLibraryFile | None:
"""
If a python lib is specified without its version this will sort through the python libs for the best lib to use.
"""

mlog.warning(
"Support for ", mlog.bold(mod_name), ' without specifying the version will be dropped in a future version of ',
"meson. For the correct solution for these libraries, please see ",
"https://mesonbuild.com/Dependencies.html#boost-python-and-boost-numpy"
)
pylibs = [lib for lib in f_libs if lib.mod_name.startswith(mod_name)]

if len(pylibs) == 0:
return None
elif len(pylibs) == 1:
# if only one is found go with that one
return pylibs[0]

# if one of the libs matches the current interperter got with that one
for lib in pylibs:
if lib.python_version == version_info[:2]:
return lib

# if nothing else works just go with the most recent version
pylibs.sort(key= lambda x: x.python_version)
return pylibs[-1]

def run_check(self, inc_dirs: T.List[BoostIncludeDir], lib_dirs: T.List[Path]) -> bool:
mlog.debug(' - potential library dirs: {}'.format([x.as_posix() for x in lib_dirs]))
mlog.debug(' - potential include dirs: {}'.format([x.path.as_posix() for x in inc_dirs]))
Expand Down Expand Up @@ -473,10 +457,17 @@ def run_check(self, inc_dirs: T.List[BoostIncludeDir], lib_dirs: T.List[Path]) -
for mod in modules:
found = False
for l in f_libs:
if l.mod_name_matches(mod):
if l.mod_name == mod:
selected_modules += [l]
found = True
break

if not found and self.is_unversioned_python_lib(mod):
pymod = self.find_python_lib(mod, f_libs)
if pymod:
selected_modules += [pymod]
found = True

if not found:
not_found_as_libs += [mod]

Expand Down Expand Up @@ -636,19 +627,6 @@ def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.L
abitag = libs[0].abitag
libs = [x for x in libs if x.abitag == abitag]

# Assume that we are building against the latest Python version
# and that the other ones are only there for backwards compatibility.
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1141440
no_python_libs = []
python_libs = []
for l in libs:
if l.is_python_lib():
python_libs.append(l)
else:
no_python_libs.append(l)
sorted_pylibs = sorted(python_libs, key=lambda l: l.name, reverse=True)
libs = no_python_libs + sorted_pylibs[:1]

return libs

def detect_libraries(self, libdir: Path) -> T.List[BoostLibraryFile]:
Expand Down
11 changes: 6 additions & 5 deletions test cases/frameworks/1 boost/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ python2dep = python2.dependency(required: false , embe
python3dep = python3.dependency(required: require_bp, embed: true, disabler: true)

# compile python 2/3 modules only if we found a corresponding python version
if(python2dep.found() and require_bp and not s)
bpython2dep = dependency('boost', static: s, modules : ['python'], required: false, disabler: true)
else
# if(python2dep.found() and require_bp and not s)
# bpython2dep = dependency('boost', static: s, modules : ['python'], required: false, disabler: true)
# else
python2dep = disabler()
bpython2dep = disabler()
endif
# endif

py3ver = ''.join(python3.language_version().split('.'))
if(python3dep.found() and require_bp and not s)
bpython3dep = dependency('boost', static: s, modules : ['python3'])
bpython3dep = dependency('boost', static: s, modules : ['python'+py3ver,'numpy'+py3ver])
else
python3dep = disabler()
bpython3dep = disabler()
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

14 changes: 13 additions & 1 deletion test cases/frameworks/35 boost symlinks/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@ if bm == 'windows' or bm == 'cygwin'
error('MESON_SKIP_TEST: Windows and symlinks do not mix.')
endif

dep = dependency('boost', modules : ['regex', 'python'], required: false)
dep = dependency('boost', modules : ['regex', 'python315'], required: false)

assert(dep.found(), 'expected to find a fake version of boost')
assert(dep.version() == '0.3.0', 'expected to find version 0.3.0')

# everything below this can be removed when support for unversioned boost_python is removed

major_version = dependency('boost', modules : ['regex', 'python3'], required: false)

assert(major_version.found(), 'expected to find a fake version of boost')
assert(major_version.version() == '0.3.0', 'expected to find version 0.3.0')

no_version = dependency('boost', modules : ['regex', 'python'], required: false)

assert(no_version.found(), 'expected to find a fake version of boost')
assert(no_version.version() == '0.3.0', 'expected to find version 0.3.0')
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define BOOST_VERSION 100

#error This is not a real version of boost
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
15 changes: 15 additions & 0 deletions test cases/frameworks/42 boost python/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# this test case can be deleted if support for unversioned python libs is removed

project('boost python test', 'cpp')

bm = build_machine.system()

if bm == 'windows' or bm == 'cygwin'
error('MESON_SKIP_TEST: Windows uses different library files')
endif

dependency('boost', modules: ['python', 'numpy'], required: true)
dependency('boost', modules: ['python3', 'numpy3'], required: true)
dependency('boost', modules: ['python313', 'numpy313'], required: true)
dependency('boost', modules: ['python314', 'numpy314'], required: true)
dependency('boost', modules: ['python315', 'numpy315'], required: true)
2 changes: 2 additions & 0 deletions test cases/frameworks/42 boost python/nativefile.ini.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[properties]
boost_root = '@MESON_TEST_ROOT@/boost'
3 changes: 3 additions & 0 deletions test cases/frameworks/42 boost python/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"expect_skip_on_jobname": ["azure", "windows"]
}
Loading