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