The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
14 KiB

# Copyright 2019 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from ..mesonlib import MesonException, OptionKey
from .. import mlog
from pathlib import Path
import typing as T
if T.TYPE_CHECKING:
from ..environment import Environment
language_map = {
'c': 'C',
'cpp': 'CXX',
'cuda': 'CUDA',
'objc': 'OBJC',
'objcpp': 'OBJCXX',
'cs': 'CSharp',
'java': 'Java',
'fortran': 'Fortran',
'swift': 'Swift',
}
backend_generator_map = {
'ninja': 'Ninja',
'xcode': 'Xcode',
'vs2010': 'Visual Studio 10 2010',
Add Visual Studio 2012/2013 backends (#8803) * backends: Add a Visual Studio 2013 backend This is more-or-less a quick port from the VS2015 backend, except that we update the Visual Studio version strings and toolset versions accordingly. Also correct the generator string for Visual Studio 2015 in mesonbuild/cmake/common.py. * backend: Add VS2012 backend Similar to what we did for Visual Studio 2013, add a Visual Studio 2012 backend. * vs2010backend.py: Implement `link_whole:` if needed We actually need Visual Studio 2015 Update 2 to use `/WHOLEARCHIVE:`, which is what we are currently using for `link_whole:` on Visual Studio. For Visual Studio versions before that, we need to expand from the static targets that were indicated by `link_whole:`, and any of the sub-dependent targets that were pulled in via the dependent target's `link_whole:`. This wil ensure `link_whole:` would actually work in such cases. * vs2010backend.py: Handle objects from generated sources Unforunately, we can't use backends.determine_ext_objs() reliably, as the Visual Studio backends handle this differently. * vs2010backend.py: Fix generating VS2010 projects Visual Studio 2010 (at least the Express Edition) does not set the envvar %VisualStudioVersion% in its command prompt, so fix generating VS2010 projects by taking account into this, so that we can determine the location of vcvarsall.bat correctly. * whole archive test: Disable on vs2012/2013 backends too The Visual Studio 2012/2013 IDE has problems handling the items that would be generated from this test case, so skip this test when using --backend=vs[2012|2013]. This test does work for the Ninja backend when VS2012 or VS2013 is used, though. Consolidate this error message with XCode along with the vs2010 backend. * docs: Add the new vs2012 and vs2013 backends Let people know that we have backends for vs2012 and 2013. Also let people know that generating Visual Studio 2010 projects have been fixed and the pre-vs2015 backends now handle the `link_whole:` project option.
3 years ago
'vs2012': 'Visual Studio 11 2012',
'vs2013': 'Visual Studio 12 2013',
'vs2015': 'Visual Studio 14 2015',
'vs2017': 'Visual Studio 15 2017',
'vs2019': 'Visual Studio 16 2019',
'vs2022': 'Visual Studio 17 2022',
}
blacklist_cmake_defs = [
'CMAKE_TOOLCHAIN_FILE',
'CMAKE_PROJECT_INCLUDE',
'MESON_PRELOAD_FILE',
'MESON_PS_CMAKE_CURRENT_BINARY_DIR',
'MESON_PS_CMAKE_CURRENT_SOURCE_DIR',
'MESON_PS_DELAYED_CALLS',
'MESON_PS_LOADED',
'MESON_FIND_ROOT_PATH',
'MESON_CMAKE_SYSROOT',
'MESON_PATHS_LIST',
'MESON_CMAKE_ROOT',
]
def cmake_is_debug(env: 'Environment') -> bool:
if OptionKey('b_vscrt') in env.coredata.options:
is_debug = env.coredata.get_option(OptionKey('buildtype')) == 'debug'
if env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}:
is_debug = True
return is_debug
else:
# Don't directly assign to is_debug to make mypy happy
debug_opt = env.coredata.get_option(OptionKey('debug'))
assert isinstance(debug_opt, bool)
return debug_opt
class CMakeException(MesonException):
pass
class CMakeBuildFile:
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
def __repr__(self) -> str:
return f'<{self.__class__.__name__}: {self.file}; cmake={self.is_cmake}; temp={self.is_temp}>'
def _flags_to_list(raw: str) -> T.List[str]:
# Convert a raw commandline string into a list of strings
res = []
curr = ''
escape = False
in_string = False
for i in raw:
if escape:
# If the current char is not a quote, the '\' is probably important
if i not in ['"', "'"]:
curr += '\\'
curr += i
escape = False
elif i == '\\':
escape = True
elif i in ['"', "'"]:
in_string = not in_string
elif i in [' ', '\n']:
if in_string:
curr += i
else:
res += [curr]
curr = ''
else:
curr += i
res += [curr]
res = list(filter(lambda x: len(x) > 0, res))
return res
def cmake_get_generator_args(env: 'Environment') -> T.List[str]:
backend_name = env.coredata.get_option(OptionKey('backend'))
assert isinstance(backend_name, str)
assert backend_name in backend_generator_map
return ['-G', backend_generator_map[backend_name]]
def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]:
res = [] # type: T.List[str]
if not isinstance(raw, list):
raw = [raw]
for i in raw:
if not isinstance(i, dict):
raise MesonException('Invalid CMake defines. Expected a dict, but got a {}'.format(type(i).__name__))
for key, val in i.items():
assert isinstance(key, str)
if key in blacklist_cmake_defs:
mlog.warning('Setting', mlog.bold(key), 'is not supported. See the meson docs for cross compilation support:')
mlog.warning(' - URL: https://mesonbuild.com/CMake-module.html#cross-compilation')
mlog.warning(' --> Ignoring this option')
continue
if isinstance(val, (str, int, float)):
res += [f'-D{key}={val}']
elif isinstance(val, bool):
val_str = 'ON' if val else 'OFF'
res += [f'-D{key}={val_str}']
else:
raise MesonException('Type "{}" of "{}" is not supported as for a CMake define value'.format(type(val).__name__, key))
return res
# TODO: this functuin will become obsolete once the `cmake_args` kwarg is dropped
def check_cmake_args(args: T.List[str]) -> T.List[str]:
res = [] # type: T.List[str]
dis = ['-D' + x for x in blacklist_cmake_defs]
assert dis # Ensure that dis is not empty.
for i in args:
if any(i.startswith(x) for x in dis):
mlog.warning('Setting', mlog.bold(i), 'is not supported. See the meson docs for cross compilation support:')
mlog.warning(' - URL: https://mesonbuild.com/CMake-module.html#cross-compilation')
mlog.warning(' --> Ignoring this option')
continue
res += [i]
return res
class CMakeInclude:
def __init__(self, path: Path, isSystem: bool = False):
self.path = path
self.isSystem = isSystem
def __repr__(self) -> str:
return f'<CMakeInclude: {self.path} -- isSystem = {self.isSystem}>'
class CMakeFileGroup:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.defines = data.get('defines', '') # type: str
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 = [Path(x) for x in data.get('sources', [])] # type: T.List[Path]
# Fix the include directories
self.includes = [] # type: T.List[CMakeInclude]
for i in data.get('includePath', []):
if isinstance(i, dict) and 'path' in i:
isSystem = i.get('isSystem', False)
assert isinstance(isSystem, bool)
assert isinstance(i['path'], str)
self.includes += [CMakeInclude(Path(i['path']), isSystem)]
elif isinstance(i, str):
self.includes += [CMakeInclude(Path(i))]
def log(self) -> None:
mlog.log('flags =', mlog.bold(', '.join(self.flags)))
mlog.log('defines =', mlog.bold(', '.join(self.defines)))
mlog.log('includes =', mlog.bold(', '.join([str(x) for x in self.includes])))
mlog.log('is_generated =', mlog.bold('true' if self.is_generated else 'false'))
mlog.log('language =', mlog.bold(self.language))
mlog.log('sources:')
for i in self.sources:
with mlog.nested():
mlog.log(i.as_posix())
class CMakeTarget:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
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 = [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 = 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]
for i in data.get('fileGroups', []):
self.files += [CMakeFileGroup(i)]
def log(self) -> None:
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([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)))
mlog.log('link_lang_flags =', mlog.bold(', '.join(self.link_lang_flags)))
# mlog.log('link_path =', mlog.bold(self.link_path))
mlog.log('type =', mlog.bold(self.type))
# mlog.log('is_generator_provided =', mlog.bold('true' if self.is_generator_provided else 'false'))
for idx, i in enumerate(self.files):
mlog.log(f'Files {idx}:')
with mlog.nested():
i.log()
class CMakeProject:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
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.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(f'Target {idx}:')
with mlog.nested():
i.log()
class CMakeConfiguration:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.name = data.get('name', '') # type: str
self.projects = [] # type: T.List[CMakeProject]
for i in data.get('projects', []):
self.projects += [CMakeProject(i)]
def log(self) -> None:
mlog.log('name =', mlog.bold(self.name))
for idx, i in enumerate(self.projects):
mlog.log(f'Project {idx}:')
with mlog.nested():
i.log()
class SingleTargetOptions:
def __init__(self) -> None:
self.opts = {} # type: T.Dict[str, str]
self.lang_args = {} # type: T.Dict[str, T.List[str]]
self.link_args = [] # type: T.List[str]
self.install = 'preserve'
def set_opt(self, opt: str, val: str) -> None:
self.opts[opt] = val
def append_args(self, lang: str, args: T.List[str]) -> None:
if lang not in self.lang_args:
self.lang_args[lang] = []
self.lang_args[lang] += args
def append_link_args(self, args: T.List[str]) -> None:
self.link_args += args
def set_install(self, install: bool) -> None:
self.install = 'true' if install else 'false'
def get_override_options(self, initial: T.List[str]) -> T.List[str]:
res = [] # type: T.List[str]
for i in initial:
opt = i[:i.find('=')]
if opt not in self.opts:
res += [i]
res += [f'{k}={v}' for k, v in self.opts.items()]
return res
def get_compile_args(self, lang: str, initial: T.List[str]) -> T.List[str]:
if lang in self.lang_args:
return initial + self.lang_args[lang]
return initial
def get_link_args(self, initial: T.List[str]) -> T.List[str]:
return initial + self.link_args
def get_install(self, initial: bool) -> bool:
return {'preserve': initial, 'true': True, 'false': False}[self.install]
class TargetOptions:
def __init__(self) -> None:
self.global_options = SingleTargetOptions()
self.target_options = {} # type: T.Dict[str, SingleTargetOptions]
def __getitem__(self, tgt: str) -> SingleTargetOptions:
if tgt not in self.target_options:
self.target_options[tgt] = SingleTargetOptions()
return self.target_options[tgt]
def get_override_options(self, tgt: str, initial: T.List[str]) -> T.List[str]:
initial = self.global_options.get_override_options(initial)
if tgt in self.target_options:
initial = self.target_options[tgt].get_override_options(initial)
return initial
def get_compile_args(self, tgt: str, lang: str, initial: T.List[str]) -> T.List[str]:
initial = self.global_options.get_compile_args(lang, initial)
if tgt in self.target_options:
initial = self.target_options[tgt].get_compile_args(lang, initial)
return initial
def get_link_args(self, tgt: str, initial: T.List[str]) -> T.List[str]:
initial = self.global_options.get_link_args(initial)
if tgt in self.target_options:
initial = self.target_options[tgt].get_link_args(initial)
return initial
def get_install(self, tgt: str, initial: bool) -> bool:
initial = self.global_options.get_install(initial)
if tgt in self.target_options:
initial = self.target_options[tgt].get_install(initial)
return initial