Merge pull request #4969 from mensinda/cmakeSubProject

CMake subprojects
pull/5454/head
Jussi Pakkanen 6 years ago committed by GitHub
commit 266b297515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      azure-pipelines.yml
  2. 5
      ci/azure-steps.yml
  3. 87
      docs/markdown/CMake-module.md
  4. 10
      docs/markdown/Subprojects.md
  5. 30
      docs/markdown/snippets/cmake_subprojects.md
  6. 26
      mesonbuild/cmake/__init__.py
  7. 512
      mesonbuild/cmake/client.py
  8. 21
      mesonbuild/cmake/common.py
  9. 552
      mesonbuild/cmake/interpreter.py
  10. 76
      mesonbuild/dependencies/base.py
  11. 87
      mesonbuild/interpreter.py
  12. 70
      mesonbuild/modules/cmake.py
  13. 2
      mesonbuild/msubprojects.py
  14. 16
      mesonbuild/wrap/wrap.py
  15. 16
      run_project_tests.py
  16. 1
      setup.py
  17. 10
      test cases/cmake/1 basic/main.cpp
  18. 12
      test cases/cmake/1 basic/meson.build
  19. 10
      test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt
  20. 11
      test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp
  21. 13
      test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp
  22. 4
      test cases/cmake/2 advanced/installed_files.txt
  23. 15
      test cases/cmake/2 advanced/main.cpp
  24. 20
      test cases/cmake/2 advanced/meson.build
  25. 22
      test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt
  26. 3
      test cases/cmake/2 advanced/subprojects/cmMod/config.h.in
  27. 17
      test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp
  28. 13
      test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp
  29. 11
      test cases/cmake/2 advanced/subprojects/cmMod/main.cpp
  30. 6
      test cases/cmake/3 advanced no dep/installed_files.txt
  31. 15
      test cases/cmake/3 advanced no dep/main.cpp
  32. 15
      test cases/cmake/3 advanced no dep/meson.build
  33. 19
      test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt
  34. 3
      test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in
  35. 16
      test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp
  36. 13
      test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp
  37. 10
      test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp
  38. 8
      test cases/cmake/4 code gen/main.cpp
  39. 20
      test cases/cmake/4 code gen/meson.build
  40. 5
      test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt
  41. 21
      test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp
  42. 5
      test cases/cmake/4 code gen/test.hpp
  43. 9
      test cases/cmake/5 object library/main.cpp
  44. 25
      test cases/cmake/5 object library/meson.build
  45. 10
      test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt
  46. 5
      test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp
  47. 5
      test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp
  48. 6
      test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp
  49. 5
      test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp
  50. 9
      test cases/cmake/6 object library no dep/main.cpp
  51. 17
      test cases/cmake/6 object library no dep/meson.build
  52. 5
      test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt
  53. 5
      test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp
  54. 5
      test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp
  55. 5
      test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp
  56. 5
      test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp
  57. 3
      test cases/cmake/7 cmake options/meson.build
  58. 5
      test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt

@ -88,6 +88,7 @@ jobs:
gccx64ninja: {} gccx64ninja: {}
variables: variables:
CYGWIN_ROOT: $(System.Workfolder)\cygwin CYGWIN_ROOT: $(System.Workfolder)\cygwin
CYGWIN_CMAKE_LINK: http://cygwin.mirror.constant.com/x86_64/release/cmake/cmake-3.13.1-1.tar.xz
CYGWIN_MIRROR: http://cygwin.mirror.constant.com CYGWIN_MIRROR: http://cygwin.mirror.constant.com
steps: steps:
- script: | - script: |
@ -95,20 +96,28 @@ jobs:
displayName: Install Cygwin displayName: Install Cygwin
- script: | - script: |
%CYGWIN_ROOT%\cygwinsetup.exe -qnNdO -R "%CYGWIN_ROOT%" -s "%CYGWIN_MIRROR%" -g -P ^ %CYGWIN_ROOT%\cygwinsetup.exe -qnNdO -R "%CYGWIN_ROOT%" -s "%CYGWIN_MIRROR%" -g -P ^
cmake,^
gcc-fortran,^ gcc-fortran,^
gcc-objc++,^ gcc-objc++,^
gcc-objc,^ gcc-objc,^
git,^ git,^
gobject-introspection,^ gobject-introspection,^
libarchive13,^
libboost-devel,^ libboost-devel,^
libglib2.0-devel,^ libglib2.0-devel,^
libgtk3-devel,^ libgtk3-devel,^
libjsoncpp19,^
librhash0,^
libuv1,^
ninja,^ ninja,^
python35-pip,^ python35-pip,^
vala,^ vala,^
wget,^
zlib-devel zlib-devel
displayName: Install Dependencies displayName: Install Dependencies
- script: |
%CYGWIN_ROOT%\bin\bash.exe -cl "wget %CYGWIN_CMAKE_LINK% -O cmake.tar.xz"
%CYGWIN_ROOT%\bin\bash.exe -cl "tar -xf cmake.tar.xz -C /"
displayName: Manually install CMake 3.13.1
- script: | - script: |
set BOOST_ROOT= set BOOST_ROOT=
set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32

@ -154,6 +154,11 @@ steps:
where.exe python where.exe python
python --version python --version
echo ""
echo "Locating cl, rc:"
where.exe cl
where.exe rc
echo "" echo ""
echo "=== Start running tests ===" echo "=== Start running tests ==="
# Starting from VS2019 Powershell(?) will fail the test run # Starting from VS2019 Powershell(?) will fail the test run

