cmake: Move parsing the CMake trace into the CMake module

pull/5457/head
Daniel Mensinger 6 years ago
parent 6083cfa6c8
commit b1cf0fd380
No known key found for this signature in database
GPG Key ID: 54DD94C131E277D4
  1. 4
      mesonbuild/cmake/__init__.py
  2. 317
      mesonbuild/cmake/traceparser.py
  3. 342
      mesonbuild/dependencies/base.py
  4. 8
      mesonbuild/dependencies/dev.py

@ -20,9 +20,13 @@ __all__ = [
'CMakeExecutor', 'CMakeExecutor',
'CMakeException', 'CMakeException',
'CMakeInterpreter', 'CMakeInterpreter',
'CMakeTarget',
'CMakeTraceLine',
'CMakeTraceParser',
] ]
from .common import CMakeException from .common import CMakeException
from .client import CMakeClient from .client import CMakeClient
from .executor import CMakeExecutor from .executor import CMakeExecutor
from .interpreter import CMakeInterpreter from .interpreter import CMakeInterpreter
from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser

@ -0,0 +1,317 @@
# 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 .common import CMakeException
from typing import List, Tuple
import re
class CMakeTraceLine:
def __init__(self, file, line, func, args):
self.file = file
self.line = line
self.func = func.lower()
self.args = args
def __repr__(self):
s = 'CMake TRACE: {0}:{1} {2}({3})'
return s.format(self.file, self.line, self.func, self.args)
class CMakeTarget:
def __init__(self, name, target_type, properies=None):
if properies is None:
properies = {}
self.name = name
self.type = target_type
self.properies = properies
def __repr__(self):
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- properies: {{\n{} }}'
propSTR = ''
for i in self.properies:
propSTR += " '{}': {}\n".format(i, self.properies[i])
return s.format(self.name, self.type, propSTR)
class CMakeTraceParser:
def __init__(self):
# Dict of CMake variables: '<var_name>': ['list', 'of', 'values']
self.vars = {}
# Dict of CMakeTarget
self.targets = {}
def parse(self, trace: str) -> None:
# First parse the trace
lexer1 = self._lex_trace(trace)
# All supported functions
functions = {
'set': self._cmake_set,
'unset': self._cmake_unset,
'add_executable': self._cmake_add_executable,
'add_library': self._cmake_add_library,
'add_custom_target': self._cmake_add_custom_target,
'set_property': self._cmake_set_property,
'set_target_properties': self._cmake_set_target_properties
}
# Primary pass -- parse everything
for l in lexer1:
# "Execute" the CMake function if supported
fn = functions.get(l.func, None)
if(fn):
fn(l)
def get_first_cmake_var_of(self, var_list: List[str]) -> List[str]:
# Return the first found CMake variable in list var_list
for i in var_list:
if i in self.vars:
return self.vars[i]
return []
def get_cmake_var(self, var: str) -> List[str]:
# Return the value of the CMake variable var or an empty list if var does not exist
if var in self.vars:
return self.vars[var]
return []
def var_to_bool(self, var):
if var not in self.vars:
return False
if len(self.vars[var]) < 1:
return False
if self.vars[var][0].upper() in ['1', 'ON', 'TRUE']:
return True
return False
def _cmake_set(self, tline: CMakeTraceLine) -> None:
"""Handler for the CMake set() function in all variaties.
comes in three flavors:
set(<var> <value> [PARENT_SCOPE])
set(<var> <value> CACHE <type> <docstring> [FORCE])
set(ENV{<var>} <value>)
We don't support the ENV variant, and any uses of it will be ignored
silently. the other two variates are supported, with some caveats:
- we don't properly handle scoping, so calls to set() inside a
function without PARENT_SCOPE set could incorrectly shadow the
outer scope.
- We don't honor the type of CACHE arguments
"""
# DOC: https://cmake.org/cmake/help/latest/command/set.html
# 1st remove PARENT_SCOPE and CACHE from args
args = []
for i in tline.args:
if not i or i == 'PARENT_SCOPE':
continue
# Discard everything after the CACHE keyword
if i == 'CACHE':
break
args.append(i)
if len(args) < 1:
raise CMakeException('CMake: set() requires at least one argument\n{}'.format(tline))
# Now that we've removed extra arguments all that should be left is the
# variable identifier and the value, join the value back together to
# ensure spaces in the value are correctly handled. This assumes that
# variable names don't have spaces. Please don't do that...
identifier = args.pop(0)
value = ' '.join(args)
if not value:
# Same as unset
if identifier in self.vars:
del self.vars[identifier]
else:
self.vars[identifier] = value.split(';')
def _cmake_unset(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/unset.html
if len(tline.args) < 1:
raise CMakeException('CMake: unset() requires at least one argument\n{}'.format(tline))
if tline.args[0] in self.vars:
del self.vars[tline.args[0]]
def _cmake_add_executable(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_executable.html
args = list(tline.args) # Make a working copy
# Make sure the exe is imported
if 'IMPORTED' not in args:
raise CMakeException('CMake: add_executable() non imported executables are not supported\n{}'.format(tline))
args.remove('IMPORTED')
if len(args) < 1:
raise CMakeException('CMake: add_executable() requires at least 1 argument\n{}'.format(tline))
self.targets[args[0]] = CMakeTarget(args[0], 'EXECUTABLE', {})
def _cmake_add_library(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_library.html
args = list(tline.args) # Make a working copy
# Make sure the lib is imported
if 'IMPORTED' not in args:
raise CMakeException('CMake: add_library() non imported libraries are not supported\n{}'.format(tline))
args.remove('IMPORTED')
# No only look at the first two arguments (target_name and target_type) and ignore the rest
if len(args) < 2:
raise CMakeException('CMake: add_library() requires at least 2 arguments\n{}'.format(tline))
self.targets[args[0]] = CMakeTarget(args[0], args[1], {})
def _cmake_add_custom_target(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_target.html
# We only the first parameter (the target name) is interesting
if len(tline.args) < 1:
raise CMakeException('CMake: add_custom_target() requires at least one argument\n{}'.format(tline))
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {})
def _cmake_set_property(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/set_property.html
args = list(tline.args)
# We only care for TARGET properties
if args.pop(0) != 'TARGET':
return
append = False
targets = []
while args:
curr = args.pop(0)
# XXX: APPEND_STRING is specifically *not* supposed to create a
# list, is treating them as aliases really okay?
if curr == 'APPEND' or curr == 'APPEND_STRING':
append = True
continue
if curr == 'PROPERTY':
break
targets.append(curr)
if not args:
raise CMakeException('CMake: set_property() faild to parse argument list\n{}'.format(tline))
if len(args) == 1:
# Tries to set property to nothing so nothing has to be done
return
identifier = args.pop(0)
value = ' '.join(args).split(';')
if not value:
return
for i in targets:
if i not in self.targets:
raise CMakeException('CMake: set_property() TARGET {} not found\n{}'.format(i, tline))
if identifier not in self.targets[i].properies:
self.targets[i].properies[identifier] = []
if append:
self.targets[i].properies[identifier] += value
else:
self.targets[i].properies[identifier] = value
def _cmake_set_target_properties(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/set_target_properties.html
args = list(tline.args)
targets = []
while args:
curr = args.pop(0)
if curr == 'PROPERTIES':
break
targets.append(curr)
# Now we need to try to reconsitute the original quoted format of the
# arguments, as a property value could have spaces in it. Unlike
# set_property() this is not context free. There are two approaches I
# can think of, both have drawbacks:
#
# 1. Assume that the property will be capitalized, this is convention
# but cmake doesn't require it.
# 2. Maintain a copy of the list here: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties
#
# Neither of these is awesome for obvious reasons. I'm going to try
# option 1 first and fall back to 2, as 1 requires less code and less
# synchroniztion for cmake changes.
arglist = [] # type: List[Tuple[str, List[str]]]
name = args.pop(0)
values = []
for a in args:
if a.isupper():
if values:
arglist.append((name, ' '.join(values).split(';')))
name = a
values = []
else:
values.append(a)
if values:
arglist.append((name, ' '.join(values).split(';')))
for name, value in arglist:
for i in targets:
if i not in self.targets:
raise CMakeException('CMake: set_target_properties() TARGET {} not found\n{}'.format(i, tline))
self.targets[i].properies[name] = value
def _lex_trace(self, trace):
# The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n'
reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE)
reg_other = re.compile(r'[^\n]*\n')
reg_genexp = re.compile(r'\$<.*>')
loc = 0
while loc < len(trace):
mo_file_line = reg_tline.match(trace, loc)
if not mo_file_line:
skip_match = reg_other.match(trace, loc)
if not skip_match:
print(trace[loc:])
raise CMakeException('Failed to parse CMake trace')
loc = skip_match.end()
continue
loc = mo_file_line.end()
file = mo_file_line.group(1)
line = mo_file_line.group(3)
func = mo_file_line.group(4)
args = mo_file_line.group(5).split(' ')
args = list(map(lambda x: x.strip(), args))
args = list(map(lambda x: reg_genexp.sub('', x), args)) # Remove generator expressions
yield CMakeTraceLine(file, line, func, args)

@ -32,7 +32,7 @@ from .. import mlog
from .. import mesonlib from .. import mesonlib
from ..compilers import clib_langs from ..compilers import clib_langs
from ..environment import BinaryTable, Environment, MachineInfo from ..environment import BinaryTable, Environment, MachineInfo
from ..cmake import CMakeExecutor, CMakeException from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException
from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine
from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list
from ..mesonlib import Version, LibType from ..mesonlib import Version, LibType
@ -960,32 +960,6 @@ class PkgConfigDependency(ExternalDependency):
return default_value return default_value
raise DependencyException('Could not get pkg-config variable and no default provided for {!r}'.format(self)) raise DependencyException('Could not get pkg-config variable and no default provided for {!r}'.format(self))
class CMakeTraceLine:
def __init__(self, file, line, func, args):
self.file = file
self.line = line
self.func = func.lower()
self.args = args
def __repr__(self):
s = 'CMake TRACE: {0}:{1} {2}({3})'
return s.format(self.file, self.line, self.func, self.args)
class CMakeTarget:
def __init__(self, name, target_type, properies=None):
if properies is None:
properies = {}
self.name = name
self.type = target_type
self.properies = properies
def __repr__(self):
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- properies: {{\n{} }}'
propSTR = ''
for i in self.properies:
propSTR += " '{}': {}\n".format(i, self.properies[i])
return s.format(self.name, self.type, propSTR)
class CMakeDependency(ExternalDependency): class CMakeDependency(ExternalDependency):
# The class's copy of the CMake path. Avoids having to search for it # The class's copy of the CMake path. Avoids having to search for it
# multiple times in the same Meson invocation. # multiple times in the same Meson invocation.
@ -1025,12 +999,7 @@ class CMakeDependency(ExternalDependency):
# stored in the pickled coredata and recovered. # stored in the pickled coredata and recovered.
self.cmakebin = None self.cmakebin = None
self.cmakeinfo = None self.cmakeinfo = None
self.traceparser = CMakeTraceParser()
# Dict of CMake variables: '<var_name>': ['list', 'of', 'values']
self.vars = {}
# Dict of CMakeTarget
self.targets = {}
# Where all CMake "build dirs" are located # Where all CMake "build dirs" are located
self.cmake_root_dir = environment.scratch_dir self.cmake_root_dir = environment.scratch_dir
@ -1110,20 +1079,15 @@ class CMakeDependency(ExternalDependency):
return None return None
try: try:
# First parse the trace temp_parser = CMakeTraceParser()
lexer1 = self._lex_trace(err1) temp_parser.parse(err1)
# Primary pass -- parse all invocations of set
for l in lexer1:
if l.func == 'set':
self._cmake_set(l)
except MesonException: except MesonException:
return None return None
# Extract the variables and sanity check them # Extract the variables and sanity check them
module_paths = sorted(set(self.get_cmake_var('MESON_PATHS_LIST'))) module_paths = sorted(set(temp_parser.get_cmake_var('MESON_PATHS_LIST')))
module_paths = list(filter(lambda x: os.path.isdir(x), module_paths)) module_paths = list(filter(lambda x: os.path.isdir(x), module_paths))
archs = self.get_cmake_var('MESON_ARCH_LIST') archs = temp_parser.get_cmake_var('MESON_ARCH_LIST')
common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share'] common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share']
for i in archs: for i in archs:
@ -1131,7 +1095,7 @@ class CMakeDependency(ExternalDependency):
res = { res = {
'module_paths': module_paths, 'module_paths': module_paths,
'cmake_root': self.get_cmake_var('MESON_CMAKE_ROOT')[0], 'cmake_root': temp_parser.get_cmake_var('MESON_CMAKE_ROOT')[0],
'archs': archs, 'archs': archs,
'common_paths': common_paths 'common_paths': common_paths
} }
@ -1141,8 +1105,6 @@ class CMakeDependency(ExternalDependency):
mlog.debug(' -- CMake architectures: {}'.format(res['archs'])) mlog.debug(' -- CMake architectures: {}'.format(res['archs']))
mlog.debug(' -- CMake lib search paths: {}'.format(res['common_paths'])) mlog.debug(' -- CMake lib search paths: {}'.format(res['common_paths']))
# Reset variables
self.vars = {}
return res return res
@staticmethod @staticmethod
@ -1210,7 +1172,6 @@ class CMakeDependency(ExternalDependency):
if search_lib_dirs(i): if search_lib_dirs(i):
return True return True
# Check the system paths # Check the system paths
for i in self.cmakeinfo['module_paths']: for i in self.cmakeinfo['module_paths']:
if find_module(i): if find_module(i):
@ -1283,28 +1244,9 @@ class CMakeDependency(ExternalDependency):
return return
try: try:
# First parse the trace self.traceparser.parse(err1)
lexer1 = self._lex_trace(err1) except CMakeException as e:
e = self._gen_exception(str(e))
# All supported functions
functions = {
'set': self._cmake_set,
'unset': self._cmake_unset,
'add_executable': self._cmake_add_executable,
'add_library': self._cmake_add_library,
'add_custom_target': self._cmake_add_custom_target,
'set_property': self._cmake_set_property,
'set_target_properties': self._cmake_set_target_properties
}
# Primary pass -- parse everything
for l in lexer1:
# "Execute" the CMake function if supported
fn = functions.get(l.func, None)
if(fn):
fn(l)
except DependencyException as e:
if self.required: if self.required:
raise raise
else: else:
@ -1315,12 +1257,12 @@ class CMakeDependency(ExternalDependency):
return return
# Whether the package is found or not is always stored in PACKAGE_FOUND # Whether the package is found or not is always stored in PACKAGE_FOUND
self.is_found = self._var_to_bool('PACKAGE_FOUND') self.is_found = self.traceparser.var_to_bool('PACKAGE_FOUND')
if not self.is_found: if not self.is_found:
return return
# Try to detect the version # Try to detect the version
vers_raw = self.get_first_cmake_var_of(['PACKAGE_VERSION']) vers_raw = self.traceparser.get_cmake_var('PACKAGE_VERSION')
if len(vers_raw) > 0: if len(vers_raw) > 0:
self.version = vers_raw[0] self.version = vers_raw[0]
@ -1333,7 +1275,7 @@ class CMakeDependency(ExternalDependency):
# Try guessing a CMake target if none is provided # Try guessing a CMake target if none is provided
if len(modules) == 0: if len(modules) == 0:
for i in self.targets: for i in self.traceparser.targets:
tg = i.lower() tg = i.lower()
lname = name.lower() lname = name.lower()
if '{}::{}'.format(lname, lname) == tg or lname == tg.replace('::', ''): if '{}::{}'.format(lname, lname) == tg or lname == tg.replace('::', ''):
@ -1344,9 +1286,9 @@ class CMakeDependency(ExternalDependency):
# Failed to guess a target --> try the old-style method # Failed to guess a target --> try the old-style method
if len(modules) == 0: if len(modules) == 0:
incDirs = self.get_first_cmake_var_of(['PACKAGE_INCLUDE_DIRS']) incDirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS')
defs = self.get_first_cmake_var_of(['PACKAGE_DEFINITIONS']) defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS')
libs = self.get_first_cmake_var_of(['PACKAGE_LIBRARIES']) libs = self.traceparser.get_cmake_var('PACKAGE_LIBRARIES')
# Try to use old style variables if no module is specified # Try to use old style variables if no module is specified
if len(libs) > 0: if len(libs) > 0:
@ -1359,7 +1301,7 @@ class CMakeDependency(ExternalDependency):
self.is_found = False self.is_found = False
raise self._gen_exception('CMake: failed to guess a CMake target for {}.\n' raise self._gen_exception('CMake: failed to guess a CMake target for {}.\n'
'Try to explicitly specify one or more targets with the "modules" property.\n' 'Try to explicitly specify one or more targets with the "modules" property.\n'
'Valid targets are:\n{}'.format(name, list(self.targets.keys()))) 'Valid targets are:\n{}'.format(name, list(self.traceparser.targets.keys())))
# Set dependencies with CMake targets # Set dependencies with CMake targets
processed_targets = [] processed_targets = []
@ -1368,13 +1310,13 @@ class CMakeDependency(ExternalDependency):
compileOptions = [] compileOptions = []
libraries = [] libraries = []
for i, required in modules: for i, required in modules:
if i not in self.targets: if i not in self.traceparser.targets:
if not required: if not required:
mlog.warning('CMake: Optional module', mlog.bold(self._original_module_name(i)), 'for', mlog.bold(name), 'was not found') mlog.warning('CMake: Optional module', mlog.bold(self._original_module_name(i)), 'for', mlog.bold(name), 'was not found')
continue continue
raise self._gen_exception('CMake: invalid module {} for {}.\n' raise self._gen_exception('CMake: invalid module {} for {}.\n'
'Try to explicitly specify one or more targets with the "modules" property.\n' 'Try to explicitly specify one or more targets with the "modules" property.\n'
'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.targets.keys()))) 'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.traceparser.targets.keys())))
targets = [i] targets = [i]
if not autodetected_module_list: if not autodetected_module_list:
@ -1387,7 +1329,7 @@ class CMakeDependency(ExternalDependency):
if curr in processed_targets: if curr in processed_targets:
continue continue
tgt = self.targets[curr] tgt = self.traceparser.targets[curr]
cfgs = [] cfgs = []
cfg = '' cfg = ''
otherDeps = [] otherDeps = []
@ -1425,7 +1367,7 @@ class CMakeDependency(ExternalDependency):
otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES'] otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES']
for j in otherDeps: for j in otherDeps:
if j in self.targets: if j in self.traceparser.targets:
targets += [j] targets += [j]
processed_targets += [curr] processed_targets += [curr]
@ -1444,246 +1386,6 @@ class CMakeDependency(ExternalDependency):
self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs)) self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs))
self.link_args = libraries self.link_args = libraries
def get_first_cmake_var_of(self, var_list: List[str]) -> List[str]:
# Return the first found CMake variable in list var_list
for i in var_list:
if i in self.vars:
return self.vars[i]
return []
def get_cmake_var(self, var: str) -> List[str]:
# Return the value of the CMake variable var or an empty list if var does not exist
if var in self.vars:
return self.vars[var]
return []
def _var_to_bool(self, var):
if var not in self.vars:
return False
if len(self.vars[var]) < 1:
return False
if self.vars[var][0].upper() in ['1', 'ON', 'TRUE']:
return True
return False
def _cmake_set(self, tline: CMakeTraceLine) -> None:
"""Handler for the CMake set() function in all variaties.
comes in three flavors:
set(<var> <value> [PARENT_SCOPE])
set(<var> <value> CACHE <type> <docstring> [FORCE])
set(ENV{<var>} <value>)
We don't support the ENV variant, and any uses of it will be ignored
silently. the other two variates are supported, with some caveats:
- we don't properly handle scoping, so calls to set() inside a
function without PARENT_SCOPE set could incorrectly shadow the
outer scope.
- We don't honor the type of CACHE arguments
"""
# DOC: https://cmake.org/cmake/help/latest/command/set.html
# 1st remove PARENT_SCOPE and CACHE from args
args = []
for i in tline.args:
if not i or i == 'PARENT_SCOPE':
continue
# Discard everything after the CACHE keyword
if i == 'CACHE':
break
args.append(i)
if len(args) < 1:
raise self._gen_exception('CMake: set() requires at least one argument\n{}'.format(tline))
# Now that we've removed extra arguments all that should be left is the
# variable identifier and the value, join the value back together to
# ensure spaces in the value are correctly handled. This assumes that
# variable names don't have spaces. Please don't do that...
identifier = args.pop(0)
value = ' '.join(args)
if not value:
# Same as unset
if identifier in self.vars:
del self.vars[identifier]
else:
self.vars[identifier] = value.split(';')
def _cmake_unset(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/unset.html
if len(tline.args) < 1:
raise self._gen_exception('CMake: unset() requires at least one argument\n{}'.format(tline))
if tline.args[0] in self.vars:
del self.vars[tline.args[0]]
def _cmake_add_executable(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_executable.html
args = list(tline.args) # Make a working copy
# Make sure the exe is imported
if 'IMPORTED' not in args:
raise self._gen_exception('CMake: add_executable() non imported executables are not supported\n{}'.format(tline))
args.remove('IMPORTED')
if len(args) < 1:
raise self._gen_exception('CMake: add_executable() requires at least 1 argument\n{}'.format(tline))
self.targets[args[0]] = CMakeTarget(args[0], 'EXECUTABLE', {})
def _cmake_add_library(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_library.html
args = list(tline.args) # Make a working copy
# Make sure the lib is imported
if 'IMPORTED' not in args:
raise self._gen_exception('CMake: add_library() non imported libraries are not supported\n{}'.format(tline))
args.remove('IMPORTED')
# No only look at the first two arguments (target_name and target_type) and ignore the rest
if len(args) < 2:
raise self._gen_exception('CMake: add_library() requires at least 2 arguments\n{}'.format(tline))
self.targets[args[0]] = CMakeTarget(args[0], args[1], {})
def _cmake_add_custom_target(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_target.html
# We only the first parameter (the target name) is interesting
if len(tline.args) < 1:
raise self._gen_exception('CMake: add_custom_target() requires at least one argument\n{}'.format(tline))
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {})
def _cmake_set_property(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/set_property.html
args = list(tline.args)
# We only care for TARGET properties
if args.pop(0) != 'TARGET':
return
append = False
targets = []
while args:
curr = args.pop(0)
# XXX: APPEND_STRING is specifically *not* supposed to create a
# list, is treating them as aliases really okay?
if curr == 'APPEND' or curr == 'APPEND_STRING':
append = True
continue
if curr == 'PROPERTY':
break
targets.append(curr)
if not args:
raise self._gen_exception('CMake: set_property() faild to parse argument list\n{}'.format(tline))
if len(args) == 1:
# Tries to set property to nothing so nothing has to be done
return
identifier = args.pop(0)
value = ' '.join(args).split(';')
if not value:
return
for i in targets:
if i not in self.targets:
raise self._gen_exception('CMake: set_property() TARGET {} not found\n{}'.format(i, tline))
if identifier not in self.targets[i].properies:
self.targets[i].properies[identifier] = []
if append:
self.targets[i].properies[identifier] += value
else:
self.targets[i].properies[identifier] = value
def _cmake_set_target_properties(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/set_target_properties.html
args = list(tline.args)
targets = []
while args:
curr = args.pop(0)
if curr == 'PROPERTIES':
break
targets.append(curr)
# Now we need to try to reconsitute the original quoted format of the
# arguments, as a property value could have spaces in it. Unlike
# set_property() this is not context free. There are two approaches I
# can think of, both have drawbacks:
#
# 1. Assume that the property will be capitalized, this is convention
# but cmake doesn't require it.
# 2. Maintain a copy of the list here: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties
#
# Neither of these is awesome for obvious reasons. I'm going to try
# option 1 first and fall back to 2, as 1 requires less code and less
# synchroniztion for cmake changes.
arglist = [] # type: typing.List[typing.Tuple[str, typing.List[str]]]
name = args.pop(0)
values = []
for a in args:
if a.isupper():
if values:
arglist.append((name, ' '.join(values).split(';')))
name = a
values = []
else:
values.append(a)
if values:
arglist.append((name, ' '.join(values).split(';')))
for name, value in arglist:
for i in targets:
if i not in self.targets:
raise self._gen_exception('CMake: set_target_properties() TARGET {} not found\n{}'.format(i, tline))
self.targets[i].properies[name] = value
def _lex_trace(self, trace):
# The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n'
reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE)
reg_other = re.compile(r'[^\n]*\n')
reg_genexp = re.compile(r'\$<.*>')
loc = 0
while loc < len(trace):
mo_file_line = reg_tline.match(trace, loc)
if not mo_file_line:
skip_match = reg_other.match(trace, loc)
if not skip_match:
print(trace[loc:])
raise self._gen_exception('Failed to parse CMake trace')
loc = skip_match.end()
continue
loc = mo_file_line.end()
file = mo_file_line.group(1)
line = mo_file_line.group(3)
func = mo_file_line.group(4)
args = mo_file_line.group(5).split(' ')
args = list(map(lambda x: x.strip(), args))
args = list(map(lambda x: reg_genexp.sub('', x), args)) # Remove generator expressions
yield CMakeTraceLine(file, line, func, args)
def _setup_cmake_dir(self, cmake_file: str) -> str: def _setup_cmake_dir(self, cmake_file: str) -> str:
# Setup the CMake build environment and return the "build" directory # Setup the CMake build environment and return the "build" directory
build_dir = '{}/cmake_{}'.format(self.cmake_root_dir, self.name) build_dir = '{}/cmake_{}'.format(self.cmake_root_dir, self.name)
@ -1722,7 +1424,7 @@ class CMakeDependency(ExternalDependency):
pkgconfig_define: typing.Optional[typing.List[str]] = None) -> typing.Union[str, typing.List[str]]: pkgconfig_define: typing.Optional[typing.List[str]] = None) -> typing.Union[str, typing.List[str]]:
if cmake: if cmake:
try: try:
v = self.vars[cmake] v = self.traceparser.vars[cmake]
except KeyError: except KeyError:
pass pass
else: else:

@ -418,8 +418,8 @@ class LLVMDependencyCMake(CMakeDependency):
super().__init__(name='LLVM', environment=env, language='cpp', kwargs=kwargs) super().__init__(name='LLVM', environment=env, language='cpp', kwargs=kwargs)
# Extract extra include directories and definitions # Extract extra include directories and definitions
inc_dirs = self.get_cmake_var('PACKAGE_INCLUDE_DIRS') inc_dirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS')
defs = self.get_cmake_var('PACKAGE_DEFINITIONS') defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS')
temp = ['-I' + x for x in inc_dirs] + defs temp = ['-I' + x for x in inc_dirs] + defs
self.compile_args += [x for x in temp if x not in self.compile_args] self.compile_args += [x for x in temp if x not in self.compile_args]
self._add_sub_dependency(ThreadDependency, env, kwargs) self._add_sub_dependency(ThreadDependency, env, kwargs)
@ -434,7 +434,7 @@ class LLVMDependencyCMake(CMakeDependency):
def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]: def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]:
res = [] res = []
for mod, required in modules: for mod, required in modules:
cm_targets = self.get_cmake_var('MESON_LLVM_TARGETS_{}'.format(mod)) cm_targets = self.traceparser.get_cmake_var('MESON_LLVM_TARGETS_{}'.format(mod))
if not cm_targets: if not cm_targets:
if required: if required:
raise self._gen_exception('LLVM module {} was not found'.format(mod)) raise self._gen_exception('LLVM module {} was not found'.format(mod))
@ -446,7 +446,7 @@ class LLVMDependencyCMake(CMakeDependency):
return res return res
def _original_module_name(self, module: str) -> str: def _original_module_name(self, module: str) -> str:
orig_name = self.get_cmake_var('MESON_TARGET_TO_LLVM_{}'.format(module)) orig_name = self.traceparser.get_cmake_var('MESON_TARGET_TO_LLVM_{}'.format(module))
if orig_name: if orig_name:
return orig_name[0] return orig_name[0]
return module return module

Loading…
Cancel
Save