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]) -> 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)
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)
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) -> T.List[Path]:
536        # First check the system include paths. Only consider those within the
537        # given root path
538        system_dirs_t = self.clib_compiler.get_library_dirs(self.env)
539        system_dirs = [Path(x) for x in system_dirs_t]
540        system_dirs = [x.resolve() for x in system_dirs if x.exists()]
541        system_dirs = [x for x in system_dirs if mesonlib.path_is_in_root(x, root)]
542        system_dirs = list(mesonlib.OrderedSet(system_dirs))
543
544        if system_dirs:
545            return system_dirs
546
547        # No system include paths were found --> fall back to manually looking
548        # for library dirs in root
549        dirs = []     # type: T.List[Path]
550        subdirs = []  # type: T.List[Path]
551        for i in root.iterdir():
552            if i.is_dir() and i.name.startswith('lib'):
553                dirs += [i]
554
555        # Some distros put libraries not directly inside /usr/lib but in /usr/lib/x86_64-linux-gnu
556        for i in dirs:
557            for j in i.iterdir():
558                if j.is_dir() and j.name.endswith('-linux-gnu'):
559                    subdirs += [j]
560
561        # Filter out paths that don't match the target arch to avoid finding
562        # the wrong libraries. See https://github.com/mesonbuild/meson/issues/7110
563        if not self.arch:
564            return dirs + subdirs
565
566        arch_list_32 = ['32', 'i386']
567        arch_list_64 = ['64']
568
569        raw_list = dirs + subdirs
570        no_arch = [x for x in raw_list if not any([y in x.name for y in arch_list_32 + arch_list_64])]
571
572        matching_arch = []  # type: T.List[Path]
573        if '32' in self.arch:
574            matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_32])]
575        elif '64' in self.arch:
576            matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_64])]
577
578        return sorted(matching_arch) + sorted(no_arch)
579
580    def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.List[BoostLibraryFile]:
581        # MSVC is very picky with the library tags
582        vscrt = ''
583        try:
584            crt_val = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value
585            buildtype = self.env.coredata.options[mesonlib.OptionKey('buildtype')].value
586            vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0]
587        except (KeyError, IndexError, AttributeError):
588            pass
589
590        # mlog.debug('    - static: {}'.format(self.static))
591        # mlog.debug('    - not explicit static: {}'.format(not self.explicit_static))
592        # mlog.debug('    - mt: {}'.format(self.multithreading))
593        # mlog.debug('    - version: {}'.format(lib_vers))
594        # mlog.debug('    - arch: {}'.format(self.arch))
595        # mlog.debug('    - vscrt: {}'.format(vscrt))
596        libs = [x for x in libs if x.static == self.static or not self.explicit_static]
597        libs = [x for x in libs if x.mt == self.multithreading]
598        libs = [x for x in libs if x.version_matches(lib_vers)]
599        libs = [x for x in libs if x.arch_matches(self.arch)]
600        libs = [x for x in libs if x.vscrt_matches(vscrt)]
601        libs = [x for x in libs if x.nvsuffix != 'dll']  # Only link to import libraries
602
603        # Only filter by debug when we are building in release mode. Debug
604        # libraries are automatically preferred through sorting otherwise.
605        if not self.debug:
606            libs = [x for x in libs if not x.debug]
607
608        # Take the abitag from the first library and filter by it. This
609        # ensures that we have a set of libraries that are always compatible.
610        if not libs:
611            return []
612        abitag = libs[0].abitag
613        libs = [x for x in libs if x.abitag == abitag]
614
615        return libs
616
617    def detect_libraries(self, libdir: Path) -> T.List[BoostLibraryFile]:
618        libs = []  # type: T.List[BoostLibraryFile]
619        for i in libdir.iterdir():
620            if not i.is_file() or i.is_symlink():
621                continue
622            if not any([i.name.startswith(x) for x in ['libboost_', 'boost_']]):
623                continue
624
625            libs += [BoostLibraryFile(i)]
626        return [x for x in libs if x.is_boost()]  # Filter out no boost libraries
627
628    def detect_split_root(self, inc_dir: Path, lib_dir: Path) -> None:
629        boost_inc_dir = None
630        for j in [inc_dir / 'version.hpp', inc_dir / 'boost' / 'version.hpp']:
631            if j.is_file():
632                boost_inc_dir = self._include_dir_from_version_header(j)
633                break
634        if not boost_inc_dir:
635            self.is_found = False
636            return
637
638        self.is_found = self.run_check([boost_inc_dir], [lib_dir])
639
640    def detect_roots(self) -> None:
641        roots = []  # type: T.List[Path]
642
643        # Try getting the BOOST_ROOT from a boost.pc if it exists. This primarily
644        # allows BoostDependency to find boost from Conan. See #5438
645        try:
646            boost_pc = PkgConfigDependency('boost', self.env, {'required': False})
647            if boost_pc.found():
648                boost_root = boost_pc.get_pkgconfig_variable('prefix', {'default': None})
649                if boost_root:
650                    roots += [Path(boost_root)]
651        except DependencyException:
652            pass
653
654        # Add roots from system paths
655        inc_paths = [Path(x) for x in self.clib_compiler.get_default_include_dirs()]
656        inc_paths = [x.parent for x in inc_paths if x.exists()]
657        inc_paths = [x.resolve() for x in inc_paths]
658        roots += inc_paths
659
660        # Add system paths
661        if self.env.machines[self.for_machine].is_windows():
662            # Where boost built from source actually installs it
663            c_root = Path('C:/Boost')
664            if c_root.is_dir():
665                roots += [c_root]
666
667            # Where boost documentation says it should be
668            prog_files = Path('C:/Program Files/boost')
669            # Where boost prebuilt binaries are
670            local_boost = Path('C:/local')
671
672            candidates = []  # type: T.List[Path]
673            if prog_files.is_dir():
674                candidates += [*prog_files.iterdir()]
675            if local_boost.is_dir():
676                candidates += [*local_boost.iterdir()]
677
678            roots += [x for x in candidates if x.name.lower().startswith('boost') and x.is_dir()]
679        else:
680            tmp = []  # type: T.List[Path]
681
682            # Homebrew
683            brew_boost = Path('/usr/local/Cellar/boost')
684            if brew_boost.is_dir():
685                tmp += [x for x in brew_boost.iterdir()]
686
687            # Add some default system paths
688            tmp += [Path('/opt/local')]
689            tmp += [Path('/usr/local/opt/boost')]
690            tmp += [Path('/usr/local')]
691            tmp += [Path('/usr')]
692
693            # Cleanup paths
694            tmp = [x for x in tmp if x.is_dir()]
695            tmp = [x.resolve() for x in tmp]
696            roots += tmp
697
698        self.check_and_set_roots(roots)
699
700    def log_details(self) -> str:
701        res = ''
702        if self.modules_found:
703            res += 'found: ' + ', '.join(self.modules_found)
704        if self.modules_missing:
705            if res:
706                res += ' | '
707            res += 'missing: ' + ', '.join(self.modules_missing)
708        return res
709
710    def log_info(self) -> str:
711        if self.boost_root:
712            return self.boost_root.as_posix()
713        return ''
714
715    def _include_dir_from_version_header(self, hfile: Path) -> BoostIncludeDir:
716        # Extract the version with a regex. Using clib_compiler.get_define would
717        # also work, however, this is slower (since it the compiler has to be
718        # invoked) and overkill since the layout of the header is always the same.
719        assert hfile.exists()
720        raw = hfile.read_text(encoding='utf-8')
721        m = re.search(r'#define\s+BOOST_VERSION\s+([0-9]+)', raw)
722        if not m:
723            mlog.debug(f'Failed to extract version information from {hfile}')
724            return BoostIncludeDir(hfile.parents[1], 0)
725        return BoostIncludeDir(hfile.parents[1], int(m.group(1)))
726
727    def _extra_compile_args(self) -> T.List[str]:
728        # BOOST_ALL_DYN_LINK should not be required with the known defines below
729        return ['-DBOOST_ALL_NO_LIB']  # Disable automatic linking
730
731
732# See https://www.boost.org/doc/libs/1_72_0/more/getting_started/unix-variants.html#library-naming
733# See https://mesonbuild.com/Reference-tables.html#cpu-families
734boost_arch_map = {
735    'aarch64': 'a64',
736    'arc': 'a32',
737    'arm': 'a32',
738    'ia64': 'i64',
739    'mips': 'm32',
740    'mips64': 'm64',
741    'ppc': 'p32',
742    'ppc64': 'p64',
743    'sparc': 's32',
744    'sparc64': 's64',
745    'x86': 'x32',
746    'x86_64': 'x64',
747}
748
749
750####      ---- BEGIN GENERATED ----      ####
751#                                           #
752# Generated with tools/boost_names.py:
753#  - boost version:   1.73.0
754#  - modules found:   159
755#  - libraries found: 43
756#
757
758class BoostLibrary():
759    def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]):
760        self.name = name
761        self.shared = shared
762        self.static = static
763        self.single = single
764        self.multi = multi
765
766class BoostModule():
767    def __init__(self, name: str, key: str, desc: str, libs: T.List[str]):
768        self.name = name
769        self.key = key
770        self.desc = desc
771        self.libs = libs
772
773
774# dict of all know libraries with additional compile options
775boost_libraries = {
776    'boost_atomic': BoostLibrary(
777        name='boost_atomic',
778        shared=['-DBOOST_ATOMIC_DYN_LINK=1'],
779        static=['-DBOOST_ATOMIC_STATIC_LINK=1'],
780        single=[],
781        multi=[],
782    ),
783    'boost_chrono': BoostLibrary(
784        name='boost_chrono',
785        shared=['-DBOOST_CHRONO_DYN_LINK=1'],
786        static=['-DBOOST_CHRONO_STATIC_LINK=1'],
787        single=['-DBOOST_CHRONO_THREAD_DISABLED'],
788        multi=[],
789    ),
790    'boost_container': BoostLibrary(
791        name='boost_container',
792        shared=['-DBOOST_CONTAINER_DYN_LINK=1'],
793        static=['-DBOOST_CONTAINER_STATIC_LINK=1'],
794        single=[],
795        multi=[],
796    ),
797    'boost_context': BoostLibrary(
798        name='boost_context',
799        shared=['-DBOOST_CONTEXT_DYN_LINK=1'],
800        static=[],
801        single=[],
802        multi=[],
803    ),
804    'boost_contract': BoostLibrary(
805        name='boost_contract',
806        shared=['-DBOOST_CONTRACT_DYN_LINK'],
807        static=['-DBOOST_CONTRACT_STATIC_LINK'],
808        single=['-DBOOST_CONTRACT_DISABLE_THREADS'],
809        multi=[],
810    ),
811    'boost_coroutine': BoostLibrary(
812        name='boost_coroutine',
813        shared=['-DBOOST_COROUTINES_DYN_LINK=1'],
814        static=[],
815        single=[],
816        multi=[],
817    ),
818    'boost_date_time': BoostLibrary(
819        name='boost_date_time',
820        shared=['-DBOOST_DATE_TIME_DYN_LINK=1'],
821        static=[],
822        single=[],
823        multi=[],
824    ),
825    'boost_exception': BoostLibrary(
826        name='boost_exception',
827        shared=[],
828        static=[],
829        single=[],
830        multi=[],
831    ),
832    'boost_fiber': BoostLibrary(
833        name='boost_fiber',
834        shared=['-DBOOST_FIBERS_DYN_LINK=1'],
835        static=[],
836        single=[],
837        multi=[],
838    ),
839    'boost_fiber_numa': BoostLibrary(
840        name='boost_fiber_numa',
841        shared=['-DBOOST_FIBERS_DYN_LINK=1'],
842        static=[],
843        single=[],
844        multi=[],
845    ),
846    'boost_filesystem': BoostLibrary(
847        name='boost_filesystem',
848        shared=['-DBOOST_FILESYSTEM_DYN_LINK=1'],
849        static=['-DBOOST_FILESYSTEM_STATIC_LINK=1'],
850        single=[],
851        multi=[],
852    ),
853    'boost_graph': BoostLibrary(
854        name='boost_graph',
855        shared=[],
856        static=[],
857        single=[],
858        multi=[],
859    ),
860    'boost_iostreams': BoostLibrary(
861        name='boost_iostreams',
862        shared=['-DBOOST_IOSTREAMS_DYN_LINK=1'],
863        static=[],
864        single=[],
865        multi=[],
866    ),
867    'boost_locale': BoostLibrary(
868        name='boost_locale',
869        shared=[],
870        static=[],
871        single=[],
872        multi=[],
873    ),
874    'boost_log': BoostLibrary(
875        name='boost_log',
876        shared=['-DBOOST_LOG_DYN_LINK=1'],
877        static=[],
878        single=['-DBOOST_LOG_NO_THREADS'],
879        multi=[],
880    ),
881    'boost_log_setup': BoostLibrary(
882        name='boost_log_setup',
883        shared=['-DBOOST_LOG_SETUP_DYN_LINK=1'],
884        static=[],
885        single=['-DBOOST_LOG_NO_THREADS'],
886        multi=[],
887    ),
888    'boost_math_c99': BoostLibrary(
889        name='boost_math_c99',
890        shared=[],
891        static=[],
892        single=[],
893        multi=[],
894    ),
895    'boost_math_c99f': BoostLibrary(
896        name='boost_math_c99f',
897        shared=[],
898        static=[],
899        single=[],
900        multi=[],
901    ),
902    'boost_math_c99l': BoostLibrary(
903        name='boost_math_c99l',
904        shared=[],
905        static=[],
906        single=[],
907        multi=[],
908    ),
909    'boost_math_tr1': BoostLibrary(
910        name='boost_math_tr1',
911        shared=[],
912        static=[],
913        single=[],
914        multi=[],
915    ),
916    'boost_math_tr1f': BoostLibrary(
917        name='boost_math_tr1f',
918        shared=[],
919        static=[],
920        single=[],
921        multi=[],
922    ),
923    'boost_math_tr1l': BoostLibrary(
924        name='boost_math_tr1l',
925        shared=[],
926        static=[],
927        single=[],
928        multi=[],
929    ),
930    'boost_mpi': BoostLibrary(
931        name='boost_mpi',
932        shared=[],
933        static=[],
934        single=[],
935        multi=[],
936    ),
937    'boost_nowide': BoostLibrary(
938        name='boost_nowide',
939        shared=['-DBOOST_NOWIDE_DYN_LINK=1'],
940        static=[],
941        single=[],
942        multi=[],
943    ),
944    'boost_prg_exec_monitor': BoostLibrary(
945        name='boost_prg_exec_monitor',
946        shared=['-DBOOST_TEST_DYN_LINK=1'],
947        static=[],
948        single=[],
949        multi=[],
950    ),
951    'boost_program_options': BoostLibrary(
952        name='boost_program_options',
953        shared=[],
954        static=[],
955        single=[],
956        multi=[],
957    ),
958    'boost_random': BoostLibrary(
959        name='boost_random',
960        shared=['-DBOOST_RANDOM_DYN_LINK'],
961        static=[],
962        single=[],
963        multi=[],
964    ),
965    'boost_regex': BoostLibrary(
966        name='boost_regex',
967        shared=[],
968        static=[],
969        single=[],
970        multi=[],
971    ),
972    'boost_serialization': BoostLibrary(
973        name='boost_serialization',
974        shared=[],
975        static=[],
976        single=[],
977        multi=[],
978    ),
979    'boost_stacktrace_addr2line': BoostLibrary(
980        name='boost_stacktrace_addr2line',
981        shared=[],
982        static=[],
983        single=[],
984        multi=[],
985    ),
986    'boost_stacktrace_backtrace': BoostLibrary(
987        name='boost_stacktrace_backtrace',
988        shared=[],
989        static=[],
990        single=[],
991        multi=[],
992    ),
993    'boost_stacktrace_basic': BoostLibrary(
994        name='boost_stacktrace_basic',
995        shared=[],
996        static=[],
997        single=[],
998        multi=[],
999    ),
1000    'boost_stacktrace_noop': BoostLibrary(
1001        name='boost_stacktrace_noop',
1002        shared=[],
1003        static=[],
1004        single=[],
1005        multi=[],
1006    ),
1007    'boost_stacktrace_windbg': BoostLibrary(
1008        name='boost_stacktrace_windbg',
1009        shared=[],
1010        static=[],
1011        single=[],
1012        multi=[],
1013    ),
1014    'boost_stacktrace_windbg_cached': BoostLibrary(
1015        name='boost_stacktrace_windbg_cached',
1016        shared=[],
1017        static=[],
1018        single=[],
1019        multi=[],
1020    ),
1021    'boost_system': BoostLibrary(
1022        name='boost_system',
1023        shared=['-DBOOST_SYSTEM_DYN_LINK=1'],
1024        static=['-DBOOST_SYSTEM_STATIC_LINK=1'],
1025        single=[],
1026        multi=[],
1027    ),
1028    'boost_test_exec_monitor': BoostLibrary(
1029        name='boost_test_exec_monitor',
1030        shared=['-DBOOST_TEST_DYN_LINK=1'],
1031        static=[],
1032        single=[],
1033        multi=[],
1034    ),
1035    'boost_thread': BoostLibrary(
1036        name='boost_thread',
1037        shared=['-DBOOST_THREAD_BUILD_DLL=1', '-DBOOST_THREAD_USE_DLL=1'],
1038        static=['-DBOOST_THREAD_BUILD_LIB=1', '-DBOOST_THREAD_USE_LIB=1'],
1039        single=[],
1040        multi=[],
1041    ),
1042    'boost_timer': BoostLibrary(
1043        name='boost_timer',
1044        shared=['-DBOOST_TIMER_DYN_LINK=1'],
1045        static=['-DBOOST_TIMER_STATIC_LINK=1'],
1046        single=[],
1047        multi=[],
1048    ),
1049    'boost_type_erasure': BoostLibrary(
1050        name='boost_type_erasure',
1051        shared=['-DBOOST_TYPE_ERASURE_DYN_LINK'],
1052        static=[],
1053        single=[],
1054        multi=[],
1055    ),
1056    'boost_unit_test_framework': BoostLibrary(
1057        name='boost_unit_test_framework',
1058        shared=['-DBOOST_TEST_DYN_LINK=1'],
1059        static=[],
1060        single=[],
1061        multi=[],
1062    ),
1063    'boost_wave': BoostLibrary(
1064        name='boost_wave',
1065        shared=[],
1066        static=[],
1067        single=[],
1068        multi=[],
1069    ),
1070    'boost_wserialization': BoostLibrary(
1071        name='boost_wserialization',
1072        shared=[],
1073        static=[],
1074        single=[],
1075        multi=[],
1076    ),
1077}
1078
1079#                                           #
1080####       ---- END GENERATED ----       ####
1081