aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/cmake
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/cmake')
-rw-r--r--mesonbuild/cmake/__init__.py5
-rw-r--r--mesonbuild/cmake/common.py95
-rwxr-xr-xmesonbuild/cmake/data/run_ctgt.py96
-rw-r--r--mesonbuild/cmake/executor.py51
-rw-r--r--mesonbuild/cmake/interpreter.py77
-rw-r--r--mesonbuild/cmake/traceparser.py26
6 files changed, 199 insertions, 151 deletions
diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py
index 01cc3f9..db7aefd 100644
--- a/mesonbuild/cmake/__init__.py
+++ b/mesonbuild/cmake/__init__.py
@@ -24,11 +24,14 @@ __all__ = [
'CMakeTarget',
'CMakeTraceLine',
'CMakeTraceParser',
+ 'SingleTargetOptions',
+ 'TargetOptions',
'parse_generator_expressions',
'language_map',
+ 'cmake_defines_to_args',
]
-from .common import CMakeException
+from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args
from .client import CMakeClient
from .executor import CMakeExecutor
from .fileapi import CMakeFileAPI
diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py
index e7da0d7..4510b5d 100644
--- a/mesonbuild/cmake/common.py
+++ b/mesonbuild/cmake/common.py
@@ -60,6 +60,26 @@ def _flags_to_list(raw: str) -> T.List[str]:
res = list(filter(lambda x: len(x) > 0, res))
return res
+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 isinstance(val, (str, int, float)):
+ res += ['-D{}={}'.format(key, val)]
+ elif isinstance(val, bool):
+ val_str = 'ON' if val else 'OFF'
+ res += ['-D{}={}'.format(key, val_str)]
+ else:
+ raise MesonException('Type "{}" of "{}" is not supported as for a CMake define value'.format(type(val).__name__, key))
+
+ return res
+
class CMakeFileGroup:
def __init__(self, data: dict):
self.defines = data.get('defines', '')
@@ -163,3 +183,78 @@ class CMakeConfiguration:
mlog.log('Project {}:'.format(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 += ['{}={}'.format(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
diff --git a/mesonbuild/cmake/data/run_ctgt.py b/mesonbuild/cmake/data/run_ctgt.py
deleted file mode 100755
index 9d5d437..0000000
--- a/mesonbuild/cmake/data/run_ctgt.py
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import subprocess
-import shutil
-import os
-import sys
-from pathlib import Path
-
-commands = [[]]
-SEPARATOR = ';;;'
-
-# Generate CMD parameters
-parser = argparse.ArgumentParser(description='Wrapper for add_custom_command')
-parser.add_argument('-d', '--directory', type=str, metavar='D', required=True, help='Working directory to cwd to')
-parser.add_argument('-o', '--outputs', nargs='+', metavar='O', required=True, help='Expected output files')
-parser.add_argument('-O', '--original-outputs', nargs='*', metavar='O', default=[], help='Output files expected by CMake')
-parser.add_argument('commands', nargs=argparse.REMAINDER, help='A "{}" seperated list of commands'.format(SEPARATOR))
-
-# Parse
-args = parser.parse_args()
-
-dummy_target = None
-if len(args.outputs) == 1 and len(args.original_outputs) == 0:
- dummy_target = args.outputs[0]
-elif len(args.outputs) != len(args.original_outputs):
- print('Length of output list and original output list differ')
- sys.exit(1)
-
-for i in args.commands:
- if i == SEPARATOR:
- commands += [[]]
- continue
-
- i = i.replace('"', '') # Remove lefover quotes
- commands[-1] += [i]
-
-# Execute
-for i in commands:
- # Skip empty lists
- if not i:
- continue
-
- cmd = []
- stdout = None
- stderr = None
- capture_file = ''
-
- for j in i:
- if j in ['>', '>>']:
- stdout = subprocess.PIPE
- continue
- elif j in ['&>', '&>>']:
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT
- continue
-
- if stdout is not None or stderr is not None:
- capture_file += j
- else:
- cmd += [j]
-
- try:
- os.makedirs(args.directory, exist_ok=True)
-
- res = subprocess.run(cmd, stdout=stdout, stderr=stderr, cwd=args.directory, check=True)
- if capture_file:
- out_file = Path(args.directory) / capture_file
- out_file.write_bytes(res.stdout)
- except subprocess.CalledProcessError:
- exit(1)
-
-if dummy_target:
- with open(dummy_target, 'a'):
- os.utime(dummy_target, None)
- exit(0)
-
-# Copy outputs
-zipped_outputs = zip(args.outputs, args.original_outputs)
-for expected, generated in zipped_outputs:
- do_copy = False
- if not os.path.exists(expected):
- if not os.path.exists(generated):
- print('Unable to find generated file. This can cause the build to fail:')
- print(generated)
- do_copy = False
- else:
- do_copy = True
- elif os.path.exists(generated):
- if os.path.getmtime(generated) > os.path.getmtime(expected):
- do_copy = True
-
- if do_copy:
- if os.path.exists(expected):
- os.remove(expected)
- shutil.copyfile(generated, expected)
diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py
index 349c8ec..d41cd22 100644
--- a/mesonbuild/cmake/executor.py
+++ b/mesonbuild/cmake/executor.py
@@ -28,6 +28,7 @@ import textwrap
from .. import mlog, mesonlib
from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice
from ..environment import Environment
+from ..envconfig import get_env_var
if T.TYPE_CHECKING:
from ..dependencies.base import ExternalProgram
@@ -48,6 +49,8 @@ class CMakeExecutor:
self.cmakebin, self.cmakevers = self.find_cmake_binary(self.environment, silent=silent)
self.always_capture_stderr = True
self.print_cmout = False
+ self.prefix_paths = [] # type: T.List[str]
+ self.extra_cmake_args = [] # type: T.List[str]
if self.cmakebin is False:
self.cmakebin = None
return
@@ -60,26 +63,23 @@ class CMakeExecutor:
self.cmakebin = None
return
+ self.prefix_paths = self.environment.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value
+ env_pref_path = get_env_var(
+ self.for_machine,
+ self.environment.is_cross_build(),
+ 'CMAKE_PREFIX_PATH')
+ if env_pref_path is not None:
+ env_pref_path = re.split(r':|;', env_pref_path)
+ env_pref_path = [x for x in env_pref_path if x] # Filter out empty strings
+ if not self.prefix_paths:
+ self.prefix_paths = []
+ self.prefix_paths += env_pref_path
+
+ if self.prefix_paths:
+ self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))]
+
def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple['ExternalProgram', str]:
- from ..dependencies.base import ExternalProgram
-
- # Create an iterator of options
- def search():
- # Lookup in cross or machine file.
- potential_cmakepath = environment.lookup_binary_entry(self.for_machine, 'cmake')
- if potential_cmakepath is not None:
- mlog.debug('CMake binary for %s specified from cross file, native file, or env var as %s.', self.for_machine, potential_cmakepath)
- yield ExternalProgram.from_entry('cmake', potential_cmakepath)
- # We never fallback if the user-specified option is no good, so
- # stop returning options.
- return
- mlog.debug('CMake binary missing from cross or native file, or env var undefined.')
- # Fallback on hard-coded defaults.
- # TODO prefix this for the cross case instead of ignoring thing.
- if environment.machines.matches_build_machine(self.for_machine):
- for potential_cmakepath in environment.default_cmake:
- mlog.debug('Trying a default CMake fallback at', potential_cmakepath)
- yield ExternalProgram(potential_cmakepath, silent=True)
+ from ..dependencies.base import find_external_program
# Only search for CMake the first time and store the result in the class
# definition
@@ -89,10 +89,11 @@ class CMakeExecutor:
mlog.debug('CMake binary for %s is cached.' % self.for_machine)
else:
assert CMakeExecutor.class_cmakebin[self.for_machine] is None
+
mlog.debug('CMake binary for %s is not cached' % self.for_machine)
- for potential_cmakebin in search():
- mlog.debug('Trying CMake binary {} for machine {} at {}'
- .format(potential_cmakebin.name, self.for_machine, potential_cmakebin.command))
+ for potential_cmakebin in find_external_program(
+ environment, self.for_machine, 'cmake', 'CMake',
+ environment.default_cmake, allow_default_for_cross=False):
version_if_ok = self.check_cmake(potential_cmakebin)
if not version_if_ok:
continue
@@ -132,7 +133,7 @@ class CMakeExecutor:
msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.'
mlog.warning(msg)
return None
- cmvers = re.sub(r'\s*cmake version\s*', '', out.split('\n')[0]).strip()
+ cmvers = re.search(r'(cmake|cmake3)\s*version\s*([\d.]+)', out).group(2)
return cmvers
def set_exec_mode(self, print_cmout: T.Optional[bool] = None, always_capture_stderr: T.Optional[bool] = None) -> None:
@@ -226,6 +227,7 @@ class CMakeExecutor:
if env is None:
env = os.environ
+ args = args + self.extra_cmake_args
if disable_cache:
return self._call_impl(args, build_dir, env)
@@ -362,5 +364,8 @@ class CMakeExecutor:
def get_command(self) -> T.List[str]:
return self.cmakebin.get_command()
+ def get_cmake_prefix_paths(self) -> T.List[str]:
+ return self.prefix_paths
+
def machine_choice(self) -> MachineChoice:
return self.for_machine
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py
index 6208696..91700c7 100644
--- a/mesonbuild/cmake/interpreter.py
+++ b/mesonbuild/cmake/interpreter.py
@@ -15,16 +15,15 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
-import pkg_resources
-
-from .common import CMakeException, CMakeTarget
+from .common import CMakeException, CMakeTarget, TargetOptions
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel
from .fileapi import CMakeFileAPI
from .executor import CMakeExecutor
from .traceparser import CMakeTraceParser, CMakeGeneratorTarget
-from .. import mlog
+from .. import mlog, mesonlib
from ..environment import Environment
from ..mesonlib import Language, MachineChoice, OrderedSet, version_compare
+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
@@ -289,7 +288,17 @@ class ConverterTarget:
for j in self.compile_opts[i]:
m = ConverterTarget.std_regex.match(j)
if m:
- self.override_options += ['{}_std={}'.format(i, m.group(2))]
+ std = m.group(2)
+ supported = self._all_lang_stds(i)
+ if std not in supported:
+ mlog.warning(
+ 'Unknown {0}_std "{1}" -> Ignoring. Try setting the project-'
+ 'level {0}_std if build errors occur. Known '
+ '{0}_stds are: {2}'.format(i, std, ' '.join(supported)),
+ once=True
+ )
+ continue
+ self.override_options += ['{}_std={}'.format(i, std)]
elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']:
self.pie = True
elif j in blacklist_compiler_flags:
@@ -307,13 +316,6 @@ class ConverterTarget:
tgt = trace.targets.get(self.cmake_name)
if tgt:
self.depends_raw = trace.targets[self.cmake_name].depends
- if self.type.upper() == 'INTERFACE_LIBRARY':
- props = tgt.properties
-
- self.includes += props.get('INTERFACE_INCLUDE_DIRECTORIES', [])
- self.public_compile_opts += props.get('INTERFACE_COMPILE_DEFINITIONS', [])
- self.public_compile_opts += props.get('INTERFACE_COMPILE_OPTIONS', [])
- self.link_flags += props.get('INTERFACE_LINK_OPTIONS', [])
# TODO refactor this copy paste from CMakeDependency for future releases
reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-l?pthread)$')
@@ -332,6 +334,12 @@ class ConverterTarget:
libraries = []
mlog.debug(tgt)
+ if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
+ self.includes += [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]
+
if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties:
self.public_compile_opts += ['-D' + re.sub('^-D', '', x) for x in tgt.properties['INTERFACE_COMPILE_DEFINITIONS'] if x]
@@ -346,8 +354,15 @@ class ConverterTarget:
cfgs += [x for x in tgt.properties['CONFIGURATIONS'] if x]
cfg = cfgs[0]
- if 'RELEASE' in cfgs:
- cfg = 'RELEASE'
+ is_debug = self.env.coredata.get_builtin_option('debug');
+ if is_debug:
+ if 'DEBUG' in cfgs:
+ cfg = 'DEBUG'
+ elif 'RELEASE' in cfgs:
+ cfg = 'RELEASE'
+ else:
+ if 'RELEASE' in cfgs:
+ cfg = 'RELEASE'
if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properties:
libraries += [x for x in tgt.properties['IMPORTED_IMPLIB_{}'.format(cfg)] if x]
@@ -539,6 +554,13 @@ class ConverterTarget:
suffixes += [x for x in exts]
return suffixes
+ @lru_cache(maxsize=None)
+ def _all_lang_stds(self, lang: str) -> T.List[str]:
+ lang_opts = self.env.coredata.compiler_options.build.get(lang, None)
+ if not lang_opts or 'std' not in lang_opts:
+ return []
+ return lang_opts['std'].choices
+
def process_inter_target_dependencies(self):
# Move the dependencies from all transfer_dependencies_from to the target
to_process = list(self.depends)
@@ -791,7 +813,7 @@ class CMakeInterpreter:
raise CMakeException('Unable to find CMake')
self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, permissive=True)
- preload_file = pkg_resources.resource_filename('mesonbuild', 'cmake/data/preload.cmake')
+ preload_file = mesondata['cmake/data/preload.cmake'].write_to_private(self.env)
# Prefere CMAKE_PROJECT_INCLUDE over CMAKE_TOOLCHAIN_FILE if possible,
# since CMAKE_PROJECT_INCLUDE was actually designed for code injection.
@@ -970,7 +992,7 @@ class CMakeInterpreter:
mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.')
- def pretend_to_be_meson(self) -> CodeBlockNode:
+ def pretend_to_be_meson(self, options: TargetOptions) -> CodeBlockNode:
if not self.project_name:
raise CMakeException('CMakeInterpreter was not analysed')
@@ -1036,9 +1058,6 @@ class CMakeInterpreter:
root_cb.lines += [function('project', [self.project_name] + self.languages)]
# Add the run script for custom commands
- run_script = pkg_resources.resource_filename('mesonbuild', 'cmake/data/run_ctgt.py')
- run_script_var = 'ctgt_run_script'
- root_cb.lines += [assign(run_script_var, function('find_program', [[run_script]], {'required': True}))]
# Add the targets
processing = []
@@ -1134,21 +1153,26 @@ class CMakeInterpreter:
dep_var = '{}_dep'.format(tgt.name)
tgt_var = tgt.name
+ install_tgt = options.get_install(tgt.cmake_name, tgt.install)
+
# Generate target kwargs
tgt_kwargs = {
- 'build_by_default': tgt.install,
- 'link_args': tgt.link_flags + tgt.link_libraries,
+ 'build_by_default': install_tgt,
+ 'link_args': options.get_link_args(tgt.cmake_name, tgt.link_flags + tgt.link_libraries),
'link_with': link_with,
'include_directories': id_node(inc_var),
- 'install': tgt.install,
- 'install_dir': tgt.install_dir,
- 'override_options': tgt.override_options,
+ 'install': install_tgt,
+ 'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options),
'objects': [method(x, 'extract_all_objects') for x in objec_libs],
}
+ # Only set if installed and only override if it is set
+ if install_tgt and tgt.install_dir:
+ tgt_kwargs['install_dir'] = tgt.install_dir
+
# Handle compiler args
for key, val in tgt.compile_opts.items():
- tgt_kwargs['{}_args'.format(key)] = val
+ tgt_kwargs['{}_args'.format(key)] = options.get_compile_args(tgt.cmake_name, key, val)
# Handle -fPCI, etc
if tgt_func == 'executable':
@@ -1220,7 +1244,8 @@ class CMakeInterpreter:
# Generate the command list
command = []
- command += [id_node(run_script_var)]
+ command += mesonlib.meson_command
+ command += ['--internal', 'cmake_run_ctgt']
command += ['-o', '@OUTPUT@']
if tgt.original_outputs:
command += ['-O'] + tgt.original_outputs
diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py
index 432cd21..a241360 100644
--- a/mesonbuild/cmake/traceparser.py
+++ b/mesonbuild/cmake/traceparser.py
@@ -64,6 +64,7 @@ class CMakeTarget:
return
for key, val in self.properties.items():
self.properties[key] = [x.strip() for x in val]
+ assert all([';' not in x for x in self.properties[key]])
class CMakeGeneratorTarget(CMakeTarget):
def __init__(self, name):
@@ -138,7 +139,7 @@ class CMakeTraceParser:
if not self.requires_stderr():
if not self.trace_file_path.exists and not self.trace_file_path.is_file():
raise CMakeException('CMake: Trace file "{}" not found'.format(str(self.trace_file_path)))
- trace = self.trace_file_path.read_text()
+ trace = self.trace_file_path.read_text(errors='ignore')
if not trace:
raise CMakeException('CMake: The CMake trace was not provided or is empty')
@@ -574,10 +575,10 @@ class CMakeTraceParser:
continue
if mode in ['INTERFACE', 'LINK_INTERFACE_LIBRARIES', 'PUBLIC', 'LINK_PUBLIC']:
- interface += [i]
+ interface += i.split(';')
if mode in ['PUBLIC', 'PRIVATE', 'LINK_PRIVATE']:
- private += [i]
+ private += i.split(';')
if paths:
interface = self._guess_files(interface)
@@ -655,30 +656,45 @@ class CMakeTraceParser:
# Try joining file paths that contain spaces
- reg_start = re.compile(r'^([A-Za-z]:)?/.*/[^./]+$')
+ reg_start = re.compile(r'^([A-Za-z]:)?/(.*/)*[^./]+$')
reg_end = re.compile(r'^.*\.[a-zA-Z]+$')
fixed_list = [] # type: T.List[str]
curr_str = None # type: T.Optional[str]
+ path_found = False # type: bool
for i in broken_list:
if curr_str is None:
curr_str = i
+ path_found = False
elif os.path.isfile(curr_str):
# Abort concatenation if curr_str is an existing file
fixed_list += [curr_str]
curr_str = i
+ path_found = False
elif not reg_start.match(curr_str):
# Abort concatenation if curr_str no longer matches the regex
fixed_list += [curr_str]
curr_str = i
- elif reg_end.match(i) or os.path.exists('{} {}'.format(curr_str, i)):
+ path_found = False
+ elif reg_end.match(i):
# File detected
curr_str = '{} {}'.format(curr_str, i)
fixed_list += [curr_str]
curr_str = None
+ path_found = False
+ elif os.path.exists('{} {}'.format(curr_str, i)):
+ # Path detected
+ curr_str = '{} {}'.format(curr_str, i)
+ path_found = True
+ elif path_found:
+ # Add path to fixed_list after ensuring the whole path is in curr_str
+ fixed_list += [curr_str]
+ curr_str = i
+ path_found = False
else:
curr_str = '{} {}'.format(curr_str, i)
+ path_found = False
if curr_str:
fixed_list += [curr_str]