diff --git a/.github/workflows/lint_mypy.yml b/.github/workflows/lint_mypy.yml index b76a751fb..54535b3b1 100644 --- a/.github/workflows/lint_mypy.yml +++ b/.github/workflows/lint_mypy.yml @@ -30,4 +30,4 @@ jobs: with: python-version: '3.x' - run: python -m pip install mypy - - run: mypy --follow-imports=skip mesonbuild/mtest.py mesonbuild/minit.py mesonbuild/mintro.py mesonbuild/msetup.py mesonbuild/wrap tools/ mesonbuild/modules/fs.py mesonbuild/dependencies/mpi.py mesonbuild/dependencies/hdf5.py mesonbuild/compilers/mixins/intel.py + - run: mypy --follow-imports=skip mesonbuild/mtest.py mesonbuild/minit.py mesonbuild/mintro.py mesonbuild/msetup.py mesonbuild/wrap tools/ mesonbuild/modules/fs.py mesonbuild/dependencies/boost.py mesonbuild/dependencies/mpi.py mesonbuild/dependencies/hdf5.py mesonbuild/compilers/mixins/intel.py diff --git a/.github/workflows/os_comp.yml b/.github/workflows/os_comp.yml index dcbbdde89..19281c997 100644 --- a/.github/workflows/os_comp.yml +++ b/.github/workflows/os_comp.yml @@ -11,9 +11,7 @@ jobs: - name: Install Dependencies run: | sudo apt update -yq - sudo apt install -yq --no-install-recommends python3-setuptools python3-pip g++ gfortran gobjc gobjc++ zlib1g-dev python-dev python3-dev libboost-all-dev - - name: Remove GitHub boost version - run: sudo rm -rf /usr/local/share/boost + sudo apt install -yq --no-install-recommends python3-setuptools python3-pip g++ gfortran gobjc gobjc++ zlib1g-dev python-dev python3-dev - name: Install ninja-build tool uses: seanmiddleditch/gha-setup-ninja@v1 - name: Python version @@ -21,7 +19,7 @@ jobs: - name: Ninja version run: ninja --version - name: Run tests - run: python3 run_tests.py + run: LD_LIBRARY_PATH=/usr/local/share/boost/1.69.0/lib/:$LD_LIBRARY_PATH python3 run_tests.py env: CI: '1' XENIAL: '1' @@ -48,6 +46,7 @@ jobs: env: CI: '1' SKIP_SCIENTIFIC: '1' + SKIP_STATIC_BOOST: '1' opensuse: name: OpenSUSE @@ -60,3 +59,4 @@ jobs: env: CI: '1' SKIP_SCIENTIFIC: '1' + SKIP_STATIC_BOOST: '1' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1a9de56d1..40d422d05 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -105,7 +105,10 @@ jobs: - script: | set BOOST_ROOT= set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 + set SKIP_STATIC_BOOST=1 env.exe -- python3 run_tests.py --backend=ninja + # Cygwin's static boost installation is broken (some static library + # variants such as boost_thread are not present) displayName: Run Tests - task: CopyFiles@2 condition: not(canceled()) diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index 1c861e77c..fa2804675 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -49,8 +49,8 @@ steps: # install boost (except for clang-cl) if ($env:arch -eq 'x86') { $boost_bitness = '32' } else { $boost_bitness = '64' } - if ($env:compiler -eq 'msvc2017') { - $boost_version = '1.64.0' ; $boost_abi_tag = '14.1' + if ($env:compiler -eq 'msvc2017' -Or $env:compiler -eq 'msvc2019' -Or $env:compiler -eq 'clang-cl') { + $boost_version = '1.72.0' ; $boost_abi_tag = '14.1' } if ($boost_version) { $boost_filename = $boost_version.Replace('.', '_') diff --git a/ci/ciimage/eoan/Dockerfile b/ci/ciimage/eoan/Dockerfile index a98662cb0..dcc854939 100644 --- a/ci/ciimage/eoan/Dockerfile +++ b/ci/ciimage/eoan/Dockerfile @@ -24,7 +24,7 @@ RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \ && eatmydata apt-get -y install libgcrypt20-dev \ && eatmydata apt-get -y install libgpgme-dev \ && eatmydata apt-get -y install libhdf5-dev \ -&& eatmydata apt-get -y install libboost-python-dev \ +&& eatmydata apt-get -y install libboost-python-dev libboost-regex-dev \ && eatmydata apt-get -y install libblocksruntime-dev \ && eatmydata apt-get -y install libperl-dev \ && eatmydata apt-get -y install liblapack-dev libscalapack-mpi-dev \ diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 916903944..53a0cfb7a 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -1,4 +1,4 @@ -# Copyright 2013-2017 The Meson development team +# Copyright 2013-2020 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This file contains the detection logic for miscellaneous external dependencies. - -import glob import os +import re +import functools +import typing as T +from pathlib import Path from .. import mlog from .. import mesonlib -from ..environment import detect_cpu_family +from ..environment import Environment from .base import (DependencyException, ExternalDependency) from .misc import threads_factory @@ -39,16 +40,6 @@ from .misc import threads_factory # mingw-w64 / Windows : libboost_-mt.a (location = /mingw64/lib/) # libboost_-mt.dll.a # -# Library names supported: -# - libboost_--mt-gd-x_x.lib (static) -# - boost_--mt-gd-x_x.lib|.dll (shared) -# - libboost_.lib (static) -# - boost_.lib|.dll (shared) -# where compiler is vc141 for example. -# -# NOTE: -gd means runtime and build time debugging is on -# -mt means threading=multi -# # The `modules` argument accept library names. This is because every module that # has libraries to link against also has multiple options regarding how to # link. See for example: @@ -78,616 +69,832 @@ from .misc import threads_factory # Furthermore, the boost documentation for unix above uses examples from windows like # "libboost_regex-vc71-mt-d-x86-1_34.lib", so apparently the abi tags may be more aimed at windows. # -# Probably we should use the linker search path to decide which libraries to use. This will -# make it possible to find the macports boost libraries without setting BOOST_ROOT, and will -# also mean that it would be possible to use user-installed boost libraries when official -# packages are installed. -# -# We thus follow the following strategy: -# 1. Look for libraries using compiler.find_library( ) -# 1.1 On Linux, just look for boost_ -# 1.2 On other systems (e.g. Mac) look for boost_-mt if multithreading. -# 1.3 Otherwise look for boost_ -# 2. Fall back to previous approach -# 2.1. Search particular directories. -# 2.2. Find boost libraries with unknown suffixes using file-name globbing. - -# TODO: Unix: Don't assume we know where the boost dir is, rely on -Idir and -Ldir being set. -# TODO: Allow user to specify suffix in BOOST_SUFFIX, or add specific options like BOOST_DEBUG for 'd' for debug. - -class BoostDependency(ExternalDependency): - def __init__(self, environment, kwargs): - super().__init__('boost', environment, kwargs, language='cpp') - self.need_static_link = ['boost_exception', 'boost_test_exec_monitor'] - self.is_debug = environment.coredata.get_builtin_option('buildtype').startswith('debug') - threading = kwargs.get("threading", "multi") - self.is_multithreading = threading == "multi" +# We follow the following strategy for finding modules: +# A) Detect potential boost root directories (uses also BOOST_ROOT env var) +# B) Foreach candidate +# 1. Look for the boost headers (boost/version.pp) +# 2. Find all boost libraries +# 2.1 Add all libraries in lib* +# 2.2 Filter out non boost libraries +# 2.3 Filter the renaining libraries based on the meson requirements (static/shared, etc.) +# 2.4 Ensure that all libraries have the same boost tag (and are thus compatible) +# 3. Select the libraries matching the requested modules + +@functools.total_ordering +class BoostIncludeDir(): + def __init__(self, path: Path, version_int: int): + self.path = path + self.version_int = version_int + major = int(self.version_int / 100000) + minor = int((self.version_int / 100) % 1000) + patch = int(self.version_int % 100) + self.version = '{}.{}.{}'.format(major, minor, patch) + self.version_lib = '{}_{}'.format(major, minor) + + def __repr__(self) -> str: + return ''.format(self.version, self.path) + + def __lt__(self, other: T.Any) -> bool: + if isinstance(other, BoostIncludeDir): + return (self.version_int, self.path) < (other.version_int, other.path) + return NotImplemented + +@functools.total_ordering +class BoostLibraryFile(): + reg_abi_tag = re.compile(r'^s?g?y?d?p?n?$') + reg_ver_tag = re.compile(r'^[0-9_]+$') + + def __init__(self, path: Path): + self.path = path + self.name = self.path.name + + # Initialize default properties + self.static = False + self.toolset = '' + self.arch = '' + self.version_lib = '' + self.mt = True + + self.runtime_static = False + self.runtime_debug = False + self.python_debug = False + self.debug = False + self.stlport = False + self.deprecated_iostreams = False + + # Post process the library name + name_parts = self.name.split('.') + self.basename = name_parts[0] + self.suffixes = name_parts[1:] + self.suffixes = [x for x in self.suffixes if not x.isdigit()] + self.nvsuffix = '.'.join(self.suffixes) # Used for detecting the library type + self.nametags = self.basename.split('-') + self.mod_name = self.nametags[0] + if self.mod_name.startswith('lib'): + self.mod_name = self.mod_name[3:] + + # Detecting library type + if self.nvsuffix in ['so', 'dll', 'dll.a', 'dll.lib', 'dylib']: + self.static = False + elif self.nvsuffix in ['a', 'lib']: + self.static = True + else: + raise DependencyException('Unable to process library extension "{}" ({})'.format(self.nvsuffix, self.path)) - self.requested_modules = self.get_requested(kwargs) - if 'thread' in self.requested_modules: - if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): - self.is_found = False - return + # boost_.lib is the dll import library + if self.basename.startswith('boost_') and self.nvsuffix == 'lib': + self.static = False - self.boost_root = None - self.boost_roots = [] - self.incdir = None - self.libdir = None - - if 'BOOST_ROOT' in os.environ: - self.boost_root = os.environ['BOOST_ROOT'] - self.boost_roots = [self.boost_root] - if not os.path.isabs(self.boost_root): - raise DependencyException('BOOST_ROOT must be an absolute path.') - if 'BOOST_INCLUDEDIR' in os.environ: - self.incdir = os.environ['BOOST_INCLUDEDIR'] - if 'BOOST_LIBRARYDIR' in os.environ: - self.libdir = os.environ['BOOST_LIBRARYDIR'] - - if self.boost_root is None: - if self.env.machines[self.for_machine].is_windows(): - self.boost_roots = self.detect_win_roots() - else: - self.boost_roots = self.detect_nix_roots() + # Process tags + tags = self.nametags[1:] + if not tags: + return - if self.incdir is None: - if self.env.machines[self.for_machine].is_windows(): - self.incdir = self.detect_win_incdir() + # Without any tags mt is assumed, however, an absents of mt in the name + # with tags present indicates that the lib was build without mt support + self.mt = False + for i in tags: + if i == 'mt': + self.mt = True + elif len(i) == 3 and i[1:] in ['32', '64']: + self.arch = i + elif BoostLibraryFile.reg_abi_tag.match(i): + self.runtime_static = 's' in i + self.runtime_debug = 'g' in i + self.python_debug = 'y' in i + self.debug = 'd' in i + self.stlport = 'p' in i + self.deprecated_iostreams = 'n' in i + elif BoostLibraryFile.reg_ver_tag.match(i): + self.version_lib = i else: - self.incdir = self.detect_nix_incdir() - - mlog.debug('Boost library root dir is', mlog.bold(self.boost_root)) - mlog.debug('Boost include directory is', mlog.bold(self.incdir)) - - # 1. check if we can find BOOST headers. - self.detect_headers_and_version() - - if not self.is_found: - return # if we can not find 'boost/version.hpp' - - # 2. check if we can find BOOST libraries. - self.detect_lib_modules() - mlog.debug('Boost library directory is', mlog.bold(self.libdir)) - - mlog.debug('Installed Boost libraries: ') - for key in sorted(self.lib_modules.keys()): - mlog.debug(key, self.lib_modules[key]) - - # 3. check if requested modules are valid, that is, either found or in the list of known boost libraries - self.check_invalid_modules() - - # 4. final check whether or not we find all requested and valid modules - self.check_find_requested_modules() + self.toolset = i + + def __repr__(self) -> str: + return ''.format(self.abitag, self.mod_name, self.path) + + def __lt__(self, other: T.Any) -> bool: + if isinstance(other, BoostLibraryFile): + return ( + self.mod_name, self.version_lib, self.arch, self.static, + not self.mt, not self.runtime_static, + not self.debug, self.runtime_debug, self.python_debug, + self.stlport, self.deprecated_iostreams, + self.name, + ) < ( + other.mod_name, other.version_lib, other.arch, other.static, + not other.mt, not other.runtime_static, + not other.debug, other.runtime_debug, other.python_debug, + other.stlport, other.deprecated_iostreams, + other.name, + ) + return NotImplemented + + def __eq__(self, other: T.Any) -> bool: + if isinstance(other, BoostLibraryFile): + return self.name == other.name + return NotImplemented + + def __hash__(self) -> int: + return hash(self.name) + + @property + def abitag(self) -> str: + abitag = '' + abitag += 'S' if self.static else '-' + abitag += 'M' if self.mt else '-' + abitag += ' ' + abitag += 's' if self.runtime_static else '-' + abitag += 'g' if self.runtime_debug else '-' + abitag += 'y' if self.python_debug else '-' + abitag += 'd' if self.debug else '-' + abitag += 'p' if self.stlport else '-' + abitag += 'n' if self.deprecated_iostreams else '-' + abitag += ' ' + (self.arch or '???') + abitag += ' ' + (self.toolset or '?') + abitag += ' ' + (self.version_lib or 'x_xx') + return abitag + + def is_boost(self) -> bool: + return any([self.name.startswith(x) for x in ['libboost_', 'boost_']]) + + 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: + return True + return self.version_lib == version_lib - def check_invalid_modules(self): - invalid_modules = [c for c in self.requested_modules if 'boost_' + c not in self.lib_modules and 'boost_' + c not in BOOST_LIBS] + def arch_matches(self, arch: str) -> bool: + # If no version tag is present, assume that it fits + if not self.arch or not arch: + return True + return self.arch == arch - # previous versions of meson allowed include dirs as modules - remove = [] - for m in invalid_modules: - if m in BOOST_DIRS: - mlog.warning('Requested boost library', mlog.bold(m), 'that doesn\'t exist. ' - 'This will be an error in the future') - remove.append(m) + def vscrt_matches(self, vscrt: str) -> bool: + # If no vscrt tag present, assume that it fits ['/MD', '/MDd', '/MT', '/MTd'] + if not vscrt: + return True + if vscrt in ['/MD', '-MD']: + return not self.runtime_static and not self.runtime_debug + elif vscrt in ['/MDd', '-MDd']: + return not self.runtime_static and self.runtime_debug + elif vscrt in ['/MT', '-MT']: + return (self.runtime_static or not self.static) and not self.runtime_debug + elif vscrt in ['/MTd', '-MTd']: + return (self.runtime_static or not self.static) and self.runtime_debug + + mlog.warning('Boost: unknow vscrt tag {}. This may cause the compilation to fail. Please consider reporting this as a bug.'.format(vscrt), once=True) + return True + + def get_compiler_args(self) -> T.List[str]: + args = [] # type: T.List[str] + if self.mod_name in boost_libraries: + libdef = boost_libraries[self.mod_name] # type: BoostLibrary + if self.static: + args += libdef.static + else: + args += libdef.shared + if self.mt: + args += libdef.multi + else: + args += libdef.single + return args - self.requested_modules = [x for x in self.requested_modules if x not in remove] - invalid_modules = [x for x in invalid_modules if x not in remove] + def get_link_args(self) -> T.List[str]: + return [self.path.as_posix()] - if invalid_modules: - mlog.error('Invalid Boost modules: ' + ', '.join(invalid_modules)) - return True - else: - return False +class BoostDependency(ExternalDependency): + def __init__(self, environment: Environment, kwargs): + super().__init__('boost', environment, kwargs, language='cpp') + self.debug = environment.coredata.get_builtin_option('buildtype').startswith('debug') + self.multithreading = kwargs.get('threading', 'multi') == 'multi' - def log_details(self): - module_str = ', '.join(self.requested_modules) - return module_str + self.boost_root = None - def log_info(self): - if self.boost_root: - return self.boost_root - return '' + # Extract and validate modules + self.modules = mesonlib.extract_as_list(kwargs, 'modules') + for i in self.modules: + if not isinstance(i, str): + raise DependencyException('Boost module argument is not a string.') + if i.startswith('boost_'): + raise DependencyException('Boost modules must be passed without the boost_ prefix') - def detect_nix_roots(self): - return [os.path.abspath(os.path.join(x, '..')) - for x in self.clib_compiler.get_default_include_dirs()] + # Do we need threads? + if 'thread' in self.modules: + if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): + self.is_found = False + return - def detect_win_roots(self): - res = [] - # Where boost documentation says it should be - globtext = 'C:\\Program Files\\boost\\boost_*' - files = glob.glob(globtext) - res.extend(files) + # Try figuring out the architecture tag + self.arch = environment.machines[self.for_machine].cpu_family + self.arch = boost_arch_map.get(self.arch, None) + + # Prefere BOOST_INCLUDEDIR and BOOST_LIBRARYDIR if preset + boost_manual_env = [x in os.environ for x in ['BOOST_INCLUDEDIR', 'BOOST_LIBRARYDIR']] + if all(boost_manual_env): + inc_dir = Path(os.environ['BOOST_INCLUDEDIR']) + lib_dir = Path(os.environ['BOOST_LIBRARYDIR']) + mlog.debug('Trying to find boost with:') + mlog.debug(' - BOOST_INCLUDEDIR = {}'.format(inc_dir)) + mlog.debug(' - BOOST_LIBRARYDIR = {}'.format(lib_dir)) + + boost_inc_dir = None + for i in [inc_dir / 'version.hpp', inc_dir / 'boost' / 'version.hpp']: + if i.is_file(): + boost_inc_dir = self._include_dir_from_version_header(i) + break + if not boost_inc_dir: + self.is_found = False + return - # Where boost built from source actually installs it - if os.path.isdir('C:\\Boost'): - res.append('C:\\Boost') + self.is_found = self.run_check([boost_inc_dir], [lib_dir]) + return + elif any(boost_manual_env): + mlog.warning('Both BOOST_INCLUDEDIR *and* BOOST_LIBRARYDIR have to be set (one is not enough). Ignoring.') + + # A) Detect potential boost root directories (uses also BOOST_ROOT env var) + roots = self.detect_roots() + roots = list(mesonlib.OrderedSet(roots)) + + # B) Foreach candidate + for i in roots: + # 1. Look for the boost headers (boost/version.pp) + mlog.debug('Checking potential boost root {}'.format(i.as_posix())) + inc_dirs = self.detect_inc_dirs(i) + inc_dirs = sorted(inc_dirs, reverse=True) # Prefer the newer versions + + # Early abort when boost is not found + if not inc_dirs: + continue + + lib_dirs = self.detect_lib_dirs(i) + self.is_found = self.run_check(inc_dirs, lib_dirs) + if self.is_found: + self.boost_root = i + break + + def run_check(self, inc_dirs: T.List[BoostIncludeDir], lib_dirs: T.List[Path]) -> bool: + # 2. Find all boost libraries + libs = [] # type: T.List[BoostLibraryFile] + for i in lib_dirs: + libs += self.detect_libraries(i) + libs = sorted(set(libs)) + + modules = ['boost_' + x for x in self.modules] + for inc in inc_dirs: + mlog.debug(' - found boost {} include dir: {}'.format(inc.version, inc.path)) + f_libs = self.filter_libraries(libs, inc.version_lib) + + # mlog.debug(' - raw library list:') + # for j in libs: + # mlog.debug(' - {}'.format(j)) + mlog.debug(' - filtered library list:') + for j in f_libs: + mlog.debug(' - {}'.format(j)) + + # 3. Select the libraries matching the requested modules + not_found = [] # type: T.List[str] + selected_modules = [] # type: T.List[BoostLibraryFile] + for mod in modules: + found = False + for l in f_libs: + if l.mod_name == mod: + selected_modules += [l] + found = True + break + if not found: + not_found += [mod] + + # log the result + mlog.debug(' - found:') + comp_args = [] # type: T.List[str] + link_args = [] # type: T.List[str] + for j in selected_modules: + c_args = j.get_compiler_args() + l_args = j.get_link_args() + mlog.debug(' - {:<24} link={} comp={}'.format(j.mod_name, str(l_args), str(c_args))) + comp_args += c_args + link_args += l_args + + comp_args = list(set(comp_args)) + link_args = list(set(link_args)) + + # if we found all modules we are done + if not not_found: + self.version = inc.version + self.compile_args = ['-I' + inc.path.as_posix()] + self.compile_args += comp_args + self.compile_args += self._extra_compile_args() + self.compile_args = list(mesonlib.OrderedSet(self.compile_args)) + self.link_args = link_args + mlog.debug(' - final compile args: {}'.format(self.compile_args)) + mlog.debug(' - final link args: {}'.format(self.link_args)) + return True - # Where boost prebuilt binaries are - globtext = 'C:\\local\\boost_*' - files = glob.glob(globtext) - res.extend(files) - return res + # in case we missed something log it and try again + mlog.debug(' - NOT found:') + for mod in not_found: + mlog.debug(' - {}'.format(mod)) - def detect_nix_incdir(self): - if self.boost_root: - return os.path.join(self.boost_root, 'include') - return None - - # FIXME: Should pick a version that matches the requested version - # Returns the folder that contains the boost folder. - def detect_win_incdir(self): - for root in self.boost_roots: - globtext = os.path.join(root, 'include', 'boost-*') - incdirs = glob.glob(globtext) - if incdirs: - return incdirs[0] - incboostdir = os.path.join(root, 'include', 'boost') - if os.path.isdir(incboostdir): - return os.path.join(root, 'include') - incboostdir = os.path.join(root, 'boost') - if os.path.isdir(incboostdir): - return root - return None - - def get_compile_args(self): - args = [] - include_dir = self.incdir - - # Use "-isystem" when including boost headers instead of "-I" - # to avoid compiler warnings/failures when "-Werror" is used - - # Careful not to use "-isystem" on default include dirs as it - # breaks some of the headers for certain gcc versions - - # For example, doing g++ -isystem /usr/include on a simple - # "int main()" source results in the error: - # "/usr/include/c++/6.3.1/cstdlib:75:25: fatal error: stdlib.h: No such file or directory" - - # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129 - # and http://stackoverflow.com/questions/37218953/isystem-on-a-system-include-directory-causes-errors - # for more details - - if include_dir and include_dir not in self.clib_compiler.get_default_include_dirs(): - args.append("".join(self.clib_compiler.get_include_args(include_dir, True))) - return args + return False - def get_requested(self, kwargs): - candidates = mesonlib.extract_as_list(kwargs, 'modules') - for c in candidates: - if not isinstance(c, str): - raise DependencyException('Boost module argument is not a string.') - return candidates + def detect_inc_dirs(self, root: Path) -> T.List[BoostIncludeDir]: + candidates = [] # type: T.List[Path] + inc_root = root / 'include' - def detect_headers_and_version(self): + candidates += [root / 'boost'] + candidates += [inc_root / 'boost'] + if inc_root.is_dir(): + for i in inc_root.iterdir(): + if not i.is_dir() or not i.name.startswith('boost-'): + continue + candidates += [i / 'boost'] + candidates = [x for x in candidates if x.is_dir()] + candidates = [x / 'version.hpp' for x in candidates] + candidates = [x for x in candidates if x.exists()] + return [self._include_dir_from_version_header(x) for x in candidates] + + def detect_lib_dirs(self, root: Path) -> T.List[Path]: + dirs = [] # type: T.List[Path] + subdirs = [] # type: T.List[Path] + for i in root.iterdir(): + if i.is_dir() and i.name.startswith('lib'): + dirs += [i] + + # Some distros put libraries not directly inside /usr/lib but in /usr/lib/x86_64-linux-gnu + for i in dirs: + for j in i.iterdir(): + if j.is_dir() and j.name.endswith('-linux-gnu'): + subdirs += [j] + return dirs + subdirs + + def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.List[BoostLibraryFile]: + # MSVC is very picky with the library tags + vscrt = '' try: - version = self.clib_compiler.get_define('BOOST_LIB_VERSION', '#include ', self.env, self.get_compile_args(), [], disable_cache=True)[0] - except mesonlib.EnvironmentException: - return - except TypeError: - return - # Remove quotes - version = version[1:-1] - # Fix version string - self.version = version.replace('_', '.') - self.is_found = True - - def detect_lib_modules(self): - self.lib_modules = {} - # 1. Try to find modules using compiler.find_library( ) - if self.find_libraries_with_abi_tags(self.abi_tags()): + crt_val = self.env.coredata.base_options['b_vscrt'].value + buildtype = self.env.coredata.builtins['buildtype'].value + vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0] + except (KeyError, IndexError, AttributeError): pass - # 2. Fall back to the old method - else: - if self.env.machines[self.for_machine].is_windows(): - self.detect_lib_modules_win() - else: - self.detect_lib_modules_nix() - def check_find_requested_modules(self): - # 3. Check if we can find the modules - for m in self.requested_modules: - if 'boost_' + m not in self.lib_modules: - mlog.debug('Requested Boost library {!r} not found'.format(m)) - self.is_found = False - - def modname_from_filename(self, filename): - modname = os.path.basename(filename) - modname = modname.split('.', 1)[0] - modname = modname.split('-', 1)[0] - if modname.startswith('libboost'): - modname = modname[3:] - return modname - - def compiler_tag(self): - tag = None - compiler = self.env.detect_cpp_compiler(self.for_machine) + libs = [x for x in libs if x.static == self.static] + libs = [x for x in libs if x.mt == self.multithreading] + libs = [x for x in libs if x.version_matches(lib_vers)] + libs = [x for x in libs if x.arch_matches(self.arch)] + libs = [x for x in libs if x.vscrt_matches(vscrt)] + libs = [x for x in libs if x.nvsuffix != 'dll'] # Only link to import libraries + + # Only filter by debug when we are building in release mode. Debug + # libraries are automatically prefered through sorting otherwise. + if not self.debug: + libs = [x for x in libs if not x.debug] + + # Take the abitag from the first library and filter by it. This + # ensures that we have a set of libraries that are always compatible. + if not libs: + return [] + abitag = libs[0].abitag + libs = [x for x in libs if x.abitag == abitag] + + return libs + + def detect_libraries(self, libdir: Path) -> T.List[BoostLibraryFile]: + libs = [] # type: T.List[BoostLibraryFile] + for i in libdir.iterdir(): + if not i.is_file() or i.is_symlink(): + continue + if not any([i.name.startswith(x) for x in ['libboost_', 'boost_']]): + continue + + libs += [BoostLibraryFile(i)] + return [x for x in libs if x.is_boost()] # Filter out no boost libraries + + def detect_roots(self) -> T.List[Path]: + roots = [] # type: T.List[Path] + + # Add roots from the environment + for i in ['BOOST_ROOT', 'BOOSTROOT']: + if i in os.environ: + raw_paths = os.environ[i].split(os.pathsep) + paths = [Path(x) for x in raw_paths] + if paths and any([not x.is_absolute() for x in paths]): + raise DependencyException('Paths in {} must be absolute'.format(i)) + roots += paths + return roots # Do not add system paths if BOOST_ROOT is present + + # Add system paths if self.env.machines[self.for_machine].is_windows(): - if compiler.get_id() in ['msvc', 'clang-cl']: - comp_ts_version = compiler.get_toolset_version() - compiler_ts = comp_ts_version.split('.') - # FIXME - what about other compilers? - tag = '-vc{}{}'.format(compiler_ts[0], compiler_ts[1]) - else: - tag = '' - return tag - - def threading_tag(self): - if not self.is_multithreading: - return '' - - if self.env.machines[self.for_machine].is_darwin(): - # - Mac: requires -mt for multithreading, so should not fall back to non-mt libraries. - return '-mt' - elif self.env.machines[self.for_machine].is_windows(): - # - Windows: requires -mt for multithreading, so should not fall back to non-mt libraries. - return '-mt' + # Where boost built from source actually installs it + c_root = Path('C:/Boost') + if c_root.is_dir(): + roots += [c_root] + + # Where boost documentation says it should be + prog_files = Path('C:/Program Files/boost') + # Where boost prebuilt binaries are + local_boost = Path('C:/local') + + candidates = [] # type: T.List[Path] + if prog_files.is_dir(): + candidates += [*prog_files.iterdir()] + if local_boost.is_dir(): + candidates += [*local_boost.iterdir()] + + roots += [x for x in candidates if x.name.lower().startswith('boost') and x.is_dir()] else: - # - Linux: leaves off -mt but libraries are multithreading-aware. - # - Cygwin: leaves off -mt but libraries are multithreading-aware. - return '' - - def version_tag(self): - return '-' + self.version.replace('.', '_') - - def debug_tag(self): - return '-gd' if self.is_debug else '' - - def arch_tag(self): - # currently only applies to windows msvc installed binaries - if self.env.detect_cpp_compiler(self.for_machine).get_id() not in ['msvc', 'clang-cl']: - return '' - # pre-compiled binaries only added arch tag for versions > 1.64 - if float(self.version) < 1.65: - return '' - arch = detect_cpu_family(self.env.coredata.compilers.host) - if arch == 'x86': - return '-x32' - elif arch == 'x86_64': - return '-x64' + tmp = [] # type: T.List[Path] + # Add unix paths + tmp += [Path(x).parent for x in self.clib_compiler.get_default_include_dirs()] + + # Homebrew + brew_boost = Path('/usr/local/Cellar/boost') + if brew_boost.is_dir(): + tmp += [x for x in brew_boost.iterdir()] + + # Add some default system paths + tmp += [Path('/opt/local')] + tmp += [Path('/usr/local/opt/boost')] + tmp += [Path('/usr/local')] + tmp += [Path('/usr')] + + # Cleanup paths + tmp = [x for x in tmp if x.is_dir()] + tmp = [x.resolve() for x in tmp] + roots += tmp + + return roots + + def log_details(self) -> str: + modules = sorted(set(self.modules)) + if modules: + return 'modules: ' + ', '.join(modules) return '' - def versioned_abi_tag(self): - return self.compiler_tag() + self.threading_tag() + self.debug_tag() + self.arch_tag() + self.version_tag() - - # FIXME - how to handle different distributions, e.g. for Mac? Currently we handle homebrew and macports, but not fink. - def abi_tags(self): - if self.env.machines[self.for_machine].is_windows(): - return [self.versioned_abi_tag(), self.threading_tag()] - else: - return [self.threading_tag()] - - def sourceforge_dir(self): - if self.env.detect_cpp_compiler(self.for_machine).get_id() != 'msvc': - return None - comp_ts_version = self.env.detect_cpp_compiler(self.for_machine).get_toolset_version() - arch = detect_cpu_family(self.env.coredata.compilers.host) - if arch == 'x86': - return 'lib32-msvc-{}'.format(comp_ts_version) - elif arch == 'x86_64': - return 'lib64-msvc-{}'.format(comp_ts_version) - else: - # Does anyone do Boost cross-compiling to other archs on Windows? - return None - - def find_libraries_with_abi_tag(self, tag): - - # All modules should have the same tag - self.lib_modules = {} - - all_found = True - - for module in self.requested_modules: - libname = 'boost_' + module + tag - - args = self.clib_compiler.find_library(libname, self.env, self.extra_lib_dirs()) - if args is None: - mlog.debug("Couldn\'t find library '{}' for boost module '{}' (ABI tag = '{}')".format(libname, module, tag)) - all_found = False - else: - mlog.debug('Link args for boost module "{}" are {}'.format(module, args)) - self.lib_modules['boost_' + module] = args - - return all_found - - def find_libraries_with_abi_tags(self, tags): - for tag in tags: - if self.find_libraries_with_abi_tag(tag): - return True - return False - - def detect_lib_modules_win(self): - if not self.libdir: - # The libdirs in the distributed binaries (from sf) - lib_sf = self.sourceforge_dir() - - if self.boost_root: - roots = [self.boost_root] - else: - roots = self.boost_roots - for root in roots: - # The default libdir when building - libdir = os.path.join(root, 'lib') - if os.path.isdir(libdir): - self.libdir = libdir - break - if lib_sf: - full_path = os.path.join(root, lib_sf) - if os.path.isdir(full_path): - self.libdir = full_path - break + def log_info(self) -> str: + if self.boost_root: + return self.boost_root.as_posix() + return '' - if not self.libdir: - return + def _include_dir_from_version_header(self, hfile: Path) -> BoostIncludeDir: + # Extract the version with a regex. Using clib_compiler.get_define would + # also work, however, this is slower (since it the compiler has to be + # invoked) and overkill since the layout of the header is always the same. + assert hfile.exists() + raw = hfile.read_text() + m = re.search(r'#define\s+BOOST_VERSION\s+([0-9]+)', raw) + if not m: + mlog.debug('Failed to extract version information from {}'.format(hfile)) + return BoostIncludeDir(hfile.parents[1], 0) + return BoostIncludeDir(hfile.parents[1], int(m.group(1))) + + def _extra_compile_args(self) -> T.List[str]: + args = [] # type: T.List[str] + args += ['-DBOOST_ALL_NO_LIB'] # Disable automatic linking + if not self.static: + args += ['-DBOOST_ALL_DYN_LINK'] + return args - for name in self.need_static_link: - # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a - libname = 'lib' + name + self.versioned_abi_tag() + '.lib' - if os.path.isfile(os.path.join(self.libdir, libname)): - self.lib_modules[self.modname_from_filename(libname)] = [libname] - else: - libname = "lib{}.lib".format(name) - if os.path.isfile(os.path.join(self.libdir, libname)): - self.lib_modules[name[3:]] = [libname] - - # globber1 applies to a layout=system installation - # globber2 applies to a layout=versioned installation - globber1 = 'libboost_*' if self.static else 'boost_*' - globber2 = globber1 + self.versioned_abi_tag() - # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a - globber2_matches = glob.glob(os.path.join(self.libdir, globber2 + '.lib')) - for entry in globber2_matches: - fname = os.path.basename(entry) - self.lib_modules[self.modname_from_filename(fname)] = [fname] - if not globber2_matches: - # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a - for entry in glob.glob(os.path.join(self.libdir, globber1 + '.lib')): - if self.static: - fname = os.path.basename(entry) - self.lib_modules[self.modname_from_filename(fname)] = [fname] - - def detect_lib_modules_nix(self): - if self.static: - libsuffix = 'a' - elif self.env.machines[self.for_machine].is_darwin(): - libsuffix = 'dylib' - else: - libsuffix = 'so' - globber = 'libboost_*.{}'.format(libsuffix) - if self.libdir: - libdirs = [self.libdir] - elif self.boost_root is None: - libdirs = mesonlib.get_library_dirs() - else: - libdirs = [os.path.join(self.boost_root, 'lib')] - for libdir in libdirs: - for name in self.need_static_link: - libname = 'lib{}.a'.format(name) - if os.path.isfile(os.path.join(libdir, libname)): - self.lib_modules[name] = [libname] - for entry in glob.glob(os.path.join(libdir, globber)): - # I'm not 100% sure what to do here. Some distros - # have modules such as thread only as -mt versions. - # On debian all packages are built threading=multi - # but not suffixed with -mt. - # FIXME: implement detect_lib_modules_{debian, redhat, ...} - # FIXME: this wouldn't work with -mt-gd either. -BDR - if self.is_multithreading and mesonlib.is_debianlike(): - pass - elif self.is_multithreading and entry.endswith('-mt.{}'.format(libsuffix)): - pass - elif not entry.endswith('-mt.{}'.format(libsuffix)): - pass - else: - continue - modname = self.modname_from_filename(entry) - if modname not in self.lib_modules: - self.lib_modules[modname] = [entry] - - def extra_lib_dirs(self): - if self.libdir: - return [self.libdir] - elif self.boost_root: - return [os.path.join(self.boost_root, 'lib')] - return [] - - def get_link_args(self, **kwargs): - args = [] - for d in self.extra_lib_dirs(): - args += self.clib_compiler.get_linker_search_args(d) - for lib in self.requested_modules: - args += self.lib_modules['boost_' + lib] - return args +# See https://www.boost.org/doc/libs/1_72_0/more/getting_started/unix-variants.html#library-naming +# See https://mesonbuild.com/Reference-tables.html#cpu-families +boost_arch_map = { + 'aarch64': 'a64', + 'arc': 'a32', + 'arm': 'a32', + 'ia64': 'i64', + 'mips': 'm32', + 'mips64': 'm64', + 'ppc': 'p32', + 'ppc64': 'p64', + 'sparc': 's32', + 'sparc64': 's64', + 'x86': 'x32', + 'x86_64': 'x64', +} + + +#### ---- BEGIN GENERATED ---- #### +# # +# Generated with tools/boost_names.py: +# - boost version: 1.72.0 +# - modules found: 158 +# - libraries found: 42 +# - def get_sources(self): - return [] - -# Generated with boost_names.py -BOOST_LIBS = [ - 'boost_atomic', - 'boost_chrono', - 'boost_chrono', - 'boost_container', - 'boost_context', - 'boost_coroutine', - 'boost_date_time', - 'boost_exception', - 'boost_fiber', - 'boost_filesystem', - 'boost_graph', - 'boost_iostreams', - 'boost_locale', - 'boost_log', - 'boost_log_setup', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_math_tr1', - 'boost_math_tr1f', - 'boost_math_tr1l', - 'boost_math_c99', - 'boost_math_c99f', - 'boost_math_c99l', - 'boost_mpi', - 'boost_program_options', - 'boost_random', - 'boost_regex', - 'boost_serialization', - 'boost_wserialization', - 'boost_signals', - 'boost_stacktrace_noop', - 'boost_stacktrace_backtrace', - 'boost_stacktrace_addr2line', - 'boost_stacktrace_basic', - 'boost_stacktrace_windbg', - 'boost_stacktrace_windbg_cached', - 'boost_system', - 'boost_prg_exec_monitor', - 'boost_test_exec_monitor', - 'boost_unit_test_framework', - 'boost_thread', - 'boost_timer', - 'boost_type_erasure', - 'boost_wave' -] - -BOOST_DIRS = [ - 'lambda', - 'optional', - 'convert', - 'system', - 'uuid', - 'archive', - 'align', - 'timer', - 'chrono', - 'gil', - 'logic', - 'signals', - 'predef', - 'tr1', - 'multi_index', - 'property_map', - 'multi_array', - 'context', - 'random', - 'endian', - 'circular_buffer', - 'proto', - 'assign', - 'format', - 'math', - 'phoenix', - 'graph', - 'locale', - 'mpl', - 'pool', - 'unordered', - 'core', - 'exception', - 'ptr_container', - 'flyweight', - 'range', - 'typeof', - 'thread', - 'move', - 'spirit', - 'dll', - 'compute', - 'serialization', - 'ratio', - 'msm', - 'config', - 'metaparse', - 'coroutine2', - 'qvm', - 'program_options', - 'concept', - 'detail', - 'hana', - 'concept_check', - 'compatibility', - 'variant', - 'type_erasure', - 'mpi', - 'test', - 'fusion', - 'log', - 'sort', - 'local_function', - 'units', - 'functional', - 'preprocessor', - 'integer', - 'container', - 'polygon', - 'interprocess', - 'numeric', - 'iterator', - 'wave', - 'lexical_cast', - 'multiprecision', - 'utility', - 'tti', - 'asio', - 'dynamic_bitset', - 'algorithm', - 'xpressive', - 'bimap', - 'signals2', - 'type_traits', - 'regex', - 'statechart', - 'parameter', - 'icl', - 'python', - 'lockfree', - 'intrusive', - 'io', - 'pending', - 'geometry', - 'tuple', - 'iostreams', - 'heap', - 'atomic', - 'filesystem', - 'smart_ptr', - 'function', - 'fiber', - 'type_index', - 'accumulators', - 'function_types', - 'coroutine', - 'vmd', - 'date_time', - 'property_tree', - 'bind' -] +class BoostLibrary(): + def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): + self.name = name + self.shared = shared + self.static = static + self.single = single + self.multi = multi + +class BoostModule(): + def __init__(self, name: str, key: str, desc: str, libs: T.List[str]): + self.name = name + self.key = key + self.desc = desc + self.libs = libs + + +# dict of all know libraries with additional compile options +boost_libraries = { + 'boost_atomic': BoostLibrary( + name='boost_atomic', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_chrono': BoostLibrary( + name='boost_chrono', + shared=['-DBOOST_ALL_DYN_LINK=1'], + static=['-DBOOST_All_STATIC_LINK=1'], + single=[], + multi=[], + ), + 'boost_container': BoostLibrary( + name='boost_container', + shared=['-DBOOST_CONTAINER_DYN_LINK=1'], + static=['-DBOOST_CONTAINER_STATIC_LINK=1'], + single=[], + multi=[], + ), + 'boost_context': BoostLibrary( + name='boost_context', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_contract': BoostLibrary( + name='boost_contract', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_coroutine': BoostLibrary( + name='boost_coroutine', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_date_time': BoostLibrary( + name='boost_date_time', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_exception': BoostLibrary( + name='boost_exception', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_fiber': BoostLibrary( + name='boost_fiber', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_fiber_numa': BoostLibrary( + name='boost_fiber_numa', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_filesystem': BoostLibrary( + name='boost_filesystem', + shared=['-DBOOST_FILESYSTEM_DYN_LINK=1'], + static=['-DBOOST_FILESYSTEM_STATIC_LINK=1'], + single=[], + multi=[], + ), + 'boost_graph': BoostLibrary( + name='boost_graph', + shared=['-DBOOST_GRAPH_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_iostreams': BoostLibrary( + name='boost_iostreams', + shared=['-DBOOST_IOSTREAMS_DYN_LINK=1', '-DBOOST_IOSTREAMS_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_locale': BoostLibrary( + name='boost_locale', + shared=['-DBOOST_LOCALE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_log': BoostLibrary( + name='boost_log', + shared=['-DBOOST_LOG_DLL', '-DBOOST_LOG_DYN_LINK=1'], + static=[], + single=['BOOST_LOG_NO_THREADS'], + multi=[], + ), + 'boost_log_setup': BoostLibrary( + name='boost_log_setup', + shared=['-DBOOST_LOG_DYN_LINK=1', '-DBOOST_LOG_SETUP_DLL', '-DBOOST_LOG_SETUP_DYN_LINK=1'], + static=[], + single=['BOOST_LOG_NO_THREADS'], + multi=[], + ), + 'boost_math_c99': BoostLibrary( + name='boost_math_c99', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_math_c99f': BoostLibrary( + name='boost_math_c99f', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_math_c99l': BoostLibrary( + name='boost_math_c99l', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_math_tr1': BoostLibrary( + name='boost_math_tr1', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_math_tr1f': BoostLibrary( + name='boost_math_tr1f', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_math_tr1l': BoostLibrary( + name='boost_math_tr1l', + shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_mpi': BoostLibrary( + name='boost_mpi', + shared=['-DBOOST_MPI_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_prg_exec_monitor': BoostLibrary( + name='boost_prg_exec_monitor', + shared=['-DBOOST_TEST_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_program_options': BoostLibrary( + name='boost_program_options', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_random': BoostLibrary( + name='boost_random', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_regex': BoostLibrary( + name='boost_regex', + shared=['-DBOOST_REGEX_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_serialization': BoostLibrary( + name='boost_serialization', + shared=['-DBOOST_SERIALIZATION_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_addr2line': BoostLibrary( + name='boost_stacktrace_addr2line', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_backtrace': BoostLibrary( + name='boost_stacktrace_backtrace', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_basic': BoostLibrary( + name='boost_stacktrace_basic', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_noop': BoostLibrary( + name='boost_stacktrace_noop', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_windbg': BoostLibrary( + name='boost_stacktrace_windbg', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_stacktrace_windbg_cached': BoostLibrary( + name='boost_stacktrace_windbg_cached', + shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_system': BoostLibrary( + name='boost_system', + shared=['-DBOOST_SYSTEM_DYN_LINK=1'], + static=['-DBOOST_SYSTEM_STATIC_LINK=1'], + single=[], + multi=[], + ), + 'boost_test_exec_monitor': BoostLibrary( + name='boost_test_exec_monitor', + shared=['-DBOOST_TEST_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_thread': BoostLibrary( + name='boost_thread', + shared=['-DBOOST_THREAD_USE_DLL=1'], + static=['-DBOOST_THREAD_USE_LIB=1'], + single=[], + multi=[], + ), + 'boost_timer': BoostLibrary( + name='boost_timer', + shared=['-DBOOST_TIMER_DYN_LINK=1'], + static=['-DBOOST_TIMER_STATIC_LINK=1'], + single=[], + multi=[], + ), + 'boost_type_erasure': BoostLibrary( + name='boost_type_erasure', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_unit_test_framework': BoostLibrary( + name='boost_unit_test_framework', + shared=['-DBOOST_TEST_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), + 'boost_wave': BoostLibrary( + name='boost_wave', + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_wserialization': BoostLibrary( + name='boost_wserialization', + shared=['-DBOOST_SERIALIZATION_DYN_LINK=1'], + static=[], + single=[], + multi=[], + ), +} + +# # +#### ---- END GENERATED ---- #### diff --git a/run_project_tests.py b/run_project_tests.py index 65e1d0cd8..1194abf49 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -136,6 +136,7 @@ do_debug = under_ci or print_debug no_meson_log_msg = 'No meson-log.txt found.' system_compiler = None +compiler_id_map = {} # type: T.Dict[str, str] class StopException(Exception): def __init__(self): @@ -544,12 +545,24 @@ def gather_tests(testdir: Path) -> T.Iterator[TestDef]: assert "val" in i skip = False + # Add an empty matrix entry + if i['val'] is None: + tmp_opts += [(None, False)] + continue + # Skip the matrix entry if environment variable is present if 'skip_on_env' in i: for env in i['skip_on_env']: if env in os.environ: skip = True + # Only run the test if all compiler ID's match + if 'compilers' in i: + for lang, id_list in i['compilers'].items(): + if lang not in compiler_id_map or compiler_id_map[lang] not in id_list: + skip = True + break + tmp_opts += [('{}={}'.format(key, i['val']), skip)] if opt_list: @@ -561,9 +574,27 @@ def gather_tests(testdir: Path) -> T.Iterator[TestDef]: else: opt_list = [[x] for x in tmp_opts] + # Exclude specific configurations + if 'exclude' in matrix: + assert isinstance(matrix['exclude'], list) + new_opt_list = [] # type: T.List[T.List[T.Tuple[str, bool]]] + for i in opt_list: + exclude = False + opt_names = [x[0] for x in i] + for j in matrix['exclude']: + ex_list = ['{}={}'.format(k, v) for k, v in j.items()] + if all([x in opt_names for x in ex_list]): + exclude = True + break + + if not exclude: + new_opt_list += [i] + + opt_list = new_opt_list + for i in opt_list: - name = ' '.join([x[0] for x in i]) - opts = ['-D' + x[0] for x in i] + name = ' '.join([x[0] for x in i if x[0] is not None]) + opts = ['-D' + x[0] for x in i if x[0] is not None] skip = any([x[1] for x in i]) all_tests += [TestDef(t.path, name, opts, skip)] @@ -991,7 +1022,7 @@ def check_meson_commands_work(options): def detect_system_compiler(options): - global system_compiler + global system_compiler, compiler_id_map with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: fake_opts = get_fake_options('/') @@ -1002,7 +1033,8 @@ def detect_system_compiler(options): for lang in sorted(compilers.all_languages): try: comp = env.compiler_from_language(lang, MachineChoice.HOST) - details = '%s %s' % (' '.join(comp.get_exelist()), comp.get_version_string()) + details = '{} {} [{}]'.format(' '.join(comp.get_exelist()), comp.get_version_string(), comp.get_id()) + compiler_id_map[lang] = comp.get_id() except mesonlib.MesonException: comp = None details = 'not found' diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index eec8728eb..ccfaa6647 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -1,31 +1,25 @@ # this test requires the following on Ubuntu: libboost-{system,python,log,thread,test}-dev project('boosttest', 'cpp', - default_options : ['cpp_std=c++11']) + default_options : ['cpp_std=c++14']) -add_project_arguments(['-DBOOST_LOG_DYN_LINK'], - language : 'cpp' -) +s = get_option('static') -dep = dependency('boost', required: false) +dep = dependency('boost', static: s, required: false) if not dep.found() error('MESON_SKIP_TEST boost not found.') endif -compiler = meson.get_compiler('cpp') -if compiler.has_argument('-permissive') - # boost 1.64, the version we test against, doesn't work with -permissive - add_project_arguments('-permissive', language: 'cpp') -endif - # We want to have multiple separate configurations of Boost # within one project. The need to be independent of each other. # Use one without a library dependency and one with it. -linkdep = dependency('boost', modules : ['thread', 'system', 'test']) -staticdep = dependency('boost', modules : ['thread', 'system'], static : true) -testdep = dependency('boost', modules : ['unit_test_framework']) -nomoddep = dependency('boost') -extralibdep = dependency('boost', modules : ['thread', 'system', 'log_setup', 'log']) +linkdep = dependency('boost', static: s, modules : ['thread', 'system']) +testdep = dependency('boost', static: s, modules : ['unit_test_framework']) +nomoddep = dependency('boost', static: s) +extralibdep = dependency('boost', static: s, modules : ['thread', 'system', 'date_time', 'log_setup', 'log', 'filesystem', 'regex']) +notfound = dependency('boost', static: s, modules : ['this_should_not_exist_on_any_systen'], required: false) + +assert(not notfound.found()) pymod = import('python') python2 = pymod.find_installation('python2', required: host_machine.system() == 'linux', disabler: true) @@ -34,28 +28,28 @@ python2dep = python2.dependency(required: host_machine.system() == 'linux', embe python3dep = python3.dependency(required: host_machine.system() == 'linux', embed: true, disabler: true) # compile python 2/3 modules only if we found a corresponding python version -if(python2dep.found() and host_machine.system() == 'linux') +if(python2dep.found() and host_machine.system() == 'linux' and not s) if(dep.version().version_compare('>=1.67')) # if we have a new version of boost, we need to construct the module name based # on the installed version of python (and hope that they match the version boost # was compiled against) py2version_string = ''.join(python2dep.version().split('.')) - bpython2dep = dependency('boost', modules : ['python' + py2version_string], required: false, disabler: true) + bpython2dep = dependency('boost', static: s, modules : ['python' + py2version_string], required: false, disabler: true) else # if we have an older version of boost, we need to use the old module names - bpython2dep = dependency('boost', modules : ['python'], required: false, disabler: true) + bpython2dep = dependency('boost', static: s, modules : ['python'], required: false, disabler: true) endif else python2dep = disabler() bpython2dep = disabler() endif -if(python3dep.found() and host_machine.system() == 'linux') +if(python3dep.found() and host_machine.system() == 'linux' and not s) if(dep.version().version_compare('>=1.67')) py3version_string = ''.join(python3dep.version().split('.')) - bpython3dep = dependency('boost', modules : ['python' + py3version_string], required: false, disabler: true) + bpython3dep = dependency('boost', static: s, modules : ['python' + py3version_string], required: false, disabler: true) else - bpython3dep = dependency('boost', modules : ['python3'], required: false, disabler: true) + bpython3dep = dependency('boost', static: s, modules : ['python3'], required: false, disabler: true) endif else python3dep = disabler() @@ -63,7 +57,6 @@ else endif linkexe = executable('linkedexe', 'linkexe.cc', dependencies : linkdep) -staticexe = executable('staticlinkedexe', 'linkexe.cc', dependencies : staticdep) unitexe = executable('utf', 'unit_test.cpp', dependencies: testdep) nomodexe = executable('nomod', 'nomod.cpp', dependencies : nomoddep) extralibexe = executable('extralibexe', 'extralib.cpp', dependencies : extralibdep) @@ -73,7 +66,6 @@ python2module = shared_library('python2_module', ['python_module.cpp'], dependen python3module = shared_library('python3_module', ['python_module.cpp'], dependencies: [python3dep, bpython3dep], name_prefix: '', cpp_args: ['-DMOD_NAME=python3_module']) test('Boost linktest', linkexe) -test('Boost statictest', staticexe) test('Boost UTF test', unitexe) test('Boost nomod', nomodexe) test('Boost extralib test', extralibexe) @@ -87,4 +79,4 @@ test('Boost Python3', python3interpreter, args: ['./test_python_module.py', meso subdir('partial_dep') # check we can apply a version constraint -dependency('boost', version: '>=@0@'.format(dep.version())) +dependency('boost', static: s, version: '>=@0@'.format(dep.version())) diff --git a/test cases/frameworks/1 boost/meson_options.txt b/test cases/frameworks/1 boost/meson_options.txt new file mode 100644 index 000000000..019feaf38 --- /dev/null +++ b/test cases/frameworks/1 boost/meson_options.txt @@ -0,0 +1 @@ +option('static', type: 'boolean', value: false) diff --git a/test cases/frameworks/1 boost/test_matrix.json b/test cases/frameworks/1 boost/test_matrix.json new file mode 100644 index 000000000..730610ee5 --- /dev/null +++ b/test cases/frameworks/1 boost/test_matrix.json @@ -0,0 +1,19 @@ +{ + "options": { + "static": [ + { "val": "true", "skip_on_env": [ "SKIP_STATIC_BOOST" ] }, + { "val": "false" } + ], + "b_vscrt": [ + { "val": null }, + { "val": "md", "compilers": { "cpp": [ "msvc" ] } }, + { "val": "mdd", "compilers": { "cpp": [ "msvc" ] } }, + { "val": "mt", "compilers": { "cpp": [ "msvc" ] } }, + { "val": "mtd", "compilers": { "cpp": [ "msvc" ] } } + ] + }, + "exclude": [ + { "static": "false", "b_vscrt": "mt" }, + { "static": "false", "b_vscrt": "mtd" } + ] +} diff --git a/test cases/frameworks/1 boost/unit_test.cpp b/test cases/frameworks/1 boost/unit_test.cpp index 35059997d..fa1fbaa58 100644 --- a/test cases/frameworks/1 boost/unit_test.cpp +++ b/test cases/frameworks/1 boost/unit_test.cpp @@ -1,4 +1,3 @@ -#define BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE "MesonTest" #define BOOST_TEST_MAIN #include diff --git a/tools/boost_names.py b/tools/boost_names.py index af461d80b..d26d34b65 100755 --- a/tools/boost_names.py +++ b/tools/boost_names.py @@ -24,164 +24,249 @@ boost/$ path/to/meson/tools/boost_names.py >> path/to/meson/dependencies/misc.py """ import sys -import os -import collections -import pprint import json import re +import textwrap +import functools +import typing as T +from pathlib import Path + +lib_dir = Path('libs') +jamroot = Path('Jamroot') + +not_modules = ['config', 'disjoint_sets', 'headers'] + +export_modules = False + + +@functools.total_ordering +class BoostLibrary(): + def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): + self.name = name + self.shared = shared + self.static = static + self.single = single + self.multi = multi + + def __lt__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: + if isinstance(other, BoostLibrary): + return self.name < other.name + return NotImplemented + + def __eq__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: + if isinstance(other, BoostLibrary): + return self.name == other.name + elif isinstance(other, str): + return self.name == other + return NotImplemented + + def __hash__(self) -> int: + return hash(self.name) + +@functools.total_ordering +class BoostModule(): + def __init__(self, name: str, key: str, desc: str, libs: T.List[BoostLibrary]): + self.name = name + self.key = key + self.desc = desc + self.libs = libs + + def __lt__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: + if isinstance(other, BoostModule): + return self.key < other.key + return NotImplemented -Module = collections.namedtuple('Module', ['dirname', 'name', 'libnames']) -Module.__repr__ = lambda self: str((self.dirname, self.name, self.libnames)) # type: ignore - -LIBS = 'libs' - -manual_map = { - 'callable_traits': 'Call Traits', - 'crc': 'CRC', - 'dll': 'DLL', - 'gil': 'GIL', - 'graph_parallel': 'GraphParallel', - 'icl': 'ICL', - 'io': 'IO State Savers', - 'msm': 'Meta State Machine', - 'mpi': 'MPI', - 'mpl': 'MPL', - 'multi_array': 'Multi-Array', - 'multi_index': 'Multi-Index', - 'numeric': 'Numeric Conversion', - 'ptr_container': 'Pointer Container', - 'poly_collection': 'PolyCollection', - 'qvm': 'QVM', - 'throw_exception': 'ThrowException', - 'tti': 'TTI', - 'vmd': 'VMD', -} - -extra = [ - Module('utility', 'Compressed Pair', []), - Module('core', 'Enable If', []), - Module('functional', 'Functional/Factory', []), - Module('functional', 'Functional/Forward', []), - Module('functional', 'Functional/Hash', []), - Module('functional', 'Functional/Overloaded Function', []), - Module('utility', 'Identity Type', []), - Module('utility', 'In Place Factory, Typed In Place Factory', []), - Module('numeric', 'Interval', []), - Module('math', 'Math Common Factor', []), - Module('math', 'Math Octonion', []), - Module('math', 'Math Quaternion', []), - Module('math', 'Math/Special Functions', []), - Module('math', 'Math/Statistical Distributions', []), - Module('bind', 'Member Function', []), - Module('algorithm', 'Min-Max', []), - Module('numeric', 'Odeint', []), - Module('utility', 'Operators', []), - Module('core', 'Ref', []), - Module('utility', 'Result Of', []), - Module('algorithm', 'String Algo', []), - Module('core', 'Swap', []), - Module('', 'Tribool', []), - Module('numeric', 'uBLAS', []), - Module('utility', 'Value Initialized', []), -] - -# Cannot find the following modules in the documentation of boost -not_modules = ['beast', 'logic', 'mp11', 'winapi'] - -def eprint(message): - print(message, file=sys.stderr) - -def get_library_names(jamfile): - libs = [] - with open(jamfile) as jamfh: - jam = jamfh.read() - res = re.finditer(r'^lib[\s]+([A-Za-z0-9_]+)([^;]*);', jam, re.MULTILINE | re.DOTALL) - for matches in res: - if ':' in matches.group(2): - libs.append(matches.group(1)) - res = re.finditer(r'^boost-lib[\s]+([A-Za-z0-9_]+)([^;]*);', jam, re.MULTILINE | re.DOTALL) - for matches in res: - if ':' in matches.group(2): - libs.append('boost_{}'.format(matches.group(1))) - return libs -def exists(modules, module): - return len([x for x in modules if x.dirname == module.dirname]) != 0 +def get_boost_version() -> T.Optional[str]: + raw = jamroot.read_text() + m = re.search(r'BOOST_VERSION\s*:\s*([0-9\.]+)\s*;', raw) + if m: + return m.group(1) + return None -def get_modules(init=extra): - modules = init - for directory in os.listdir(LIBS): - if not os.path.isdir(os.path.join(LIBS, directory)): + +def get_libraries(jamfile: Path) -> T.List[BoostLibrary]: + # Extract libraries from the boost Jamfiles. This includes: + # - library name + # - compiler flags + + libs: T.List[BoostLibrary] = [] + raw = jamfile.read_text() + raw = re.sub(r'#.*\n', '\n', raw) # Remove comments + raw = re.sub(r'\s+', ' ', raw) # Force single space + raw = re.sub(r'}', ';', raw) # Cheat code blocks by converting } to ; + + cmds = raw.split(';') # Commands always terminate with a ; (I hope) + cmds = [x.strip() for x in cmds] # Some cleanup + + # "Parse" the relevant sections + for i in cmds: + parts = i.split(' ') + parts = [x for x in parts if x not in ['', ':']] + if not parts: continue - if directory in not_modules: + + # Parese libraries + if parts[0] in ['lib', 'boost-lib']: + assert len(parts) >= 2 + + # Get and check the library name + lname = parts[1] + if parts[0] == 'boost-lib': + lname = f'boost_{lname}' + if not lname.startswith('boost_'): + continue + + # Get shared / static defines + shared: T.List[str] = [] + static: T.List[str] = [] + single: T.List[str] = [] + multi: T.List[str] = [] + for j in parts: + m1 = re.match(r'shared:(.*)', j) + m2 = re.match(r'static:(.*)', j) + m3 = re.match(r'single:(.*)', j) + m4 = re.match(r'multi:(.*)', j) + + if m1: + shared += [m1.group(1)] + if m2: + static += [m2.group(1)] + if m3: + single += [m3.group(1)] + if m4: + multi += [m4.group(1)] + + shared = [f'-D{x}' for x in shared] + static = [f'-D{x}' for x in static] + libs += [BoostLibrary(lname, shared, static, single, multi)] + + return libs + + +def process_lib_dir(ldir: Path) -> T.List[BoostModule]: + meta_file = ldir / 'meta' / 'libraries.json' + bjam_file = ldir / 'build' / 'Jamfile.v2' + if not meta_file.exists(): + print(f'WARNING: Meta file {meta_file} does not exist') + return [] + + # Extract libs + libs: T.List[BoostLibrary] = [] + if bjam_file.exists(): + libs = get_libraries(bjam_file) + + # Extract metadata + data = json.loads(meta_file.read_text()) + if not isinstance(data, list): + data = [data] + + modules: T.List[BoostModule] = [] + for i in data: + modules += [BoostModule(i['name'], i['key'], i['description'], libs)] + + return modules + + +def get_modules() -> T.List[BoostModule]: + modules: T.List[BoostModule] = [] + for i in lib_dir.iterdir(): + if not i.is_dir() or i.name in not_modules: continue - jamfile = os.path.join(LIBS, directory, 'build', 'Jamfile.v2') - if os.path.isfile(jamfile): - libs = get_library_names(jamfile) - else: - libs = [] - if directory in manual_map.keys(): - modname = manual_map[directory] + + # numeric has sub libs + subdirs = i / 'sublibs' + metadir = i / 'meta' + if subdirs.exists() and not metadir.exists(): + for j in i.iterdir(): + if not j.is_dir(): + continue + modules += process_lib_dir(j) else: - modname = directory.replace('_', ' ').title() - modules.append(Module(directory, modname, libs)) + modules += process_lib_dir(i) + return modules -def get_modules_2(): - modules = [] - # The python module uses an older build system format and is not easily parseable. - # We add the python module libraries manually. - modules.append(Module('python', 'Python', ['boost_python', 'boost_python3', 'boost_numpy', 'boost_numpy3'])) - for (root, _, files) in os.walk(LIBS): - for f in files: - if f == "libraries.json": - projectdir = os.path.dirname(root) - - jamfile = os.path.join(projectdir, 'build', 'Jamfile.v2') - if os.path.isfile(jamfile): - libs = get_library_names(jamfile) - else: - libs = [] - - # Get metadata for module - jsonfile = os.path.join(root, f) - with open(jsonfile) as jsonfh: - boost_modules = json.loads(jsonfh.read()) - if(isinstance(boost_modules, dict)): - boost_modules = [boost_modules] - for boost_module in boost_modules: - modules.append(Module(boost_module['key'], boost_module['name'], libs)) - - # Some subprojects do not have meta directory with json file. Find those - jsonless_modules = [x for x in get_modules([]) if not exists(modules, x)] - for module in jsonless_modules: - eprint("WARNING: {} does not have meta/libraries.json. Will guess pretty name '{}'".format(module.dirname, module.name)) - modules.extend(jsonless_modules) - return modules +def main() -> int: + if not lib_dir.is_dir() or not jamroot.exists(): + print("ERROR: script must be run in boost source directory") + return 1 + + vers = get_boost_version() + modules = get_modules() + modules = sorted(modules) + libraries = [x for y in modules for x in y.libs] + libraries = sorted(set(libraries)) + + print(textwrap.dedent(f'''\ + #### ---- BEGIN GENERATED ---- #### + # # + # Generated with tools/boost_names.py: + # - boost version: {vers} + # - modules found: {len(modules)} + # - libraries found: {len(libraries)} + # + + class BoostLibrary(): + def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): + self.name = name + self.shared = shared + self.static = static + self.single = single + self.multi = multi + + class BoostModule(): + def __init__(self, name: str, key: str, desc: str, libs: T.List[str]): + self.name = name + self.key = key + self.desc = desc + self.libs = libs + + + # dict of all know libraries with additional compile options + boost_libraries = {{\ + ''')) + + for i in libraries: + print(textwrap.indent(textwrap.dedent(f"""\ + '{i.name}': BoostLibrary( + name='{i.name}', + shared={i.shared}, + static={i.static}, + single={i.single}, + multi={i.multi}, + ),\ + """), ' ')) + + if export_modules: + print(textwrap.dedent(f'''\ + }} + -def main(args): - if not os.path.isdir(LIBS): - eprint("ERROR: script must be run in boost source directory") + # dict of all modules with metadata + boost_modules = {{\ + ''')) - # It will pick jsonless algorithm if 1 is given as argument - impl = 0 - if len(args) > 1: - if args[1] == '1': - impl = 1 + for mod in modules: + desc_excaped = re.sub(r"'", "\\'", mod.desc) + print(textwrap.indent(textwrap.dedent(f"""\ + '{mod.key}': BoostModule( + name='{mod.name}', + key='{mod.key}', + desc='{desc_excaped}', + libs={[x.name for x in mod.libs]}, + ),\ + """), ' ')) - if impl == 1: - modules = get_modules() - else: - modules = get_modules_2() + print(textwrap.dedent(f'''\ + }} - sorted_modules = sorted(modules, key=lambda module: module.name.lower()) - sorted_modules = [x[2] for x in sorted_modules if x[2]] - sorted_modules = sum(sorted_modules, []) - sorted_modules = [x for x in sorted_modules if x.startswith('boost')] + # # + #### ---- END GENERATED ---- ####\ + ''')) - pp = pprint.PrettyPrinter() - pp.pprint(sorted_modules) + return 0 if __name__ == '__main__': - main(sys.argv) + sys.exit(main())