diff options
Diffstat (limited to 'mesonbuild/cmake')
-rw-r--r-- | mesonbuild/cmake/__init__.py | 5 | ||||
-rw-r--r-- | mesonbuild/cmake/common.py | 95 | ||||
-rwxr-xr-x | mesonbuild/cmake/data/run_ctgt.py | 96 | ||||
-rw-r--r-- | mesonbuild/cmake/executor.py | 51 | ||||
-rw-r--r-- | mesonbuild/cmake/interpreter.py | 77 | ||||
-rw-r--r-- | mesonbuild/cmake/traceparser.py | 26 |
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] |