1# Copyright 2013-2020 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import re
16import functools
17import typing as T
18from pathlib import Path
19
20from .. import mlog
21from .. import mesonlib
22from ..environment import Environment
23
24from .base import DependencyException, SystemDependency
25from .pkgconfig import PkgConfigDependency
26from .misc import threads_factory
27
28if T.TYPE_CHECKING:
29    from ..environment import Properties
30
31# On windows 3 directory layouts are supported:
32# * The default layout (versioned) installed:
33#   - $BOOST_ROOT/include/boost-x_x/boost/*.hpp
34#   - $BOOST_ROOT/lib/*.lib
35# * The non-default layout (system) installed:
36#   - $BOOST_ROOT/include/boost/*.hpp
37#   - $BOOST_ROOT/lib/*.lib
38# * The pre-built binaries from sf.net:
39#   - $BOOST_ROOT/boost/*.hpp
40#   - $BOOST_ROOT/lib<arch>-<compiler>/*.lib where arch=32/64 and compiler=msvc-14.1
41#
42# Note that we should also try to support:
43# mingw-w64 / Windows : libboost_<module>-mt.a            (location = <prefix>/mingw64/lib/)
44#                       libboost_<module>-mt.dll.a
45#
46# The `modules` argument accept library names. This is because every module that
47# has libraries to link against also has multiple options regarding how to
48# link. See for example:
49# * http://www.boost.org/doc/libs/1_65_1/libs/test/doc/html/boost_test/usage_variants.html
50# * http://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace/configuration_and_build.html
51# * http://www.boost.org/doc/libs/1_65_1/libs/math/doc/html/math_toolkit/main_tr1.html
52
53# **On Unix**, official packaged versions of boost libraries follow the following schemes:
54#
55# Linux / Debian:   libboost_<module>.so -> libboost_<module>.so.1.66.0
56# Linux / Red Hat:  libboost_<module>.so -> libboost_<module>.so.1.66.0
57# Linux / OpenSuse: libboost_<module>.so -> libboost_<module>.so.1.66.0
58# Win   / Cygwin:   libboost_<module>.dll.a                                 (location = /usr/lib)
59#                   libboost_<module>.a
60#                   cygboost_<module>_1_64.dll                              (location = /usr/bin)
61# Win   / VS:       boost_<module>-vc<ver>-mt[-gd]-<arch>-1_67.dll          (location = C:/local/boost_1_67_0)
62# Mac   / homebrew: libboost_<module>.dylib + libboost_<module>-mt.dylib    (location = /usr/local/lib)
63# Mac   / macports: libboost_<module>.dylib + libboost_<module>-mt.dylib    (location = /opt/local/lib)
64#
65# Its not clear that any other abi tags (e.g. -gd) are used in official packages.
66#
67# On Linux systems, boost libs have multithreading support enabled, but without the -mt tag.
68#
69# Boost documentation recommends using complex abi tags like "-lboost_regex-gcc34-mt-d-1_36".
70# (See http://www.boost.org/doc/libs/1_66_0/more/getting_started/unix-variants.html#library-naming)
71# However, its not clear that any Unix distribution follows this scheme.
72# Furthermore, the boost documentation for unix above uses examples from windows like
73#   "libboost_regex-vc71-mt-d-x86-1_34.lib", so apparently the abi tags may be more aimed at windows.
74#
75# We follow the following strategy for finding modules:
76# A) Detect potential boost root directories (uses also BOOST_ROOT env var)
77# B) Foreach candidate
78#   1. Look for the boost headers (boost/version.pp)
79#   2. Find all boost libraries
80#     2.1 Add all libraries in lib*
81#     2.2 Filter out non boost libraries
82#     2.3 Filter the renaining libraries based on the meson requirements (static/shared, etc.)
83#     2.4 Ensure that all libraries have the same boost tag (and are thus compatible)
84#   3. Select the libraries matching the requested modules
85
86@functools.total_ordering
87class BoostIncludeDir():
88    def __init__(self, path: Path, version_int: int):
89        self.path = path
90        self.version_int = version_int
91        major = int(self.version_int / 100000)
92        minor = int((self.version_int / 100) % 1000)
93        patch = int(self.version_int % 100)
94        self.version = f'{major}.{minor}.{patch}'
95        self.version_lib = f'{major}_{minor}'
96
97    def __repr__(self) -> str:
98        return f'<BoostIncludeDir: {self.version} -- {self.path}>'
99
100    def __lt__(self, other: object) -> bool:
101        if isinstance(other, BoostIncludeDir):
102            return (self.version_int, self.path) < (other.version_int, other.path)
103        return NotImplemented
104
105@functools.total_ordering
106class BoostLibraryFile():
107    # Python libraries are special because of the included
108    # minor version in the module name.
109    boost_python_libs = ['boost_python', 'boost_numpy']
110    reg_python_mod_split = re.compile(r'(boost_[a-zA-Z]+)([0-9]*)')
111
112    reg_abi_tag = re.compile(r'^s?g?y?d?p?n?$')
113    reg_ver_tag = re.compile(r'^[0-9_]+$')
114
115    def __init__(self, path: Path):
116        self.path = path
117        self.name = self.path.name
118
119        # Initialize default properties
120        self.static = False
121        self.toolset = ''
122        self.arch = ''
123        self.version_lib = ''
124        self.mt = True
125
126        self.runtime_static = False
127        self.runtime_debug = False
128        self.python_debug = False
129        self.debug = False
130        self.stlport = False
131        self.deprecated_iostreams = False
132
133        # Post process the library name
134        name_parts = self.name.split('.')
135        self.basename = name_parts[0]
136        self.suffixes = name_parts[1:]
137        self.vers_raw = [x for x in self.suffixes if x.isdigit()]
138        self.suffixes = [x for x in self.suffixes if not x.isdigit()]
139        self.nvsuffix = '.'.join(self.suffixes)  # Used for detecting the library type
140        self.nametags = self.basename.split('-')
141        self.mod_name = self.nametags[0]
142        if self.mod_name.startswith('lib'):
143            self.mod_name = self.mod_name[3:]
144
145        # Set library version if possible
146        if len(self.vers_raw) >= 2:
147            self.version_lib = '{}_{}'.format(self.vers_raw[0], self.vers_raw[1])
148
149        # Detecting library type
150        if self.nvsuffix in ['so', 'dll', 'dll.a', 'dll.lib', 'dylib']:
151            self.static = False
152        elif self.nvsuffix in ['a', 'lib']:
153            self.static = True
154        else:
155            raise DependencyException(f'Unable to process library extension "{self.nvsuffix}" ({self.path})')
156
157        # boost_.lib is the dll import library
158        if self.basename.startswith('boost_') and self.nvsuffix == 'lib':
159            self.static = False
160
161        # Process tags
162        tags = self.nametags[1:]
163        # Filter out the python version tag and fix modname
164        if self.is_python_lib():
165            tags = self.fix_python_name(tags)
166        if not tags:
167            return
168
169        # Without any tags mt is assumed, however, an absence of mt in the name
170        # with tags present indicates that the lib was built without mt support
171        self.mt = False
172        for i in tags:
173            if i == 'mt':
174                self.mt = True
175            elif len(i) == 3 and i[1:] in ['32', '64']:
176                self.arch = i
177            elif BoostLibraryFile.reg_abi_tag.match(i):
178                self.runtime_static = 's' in i
179                self.runtime_debug = 'g' in i
180                self.python_debug = 'y' in i
181                self.debug = 'd' in i
182                self.stlport = 'p' in i
183                self.deprecated_iostreams = 'n' in i
184            elif BoostLibraryFile.reg_ver_tag.match(i):
185                self.version_lib = i
186            else:
187                self.toolset = i
188
189    def __repr__(self) -> str:
190        return f'<LIB: {self.abitag} {self.mod_name:<32} {self.path}>'
191
192    def __lt__(self, other: object) -> bool:
193        if isinstance(other, BoostLibraryFile):
194            return (
195                self.mod_name, self.static, self.version_lib, self.arch,
196                not self.mt, not self.runtime_static,
197                not self.debug, self.runtime_debug, self.python_debug,
198                self.stlport, self.deprecated_iostreams,
199                self.name,
200            ) < (
201                other.mod_name, other.static, other.version_lib, other.arch,
202                not other.mt, not other.runtime_static,
203                not other.debug, other.runtime_debug, other.python_debug,
204                other.stlport, other.deprecated_iostreams,
205                other.name,
206            )
207        return NotImplemented
208
209    def __eq__(self, other: object) -> bool:
210        if isinstance(other, BoostLibraryFile):
211            return self.name == other.name
212        return NotImplemented
213
214    def __hash__(self) -> int:
215        return hash(self.name)
216
217    @property
218    def abitag(self) -> str:
219        abitag = ''
220        abitag += 'S' if self.static else '-'
221        abitag += 'M' if self.mt else '-'
222        abitag += ' '
223        abitag += 's' if self.runtime_static else '-'
224        abitag += 'g' if self.runtime_debug else '-'
225        abitag += 'y' if self.python_debug else '-'
226        abitag += 'd' if self.debug else '-'
227        abitag += 'p' if self.stlport else '-'
228        abitag += 'n' if self.deprecated_iostreams else '-'
229        abitag += ' ' + (self.arch or '???')
230        abitag += ' ' + (self.toolset or '?')
231        abitag += ' ' + (self.version_lib or 'x_xx')
232        return abitag
233
234    def is_boost(self) -> bool:
235        return any([self.name.startswith(x) for x in ['libboost_', 'boost_']])
236
237    def is_python_lib(self) -> bool:
238        return any([self.mod_name.startswith(x) for x in BoostLibraryFile.boost_python_libs])
239
240    def fix_python_name(self, tags: T.List[str]) -> T.List[str]:
241        # Handle the boost_python naming madeness.
242        # See https://github.com/mesonbuild/meson/issues/4788 for some distro
243        # specific naming variantions.
244        other_tags = []  # type: T.List[str]
245
246        # Split the current modname into the base name and the version
247        m_cur = BoostLibraryFile.reg_python_mod_split.match(self.mod_name)
248        cur_name = m_cur.group(1)
249        cur_vers = m_cur.group(2)
250
251        # Update the current version string if the new version string is longer
252        def update_vers(new_vers: str) -> None:
253            nonlocal cur_vers
254            new_vers = new_vers.replace('_', '')
255            new_vers = new_vers.replace('.', '')
256            if not new_vers.isdigit():
257                return
258            if len(new_vers) > len(cur_vers):
259                cur_vers = new_vers
260
261        for i in tags:
262            if i.startswith('py'):
263                update_vers(i[2:])
264            elif i.isdigit():
265                update_vers(i)
266            elif len(i) >= 3 and i[0].isdigit and i[2].isdigit() and i[1] == '.':
267                update_vers(i)
268            else:
269                other_tags += [i]
270
271        self.mod_name = cur_name + cur_vers
272        return other_tags
273
274    def mod_name_matches(self, mod_name: str) -> bool:
275        if self.mod_name == mod_name:
276            return True
277        if not self.is_python_lib():
278            return False
279
280        m_cur = BoostLibraryFile.reg_python_mod_split.match(self.mod_name)
281        m_arg = BoostLibraryFile.reg_python_mod_split.match(mod_name)
282
283        if not m_cur or not m_arg:
284            return False
285
286        if m_cur.group(1) != m_arg.group(1):
287            return False
288
289        cur_vers = m_cur.group(2)
290        arg_vers = m_arg.group(2)
291
292        # Always assume python 2 if nothing is specified
293        if not arg_vers:
294            arg_vers = '2'
295
296        return cur_vers.startswith(arg_vers)
297
298    def version_matches(self, version_lib: str) -> bool:
299        # If no version tag is present, assume that it fits
300        if not self.version_lib or not version_lib:
301            return True
302        return self.version_lib == version_lib
303
304    def arch_matches(self, arch: str) -> bool:
305        # If no version tag is present, assume that it fits
306        if not self.arch or not arch:
307            return True
308        return self.arch == arch
309
310    def vscrt_matches(self, vscrt: str) -> bool:
311        # If no vscrt tag present, assume that it fits  ['/MD', '/MDd', '/MT', '/MTd']
312        if not vscrt:
313            return True
314        if vscrt in ['/MD', '-MD']:
315            return not self.runtime_static and not self.runtime_debug
316        elif vscrt in ['/MDd', '-MDd']:
317            return not self.runtime_static and self.runtime_debug
318        elif vscrt in ['/MT', '-MT']:
319            return (self.runtime_static or not self.static) and not self.runtime_debug
320        elif vscrt in ['/MTd', '-MTd']:
321            return (self.runtime_static or not self.static) and self.runtime_debug
322
323        mlog.warning(f'Boost: unknow vscrt tag {vscrt}. This may cause the compilation to fail. Please consider reporting this as a bug.', once=True)
324        return True
325
326    def get_compiler_args(self) -> T.List[str]:
327        args = []  # type: T.List[str]
328        if self.mod_name in boost_libraries:
329            libdef = boost_libraries[self.mod_name]  # type: BoostLibrary
330            if self.static:
331                args += libdef.static
332            else:
333                args += libdef.shared
334            if self.mt:
335                args += libdef.multi
336            else:
337                args += libdef.single
338        return args
339
340    def get_link_args(self) -> T.List[str]:
341        return [self.path.as_posix()]
342
343class BoostDependency(SystemDependency):
344    def __init__(self, environment: Environment, kwargs: T.Dict[str, T.Any]) -> None:
345        super().__init__('boost', environment, kwargs, language='cpp')
346        buildtype = environment.coredata.get_option(mesonlib.OptionKey('buildtype'))
347        assert isinstance(buildtype, str)
348        self.debug = buildtype.startswith('debug')
349        self.multithreading = kwargs.get('threading', 'multi') == 'multi'
350
351        self.boost_root = None  # type: T.Optional[Path]
352        self.explicit_static = 'static' in kwargs
353
354        # Extract and validate modules
355        self.modules = mesonlib.extract_as_list(kwargs, 'modules')  # type: T.List[str]
356        for i in self.modules:
357            if not isinstance(i, str):
358                raise DependencyException('Boost module argument is not a string.')
359            if i.startswith('boost_'):
360                raise DependencyException('Boost modules must be passed without the boost_ prefix')
361
362        self.modules_found = []    # type: T.List[str]
363        self.modules_missing = []  # type: T.List[str]
364
365        # Do we need threads?
366        if 'thread' in self.modules:
367            if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})):
368                self.is_found = False
369                return
370
371        # Try figuring out the architecture tag
372        self.arch = environment.machines[self.for_machine].cpu_family
373        self.arch = boost_arch_map.get(self.arch, None)
374
375        # First, look for paths specified in a machine file
376        props = self.env.properties[self.for_machine]
377        if any(x in self.env.properties[self.for_machine] for x in
378               ['boost_includedir', 'boost_librarydir', 'boost_root']):
379            self.detect_boost_machine_file(props)
380            return
381
382        # Finally, look for paths from .pc files and from searching the filesystem
383        self.detect_roots()
384
385    def check_and_set_roots(self, roots: T.List[Path], use_system: bool) -> None:
386        roots = list(mesonlib.OrderedSet(roots))
387        for j in roots:
388            #   1. Look for the boost headers (boost/version.hpp)
389            mlog.debug(f'Checking potential boost root {j.as_posix()}')
390            inc_dirs = self.detect_inc_dirs(j)
391            inc_dirs = sorted(inc_dirs, reverse=True)  # Prefer the newer versions
392
393            # Early abort when boost is not found
394            if not inc_dirs:
395                continue
396
397            lib_dirs = self.detect_lib_dirs(j, use_system)
398            self.is_found = self.run_check(inc_dirs, lib_dirs)
399            if self.is_found:
400                self.boost_root = j
401                break
402
403    def detect_boost_machine_file(self, props: 'Properties') -> None:
404        """Detect boost with values in the machine file or environment.
405
406        The machine file values are defaulted to the environment values.
407        """
408        # XXX: if we had a TypedDict we woudn't need this
409        incdir = props.get('boost_includedir')
410        assert incdir is None or isinstance(incdir, str)
411        libdir = props.get('boost_librarydir')
412        assert libdir is None or isinstance(libdir, str)
413
414        if incdir and libdir:
415            inc_dir = Path(incdir)
416            lib_dir = Path(libdir)
417
418            if not inc_dir.is_absolute() or not lib_dir.is_absolute():
419                raise DependencyException('Paths given for boost_includedir and boost_librarydir in machine file must be absolute')
420
421            mlog.debug('Trying to find boost with:')
422            mlog.debug(f'  - boost_includedir = {inc_dir}')
423            mlog.debug(f'  - boost_librarydir = {lib_dir}')
424
425            return self.detect_split_root(inc_dir, lib_dir)
426
427        elif incdir or libdir:
428            raise DependencyException('Both boost_includedir *and* boost_librarydir have to be set in your machine file (one is not enough)')
429
430        rootdir = props.get('boost_root')
431        # It shouldn't be possible to get here without something in boost_root
432        assert rootdir
433
434        raw_paths = mesonlib.stringlistify(rootdir)
435        paths = [Path(x) for x in raw_paths]
436        if paths and any([not x.is_absolute() for x in paths]):
437            raise DependencyException('boost_root path given in machine file must be absolute')
438
439        self.check_and_set_roots(paths, use_system=False)
440
441    def run_check(self, inc_dirs: T.List[BoostIncludeDir], lib_dirs: T.List[Path]) -> bool:
442        mlog.debug('  - potential library dirs: {}'.format([x.as_posix() for x in lib_dirs]))
443        mlog.debug('  - potential include dirs: {}'.format([x.path.as_posix() for x in inc_dirs]))
444
445        #   2. Find all boost libraries
446        libs = []  # type: T.List[BoostLibraryFile]
447        for i in lib_dirs:
448            libs = self.detect_libraries(i)
449            if libs:
450                mlog.debug(f'  - found boost library dir: {i}')
451                # mlog.debug('  - raw library list:')
452                # for j in libs:
453                #     mlog.debug('    - {}'.format(j))
454                break
455        libs = sorted(set(libs))
456
457        modules = ['boost_' + x for x in self.modules]
458        for inc in inc_dirs:
459            mlog.debug(f'  - found boost {inc.version} include dir: {inc.path}')
460            f_libs = self.filter_libraries(libs, inc.version_lib)
461
462            mlog.debug('  - filtered library list:')
463            for j in f_libs:
464                mlog.debug(f'    - {j}')
465
466            #   3. Select the libraries matching the requested modules
467            not_found = []  # type: T.List[str]
468            selected_modules = []  # type: T.List[BoostLibraryFile]
469            for mod in modules:
470                found = False
471                for l in f_libs:
472                    if l.mod_name_matches(mod):
473                        selected_modules += [l]
474                        found = True
475                        break
476                if not found:
477                    not_found += [mod]
478
479            # log the result
480            mlog.debug('  - found:')
481            comp_args = []  # type: T.List[str]
482            link_args = []  # type: T.List[str]
483            for j in selected_modules:
484                c_args = j.get_compiler_args()
485                l_args = j.get_link_args()
486                mlog.debug('    - {:<24} link={} comp={}'.format(j.mod_name, str(l_args), str(c_args)))
487                comp_args += c_args
488                link_args += l_args
489
490            comp_args = list(set(comp_args))
491            link_args = list(set(link_args))
492
493            self.modules_found = [x.mod_name for x in selected_modules]
494            self.modules_found = [x[6:] for x in self.modules_found]
495            self.modules_found = sorted(set(self.modules_found))
496            self.modules_missing = not_found
497            self.modules_missing = [x[6:] for x in self.modules_missing]
498            self.modules_missing = sorted(set(self.modules_missing))
499
500            # if we found all modules we are done
501            if not not_found:
502                self.version = inc.version
503                self.compile_args = ['-I' + inc.path.as_posix()]
504                self.compile_args += comp_args
505                self.compile_args += self._extra_compile_args()
506                self.compile_args = list(mesonlib.OrderedSet(self.compile_args))
507                self.link_args = link_args
508                mlog.debug(f'  - final compile args: {self.compile_args}')
509                mlog.debug(f'  - final link args:    {self.link_args}')
510                return True
511
512            # in case we missed something log it and try again
513            mlog.debug('  - NOT found:')
514            for mod in not_found:
515                mlog.debug(f'    - {mod}')
516
517        return False
518
519    def detect_inc_dirs(self, root: Path) -> T.List[BoostIncludeDir]:
520        candidates = []  # type: T.List[Path]
521        inc_root = root / 'include'
522
523        candidates += [root / 'boost']
524        candidates += [inc_root / 'boost']
525        if inc_root.is_dir():
526            for i in inc_root.iterdir():
527                if not i.is_dir() or not i.name.startswith('boost-'):
528                    continue
529                candidates += [i / 'boost']
530        candidates = [x for x in candidates if x.is_dir()]
531        candidates = [x / 'version.hpp' for x in candidates]
532        candidates = [x for x in candidates if x.exists()]
533        return [self._include_dir_from_version_header(x) for x in candidates]
534
535    def detect_lib_dirs(self, root: Path, use_system: bool) -> T.List[Path]:
536        # First check the system include paths. Only consider those within the
537        # given root path
538
539        if use_system:
540            system_dirs_t = self.clib_compiler.get_library_dirs(self.env)
541            system_dirs = [Path(x) for x in system_dirs_t]
542            system_dirs = [x.resolve() for x in system_dirs if x.exists()]
543            system_dirs = [x for x in system_dirs if mesonlib.path_is_in_root(x, root)]
544            system_dirs = list(mesonlib.OrderedSet(system_dirs))
545
546            if system_dirs:
547                return system_dirs
548
549        # No system include paths were found --> fall back to manually looking
550        # for library dirs in root
551        dirs = []     # type: T.List[Path]
552        subdirs = []  # type: T.List[Path]
553        for i in root.iterdir():
554            if i.is_dir() and i.name.startswith('lib'):
555                dirs += [i]
556
557        # Some distros put libraries not directly inside /usr/lib but in /usr/lib/x86_64-linux-gnu
558        for i in dirs:
559            for j in i.iterdir():
560                if j.is_dir() and j.name.endswith('-linux-gnu'):
561                    subdirs += [j]
562
563        # Filter out paths that don't match the target arch to avoid finding
564        # the wrong libraries. See https://github.com/mesonbuild/meson/issues/7110
565        if not self.arch:
566            return dirs + subdirs
567
568        arch_list_32 = ['32', 'i386']
569        arch_list_64 = ['64']
570
571        raw_list = dirs + subdirs
572        no_arch = [x for x in raw_list if not any([y in x.name for y in arch_list_32 + arch_list_64])]
573
574        matching_arch = []  # type: T.List[Path]
575        if '32' in self.arch:
576            matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_32])]
577        elif '64' in self.arch:
578            matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_64])]
579
580        return sorted(matching_arch) + sorted(no_arch)
581
582    def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.List[BoostLibraryFile]:
583        # MSVC is very picky with the library tags
584        vscrt = ''
585        try:
586            crt_val = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value
587            buildtype = self.env.coredata.options[mesonlib.OptionKey('buildtype')].value
588            vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0]
589        except (KeyError, IndexError, AttributeError):
590            pass
591
592        # mlog.debug('    - static: {}'.format(self.static))
593        # mlog.debug('    - not explicit static: {}'.format(not self.explicit_static))
594        # mlog.debug('    - mt: {}'.format(self.multithreading))
595        # mlog.debug('    - version: {}'.format(lib_vers))
596        # mlog.debug('    - arch: {}'.format(self.arch))
597        # mlog.debug('    - vscrt: {}'.format(vscrt))
598        libs = [x for x in libs if x.static == self.static or not self.explicit_static]
599        libs = [x for x in libs if x.mt == self.multithreading]
600        libs = [x for x in libs if x.version_matches(lib_vers)]
601        libs = [x for x in libs if x.arch_matches(self.arch)]
602        libs = [x for x in libs if x.vscrt_matches(vscrt)]
603        libs = [x for x in libs if x.nvsuffix != 'dll']  # Only link to import libraries
604
605        # Only filter by debug when we are building in release mode. Debug
606        # libraries are automatically preferred through sorting otherwise.
607        if not self.debug:
608            libs = [x for x in libs if not x.debug]
609
610        # Take the abitag from the first library and filter by it. This
611        # ensures that we have a set of libraries that are always compatible.
612        if not libs:
613            return []
614        abitag = libs[0].abitag
615        libs = [x for x in libs if x.abitag == abitag]
616
617        return libs
618
619    def detect_libraries(self, libdir: Path) -> T.List[BoostLibraryFile]:
620        libs = set()  # type: T.Set[BoostLibraryFile]
621        for i in libdir.iterdir():
622            if not i.is_file():
623                continue
624            if not any([i.name.startswith(x) for x in ['libboost_', 'boost_']]):
625                continue
626
627            libs.add(BoostLibraryFile(i.resolve()))
628
629        return [x for x in libs if x.is_boost()]  # Filter out no boost libraries
630
631    def detect_split_root(self, inc_dir: Path, lib_dir: Path) -> None:
632        boost_inc_dir = None
633        for j in [inc_dir / 'version.hpp', inc_dir / 'boost' / 'version.hpp']:
634            if j.is_file():
635                boost_inc_dir = self._include_dir_from_version_header(j)
636                break
637        if not boost_inc_dir:
638            self.is_found = False
639            return
640
641        self.is_found = self.run_check([boost_inc_dir], [lib_dir])
642
643    def detect_roots(self) -> None:
644        roots = []  # type: T.List[Path]
645
646        # Try getting the BOOST_ROOT from a boost.pc if it exists. This primarily
647        # allows BoostDependency to find boost from Conan. See #5438
648        try:
649            boost_pc = PkgConfigDependency('boost', self.env, {'required': False})
650            if boost_pc.found():
651                boost_root = boost_pc.get_pkgconfig_variable('prefix', {'default': None})
652                if boost_root:
653                    roots += [Path(boost_root)]
654        except DependencyException:
655            pass
656
657        # Add roots from system paths
658        inc_paths = [Path(x) for x in self.clib_compiler.get_default_include_dirs()]
659        inc_paths = [x.parent for x in inc_paths if x.exists()]
660        inc_paths = [x.resolve() for x in inc_paths]
661        roots += inc_paths
662
663        # Add system paths
664        if self.env.machines[self.for_machine].is_windows():
665            # Where boost built from source actually installs it
666            c_root = Path('C:/Boost')
667            if c_root.is_dir():
668                roots += [c_root]
669
670            # Where boost documentation says it should be
671            prog_files = Path('C:/Program Files/boost')
672            # Where boost prebuilt binaries are
673            local_boost = Path('C:/local')
674
675            candidates = []  # type: T.List[Path]
676            if prog_files.is_dir():
677                candidates += [*prog_files.iterdir()]
678            if local_boost.is_dir():
679                candidates += [*local_boost.iterdir()]
680
681            roots += [x for x in candidates if x.name.lower().startswith('boost') and x.is_dir()]
682        else:
683            tmp = []  # type: T.List[Path]
684
685            # Add some default system paths
686            tmp += [Path('/opt/local')]
687            tmp += [Path('/usr/local/opt/boost')]
688            tmp += [Path('/usr/local')]
689            tmp += [Path('/usr')]
690
691            # Cleanup paths
692            tmp = [x for x in tmp if x.is_dir()]
693            tmp = [x.resolve() for x in tmp]
694            roots += tmp
695
696        self.check_and_set_roots(roots, use_system=True)
697
698    def log_details(self) -> str:
699        res = ''
700        if self.modules_found:
701            res += 'found: ' + ', '.join(self.modules_found)
702        if self.modules_missing:
703            if res:
704                res += ' | '
705            res += 'missing: ' + ', '.join(self.modules_missing)
706        return res
707
708    def log_info(self) -> str:
709        if self.boost_root:
710            return self.boost_root.as_posix()
711        return ''
712
713    def _include_dir_from_version_header(self, hfile: Path) -> BoostIncludeDir:
714        # Extract the version with a regex. Using clib_compiler.get_define would
715        # also work, however, this is slower (since it the compiler has to be
716        # invoked) and overkill since the layout of the header is always the same.
717        assert hfile.exists()
718        raw = hfile.read_text(encoding='utf-8')
719        m = re.search(r'#define\s+BOOST_VERSION\s+([0-9]+)', raw)
720        if not m:
721            mlog.debug(f'Failed to extract version information from {hfile}')
722            return BoostIncludeDir(hfile.parents[1], 0)
723        return BoostIncludeDir(hfile.parents[1], int(m.group(1)))
724
725    def _extra_compile_args(self) -> T.List[str]:
726        # BOOST_ALL_DYN_LINK should not be required with the known defines below
727        return ['-DBOOST_ALL_NO_LIB']  # Disable automatic linking
728
729
730# See https://www.boost.org/doc/libs/1_72_0/more/getting_started/unix-variants.html#library-naming
731# See https://mesonbuild.com/Reference-tables.html#cpu-families
732boost_arch_map = {
733    'aarch64': 'a64',
734    'arc': 'a32',
735    'arm': 'a32',
736    'ia64': 'i64',
737    'mips': 'm32',
738    'mips64': 'm64',
739    'ppc': 'p32',
740    'ppc64': 'p64',
741    'sparc': 's32',
742    'sparc64': 's64',
743    'x86': 'x32',
744    'x86_64': 'x64',
745}
746
747
748####      ---- BEGIN GENERATED ----      ####
749#                                           #
750# Generated with tools/boost_names.py:
751#  - boost version:   1.73.0
752#  - modules found:   159
753#  - libraries found: 43
754#
755
756class BoostLibrary():
757    def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]):
758        self.name = name
759        self.shared = shared
760        self.static = static
761        self.single = single
762        self.multi = multi
763
764class BoostModule():
765    def __init__(self, name: str, key: str, desc: str, libs: T.List[str]):
766        self.name = name
767        self.key = key
768        self.desc = desc
769        self.libs = libs
770
771
772# dict of all know libraries with additional compile options
773boost_libraries = {
774    'boost_atomic': BoostLibrary(
775        name='boost_atomic',
776        shared=['-DBOOST_ATOMIC_DYN_LINK=1'],
777        static=['-DBOOST_ATOMIC_STATIC_LINK=1'],
778        single=[],
779        multi=[],
780    ),
781    'boost_chrono': BoostLibrary(
782        name='boost_chrono',
783        shared=['-DBOOST_CHRONO_DYN_LINK=1'],
784        static=['-DBOOST_CHRONO_STATIC_LINK=1'],
785        single=['-DBOOST_CHRONO_THREAD_DISABLED'],
786        multi=[],
787    ),
788    'boost_container': BoostLibrary(
789        name='boost_container',
790        shared=['-DBOOST_CONTAINER_DYN_LINK=1'],
791        static=['-DBOOST_CONTAINER_STATIC_LINK=1'],
792        single=[],
793        multi=[],
794    ),
795    'boost_context': BoostLibrary(
796        name='boost_context',
797        shared=['-DBOOST_CONTEXT_DYN_LINK=1'],
798        static=[],
799        single=[],
800        multi=[],
801    ),
802    'boost_contract': BoostLibrary(
803        name='boost_contract',
804        shared=['-DBOOST_CONTRACT_DYN_LINK'],
805        static=['-DBOOST_CONTRACT_STATIC_LINK'],
806        single=['-DBOOST_CONTRACT_DISABLE_THREADS'],
807        multi=[],
808    ),
809    'boost_coroutine': BoostLibrary(
810        name='boost_coroutine',
811        shared=['-DBOOST_COROUTINES_DYN_LINK=1'],
812        static=[],
813        single=[],
814        multi=[],
815    ),
816    'boost_date_time': BoostLibrary(
817        name='boost_date_time',
818        shared=['-DBOOST_DATE_TIME_DYN_LINK=1'],
819        static=[],
820        single=[],
821        multi=[],
822    ),
823    'boost_exception': BoostLibrary(
824        name='boost_exception',
825        shared=[],
826        static=[],
827        single=[],
828        multi=[],
829    ),
830    'boost_fiber': BoostLibrary(
831        name='boost_fiber',
832        shared=['-DBOOST_FIBERS_DYN_LINK=1'],
833        static=[],
834        single=[],
835        multi=[],
836    ),
837    'boost_fiber_numa': BoostLibrary(
838        name='boost_fiber_numa',
839        shared=['-DBOOST_FIBERS_DYN_LINK=1'],
840        static=[],
841        single=[],
842        multi=[],
843    ),
844    'boost_filesystem': BoostLibrary(
845        name='boost_filesystem',
846        shared=['-DBOOST_FILESYSTEM_DYN_LINK=1'],
847        static=['-DBOOST_FILESYSTEM_STATIC_LINK=1'],
848        single=[],
849        multi=[],
850    ),
851    'boost_graph': BoostLibrary(
852        name='boost_graph',
853        shared=[],
854        static=[],
855        single=[],
856        multi=[],
857    ),
858    'boost_iostreams': BoostLibrary(
859        name='boost_iostreams',
860        shared=['-DBOOST_IOSTREAMS_DYN_LINK=1'],
861        static=[],
862        single=[],
863        multi=[],
864    ),
865    'boost_locale': BoostLibrary(
866        name='boost_locale',
867        shared=[],
868        static=[],
869        single=[],
870        multi=[],
871    ),
872    'boost_log': BoostLibrary(
873        name='boost_log',
874        shared=['-DBOOST_LOG_DYN_LINK=1'],
875        static=[],
876        single=['-DBOOST_LOG_NO_THREADS'],
877        multi=[],
878    ),
879    'boost_log_setup': BoostLibrary(
880        name='boost_log_setup',
881        shared=['-DBOOST_LOG_SETUP_DYN_LINK=1'],
882        static=[],
883        single=['-DBOOST_LOG_NO_THREADS'],
884        multi=[],
885    ),
886    'boost_math_c99': BoostLibrary(
887        name='boost_math_c99',
888        shared=[],
889        static=[],
890        single=[],
891        multi=[],
892    ),
893    'boost_math_c99f': BoostLibrary(
894        name='boost_math_c99f',
895        shared=[],
896        static=[],
897        single=[],
898        multi=[],
899    ),
900    'boost_math_c99l': BoostLibrary(
901        name='boost_math_c99l',
902        shared=[],
903        static=[],
904        single=[],
905        multi=[],
906    ),
907    'boost_math_tr1': BoostLibrary(
908        name='boost_math_tr1',
909        shared=[],
910        static=[],
911        single=[],
912        multi=[],
913    ),
914    'boost_math_tr1f': BoostLibrary(
915        name='boost_math_tr1f',
916        shared=[],
917        static=[],
918        single=[],
919        multi=[],
920    ),
921    'boost_math_tr1l': BoostLibrary(
922        name='boost_math_tr1l',
923        shared=[],
924        static=[],
925        single=[],
926        multi=[],
927    ),
928    'boost_mpi': BoostLibrary(
929        name='boost_mpi',
930        shared=[],
931        static=[],
932        single=[],
933        multi=[],
934    ),
935    'boost_nowide': BoostLibrary(
936        name='boost_nowide',
937        shared=['-DBOOST_NOWIDE_DYN_LINK=1'],
938        static=[],
939        single=[],
940        multi=[],
941    ),
942    'boost_prg_exec_monitor': BoostLibrary(
943        name='boost_prg_exec_monitor',
944        shared=['-DBOOST_TEST_DYN_LINK=1'],
945        static=[],
946        single=[],
947        multi=[],
948    ),
949    'boost_program_options': BoostLibrary(
950        name='boost_program_options',
951        shared=[],
952        static=[],
953        single=[],
954        multi=[],
955    ),
956    'boost_random': BoostLibrary(
957        name='boost_random',
958        shared=['-DBOOST_RANDOM_DYN_LINK'],
959        static=[],
960        single=[],
961        multi=[],
962    ),
963    'boost_regex': BoostLibrary(
964        name='boost_regex',
965        shared=[],
966        static=[],
967        single=[],
968        multi=[],
969    ),
970    'boost_serialization': BoostLibrary(
971        name='boost_serialization',
972        shared=[],
973        static=[],
974        single=[],
975        multi=[],
976    ),
977    'boost_stacktrace_addr2line': BoostLibrary(
978        name='boost_stacktrace_addr2line',
979        shared=[],
980        static=[],
981        single=[],
982        multi=[],
983    ),
984    'boost_stacktrace_backtrace': BoostLibrary(
985        name='boost_stacktrace_backtrace',
986        shared=[],
987        static=[],
988        single=[],
989        multi=[],
990    ),
991    'boost_stacktrace_basic': BoostLibrary(
992        name='boost_stacktrace_basic',
993        shared=[],
994        static=[],
995        single=[],
996        multi=[],
997    ),
998    'boost_stacktrace_noop': BoostLibrary(
999        name='boost_stacktrace_noop',
1000        shared=[],
1001        static=[],
1002        single=[],
1003        multi=[],
1004    ),
1005    'boost_stacktrace_windbg': BoostLibrary(
1006        name='boost_stacktrace_windbg',
1007        shared=[],
1008        static=[],
1009        single=[],
1010        multi=[],
1011    ),
1012    'boost_stacktrace_windbg_cached': BoostLibrary(
1013        name='boost_stacktrace_windbg_cached',
1014        shared=[],
1015        static=[],
1016        single=[],
1017        multi=[],
1018    ),
1019    'boost_system': BoostLibrary(
1020        name='boost_system',
1021        shared=['-DBOOST_SYSTEM_DYN_LINK=1'],
1022        static=['-DBOOST_SYSTEM_STATIC_LINK=1'],
1023        single=[],
1024        multi=[],
1025    ),
1026    'boost_test_exec_monitor': BoostLibrary(
1027        name='boost_test_exec_monitor',
1028        shared=['-DBOOST_TEST_DYN_LINK=1'],
1029        static=[],
1030        single=[],
1031        multi=[],
1032    ),
1033    'boost_thread': BoostLibrary(
1034        name='boost_thread',
1035        shared=['-DBOOST_THREAD_BUILD_DLL=1', '-DBOOST_THREAD_USE_DLL=1'],
1036        static=['-DBOOST_THREAD_BUILD_LIB=1', '-DBOOST_THREAD_USE_LIB=1'],
1037        single=[],
1038        multi=[],
1039    ),
1040    'boost_timer': BoostLibrary(
1041        name='boost_timer',
1042        shared=['-DBOOST_TIMER_DYN_LINK=1'],
1043        static=['-DBOOST_TIMER_STATIC_LINK=1'],
1044        single=[],
1045        multi=[],
1046    ),
1047    'boost_type_erasure': BoostLibrary(
1048        name='boost_type_erasure',
1049        shared=['-DBOOST_TYPE_ERASURE_DYN_LINK'],
1050        static=[],
1051        single=[],
1052        multi=[],
1053    ),
1054    'boost_unit_test_framework': BoostLibrary(
1055        name='boost_unit_test_framework',
1056        shared=['-DBOOST_TEST_DYN_LINK=1'],
1057        static=[],
1058        single=[],
1059        multi=[],
1060    ),
1061    'boost_wave': BoostLibrary(
1062        name='boost_wave',
1063        shared=[],
1064        static=[],
1065        single=[],
1066        multi=[],
1067    ),
1068    'boost_wserialization': BoostLibrary(
1069        name='boost_wserialization',
1070        shared=[],
1071        static=[],
1072        single=[],
1073        multi=[],
1074    ),
1075}
1076
1077#                                           #
1078####       ---- END GENERATED ----       ####
1079