Merge pull request #6074 from mensinda/cmFileAPI

cmake: Add CMake file API support
pull/6088/head
Jussi Pakkanen 5 years ago committed by GitHub
commit aece7ecded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      mesonbuild/cmake/__init__.py
  2. 143
      mesonbuild/cmake/client.py
  3. 143
      mesonbuild/cmake/common.py
  4. 318
      mesonbuild/cmake/fileapi.py
  5. 57
      mesonbuild/cmake/interpreter.py
  6. 18
      mesonbuild/cmake/traceparser.py
  7. 33
      run_project_tests.py

@ -19,6 +19,7 @@ __all__ = [
'CMakeClient',
'CMakeExecutor',
'CMakeException',
'CMakeFileAPI',
'CMakeInterpreter',
'CMakeTarget',
'CMakeTraceLine',
@ -29,6 +30,7 @@ __all__ = [
from .common import CMakeException
from .client import CMakeClient
from .executor import CMakeExecutor
from .fileapi import CMakeFileAPI
from .generator import parse_generator_expressions
from .interpreter import CMakeInterpreter
from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser

@ -15,7 +15,7 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from .common import CMakeException
from .common import CMakeException, CMakeConfiguration, CMakeBuildFile
from .executor import CMakeExecutor
from ..environment import Environment
from ..mesonlib import MachineChoice
@ -186,15 +186,6 @@ class ReplyCompute(ReplyBase):
def __init__(self, cookie: str):
super().__init__(cookie, 'compute')
class CMakeBuildFile:
def __init__(self, file: str, is_cmake: bool, is_temp: bool):
self.file = file
self.is_cmake = is_cmake
self.is_temp = is_temp
def __repr__(self):
return '<{}: {}; cmake={}; temp={}>'.format(self.__class__.__name__, self.file, self.is_cmake, self.is_temp)
class ReplyCMakeInputs(ReplyBase):
def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: List[CMakeBuildFile]):
super().__init__(cookie, 'cmakeInputs')
@ -210,138 +201,6 @@ class ReplyCMakeInputs(ReplyBase):
for i in self.build_files:
mlog.log(str(i))
def _flags_to_list(raw: str) -> 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
class CMakeFileGroup:
def __init__(self, data: dict):
self.defines = data.get('defines', '')
self.flags = _flags_to_list(data.get('compileFlags', ''))
self.includes = data.get('includePath', [])
self.is_generated = data.get('isGenerated', False)
self.language = data.get('language', 'C')
self.sources = data.get('sources', [])
# Fix the include directories
tmp = []
for i in self.includes:
if isinstance(i, dict) and 'path' in i:
tmp += [i['path']]
elif isinstance(i, str):
tmp += [i]
self.includes = tmp
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(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)
class CMakeTarget:
def __init__(self, data: dict):
self.artifacts = data.get('artifacts', [])
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.full_name = data.get('fullName', '')
self.install = data.get('hasInstallRule', False)
self.install_paths = list(set(data.get('installPaths', [])))
self.link_lang = data.get('linkerLanguage', '')
self.link_libraries = _flags_to_list(data.get('linkLibraries', ''))
self.link_flags = _flags_to_list(data.get('linkFlags', ''))
self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', ''))
self.link_path = data.get('linkPath', '')
self.type = data.get('type', 'EXECUTABLE')
self.is_generator_provided = data.get('isGeneratorProvided', False)
self.files = []
for i in data.get('fileGroups', []):
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('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('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('Files {}:'.format(idx))
with mlog.nested():
i.log()
class CMakeProject:
def __init__(self, data: dict):
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.targets = []
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('name =', mlog.bold(self.name))
for idx, i in enumerate(self.targets):
mlog.log('Target {}:'.format(idx))
with mlog.nested():
i.log()
class CMakeConfiguration:
def __init__(self, data: dict):
self.name = data.get('name', '')
self.projects = []
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('Project {}:'.format(idx))
with mlog.nested():
i.log()
class ReplyCodeModel(ReplyBase):
def __init__(self, data: dict):
super().__init__(data['cookie'], 'codemodel')

@ -16,6 +16,149 @@
# or an interpreter-based tool.
from ..mesonlib import MesonException
from .. import mlog
from typing import List
class CMakeException(MesonException):
pass
class CMakeBuildFile:
def __init__(self, file: str, is_cmake: bool, is_temp: bool):
self.file = file
self.is_cmake = is_cmake
self.is_temp = is_temp
def __repr__(self):
return '<{}: {}; cmake={}; temp={}>'.format(self.__class__.__name__, self.file, self.is_cmake, self.is_temp)
def _flags_to_list(raw: str) -> 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
class CMakeFileGroup:
def __init__(self, data: dict):
self.defines = data.get('defines', '')
self.flags = _flags_to_list(data.get('compileFlags', ''))
self.includes = data.get('includePath', [])
self.is_generated = data.get('isGenerated', False)
self.language = data.get('language', 'C')
self.sources = data.get('sources', [])
# Fix the include directories
tmp = []
for i in self.includes:
if isinstance(i, dict) and 'path' in i:
tmp += [i['path']]
elif isinstance(i, str):
tmp += [i]
self.includes = tmp
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(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)
class CMakeTarget:
def __init__(self, data: dict):
self.artifacts = data.get('artifacts', [])
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.full_name = data.get('fullName', '')
self.install = data.get('hasInstallRule', False)
self.install_paths = list(set(data.get('installPaths', [])))
self.link_lang = data.get('linkerLanguage', '')
self.link_libraries = _flags_to_list(data.get('linkLibraries', ''))
self.link_flags = _flags_to_list(data.get('linkFlags', ''))
self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', ''))
# self.link_path = data.get('linkPath', '')
self.type = data.get('type', 'EXECUTABLE')
# self.is_generator_provided = data.get('isGeneratorProvided', False)
self.files = []
for i in data.get('fileGroups', []):
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('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('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('Files {}:'.format(idx))
with mlog.nested():
i.log()
class CMakeProject:
def __init__(self, data: dict):
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.targets = []
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('name =', mlog.bold(self.name))
for idx, i in enumerate(self.targets):
mlog.log('Target {}:'.format(idx))
with mlog.nested():
i.log()
class CMakeConfiguration:
def __init__(self, data: dict):
self.name = data.get('name', '')
self.projects = []
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('Project {}:'.format(idx))
with mlog.nested():
i.log()

@ -0,0 +1,318 @@
# 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.
from .common import CMakeException, CMakeBuildFile, CMakeConfiguration
from typing import Any, List, Tuple
import os
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 = []
self.cmake_configurations = []
self.kind_resolver_map = {
'codemodel': self._parse_codemodel,
'cmakeFiles': self._parse_cmakeFiles,
}
def get_cmake_sources(self) -> List[CMakeBuildFile]:
return self.cmake_sources
def get_cmake_configurations(self) -> List[CMakeConfiguration]:
return self.cmake_configurations
def setup_request(self) -> None:
os.makedirs(self.request_dir, exist_ok=True)
query = {
'requests': [
{'kind': 'codemodel', 'version': {'major': 2, 'minor': 0}},
{'kind': 'cmakeFiles', 'version': {'major': 1, 'minor': 0}},
]
}
with open(os.path.join(self.request_dir, 'query.json'), 'w') as fp:
json.dump(query, fp, indent=2)
def load_reply(self) -> None:
if not os.path.isdir(self.reply_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):
root = i
break
if not root:
raise CMakeException('Failed to find the CMake file API index')
index = self._reply_file_content(root) # Load the root index
index = self._strip_data(index) # Avoid loading duplicate files
index = self._resolve_references(index) # Load everything
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)
# parse the JSON
for i in index['objects']:
assert(isinstance(i, dict))
assert('kind' in i)
assert(i['kind'] in self.kind_resolver_map)
self.kind_resolver_map[i['kind']](i)
def _parse_codemodel(self, data: dict) -> None:
assert('configurations' in data)
assert('paths' in data)
source_dir = data['paths']['source']
build_dir = data['paths']['build']
# The file API output differs quite a bit from the server
# output. It is more flat than the server output and makes
# heavy use of references. Here these references are
# resolved and the resulting data structure is identical
# to the CMake serve output.
def helper_parse_dir(dir_entry: dict) -> 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)
return src_dir, bld_dir
def parse_sources(comp_group: dict, tgt: dict) -> Tuple[List[str], List[str], List[int]]:
gen = []
src = []
idx = []
src_list_raw = tgt.get('sources', [])
for i in comp_group.get('sourceIndexes', []):
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']]
else:
src += [src_list_raw[i]['path']]
idx += [i]
return src, gen, idx
def parse_target(tgt: dict) -> dict:
src_dir, bld_dir = helper_parse_dir(cnf.get('paths', {}))
# 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']]
install_paths = list(set(install_paths))
# On the first look, it looks really nice that the CMake devs have
# decided to use arrays for the linker flags. However, this feeling
# soon turns into despair when you realize that there only one entry
# per type in most cases, and we still have to do manual string splitting.
link_flags = []
link_libs = []
for i in tgt.get('link', {}).get('commandFragments', []):
if i['role'] == 'flags':
link_flags += [i['fragment']]
elif i['role'] == 'libraries':
link_libs += [i['fragment']]
elif i['role'] == 'libraryPath':
link_flags += ['-L{}'.format(i['fragment'])]
elif i['role'] == 'frameworkPath':
link_flags += ['-F{}'.format(i['fragment'])]
for i in tgt.get('archive', {}).get('commandFragments', []):
if i['role'] == 'flags':
link_flags += [i['fragment']]
# TODO The `dependencies` entry is new in the file API.
# maybe we can make use of that in addtion to the
# implicit dependency detection
tgt_data = {
'artifacts': [x.get('path', '') for x in tgt.get('artifacts', [])],
'sourceDirectory': src_dir,
'buildDirectory': bld_dir,
'name': tgt.get('name', ''),
'fullName': tgt.get('nameOnDisk', ''),
'hasInstallRule': 'install' in tgt,
'installPaths': install_paths,
'linkerLanguage': tgt.get('link', {}).get('language', 'CXX'),
'linkLibraries': ' '.join(link_libs), # See previous comment block why we join the array
'linkFlags': ' '.join(link_flags), # See previous comment block why we join the array
'type': tgt.get('type', 'EXECUTABLE'),
'fileGroups': [],
}
processed_src_idx = []
for cg in tgt.get('compileGroups', []):
# Again, why an array, when there is usually only one element
# and arguments are seperated with spaces...
flags = []
for i in cg.get('compileCommandFragments', []):
flags += [i['fragment']]
cg_data = {
'defines': [x.get('define', '') for x in cg.get('defines', [])],
'compileFlags': ' '.join(flags),
'language': cg.get('language', 'C'),
'isGenerated': None, # Set later, flag is stored per source file
'sources': [],
# TODO handle isSystem
'includePath': [x.get('path', '') for x in cg.get('includes', [])],
}
normal_src, generated_src, src_idx = parse_sources(cg, tgt)
if normal_src:
cg_data = dict(cg_data)
cg_data['isGenerated'] = False
cg_data['sources'] = normal_src
tgt_data['fileGroups'] += [cg_data]
if generated_src:
cg_data = dict(cg_data)
cg_data['isGenerated'] = True
cg_data['sources'] = generated_src
tgt_data['fileGroups'] += [cg_data]
processed_src_idx += src_idx
# Object libraries have no compile groups, only source groups.
# So we add all the source files to a dummy source group that were
# not found in the previous loop
normal_src = []
generated_src = []
for idx, src in enumerate(tgt.get('sources', [])):
if idx in processed_src_idx:
continue
if src.get('isGenerated', False):
generated_src += [src['path']]
else:
normal_src += [src['path']]
if normal_src:
tgt_data['fileGroups'] += [{
'isGenerated': False,
'sources': normal_src,
}]
if generated_src:
tgt_data['fileGroups'] += [{
'isGenerated': True,
'sources': generated_src,
}]
return tgt_data
def parse_project(pro: dict) -> dict:
# Only look at the first directory specified in directoryIndexes
# TODO Figure out what the other indexes are there for
p_src_dir = source_dir
p_bld_dir = build_dir
try:
p_src_dir, p_bld_dir = helper_parse_dir(cnf['directories'][pro['directoryIndexes'][0]])
except (IndexError, KeyError):
pass
pro_data = {
'name': pro.get('name', ''),
'sourceDirectory': p_src_dir,
'buildDirectory': p_bld_dir,
'targets': [],
}
for ref in pro.get('targetIndexes', []):
tgt = {}
try:
tgt = cnf['targets'][ref]
except (IndexError, KeyError):
pass
pro_data['targets'] += [parse_target(tgt)]
return pro_data
for cnf in data.get('configurations', []):
cnf_data = {
'name': cnf.get('name', ''),
'projects': [],
}
for pro in cnf.get('projects', []):
cnf_data['projects'] += [parse_project(pro)]
self.cmake_configurations += [CMakeConfiguration(cnf_data)]
def _parse_cmakeFiles(self, data: dict) -> None:
assert('inputs' in data)
assert('paths' in data)
src_dir = 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)
self.cmake_sources += [CMakeBuildFile(path, i.get('isCMake', False), i.get('isGenerated', False))]
def _strip_data(self, data: Any) -> Any:
if isinstance(data, list):
for idx, i in enumerate(data):
data[idx] = self._strip_data(i)
elif isinstance(data, dict):
new = {}
for key, val in data.items():
if key not in STRIP_KEYS:
new[key] = self._strip_data(val)
data = new
return data
def _resolve_references(self, data: Any) -> Any:
if isinstance(data, list):
for idx, i in enumerate(data):
data[idx] = self._resolve_references(i)
elif isinstance(data, dict):
# Check for the "magic" reference entry and insert
# it into the root data dict
if 'jsonFile' in data:
data.update(self._reply_file_content(data['jsonFile']))
for key, val in data.items():
data[key] = self._resolve_references(val)
return data
def _reply_file_content(self, filename: str) -> dict:
real_path = os.path.join(self.reply_dir, filename)
if not os.path.exists(real_path):
raise CMakeException('File "{}" does not exist'.format(real_path))
with open(real_path, 'r') as fp:
return json.load(fp)