@ -1,6 +1,8 @@
# CMake module # CMake module
This module provides helper tools for generating cmake package files. This module provides helper tools for generating cmake package files.
It also supports the usage of CMake based subprojects, similar to
the normal [meson subprojects](Subprojects.md).
## Usage ## Usage
@ -10,6 +12,91 @@ following functions will then be available as methods on the object
with the name `cmake`. You can, of course, replace the name `cmake` with the name `cmake`. You can, of course, replace the name `cmake`
with anything else. with anything else.
## CMake subprojects
Using CMake subprojects is similar to using the "normal" meson
subprojects. They also have to be located in the `subprojects`
directory.
Example:
```cmake
add_library(cm_lib SHARED ${SOURCES})
```
```meson
cmake = import('cmake')
# Configure the CMake project
sub_proj = cmake.subproject('libsimple_cmake')
# Fetch the dependency object
cm_lib = sub_proj.dependency('cm_lib')
executable(exe1, ['sources'], dependencies: [cm_lib])
```
The `subproject` method is almost identical to the normal meson
`subproject` function. The only difference is that a CMake project
instead of a meson project is configured.
Also, project specific CMake options can be added with the `cmake_options` key.
The returned `sub_proj` supports the same options as a "normal" subproject.
Meson automatically detects CMake build targets, which can be accessed with
the methods listed [below](#subproject-object).
It is usually enough to just use the dependency object returned by the
`dependency()` method in the build targets. This is almost identical to
using `declare_dependency()` object from a normal meson subproject.
It is also possible to use executables defined in the CMake project as code
generators with the `target()` method:
```cmake
add_executable(cm_exe ${EXE_SRC})
```
```meson
cmake = import('cmake')
# Subproject with the "code generator"
sub_pro = cmake.subproject('cmCodeGen')
# Fetch the code generator exe
sub_exe = sub_pro.target('cm_exe')
# Use the code generator
generated = custom_target(
'cmake-generated',
input: [],
output: ['test.cpp'],
command: [sub_exe, '@OUTPUT@']
)
```
It should be noted that not all projects are guaranteed to work. The
safest approach would still be to create a `meson.build` for the
subprojects in question.
### `subproject` object
This object is returned by the `subproject` function described above
and supports the following methods:
- `dependency(target)` returns a dependency object for any CMake target.
- `include_directories(target)` returns a meson `include_directories()`
object for the specified target. Using this function is not neccessary
if the dependency object is used.
- `target(target)` returns the raw build target.
- `target_type(target)` returns the type of the target as a string
- `target_list()` returns a list of all target *names*.
- `get_variable(name)` fetches the specified variable from inside
the subproject. Usually `dependency()` or `target()` should be
prefered to extract build targets.
## CMake configuration files
### cmake.write_basic_package_version_file() ### cmake.write_basic_package_version_file()
This function is the equivalent of the corresponding [CMake function](https://cmake.org/cmake/help/v3.11/module/CMakePackageConfigHelpers.html#generating-a-package-version-file), This function is the equivalent of the corresponding [CMake function](https://cmake.org/cmake/help/v3.11/module/CMakePackageConfigHelpers.html#generating-a-package-version-file),

@ -16,10 +16,12 @@ allows you to take any other Meson project and make it a part of your
build without (in the best case) any changes to its Meson setup. It build without (in the best case) any changes to its Meson setup. It
becomes a transparent part of the project. becomes a transparent part of the project.
It should be noted that this only works for subprojects that are built It should be noted that this is only guaranteed to work for subprojects
with Meson. It can not be used with any other build system. The reason that are built with Meson. The reason is the simple fact that there is
is the simple fact that there is no possible way to do this reliably no possible way to do this reliably with mixed build systems. Because of
with mixed build systems. this, only meson subprojects are described here.
[CMake based subprojects](CMake-module.md#CMake-subprojects) are also
supported but not guaranteed to work.
## A subproject example ## A subproject example

@ -0,0 +1,30 @@
## CMake subprojects
Meson can now directly consume CMake based subprojects with the
CMake module.
Using CMake subprojects is similar to using the "normal" meson
subprojects. They also have to be located in the `subprojects`
directory.
Example:
```cmake
add_library(cm_lib SHARED ${SOURCES})
```
```meson
cmake = import('cmake')
# Configure the CMake project
sub_proj = cmake.subproject('libsimple_cmake')
# Fetch the dependency object
cm_lib = sub_proj.dependency('cm_lib')
executable(exe1, ['sources'], dependencies: [cm_lib])
```
It should be noted that not all projects are guaranteed to work. The
safest approach would still be to create a `meson.build` for the
subprojects in question.

@ -0,0 +1,26 @@
# 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.
__all__ = [
'CMakeClient',
'CMakeException',
'CMakeInterpreter',
]
from .common import CMakeException
from .client import CMakeClient
from .interpreter import CMakeInterpreter

@ -0,0 +1,512 @@
# 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 ..environment import Environment
from ..dependencies.base import CMakeDependency, ExternalProgram
from .. import mlog
from contextlib import contextmanager
from subprocess import Popen, PIPE, TimeoutExpired
from typing import List, Optional
import json
import os
CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==['
CMAKE_SERVER_END_STR = ']== "CMake Server" ==]'
CMAKE_MESSAGE_TYPES = {
'error': ['cookie', 'errorMessage'],
'hello': ['supportedProtocolVersions'],
'message': ['cookie', 'message'],
'progress': ['cookie'],
'reply': ['cookie', 'inReplyTo'],
'signal': ['cookie', 'name'],
}
CMAKE_REPLY_TYPES = {
'handshake': [],
'configure': [],
'compute': [],
'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'],
'codemodel': ['configurations']
}
# Base CMake server message classes
class MessageBase:
def __init__(self, msg_type: str, cookie: str):
self.type = msg_type
self.cookie = cookie
def to_dict(self) -> dict:
return {'type': self.type, 'cookie': self.cookie}
def log(self) -> None:
mlog.warning('CMake server message of type', mlog.bold(type(self).__name__), 'has no log function')
class RequestBase(MessageBase):
cookie_counter = 0
def __init__(self, msg_type: str):
super().__init__(msg_type, self.gen_cookie())
@staticmethod
def gen_cookie():
RequestBase.cookie_counter += 1
return 'meson_{}'.format(RequestBase.cookie_counter)
class ReplyBase(MessageBase):
def __init__(self, cookie: str, in_reply_to: str):
super().__init__('reply', cookie)
self.in_reply_to = in_reply_to
class SignalBase(MessageBase):
def __init__(self, cookie: str, signal_name: str):
super().__init__('signal', cookie)
self.signal_name = signal_name
def log(self) -> None:
mlog.log(mlog.bold('CMake signal:'), mlog.yellow(self.signal_name))
# Special Message classes
class Error(MessageBase):
def __init__(self, cookie: str, message: str):
super().__init__('error', cookie)
self.message = message
def log(self) -> None:
mlog.error(mlog.bold('CMake server error:'), mlog.red(self.message))
class Message(MessageBase):
def __init__(self, cookie: str, message: str):
super().__init__('message', cookie)
self.message = message
def log(self) -> None:
#mlog.log(mlog.bold('CMake:'), self.message)
pass
class Progress(MessageBase):
def __init__(self, cookie: str):
super().__init__('progress', cookie)
def log(self) -> None:
pass
class MessageHello(MessageBase):
def __init__(self, supported_protocol_versions: List[dict]):
super().__init__('hello', '')
self.supported_protocol_versions = supported_protocol_versions
def supports(self, major: int, minor: Optional[int] = None) -> bool:
for i in self.supported_protocol_versions:
if major == i['major']:
if minor is None or minor == i['minor']:
return True
return False
# Request classes
class RequestHandShake(RequestBase):
def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: Optional[int] = None):
super().__init__('handshake')
self.src_dir = src_dir
self.build_dir = build_dir
self.generator = generator
self.vers_major = vers_major
self.vers_minor = vers_minor
def to_dict(self) -> dict:
vers = {'major': self.vers_major}
if self.vers_minor is not None:
vers['minor'] = self.vers_minor
# Old CMake versions (3.7) want '/' even on Windows
src_list = os.path.normpath(self.src_dir).split(os.sep)
bld_list = os.path.normpath(self.build_dir).split(os.sep)
return {
**super().to_dict(),
'sourceDirectory': '/'.join(src_list),
'buildDirectory': '/'.join(bld_list),
'generator': self.generator,
'protocolVersion': vers
}
class RequestConfigure(RequestBase):
def __init__(self, args: Optional[List[str]] = None):
super().__init__('configure')
self.args = args
def to_dict(self) -> dict:
res = super().to_dict()
if self.args:
res['cacheArguments'] = self.args
return res
class RequestCompute(RequestBase):
def __init__(self):
super().__init__('compute')
class RequestCMakeInputs(RequestBase):
def __init__(self):
super().__init__('cmakeInputs')
class RequestCodeModel(RequestBase):
def __init__(self):
super().__init__('codemodel')
# Reply classes
class ReplyHandShake(ReplyBase):
def __init__(self, cookie: str):
super().__init__(cookie, 'handshake')
class ReplyConfigure(ReplyBase):
def __init__(self, cookie: str):
super().__init__(cookie, 'configure')
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')
self.cmake_root = cmake_root
self.src_dir = src_dir
self.build_files = build_files
def log(self) -> None:
mlog.log('CMake root: ', mlog.bold(self.cmake_root))
mlog.log('Source dir: ', mlog.bold(self.src_dir))
mlog.log('Build files:', mlog.bold(str(len(self.build_files))))
with mlog.nested():
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')
self.configs = []
for i in data['configurations']:
self.configs += [CMakeConfiguration(i)]
def log(self) -> None:
mlog.log('CMake code mode:')
for idx, i in enumerate(self.configs):
mlog.log('Configuration {}:'.format(idx))
with mlog.nested():
i.log()
# Main client class
class CMakeClient:
def __init__(self, env: Environment):
self.env = env
self.proc = None
self.type_map = {
'error': lambda data: Error(data['cookie'], data['errorMessage']),
'hello': lambda data: MessageHello(data['supportedProtocolVersions']),
'message': lambda data: Message(data['cookie'], data['message']),
'progress': lambda data: Progress(data['cookie']),
'reply': self.resolve_type_reply,
'signal': lambda data: SignalBase(data['cookie'], data['name'])
}
self.reply_map = {
'handshake': lambda data: ReplyHandShake(data['cookie']),
'configure': lambda data: ReplyConfigure(data['cookie']),
'compute': lambda data: ReplyCompute(data['cookie']),
'cmakeInputs': self.resolve_reply_cmakeInputs,
'codemodel': lambda data: ReplyCodeModel(data),
}
def readMessageRaw(self) -> dict:
assert(self.proc is not None)
rawData = []
begin = False
while self.proc.poll() is None:
line = self.proc.stdout.readline()
if not line:
break
line = line.decode('utf-8')
line = line.strip()
if begin and line == CMAKE_SERVER_END_STR:
break # End of the message
elif begin:
rawData += [line]
elif line == CMAKE_SERVER_BEGIN_STR:
begin = True # Begin of the message
if rawData:
return json.loads('\n'.join(rawData))
raise CMakeException('Failed to read data from the CMake server')
def readMessage(self) -> MessageBase:
raw_data = self.readMessageRaw()
if 'type' not in raw_data:
raise CMakeException('The "type" attribute is missing from the message')
msg_type = raw_data['type']
func = self.type_map.get(msg_type, None)
if not func:
raise CMakeException('Recieved unknown message type "{}"'.format(msg_type))
for i in CMAKE_MESSAGE_TYPES[msg_type]:
if i not in raw_data:
raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, msg_type))
return func(raw_data)
def writeMessage(self, msg: MessageBase) -> None:
raw_data = '\n{}\n{}\n{}\n'.format(CMAKE_SERVER_BEGIN_STR, json.dumps(msg.to_dict(), indent=2), CMAKE_SERVER_END_STR)
self.proc.stdin.write(raw_data.encode('ascii'))
self.proc.stdin.flush()
def query(self, request: RequestBase) -> MessageBase:
self.writeMessage(request)
while True:
reply = self.readMessage()
if reply.cookie == request.cookie and reply.type in ['reply', 'error']:
return reply
reply.log()
def query_checked(self, request: RequestBase, message: str) -> ReplyBase:
reply = self.query(request)
h = mlog.green('SUCCEEDED') if reply.type == 'reply' else mlog.red('FAILED')
mlog.log(message + ':', h)
if reply.type != 'reply':
reply.log()
raise CMakeException('CMake server query failed')
return reply
def do_handshake(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: Optional[int] = None) -> None:
# CMake prints the hello message on startup
msg = self.readMessage()
if not isinstance(msg, MessageHello):
raise CMakeException('Recieved an unexpected message from the CMake server')
request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor)
self.query_checked(request, 'CMake server handshake')
def resolve_type_reply(self, data: dict) -> ReplyBase:
reply_type = data['inReplyTo']
func = self.reply_map.get(reply_type, None)
if not func:
raise CMakeException('Recieved unknown reply type "{}"'.format(reply_type))
for i in ['cookie'] + CMAKE_REPLY_TYPES[reply_type]:
if i not in data:
raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type))
return func(data)
def resolve_reply_cmakeInputs(self, data: dict) -> ReplyCMakeInputs:
files = []
for i in data['buildFiles']:
for j in i['sources']:
files += [CMakeBuildFile(j, i['isCMake'], i['isTemporary'])]
return ReplyCMakeInputs(data['cookie'], data['cmakeRootDirectory'], data['sourceDirectory'], files)
@contextmanager
def connect(self):
self.startup()
try:
yield
finally:
self.shutdown()
def startup(self) -> None:
if self.proc is not None:
raise CMakeException('The CMake server was already started')
cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env)
if cmake_exe is None or cmake_exe is False:
raise CMakeException('Unable to find CMake')
assert(isinstance(cmake_exe, ExternalProgram))
if not cmake_exe.found():
raise CMakeException('Unable to find CMake')
mlog.debug('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_vers))
self.proc = Popen(cmake_exe.get_command() + ['-E', 'server', '--experimental', '--debug'], stdin=PIPE, stdout=PIPE)
def shutdown(self) -> None:
if self.proc is None:
return
mlog.debug('Shutting down the CMake server')
# Close the pipes to exit
self.proc.stdin.close()
self.proc.stdout.close()
# Wait for CMake to finish
try:
self.proc.wait(timeout=2)
except TimeoutExpired:
# Terminate CMake if there is a timeout
# terminate() may throw a platform specific exception if the process has already
# terminated. This may be the case if there is a race condition (CMake exited after
# the timeout but before the terminate() call). Additionally, this behavior can
# also be triggered on cygwin if CMake crashes.
# See https://github.com/mesonbuild/meson/pull/4969#issuecomment-499413233
try:
self.proc.terminate()
except Exception:
pass
self.proc = None

@ -0,0 +1,21 @@
# Copyright 2019 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from ..mesonlib import MesonException
class CMakeException(MesonException):
pass

@ -0,0 +1,552 @@
# 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 .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, CMakeTarget
from .. import mlog
from ..build import Build
from ..environment import Environment
from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode, MethodNode
from ..backend.backends import Backend
from ..compilers.compilers import obj_suffixes
from ..dependencies.base import CMakeDependency, ExternalProgram
from subprocess import Popen, PIPE, STDOUT
from typing import List, Dict, Optional
import os, re
backend_generator_map = {
'ninja': 'Ninja',
'xcode': 'Xcode',
'vs2010': 'Visual Studio 10 2010',
'vs2015': 'Visual Studio 15 2017',
'vs2017': 'Visual Studio 15 2017',
'vs2019': 'Visual Studio 16 2019',
}
language_map = {
'c': 'C',
'cpp': 'CXX',
'cuda': 'CUDA',
'cs': 'CSharp',
'java': 'Java',
'fortran': 'Fortran',
'swift': 'Swift',
}
target_type_map = {
'STATIC_LIBRARY': 'static_library',
'MODULE_LIBRARY': 'shared_module',
'SHARED_LIBRARY': 'shared_library',
'EXECUTABLE': 'executable',
'OBJECT_LIBRARY': 'static_library',
}
skip_targets = ['UTILITY']
skip_input_extensions = ['.rule']
blacklist_compiler_flags = [
'/W1', '/W2', '/W3', '/W4', '/Wall',
'/O1', '/O2', '/Ob', '/Od', '/Og', '/Oi', '/Os', '/Ot', '/Ox', '/Oy', '/Ob0',
'/RTC1', '/RTCc', '/RTCs', '/RTCu'
]
blacklist_link_flags = [
'/machine:x64', '/machine:x86', '/machine:arm', '/machine:ebc',
'/debug', '/debug:fastlink', '/debug:full', '/debug:none',
'/incremental',
]
blacklist_clang_cl_link_flags = ['/GR', '/EHsc', '/MDd', '/Zi', '/RTC1']
blacklist_link_libs = [
'kernel32.lib',
'user32.lib',
'gdi32.lib',
'winspool.lib',
'shell32.lib',
'ole32.lib',
'oleaut32.lib',
'uuid.lib',
'comdlg32.lib',
'advapi32.lib'
]
class ConverterTarget:
lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()}
def __init__(self, target: CMakeTarget, env: Environment):
self.env = env
self.artifacts = target.artifacts
self.src_dir = target.src_dir
self.build_dir = target.build_dir
self.name = target.name
self.full_name = target.full_name
self.type = target.type
self.install = target.install
self.install_dir = ''
self.link_libraries = target.link_libraries
self.link_flags = target.link_flags + target.link_lang_flags
if target.install_paths:
self.install_dir = target.install_paths[0]
self.languages = []
self.sources = []
self.generated = []
self.includes = []
self.link_with = []
self.object_libs = []
self.compile_opts = {}
self.pie = False
# Project default override options (c_std, cpp_std, etc.)
self.override_options = []
for i in target.files:
# Determine the meson language
lang = ConverterTarget.lang_cmake_to_meson.get(i.language.lower(), 'c')
if lang not in self.languages:
self.languages += [lang]
if lang not in self.compile_opts:
self.compile_opts[lang] = []
# Add arguments, but avoid duplicates
args = i.flags
args += ['-D{}'.format(x) for x in i.defines]
self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]]
# Handle include directories
self.includes += [x for x in i.includes if x not in self.includes]
# Add sources to the right array
if i.is_generated:
self.generated += i.sources
else:
self.sources += i.sources
def __repr__(self) -> str:
return '<{}: {}>'.format(self.__class__.__name__, self.name)
std_regex = re.compile(r'([-]{1,2}std=|/std:v?|[-]{1,2}std:)(.*)')
def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None:
# Detect setting the C and C++ standard
for i in ['c', 'cpp']:
if i not in self.compile_opts:
continue
temp = []
for j in self.compile_opts[i]:
m = ConverterTarget.std_regex.match(j)
if m:
self.override_options += ['{}_std={}'.format(i, m.group(2))]
elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']:
self.pie = True
elif j in blacklist_compiler_flags:
pass
else:
temp += [j]
self.compile_opts[i] = temp
# Make sure to force enable -fPIC for OBJECT libraries
if self.type.upper() == 'OBJECT_LIBRARY':
self.pie = True
# Fix link libraries
temp = []
for i in self.link_libraries:
# Let meson handle this arcane magic
if ',-rpath,' in i:
continue
if not os.path.isabs(i):
basename = os.path.basename(i)
if basename in output_target_map:
self.link_with += [output_target_map[basename]]
continue
temp += [i]
self.link_libraries = temp
# Make paths relative
def rel_path(x: str) -> str:
if not os.path.isabs(x):
x = os.path.normpath(os.path.join(self.src_dir, x))
if os.path.isabs(x) and os.path.commonpath([x, root_src_dir]) == root_src_dir:
return os.path.relpath(x, root_src_dir)
if os.path.isabs(x) and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir():
return os.path.relpath(x, os.path.join(self.env.get_build_dir(), subdir))
return x
build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir))
self.includes = list(set([rel_path(x) for x in set(self.includes)] + [build_dir_rel]))
self.sources = [rel_path(x) for x in self.sources]
self.generated = [rel_path(x) for x in self.generated]
# Filter out CMake rule files
self.sources = [x for x in self.sources if not any([x.endswith(y) for y in skip_input_extensions])]
self.generated = [x for x in self.generated if not any([x.endswith(y) for y in skip_input_extensions])]
# Make sure '.' is always in the include directories
if '.' not in self.includes:
self.includes += ['.']
# make install dir relative to the install prefix
if self.install_dir and os.path.isabs(self.install_dir):
if os.path.commonpath([self.install_dir, install_prefix]) == install_prefix:
self.install_dir = os.path.relpath(self.install_dir, install_prefix)
# Remove blacklisted options and libs
def check_flag(flag: str) -> bool:
if flag.lower() in blacklist_link_flags or flag in blacklist_compiler_flags + blacklist_clang_cl_link_flags:
return False
if flag.startswith('/D'):
return False
return True
self.link_libraries = [x for x in self.link_libraries if x.lower() not in blacklist_link_libs]
self.link_flags = [x for x in self.link_flags if check_flag(x)]
def process_object_libs(self, obj_target_list: List['ConverterTarget']):
# Try to detect the object library(s) from the generated input sources
temp = [os.path.basename(x) for x in self.generated]
temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])]
temp = [os.path.splitext(x)[0] for x in temp]
# Temp now stores the source filenames of the object files
for i in obj_target_list:
source_files = [os.path.basename(x) for x in i.sources + i.generated]
for j in source_files:
if j in temp:
self.object_libs += [i]
break
# Filter out object files from the sources
self.generated = [x for x in self.generated if not any([x.endswith('.' + y) for y in obj_suffixes])]
def meson_func(self) -> str:
return target_type_map.get(self.type.upper())
def log(self) -> None:
mlog.log('Target', mlog.bold(self.name))
mlog.log(' -- artifacts: ', mlog.bold(str(self.artifacts)))
mlog.log(' -- full_name: ', mlog.bold(self.full_name))
mlog.log(' -- type: ', mlog.bold(self.type))
mlog.log(' -- install: ', mlog.bold('true' if self.install else 'false'))
mlog.log(' -- install_dir: ', mlog.bold(self.install_dir))
mlog.log(' -- link_libraries: ', mlog.bold(str(self.link_libraries)))
mlog.log(' -- link_with: ', mlog.bold(str(self.link_with)))
mlog.log(' -- object_libs: ', mlog.bold(str(self.object_libs)))
mlog.log(' -- link_flags: ', mlog.bold(str(self.link_flags)))
mlog.log(' -- languages: ', mlog.bold(str(self.languages)))
mlog.log(' -- includes: ', mlog.bold(str(self.includes)))
mlog.log(' -- sources: ', mlog.bold(str(self.sources)))
mlog.log(' -- generated: ', mlog.bold(str(self.generated)))
mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false'))
mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options)))
mlog.log(' -- options:')
for key, val in self.compile_opts.items():
mlog.log(' -', key, '=', mlog.bold(str(val)))
class CMakeInterpreter:
def __init__(self, build: Build, subdir: str, src_dir: str, install_prefix: str, env: Environment, backend: Backend):
assert(hasattr(backend, 'name'))
self.build = build
self.subdir = subdir
self.src_dir = src_dir
self.build_dir_rel = os.path.join(subdir, '__CMake_build')
self.build_dir = os.path.join(env.get_build_dir(), self.build_dir_rel)
self.install_prefix = install_prefix
self.env = env
self.backend_name = backend.name
self.client = CMakeClient(self.env)
# Raw CMake results
self.bs_files = []
self.codemodel = None
# Analysed data
self.project_name = ''
self.languages = []
self.targets = []
# Generated meson data
self.generated_targets = {}
def configure(self, extra_cmake_options: List[str]) -> None:
# Find CMake
cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env)
if cmake_exe is None or cmake_exe is False:
raise CMakeException('Unable to find CMake')
assert(isinstance(cmake_exe, ExternalProgram))
if not cmake_exe.found():
raise CMakeException('Unable to find CMake')
generator = backend_generator_map[self.backend_name]
cmake_args = cmake_exe.get_command()
# Map meson compiler to CMake variables
for lang, comp in self.env.coredata.compilers.items():
if lang not in language_map:
continue
cmake_lang = language_map[lang]
exelist = comp.get_exelist()
if len(exelist) == 1:
cmake_args += ['-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[0])]
elif len(exelist) == 2:
cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]),
'-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])]
if hasattr(comp, 'get_linker_exelist') and comp.get_id() == 'clang-cl':
cmake_args += ['-DCMAKE_LINKER={}'.format(comp.get_linker_exelist()[0])]
cmake_args += ['-G', generator]
cmake_args += ['-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)]
cmake_args += extra_cmake_options
# Run CMake
mlog.log()
with mlog.nested():
mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_vers))
mlog.log(mlog.bold('Running:'), ' '.join(cmake_args))
mlog.log()
os.makedirs(self.build_dir, exist_ok=True)
os_env = os.environ.copy()
os_env['LC_ALL'] = 'C'
proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=STDOUT, cwd=self.build_dir, env=os_env)
# Print CMake log in realtime
while True:
line = proc.stdout.readline()
if not line:
break
mlog.log(line.decode('utf-8').strip('\n'))
# Wait for CMake to finish
proc.communicate()
mlog.log()
h = mlog.green('SUCCEEDED') if proc.returncode == 0 else mlog.red('FAILED')
mlog.log('CMake configuration:', h)
if proc.returncode != 0:
raise CMakeException('Failed to configure the CMake subproject')
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
self.configure(extra_cmake_options)
with self.client.connect():
generator = backend_generator_map[self.backend_name]
self.client.do_handshake(self.src_dir, self.build_dir, generator, 1)
# Do a second configure to initialise the server
self.client.query_checked(RequestConfigure(), 'CMake server configure')
# Generate the build system files
self.client.query_checked(RequestCompute(), 'Generating build system files')
# Get CMake build system files
bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files')
# Now get the CMake code model
cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model')
src_dir = bs_reply.src_dir
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
def analyse(self) -> None:
if self.codemodel is None:
raise CMakeException('CMakeInterpreter was not initialized')
# Clear analyser data
self.project_name = ''
self.languages = []
self.targets = []
# Find all targets
for i in self.codemodel.configs:
for j in i.projects:
if not self.project_name:
self.project_name = j.name
for k in j.targets:
if k.type not in skip_targets:
self.targets += [ConverterTarget(k, self.env)]
output_target_map = {x.full_name: x for x in self.targets}
for i in self.targets:
for j in i.artifacts:
output_target_map[os.path.basename(j)] = i
object_libs = []
# First pass: Basic target cleanup
for i in self.targets:
i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix)
if i.type == 'OBJECT_LIBRARY':
object_libs += [i]
self.languages += [x for x in i.languages if x not in self.languages]
# Second pass: Detect object library dependencies
for i in self.targets:
i.process_object_libs(object_libs)
mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets))), 'build targets.')
def pretend_to_be_meson(self) -> CodeBlockNode:
if not self.project_name:
raise CMakeException('CMakeInterpreter was not analysed')
def token(tid: str = 'string', val='') -> Token:
return Token(tid, self.subdir, 0, 0, 0, None, val)
def string(value: str) -> StringNode:
return StringNode(token(val=value))
def id_node(value: str) -> IdNode:
return IdNode(token(val=value))
def nodeify(value):
if isinstance(value, str):
return string(value)
elif isinstance(value, bool):
return BooleanNode(token(), value)
elif isinstance(value, list):
return array(value)
return value
def array(elements) -> ArrayNode:
args = ArgumentNode(token())
if not isinstance(elements, list):
elements = [args]
args.arguments += [nodeify(x) for x in elements]
return ArrayNode(args, 0, 0, 0, 0)
def function(name: str, args=None, kwargs=None) -> FunctionNode:
if args is None:
args = []
if kwargs is None:
kwargs = {}
args_n = ArgumentNode(token())
if not isinstance(args, list):
args = [args]
args_n.arguments = [nodeify(x) for x in args]
args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()}
func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n)
return func_n
def method(obj: BaseNode, name: str, args=None, kwargs=None) -> MethodNode:
if args is None:
args = []
if kwargs is None:
kwargs = {}
args_n = ArgumentNode(token())
if not isinstance(args, list):
args = [args]
args_n.arguments = [nodeify(x) for x in args]
args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()}
return MethodNode(self.subdir, 0, 0, obj, name, args_n)
def assign(var_name: str, value: BaseNode) -> AssignmentNode:
return AssignmentNode(self.subdir, 0, 0, var_name, value)
# Generate the root code block and the project function call
root_cb = CodeBlockNode(token())
root_cb.lines += [function('project', [self.project_name] + self.languages)]
processed = {}
def process_target(tgt: ConverterTarget):
# First handle inter target dependencies
link_with = []
objec_libs = []
for i in tgt.link_with:
assert(isinstance(i, ConverterTarget))
if i.name not in processed:
process_target(i)
link_with += [id_node(processed[i.name]['tgt'])]
for i in tgt.object_libs:
assert(isinstance(i, ConverterTarget))
if i.name not in processed:
process_target(i)
objec_libs += [processed[i.name]['tgt']]
# Determine the meson function to use for the build target
tgt_func = tgt.meson_func()
if not tgt_func:
raise CMakeException('Unknown target type "{}"'.format(tgt.type))
# Determine the variable names
base_name = str(tgt.name)
base_name = base_name.replace('-', '_')
inc_var = '{}_inc'.format(base_name)
src_var = '{}_src'.format(base_name)
dep_var = '{}_dep'.format(base_name)
tgt_var = base_name
# Generate target kwargs
tgt_kwargs = {
'link_args': 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,
'objects': [method(id_node(x), 'extract_all_objects') for x in objec_libs],
}
# Handle compiler args
for key, val in tgt.compile_opts.items():
tgt_kwargs['{}_args'.format(key)] = val
# Handle -fPCI, etc
if tgt_func == 'executable':
tgt_kwargs['pie'] = tgt.pie
elif tgt_func == 'static_library':
tgt_kwargs['pic'] = tgt.pie
# declare_dependency kwargs
dep_kwargs = {
'link_args': tgt.link_flags + tgt.link_libraries,
'link_with': id_node(tgt_var),
'include_directories': id_node(inc_var),
}
# Generate the function nodes
inc_node = assign(inc_var, function('include_directories', tgt.includes))
src_node = assign(src_var, function('files', tgt.sources + tgt.generated))
tgt_node = assign(tgt_var, function(tgt_func, [base_name, id_node(src_var)], tgt_kwargs))
dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs))
# Add the nodes to the ast
root_cb.lines += [inc_node, src_node, tgt_node, dep_node]
processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var, 'func': tgt_func}
# Now generate the target function calls
for i in self.targets:
if i.name not in processed:
process_target(i)
self.generated_targets = processed
return root_cb
def target_info(self, target: str) -> Optional[Dict[str, str]]:
if target in self.generated_targets:
return self.generated_targets[target]
return None
def target_list(self) -> List[str]:
return list(self.generated_targets.keys())

