cmake: switch to pathlib (fixes #7322)

pull/7813/head
Daniel Mensinger 4 years ago
parent 10b44584ff
commit 77b5c82d07
  1. 24
      mesonbuild/cmake/client.py
  2. 43
      mesonbuild/cmake/common.py
  3. 40
      mesonbuild/cmake/executor.py
  4. 89
      mesonbuild/cmake/fileapi.py
  5. 252
      mesonbuild/cmake/interpreter.py
  6. 45
      mesonbuild/cmake/traceparser.py
  7. 8
      mesonbuild/dependencies/base.py
  8. 2
      mesonbuild/interpreter.py
  9. 9
      mesonbuild/mesonlib.py
  10. 3
      test cases/cmake/2 advanced/test.json
  11. 3
      test cases/cmake/3 advanced no dep/test.json

@ -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]:

@ -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))

@ -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'

@ -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

@ -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:

@ -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

@ -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())

@ -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()

@ -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."""

@ -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": {

@ -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"},

Loading…
Cancel
Save