# Copyright 2013-2017 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This file contains the detection logic for miscellaneous external dependencies. import glob import os from .. import mlog from .. import mesonlib from ..environment import detect_cpu_family from .base import (DependencyException, ExternalDependency) from .misc import ThreadDependency # On windows 3 directory layouts are supported: # * The default layout (versioned) installed: # - $BOOST_ROOT/include/boost-x_x/boost/*.hpp # - $BOOST_ROOT/lib/*.lib # * The non-default layout (system) installed: # - $BOOST_ROOT/include/boost/*.hpp # - $BOOST_ROOT/lib/*.lib # * The pre-built binaries from sf.net: # - $BOOST_ROOT/boost/*.hpp # - $BOOST_ROOT/lib-/*.lib where arch=32/64 and compiler=msvc-14.1 # # Note that we should also try to support: # mingw-w64 / Windows : libboost_-mt.a (location = /mingw64/lib/) # libboost_-mt.dll.a # # Library names supported: # - libboost_--mt-gd-x_x.lib (static) # - boost_--mt-gd-x_x.lib|.dll (shared) # - libboost_.lib (static) # - boost_.lib|.dll (shared) # where compiler is vc141 for example. # # NOTE: -gd means runtime and build time debugging is on # -mt means threading=multi # # The `modules` argument accept library names. This is because every module that # has libraries to link against also has multiple options regarding how to # link. See for example: # * http://www.boost.org/doc/libs/1_65_1/libs/test/doc/html/boost_test/usage_variants.html # * http://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace/configuration_and_build.html # * http://www.boost.org/doc/libs/1_65_1/libs/math/doc/html/math_toolkit/main_tr1.html # **On Unix**, official packaged versions of boost libraries follow the following schemes: # # Linux / Debian: libboost_.so -> libboost_.so.1.66.0 # Linux / Red Hat: libboost_.so -> libboost_.so.1.66.0 # Linux / OpenSuse: libboost_.so -> libboost_.so.1.66.0 # Win / Cygwin: libboost_.dll.a (location = /usr/lib) # libboost_.a # cygboost__1_64.dll (location = /usr/bin) # Win / VS: boost_-vc-mt[-gd]--1_67.dll (location = C:/local/boost_1_67_0) # Mac / homebrew: libboost_.dylib + libboost_-mt.dylib (location = /usr/local/lib) # Mac / macports: libboost_.dylib + libboost_-mt.dylib (location = /opt/local/lib) # # Its not clear that any other abi tags (e.g. -gd) are used in official packages. # # On Linux systems, boost libs have multithreading support enabled, but without the -mt tag. # # Boost documentation recommends using complex abi tags like "-lboost_regex-gcc34-mt-d-1_36". # (See http://www.boost.org/doc/libs/1_66_0/more/getting_started/unix-variants.html#library-naming) # However, its not clear that any Unix distribution follows this scheme. # Furthermore, the boost documentation for unix above uses examples from windows like # "libboost_regex-vc71-mt-d-x86-1_34.lib", so apparently the abi tags may be more aimed at windows. # # Probably we should use the linker search path to decide which libraries to use. This will # make it possible to find the macports boost libraries without setting BOOST_ROOT, and will # also mean that it would be possible to use user-installed boost libraries when official # packages are installed. # # We thus follow the following strategy: # 1. Look for libraries using compiler.find_library( ) # 1.1 On Linux, just look for boost_ # 1.2 On other systems (e.g. Mac) look for boost_-mt if multithreading. # 1.3 Otherwise look for boost_ # 2. Fall back to previous approach # 2.1. Search particular directories. # 2.2. Find boost libraries with unknown suffixes using file-name globbing. # TODO: Unix: Don't assume we know where the boost dir is, rely on -Idir and -Ldir being set. # TODO: Allow user to specify suffix in BOOST_SUFFIX, or add specific options like BOOST_DEBUG for 'd' for debug. class BoostDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('boost', environment, kwargs, language='cpp') self.need_static_link = ['boost_exception', 'boost_test_exec_monitor'] self.is_debug = environment.coredata.get_builtin_option('buildtype').startswith('debug') threading = kwargs.get("threading", "multi") self.is_multithreading = threading == "multi" self.requested_modules = self.get_requested(kwargs) if 'thread' in self.requested_modules: self._add_sub_dependency(ThreadDependency, environment, kwargs) self.boost_root = None self.boost_roots = [] self.incdir = None self.libdir = None if 'BOOST_ROOT' in os.environ: self.boost_root = os.environ['BOOST_ROOT'] self.boost_roots = [self.boost_root] if not os.path.isabs(self.boost_root): raise DependencyException('BOOST_ROOT must be an absolute path.') if 'BOOST_INCLUDEDIR' in os.environ: self.incdir = os.environ['BOOST_INCLUDEDIR'] if 'BOOST_LIBRARYDIR' in os.environ: self.libdir = os.environ['BOOST_LIBRARYDIR'] if self.boost_root is None: if self.env.machines[self.for_machine].is_windows(): self.boost_roots = self.detect_win_roots() else: self.boost_roots = self.detect_nix_roots() if self.incdir is None: if self.env.machines[self.for_machine].is_windows(): self.incdir = self.detect_win_incdir() else: self.incdir = self.detect_nix_incdir() mlog.debug('Boost library root dir is', mlog.bold(self.boost_root)) mlog.debug('Boost include directory is', mlog.bold(self.incdir)) # 1. check if we can find BOOST headers. self.detect_headers_and_version() if not self.is_found: return # if we can not find 'boost/version.hpp' # 2. check if we can find BOOST libraries. self.detect_lib_modules() mlog.debug('Boost library directory is', mlog.bold(self.libdir)) mlog.debug('Installed Boost libraries: ') for key in sorted(self.lib_modules.keys()): mlog.debug(key, self.lib_modules[key]) # 3. check if requested modules are valid, that is, either found or in the list of known boost libraries self.check_invalid_modules() # 4. final check whether or not we find all requested and valid modules self.check_find_requested_modules() def check_invalid_modules(self): invalid_modules = [c for c in self.requested_modules if 'boost_' + c not in self.lib_modules and 'boost_' + c not in BOOST_LIBS] # previous versions of meson allowed include dirs as modules remove = [] for m in invalid_modules: if m in BOOST_DIRS: mlog.warning('Requested boost library', mlog.bold(m), 'that doesn\'t exist. ' 'This will be an error in the future') remove.append(m) self.requested_modules = [x for x in self.requested_modules if x not in remove] invalid_modules = [x for x in invalid_modules if x not in remove] if invalid_modules: mlog.error('Invalid Boost modules: ' + ', '.join(invalid_modules)) return True else: return False def log_details(self): module_str = ', '.join(self.requested_modules) return module_str def log_info(self): if self.boost_root: return self.boost_root return '' def detect_nix_roots(self): return [os.path.abspath(os.path.join(x, '..')) for x in self.clib_compiler.get_default_include_dirs()] def detect_win_roots(self): res = [] # Where boost documentation says it should be globtext = 'C:\\Program Files\\boost\\boost_*' files = glob.glob(globtext) res.extend(files) # Where boost built from source actually installs it if os.path.isdir('C:\\Boost'): res.append('C:\\Boost') # Where boost prebuilt binaries are globtext = 'C:\\local\\boost_*' files = glob.glob(globtext) res.extend(files) return res def detect_nix_incdir(self): if self.boost_root: return os.path.join(self.boost_root, 'include') return None # FIXME: Should pick a version that matches the requested version # Returns the folder that contains the boost folder. def detect_win_incdir(self): for root in self.boost_roots: globtext = os.path.join(root, 'include', 'boost-*') incdirs = glob.glob(globtext) if incdirs: return incdirs[0] incboostdir = os.path.join(root, 'include', 'boost') if os.path.isdir(incboostdir): return os.path.join(root, 'include') incboostdir = os.path.join(root, 'boost') if os.path.isdir(incboostdir): return root return None def get_compile_args(self): args = [] include_dir = self.incdir # Use "-isystem" when including boost headers instead of "-I" # to avoid compiler warnings/failures when "-Werror" is used # Careful not to use "-isystem" on default include dirs as it # breaks some of the headers for certain gcc versions # For example, doing g++ -isystem /usr/include on a simple # "int main()" source results in the error: # "/usr/include/c++/6.3.1/cstdlib:75:25: fatal error: stdlib.h: No such file or directory" # See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129 # and http://stackoverflow.com/questions/37218953/isystem-on-a-system-include-directory-causes-errors # for more details if include_dir and include_dir not in self.clib_compiler.get_default_include_dirs(): args.append("".join(self.clib_compiler.get_include_args(include_dir, True))) return args def get_requested(self, kwargs): candidates = mesonlib.extract_as_list(kwargs, 'modules') for c in candidates: if not isinstance(c, str): raise DependencyException('Boost module argument is not a string.') return candidates def detect_headers_and_version(self): try: version = self.clib_compiler.get_define('BOOST_LIB_VERSION', '#include ', self.env, self.get_compile_args(), [], disable_cache=True)[0] except mesonlib.EnvironmentException: return except TypeError: return # Remove quotes version = version[1:-1] # Fix version string self.version = version.replace('_', '.') self.is_found = True def detect_lib_modules(self): self.lib_modules = {} # 1. Try to find modules using compiler.find_library( ) if self.find_libraries_with_abi_tags(self.abi_tags()): pass # 2. Fall back to the old method else: if self.env.machines[self.for_machine].is_windows(): self.detect_lib_modules_win() else: self.detect_lib_modules_nix() def check_find_requested_modules(self): # 3. Check if we can find the modules for m in self.requested_modules: if 'boost_' + m not in self.lib_modules: mlog.debug('Requested Boost library {!r} not found'.format(m)) self.is_found = False def modname_from_filename(self, filename): modname = os.path.basename(filename) modname = modname.split('.', 1)[0] modname = modname.split('-', 1)[0] if modname.startswith('libboost'): modname = modname[3:] return modname def compiler_tag(self): tag = None compiler = self.env.detect_cpp_compiler(self.for_machine) if self.env.machines[self.for_machine].is_windows(): if compiler.get_id() in ['msvc', 'clang-cl']: comp_ts_version = compiler.get_toolset_version() compiler_ts = comp_ts_version.split('.') # FIXME - what about other compilers? tag = '-vc{}{}'.format(compiler_ts[0], compiler_ts[1]) else: tag = '' return tag def threading_tag(self): if not self.is_multithreading: return '' if self.env.machines[self.for_machine].is_darwin(): # - Mac: requires -mt for multithreading, so should not fall back to non-mt libraries. return '-mt' elif self.env.machines[self.for_machine].is_windows(): # - Windows: requires -mt for multithreading, so should not fall back to non-mt libraries. return '-mt' else: # - Linux: leaves off -mt but libraries are multithreading-aware. # - Cygwin: leaves off -mt but libraries are multithreading-aware. return '' def version_tag(self): return '-' + self.version.replace('.', '_') def debug_tag(self): return '-gd' if self.is_debug else '' def arch_tag(self): # currently only applies to windows msvc installed binaries if self.env.detect_cpp_compiler(self.for_machine).get_id() not in ['msvc', 'clang-cl']: return '' # pre-compiled binaries only added arch tag for versions > 1.64 if float(self.version) < 1.65: return '' arch = detect_cpu_family(self.env.coredata.compilers.host) if arch == 'x86': return '-x32' elif arch == 'x86_64': return '-x64' return '' def versioned_abi_tag(self): return self.compiler_tag() + self.threading_tag() + self.debug_tag() + self.arch_tag() + self.version_tag() # FIXME - how to handle different distributions, e.g. for Mac? Currently we handle homebrew and macports, but not fink. def abi_tags(self): if self.env.machines[self.for_machine].is_windows(): return [self.versioned_abi_tag(), self.threading_tag()] else: return [self.threading_tag()] def sourceforge_dir(self): if self.env.detect_cpp_compiler(self.for_machine).get_id() != 'msvc': return None comp_ts_version = self.env.detect_cpp_compiler(self.for_machine).get_toolset_version() arch = detect_cpu_family(self.env.coredata.compilers.host) if arch == 'x86': return 'lib32-msvc-{}'.format(comp_ts_version) elif arch == 'x86_64': return 'lib64-msvc-{}'.format(comp_ts_version) else: # Does anyone do Boost cross-compiling to other archs on Windows? return None def find_libraries_with_abi_tag(self, tag): # All modules should have the same tag self.lib_modules = {} all_found = True for module in self.requested_modules: libname = 'boost_' + module + tag args = self.clib_compiler.find_library(libname, self.env, self.extra_lib_dirs()) if args is None: mlog.debug("Couldn\'t find library '{}' for boost module '{}' (ABI tag = '{}')".format(libname, module, tag)) all_found = False else: mlog.debug('Link args for boost module "{}" are {}'.format(module, args)) self.lib_modules['boost_' + module] = args return all_found def find_libraries_with_abi_tags(self, tags): for tag in tags: if self.find_libraries_with_abi_tag(tag): return True return False def detect_lib_modules_win(self): if not self.libdir: # The libdirs in the distributed binaries (from sf) lib_sf = self.sourceforge_dir() if self.boost_root: roots = [self.boost_root] else: roots = self.boost_roots for root in roots: # The default libdir when building libdir = os.path.join(root, 'lib') if os.path.isdir(libdir): self.libdir = libdir break if lib_sf: full_path = os.path.join(root, lib_sf) if os.path.isdir(full_path): self.libdir = full_path break if not self.libdir: return for name in self.need_static_link: # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a libname = 'lib' + name + self.versioned_abi_tag() + '.lib' if os.path.isfile(os.path.join(self.libdir, libname)): self.lib_modules[self.modname_from_filename(libname)] = [libname] else: libname = "lib{}.lib".format(name) if os.path.isfile(os.path.join(self.libdir, libname)): self.lib_modules[name[3:]] = [libname] # globber1 applies to a layout=system installation # globber2 applies to a layout=versioned installation globber1 = 'libboost_*' if self.static else 'boost_*' globber2 = globber1 + self.versioned_abi_tag() # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a globber2_matches = glob.glob(os.path.join(self.libdir, globber2 + '.lib')) for entry in globber2_matches: fname = os.path.basename(entry) self.lib_modules[self.modname_from_filename(fname)] = [fname] if not globber2_matches: # FIXME - why are we only looking for *.lib? Mingw provides *.dll.a and *.a for entry in glob.glob(os.path.join(self.libdir, globber1 + '.lib')): if self.static: fname = os.path.basename(entry) self.lib_modules[self.modname_from_filename(fname)] = [fname] def detect_lib_modules_nix(self): if self.static: libsuffix = 'a' elif self.env.machines[self.for_machine].is_darwin(): libsuffix = 'dylib' else: libsuffix = 'so' globber = 'libboost_*.{}'.format(libsuffix) if self.libdir: libdirs = [self.libdir] elif self.boost_root is None: libdirs = mesonlib.get_library_dirs() else: libdirs = [os.path.join(self.boost_root, 'lib')] for libdir in libdirs: for name in self.need_static_link: libname = 'lib{}.a'.format(name) if os.path.isfile(os.path.join(libdir, libname)): self.lib_modules[name] = [libname] for entry in glob.glob(os.path.join(libdir, globber)): # I'm not 100% sure what to do here. Some distros # have modules such as thread only as -mt versions. # On debian all packages are built threading=multi # but not suffixed with -mt. # FIXME: implement detect_lib_modules_{debian, redhat, ...} # FIXME: this wouldn't work with -mt-gd either. -BDR if self.is_multithreading and mesonlib.is_debianlike(): pass elif self.is_multithreading and entry.endswith('-mt.{}'.format(libsuffix)): pass elif not entry.endswith('-mt.{}'.format(libsuffix)): pass else: continue modname = self.modname_from_filename(entry) if modname not in self.lib_modules: self.lib_modules[modname] = [entry] def extra_lib_dirs(self): if self.libdir: return [self.libdir] elif self.boost_root: return [os.path.join(self.boost_root, 'lib')] return [] def get_link_args(self, **kwargs): args = [] for d in self.extra_lib_dirs(): args += self.clib_compiler.get_linker_search_args(d) for lib in self.requested_modules: args += self.lib_modules['boost_' + lib] return args def get_sources(self): return [] # Generated with boost_names.py BOOST_LIBS = [ 'boost_atomic', 'boost_chrono', 'boost_chrono', 'boost_container', 'boost_context', 'boost_coroutine', 'boost_date_time', 'boost_exception', 'boost_fiber', 'boost_filesystem', 'boost_graph', 'boost_iostreams', 'boost_locale', 'boost_log', 'boost_log_setup', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_math_tr1', 'boost_math_tr1f', 'boost_math_tr1l', 'boost_math_c99', 'boost_math_c99f', 'boost_math_c99l', 'boost_mpi', 'boost_program_options', 'boost_random', 'boost_regex', 'boost_serialization', 'boost_wserialization', 'boost_signals', 'boost_stacktrace_noop', 'boost_stacktrace_backtrace', 'boost_stacktrace_addr2line', 'boost_stacktrace_basic', 'boost_stacktrace_windbg', 'boost_stacktrace_windbg_cached', 'boost_system', 'boost_prg_exec_monitor', 'boost_test_exec_monitor', 'boost_unit_test_framework', 'boost_thread', 'boost_timer', 'boost_type_erasure', 'boost_wave' ] BOOST_DIRS = [ 'lambda', 'optional', 'convert', 'system', 'uuid', 'archive', 'align', 'timer', 'chrono', 'gil', 'logic', 'signals', 'predef', 'tr1', 'multi_index', 'property_map', 'multi_array', 'context', 'random', 'endian', 'circular_buffer', 'proto', 'assign', 'format', 'math', 'phoenix', 'graph', 'locale', 'mpl', 'pool', 'unordered', 'core', 'exception', 'ptr_container', 'flyweight', 'range', 'typeof', 'thread', 'move', 'spirit', 'dll', 'compute', 'serialization', 'ratio', 'msm', 'config', 'metaparse', 'coroutine2', 'qvm', 'program_options', 'concept', 'detail', 'hana', 'concept_check', 'compatibility', 'variant', 'type_erasure', 'mpi', 'test', 'fusion', 'log', 'sort', 'local_function', 'units', 'functional', 'preprocessor', 'integer', 'container', 'polygon', 'interprocess', 'numeric', 'iterator', 'wave', 'lexical_cast', 'multiprecision', 'utility', 'tti', 'asio', 'dynamic_bitset', 'algorithm', 'xpressive', 'bimap', 'signals2', 'type_traits', 'regex', 'statechart', 'parameter', 'icl', 'python', 'lockfree', 'intrusive', 'io', 'pending', 'geometry', 'tuple', 'iostreams', 'heap', 'atomic', 'filesystem', 'smart_ptr', 'function', 'fiber', 'type_index', 'accumulators', 'function_types', 'coroutine', 'vmd', 'date_time', 'property_tree', 'bind' ]