@ -15,17 +15,19 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from .common import CMakeException
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, CMakeTarget
from .common import CMakeException, CMakeTarget
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 ..environment import Environment
from ..mesonlib import MachineChoice
from ..mesonlib import MachineChoice, version_compare
from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header
from subprocess import Popen, PIPE
from typing import Any, List, Dict, Optional, TYPE_CHECKING
from threading import Thread
from enum import Enum
import os, re
from ..mparser import (
@ -458,6 +460,10 @@ class ConverterCustomTarget:
mlog.log(' -- inputs: ', mlog.bold(str(self.inputs)))
mlog.log(' -- depends: ', mlog.bold(str(self.depends)))
class CMakeAPI(Enum):
SERVER = 1
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'))
@ -469,11 +475,13 @@ class CMakeInterpreter:
self.install_prefix = install_prefix
self.env = env
self.backend_name = backend.name
self.cmake_api = CMakeAPI.SERVER
self.client = CMakeClient(self.env)
self.fileapi = CMakeFileAPI(self.build_dir)
# Raw CMake results
self.bs_files = []
self.codemodel = None
self.codemodel_configs = None
self.raw_trace = None
# Analysed data
@ -496,6 +504,10 @@ class CMakeInterpreter:
generator = backend_generator_map[self.backend_name]
cmake_args = cmake_exe.get_command()
if version_compare(cmake_exe.version(), '>=3.14'):
self.cmake_api = CMakeAPI.FILE
self.fileapi.setup_request()
# Map meson compiler to CMake variables
for lang, comp in self.env.coredata.compilers[for_machine].items():
if lang not in language_map:
@ -552,8 +564,24 @@ class CMakeInterpreter:
def initialise(self, extra_cmake_options: List[str]) -> None:
# Run configure the old way becuse doing it
# with the server doesn't work for some reason
# Aditionally, the File API requires a configure anyway
self.configure(extra_cmake_options)
# Continue with the file API If supported
if self.cmake_api is CMakeAPI.FILE:
# Parse the result
self.fileapi.load_reply()
# 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 = list(set(self.bs_files))
# Load the codemodel configurations
self.codemodel_configs = self.fileapi.get_cmake_configurations()
return
with self.client.connect():
generator = backend_generator_map[self.backend_name]
self.client.do_handshake(self.src_dir, self.build_dir, generator, 1)
@ -574,10 +602,10 @@ class CMakeInterpreter:
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 = list(set(self.bs_files))
self.codemodel = cm_reply
self.codemodel_configs = cm_reply.configs
def analyse(self) -> None:
if self.codemodel is None:
if self.codemodel_configs is None:
raise CMakeException('CMakeInterpreter was not initialized')
# Clear analyser data
@ -591,7 +619,7 @@ class CMakeInterpreter:
self.trace.parse(self.raw_trace)
# Find all targets
for i in self.codemodel.configs:
for i in self.codemodel_configs:
for j in i.projects:
if not self.project_name:
self.project_name = j.name
@ -599,6 +627,21 @@ class CMakeInterpreter:
if k.type not in skip_targets:
self.targets += [ConverterTarget(k, self.env)]
# Add interface targets from trace, if not already present.
# This step is required because interface targets were removed from
# the CMake file API output.
api_target_name_list = [x.name for x in self.targets]
for i in self.trace.targets.values():
if i.type != 'INTERFACE' or i.name in api_target_name_list or i.imported:
continue
dummy = CMakeTarget({
'name': i.name,
'type': 'INTERFACE_LIBRARY',
'sourceDirectory': self.src_dir,
'buildDirectory': self.build_dir,
})
self.targets += [ConverterTarget(dummy, self.env)]
for i in self.trace.custom_targets:
self.custom_targets += [ConverterCustomTarget(i)]

@ -35,19 +35,21 @@ class CMakeTraceLine:
return s.format(self.file, self.line, self.func, self.args)
class CMakeTarget:
def __init__(self, name, target_type, properties=None):
def __init__(self, name, target_type, properties=None, imported: bool = False, tline: Optional[CMakeTraceLine] = None):
if properties is None:
properties = {}
self.name = name
self.type = target_type
self.properties = properties
self.imported = imported
self.tline = tline
def __repr__(self):
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- properties: {{\n{} }}'
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- imported: {}\n -- properties: {{\n{} }}\n -- tline: {}'
propSTR = ''
for i in self.properties:
propSTR += " '{}': {}\n".format(i, self.properties[i])
return s.format(self.name, self.type, propSTR)
return s.format(self.name, self.type, self.imported, propSTR, self.tline)
class CMakeGeneratorTarget:
def __init__(self):
@ -210,7 +212,7 @@ class CMakeTraceParser:
if len(args) < 1:
return self._gen_exception('add_library', 'interface library name not specified', tline)
self.targets[args[0]] = CMakeTarget(args[0], 'INTERFACE', {})
self.targets[args[0]] = CMakeTarget(args[0], 'INTERFACE', {}, tline=tline, imported='IMPORTED' in args)
elif 'IMPORTED' in args:
args.remove('IMPORTED')
@ -218,7 +220,7 @@ class CMakeTraceParser:
if len(args) < 2:
return self._gen_exception('add_library', 'requires at least 2 arguments', tline)
self.targets[args[0]] = CMakeTarget(args[0], args[1], {})
self.targets[args[0]] = CMakeTarget(args[0], args[1], {}, tline=tline, imported=True)
elif 'ALIAS' in args:
args.remove('ALIAS')
@ -227,11 +229,11 @@ class CMakeTraceParser:
return self._gen_exception('add_library', 'requires at least 2 arguments', tline)
# Simulate the ALIAS with INTERFACE_LINK_LIBRARIES
self.targets[args[0]] = CMakeTarget(args[0], 'ALIAS', {'INTERFACE_LINK_LIBRARIES': [args[1]]})
self.targets[args[0]] = CMakeTarget(args[0], 'ALIAS', {'INTERFACE_LINK_LIBRARIES': [args[1]]}, tline=tline)
elif 'OBJECT' in args:
return self._gen_exception('add_library', 'OBJECT libraries are not supported', tline)
else:
self.targets[args[0]] = CMakeTarget(args[0], 'NORMAL', {})
self.targets[args[0]] = CMakeTarget(args[0], 'NORMAL', {}, tline=tline)
def _cmake_add_custom_command(self, tline: CMakeTraceLine):
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html
@ -300,7 +302,7 @@ class CMakeTraceParser:
if len(tline.args) < 1:
return self._gen_exception('add_custom_target', 'requires at least one argument', tline)
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {})
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {}, tline=tline)
def _cmake_set_property(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/set_property.html

@ -867,6 +867,38 @@ def detect_system_compiler():
raise RuntimeError("Could not find C compiler.")
print()
def print_tool_versions():
tools = [
{
'tool': 'cmake',
'args': ['--version'],
'regex': re.compile(r'^cmake version ([0-9]+(\.[0-9]+)*)$'),
'match_group': 1,
},
]
def get_version(t: dict) -> str:
exe = shutil.which(t['tool'])
if not exe:
return 'not found'
args = [t['tool']] + t['args']
pc, o, e = Popen_safe(args)
if pc.returncode != 0:
return '{} (invalid {} executable)'.format(exe, t['tool'])
for i in o.split('\n'):
i = i.strip('\n\r\t ')
m = t['regex'].match(i)
if m is not None:
return '{} ({})'.format(exe, m.group(t['match_group']))
return '{} (unknown)'.format(exe)
max_width = max([len(x['tool']) for x in tools] + [7])
for tool in tools:
print('{0:<{2}}: {1}'.format(tool['tool'], get_version(tool), max_width))
print()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Run the test suite of Meson.")
parser.add_argument('extra_args', nargs='*',
@ -882,6 +914,7 @@ if __name__ == '__main__':
setup_commands(options.backend)
detect_system_compiler()
print_tool_versions()
script_dir = os.path.split(__file__)[0]
if script_dir != '':
os.chdir(script_dir)

Loading…
Cancel
Save