@ -1059,10 +1059,43 @@ class CMakeDependency(ExternalDependency):
# List of successfully found modules # List of successfully found modules
self.found_modules = [] self.found_modules = []
self.cmakebin, self.cmakevers, for_machine = self.find_cmake_binary(environment, self.want_cross, self.silent)
if self.cmakebin is False:
self.cmakebin = None
msg = 'No CMake binary for machine %s not found. Giving up.' % for_machine
if self.required:
raise DependencyException(msg)
mlog.debug(msg)
return
if CMakeDependency.class_cmakeinfo[for_machine] is None:
CMakeDependency.class_cmakeinfo[for_machine] = self._get_cmake_info()
self.cmakeinfo = CMakeDependency.class_cmakeinfo[for_machine]
if self.cmakeinfo is None:
raise self._gen_exception('Unable to obtain CMake system information')
modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))]
modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))]
cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path'))
cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path]
cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args'))
if cm_path:
cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path))
pref_path = self.env.coredata.builtins_per_machine[for_machine]['cmake_prefix_path'].value
if pref_path:
cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path)))
if not self._preliminary_find_check(name, cm_path, pref_path, environment.machines[for_machine]):
return
self._detect_dep(name, modules, cm_args)
@staticmethod
def find_cmake_binary(environment: Environment, want_cross: bool = False, silent: bool = False) -> Tuple[str, str, MachineChoice]:
# When finding dependencies for cross-compiling, we don't care about # When finding dependencies for cross-compiling, we don't care about
# the 'native' CMake binary # the 'native' CMake binary
# TODO: Test if this works as expected # TODO: Test if this works as expected
if environment.is_cross_build() and not self.want_cross: if environment.is_cross_build() and not want_cross:
for_machine = MachineChoice.BUILD for_machine = MachineChoice.BUILD
else: else:
for_machine = MachineChoice.HOST for_machine = MachineChoice.HOST
@ -1097,54 +1130,24 @@ class CMakeDependency(ExternalDependency):
for potential_cmakebin in search(): for potential_cmakebin in search():
mlog.debug('Trying CMake binary {} for machine {} at {}' mlog.debug('Trying CMake binary {} for machine {} at {}'
.format(potential_cmakebin.name, for_machine, potential_cmakebin.command)) .format(potential_cmakebin.name, for_machine, potential_cmakebin.command))
version_if_ok = self.check_cmake(potential_cmakebin) version_if_ok = CMakeDependency.check_cmake(potential_cmakebin)
if not version_if_ok: if not version_if_ok:
continue continue
if not self.silent: if not silent:
mlog.log('Found CMake:', mlog.bold(potential_cmakebin.get_path()), mlog.log('Found CMake:', mlog.bold(potential_cmakebin.get_path()),
'(%s)' % version_if_ok) '(%s)' % version_if_ok)
CMakeDependency.class_cmakebin[for_machine] = potential_cmakebin CMakeDependency.class_cmakebin[for_machine] = potential_cmakebin
CMakeDependency.class_cmakevers[for_machine] = version_if_ok CMakeDependency.class_cmakevers[for_machine] = version_if_ok
break break
else: else:
if not self.silent: if not silent:
mlog.log('Found CMake:', mlog.red('NO')) mlog.log('Found CMake:', mlog.red('NO'))
# Set to False instead of None to signify that we've already # Set to False instead of None to signify that we've already
# searched for it and not found it # searched for it and not found it
CMakeDependency.class_cmakebin[for_machine] = False CMakeDependency.class_cmakebin[for_machine] = False
CMakeDependency.class_cmakevers[for_machine] = None CMakeDependency.class_cmakevers[for_machine] = None
self.cmakebin = CMakeDependency.class_cmakebin[for_machine] return CMakeDependency.class_cmakebin[for_machine], CMakeDependency.class_cmakevers[for_machine], for_machine
self.cmakevers = CMakeDependency.class_cmakevers[for_machine]
if self.cmakebin is False:
self.cmakebin = None
msg = 'No CMake binary for machine %s not found. Giving up.' % for_machine
if self.required:
raise DependencyException(msg)
mlog.debug(msg)
return
if CMakeDependency.class_cmakeinfo[for_machine] is None:
CMakeDependency.class_cmakeinfo[for_machine] = self._get_cmake_info()
self.cmakeinfo = CMakeDependency.class_cmakeinfo[for_machine]
if self.cmakeinfo is None:
raise self._gen_exception('Unable to obtain CMake system information')
modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))]
modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))]
cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path'))
cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path]
cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args'))
if cm_path:
cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path))
pref_path = self.env.coredata.builtins_per_machine[for_machine]['cmake_prefix_path'].value
if pref_path:
cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path)))
if not self._preliminary_find_check(name, cm_path, pref_path, environment.machines[for_machine]):
return
self._detect_dep(name, modules, cm_args)
def __repr__(self): def __repr__(self):
s = '<{0} {1}: {2} {3}>' s = '<{0} {1}: {2} {3}>'
@ -1841,7 +1844,8 @@ set(CMAKE_SIZEOF_VOID_P "{}")
def get_methods(): def get_methods():
return [DependencyMethods.CMAKE] return [DependencyMethods.CMAKE]
def check_cmake(self, cmakebin): @staticmethod
def check_cmake(cmakebin):
if not cmakebin.found(): if not cmakebin.found():
mlog.log('Did not find CMake {!r}'.format(cmakebin.name)) mlog.log('Did not find CMake {!r}'.format(cmakebin.name))
return None return None

@ -31,6 +31,7 @@ from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabl
from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
from .interpreterbase import ObjectHolder from .interpreterbase import ObjectHolder
from .modules import ModuleReturnValue from .modules import ModuleReturnValue
from .cmake import CMakeInterpreter
import os, shutil, uuid import os, shutil, uuid
import re, shlex import re, shlex
@ -2037,7 +2038,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'},
class Interpreter(InterpreterBase): class Interpreter(InterpreterBase):
def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects', def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects',
modules = None, default_project_options=None, mock=False): modules = None, default_project_options=None, mock=False, ast=None):
super().__init__(build.environment.get_source_dir(), subdir) super().__init__(build.environment.get_source_dir(), subdir)
self.an_unpicklable_object = mesonlib.an_unpicklable_object self.an_unpicklable_object = mesonlib.an_unpicklable_object
self.build = build self.build = build
@ -2054,9 +2055,12 @@ class Interpreter(InterpreterBase):
self.subproject_directory_name = subdir.split(os.path.sep)[-1] self.subproject_directory_name = subdir.split(os.path.sep)[-1]
self.subproject_dir = subproject_dir self.subproject_dir = subproject_dir
self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
if not mock: if not mock and ast is None:
self.load_root_meson_file() self.load_root_meson_file()
self.sanity_check_ast() self.sanity_check_ast()
elif ast is not None:
self.ast = ast
self.sanity_check_ast()
self.builtin.update({'meson': MesonMain(build, self)}) self.builtin.update({'meson': MesonMain(build, self)})
self.generators = [] self.generators = []
self.visited_subdirs = {} self.visited_subdirs = {}
@ -2243,7 +2247,7 @@ class Interpreter(InterpreterBase):
raise InterpreterException('Stdlib definition for %s should have exactly two elements.' raise InterpreterException('Stdlib definition for %s should have exactly two elements.'
% l) % l)
projname, depname = di projname, depname = di
subproj = self.do_subproject(projname, {}) subproj = self.do_subproject(projname, 'meson', {})
self.build.cross_stdlibs[l] = subproj.get_variable_method([depname], {}) self.build.cross_stdlibs[l] = subproj.get_variable_method([depname], {})
except KeyError: except KeyError:
pass pass
@ -2418,13 +2422,13 @@ external dependencies (including libraries) must go to "dependencies".''')
if len(args) != 1: if len(args) != 1:
raise InterpreterException('Subproject takes exactly one argument') raise InterpreterException('Subproject takes exactly one argument')
dirname = args[0] dirname = args[0]
return self.do_subproject(dirname, kwargs) return self.do_subproject(dirname, 'meson', kwargs)
def disabled_subproject(self, dirname): def disabled_subproject(self, dirname):
self.subprojects[dirname] = SubprojectHolder(None, self.subproject_dir, dirname) self.subprojects[dirname] = SubprojectHolder(None, self.subproject_dir, dirname)
return self.subprojects[dirname] return self.subprojects[dirname]
def do_subproject(self, dirname, kwargs): def do_subproject(self, dirname: str, method: str, kwargs):
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled: if disabled:
mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled') mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
@ -2457,7 +2461,7 @@ external dependencies (including libraries) must go to "dependencies".''')
subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode')) r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode'))
try: try:
resolved = r.resolve(dirname) resolved = r.resolve(dirname, method)
except wrap.WrapException as e: except wrap.WrapException as e:
subprojdir = os.path.join(self.subproject_dir, r.directory) subprojdir = os.path.join(self.subproject_dir, r.directory)
if isinstance(e, wrap.WrapNotFoundException): if isinstance(e, wrap.WrapNotFoundException):
@ -2473,22 +2477,20 @@ external dependencies (including libraries) must go to "dependencies".''')
raise e raise e
subdir = os.path.join(self.subproject_dir, resolved) subdir = os.path.join(self.subproject_dir, resolved)
subdir_abs = os.path.join(subproject_dir_abs, resolved)
os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
self.global_args_frozen = True self.global_args_frozen = True
mlog.log() mlog.log()
with mlog.nested(): with mlog.nested():
mlog.log('Executing subproject', mlog.bold(dirname), '\n') mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n')
try: try:
with mlog.nested(): if method == 'meson':
new_build = self.build.copy() return self._do_subproject_meson(dirname, subdir, default_options, required, kwargs)
subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, elif method == 'cmake':
self.modules, default_options) return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs)
subi.subprojects = self.subprojects else:
raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname))
subi.subproject_stack = self.subproject_stack + [dirname]
current_active = self.active_projectname
subi.run()
mlog.log('Subproject', mlog.bold(dirname), 'finished.')
# Invalid code is always an error # Invalid code is always an error
except InvalidCode: except InvalidCode:
raise raise
@ -2502,6 +2504,18 @@ external dependencies (including libraries) must go to "dependencies".''')
return self.disabled_subproject(dirname) return self.disabled_subproject(dirname)
raise e raise e
def _do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None):
with mlog.nested():
new_build = self.build.copy()
subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir,
self.modules, default_options, ast=ast)
subi.subprojects = self.subprojects
subi.subproject_stack = self.subproject_stack + [dirname]
current_active = self.active_projectname
subi.run()
mlog.log('Subproject', mlog.bold(dirname), 'finished.')
mlog.log() mlog.log()
if 'version' in kwargs: if 'version' in kwargs:
@ -2513,11 +2527,48 @@ external dependencies (including libraries) must go to "dependencies".''')
self.subprojects.update(subi.subprojects) self.subprojects.update(subi.subprojects)
self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname) self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname)
# Duplicates are possible when subproject uses files from project root # Duplicates are possible when subproject uses files from project root
if build_def_files:
self.build_def_files = list(set(self.build_def_files + build_def_files))
else:
self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) self.build_def_files = list(set(self.build_def_files + subi.build_def_files))
self.build.merge(subi.build) self.build.merge(subi.build)
self.build.subprojects[dirname] = subi.project_version self.build.subprojects[dirname] = subi.project_version
return self.subprojects[dirname] return self.subprojects[dirname]
def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs):
with mlog.nested():
new_build = self.build.copy()
prefix = self.coredata.builtins['prefix'].value
cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend)
cm_int.initialise(cmake_options)
cm_int.analyse()
# Generate a meson ast and execute it with the normal do_subproject_meson
ast = cm_int.pretend_to_be_meson()
mlog.log()
with mlog.nested():
mlog.log('Processing generated meson AST')
mlog.log()
# Debug print the generated meson file
mlog.debug('=== BEGIN meson.build ===')
from .ast import AstIndentationGenerator, AstPrinter
printer = AstPrinter()
ast.accept(AstIndentationGenerator())
ast.accept(printer)
printer.post_process()
mlog.debug(printer.result)
mlog.debug('=== END meson.build ===')
mlog.debug()
result = self._do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files)
result.cm_interpreter = cm_int
mlog.log()
return result
def get_option_internal(self, optname): def get_option_internal(self, optname):
for opts in chain( for opts in chain(
[self.coredata.base_options, compilers.base_options, self.coredata.builtins], [self.coredata.base_options, compilers.base_options, self.coredata.builtins],
@ -3105,7 +3156,7 @@ external dependencies (including libraries) must go to "dependencies".''')
'default_options': kwargs.get('default_options', []), 'default_options': kwargs.get('default_options', []),
'required': kwargs.get('required', True), 'required': kwargs.get('required', True),
} }
self.do_subproject(dirname, sp_kwargs) self.do_subproject(dirname, 'meson', sp_kwargs)
return self.get_subproject_dep(display_name, dirname, varname, kwargs) return self.get_subproject_dep(display_name, dirname, varname, kwargs)
@FeatureNewKwargs('executable', '0.42.0', ['implib']) @FeatureNewKwargs('executable', '0.42.0', ['implib'])

@ -18,8 +18,8 @@ import shutil
from . import ExtensionModule, ModuleReturnValue from . import ExtensionModule, ModuleReturnValue
from .. import build, dependencies, mesonlib, mlog from .. import build, dependencies, mesonlib, mlog
from ..interpreterbase import permittedKwargs from ..interpreterbase import permittedKwargs, FeatureNew, stringArgs, InterpreterObject, ObjectHolder
from ..interpreter import ConfigurationDataHolder from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder
COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion'] COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion']
@ -43,6 +43,62 @@ unset(_realOrig)
unset(_realCurr) unset(_realCurr)
''' '''
class CMakeSubprojectHolder(InterpreterObject, ObjectHolder):
def __init__(self, subp, pv):
assert(isinstance(subp, SubprojectHolder))
assert(hasattr(subp, 'cm_interpreter'))
InterpreterObject.__init__(self)
ObjectHolder.__init__(self, subp, pv)
self.methods.update({'get_variable': self.get_variable,
'dependency': self.dependency,
'include_directories': self.include_directories,
'target': self.target,
'target_type': self.target_type,
'target_list': self.target_list,
})
def _args_to_info(self, args):
if len(args) != 1:
raise InterpreterException('Exactly one argument is required.')
tgt = args[0]
res = self.held_object.cm_interpreter.target_info(tgt)
if res is None:
raise InterpreterException('The CMake target {} does not exist'.format(tgt))
# Make sure that all keys are present (if not this is a bug)
assert(all([x in res for x in ['inc', 'src', 'dep', 'tgt', 'func']]))
return res
@permittedKwargs({})
def get_variable(self, args, kwargs):
return self.held_object.get_variable_method(args, kwargs)
@permittedKwargs({})
def dependency(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['dep']], kwargs)
@permittedKwargs({})
def include_directories(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['inc']], kwargs)
@permittedKwargs({})
def target(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['tgt']], kwargs)
@permittedKwargs({})
def target_type(self, args, kwargs):
info = self._args_to_info(args)
return info['func']
@permittedKwargs({})
def target_list(self, args, kwargs):
if len(args) > 0:
raise InterpreterException('target_list does not take any parameters.')
return self.held_object.cm_interpreter.target_list()
class CmakeModule(ExtensionModule): class CmakeModule(ExtensionModule):
cmake_detected = False cmake_detected = False
@ -51,6 +107,7 @@ class CmakeModule(ExtensionModule):
def __init__(self, interpreter): def __init__(self, interpreter):
super().__init__(interpreter) super().__init__(interpreter)
self.snippets.add('configure_package_config_file') self.snippets.add('configure_package_config_file')
self.snippets.add('subproject')
def detect_voidp_size(self, env): def detect_voidp_size(self, env):
compilers = env.coredata.compilers compilers = env.coredata.compilers
@ -210,5 +267,14 @@ class CmakeModule(ExtensionModule):
return res return res
@FeatureNew('subproject', '0.51.0')
@permittedKwargs({'cmake_options'})
@stringArgs
def subproject(self, interpreter, state, args, kwargs):
if len(args) != 1:
raise InterpreterException('Subproject takes exactly one argument')
dirname = args[0]
return CMakeSubprojectHolder(interpreter.do_subproject(dirname, 'cmake', kwargs), dirname)
def initialize(*args, **kwargs): def initialize(*args, **kwargs):
return CmakeModule(*args, **kwargs) return CmakeModule(*args, **kwargs)

@ -163,7 +163,7 @@ def download(wrap, repo_dir, options):
return return
try: try:
r = Resolver(os.path.dirname(repo_dir)) r = Resolver(os.path.dirname(repo_dir))
r.resolve(wrap.name) r.resolve(wrap.name, 'meson')
mlog.log(' -> done') mlog.log(' -> done')
except WrapException as e: except WrapException as e:
mlog.log(' ->', mlog.red(str(e))) mlog.log(' ->', mlog.red(str(e)))

@ -111,7 +111,7 @@ class Resolver:
self.subdir_root = subdir_root self.subdir_root = subdir_root
self.cachedir = os.path.join(self.subdir_root, 'packagecache') self.cachedir = os.path.join(self.subdir_root, 'packagecache')
def resolve(self, packagename): def resolve(self, packagename: str, method: str):
self.packagename = packagename self.packagename = packagename
self.directory = packagename self.directory = packagename
# We always have to load the wrap file, if it exists, because it could # We always have to load the wrap file, if it exists, because it could
@ -123,9 +123,15 @@ class Resolver:
raise WrapException('Directory key must be a name and not a path') raise WrapException('Directory key must be a name and not a path')
self.dirname = os.path.join(self.subdir_root, self.directory) self.dirname = os.path.join(self.subdir_root, self.directory)
meson_file = os.path.join(self.dirname, 'meson.build') meson_file = os.path.join(self.dirname, 'meson.build')
cmake_file = os.path.join(self.dirname, 'CMakeLists.txt')
if method not in ['meson', 'cmake']:
raise WrapException('Only the methods "meson" and "cmake" are supported')
# The directory is there and has meson.build? Great, use it. # The directory is there and has meson.build? Great, use it.
if os.path.exists(meson_file): if method == 'meson' and os.path.exists(meson_file):
return self.directory
if method == 'cmake' and os.path.exists(cmake_file):
return self.directory return self.directory
# Check if the subproject is a git submodule # Check if the subproject is a git submodule
@ -153,9 +159,11 @@ class Resolver:
else: else:
raise WrapException('Unknown wrap type {!r}'.format(self.wrap.type)) raise WrapException('Unknown wrap type {!r}'.format(self.wrap.type))
# A meson.build file is required in the directory # A meson.build or CMakeLists.txt file is required in the directory
if not os.path.exists(meson_file): if method == 'meson' and not os.path.exists(meson_file):
raise WrapException('Subproject exists but has no meson.build file') raise WrapException('Subproject exists but has no meson.build file')
if method == 'cmake' and not os.path.exists(cmake_file):
raise WrapException('Subproject exists but has no CMakeLists.txt file')
return self.directory return self.directory

@ -161,6 +161,11 @@ def platform_fix_name(fname, compiler, env):
if not mesonlib.for_cygwin(env.is_cross_build(), env): if not mesonlib.for_cygwin(env.is_cross_build(), env):
return None return None
if fname.startswith('?!cygwin:'):
fname = fname[9:]
if mesonlib.for_cygwin(env.is_cross_build(), env):
return None
if fname.endswith('?so'): if fname.endswith('?so'):
if mesonlib.for_windows(env.is_cross_build(), env) and canonical_compiler == 'msvc': if mesonlib.for_windows(env.is_cross_build(), env) and canonical_compiler == 'msvc':
fname = re.sub(r'lib/([^/]*)\?so$', r'bin/\1.dll', fname) fname = re.sub(r'lib/([^/]*)\?so$', r'bin/\1.dll', fname)
@ -222,6 +227,10 @@ def validate_install(srcdir, installdir, compiler, env):
for fname in found: for fname in found:
if fname not in expected: if fname not in expected:
ret_msg += 'Extra file {0} found.\n'.format(fname) ret_msg += 'Extra file {0} found.\n'.format(fname)
if ret_msg != '':
ret_msg += '\nInstall dir contents:\n'
for i in found:
ret_msg += ' - {}'.format(i)
return ret_msg return ret_msg
def log_text_file(logfile, testdir, stdo, stde): def log_text_file(logfile, testdir, stdo, stde):
@ -553,6 +562,7 @@ def skip_csharp(backend):
def detect_tests_to_run(): def detect_tests_to_run():
# Name, subdirectory, skip condition. # Name, subdirectory, skip condition.
all_tests = [ all_tests = [
('cmake', 'cmake', not shutil.which('cmake') or (os.environ.get('compiler') == 'msvc2015' and under_ci)),
('common', 'common', False), ('common', 'common', False),
('warning-meson', 'warning', False), ('warning-meson', 'warning', False),
('failing-meson', 'failing', False), ('failing-meson', 'failing', False),
@ -664,6 +674,12 @@ def _run_tests(all_tests, log_name_base, failfast, extra_args):
# print the meson log if available since it's a superset # print the meson log if available since it's a superset
# of stdout and often has very useful information. # of stdout and often has very useful information.
failing_logs.append(result.mlog) failing_logs.append(result.mlog)
elif under_ci:
# Always print the complete meson log when running in
# a CI. This helps debugging issues that only occur in
# a hard to reproduce environment
failing_logs.append(result.mlog)
failing_logs.append(result.stdo)
else: else:
failing_logs.append(result.stdo) failing_logs.append(result.stdo)
failing_logs.append(result.stde) failing_logs.append(result.stde)

@ -30,6 +30,7 @@ entries = {'console_scripts': ['meson=mesonbuild.mesonmain:main']}
packages = ['mesonbuild', packages = ['mesonbuild',
'mesonbuild.ast', 'mesonbuild.ast',
'mesonbuild.backend', 'mesonbuild.backend',
'mesonbuild.cmake',
'mesonbuild.compilers', 'mesonbuild.compilers',
'mesonbuild.dependencies', 'mesonbuild.dependencies',
'mesonbuild.modules', 'mesonbuild.modules',

@ -0,0 +1,10 @@
#include <iostream>
#include <cmMod.hpp>
using namespace std;
int main() {
cmModClass obj("Hello");
cout << obj.getStr() << endl;
return 0;
}

@ -0,0 +1,12 @@
project('cmakeSubTest', ['c', 'cpp'])
cm = import('cmake')
sub_pro = cm.subproject('cmMod')
sub_dep = sub_pro.dependency('cmModLib')
assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target')
assert(sub_pro.target_type('cmModLib') == 'shared_library', 'Target type should be shared_library')
exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep])
test('test1', exe1)

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.5)
project(cmMod)
set (CMAKE_CXX_STANDARD 14)
add_definitions("-DDO_NOTHING_JUST_A_FLAG=1")
add_library(cmModLib SHARED cmMod.cpp)
include(GenerateExportHeader)
generate_export_header(cmModLib)

@ -0,0 +1,11 @@
#include "cmMod.hpp"
using namespace std;
cmModClass::cmModClass(string foo) {
str = foo + " World";
}
string cmModClass::getStr() const {
return str;
}

@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "cmmodlib_export.h"
class CMMODLIB_EXPORT cmModClass {
private:
std::string str;
public:
cmModClass(std::string foo);
std::string getStr() const;
};

@ -0,0 +1,4 @@
usr/?lib/libcmModLib?so
?cygwin:usr/lib/libcmModLib?implib
?!cygwin:usr/bin/libcmModLib?implib
usr/bin/testEXE?exe

@ -0,0 +1,15 @@
#include <iostream>
#include <cmMod.hpp>
#include "config.h"
#if CONFIG_OPT != 42
#error "Invalid value of CONFIG_OPT"
#endif
using namespace std;
int main() {
cmModClass obj("Hello");
cout << obj.getStr() << endl;
return 0;
}

@ -0,0 +1,20 @@
project('cmakeSubTest_advanced', ['c', 'cpp'])
dep_test = dependency('ZLIB', method: 'cmake', required: false)
if not dep_test.found()
error('MESON_SKIP_TEST: zlib is not installed')
endif
cm = import('cmake')
# Test the "normal" subproject call
sub_pro = cm.subproject('cmMod')
sub_dep = sub_pro.dependency('cmModLib')
# Build some files
exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep])
test('test1', exe1)
# Test if we can also extract executables
assert(sub_pro.target_type('testEXE') == 'executable', 'The type must be executable for obvious reasons')
test('test2', sub_pro.target('testEXE'))

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5)
project(cmMod)
set(CMAKE_CXX_STANDARD 14)
find_package(ZLIB REQUIRED)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib)
set(CONFIG_OPT 42)
configure_file("config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
add_library(cmModLib SHARED lib/cmMod.cpp)
include(GenerateExportHeader)
generate_export_header(cmModLib)
add_executable(testEXE main.cpp)
target_link_libraries(cmModLib ZLIB::ZLIB)
target_link_libraries(testEXE cmModLib)
install(TARGETS cmModLib testEXE LIBRARY DESTINATION lib RUNTIME DESTINATION bin)

@ -0,0 +1,3 @@
#pragma once
#define CONFIG_OPT @CONFIG_OPT@

@ -0,0 +1,17 @@
#include "cmMod.hpp"
#include <zlib.h>
#include "config.h"
#if CONFIG_OPT != 42
#error "Invalid value of CONFIG_OPT"
#endif
using namespace std;
cmModClass::cmModClass(string foo) {
str = foo + " World " + zlibVersion();
}
string cmModClass::getStr() const {
return str;
}

@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "cmmodlib_export.h"
class CMMODLIB_EXPORT cmModClass {
private:
std::string str;
public:
cmModClass(std::string foo);
std::string getStr() const;
};

@ -0,0 +1,11 @@
#include <iostream>
#include <zlib.h>
#include "lib/cmMod.hpp"
using namespace std;
int main() {
cmModClass obj("Hello (LIB TEST)");
cout << obj.getStr() << " ZLIB: " << zlibVersion() << endl;
return 0;
}

@ -0,0 +1,6 @@
usr/?lib/libcmModLib?so
?cygwin:usr/lib/libcmModLib?implib
?!cygwin:usr/bin/libcmModLib?implib
?msvc:usr/bin/cmModLib.pdb
?msvc:usr/bin/testEXE.pdb
usr/bin/testEXE?exe

@ -0,0 +1,15 @@
#include <iostream>
#include <cmMod.hpp>
#include "config.h"
#if CONFIG_OPT != 42
#error "Invalid value of CONFIG_OPT"
#endif
using namespace std;
int main() {
cmModClass obj("Hello");
cout << obj.getStr() << endl;
return 0;
}

@ -0,0 +1,15 @@
project('cmakeSubTest_advanced', ['c', 'cpp'])
cm = import('cmake')
# Test the "normal" subproject call
sub_pro = cm.subproject('cmMod')
sub_dep = sub_pro.dependency('cmModLib')
# Build some files
exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep])
test('test1', exe1)
# Test if we can also extract executables
assert(sub_pro.target_type('testEXE') == 'executable', 'The type must be executable for obvious reasons')
test('test2', sub_pro.target('testEXE'))

@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.5)
project(cmMod)
set(CMAKE_CXX_STANDARD 14)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib)
set(CONFIG_OPT 42)
configure_file("config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
add_library(cmModLib SHARED lib/cmMod.cpp)
include(GenerateExportHeader)
generate_export_header(cmModLib)
add_executable(testEXE main.cpp)
target_link_libraries(testEXE cmModLib)
install(TARGETS cmModLib testEXE LIBRARY DESTINATION lib RUNTIME DESTINATION bin)

@ -0,0 +1,3 @@
#pragma once
#define CONFIG_OPT @CONFIG_OPT@

@ -0,0 +1,16 @@
#include "cmMod.hpp"
#include "config.h"
#if CONFIG_OPT != 42
#error "Invalid value of CONFIG_OPT"
#endif
using namespace std;
cmModClass::cmModClass(string foo) {
str = foo + " World";
}
string cmModClass::getStr() const {
return str;
}

@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "cmmodlib_export.h"
class CMMODLIB_EXPORT cmModClass {
private:
std::string str;
public:
cmModClass(std::string foo);
std::string getStr() const;
};

@ -0,0 +1,10 @@
#include <iostream>
#include "lib/cmMod.hpp"
using namespace std;
int main() {
cmModClass obj("Hello (LIB TEST)");
cout << obj.getStr() << endl;
return 0;
}

@ -0,0 +1,8 @@
#include <iostream>
#include "test.hpp"
using namespace std;
int main() {
cout << getStr() << endl;
}

@ -0,0 +1,20 @@
project('cmake_code_gen', ['c', 'cpp'])
cm = import('cmake')
# Subproject with the "code generator"
sub_pro = cm.subproject('cmCodeGen')
sub_exe = sub_pro.target('genA')
# Generate the source
generated = custom_target(
'cmake-generated',
input: [],
output: ['test.cpp'],
command: [sub_exe, '@OUTPUT@']
)
# Build the exe
exe1 = executable('main1', ['main.cpp', generated])
test('test1', exe1)

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.7)
set(CMAKE_CXX_STANDARD 14)
add_executable(genA main.cpp)

@ -0,0 +1,21 @@
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, const char *argv[]) {
if(argc < 2) {
cerr << argv[0] << " requires an output file!" << endl;
return 1;
}
ofstream out(argv[1]);
out << R"(
#include "test.hpp"
std::string getStr() {
return "Hello World";
}
)";
return 0;
}

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getStr();

@ -0,0 +1,9 @@
#include <iostream>
#include "libA.hpp"
#include "libB.hpp"
using namespace std;
int main() {
cout << getLibStr() << " -- " << getZlibVers() << endl;
}

@ -0,0 +1,25 @@
project('cmake_object_lib_test', ['c', 'cpp'])
dep_test = dependency('ZLIB', method: 'cmake', required: false)
if not dep_test.found()
error('MESON_SKIP_TEST: zlib is not installed')
endif
if build_machine.system() == 'windows'
error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems')
endif
cm = import('cmake')
sub_pro = cm.subproject('cmObjLib')
sub_sha = sub_pro.dependency('lib_sha')
sub_sta = sub_pro.dependency('lib_sta')
# Required for the static library
zlib_dep = dependency('zlib')
exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha])
exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta, zlib_dep])
test('test1', exe_sha)
test('test1', exe_sta)

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.7)
find_package(ZLIB REQUIRED)
add_library(lib_obj OBJECT libA.cpp libB.cpp)
add_library(lib_sha SHARED $<TARGET_OBJECTS:lib_obj>)
add_library(lib_sta STATIC $<TARGET_OBJECTS:lib_obj>)
target_link_libraries(lib_sha ZLIB::ZLIB)
target_link_libraries(lib_sta ZLIB::ZLIB)

@ -0,0 +1,5 @@
#include "libA.hpp"
std::string getLibStr() {
return "Hello World";
}

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getLibStr();

@ -0,0 +1,6 @@
#include "libB.hpp"
#include <zlib.h>
std::string getZlibVers() {
return zlibVersion();
}

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getZlibVers();

@ -0,0 +1,9 @@
#include <iostream>
#include "libA.hpp"
#include "libB.hpp"
using namespace std;
int main() {
cout << getLibStr() << " -- " << getZlibVers() << endl;
}

@ -0,0 +1,17 @@
project('cmake_object_lib_test', ['c', 'cpp'])
if build_machine.system() == 'windows'
error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems')
endif
cm = import('cmake')
sub_pro = cm.subproject('cmObjLib')
sub_sha = sub_pro.dependency('lib_sha')
sub_sta = sub_pro.dependency('lib_sta')
exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha])
exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta])
test('test1', exe_sha)
test('test1', exe_sta)

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.7)
add_library(lib_obj OBJECT libA.cpp libB.cpp)
add_library(lib_sha SHARED $<TARGET_OBJECTS:lib_obj>)
add_library(lib_sta STATIC $<TARGET_OBJECTS:lib_obj>)

@ -0,0 +1,5 @@
#include "libA.hpp"
std::string getLibStr() {
return "Hello World";
}

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getLibStr();

@ -0,0 +1,5 @@
#include "libB.hpp"
std::string getZlibVers() {
return "STUB";
}

@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getZlibVers();

@ -0,0 +1,3 @@
project('cmake_set_opt', ['c', 'cpp'])
import('cmake').subproject('cmOpts', cmake_options: '-DSOME_CMAKE_VAR=something')

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.7)
if(NOT "${SOME_CMAKE_VAR}" STREQUAL "something")
message(FATAL_ERROR "Setting the CMake var failed")
endif()
Loading…
Cancel
Save