diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2020-09-29 12:29:15 +0200 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2020-10-04 10:45:48 +0200 |
commit | 77b5c82d0725eb914aa341a617dd9f418de60910 (patch) | |
tree | cfdb171f0193e5b74ec3aade2ceb6e6af5865e7f | |
parent | 10b44584ff0b1f49ece260a48f89eb59c123616f (diff) | |
download | meson-77b5c82d0725eb914aa341a617dd9f418de60910.zip meson-77b5c82d0725eb914aa341a617dd9f418de60910.tar.gz meson-77b5c82d0725eb914aa341a617dd9f418de60910.tar.bz2 |
cmake: switch to pathlib (fixes #7322)
-rw-r--r-- | mesonbuild/cmake/client.py | 24 | ||||
-rw-r--r-- | mesonbuild/cmake/common.py | 43 | ||||
-rw-r--r-- | mesonbuild/cmake/executor.py | 40 | ||||
-rw-r--r-- | mesonbuild/cmake/fileapi.py | 89 | ||||
-rw-r--r-- | mesonbuild/cmake/interpreter.py | 252 | ||||
-rw-r--r-- | mesonbuild/cmake/traceparser.py | 45 | ||||
-rw-r--r-- | mesonbuild/dependencies/base.py | 8 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 2 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 9 | ||||
-rw-r--r-- | test cases/cmake/2 advanced/test.json | 3 | ||||
-rw-r--r-- | test cases/cmake/3 advanced no dep/test.json | 3 |
11 files changed, 262 insertions, 256 deletions
diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 2089be4..8057c3b 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -21,9 +21,9 @@ from ..mesonlib import MachineChoice from .. import mlog from contextlib import contextmanager from subprocess import Popen, PIPE, TimeoutExpired +from pathlib import Path import typing as T import json -import os if T.TYPE_CHECKING: from ..environment import Environment @@ -128,7 +128,7 @@ class MessageHello(MessageBase): # Request classes class RequestHandShake(RequestBase): - def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: + def __init__(self, src_dir: Path, build_dir: Path, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: super().__init__('handshake') self.src_dir = src_dir self.build_dir = build_dir @@ -142,13 +142,13 @@ class RequestHandShake(RequestBase): vers['minor'] = self.vers_minor # Old CMake versions (3.7) want '/' even on Windows - src_list = os.path.normpath(self.src_dir).split(os.sep) - bld_list = os.path.normpath(self.build_dir).split(os.sep) + self.src_dir = self.src_dir.resolve() + self.build_dir = self.build_dir.resolve() return { **super().to_dict(), - 'sourceDirectory': '/'.join(src_list), - 'buildDirectory': '/'.join(bld_list), + 'sourceDirectory': self.src_dir.as_posix(), + 'buildDirectory': self.build_dir.as_posix(), 'generator': self.generator, 'protocolVersion': vers } @@ -191,15 +191,15 @@ class ReplyCompute(ReplyBase): super().__init__(cookie, 'compute') class ReplyCMakeInputs(ReplyBase): - def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: T.List[CMakeBuildFile]) -> None: + def __init__(self, cookie: str, cmake_root: Path, src_dir: Path, build_files: T.List[CMakeBuildFile]) -> None: super().__init__(cookie, 'cmakeInputs') self.cmake_root = cmake_root self.src_dir = src_dir self.build_files = build_files def log(self) -> None: - mlog.log('CMake root: ', mlog.bold(self.cmake_root)) - mlog.log('Source dir: ', mlog.bold(self.src_dir)) + mlog.log('CMake root: ', mlog.bold(self.cmake_root.as_posix())) + mlog.log('Source dir: ', mlog.bold(self.src_dir.as_posix())) mlog.log('Build files:', mlog.bold(str(len(self.build_files)))) with mlog.nested(): for i in self.build_files: @@ -304,7 +304,7 @@ class CMakeClient: raise CMakeException('CMake server query failed') return reply - def do_handshake(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: + def do_handshake(self, src_dir: Path, build_dir: Path, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: # CMake prints the hello message on startup msg = self.readMessage() if not isinstance(msg, MessageHello): @@ -327,8 +327,8 @@ class CMakeClient: files = [] for i in data['buildFiles']: for j in i['sources']: - files += [CMakeBuildFile(j, i['isCMake'], i['isTemporary'])] - return ReplyCMakeInputs(data['cookie'], data['cmakeRootDirectory'], data['sourceDirectory'], files) + files += [CMakeBuildFile(Path(j), i['isCMake'], i['isTemporary'])] + return ReplyCMakeInputs(data['cookie'], Path(data['cmakeRootDirectory']), Path(data['sourceDirectory']), files) @contextmanager def connect(self) -> T.Generator[None, None, None]: diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index f6bd944..bb0c4ae 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -17,13 +17,14 @@ from ..mesonlib import MesonException from .. import mlog +from pathlib import Path import typing as T class CMakeException(MesonException): pass class CMakeBuildFile: - def __init__(self, file: str, is_cmake: bool, is_temp: bool) -> None: + def __init__(self, file: Path, is_cmake: bool, is_temp: bool) -> None: self.file = file self.is_cmake = is_cmake self.is_temp = is_temp @@ -81,7 +82,7 @@ def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]: return res class CMakeInclude: - def __init__(self, path: str, isSystem: bool = False): + def __init__(self, path: Path, isSystem: bool = False): self.path = path self.isSystem = isSystem @@ -94,7 +95,7 @@ class CMakeFileGroup: self.flags = _flags_to_list(data.get('compileFlags', '')) # type: T.List[str] self.is_generated = data.get('isGenerated', False) # type: bool self.language = data.get('language', 'C') # type: str - self.sources = data.get('sources', []) # type: T.List[str] + self.sources = [Path(x) for x in data.get('sources', [])] # type: T.List[Path] # Fix the include directories self.includes = [] # type: T.List[CMakeInclude] @@ -103,9 +104,9 @@ class CMakeFileGroup: isSystem = i.get('isSystem', False) assert isinstance(isSystem, bool) assert isinstance(i['path'], str) - self.includes += [CMakeInclude(i['path'], isSystem)] + self.includes += [CMakeInclude(Path(i['path']), isSystem)] elif isinstance(i, str): - self.includes += [CMakeInclude(i)] + self.includes += [CMakeInclude(Path(i))] def log(self) -> None: mlog.log('flags =', mlog.bold(', '.join(self.flags))) @@ -116,22 +117,22 @@ class CMakeFileGroup: mlog.log('sources:') for i in self.sources: with mlog.nested(): - mlog.log(i) + mlog.log(i.as_posix()) class CMakeTarget: def __init__(self, data: T.Dict[str, T.Any]) -> None: - self.artifacts = data.get('artifacts', []) # type: T.List[str] - self.src_dir = data.get('sourceDirectory', '') # type: str - self.build_dir = data.get('buildDirectory', '') # type: str + self.artifacts = [Path(x) for x in data.get('artifacts', [])] # type: T.List[Path] + self.src_dir = Path(data.get('sourceDirectory', '')) # type: Path + self.build_dir = Path(data.get('buildDirectory', '')) # type: Path self.name = data.get('name', '') # type: str self.full_name = data.get('fullName', '') # type: str self.install = data.get('hasInstallRule', False) # type: bool - self.install_paths = list(set(data.get('installPaths', []))) # type: T.List[str] + self.install_paths = [Path(x) for x in set(data.get('installPaths', []))] # type: T.List[Path] self.link_lang = data.get('linkerLanguage', '') # type: str self.link_libraries = _flags_to_list(data.get('linkLibraries', '')) # type: T.List[str] self.link_flags = _flags_to_list(data.get('linkFlags', '')) # type: T.List[str] self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', '')) # type: T.List[str] - # self.link_path = data.get('linkPath', '') # type: str + # self.link_path = Path(data.get('linkPath', '')) # type: Path self.type = data.get('type', 'EXECUTABLE') # type: str # self.is_generator_provided = data.get('isGeneratorProvided', False) # type: bool self.files = [] # type: T.List[CMakeFileGroup] @@ -140,13 +141,13 @@ class CMakeTarget: self.files += [CMakeFileGroup(i)] def log(self) -> None: - mlog.log('artifacts =', mlog.bold(', '.join(self.artifacts))) - mlog.log('src_dir =', mlog.bold(self.src_dir)) - mlog.log('build_dir =', mlog.bold(self.build_dir)) + mlog.log('artifacts =', mlog.bold(', '.join([x.as_posix() for x in self.artifacts]))) + mlog.log('src_dir =', mlog.bold(self.src_dir.as_posix())) + mlog.log('build_dir =', mlog.bold(self.build_dir.as_posix())) mlog.log('name =', mlog.bold(self.name)) mlog.log('full_name =', mlog.bold(self.full_name)) mlog.log('install =', mlog.bold('true' if self.install else 'false')) - mlog.log('install_paths =', mlog.bold(', '.join(self.install_paths))) + mlog.log('install_paths =', mlog.bold(', '.join([x.as_posix() for x in self.install_paths]))) mlog.log('link_lang =', mlog.bold(self.link_lang)) mlog.log('link_libraries =', mlog.bold(', '.join(self.link_libraries))) mlog.log('link_flags =', mlog.bold(', '.join(self.link_flags))) @@ -161,17 +162,17 @@ class CMakeTarget: class CMakeProject: def __init__(self, data: T.Dict[str, T.Any]) -> None: - self.src_dir = data.get('sourceDirectory', '') # type: str - self.build_dir = data.get('buildDirectory', '') # type: str - self.name = data.get('name', '') # type: str - self.targets = [] # type: T.List[CMakeTarget] + self.src_dir = Path(data.get('sourceDirectory', '')) # type: Path + self.build_dir = Path(data.get('buildDirectory', '')) # type: Path + self.name = data.get('name', '') # type: str + self.targets = [] # type: T.List[CMakeTarget] for i in data.get('targets', []): self.targets += [CMakeTarget(i)] def log(self) -> None: - mlog.log('src_dir =', mlog.bold(self.src_dir)) - mlog.log('build_dir =', mlog.bold(self.build_dir)) + mlog.log('src_dir =', mlog.bold(self.src_dir.as_posix())) + mlog.log('build_dir =', mlog.bold(self.build_dir.as_posix())) mlog.log('name =', mlog.bold(self.name)) for idx, i in enumerate(self.targets): mlog.log('Target {}:'.format(idx)) diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index d3588f0..bf89822 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -189,14 +189,14 @@ class CMakeExecutor: if always_capture_stderr is not None: self.always_capture_stderr = always_capture_stderr - def _cache_key(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_cache_key: + def _cache_key(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_cache_key: fenv = frozenset(env.items()) if env is not None else frozenset() targs = tuple(args) - return (self.cmakebin.get_path(), targs, build_dir, fenv) + return (self.cmakebin.get_path(), targs, build_dir.as_posix(), fenv) - def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: + def _call_cmout_stderr(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: cmd = self.cmakebin.get_command() + args - proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.PIPE, cwd=build_dir, env=env) + proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.PIPE, cwd=str(build_dir), env=env) # TODO [PYTHON_37]: drop Path conversion # stdout and stderr MUST be read at the same time to avoid pipe # blocking issues. The easiest way to do this is with a separate @@ -237,9 +237,9 @@ class CMakeExecutor: return proc.returncode, None, raw_trace - def _call_cmout(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: + def _call_cmout(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: cmd = self.cmakebin.get_command() + args - proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.STDOUT, cwd=build_dir, env=env) + proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.STDOUT, cwd=str(build_dir), env=env) # TODO [PYTHON_37]: drop Path conversion while True: line = proc.stdout.readline() if not line: @@ -249,11 +249,11 @@ class CMakeExecutor: proc.wait() return proc.returncode, None, None - def _call_quiet(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: - os.makedirs(build_dir, exist_ok=True) + def _call_quiet(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: + build_dir.mkdir(parents=True, exist_ok=True) cmd = self.cmakebin.get_command() + args - ret = S.run(cmd, env=env, cwd=build_dir, close_fds=False, - stdout=S.PIPE, stderr=S.PIPE, universal_newlines=False) + ret = S.run(cmd, env=env, cwd=str(build_dir), close_fds=False, + stdout=S.PIPE, stderr=S.PIPE, universal_newlines=False) # TODO [PYTHON_37]: drop Path conversion rc = ret.returncode out = ret.stdout.decode(errors='ignore') err = ret.stderr.decode(errors='ignore') @@ -261,7 +261,7 @@ class CMakeExecutor: mlog.debug("Called `{}` in {} -> {}".format(call, build_dir, rc)) return rc, out, err - def _call_impl(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: + def _call_impl(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: if not self.print_cmout: return self._call_quiet(args, build_dir, env) else: @@ -270,7 +270,7 @@ class CMakeExecutor: else: return self._call_cmout(args, build_dir, env) - def call(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None, disable_cache: bool = False) -> TYPE_result: + def call(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]] = None, disable_cache: bool = False) -> TYPE_result: if env is None: env = os.environ.copy() @@ -285,28 +285,28 @@ class CMakeExecutor: cache[key] = self._call_impl(args, build_dir, env) return cache[key] - def call_with_fake_build(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None) -> TYPE_result: + def call_with_fake_build(self, args: T.List[str], build_dir: Path, env: T.Optional[T.Dict[str, str]] = None) -> TYPE_result: # First check the cache cache = CMakeExecutor.class_cmake_cache key = self._cache_key(args, build_dir, env) if key in cache: return cache[key] - os.makedirs(build_dir, exist_ok=True) + build_dir.mkdir(exist_ok=True, parents=True) # Try to set the correct compiler for C and C++ # This step is required to make try_compile work inside CMake - fallback = os.path.realpath(__file__) # A file used as a fallback wehen everything else fails + fallback = Path(__file__).resolve() # A file used as a fallback wehen everything else fails compilers = self.environment.coredata.compilers[MachineChoice.BUILD] def make_abs(exe: str, lang: str) -> str: - if os.path.isabs(exe): + if Path(exe).is_absolute(): return exe p = shutil.which(exe) if p is None: mlog.debug('Failed to find a {} compiler for CMake. This might cause CMake to fail.'.format(lang)) - p = fallback + return str(fallback) return p def choose_compiler(lang: str) -> T.Tuple[str, str, str, str]: @@ -331,7 +331,7 @@ class CMakeExecutor: return make_abs(exe_list[1], lang), make_abs(exe_list[0], lang), comp_id, comp_version else: mlog.debug('Failed to find a {} compiler for CMake. This might cause CMake to fail.'.format(lang)) - return fallback, '', 'GNU', '' + return str(fallback), '', 'GNU', '' c_comp, c_launcher, c_id, c_version = choose_compiler('c') cxx_comp, cxx_launcher, cxx_id, cxx_version = choose_compiler('cpp') @@ -346,10 +346,10 @@ class CMakeExecutor: fortran_launcher = fortran_launcher.replace('\\', '/') # Reset the CMake cache - (Path(build_dir) / 'CMakeCache.txt').write_text('CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1\n') + (build_dir / 'CMakeCache.txt').write_text('CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1\n') # Fake the compiler files - comp_dir = Path(build_dir) / 'CMakeFiles' / self.cmakevers + comp_dir = build_dir / 'CMakeFiles' / self.cmakevers comp_dir.mkdir(parents=True, exist_ok=True) c_comp_file = comp_dir / 'CMakeCCompiler.cmake' diff --git a/mesonbuild/cmake/fileapi.py b/mesonbuild/cmake/fileapi.py index 0405145..4ef0caf 100644 --- a/mesonbuild/cmake/fileapi.py +++ b/mesonbuild/cmake/fileapi.py @@ -15,21 +15,21 @@ from .common import CMakeException, CMakeBuildFile, CMakeConfiguration import typing as T from .. import mlog -import os +from pathlib import Path import json import re STRIP_KEYS = ['cmake', 'reply', 'backtrace', 'backtraceGraph', 'version'] class CMakeFileAPI: - def __init__(self, build_dir: str): - self.build_dir = build_dir - self.api_base_dir = os.path.join(self.build_dir, '.cmake', 'api', 'v1') - self.request_dir = os.path.join(self.api_base_dir, 'query', 'client-meson') - self.reply_dir = os.path.join(self.api_base_dir, 'reply') - self.cmake_sources = [] # type: T.List[CMakeBuildFile] + def __init__(self, build_dir: Path): + self.build_dir = build_dir + self.api_base_dir = self.build_dir / '.cmake' / 'api' / 'v1' + self.request_dir = self.api_base_dir / 'query' / 'client-meson' + self.reply_dir = self.api_base_dir / 'reply' + self.cmake_sources = [] # type: T.List[CMakeBuildFile] self.cmake_configurations = [] # type: T.List[CMakeConfiguration] - self.kind_resolver_map = { + self.kind_resolver_map = { 'codemodel': self._parse_codemodel, 'cmakeFiles': self._parse_cmakeFiles, } @@ -41,7 +41,7 @@ class CMakeFileAPI: return self.cmake_configurations def setup_request(self) -> None: - os.makedirs(self.request_dir, exist_ok=True) + self.request_dir.mkdir(parents=True, exist_ok=True) query = { 'requests': [ @@ -50,18 +50,17 @@ class CMakeFileAPI: ] } - with open(os.path.join(self.request_dir, 'query.json'), 'w') as fp: - json.dump(query, fp, indent=2) + query_file = self.request_dir / 'query.json' + query_file.write_text(json.dumps(query, indent=2)) def load_reply(self) -> None: - if not os.path.isdir(self.reply_dir): + if not self.reply_dir.is_dir(): raise CMakeException('No response from the CMake file API') - files = os.listdir(self.reply_dir) root = None reg_index = re.compile(r'^index-.*\.json$') - for i in files: - if reg_index.match(i): + for i in self.reply_dir.iterdir(): + if reg_index.match(i.name): root = i break @@ -74,10 +73,9 @@ class CMakeFileAPI: index = self._strip_data(index) # Strip unused data (again for loaded files) # Debug output - debug_json = os.path.normpath(os.path.join(self.build_dir, '..', 'fileAPI.json')) - with open(debug_json, 'w') as fp: - json.dump(index, fp, indent=2) - mlog.cmd_ci_include(debug_json) + debug_json = self.build_dir / '..' / 'fileAPI.json' + debug_json.write_text(json.dumps(index, indent=2)) + mlog.cmd_ci_include(debug_json.as_posix()) # parse the JSON for i in index['objects']: @@ -100,17 +98,17 @@ class CMakeFileAPI: # resolved and the resulting data structure is identical # to the CMake serve output. - def helper_parse_dir(dir_entry: T.Dict[str, T.Any]) -> T.Tuple[str, str]: - src_dir = dir_entry.get('source', '.') - bld_dir = dir_entry.get('build', '.') - src_dir = src_dir if os.path.isabs(src_dir) else os.path.join(source_dir, src_dir) - bld_dir = bld_dir if os.path.isabs(bld_dir) else os.path.join(source_dir, bld_dir) - src_dir = os.path.normpath(src_dir) - bld_dir = os.path.normpath(bld_dir) + def helper_parse_dir(dir_entry: T.Dict[str, T.Any]) -> T.Tuple[Path, Path]: + src_dir = Path(dir_entry.get('source', '.')) + bld_dir = Path(dir_entry.get('build', '.')) + src_dir = src_dir if src_dir.is_absolute() else source_dir / src_dir + bld_dir = bld_dir if bld_dir.is_absolute() else build_dir / bld_dir + src_dir = src_dir.resolve() + bld_dir = bld_dir.resolve() return src_dir, bld_dir - def parse_sources(comp_group: T.Dict[str, T.Any], tgt: T.Dict[str, T.Any]) -> T.Tuple[T.List[str], T.List[str], T.List[int]]: + def parse_sources(comp_group: T.Dict[str, T.Any], tgt: T.Dict[str, T.Any]) -> T.Tuple[T.List[Path], T.List[Path], T.List[int]]: gen = [] src = [] idx = [] @@ -120,9 +118,9 @@ class CMakeFileAPI: if i >= len(src_list_raw) or 'path' not in src_list_raw[i]: continue if src_list_raw[i].get('isGenerated', False): - gen += [src_list_raw[i]['path']] + gen += [Path(src_list_raw[i]['path'])] else: - src += [src_list_raw[i]['path']] + src += [Path(src_list_raw[i]['path'])] idx += [i] return src, gen, idx @@ -133,8 +131,8 @@ class CMakeFileAPI: # Parse install paths (if present) install_paths = [] if 'install' in tgt: - prefix = tgt['install']['prefix']['path'] - install_paths = [os.path.join(prefix, x['path']) for x in tgt['install']['destinations']] + prefix = Path(tgt['install']['prefix']['path']) + install_paths = [prefix / x['path'] for x in tgt['install']['destinations']] install_paths = list(set(install_paths)) # On the first look, it looks really nice that the CMake devs have @@ -160,7 +158,7 @@ class CMakeFileAPI: # maybe we can make use of that in addition to the # implicit dependency detection tgt_data = { - 'artifacts': [x.get('path', '') for x in tgt.get('artifacts', [])], + 'artifacts': [Path(x.get('path', '')) for x in tgt.get('artifacts', [])], 'sourceDirectory': src_dir, 'buildDirectory': bld_dir, 'name': tgt.get('name', ''), @@ -269,14 +267,14 @@ class CMakeFileAPI: self.cmake_configurations += [CMakeConfiguration(cnf_data)] def _parse_cmakeFiles(self, data: T.Dict[str, T.Any]) -> None: - assert('inputs' in data) - assert('paths' in data) + assert 'inputs' in data + assert 'paths' in data - src_dir = data['paths']['source'] + src_dir = Path(data['paths']['source']) for i in data['inputs']: - path = i['path'] - path = path if os.path.isabs(path) else os.path.join(src_dir, path) + path = Path(i['path']) + path = path if path.is_absolute() else src_dir / path self.cmake_sources += [CMakeBuildFile(path, i.get('isCMake', False), i.get('isGenerated', False))] def _strip_data(self, data: T.Any) -> T.Any: @@ -309,14 +307,13 @@ class CMakeFileAPI: return data - def _reply_file_content(self, filename: str) -> T.Dict[str, T.Any]: - real_path = os.path.join(self.reply_dir, filename) - if not os.path.exists(real_path): + def _reply_file_content(self, filename: Path) -> T.Dict[str, T.Any]: + real_path = self.reply_dir / filename + if not real_path.exists(): raise CMakeException('File "{}" does not exist'.format(real_path)) - with open(real_path, 'r') as fp: - data = json.load(fp) - assert isinstance(data, dict) - for i in data.keys(): - assert isinstance(i, str) - return data + data = json.loads(real_path.read_text()) + assert isinstance(data, dict) + for i in data.keys(): + assert isinstance(i, str) + return data diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 1a7fe7a..aa43ae4 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -21,14 +21,15 @@ from .fileapi import CMakeFileAPI from .executor import CMakeExecutor from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .. import mlog, mesonlib -from ..mesonlib import MachineChoice, OrderedSet, version_compare +from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible from ..mesondata import mesondata from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from enum import Enum from functools import lru_cache from pathlib import Path import typing as T -import os, re +import re +from os import environ from ..mparser import ( Token, @@ -52,7 +53,7 @@ if T.TYPE_CHECKING: from ..backend.backends import Backend from ..environment import Environment -TYPE_mixed = T.Union[str, int, bool, BaseNode] +TYPE_mixed = T.Union[str, int, bool, Path, BaseNode] TYPE_mixed_list = T.Union[TYPE_mixed, T.Sequence[TYPE_mixed]] TYPE_mixed_kwargs = T.Dict[str, TYPE_mixed_list] @@ -141,7 +142,7 @@ def _sanitize_cmake_name(name: str) -> str: class OutputTargetMap: rm_so_version = re.compile(r'(\.[0-9]+)+$') - def __init__(self, build_dir: str): + def __init__(self, build_dir: Path): self.tgt_map = {} # type: T.Dict[str, T.Union['ConverterTarget', 'ConverterCustomTarget']] self.build_dir = build_dir @@ -186,37 +187,38 @@ class OutputTargetMap: new_name = OutputTargetMap.rm_so_version.sub('', new_name) candidates += ['{}.{}'.format(new_name, i)] for i in candidates: - keys += [self._rel_artifact_key(i), os.path.basename(i), self._base_artifact_key(i)] + keys += [self._rel_artifact_key(Path(i)), Path(i).name, self._base_artifact_key(Path(i))] return self._return_first_valid_key(keys) - def generated(self, name: str) -> T.Optional['ConverterCustomTarget']: + def generated(self, name: Path) -> T.Optional['ConverterCustomTarget']: res = self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)]) assert res is None or isinstance(res, ConverterCustomTarget) return res # Utility functions to generate local keys - def _rel_path(self, fname: str) -> T.Optional[str]: - fname = os.path.normpath(os.path.join(self.build_dir, fname)) - if os.path.commonpath([self.build_dir, fname]) != self.build_dir: - return None - return os.path.relpath(fname, self.build_dir) + def _rel_path(self, fname: Path) -> T.Optional[Path]: + try: + return fname.resolve().relative_to(self.build_dir) + except ValueError: + pass + return None def _target_key(self, tgt_name: str) -> str: return '__tgt_{}__'.format(tgt_name) - def _rel_generated_file_key(self, fname: str) -> T.Optional[str]: + def _rel_generated_file_key(self, fname: Path) -> T.Optional[str]: path = self._rel_path(fname) - return '__relgen_{}__'.format(path) if path else None + return '__relgen_{}__'.format(path.as_posix()) if path else None - def _base_generated_file_key(self, fname: str) -> str: - return '__gen_{}__'.format(os.path.basename(fname)) + def _base_generated_file_key(self, fname: Path) -> str: + return '__gen_{}__'.format(fname.name) - def _rel_artifact_key(self, fname: str) -> T.Optional[str]: + def _rel_artifact_key(self, fname: Path) -> T.Optional[str]: path = self._rel_path(fname) - return '__relart_{}__'.format(path) if path else None + return '__relart_{}__'.format(path.as_posix()) if path else None - def _base_artifact_key(self, fname: str) -> str: - return '__art_{}__'.format(os.path.basename(fname)) + def _base_artifact_key(self, fname: Path) -> str: + return '__art_{}__'.format(fname.name) class ConverterTarget: def __init__(self, target: CMakeTarget, env: 'Environment') -> None: @@ -229,7 +231,7 @@ class ConverterTarget: self.full_name = target.full_name self.type = target.type self.install = target.install - self.install_dir = '' + self.install_dir = None # type: T.Optional[Path] self.link_libraries = target.link_libraries self.link_flags = target.link_flags + target.link_lang_flags self.depends_raw = [] # type: T.List[str] @@ -239,11 +241,11 @@ class ConverterTarget: self.install_dir = target.install_paths[0] self.languages = [] # type: T.List[str] - self.sources = [] # type: T.List[str] - self.generated = [] # type: T.List[str] + self.sources = [] # type: T.List[Path] + self.generated = [] # type: T.List[Path] self.generated_ctgt = [] # type: T.List[CustomTargetReference] - self.includes = [] # type: T.List[str] - self.sys_includes = [] # type: T.List[str] + self.includes = [] # type: T.List[Path] + self.sys_includes = [] # type: T.List[Path] self.link_with = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] self.object_libs = [] # type: T.List[ConverterTarget] self.compile_opts = {} # type: T.Dict[str, T.List[str]] @@ -256,7 +258,7 @@ class ConverterTarget: # Convert the target name to a valid meson target name self.name = _sanitize_cmake_name(self.name) - self.generated_raw = [] # type: T.List[str] + self.generated_raw = [] # type: T.List[Path] for i in target.files: # Determine the meson language lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} @@ -286,7 +288,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?|[-]{1,2}std:)(.*)') - def postprocess(self, output_target_map: OutputTargetMap, root_src_dir: str, subdir: str, install_prefix: str, trace: CMakeTraceParser) -> None: + def postprocess(self, output_target_map: OutputTargetMap, root_src_dir: Path, subdir: Path, install_prefix: Path, trace: CMakeTraceParser) -> None: # Detect setting the C and C++ standard and do additional compiler args manipulation for i in ['c', 'cpp']: if i not in self.compile_opts: @@ -295,7 +297,7 @@ class ConverterTarget: temp = [] for j in self.compile_opts[i]: m = ConverterTarget.std_regex.match(j) - ctgt = output_target_map.generated(j) + ctgt = output_target_map.generated(Path(j)) if m: std = m.group(2) supported = self._all_lang_stds(i) @@ -314,7 +316,7 @@ class ConverterTarget: # Sometimes projects pass generated source files as compiler # flags. Add these as generated sources to ensure that the # corresponding custom target is run.2 - self.generated_raw += [j] + self.generated_raw += [Path(j)] temp += [j] elif j in blacklist_compiler_flags: pass @@ -350,7 +352,7 @@ class ConverterTarget: mlog.debug(str(tgt)) if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties: - self.includes += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] + self.includes += [Path(x) for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] if 'INTERFACE_LINK_OPTIONS' in tgt.properties: self.link_flags += [x for x in tgt.properties['INTERFACE_LINK_OPTIONS'] if x] @@ -402,7 +404,7 @@ class ConverterTarget: for j in otherDeps: if j in trace.targets: to_process += [j] - elif reg_is_lib.match(j) or os.path.exists(j): + elif reg_is_lib.match(j) or Path(j).exists(): libraries += [j] for j in libraries: @@ -418,7 +420,7 @@ class ConverterTarget: # Let meson handle this arcane magic if ',-rpath,' in i: continue - if not os.path.isabs(i): + if not Path(i).is_absolute(): link_with = output_target_map.artifact(i) if link_with: self.link_with += [link_with] @@ -432,48 +434,45 @@ class ConverterTarget: for i in self.languages: supported += list(lang_suffixes[i]) supported = ['.{}'.format(x) for x in supported] - self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])] - self.generated_raw = [x for x in self.generated_raw if any([x.endswith(y) for y in supported])] + self.sources = [x for x in self.sources if any([x.name.endswith(y) for y in supported])] + self.generated_raw = [x for x in self.generated_raw if any([x.name.endswith(y) for y in supported])] # Make paths relative - def rel_path(x: str, is_header: bool, is_generated: bool) -> T.Optional[str]: - if not os.path.isabs(x): - x = os.path.join(self.src_dir, x) - x = os.path.normpath(x) - if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated: + def rel_path(x: Path, is_header: bool, is_generated: bool) -> T.Optional[Path]: + if not x.is_absolute(): + x = self.src_dir / x + x = x.resolve() + assert x.is_absolute() + if not x.exists() and not any([x.name.endswith(y) for y in obj_suffixes]) and not is_generated: if ( - any([os.path.commonpath([x, os.path.normpath(os.path.join(root_src_dir, y))]) == x for y in self.generated_raw]) - and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir() + any([path_is_in_root(root_src_dir / y, x.resolve(), resolve=True) for y in self.generated_raw]) + and path_is_in_root(x, Path(self.env.get_build_dir()), resolve=True) ): - os.makedirs(x) - return os.path.relpath(x, os.path.join(self.env.get_build_dir(), subdir)) + x.mkdir(parents=True, exist_ok=True) + return x.relative_to(Path(self.env.get_build_dir()) / subdir) else: - mlog.warning('CMake: path', mlog.bold(x), 'does not exist.') + mlog.warning('CMake: path', mlog.bold(x.as_posix()), 'does not exist.') mlog.warning(' --> Ignoring. This can lead to build errors.') return None - if Path(x) in trace.explicit_headers: + if x in trace.explicit_headers: return None if ( - os.path.isabs(x) - and os.path.commonpath([x, self.env.get_source_dir()]) == self.env.get_source_dir() + path_is_in_root(x, Path(self.env.get_source_dir())) and not ( - os.path.commonpath([x, root_src_dir]) == root_src_dir or - os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir() + path_is_in_root(x, root_src_dir) or + path_is_in_root(x, Path(self.env.get_build_dir())) ) ): - mlog.warning('CMake: path', mlog.bold(x), 'is inside the root project but', mlog.bold('not'), 'inside the subproject.') + mlog.warning('CMake: path', mlog.bold(x.as_posix()), 'is inside the root project but', mlog.bold('not'), 'inside the subproject.') mlog.warning(' --> Ignoring. This can lead to build errors.') return None - if os.path.isabs(x) and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir(): - if is_header: - return os.path.relpath(x, os.path.join(self.env.get_build_dir(), subdir)) - else: - return os.path.relpath(x, root_src_dir) - if os.path.isabs(x) and os.path.commonpath([x, root_src_dir]) == root_src_dir: - return os.path.relpath(x, root_src_dir) + if path_is_in_root(x, Path(self.env.get_build_dir())) and is_header: + return x.relative_to(Path(self.env.get_build_dir()) / subdir) + if path_is_in_root(x, root_src_dir): + return x.relative_to(root_src_dir) return x - build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir)) + build_dir_rel = self.build_dir.relative_to(Path(self.env.get_build_dir()) / subdir) self.generated_raw = [rel_path(x, False, True) for x in self.generated_raw] self.includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.includes)] + [build_dir_rel])) self.sys_includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.sys_includes)])) @@ -496,13 +495,13 @@ class ConverterTarget: self.sources = [x for x in self.sources if x is not None] # Make sure '.' is always in the include directories - if '.' not in self.includes: - self.includes += ['.'] + if Path('.') not in self.includes: + self.includes += [Path('.')] # make install dir relative to the install prefix - if self.install_dir and os.path.isabs(self.install_dir): - if os.path.commonpath([self.install_dir, install_prefix]) == install_prefix: - self.install_dir = os.path.relpath(self.install_dir, install_prefix) + if self.install_dir and self.install_dir.is_absolute(): + if path_is_in_root(self.install_dir, install_prefix): + self.install_dir = self.install_dir.relative_to(install_prefix) # Remove blacklisted options and libs def check_flag(flag: str) -> bool: @@ -523,16 +522,13 @@ class ConverterTarget: def process_object_libs(self, obj_target_list: T.List['ConverterTarget'], linker_workaround: bool) -> None: # Try to detect the object library(s) from the generated input sources - temp = [x for x in self.generated] - temp = [os.path.basename(x) for x in temp] - temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])] - temp = [os.path.splitext(x)[0] for x in temp] + temp = [x for x in self.generated if any([x.name.endswith('.' + y) for y in obj_suffixes])] + stem = [x.stem for x in temp] exts = self._all_source_suffixes() # Temp now stores the source filenames of the object files for i in obj_target_list: - source_files = [x for x in i.sources + i.generated] - source_files = [os.path.basename(x) for x in source_files] - for j in temp: + source_files = [x.name for x in i.sources + i.generated] + for j in stem: # On some platforms (specifically looking at you Windows with vs20xy backend) CMake does # not produce object files with the format `foo.cpp.obj`, instead it skipps the language # suffix and just produces object files like `foo.obj`. Thus we have to do our best to @@ -552,7 +548,7 @@ class ConverterTarget: break # Filter out object files from the sources - self.generated = [x for x in self.generated if not any([x.endswith('.' + y) for y in obj_suffixes])] + self.generated = [x for x in self.generated if not any([x.name.endswith('.' + y) for y in obj_suffixes])] def _append_objlib_sources(self, tgt: 'ConverterTarget') -> None: self.includes += tgt.includes @@ -618,7 +614,7 @@ class ConverterTarget: mlog.log(' -- full_name: ', mlog.bold(self.full_name)) mlog.log(' -- type: ', mlog.bold(self.type)) mlog.log(' -- install: ', mlog.bold('true' if self.install else 'false')) - mlog.log(' -- install_dir: ', mlog.bold(self.install_dir)) + mlog.log(' -- install_dir: ', mlog.bold(self.install_dir.as_posix() if self.install_dir else '')) mlog.log(' -- link_libraries: ', mlog.bold(str(self.link_libraries))) mlog.log(' -- link_with: ', mlog.bold(str(self.link_with))) mlog.log(' -- object_libs: ', mlog.bold(str(self.object_libs))) @@ -666,15 +662,15 @@ class ConverterCustomTarget: ConverterCustomTarget.tgt_counter += 1 self.cmake_name = str(self.name) self.original_outputs = list(target.outputs) - self.outputs = [os.path.basename(x) for x in self.original_outputs] - self.conflict_map = {} # type: T.Dict[str, str] - self.command = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]] + self.outputs = [x.name for x in self.original_outputs] + self.conflict_map = {} # type: T.Dict[str, str] + self.command = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]] self.working_dir = target.working_dir self.depends_raw = target.depends - self.inputs = [] # type: T.List[T.Union[str, CustomTargetReference]] - self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] - self.current_bin_dir = Path(target.current_bin_dir) - self.current_src_dir = Path(target.current_src_dir) + self.inputs = [] # type: T.List[T.Union[str, CustomTargetReference]] + self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] + self.current_bin_dir = target.current_bin_dir # type: Path + self.current_src_dir = target.current_src_dir # type: Path self._raw_target = target # Convert the target name to a valid meson target name @@ -683,15 +679,15 @@ class ConverterCustomTarget: def __repr__(self) -> str: return '<{}: {} {}>'.format(self.__class__.__name__, self.name, self.outputs) - def postprocess(self, output_target_map: OutputTargetMap, root_src_dir: str, subdir: str, all_outputs: T.List[str], trace: CMakeTraceParser) -> None: + def postprocess(self, output_target_map: OutputTargetMap, root_src_dir: Path, all_outputs: T.List[str], trace: CMakeTraceParser) -> None: # Default the working directory to ${CMAKE_CURRENT_BINARY_DIR} - if not self.working_dir: - self.working_dir = self.current_bin_dir.as_posix() + if self.working_dir is None: + self.working_dir = self.current_bin_dir # relative paths in the working directory are always relative # to ${CMAKE_CURRENT_BINARY_DIR} - if not os.path.isabs(self.working_dir): - self.working_dir = (self.current_bin_dir / self.working_dir).as_posix() + if not self.working_dir.is_absolute(): + self.working_dir = self.current_bin_dir / self.working_dir # Modify the original outputs if they are relative. Again, # relative paths are relative to ${CMAKE_CURRENT_BINARY_DIR} @@ -700,7 +696,7 @@ class ConverterCustomTarget: return x else: return self.current_bin_dir / x - self.original_outputs = [ensure_absolute(Path(x)).as_posix() for x in self.original_outputs] + self.original_outputs = [ensure_absolute(x) for x in self.original_outputs] # Ensure that there is no duplicate output in the project so # that meson can handle cases where the same filename is @@ -748,18 +744,17 @@ class ConverterCustomTarget: self.outputs = [self.name + '.h'] # Check dependencies and input files - root = Path(root_src_dir) for i in self.depends_raw: if not i: continue raw = Path(i) art = output_target_map.artifact(i) tgt = output_target_map.target(i) - gen = output_target_map.generated(i) + gen = output_target_map.generated(raw) rel_to_root = None try: - rel_to_root = raw.relative_to(root) + rel_to_root = raw.relative_to(root_src_dir) except ValueError: rel_to_root = None @@ -768,7 +763,7 @@ class ConverterCustomTarget: # as outputs from other targets. # See https://github.com/mesonbuild/meson/issues/6632 if not raw.is_absolute() and (self.current_src_dir / raw).exists(): - self.inputs += [(self.current_src_dir / raw).relative_to(root).as_posix()] + self.inputs += [(self.current_src_dir / raw).relative_to(root_src_dir).as_posix()] elif raw.is_absolute() and raw.exists() and rel_to_root is not None: self.inputs += [rel_to_root.as_posix()] elif art: @@ -776,7 +771,7 @@ class ConverterCustomTarget: elif tgt: self.depends += [tgt] elif gen: - ctgt_ref = gen.get_ref(i) + ctgt_ref = gen.get_ref(raw) assert ctgt_ref is not None self.inputs += [ctgt_ref] @@ -793,12 +788,12 @@ class ConverterCustomTarget: new_deps += [i] self.depends = list(OrderedSet(new_deps)) - def get_ref(self, fname: str) -> T.Optional[CustomTargetReference]: - fname = os.path.basename(fname) + def get_ref(self, fname: Path) -> T.Optional[CustomTargetReference]: + name = fname.name try: - if fname in self.conflict_map: - fname = self.conflict_map[fname] - idx = self.outputs.index(fname) + if name in self.conflict_map: + name = self.conflict_map[name] + idx = self.outputs.index(name) return CustomTargetReference(self, idx) except ValueError: return None @@ -818,23 +813,22 @@ class CMakeAPI(Enum): FILE = 2 class CMakeInterpreter: - def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: 'Environment', backend: 'Backend'): - assert(hasattr(backend, 'name')) - self.build = build - self.subdir = subdir - self.src_dir = src_dir - self.build_dir_rel = os.path.join(subdir, '__CMake_build') - self.build_dir = os.path.join(env.get_build_dir(), self.build_dir_rel) + def __init__(self, build: 'Build', subdir: Path, src_dir: Path, install_prefix: Path, env: 'Environment', backend: 'Backend'): + self.build = build + self.subdir = subdir + self.src_dir = src_dir + self.build_dir_rel = subdir / '__CMake_build' + self.build_dir = Path(env.get_build_dir()) / self.build_dir_rel self.install_prefix = install_prefix - self.env = env - self.backend_name = backend.name - self.linkers = set() # type: T.Set[str] - self.cmake_api = CMakeAPI.SERVER - self.client = CMakeClient(self.env) - self.fileapi = CMakeFileAPI(self.build_dir) + self.env = env + self.backend_name = backend.name + self.linkers = set() # type: T.Set[str] + self.cmake_api = CMakeAPI.SERVER + self.client = CMakeClient(self.env) + self.fileapi = CMakeFileAPI(self.build_dir) # Raw CMake results - self.bs_files = [] # type: T.List[str] + self.bs_files = [] # type: T.List[Path] self.codemodel_configs = None # type: T.Optional[T.List[CMakeConfiguration]] self.raw_trace = None # type: T.Optional[str] @@ -843,7 +837,7 @@ class CMakeInterpreter: self.languages = [] # type: T.List[str] self.targets = [] # type: T.List[ConverterTarget] self.custom_targets = [] # type: T.List[ConverterCustomTarget] - self.trace = CMakeTraceParser('', '') # Will be replaced in analyse + self.trace = CMakeTraceParser('', Path('.')) # Will be replaced in analyse self.output_target_map = OutputTargetMap(self.build_dir) # Generated meson data @@ -899,16 +893,16 @@ class CMakeInterpreter: with mlog.nested(): mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_exe.version())) mlog.log(mlog.bold('Running:'), ' '.join(cmake_args)) - mlog.log(mlog.bold(' - build directory: '), self.build_dir) - mlog.log(mlog.bold(' - source directory: '), self.src_dir) + mlog.log(mlog.bold(' - build directory: '), self.build_dir.as_posix()) + mlog.log(mlog.bold(' - source directory: '), self.src_dir.as_posix()) mlog.log(mlog.bold(' - trace args: '), ' '.join(trace_args)) - mlog.log(mlog.bold(' - preload file: '), str(preload_file)) + mlog.log(mlog.bold(' - preload file: '), preload_file.as_posix()) mlog.log(mlog.bold(' - disabled policy warnings:'), '[{}]'.format(', '.join(disable_policy_warnings))) mlog.log() - os.makedirs(self.build_dir, exist_ok=True) - os_env = os.environ.copy() + self.build_dir.mkdir(parents=True, exist_ok=True) + os_env = environ.copy() os_env['LC_ALL'] = 'C' - final_args = cmake_args + trace_args + cmcmp_args + pload_args + [self.src_dir] + final_args = cmake_args + trace_args + cmcmp_args + pload_args + [self.src_dir.as_posix()] cmake_exe.set_exec_mode(print_cmout=True, always_capture_stderr=self.trace.requires_stderr()) rc, _, self.raw_trace = cmake_exe.call(final_args, self.build_dir, env=os_env, disable_cache=True) @@ -933,7 +927,7 @@ class CMakeInterpreter: # Load the buildsystem file list cmake_files = self.fileapi.get_cmake_sources() self.bs_files = [x.file for x in cmake_files if not x.is_cmake and not x.is_temp] - self.bs_files = [os.path.relpath(x, self.env.get_source_dir()) for x in self.bs_files] + self.bs_files = [relative_to_if_possible(x, Path(self.env.get_source_dir())) for x in self.bs_files] self.bs_files = list(OrderedSet(self.bs_files)) # Load the codemodel configurations @@ -960,7 +954,7 @@ class CMakeInterpreter: src_dir = bs_reply.src_dir self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] - self.bs_files = [os.path.relpath(os.path.join(src_dir, x), self.env.get_source_dir()) for x in self.bs_files] + self.bs_files = [relative_to_if_possible(src_dir / x, Path(self.env.get_source_dir()), resolve=True) for x in self.bs_files] self.bs_files = list(OrderedSet(self.bs_files)) self.codemodel_configs = cm_reply.configs @@ -1017,7 +1011,7 @@ class CMakeInterpreter: object_libs = [] custom_target_outputs = [] # type: T.List[str] for ctgt in self.custom_targets: - ctgt.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace) + ctgt.postprocess(self.output_target_map, self.src_dir, custom_target_outputs, self.trace) for tgt in self.targets: tgt.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) if tgt.type == 'OBJECT_LIBRARY': @@ -1045,7 +1039,7 @@ class CMakeInterpreter: raise CMakeException('CMakeInterpreter was not analysed') def token(tid: str = 'string', val: TYPE_mixed = '') -> Token: - return Token(tid, self.subdir, 0, 0, 0, None, val) + return Token(tid, self.subdir.as_posix(), 0, 0, 0, None, val) def string(value: str) -> StringNode: return StringNode(token(val=value)) @@ -1059,6 +1053,8 @@ class CMakeInterpreter: def nodeify(value: TYPE_mixed_list) -> BaseNode: if isinstance(value, str): return string(value) + if isinstance(value, Path): + return string(value.as_posix()) elif isinstance(value, bool): return BooleanNode(token(val=value)) elif isinstance(value, int): @@ -1084,11 +1080,11 @@ class CMakeInterpreter: kwargs = {} if kwargs is None else kwargs args_n = ArgumentNode(token()) if not isinstance(args, list): - assert isinstance(args, (str, int, bool, BaseNode)) + assert isinstance(args, (str, int, bool, Path, BaseNode)) args = [args] args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} - func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n) + func_n = FunctionNode(self.subdir.as_posix(), 0, 0, 0, 0, name, args_n) return func_n def method(obj: BaseNode, name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> MethodNode: @@ -1096,14 +1092,14 @@ class CMakeInterpreter: kwargs = {} if kwargs is None else kwargs args_n = ArgumentNode(token()) if not isinstance(args, list): - assert isinstance(args, (str, int, bool, BaseNode)) + assert isinstance(args, (str, int, bool, Path, BaseNode)) args = [args] args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} - return MethodNode(self.subdir, 0, 0, obj, name, args_n) + return MethodNode(self.subdir.as_posix(), 0, 0, obj, name, args_n) def assign(var_name: str, value: BaseNode) -> AssignmentNode: - return AssignmentNode(self.subdir, 0, 0, var_name, value) + return AssignmentNode(self.subdir.as_posix(), 0, 0, var_name, value) # Generate the root code block and the project function call root_cb = CodeBlockNode(token()) @@ -1144,7 +1140,7 @@ class CMakeInterpreter: # First handle inter target dependencies link_with = [] # type: T.List[IdNode] objec_libs = [] # type: T.List[IdNode] - sources = [] # type: T.List[str] + sources = [] # type: T.List[Path] generated = [] # type: T.List[T.Union[IdNode, IndexNode]] generated_filenames = [] # type: T.List[str] custom_targets = [] # type: T.List[ConverterCustomTarget] @@ -1190,7 +1186,7 @@ class CMakeInterpreter: if not is_header(j) or j in generated_filenames: continue - generated += [resolve_ctgt_ref(ctgt.get_ref(j))] + generated += [resolve_ctgt_ref(ctgt.get_ref(Path(j)))] generated_filenames += [j] # Determine the meson function to use for the build target @@ -1305,8 +1301,8 @@ class CMakeInterpreter: command += ['--internal', 'cmake_run_ctgt'] command += ['-o', '@OUTPUT@'] if tgt.original_outputs: - command += ['-O'] + tgt.original_outputs - command += ['-d', tgt.working_dir] + command += ['-O'] + [x.as_posix() for x in tgt.original_outputs] + command += ['-d', tgt.working_dir.as_posix()] # Generate the commands. Subcommands are separated by ';;;' for cmd in tgt.command: diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 5c41196..bee011c 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -23,12 +23,11 @@ from ..mesonlib import version_compare import typing as T from pathlib import Path import re -import os import json import textwrap class CMakeTraceLine: - def __init__(self, file: str, line: int, func: str, args: T.List[str]) -> None: + def __init__(self, file: Path, line: int, func: str, args: T.List[str]) -> None: self.file = file self.line = line self.func = func.lower() @@ -55,8 +54,8 @@ class CMakeTarget: self.imported = imported self.tline = tline self.depends = [] # type: T.List[str] - self.current_bin_dir = None # type: T.Optional[str] - self.current_src_dir = None # type: T.Optional[str] + self.current_bin_dir = None # type: T.Optional[Path] + self.current_src_dir = None # type: T.Optional[Path] def __repr__(self) -> str: s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- imported: {}\n -- properties: {{\n{} }}\n -- tline: {}' @@ -76,12 +75,12 @@ class CMakeTarget: class CMakeGeneratorTarget(CMakeTarget): def __init__(self, name: str) -> None: super().__init__(name, 'CUSTOM', {}) - self.outputs = [] # type: T.List[str] + self.outputs = [] # type: T.List[Path] self.command = [] # type: T.List[T.List[str]] - self.working_dir = None # type: T.Optional[str] + self.working_dir = None # type: T.Optional[Path] class CMakeTraceParser: - def __init__(self, cmake_version: str, build_dir: str, permissive: bool = True) -> None: + def __init__(self, cmake_version: str, build_dir: Path, permissive: bool = True) -> None: self.vars = {} # type: T.Dict[str, T.List[str]] self.targets = {} # type: T.Dict[str, CMakeTarget] @@ -93,7 +92,7 @@ class CMakeTraceParser: self.permissive = permissive # type: bool self.cmake_version = cmake_version # type: str self.trace_file = 'cmake_trace.txt' - self.trace_file_path = Path(build_dir) / self.trace_file + self.trace_file_path = build_dir / self.trace_file self.trace_format = 'json-v1' if version_compare(cmake_version, '>=3.17') else 'human' # State for delayed command execution. Delayed command execution is realised @@ -339,7 +338,7 @@ class CMakeTraceParser: target = CMakeGeneratorTarget(name) def handle_output(key: str, target: CMakeGeneratorTarget) -> None: - target.outputs += [key] + target.outputs += [Path(key)] def handle_command(key: str, target: CMakeGeneratorTarget) -> None: if key == 'ARGS': @@ -349,12 +348,14 @@ class CMakeTraceParser: def handle_depends(key: str, target: CMakeGeneratorTarget) -> None: target.depends += [key] + working_dir = None def handle_working_dir(key: str, target: CMakeGeneratorTarget) -> None: - if target.working_dir is None: - target.working_dir = key + nonlocal working_dir + if working_dir is None: + working_dir = key else: - target.working_dir += ' ' - target.working_dir += key + working_dir += ' ' + working_dir += key fn = None @@ -376,9 +377,13 @@ class CMakeTraceParser: if fn is not None: fn(i, target) - target.current_bin_dir = self.var_to_str('MESON_PS_CMAKE_CURRENT_BINARY_DIR') - target.current_src_dir = self.var_to_str('MESON_PS_CMAKE_CURRENT_SOURCE_DIR') - target.outputs = self._guess_files(target.outputs) + cbinary_dir = self.var_to_str('MESON_PS_CMAKE_CURRENT_BINARY_DIR') + csource_dir = self.var_to_str('MESON_PS_CMAKE_CURRENT_SOURCE_DIR') + + target.working_dir = Path(working_dir) if working_dir else None + target.current_bin_dir = Path(cbinary_dir) if cbinary_dir else None + target.current_src_dir = Path(csource_dir) if csource_dir else None + target.outputs = [Path(x) for x in self._guess_files([str(y) for y in target.outputs])] target.depends = self._guess_files(target.depends) target.command = [self._guess_files(x) for x in target.command] @@ -639,7 +644,7 @@ class CMakeTraceParser: argl = args.split(' ') argl = list(map(lambda x: x.strip(), argl)) - yield CMakeTraceLine(file, int(line), func, argl) + yield CMakeTraceLine(Path(file), int(line), func, argl) def _lex_trace_json(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]: lines = trace.splitlines(keepends=False) @@ -654,7 +659,7 @@ class CMakeTraceParser: for j in args: assert isinstance(j, str) args = [parse_generator_expressions(x) for x in args] - yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args) + yield CMakeTraceLine(Path(data['file']), data['line'], data['cmd'], args) def _flatten_args(self, args: T.List[str]) -> T.List[str]: # Split lists in arguments @@ -681,7 +686,7 @@ class CMakeTraceParser: if curr_str is None: curr_str = i path_found = False - elif os.path.isfile(curr_str): + elif Path(curr_str).is_file(): # Abort concatenation if curr_str is an existing file fixed_list += [curr_str] curr_str = i @@ -697,7 +702,7 @@ class CMakeTraceParser: fixed_list += [curr_str] curr_str = None path_found = False - elif os.path.exists('{} {}'.format(curr_str, i)): + elif Path('{} {}'.format(curr_str, i)).exists(): # Path detected curr_str = '{} {}'.format(curr_str, i) path_found = True diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 7b2f2d9..e622859 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1528,12 +1528,12 @@ class CMakeDependency(ExternalDependency): self.compile_args = compileOptions + compileDefinitions + ['-I{}'.format(x) for x in incDirs] self.link_args = libraries - def _get_build_dir(self) -> str: + def _get_build_dir(self) -> Path: build_dir = Path(self.cmake_root_dir) / 'cmake_{}'.format(self.name) build_dir.mkdir(parents=True, exist_ok=True) - return str(build_dir) + return build_dir - def _setup_cmake_dir(self, cmake_file: str) -> str: + def _setup_cmake_dir(self, cmake_file: str) -> Path: # Setup the CMake build environment and return the "build" directory build_dir = self._get_build_dir() @@ -1557,7 +1557,7 @@ cmake_minimum_required(VERSION ${{CMAKE_VERSION}}) project(MesonTemp LANGUAGES {}) """.format(' '.join(cmake_language)) + cmake_txt - cm_file = Path(build_dir) / 'CMakeLists.txt' + cm_file = build_dir / 'CMakeLists.txt' cm_file.write_text(cmake_txt) mlog.cmd_ci_include(cm_file.absolute().as_posix()) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c56eff5..eb67a45 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2983,7 +2983,7 @@ external dependencies (including libraries) must go to "dependencies".''') cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', [])) cmake_options += options.cmake_options - cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend) + cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend) cm_int.initialise(cmake_options) cm_int.analyse() diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index f297771..0b614ba 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -1570,6 +1570,15 @@ def path_is_in_root(path: Path, root: Path, resolve: bool = False) -> bool: return False return True +def relative_to_if_possible(path: Path, root: Path, resolve: bool = False) -> Path: + try: + if resolve: + return path.resolve().relative_to(root.resolve()) + else: + return path.relative_to(root) + except ValueError: + return path + class LibType(IntEnum): """Enumeration for library types.""" diff --git a/test cases/cmake/2 advanced/test.json b/test cases/cmake/2 advanced/test.json index e12f530..ff3d5a7 100644 --- a/test cases/cmake/2 advanced/test.json +++ b/test cases/cmake/2 advanced/test.json @@ -1,8 +1,7 @@ { "installed": [ {"type": "expr", "file": "usr/?lib/libcm_cmModLib?so"}, - {"type": "implib", "platform": "cygwin", "file": "usr/lib/libcm_cmModLib"}, - {"type": "implib", "platform": "!cygwin", "file": "usr/bin/libcm_cmModLib"}, + {"type": "implib", "file": "usr/lib/libcm_cmModLib"}, {"type": "exe", "file": "usr/bin/cm_testEXE"} ], "tools": { diff --git a/test cases/cmake/3 advanced no dep/test.json b/test cases/cmake/3 advanced no dep/test.json index 98a1719..af25a8e 100644 --- a/test cases/cmake/3 advanced no dep/test.json +++ b/test cases/cmake/3 advanced no dep/test.json @@ -1,8 +1,7 @@ { "installed": [ {"type": "expr", "file": "usr/?lib/libcm_cmModLib?so"}, - {"type": "implib", "platform": "cygwin", "file": "usr/lib/libcm_cmModLib"}, - {"type": "implib", "platform": "!cygwin", "file": "usr/bin/libcm_cmModLib"}, + {"type": "implib", "file": "usr/lib/libcm_cmModLib"}, {"type": "pdb", "file": "usr/bin/cm_cmModLib"}, {"type": "pdb", "file": "usr/bin/cm_testEXE"}, {"type": "exe", "file": "usr/bin/cm_testEXE"}, |