diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 7ed97b2bb..df72eff65 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -355,9 +355,9 @@ class Backend: return l, stdlib_args @staticmethod - def _libdir_is_system(libdir, compilers): + def _libdir_is_system(libdir, compilers, env): for cc in compilers.values(): - if libdir in cc.get_library_dirs(): + if libdir in cc.get_library_dirs(env): return True return False @@ -372,7 +372,7 @@ class Backend: # The only link argument is an absolute path to a library file. libpath = la[0] libdir = os.path.dirname(libpath) - if exclude_system and self._libdir_is_system(libdir, target.compilers): + if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): # No point in adding system paths. continue # Windows doesn't support rpaths, but we use this function to @@ -612,8 +612,8 @@ class Backend: # Get program and library dirs from all target compilers if isinstance(target, build.BuildTarget): for cc in target.compilers.values(): - paths.update(cc.get_program_dirs()) - paths.update(cc.get_library_dirs()) + paths.update(cc.get_program_dirs(self.environment)) + paths.update(cc.get_library_dirs(self.environment)) return list(paths) def determine_windows_extra_paths(self, target, extra_bdeps, is_cross=False): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index ace069338..824145aad 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2333,7 +2333,7 @@ rule FORTRAN_DEP_HACK%s guessed_dependencies = [] # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker if hasattr(linker, 'get_library_naming'): - search_dirs = list(search_dirs) + linker.get_library_dirs() + search_dirs = list(search_dirs) + linker.get_library_dirs(self.environment) static_patterns = linker.get_library_naming(self.environment, 'static', strict=True) shared_patterns = linker.get_library_naming(self.environment, 'shared', strict=True) for libname in libs: diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index c7092b8c5..0b169cab5 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -16,6 +16,8 @@ import re import glob import os.path import subprocess +import functools +import itertools from pathlib import Path from .. import mlog @@ -49,6 +51,7 @@ gnu_compiler_internal_libs = ('m', 'c', 'pthread', 'dl', 'rt') class CCompiler(Compiler): + # TODO: Replace this manual cache with functools.lru_cache library_dirs_cache = {} program_dirs_cache = {} find_library_cache = {} @@ -172,42 +175,47 @@ class CCompiler(Compiler): def get_std_shared_lib_link_args(self): return ['-shared'] - def get_library_dirs_real(self): - env = os.environ.copy() - env['LC_ALL'] = 'C' - stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=env)[1] + @functools.lru_cache() + def _get_search_dirs(self, env): + extra_args = ['--print-search-dirs'] + stdo = None + with self._build_wrapper('', env, extra_args, None, 'compile', True) as p: + stdo = p.stdo + return stdo + + @staticmethod + def _split_fetch_real_dirs(pathstr, sep=':'): paths = [] - for line in stdo.split('\n'): - if line.startswith('libraries:'): - libstr = line.split('=', 1)[1] - paths = [os.path.realpath(p) for p in libstr.split(':') if os.path.exists(os.path.realpath(p))] + for p in pathstr.split(sep): + p = Path(p) + if p.exists(): + paths.append(p.resolve().as_posix()) return paths - def get_library_dirs(self): - key = tuple(self.exelist) + def get_compiler_dirs(self, env, name): + ''' + Get dirs from the compiler, either `libraries:` or `programs:` + ''' + stdo = self._get_search_dirs(env) + for line in stdo.split('\n'): + if line.startswith(name + ':'): + return CCompiler._split_fetch_real_dirs(line.split('=', 1)[1]) + return [] + + def get_library_dirs(self, env): + key = (tuple(self.exelist), env) if key not in self.library_dirs_cache: - self.library_dirs_cache[key] = self.get_library_dirs_real() + self.library_dirs_cache[key] = self.get_compiler_dirs(env, 'libraries') return self.library_dirs_cache[key][:] - def get_program_dirs_real(self): - env = os.environ.copy() - env['LC_ALL'] = 'C' - stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=env)[1] - paths = [] - for line in stdo.split('\n'): - if line.startswith('programs:'): - libstr = line.split('=', 1)[1] - paths = [os.path.realpath(p) for p in libstr.split(':')] - return paths - - def get_program_dirs(self): + def get_program_dirs(self, env): ''' Programs used by the compiler. Also where toolchain DLLs such as libstdc++-6.dll are found with MinGW. ''' - key = tuple(self.exelist) + key = (tuple(self.exelist), env) if key not in self.program_dirs_cache: - self.program_dirs_cache[key] = self.get_program_dirs_real() + self.program_dirs_cache[key] = self.get_compiler_dirs(env, 'programs') return self.program_dirs_cache[key][:] def get_pic_args(self): @@ -916,35 +924,19 @@ class CCompiler(Compiler): # Not found or we want to use a specific libtype? Try to find the # library file itself. patterns = self.get_library_naming(env, libtype) - for d in extra_dirs: + # Search in the specified dirs, and then in the system libraries + for d in itertools.chain(extra_dirs, self.get_library_dirs(env)): for p in patterns: trial = self._get_trials_from_pattern(p, d, libname) if not trial: continue + # We just check whether the library exists. We can't do a link + # check because the library might have unresolved symbols that + # require other libraries. trial = self._get_file_from_list(trial) if not trial: continue return [trial] - # Search in the system libraries too - for d in self.get_library_dirs(): - for p in patterns: - trial = self._get_trials_from_pattern(p, d, libname) - if not trial: - continue - trial = self._get_file_from_list(trial) - if not trial: - continue - # When searching the system paths used by the compiler, we - # need to check linking with link-whole, as static libs - # (.a) need to be checked to ensure they are the right - # architecture, e.g. 32bit or 64-bit. - # Just a normal test link won't work as the .a file doesn't - # seem to be checked by linker if there are no unresolved - # symbols from the main C file. - extra_link_args = self.get_link_whole_for([trial]) - extra_link_args = self.linker_to_compiler_args(extra_link_args) - if self.links(code, env, extra_args=extra_link_args): - return [trial] return None def find_library_impl(self, libname, env, extra_dirs, code, libtype): diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index b40047844..34397aaf4 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -908,7 +908,7 @@ class Compiler: def find_library(self, *args, **kwargs): raise EnvironmentException('Language {} does not support library finding.'.format(self.get_display_language())) - def get_library_dirs(self): + def get_library_dirs(self, *args, **kwargs): return [] def has_multi_arguments(self, args, env): @@ -995,7 +995,9 @@ class Compiler: mlog.debug('Working directory: ', tmpdirname) mlog.debug('Command line: ', ' '.join(commands), '\n') mlog.debug('Code:\n', code) - p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname) + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname, env=os_env) mlog.debug('Compiler stdout:\n', p.stdo) mlog.debug('Compiler stderr:\n', p.stde) p.commands = commands @@ -1353,10 +1355,12 @@ class ElbrusCompiler(GnuCompiler): 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded'] - def get_library_dirs(self): - env = os.environ.copy() - env['LC_ALL'] = 'C' - stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=env)[1] + # FIXME: use _build_wrapper to call this so that linker flags from the env + # get applied + def get_library_dirs(self, env): + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1] paths = [] for line in stdo.split('\n'): if line.startswith('libraries:'): @@ -1366,10 +1370,10 @@ class ElbrusCompiler(GnuCompiler): break return paths - def get_program_dirs(self): - env = os.environ.copy() - env['LC_ALL'] = 'C' - stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=env)[1] + def get_program_dirs(self, env): + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=os_env)[1] paths = [] for line in stdo.split('\n'): if line.startswith('programs:'): diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 5648a6f46..c384f7fac 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -177,11 +177,14 @@ end program prog def get_std_shared_lib_link_args(self): return CCompiler.get_std_shared_lib_link_args(self) - def get_library_dirs_real(self): - return CCompiler.get_library_dirs_real(self) + def _get_search_dirs(self, *args, **kwargs): + return CCompiler._get_search_dirs(self, *args, **kwargs) - def get_library_dirs(self): - return CCompiler.get_library_dirs(self) + def get_compiler_dirs(self, *args, **kwargs): + return CCompiler.get_compiler_dirs(self, *args, **kwargs) + + def get_library_dirs(self, *args, **kwargs): + return CCompiler.get_library_dirs(self, *args, **kwargs) def get_pic_args(self): return CCompiler.get_pic_args(self) diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 17f92400f..b06f62d0c 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -443,7 +443,7 @@ class BoostDependency(ExternalDependency): if self.libdir: libdirs = [self.libdir] elif self.boost_root is None: - libdirs = mesonlib.get_library_dirs() + libdirs = mesonlib.get_library_dirs(self.env) else: libdirs = [os.path.join(self.boost_root, 'lib')] for libdir in libdirs: